@pecb-ui/components 1.1.9 → 1.1.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,14 +1,16 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, inject, input, output, HostBinding, Component, ChangeDetectionStrategy, model, computed, signal, HostListener, ContentChild, effect, forwardRef, ViewChild, viewChild, Directive, untracked, ElementRef, ApplicationRef, EnvironmentInjector, createComponent, contentChildren, ViewEncapsulation, PLATFORM_ID, Inject, ViewChildren, Renderer2, NgZone } from '@angular/core';
2
+ import { Injectable, inject, input, output, HostBinding, Component, ChangeDetectionStrategy, model, computed, signal, HostListener, ContentChild, effect, forwardRef, ViewChild, ViewContainerRef, viewChild, ViewEncapsulation, Directive, untracked, ElementRef, ApplicationRef, EnvironmentInjector, createComponent, contentChildren, PLATFORM_ID, NgZone, DestroyRef, booleanAttribute, numberAttribute, afterNextRender, Injector, Inject, ViewChildren, Renderer2 } from '@angular/core';
3
3
  import * as i1 from '@angular/common';
4
- import { CommonModule, NgOptimizedImage, isPlatformBrowser } from '@angular/common';
4
+ import { CommonModule, NgOptimizedImage, isPlatformBrowser, NgTemplateOutlet } from '@angular/common';
5
5
  import * as i1$1 from '@angular/platform-browser';
6
6
  import { DomSanitizer } from '@angular/platform-browser';
7
7
  import * as i2 from '@angular/cdk/overlay';
8
8
  import { Overlay, OverlayModule } from '@angular/cdk/overlay';
9
9
  import * as i1$2 from '@angular/forms';
10
10
  import { NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms';
11
- import { Subject, BehaviorSubject } from 'rxjs';
11
+ import { CdkTrapFocus } from '@angular/cdk/a11y';
12
+ import { TemplatePortal } from '@angular/cdk/portal';
13
+ import { Subscription, Subject, BehaviorSubject } from 'rxjs';
12
14
  import { map } from 'rxjs/operators';
13
15
 
14
16
  /**
@@ -4523,11 +4525,11 @@ class TableComponent {
4523
4525
  this.selectionChange.emit({ selectedRows, selectedIndices: this.selectedIndices() });
4524
4526
  }
4525
4527
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: TableComponent, deps: [{ token: i1$1.DomSanitizer }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
4526
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.16", type: TableComponent, isStandalone: true, selector: "pecb-table", inputs: { columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, showHeader: { classPropertyName: "showHeader", publicName: "showHeader", isSignal: true, isRequired: false, transformFunction: null }, hoverable: { classPropertyName: "hoverable", publicName: "hoverable", isSignal: true, isRequired: false, transformFunction: null }, showPagination: { classPropertyName: "showPagination", publicName: "showPagination", isSignal: true, isRequired: false, transformFunction: null }, totalItems: { classPropertyName: "totalItems", publicName: "totalItems", isSignal: true, isRequired: false, transformFunction: null }, currentPage: { classPropertyName: "currentPage", publicName: "currentPage", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, pageSizeOptions: { classPropertyName: "pageSizeOptions", publicName: "pageSizeOptions", isSignal: true, isRequired: false, transformFunction: null }, showPageSizeSelector: { classPropertyName: "showPageSizeSelector", publicName: "showPageSizeSelector", isSignal: true, isRequired: false, transformFunction: null }, paginationVariant: { classPropertyName: "paginationVariant", publicName: "paginationVariant", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, selectedIndices: { classPropertyName: "selectedIndices", publicName: "selectedIndices", isSignal: true, isRequired: false, transformFunction: null }, stickyHeader: { classPropertyName: "stickyHeader", publicName: "stickyHeader", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, minWidth: { classPropertyName: "minWidth", publicName: "minWidth", isSignal: true, isRequired: false, transformFunction: null }, wrapText: { classPropertyName: "wrapText", publicName: "wrapText", isSignal: true, isRequired: false, transformFunction: null }, sortColumn: { classPropertyName: "sortColumn", publicName: "sortColumn", isSignal: true, isRequired: false, transformFunction: null }, sortDirection: { classPropertyName: "sortDirection", publicName: "sortDirection", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, loadingText: { classPropertyName: "loadingText", publicName: "loadingText", isSignal: true, isRequired: false, transformFunction: null }, selectAllAriaLabel: { classPropertyName: "selectAllAriaLabel", publicName: "selectAllAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, selectRowAriaLabel: { classPropertyName: "selectRowAriaLabel", publicName: "selectRowAriaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { currentPage: "currentPageChange", pageSize: "pageSizeChange", selectedIndices: "selectedIndicesChange", sortColumn: "sortColumnChange", sortDirection: "sortDirectionChange", pageChange: "pageChange", rowClick: "rowClick", rowAction: "rowAction", sortChange: "sortChange", selectionChange: "selectionChange" }, queries: [{ propertyName: "emptyTemplate", first: true, predicate: ["emptyTemplate"], descendants: true }, { propertyName: "loadingTemplate", first: true, predicate: ["loadingTemplate"], descendants: true }], ngImport: i0, template: "<div class=\"pecb-table-wrapper\">\n <div [class]=\"containerClasses\" [style.maxHeight]=\"maxHeight()\">\n <!-- Loading State -->\n <div *ngIf=\"loading()\" class=\"pecb-table__loading\">\n <ng-container *ngIf=\"loadingTemplate; else defaultLoading\">\n <ng-container *ngTemplateOutlet=\"loadingTemplate\"></ng-container>\n </ng-container>\n <ng-template #defaultLoading>\n <div class=\"pecb-table__loading-spinner\">\n <svg class=\"pecb-table__spinner-icon\" viewBox=\"0 0 24 24\" fill=\"none\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-dasharray=\"31.4 31.4\" />\n </svg>\n <span>{{ loadingText() }}</span>\n </div>\n </ng-template>\n </div>\n\n <!-- Table -->\n <table\n *ngIf=\"!loading()\"\n [class]=\"tableClasses\"\n [style.min-width]=\"minWidth()\"\n [attr.aria-label]=\"ariaLabel()\"\n role=\"table\">\n\n <!-- Table Header -->\n <thead *ngIf=\"showHeader()\" class=\"pecb-table__head\" [class.pecb-table__head--sticky]=\"stickyHeader()\">\n <tr class=\"pecb-table__row pecb-table__row--header\" role=\"row\">\n <!-- Selection Checkbox Column -->\n <th\n *ngIf=\"selectable()\"\n class=\"pecb-table__cell pecb-table__cell--header pecb-table__cell--checkbox\"\n role=\"columnheader\"\n scope=\"col\">\n <label class=\"pecb-table__checkbox-wrapper\">\n <input\n type=\"checkbox\"\n class=\"pecb-table__checkbox\"\n [checked]=\"isAllSelected()\"\n [indeterminate]=\"isSomeSelected()\"\n (change)=\"toggleAllSelection()\"\n [attr.aria-label]=\"selectAllAriaLabel()\" />\n <span class=\"pecb-table__checkbox-custom\"></span>\n </label>\n </th>\n\n <!-- Data Columns -->\n <th\n *ngFor=\"let column of columns(); trackBy: trackByKey\"\n class=\"pecb-table__cell pecb-table__cell--header\"\n [class.pecb-table__cell--sortable]=\"column.sortable\"\n [class]=\"getAlignmentClass(column)\"\n [ngClass]=\"column.cssClass\"\n [style.width]=\"column.width\"\n [attr.aria-sort]=\"column.sortable ? (sortColumn() === column.key ? (sortDirection() === 'asc' ? 'ascending' : 'descending') : 'none') : null\"\n role=\"columnheader\"\n scope=\"col\"\n (click)=\"onHeaderClick(column)\"\n (keydown.enter)=\"onHeaderClick(column)\"\n (keydown.space)=\"onHeaderClick(column); $event.preventDefault()\"\n [attr.tabindex]=\"column.sortable ? 0 : null\">\n <div class=\"pecb-table__header-content\">\n <ng-container *ngIf=\"column.headerTemplate; else defaultHeader\">\n <ng-container *ngTemplateOutlet=\"column.headerTemplate; context: { $implicit: column }\"></ng-container>\n </ng-container>\n <ng-template #defaultHeader>\n <span class=\"pecb-table__header-text\">{{ column.label }}</span>\n </ng-template>\n\n <!-- Sort Icon -->\n <span *ngIf=\"column.sortable\" class=\"pecb-table__sort-icon\" [attr.data-sort]=\"getSortState(column)\">\n <svg *ngIf=\"getSortState(column) === 'none'\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n <path d=\"M8 3L12 7H4L8 3Z\" fill=\"currentColor\" opacity=\"0.4\"/>\n <path d=\"M8 13L4 9H12L8 13Z\" fill=\"currentColor\" opacity=\"0.4\"/>\n </svg>\n <svg *ngIf=\"getSortState(column) === 'asc'\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n <path d=\"M8 3L12 7H4L8 3Z\" fill=\"currentColor\"/>\n <path d=\"M8 13L4 9H12L8 13Z\" fill=\"currentColor\" opacity=\"0.4\"/>\n </svg>\n <svg *ngIf=\"getSortState(column) === 'desc'\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n <path d=\"M8 3L12 7H4L8 3Z\" fill=\"currentColor\" opacity=\"0.4\"/>\n <path d=\"M8 13L4 9H12L8 13Z\" fill=\"currentColor\"/>\n </svg>\n </span>\n </div>\n </th>\n </tr>\n </thead>\n\n <!-- Table Body -->\n <tbody class=\"pecb-table__body\">\n <!-- Empty State -->\n <tr *ngIf=\"data().length === 0\" class=\"pecb-table__row pecb-table__row--empty\">\n <td\n [attr.colspan]=\"selectable() ? columns().length + 1 : columns().length\"\n class=\"pecb-table__cell pecb-table__cell--empty\">\n <ng-container *ngIf=\"emptyTemplate; else defaultEmpty\">\n <ng-container *ngTemplateOutlet=\"emptyTemplate\"></ng-container>\n </ng-container>\n <ng-template #defaultEmpty>\n <div class=\"pecb-table__empty-content\">\n <span class=\"pecb-table__empty-message\">{{ emptyMessage() }}</span>\n </div>\n </ng-template>\n </td>\n </tr>\n\n <!-- Data Rows -->\n <tr\n *ngFor=\"let row of data(); let i = index; trackBy: trackByIndex\"\n class=\"pecb-table__row pecb-table__row--data\"\n [class.pecb-table__row--selected]=\"isRowSelected(i)\"\n role=\"row\"\n (click)=\"onRowClick(row, i)\"\n [attr.tabindex]=\"0\"\n (keydown.enter)=\"onRowClick(row, i)\">\n\n <!-- Selection Checkbox -->\n <td\n *ngIf=\"selectable()\"\n class=\"pecb-table__cell pecb-table__cell--checkbox\"\n role=\"cell\"\n (click)=\"$event.stopPropagation()\">\n <label class=\"pecb-table__checkbox-wrapper\">\n <input\n type=\"checkbox\"\n class=\"pecb-table__checkbox\"\n [checked]=\"isRowSelected(i)\"\n (change)=\"toggleRowSelection(i)\"\n [attr.aria-label]=\"selectRowAriaLabel() + ' ' + (i + 1)\" />\n <span class=\"pecb-table__checkbox-custom\"></span>\n </label>\n </td>\n\n <!-- Data Cells -->\n <td\n *ngFor=\"let column of columns(); trackBy: trackByKey\"\n class=\"pecb-table__cell\"\n [class]=\"getAlignmentClass(column)\"\n [ngClass]=\"column.cssClass\"\n [class.pecb-table__cell--wrap]=\"isCellWrapped(column)\"\n [class.pecb-table__cell--truncate]=\"!isCellWrapped(column)\"\n [style.max-width]=\"column.maxWidth || column.width\"\n [attr.title]=\"getCellTitle(row, column)\"\n role=\"cell\">\n\n <!-- Custom Template -->\n <ng-container *ngIf=\"column.cellTemplate\">\n <ng-container *ngTemplateOutlet=\"column.cellTemplate; context: { $implicit: getCellValue(row, column.key), row: row, column: column, index: i }\"></ng-container>\n </ng-container>\n\n <!-- Dynamic Component - renders any component passed via dynamicComponent -->\n <ng-container *ngIf=\"!column.cellTemplate && hasDynamicComponent(column)\">\n <ng-container\n *ngComponentOutlet=\"column.dynamicComponent!; inputs: getDynamicComponentInputs(row, column, i)\">\n </ng-container>\n </ng-container>\n\n <!-- Status Component - renders pecb-status -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'status'\">\n <pecb-status\n [type]=\"getStatusType(getCellValue(row, column.key), column)\"\n [variant]=\"getStatusVariant(column)\"\n [size]=\"getStatusSize(column)\">\n {{ getCellValue(row, column.key) }}\n </pecb-status>\n </ng-container>\n\n <!-- User Component - renders avatar + name + email -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'user'\">\n <div class=\"pecb-table__user-cell\">\n <img\n *ngIf=\"getUserData(row, column).avatar\"\n [src]=\"getUserData(row, column).avatar\"\n alt=\"\"\n class=\"pecb-table__user-avatar\">\n <div class=\"pecb-table__user-info\">\n <div class=\"pecb-table__user-name\">{{ getUserData(row, column).name }}</div>\n <div *ngIf=\"getUserData(row, column).secondary\" class=\"pecb-table__user-secondary\">\n {{ getUserData(row, column).secondary }}\n </div>\n </div>\n </div>\n </ng-container>\n\n <!-- Action Component - renders action buttons with icons -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'action'\">\n <div class=\"pecb-table__action-buttons\">\n <ng-container *ngFor=\"let action of getActions(column)\">\n <!-- Divider before this action (consumer-controlled grouping) -->\n <span *ngIf=\"action.dividerBefore\" class=\"pecb-table__action-divider\"></span>\n <button\n class=\"pecb-table__action-btn\"\n [class.pecb-table__action-btn--danger]=\"action.variant === 'danger'\"\n [title]=\"action.tooltip || action.id\"\n [disabled]=\"action.disabled\"\n (click)=\"onActionClick(action, row, i, $event)\">\n <!-- View Icon -->\n <svg *ngIf=\"action.icon === 'view'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z\"></path>\n <circle cx=\"12\" cy=\"12\" r=\"3\"></circle>\n </svg>\n <!-- Edit Icon -->\n <svg *ngIf=\"action.icon === 'edit'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\"></path>\n <path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\"></path>\n </svg>\n <!-- Delete Icon -->\n <svg *ngIf=\"action.icon === 'delete'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"3 6 5 6 21 6\"></polyline>\n <path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"></path>\n </svg>\n <!-- Download Icon -->\n <svg *ngIf=\"action.icon === 'download'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"></path>\n <polyline points=\"7 10 12 15 17 10\"></polyline>\n <line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\"></line>\n </svg>\n <!-- Copy Icon -->\n <svg *ngIf=\"action.icon === 'copy'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\"></rect>\n <path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"></path>\n </svg>\n <!-- Custom Icon (SVG string) -->\n <span *ngIf=\"action.icon === 'custom' && action.customIcon\" class=\"pecb-table__custom-icon\" [innerHTML]=\"getSafeIcon(action.customIcon)\"></span>\n </button>\n </ng-container>\n </div>\n </ng-container>\n\n <!-- Link Cell -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'link'\">\n <a\n [href]=\"getLinkHref(getCellValue(row, column.key))\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"pecb-table__link\"\n (click)=\"$event.stopPropagation()\">\n {{ getCellValue(row, column.key) }}\n </a>\n </ng-container>\n\n <!-- Default Text -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'text'\">\n {{ getCellValue(row, column.key) }}\n </ng-container>\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <!-- Pagination (outside table container) -->\n <div *ngIf=\"showPagination() && !loading() && data().length > 0\" [class]=\"paginationClasses\">\n <pecb-pagination\n [totalItems]=\"totalItems()\"\n [pageSize]=\"pageSize()\"\n [currentPage]=\"currentPage()\"\n [pageSizeOptions]=\"pageSizeOptions()\"\n [showPageSizeSelector]=\"showPageSizeSelector()\"\n [variant]=\"paginationVariant()\"\n size=\"md\"\n (pageChange)=\"onPageChange($event)\"\n (pageSizeChange)=\"onPageSizeChange($event)\">\n </pecb-pagination>\n </div>\n</div>\n", styles: [".pecb-table-wrapper{width:100%}.pecb-table-container{width:100%;background-color:#fff;border-radius:12px;overflow-x:auto;overflow-y:hidden;border:1px solid #ececec}.pecb-table-container--sticky{position:relative;overflow:auto}.pecb-table{width:100%;border-collapse:collapse;border-spacing:0;font-family:inherit;font-size:14px;color:#313131}.pecb-table--sm{font-size:12px}.pecb-table--sm .pecb-table__cell,.pecb-table--sm .pecb-table__cell--header{padding:8px 12px}.pecb-table--md{font-size:14px}.pecb-table--md .pecb-table__cell{padding:16px 18px}.pecb-table--md .pecb-table__cell--header{padding:12px 18px}.pecb-table--lg{font-size:16px}.pecb-table--lg .pecb-table__cell{padding:18px 20px}.pecb-table--lg .pecb-table__cell--header{padding:16px 20px}.pecb-table--default .pecb-table__row--data{border-bottom:1px solid #ececec}.pecb-table--default .pecb-table__row--data:last-child{border-bottom:none}.pecb-table--default .pecb-table__row--data:last-child .pecb-table__cell{border-bottom:none}.pecb-table--default .pecb-table__row--data:nth-child(2n){background-color:#fafafa}.pecb-table--striped .pecb-table__row--data:nth-child(2n){background-color:#fafafa}.pecb-table--bordered .pecb-table__cell{border:1px solid #f7f5f5}.pecb-table--hoverable .pecb-table__row--data{transition:background-color .15s ease-in-out}.pecb-table--selectable .pecb-table__row--data{cursor:pointer}.pecb-table__head{background-color:#212427}.pecb-table__head--sticky{position:sticky;top:0;z-index:1020}.pecb-table__row--header{text-transform:uppercase;height:52px}.pecb-table__row--data{background-color:#fff;height:58px}.pecb-table__row--data:focus,.pecb-table__row--data:focus-visible{outline:none}.pecb-table__row--selected{background-color:#f4f4f4}.pecb-table__row--selected:hover{background-color:#f7f5f5}.pecb-table__row--empty{background-color:#fff}.pecb-table__cell{text-align:left;vertical-align:middle;white-space:nowrap;border-bottom:1px solid #ececec}.pecb-table__cell--header{font-weight:500;font-size:12px;color:#fff;letter-spacing:.02em;-webkit-user-select:none;user-select:none;border-bottom:none}.pecb-table__cell--sortable{cursor:pointer;transition:background-color .15s ease-in-out}.pecb-table__cell--sortable:hover{background-color:#ffffff1a}.pecb-table__cell--sortable:focus,.pecb-table__cell--sortable:focus-visible{outline:none;box-shadow:inset 0 0 0 2px #fff}.pecb-table__cell--checkbox{width:48px;text-align:center}.pecb-table__cell--center{text-align:center}.pecb-table__cell--right{text-align:right}.pecb-table__cell--empty{text-align:center;padding:24px 0}.pecb-table__cell--truncate{overflow:hidden;text-overflow:ellipsis}.pecb-table__cell--wrap{white-space:normal;overflow-wrap:anywhere;word-break:break-word}.pecb-table__header-content{display:flex;align-items:center;gap:8px}.pecb-table__header-text{flex:1}.pecb-table__sort-icon{display:flex;align-items:center;justify-content:center;color:#ffffff80;transition:color .15s ease-in-out}.pecb-table__sort-icon svg{width:16px;height:16px}.pecb-table__sort-icon[data-sort=asc],.pecb-table__sort-icon[data-sort=desc]{color:#fff}.pecb-table__checkbox-wrapper{display:inline-flex;align-items:center;justify-content:center;position:relative;cursor:pointer}.pecb-table__checkbox{position:absolute;opacity:0;width:0;height:0}.pecb-table__checkbox:checked+.pecb-table__checkbox-custom{background-color:#212427;border-color:#212427}.pecb-table__checkbox:checked+.pecb-table__checkbox-custom:after{display:block}.pecb-table__checkbox:indeterminate+.pecb-table__checkbox-custom{background-color:#212427;border-color:#212427}.pecb-table__checkbox:indeterminate+.pecb-table__checkbox-custom:before{display:block}.pecb-table__checkbox:indeterminate+.pecb-table__checkbox-custom:after{display:none}.pecb-table__checkbox:focus+.pecb-table__checkbox-custom{box-shadow:0 0 0 2px #21242733}.pecb-table__checkbox-custom{width:18px;height:18px;border:1.5px solid #c2c2c2;border-radius:6px;background-color:#fff;position:relative;transition:all .15s ease-in-out}.pecb-table__checkbox-custom:after{content:\"\";display:none;position:absolute;left:5px;top:2px;width:4px;height:8px;border:solid #ffffff;border-width:0 2px 2px 0;transform:rotate(45deg)}.pecb-table__checkbox-custom:before{content:\"\";display:none;position:absolute;left:3px;top:6px;width:8px;height:2px;background-color:#fff}.pecb-table__empty-content{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:16px 0}.pecb-table__empty-message{color:#919191;font-size:14px}.pecb-table__loading{display:flex;align-items:center;justify-content:center;padding:48px;min-height:200px}.pecb-table__loading-spinner{display:flex;flex-direction:column;align-items:center;gap:12px;color:#616161;font-size:14px;font-family:inherit;font-weight:400}.pecb-table__spinner-icon{width:32px;height:32px;animation:spin 1s linear infinite;color:#212427}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.pecb-table__pagination{padding:16px 0;margin-top:16px}.pecb-table__pagination--bordered{padding:16px 18px;background-color:#fff;border:1px solid #ececec;border-radius:6px}.pecb-table__user-cell{display:flex;align-items:center;gap:12px}.pecb-table__user-avatar{width:40px;height:40px;border-radius:9999px;object-fit:cover;flex-shrink:0}.pecb-table__user-info{display:flex;flex-direction:column;min-width:0}.pecb-table__user-name{font-weight:500;color:#313131;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pecb-table__user-secondary{font-size:12px;color:#919191;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pecb-table__action-buttons{display:flex;align-items:center;gap:0;justify-content:center}.pecb-table__action-divider{display:block;width:1px;height:16px;background-color:#c2c2c2;flex-shrink:0}.pecb-table__action-btn{display:flex;align-items:center;justify-content:center;width:30px;height:30px;border:none;background:transparent;color:#212427;cursor:pointer;border-radius:6px;transition:all .15s ease-in-out;padding:0}.pecb-table__action-btn:hover{background-color:#f7f5f5;color:#313131}.pecb-table__action-btn:focus{outline:none;box-shadow:0 0 0 2px #21242733}.pecb-table__action-btn:disabled{opacity:.5;cursor:not-allowed}.pecb-table__action-btn--danger:hover{background-color:#fdecea;color:#dc3545}.pecb-table__action-btn svg{width:18px;height:18px}.pecb-table__custom-icon{display:flex;align-items:center;justify-content:center}.pecb-table__custom-icon svg{width:18px;height:18px}.pecb-table__link{color:#212427;text-decoration:none;font-size:inherit}.pecb-table__link:hover{text-decoration:underline;color:#313131}.pecb-table__link:focus-visible{outline:2px solid #212427;outline-offset:2px;border-radius:2px}@media (min-width: 768px){.pecb-table--md,.pecb-table--lg{font-size:14px}.pecb-table--md .pecb-table__cell,.pecb-table--lg .pecb-table__cell{padding:12px 16px}.pecb-table--md .pecb-table__cell--header,.pecb-table--lg .pecb-table__cell--header{padding:8px 16px}.pecb-table__pagination{padding:12px 16px}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: PaginationComponent, selector: "pecb-pagination", inputs: ["totalItems", "pageSize", "currentPage", "pageSizeOptions", "size", "variant", "showPageSizeSelector", "showItemsInfo", "maxVisiblePages", "disabled", "aria-label", "previousPageAriaLabel", "nextPageAriaLabel", "pageNavigationAriaLabel", "pageAriaLabel", "pageSizeListboxAriaLabel", "itemsPerPageLabel", "itemsInfoTemplate", "itemsInfoCompactTemplate"], outputs: ["pageSizeChange", "currentPageChange", "pageChange"] }, { kind: "component", type: StatusComponent, selector: "pecb-status", inputs: ["type", "variant", "size", "ariaLabel", "text", "customClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4528
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.16", type: TableComponent, isStandalone: true, selector: "pecb-table", inputs: { columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, showHeader: { classPropertyName: "showHeader", publicName: "showHeader", isSignal: true, isRequired: false, transformFunction: null }, hoverable: { classPropertyName: "hoverable", publicName: "hoverable", isSignal: true, isRequired: false, transformFunction: null }, showPagination: { classPropertyName: "showPagination", publicName: "showPagination", isSignal: true, isRequired: false, transformFunction: null }, totalItems: { classPropertyName: "totalItems", publicName: "totalItems", isSignal: true, isRequired: false, transformFunction: null }, currentPage: { classPropertyName: "currentPage", publicName: "currentPage", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, pageSizeOptions: { classPropertyName: "pageSizeOptions", publicName: "pageSizeOptions", isSignal: true, isRequired: false, transformFunction: null }, showPageSizeSelector: { classPropertyName: "showPageSizeSelector", publicName: "showPageSizeSelector", isSignal: true, isRequired: false, transformFunction: null }, paginationVariant: { classPropertyName: "paginationVariant", publicName: "paginationVariant", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, selectedIndices: { classPropertyName: "selectedIndices", publicName: "selectedIndices", isSignal: true, isRequired: false, transformFunction: null }, stickyHeader: { classPropertyName: "stickyHeader", publicName: "stickyHeader", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, minWidth: { classPropertyName: "minWidth", publicName: "minWidth", isSignal: true, isRequired: false, transformFunction: null }, wrapText: { classPropertyName: "wrapText", publicName: "wrapText", isSignal: true, isRequired: false, transformFunction: null }, sortColumn: { classPropertyName: "sortColumn", publicName: "sortColumn", isSignal: true, isRequired: false, transformFunction: null }, sortDirection: { classPropertyName: "sortDirection", publicName: "sortDirection", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, loadingText: { classPropertyName: "loadingText", publicName: "loadingText", isSignal: true, isRequired: false, transformFunction: null }, selectAllAriaLabel: { classPropertyName: "selectAllAriaLabel", publicName: "selectAllAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, selectRowAriaLabel: { classPropertyName: "selectRowAriaLabel", publicName: "selectRowAriaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { currentPage: "currentPageChange", pageSize: "pageSizeChange", selectedIndices: "selectedIndicesChange", sortColumn: "sortColumnChange", sortDirection: "sortDirectionChange", pageChange: "pageChange", rowClick: "rowClick", rowAction: "rowAction", sortChange: "sortChange", selectionChange: "selectionChange" }, queries: [{ propertyName: "emptyTemplate", first: true, predicate: ["emptyTemplate"], descendants: true }, { propertyName: "loadingTemplate", first: true, predicate: ["loadingTemplate"], descendants: true }], ngImport: i0, template: "<div class=\"pecb-table-wrapper\">\n <div [class]=\"containerClasses\" [style.maxHeight]=\"maxHeight()\">\n <!-- Loading State -->\n <div *ngIf=\"loading()\" class=\"pecb-table__loading\">\n <ng-container *ngIf=\"loadingTemplate; else defaultLoading\">\n <ng-container *ngTemplateOutlet=\"loadingTemplate\"></ng-container>\n </ng-container>\n <ng-template #defaultLoading>\n <div class=\"pecb-table__loading-spinner\">\n <svg class=\"pecb-table__spinner-icon\" viewBox=\"0 0 24 24\" fill=\"none\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-dasharray=\"31.4 31.4\" />\n </svg>\n <span>{{ loadingText() }}</span>\n </div>\n </ng-template>\n </div>\n\n <!-- Table -->\n <table\n *ngIf=\"!loading()\"\n [class]=\"tableClasses\"\n [style.min-width]=\"minWidth()\"\n [attr.aria-label]=\"ariaLabel()\"\n role=\"table\">\n\n <!-- Table Header -->\n <thead *ngIf=\"showHeader()\" class=\"pecb-table__head\" [class.pecb-table__head--sticky]=\"stickyHeader()\">\n <tr class=\"pecb-table__row pecb-table__row--header\" role=\"row\">\n <!-- Selection Checkbox Column -->\n <th\n *ngIf=\"selectable()\"\n class=\"pecb-table__cell pecb-table__cell--header pecb-table__cell--checkbox\"\n role=\"columnheader\"\n scope=\"col\">\n <label class=\"pecb-table__checkbox-wrapper\">\n <input\n type=\"checkbox\"\n class=\"pecb-table__checkbox\"\n [checked]=\"isAllSelected()\"\n [indeterminate]=\"isSomeSelected()\"\n (change)=\"toggleAllSelection()\"\n [attr.aria-label]=\"selectAllAriaLabel()\" />\n <span class=\"pecb-table__checkbox-custom\"></span>\n </label>\n </th>\n\n <!-- Data Columns -->\n <th\n *ngFor=\"let column of columns(); trackBy: trackByKey\"\n class=\"pecb-table__cell pecb-table__cell--header\"\n [class.pecb-table__cell--sortable]=\"column.sortable\"\n [class]=\"getAlignmentClass(column)\"\n [ngClass]=\"column.cssClass\"\n [style.width]=\"column.width\"\n [attr.aria-sort]=\"column.sortable ? (sortColumn() === column.key ? (sortDirection() === 'asc' ? 'ascending' : 'descending') : 'none') : null\"\n role=\"columnheader\"\n scope=\"col\"\n (click)=\"onHeaderClick(column)\"\n (keydown.enter)=\"onHeaderClick(column)\"\n (keydown.space)=\"onHeaderClick(column); $event.preventDefault()\"\n [attr.tabindex]=\"column.sortable ? 0 : null\">\n <div class=\"pecb-table__header-content\">\n <ng-container *ngIf=\"column.headerTemplate; else defaultHeader\">\n <ng-container *ngTemplateOutlet=\"column.headerTemplate; context: { $implicit: column }\"></ng-container>\n </ng-container>\n <ng-template #defaultHeader>\n <span class=\"pecb-table__header-text\">{{ column.label }}</span>\n </ng-template>\n\n <!-- Sort Icon -->\n <span *ngIf=\"column.sortable\" class=\"pecb-table__sort-icon\" [attr.data-sort]=\"getSortState(column)\">\n <svg *ngIf=\"getSortState(column) === 'none'\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n <path d=\"M8 3L12 7H4L8 3Z\" fill=\"currentColor\" opacity=\"0.4\"/>\n <path d=\"M8 13L4 9H12L8 13Z\" fill=\"currentColor\" opacity=\"0.4\"/>\n </svg>\n <svg *ngIf=\"getSortState(column) === 'asc'\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n <path d=\"M8 3L12 7H4L8 3Z\" fill=\"currentColor\"/>\n <path d=\"M8 13L4 9H12L8 13Z\" fill=\"currentColor\" opacity=\"0.4\"/>\n </svg>\n <svg *ngIf=\"getSortState(column) === 'desc'\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n <path d=\"M8 3L12 7H4L8 3Z\" fill=\"currentColor\" opacity=\"0.4\"/>\n <path d=\"M8 13L4 9H12L8 13Z\" fill=\"currentColor\"/>\n </svg>\n </span>\n </div>\n </th>\n </tr>\n </thead>\n\n <!-- Table Body -->\n <tbody class=\"pecb-table__body\">\n <!-- Empty State -->\n <tr *ngIf=\"data().length === 0\" class=\"pecb-table__row pecb-table__row--empty\">\n <td\n [attr.colspan]=\"selectable() ? columns().length + 1 : columns().length\"\n class=\"pecb-table__cell pecb-table__cell--empty\">\n <ng-container *ngIf=\"emptyTemplate; else defaultEmpty\">\n <ng-container *ngTemplateOutlet=\"emptyTemplate\"></ng-container>\n </ng-container>\n <ng-template #defaultEmpty>\n <div class=\"pecb-table__empty-content\">\n <span class=\"pecb-table__empty-message\">{{ emptyMessage() }}</span>\n </div>\n </ng-template>\n </td>\n </tr>\n\n <!-- Data Rows -->\n <tr\n *ngFor=\"let row of data(); let i = index; trackBy: trackByIndex\"\n class=\"pecb-table__row pecb-table__row--data\"\n [class.pecb-table__row--selected]=\"isRowSelected(i)\"\n role=\"row\"\n (click)=\"onRowClick(row, i)\"\n [attr.tabindex]=\"0\"\n (keydown.enter)=\"onRowClick(row, i)\">\n\n <!-- Selection Checkbox -->\n <td\n *ngIf=\"selectable()\"\n class=\"pecb-table__cell pecb-table__cell--checkbox\"\n role=\"cell\"\n (click)=\"$event.stopPropagation()\">\n <label class=\"pecb-table__checkbox-wrapper\">\n <input\n type=\"checkbox\"\n class=\"pecb-table__checkbox\"\n [checked]=\"isRowSelected(i)\"\n (change)=\"toggleRowSelection(i)\"\n [attr.aria-label]=\"selectRowAriaLabel() + ' ' + (i + 1)\" />\n <span class=\"pecb-table__checkbox-custom\"></span>\n </label>\n </td>\n\n <!-- Data Cells -->\n <td\n *ngFor=\"let column of columns(); trackBy: trackByKey\"\n class=\"pecb-table__cell\"\n [class]=\"getAlignmentClass(column)\"\n [ngClass]=\"column.cssClass\"\n [class.pecb-table__cell--wrap]=\"isCellWrapped(column)\"\n [class.pecb-table__cell--truncate]=\"!isCellWrapped(column)\"\n [style.max-width]=\"column.maxWidth || column.width\"\n [attr.title]=\"getCellTitle(row, column)\"\n role=\"cell\">\n\n <!-- Custom Template -->\n <ng-container *ngIf=\"column.cellTemplate\">\n <ng-container *ngTemplateOutlet=\"column.cellTemplate; context: { $implicit: getCellValue(row, column.key), row: row, column: column, index: i }\"></ng-container>\n </ng-container>\n\n <!-- Dynamic Component - renders any component passed via dynamicComponent -->\n <ng-container *ngIf=\"!column.cellTemplate && hasDynamicComponent(column)\">\n <ng-container\n *ngComponentOutlet=\"column.dynamicComponent!; inputs: getDynamicComponentInputs(row, column, i)\">\n </ng-container>\n </ng-container>\n\n <!-- Status Component - renders pecb-status -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'status'\">\n <pecb-status\n [type]=\"getStatusType(getCellValue(row, column.key), column)\"\n [variant]=\"getStatusVariant(column)\"\n [size]=\"getStatusSize(column)\">\n {{ getCellValue(row, column.key) }}\n </pecb-status>\n </ng-container>\n\n <!-- User Component - renders avatar + name + email -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'user'\">\n <div class=\"pecb-table__user-cell\">\n <img\n *ngIf=\"getUserData(row, column).avatar\"\n [src]=\"getUserData(row, column).avatar\"\n alt=\"\"\n class=\"pecb-table__user-avatar\">\n <div class=\"pecb-table__user-info\">\n <div class=\"pecb-table__user-name\">{{ getUserData(row, column).name }}</div>\n <div *ngIf=\"getUserData(row, column).secondary\" class=\"pecb-table__user-secondary\">\n {{ getUserData(row, column).secondary }}\n </div>\n </div>\n </div>\n </ng-container>\n\n <!-- Action Component - renders action buttons with icons -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'action'\">\n <div class=\"pecb-table__action-buttons\">\n <ng-container *ngFor=\"let action of getActions(column)\">\n <!-- Divider before this action (consumer-controlled grouping) -->\n <span *ngIf=\"action.dividerBefore\" class=\"pecb-table__action-divider\"></span>\n <button\n class=\"pecb-table__action-btn\"\n [class.pecb-table__action-btn--danger]=\"action.variant === 'danger'\"\n [title]=\"action.tooltip || action.id\"\n [disabled]=\"action.disabled\"\n (click)=\"onActionClick(action, row, i, $event)\">\n <!-- View Icon -->\n <svg *ngIf=\"action.icon === 'view'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z\"></path>\n <circle cx=\"12\" cy=\"12\" r=\"3\"></circle>\n </svg>\n <!-- Edit Icon -->\n <svg *ngIf=\"action.icon === 'edit'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\"></path>\n <path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\"></path>\n </svg>\n <!-- Delete Icon -->\n <svg *ngIf=\"action.icon === 'delete'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"3 6 5 6 21 6\"></polyline>\n <path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"></path>\n </svg>\n <!-- Download Icon -->\n <svg *ngIf=\"action.icon === 'download'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"></path>\n <polyline points=\"7 10 12 15 17 10\"></polyline>\n <line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\"></line>\n </svg>\n <!-- Copy Icon -->\n <svg *ngIf=\"action.icon === 'copy'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\"></rect>\n <path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"></path>\n </svg>\n <!-- Custom Icon (SVG string) -->\n <span *ngIf=\"action.icon === 'custom' && action.customIcon\" class=\"pecb-table__custom-icon\" [innerHTML]=\"getSafeIcon(action.customIcon)\"></span>\n </button>\n </ng-container>\n </div>\n </ng-container>\n\n <!-- Link Cell -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'link'\">\n <a\n [href]=\"getLinkHref(getCellValue(row, column.key))\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"pecb-table__link\"\n (click)=\"$event.stopPropagation()\">\n {{ getCellValue(row, column.key) }}\n </a>\n </ng-container>\n\n <!-- Default Text -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'text'\">\n {{ getCellValue(row, column.key) }}\n </ng-container>\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <!-- Pagination (outside table container) -->\n <div *ngIf=\"showPagination() && !loading() && data().length > 0\" [class]=\"paginationClasses\">\n <pecb-pagination\n [totalItems]=\"totalItems()\"\n [pageSize]=\"pageSize()\"\n [currentPage]=\"currentPage()\"\n [pageSizeOptions]=\"pageSizeOptions()\"\n [showPageSizeSelector]=\"showPageSizeSelector()\"\n [variant]=\"paginationVariant()\"\n size=\"md\"\n (pageChange)=\"onPageChange($event)\"\n (pageSizeChange)=\"onPageSizeChange($event)\">\n </pecb-pagination>\n </div>\n</div>\n", styles: [".pecb-table-wrapper{width:100%}.pecb-table-container{width:100%;background-color:#fff;border-radius:12px;overflow-x:auto;overflow-y:hidden;border:1px solid #ececec}.pecb-table-container--sticky{position:relative;overflow:auto}.pecb-table{width:100%;border-collapse:collapse;border-spacing:0;font-family:inherit;font-size:14px;color:#313131}.pecb-table--sm{font-size:12px}.pecb-table--sm .pecb-table__cell,.pecb-table--sm .pecb-table__cell--header{padding:8px 12px}.pecb-table--md{font-size:14px}.pecb-table--md .pecb-table__cell{padding:16px 18px}.pecb-table--md .pecb-table__cell--header{padding:12px 18px}.pecb-table--lg{font-size:16px}.pecb-table--lg .pecb-table__cell{padding:18px 20px}.pecb-table--lg .pecb-table__cell--header{padding:16px 20px}.pecb-table--default .pecb-table__row--data{border-bottom:1px solid #ececec}.pecb-table--default .pecb-table__row--data:last-child{border-bottom:none}.pecb-table--default .pecb-table__row--data:last-child .pecb-table__cell{border-bottom:none}.pecb-table--default .pecb-table__row--data:nth-child(2n){background-color:#fafafa}.pecb-table--striped .pecb-table__row--data:nth-child(2n){background-color:#fafafa}.pecb-table--bordered .pecb-table__cell{border:1px solid #f7f5f5}.pecb-table--hoverable .pecb-table__row--data{transition:background-color .15s ease-in-out}.pecb-table--selectable .pecb-table__row--data{cursor:pointer}.pecb-table__head{background-color:#212427}.pecb-table__head--sticky{position:sticky;top:0;z-index:1020}.pecb-table__row--header{text-transform:uppercase;height:52px}.pecb-table__row--data{background-color:#fff;height:58px}.pecb-table__row--data:focus,.pecb-table__row--data:focus-visible{outline:none}.pecb-table__row--selected{background-color:#f4f4f4}.pecb-table__row--selected:hover{background-color:#f7f5f5}.pecb-table__row--empty{background-color:#fff}.pecb-table__cell{text-align:left;vertical-align:middle;white-space:nowrap;border-bottom:1px solid #ececec}.pecb-table__cell--header{font-weight:500;font-size:12px;color:#fff;letter-spacing:.02em;-webkit-user-select:none;user-select:none;border-bottom:none;background-color:#212427}.pecb-table__cell--sortable{cursor:pointer;transition:background-color .15s ease-in-out}.pecb-table__cell--sortable:hover{background-color:#373a3d}.pecb-table__cell--sortable:focus,.pecb-table__cell--sortable:focus-visible{outline:none;box-shadow:inset 0 0 0 2px #fff}.pecb-table__cell--checkbox{width:48px;text-align:center}.pecb-table__cell--center{text-align:center}.pecb-table__cell--right{text-align:right}.pecb-table__cell--empty{text-align:center;padding:24px 0}.pecb-table__cell--truncate{overflow:hidden;text-overflow:ellipsis}.pecb-table__cell--wrap{white-space:normal;overflow-wrap:anywhere;word-break:break-word}.pecb-table__header-content{display:flex;align-items:center;gap:8px}.pecb-table__header-text{flex:1}.pecb-table__sort-icon{display:flex;align-items:center;justify-content:center;color:#ffffff80;transition:color .15s ease-in-out}.pecb-table__sort-icon svg{width:16px;height:16px}.pecb-table__sort-icon[data-sort=asc],.pecb-table__sort-icon[data-sort=desc]{color:#fff}.pecb-table__checkbox-wrapper{display:inline-flex;align-items:center;justify-content:center;position:relative;cursor:pointer}.pecb-table__checkbox{position:absolute;opacity:0;width:0;height:0}.pecb-table__checkbox:checked+.pecb-table__checkbox-custom{background-color:#212427;border-color:#212427}.pecb-table__checkbox:checked+.pecb-table__checkbox-custom:after{display:block}.pecb-table__checkbox:indeterminate+.pecb-table__checkbox-custom{background-color:#212427;border-color:#212427}.pecb-table__checkbox:indeterminate+.pecb-table__checkbox-custom:before{display:block}.pecb-table__checkbox:indeterminate+.pecb-table__checkbox-custom:after{display:none}.pecb-table__checkbox:focus+.pecb-table__checkbox-custom{box-shadow:0 0 0 2px #21242733}.pecb-table__checkbox-custom{width:18px;height:18px;border:1.5px solid #c2c2c2;border-radius:6px;background-color:#fff;position:relative;transition:all .15s ease-in-out}.pecb-table__checkbox-custom:after{content:\"\";display:none;position:absolute;left:5px;top:2px;width:4px;height:8px;border:solid #ffffff;border-width:0 2px 2px 0;transform:rotate(45deg)}.pecb-table__checkbox-custom:before{content:\"\";display:none;position:absolute;left:3px;top:6px;width:8px;height:2px;background-color:#fff}.pecb-table__empty-content{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:16px 0}.pecb-table__empty-message{color:#919191;font-size:14px}.pecb-table__loading{display:flex;align-items:center;justify-content:center;padding:48px;min-height:200px}.pecb-table__loading-spinner{display:flex;flex-direction:column;align-items:center;gap:12px;color:#616161;font-size:14px;font-family:inherit;font-weight:400}.pecb-table__spinner-icon{width:32px;height:32px;animation:spin 1s linear infinite;color:#212427}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.pecb-table__pagination{padding:16px 0;margin-top:16px}.pecb-table__pagination--bordered{padding:16px 18px;background-color:#fff;border:1px solid #ececec;border-radius:6px}.pecb-table__user-cell{display:flex;align-items:center;gap:12px}.pecb-table__user-avatar{width:40px;height:40px;border-radius:9999px;object-fit:cover;flex-shrink:0}.pecb-table__user-info{display:flex;flex-direction:column;min-width:0}.pecb-table__user-name{font-weight:500;color:#313131;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pecb-table__user-secondary{font-size:12px;color:#919191;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pecb-table__action-buttons{display:flex;align-items:center;gap:0;justify-content:center}.pecb-table__action-divider{display:block;width:1px;height:16px;background-color:#c2c2c2;flex-shrink:0}.pecb-table__action-btn{display:flex;align-items:center;justify-content:center;width:30px;height:30px;border:none;background:transparent;color:#212427;cursor:pointer;border-radius:6px;transition:all .15s ease-in-out;padding:0}.pecb-table__action-btn:hover{background-color:#f7f5f5;color:#313131}.pecb-table__action-btn:focus{outline:none;box-shadow:0 0 0 2px #21242733}.pecb-table__action-btn:disabled{opacity:.5;cursor:not-allowed}.pecb-table__action-btn--danger:hover{background-color:#fdecea;color:#dc3545}.pecb-table__action-btn svg{width:18px;height:18px}.pecb-table__custom-icon{display:flex;align-items:center;justify-content:center}.pecb-table__custom-icon svg{width:18px;height:18px}.pecb-table__link{color:#212427;text-decoration:none;font-size:inherit}.pecb-table__link:hover{text-decoration:underline;color:#313131}.pecb-table__link:focus-visible{outline:2px solid #212427;outline-offset:2px;border-radius:2px}@media (min-width: 768px){.pecb-table--md,.pecb-table--lg{font-size:14px}.pecb-table--md .pecb-table__cell,.pecb-table--lg .pecb-table__cell{padding:12px 16px}.pecb-table--md .pecb-table__cell--header,.pecb-table--lg .pecb-table__cell--header{padding:8px 16px}.pecb-table__pagination{padding:12px 16px}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: PaginationComponent, selector: "pecb-pagination", inputs: ["totalItems", "pageSize", "currentPage", "pageSizeOptions", "size", "variant", "showPageSizeSelector", "showItemsInfo", "maxVisiblePages", "disabled", "aria-label", "previousPageAriaLabel", "nextPageAriaLabel", "pageNavigationAriaLabel", "pageAriaLabel", "pageSizeListboxAriaLabel", "itemsPerPageLabel", "itemsInfoTemplate", "itemsInfoCompactTemplate"], outputs: ["pageSizeChange", "currentPageChange", "pageChange"] }, { kind: "component", type: StatusComponent, selector: "pecb-status", inputs: ["type", "variant", "size", "ariaLabel", "text", "customClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4527
4529
  }
4528
4530
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: TableComponent, decorators: [{
4529
4531
  type: Component,
4530
- args: [{ selector: 'pecb-table', imports: [CommonModule, PaginationComponent, StatusComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"pecb-table-wrapper\">\n <div [class]=\"containerClasses\" [style.maxHeight]=\"maxHeight()\">\n <!-- Loading State -->\n <div *ngIf=\"loading()\" class=\"pecb-table__loading\">\n <ng-container *ngIf=\"loadingTemplate; else defaultLoading\">\n <ng-container *ngTemplateOutlet=\"loadingTemplate\"></ng-container>\n </ng-container>\n <ng-template #defaultLoading>\n <div class=\"pecb-table__loading-spinner\">\n <svg class=\"pecb-table__spinner-icon\" viewBox=\"0 0 24 24\" fill=\"none\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-dasharray=\"31.4 31.4\" />\n </svg>\n <span>{{ loadingText() }}</span>\n </div>\n </ng-template>\n </div>\n\n <!-- Table -->\n <table\n *ngIf=\"!loading()\"\n [class]=\"tableClasses\"\n [style.min-width]=\"minWidth()\"\n [attr.aria-label]=\"ariaLabel()\"\n role=\"table\">\n\n <!-- Table Header -->\n <thead *ngIf=\"showHeader()\" class=\"pecb-table__head\" [class.pecb-table__head--sticky]=\"stickyHeader()\">\n <tr class=\"pecb-table__row pecb-table__row--header\" role=\"row\">\n <!-- Selection Checkbox Column -->\n <th\n *ngIf=\"selectable()\"\n class=\"pecb-table__cell pecb-table__cell--header pecb-table__cell--checkbox\"\n role=\"columnheader\"\n scope=\"col\">\n <label class=\"pecb-table__checkbox-wrapper\">\n <input\n type=\"checkbox\"\n class=\"pecb-table__checkbox\"\n [checked]=\"isAllSelected()\"\n [indeterminate]=\"isSomeSelected()\"\n (change)=\"toggleAllSelection()\"\n [attr.aria-label]=\"selectAllAriaLabel()\" />\n <span class=\"pecb-table__checkbox-custom\"></span>\n </label>\n </th>\n\n <!-- Data Columns -->\n <th\n *ngFor=\"let column of columns(); trackBy: trackByKey\"\n class=\"pecb-table__cell pecb-table__cell--header\"\n [class.pecb-table__cell--sortable]=\"column.sortable\"\n [class]=\"getAlignmentClass(column)\"\n [ngClass]=\"column.cssClass\"\n [style.width]=\"column.width\"\n [attr.aria-sort]=\"column.sortable ? (sortColumn() === column.key ? (sortDirection() === 'asc' ? 'ascending' : 'descending') : 'none') : null\"\n role=\"columnheader\"\n scope=\"col\"\n (click)=\"onHeaderClick(column)\"\n (keydown.enter)=\"onHeaderClick(column)\"\n (keydown.space)=\"onHeaderClick(column); $event.preventDefault()\"\n [attr.tabindex]=\"column.sortable ? 0 : null\">\n <div class=\"pecb-table__header-content\">\n <ng-container *ngIf=\"column.headerTemplate; else defaultHeader\">\n <ng-container *ngTemplateOutlet=\"column.headerTemplate; context: { $implicit: column }\"></ng-container>\n </ng-container>\n <ng-template #defaultHeader>\n <span class=\"pecb-table__header-text\">{{ column.label }}</span>\n </ng-template>\n\n <!-- Sort Icon -->\n <span *ngIf=\"column.sortable\" class=\"pecb-table__sort-icon\" [attr.data-sort]=\"getSortState(column)\">\n <svg *ngIf=\"getSortState(column) === 'none'\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n <path d=\"M8 3L12 7H4L8 3Z\" fill=\"currentColor\" opacity=\"0.4\"/>\n <path d=\"M8 13L4 9H12L8 13Z\" fill=\"currentColor\" opacity=\"0.4\"/>\n </svg>\n <svg *ngIf=\"getSortState(column) === 'asc'\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n <path d=\"M8 3L12 7H4L8 3Z\" fill=\"currentColor\"/>\n <path d=\"M8 13L4 9H12L8 13Z\" fill=\"currentColor\" opacity=\"0.4\"/>\n </svg>\n <svg *ngIf=\"getSortState(column) === 'desc'\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n <path d=\"M8 3L12 7H4L8 3Z\" fill=\"currentColor\" opacity=\"0.4\"/>\n <path d=\"M8 13L4 9H12L8 13Z\" fill=\"currentColor\"/>\n </svg>\n </span>\n </div>\n </th>\n </tr>\n </thead>\n\n <!-- Table Body -->\n <tbody class=\"pecb-table__body\">\n <!-- Empty State -->\n <tr *ngIf=\"data().length === 0\" class=\"pecb-table__row pecb-table__row--empty\">\n <td\n [attr.colspan]=\"selectable() ? columns().length + 1 : columns().length\"\n class=\"pecb-table__cell pecb-table__cell--empty\">\n <ng-container *ngIf=\"emptyTemplate; else defaultEmpty\">\n <ng-container *ngTemplateOutlet=\"emptyTemplate\"></ng-container>\n </ng-container>\n <ng-template #defaultEmpty>\n <div class=\"pecb-table__empty-content\">\n <span class=\"pecb-table__empty-message\">{{ emptyMessage() }}</span>\n </div>\n </ng-template>\n </td>\n </tr>\n\n <!-- Data Rows -->\n <tr\n *ngFor=\"let row of data(); let i = index; trackBy: trackByIndex\"\n class=\"pecb-table__row pecb-table__row--data\"\n [class.pecb-table__row--selected]=\"isRowSelected(i)\"\n role=\"row\"\n (click)=\"onRowClick(row, i)\"\n [attr.tabindex]=\"0\"\n (keydown.enter)=\"onRowClick(row, i)\">\n\n <!-- Selection Checkbox -->\n <td\n *ngIf=\"selectable()\"\n class=\"pecb-table__cell pecb-table__cell--checkbox\"\n role=\"cell\"\n (click)=\"$event.stopPropagation()\">\n <label class=\"pecb-table__checkbox-wrapper\">\n <input\n type=\"checkbox\"\n class=\"pecb-table__checkbox\"\n [checked]=\"isRowSelected(i)\"\n (change)=\"toggleRowSelection(i)\"\n [attr.aria-label]=\"selectRowAriaLabel() + ' ' + (i + 1)\" />\n <span class=\"pecb-table__checkbox-custom\"></span>\n </label>\n </td>\n\n <!-- Data Cells -->\n <td\n *ngFor=\"let column of columns(); trackBy: trackByKey\"\n class=\"pecb-table__cell\"\n [class]=\"getAlignmentClass(column)\"\n [ngClass]=\"column.cssClass\"\n [class.pecb-table__cell--wrap]=\"isCellWrapped(column)\"\n [class.pecb-table__cell--truncate]=\"!isCellWrapped(column)\"\n [style.max-width]=\"column.maxWidth || column.width\"\n [attr.title]=\"getCellTitle(row, column)\"\n role=\"cell\">\n\n <!-- Custom Template -->\n <ng-container *ngIf=\"column.cellTemplate\">\n <ng-container *ngTemplateOutlet=\"column.cellTemplate; context: { $implicit: getCellValue(row, column.key), row: row, column: column, index: i }\"></ng-container>\n </ng-container>\n\n <!-- Dynamic Component - renders any component passed via dynamicComponent -->\n <ng-container *ngIf=\"!column.cellTemplate && hasDynamicComponent(column)\">\n <ng-container\n *ngComponentOutlet=\"column.dynamicComponent!; inputs: getDynamicComponentInputs(row, column, i)\">\n </ng-container>\n </ng-container>\n\n <!-- Status Component - renders pecb-status -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'status'\">\n <pecb-status\n [type]=\"getStatusType(getCellValue(row, column.key), column)\"\n [variant]=\"getStatusVariant(column)\"\n [size]=\"getStatusSize(column)\">\n {{ getCellValue(row, column.key) }}\n </pecb-status>\n </ng-container>\n\n <!-- User Component - renders avatar + name + email -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'user'\">\n <div class=\"pecb-table__user-cell\">\n <img\n *ngIf=\"getUserData(row, column).avatar\"\n [src]=\"getUserData(row, column).avatar\"\n alt=\"\"\n class=\"pecb-table__user-avatar\">\n <div class=\"pecb-table__user-info\">\n <div class=\"pecb-table__user-name\">{{ getUserData(row, column).name }}</div>\n <div *ngIf=\"getUserData(row, column).secondary\" class=\"pecb-table__user-secondary\">\n {{ getUserData(row, column).secondary }}\n </div>\n </div>\n </div>\n </ng-container>\n\n <!-- Action Component - renders action buttons with icons -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'action'\">\n <div class=\"pecb-table__action-buttons\">\n <ng-container *ngFor=\"let action of getActions(column)\">\n <!-- Divider before this action (consumer-controlled grouping) -->\n <span *ngIf=\"action.dividerBefore\" class=\"pecb-table__action-divider\"></span>\n <button\n class=\"pecb-table__action-btn\"\n [class.pecb-table__action-btn--danger]=\"action.variant === 'danger'\"\n [title]=\"action.tooltip || action.id\"\n [disabled]=\"action.disabled\"\n (click)=\"onActionClick(action, row, i, $event)\">\n <!-- View Icon -->\n <svg *ngIf=\"action.icon === 'view'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z\"></path>\n <circle cx=\"12\" cy=\"12\" r=\"3\"></circle>\n </svg>\n <!-- Edit Icon -->\n <svg *ngIf=\"action.icon === 'edit'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\"></path>\n <path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\"></path>\n </svg>\n <!-- Delete Icon -->\n <svg *ngIf=\"action.icon === 'delete'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"3 6 5 6 21 6\"></polyline>\n <path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"></path>\n </svg>\n <!-- Download Icon -->\n <svg *ngIf=\"action.icon === 'download'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"></path>\n <polyline points=\"7 10 12 15 17 10\"></polyline>\n <line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\"></line>\n </svg>\n <!-- Copy Icon -->\n <svg *ngIf=\"action.icon === 'copy'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\"></rect>\n <path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"></path>\n </svg>\n <!-- Custom Icon (SVG string) -->\n <span *ngIf=\"action.icon === 'custom' && action.customIcon\" class=\"pecb-table__custom-icon\" [innerHTML]=\"getSafeIcon(action.customIcon)\"></span>\n </button>\n </ng-container>\n </div>\n </ng-container>\n\n <!-- Link Cell -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'link'\">\n <a\n [href]=\"getLinkHref(getCellValue(row, column.key))\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"pecb-table__link\"\n (click)=\"$event.stopPropagation()\">\n {{ getCellValue(row, column.key) }}\n </a>\n </ng-container>\n\n <!-- Default Text -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'text'\">\n {{ getCellValue(row, column.key) }}\n </ng-container>\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <!-- Pagination (outside table container) -->\n <div *ngIf=\"showPagination() && !loading() && data().length > 0\" [class]=\"paginationClasses\">\n <pecb-pagination\n [totalItems]=\"totalItems()\"\n [pageSize]=\"pageSize()\"\n [currentPage]=\"currentPage()\"\n [pageSizeOptions]=\"pageSizeOptions()\"\n [showPageSizeSelector]=\"showPageSizeSelector()\"\n [variant]=\"paginationVariant()\"\n size=\"md\"\n (pageChange)=\"onPageChange($event)\"\n (pageSizeChange)=\"onPageSizeChange($event)\">\n </pecb-pagination>\n </div>\n</div>\n", styles: [".pecb-table-wrapper{width:100%}.pecb-table-container{width:100%;background-color:#fff;border-radius:12px;overflow-x:auto;overflow-y:hidden;border:1px solid #ececec}.pecb-table-container--sticky{position:relative;overflow:auto}.pecb-table{width:100%;border-collapse:collapse;border-spacing:0;font-family:inherit;font-size:14px;color:#313131}.pecb-table--sm{font-size:12px}.pecb-table--sm .pecb-table__cell,.pecb-table--sm .pecb-table__cell--header{padding:8px 12px}.pecb-table--md{font-size:14px}.pecb-table--md .pecb-table__cell{padding:16px 18px}.pecb-table--md .pecb-table__cell--header{padding:12px 18px}.pecb-table--lg{font-size:16px}.pecb-table--lg .pecb-table__cell{padding:18px 20px}.pecb-table--lg .pecb-table__cell--header{padding:16px 20px}.pecb-table--default .pecb-table__row--data{border-bottom:1px solid #ececec}.pecb-table--default .pecb-table__row--data:last-child{border-bottom:none}.pecb-table--default .pecb-table__row--data:last-child .pecb-table__cell{border-bottom:none}.pecb-table--default .pecb-table__row--data:nth-child(2n){background-color:#fafafa}.pecb-table--striped .pecb-table__row--data:nth-child(2n){background-color:#fafafa}.pecb-table--bordered .pecb-table__cell{border:1px solid #f7f5f5}.pecb-table--hoverable .pecb-table__row--data{transition:background-color .15s ease-in-out}.pecb-table--selectable .pecb-table__row--data{cursor:pointer}.pecb-table__head{background-color:#212427}.pecb-table__head--sticky{position:sticky;top:0;z-index:1020}.pecb-table__row--header{text-transform:uppercase;height:52px}.pecb-table__row--data{background-color:#fff;height:58px}.pecb-table__row--data:focus,.pecb-table__row--data:focus-visible{outline:none}.pecb-table__row--selected{background-color:#f4f4f4}.pecb-table__row--selected:hover{background-color:#f7f5f5}.pecb-table__row--empty{background-color:#fff}.pecb-table__cell{text-align:left;vertical-align:middle;white-space:nowrap;border-bottom:1px solid #ececec}.pecb-table__cell--header{font-weight:500;font-size:12px;color:#fff;letter-spacing:.02em;-webkit-user-select:none;user-select:none;border-bottom:none}.pecb-table__cell--sortable{cursor:pointer;transition:background-color .15s ease-in-out}.pecb-table__cell--sortable:hover{background-color:#ffffff1a}.pecb-table__cell--sortable:focus,.pecb-table__cell--sortable:focus-visible{outline:none;box-shadow:inset 0 0 0 2px #fff}.pecb-table__cell--checkbox{width:48px;text-align:center}.pecb-table__cell--center{text-align:center}.pecb-table__cell--right{text-align:right}.pecb-table__cell--empty{text-align:center;padding:24px 0}.pecb-table__cell--truncate{overflow:hidden;text-overflow:ellipsis}.pecb-table__cell--wrap{white-space:normal;overflow-wrap:anywhere;word-break:break-word}.pecb-table__header-content{display:flex;align-items:center;gap:8px}.pecb-table__header-text{flex:1}.pecb-table__sort-icon{display:flex;align-items:center;justify-content:center;color:#ffffff80;transition:color .15s ease-in-out}.pecb-table__sort-icon svg{width:16px;height:16px}.pecb-table__sort-icon[data-sort=asc],.pecb-table__sort-icon[data-sort=desc]{color:#fff}.pecb-table__checkbox-wrapper{display:inline-flex;align-items:center;justify-content:center;position:relative;cursor:pointer}.pecb-table__checkbox{position:absolute;opacity:0;width:0;height:0}.pecb-table__checkbox:checked+.pecb-table__checkbox-custom{background-color:#212427;border-color:#212427}.pecb-table__checkbox:checked+.pecb-table__checkbox-custom:after{display:block}.pecb-table__checkbox:indeterminate+.pecb-table__checkbox-custom{background-color:#212427;border-color:#212427}.pecb-table__checkbox:indeterminate+.pecb-table__checkbox-custom:before{display:block}.pecb-table__checkbox:indeterminate+.pecb-table__checkbox-custom:after{display:none}.pecb-table__checkbox:focus+.pecb-table__checkbox-custom{box-shadow:0 0 0 2px #21242733}.pecb-table__checkbox-custom{width:18px;height:18px;border:1.5px solid #c2c2c2;border-radius:6px;background-color:#fff;position:relative;transition:all .15s ease-in-out}.pecb-table__checkbox-custom:after{content:\"\";display:none;position:absolute;left:5px;top:2px;width:4px;height:8px;border:solid #ffffff;border-width:0 2px 2px 0;transform:rotate(45deg)}.pecb-table__checkbox-custom:before{content:\"\";display:none;position:absolute;left:3px;top:6px;width:8px;height:2px;background-color:#fff}.pecb-table__empty-content{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:16px 0}.pecb-table__empty-message{color:#919191;font-size:14px}.pecb-table__loading{display:flex;align-items:center;justify-content:center;padding:48px;min-height:200px}.pecb-table__loading-spinner{display:flex;flex-direction:column;align-items:center;gap:12px;color:#616161;font-size:14px;font-family:inherit;font-weight:400}.pecb-table__spinner-icon{width:32px;height:32px;animation:spin 1s linear infinite;color:#212427}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.pecb-table__pagination{padding:16px 0;margin-top:16px}.pecb-table__pagination--bordered{padding:16px 18px;background-color:#fff;border:1px solid #ececec;border-radius:6px}.pecb-table__user-cell{display:flex;align-items:center;gap:12px}.pecb-table__user-avatar{width:40px;height:40px;border-radius:9999px;object-fit:cover;flex-shrink:0}.pecb-table__user-info{display:flex;flex-direction:column;min-width:0}.pecb-table__user-name{font-weight:500;color:#313131;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pecb-table__user-secondary{font-size:12px;color:#919191;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pecb-table__action-buttons{display:flex;align-items:center;gap:0;justify-content:center}.pecb-table__action-divider{display:block;width:1px;height:16px;background-color:#c2c2c2;flex-shrink:0}.pecb-table__action-btn{display:flex;align-items:center;justify-content:center;width:30px;height:30px;border:none;background:transparent;color:#212427;cursor:pointer;border-radius:6px;transition:all .15s ease-in-out;padding:0}.pecb-table__action-btn:hover{background-color:#f7f5f5;color:#313131}.pecb-table__action-btn:focus{outline:none;box-shadow:0 0 0 2px #21242733}.pecb-table__action-btn:disabled{opacity:.5;cursor:not-allowed}.pecb-table__action-btn--danger:hover{background-color:#fdecea;color:#dc3545}.pecb-table__action-btn svg{width:18px;height:18px}.pecb-table__custom-icon{display:flex;align-items:center;justify-content:center}.pecb-table__custom-icon svg{width:18px;height:18px}.pecb-table__link{color:#212427;text-decoration:none;font-size:inherit}.pecb-table__link:hover{text-decoration:underline;color:#313131}.pecb-table__link:focus-visible{outline:2px solid #212427;outline-offset:2px;border-radius:2px}@media (min-width: 768px){.pecb-table--md,.pecb-table--lg{font-size:14px}.pecb-table--md .pecb-table__cell,.pecb-table--lg .pecb-table__cell{padding:12px 16px}.pecb-table--md .pecb-table__cell--header,.pecb-table--lg .pecb-table__cell--header{padding:8px 16px}.pecb-table__pagination{padding:12px 16px}}\n"] }]
4532
+ args: [{ selector: 'pecb-table', imports: [CommonModule, PaginationComponent, StatusComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"pecb-table-wrapper\">\n <div [class]=\"containerClasses\" [style.maxHeight]=\"maxHeight()\">\n <!-- Loading State -->\n <div *ngIf=\"loading()\" class=\"pecb-table__loading\">\n <ng-container *ngIf=\"loadingTemplate; else defaultLoading\">\n <ng-container *ngTemplateOutlet=\"loadingTemplate\"></ng-container>\n </ng-container>\n <ng-template #defaultLoading>\n <div class=\"pecb-table__loading-spinner\">\n <svg class=\"pecb-table__spinner-icon\" viewBox=\"0 0 24 24\" fill=\"none\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-dasharray=\"31.4 31.4\" />\n </svg>\n <span>{{ loadingText() }}</span>\n </div>\n </ng-template>\n </div>\n\n <!-- Table -->\n <table\n *ngIf=\"!loading()\"\n [class]=\"tableClasses\"\n [style.min-width]=\"minWidth()\"\n [attr.aria-label]=\"ariaLabel()\"\n role=\"table\">\n\n <!-- Table Header -->\n <thead *ngIf=\"showHeader()\" class=\"pecb-table__head\" [class.pecb-table__head--sticky]=\"stickyHeader()\">\n <tr class=\"pecb-table__row pecb-table__row--header\" role=\"row\">\n <!-- Selection Checkbox Column -->\n <th\n *ngIf=\"selectable()\"\n class=\"pecb-table__cell pecb-table__cell--header pecb-table__cell--checkbox\"\n role=\"columnheader\"\n scope=\"col\">\n <label class=\"pecb-table__checkbox-wrapper\">\n <input\n type=\"checkbox\"\n class=\"pecb-table__checkbox\"\n [checked]=\"isAllSelected()\"\n [indeterminate]=\"isSomeSelected()\"\n (change)=\"toggleAllSelection()\"\n [attr.aria-label]=\"selectAllAriaLabel()\" />\n <span class=\"pecb-table__checkbox-custom\"></span>\n </label>\n </th>\n\n <!-- Data Columns -->\n <th\n *ngFor=\"let column of columns(); trackBy: trackByKey\"\n class=\"pecb-table__cell pecb-table__cell--header\"\n [class.pecb-table__cell--sortable]=\"column.sortable\"\n [class]=\"getAlignmentClass(column)\"\n [ngClass]=\"column.cssClass\"\n [style.width]=\"column.width\"\n [attr.aria-sort]=\"column.sortable ? (sortColumn() === column.key ? (sortDirection() === 'asc' ? 'ascending' : 'descending') : 'none') : null\"\n role=\"columnheader\"\n scope=\"col\"\n (click)=\"onHeaderClick(column)\"\n (keydown.enter)=\"onHeaderClick(column)\"\n (keydown.space)=\"onHeaderClick(column); $event.preventDefault()\"\n [attr.tabindex]=\"column.sortable ? 0 : null\">\n <div class=\"pecb-table__header-content\">\n <ng-container *ngIf=\"column.headerTemplate; else defaultHeader\">\n <ng-container *ngTemplateOutlet=\"column.headerTemplate; context: { $implicit: column }\"></ng-container>\n </ng-container>\n <ng-template #defaultHeader>\n <span class=\"pecb-table__header-text\">{{ column.label }}</span>\n </ng-template>\n\n <!-- Sort Icon -->\n <span *ngIf=\"column.sortable\" class=\"pecb-table__sort-icon\" [attr.data-sort]=\"getSortState(column)\">\n <svg *ngIf=\"getSortState(column) === 'none'\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n <path d=\"M8 3L12 7H4L8 3Z\" fill=\"currentColor\" opacity=\"0.4\"/>\n <path d=\"M8 13L4 9H12L8 13Z\" fill=\"currentColor\" opacity=\"0.4\"/>\n </svg>\n <svg *ngIf=\"getSortState(column) === 'asc'\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n <path d=\"M8 3L12 7H4L8 3Z\" fill=\"currentColor\"/>\n <path d=\"M8 13L4 9H12L8 13Z\" fill=\"currentColor\" opacity=\"0.4\"/>\n </svg>\n <svg *ngIf=\"getSortState(column) === 'desc'\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n <path d=\"M8 3L12 7H4L8 3Z\" fill=\"currentColor\" opacity=\"0.4\"/>\n <path d=\"M8 13L4 9H12L8 13Z\" fill=\"currentColor\"/>\n </svg>\n </span>\n </div>\n </th>\n </tr>\n </thead>\n\n <!-- Table Body -->\n <tbody class=\"pecb-table__body\">\n <!-- Empty State -->\n <tr *ngIf=\"data().length === 0\" class=\"pecb-table__row pecb-table__row--empty\">\n <td\n [attr.colspan]=\"selectable() ? columns().length + 1 : columns().length\"\n class=\"pecb-table__cell pecb-table__cell--empty\">\n <ng-container *ngIf=\"emptyTemplate; else defaultEmpty\">\n <ng-container *ngTemplateOutlet=\"emptyTemplate\"></ng-container>\n </ng-container>\n <ng-template #defaultEmpty>\n <div class=\"pecb-table__empty-content\">\n <span class=\"pecb-table__empty-message\">{{ emptyMessage() }}</span>\n </div>\n </ng-template>\n </td>\n </tr>\n\n <!-- Data Rows -->\n <tr\n *ngFor=\"let row of data(); let i = index; trackBy: trackByIndex\"\n class=\"pecb-table__row pecb-table__row--data\"\n [class.pecb-table__row--selected]=\"isRowSelected(i)\"\n role=\"row\"\n (click)=\"onRowClick(row, i)\"\n [attr.tabindex]=\"0\"\n (keydown.enter)=\"onRowClick(row, i)\">\n\n <!-- Selection Checkbox -->\n <td\n *ngIf=\"selectable()\"\n class=\"pecb-table__cell pecb-table__cell--checkbox\"\n role=\"cell\"\n (click)=\"$event.stopPropagation()\">\n <label class=\"pecb-table__checkbox-wrapper\">\n <input\n type=\"checkbox\"\n class=\"pecb-table__checkbox\"\n [checked]=\"isRowSelected(i)\"\n (change)=\"toggleRowSelection(i)\"\n [attr.aria-label]=\"selectRowAriaLabel() + ' ' + (i + 1)\" />\n <span class=\"pecb-table__checkbox-custom\"></span>\n </label>\n </td>\n\n <!-- Data Cells -->\n <td\n *ngFor=\"let column of columns(); trackBy: trackByKey\"\n class=\"pecb-table__cell\"\n [class]=\"getAlignmentClass(column)\"\n [ngClass]=\"column.cssClass\"\n [class.pecb-table__cell--wrap]=\"isCellWrapped(column)\"\n [class.pecb-table__cell--truncate]=\"!isCellWrapped(column)\"\n [style.max-width]=\"column.maxWidth || column.width\"\n [attr.title]=\"getCellTitle(row, column)\"\n role=\"cell\">\n\n <!-- Custom Template -->\n <ng-container *ngIf=\"column.cellTemplate\">\n <ng-container *ngTemplateOutlet=\"column.cellTemplate; context: { $implicit: getCellValue(row, column.key), row: row, column: column, index: i }\"></ng-container>\n </ng-container>\n\n <!-- Dynamic Component - renders any component passed via dynamicComponent -->\n <ng-container *ngIf=\"!column.cellTemplate && hasDynamicComponent(column)\">\n <ng-container\n *ngComponentOutlet=\"column.dynamicComponent!; inputs: getDynamicComponentInputs(row, column, i)\">\n </ng-container>\n </ng-container>\n\n <!-- Status Component - renders pecb-status -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'status'\">\n <pecb-status\n [type]=\"getStatusType(getCellValue(row, column.key), column)\"\n [variant]=\"getStatusVariant(column)\"\n [size]=\"getStatusSize(column)\">\n {{ getCellValue(row, column.key) }}\n </pecb-status>\n </ng-container>\n\n <!-- User Component - renders avatar + name + email -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'user'\">\n <div class=\"pecb-table__user-cell\">\n <img\n *ngIf=\"getUserData(row, column).avatar\"\n [src]=\"getUserData(row, column).avatar\"\n alt=\"\"\n class=\"pecb-table__user-avatar\">\n <div class=\"pecb-table__user-info\">\n <div class=\"pecb-table__user-name\">{{ getUserData(row, column).name }}</div>\n <div *ngIf=\"getUserData(row, column).secondary\" class=\"pecb-table__user-secondary\">\n {{ getUserData(row, column).secondary }}\n </div>\n </div>\n </div>\n </ng-container>\n\n <!-- Action Component - renders action buttons with icons -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'action'\">\n <div class=\"pecb-table__action-buttons\">\n <ng-container *ngFor=\"let action of getActions(column)\">\n <!-- Divider before this action (consumer-controlled grouping) -->\n <span *ngIf=\"action.dividerBefore\" class=\"pecb-table__action-divider\"></span>\n <button\n class=\"pecb-table__action-btn\"\n [class.pecb-table__action-btn--danger]=\"action.variant === 'danger'\"\n [title]=\"action.tooltip || action.id\"\n [disabled]=\"action.disabled\"\n (click)=\"onActionClick(action, row, i, $event)\">\n <!-- View Icon -->\n <svg *ngIf=\"action.icon === 'view'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z\"></path>\n <circle cx=\"12\" cy=\"12\" r=\"3\"></circle>\n </svg>\n <!-- Edit Icon -->\n <svg *ngIf=\"action.icon === 'edit'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\"></path>\n <path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\"></path>\n </svg>\n <!-- Delete Icon -->\n <svg *ngIf=\"action.icon === 'delete'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"3 6 5 6 21 6\"></polyline>\n <path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"></path>\n </svg>\n <!-- Download Icon -->\n <svg *ngIf=\"action.icon === 'download'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"></path>\n <polyline points=\"7 10 12 15 17 10\"></polyline>\n <line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\"></line>\n </svg>\n <!-- Copy Icon -->\n <svg *ngIf=\"action.icon === 'copy'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\"></rect>\n <path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"></path>\n </svg>\n <!-- Custom Icon (SVG string) -->\n <span *ngIf=\"action.icon === 'custom' && action.customIcon\" class=\"pecb-table__custom-icon\" [innerHTML]=\"getSafeIcon(action.customIcon)\"></span>\n </button>\n </ng-container>\n </div>\n </ng-container>\n\n <!-- Link Cell -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'link'\">\n <a\n [href]=\"getLinkHref(getCellValue(row, column.key))\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"pecb-table__link\"\n (click)=\"$event.stopPropagation()\">\n {{ getCellValue(row, column.key) }}\n </a>\n </ng-container>\n\n <!-- Default Text -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'text'\">\n {{ getCellValue(row, column.key) }}\n </ng-container>\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <!-- Pagination (outside table container) -->\n <div *ngIf=\"showPagination() && !loading() && data().length > 0\" [class]=\"paginationClasses\">\n <pecb-pagination\n [totalItems]=\"totalItems()\"\n [pageSize]=\"pageSize()\"\n [currentPage]=\"currentPage()\"\n [pageSizeOptions]=\"pageSizeOptions()\"\n [showPageSizeSelector]=\"showPageSizeSelector()\"\n [variant]=\"paginationVariant()\"\n size=\"md\"\n (pageChange)=\"onPageChange($event)\"\n (pageSizeChange)=\"onPageSizeChange($event)\">\n </pecb-pagination>\n </div>\n</div>\n", styles: [".pecb-table-wrapper{width:100%}.pecb-table-container{width:100%;background-color:#fff;border-radius:12px;overflow-x:auto;overflow-y:hidden;border:1px solid #ececec}.pecb-table-container--sticky{position:relative;overflow:auto}.pecb-table{width:100%;border-collapse:collapse;border-spacing:0;font-family:inherit;font-size:14px;color:#313131}.pecb-table--sm{font-size:12px}.pecb-table--sm .pecb-table__cell,.pecb-table--sm .pecb-table__cell--header{padding:8px 12px}.pecb-table--md{font-size:14px}.pecb-table--md .pecb-table__cell{padding:16px 18px}.pecb-table--md .pecb-table__cell--header{padding:12px 18px}.pecb-table--lg{font-size:16px}.pecb-table--lg .pecb-table__cell{padding:18px 20px}.pecb-table--lg .pecb-table__cell--header{padding:16px 20px}.pecb-table--default .pecb-table__row--data{border-bottom:1px solid #ececec}.pecb-table--default .pecb-table__row--data:last-child{border-bottom:none}.pecb-table--default .pecb-table__row--data:last-child .pecb-table__cell{border-bottom:none}.pecb-table--default .pecb-table__row--data:nth-child(2n){background-color:#fafafa}.pecb-table--striped .pecb-table__row--data:nth-child(2n){background-color:#fafafa}.pecb-table--bordered .pecb-table__cell{border:1px solid #f7f5f5}.pecb-table--hoverable .pecb-table__row--data{transition:background-color .15s ease-in-out}.pecb-table--selectable .pecb-table__row--data{cursor:pointer}.pecb-table__head{background-color:#212427}.pecb-table__head--sticky{position:sticky;top:0;z-index:1020}.pecb-table__row--header{text-transform:uppercase;height:52px}.pecb-table__row--data{background-color:#fff;height:58px}.pecb-table__row--data:focus,.pecb-table__row--data:focus-visible{outline:none}.pecb-table__row--selected{background-color:#f4f4f4}.pecb-table__row--selected:hover{background-color:#f7f5f5}.pecb-table__row--empty{background-color:#fff}.pecb-table__cell{text-align:left;vertical-align:middle;white-space:nowrap;border-bottom:1px solid #ececec}.pecb-table__cell--header{font-weight:500;font-size:12px;color:#fff;letter-spacing:.02em;-webkit-user-select:none;user-select:none;border-bottom:none;background-color:#212427}.pecb-table__cell--sortable{cursor:pointer;transition:background-color .15s ease-in-out}.pecb-table__cell--sortable:hover{background-color:#373a3d}.pecb-table__cell--sortable:focus,.pecb-table__cell--sortable:focus-visible{outline:none;box-shadow:inset 0 0 0 2px #fff}.pecb-table__cell--checkbox{width:48px;text-align:center}.pecb-table__cell--center{text-align:center}.pecb-table__cell--right{text-align:right}.pecb-table__cell--empty{text-align:center;padding:24px 0}.pecb-table__cell--truncate{overflow:hidden;text-overflow:ellipsis}.pecb-table__cell--wrap{white-space:normal;overflow-wrap:anywhere;word-break:break-word}.pecb-table__header-content{display:flex;align-items:center;gap:8px}.pecb-table__header-text{flex:1}.pecb-table__sort-icon{display:flex;align-items:center;justify-content:center;color:#ffffff80;transition:color .15s ease-in-out}.pecb-table__sort-icon svg{width:16px;height:16px}.pecb-table__sort-icon[data-sort=asc],.pecb-table__sort-icon[data-sort=desc]{color:#fff}.pecb-table__checkbox-wrapper{display:inline-flex;align-items:center;justify-content:center;position:relative;cursor:pointer}.pecb-table__checkbox{position:absolute;opacity:0;width:0;height:0}.pecb-table__checkbox:checked+.pecb-table__checkbox-custom{background-color:#212427;border-color:#212427}.pecb-table__checkbox:checked+.pecb-table__checkbox-custom:after{display:block}.pecb-table__checkbox:indeterminate+.pecb-table__checkbox-custom{background-color:#212427;border-color:#212427}.pecb-table__checkbox:indeterminate+.pecb-table__checkbox-custom:before{display:block}.pecb-table__checkbox:indeterminate+.pecb-table__checkbox-custom:after{display:none}.pecb-table__checkbox:focus+.pecb-table__checkbox-custom{box-shadow:0 0 0 2px #21242733}.pecb-table__checkbox-custom{width:18px;height:18px;border:1.5px solid #c2c2c2;border-radius:6px;background-color:#fff;position:relative;transition:all .15s ease-in-out}.pecb-table__checkbox-custom:after{content:\"\";display:none;position:absolute;left:5px;top:2px;width:4px;height:8px;border:solid #ffffff;border-width:0 2px 2px 0;transform:rotate(45deg)}.pecb-table__checkbox-custom:before{content:\"\";display:none;position:absolute;left:3px;top:6px;width:8px;height:2px;background-color:#fff}.pecb-table__empty-content{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:16px 0}.pecb-table__empty-message{color:#919191;font-size:14px}.pecb-table__loading{display:flex;align-items:center;justify-content:center;padding:48px;min-height:200px}.pecb-table__loading-spinner{display:flex;flex-direction:column;align-items:center;gap:12px;color:#616161;font-size:14px;font-family:inherit;font-weight:400}.pecb-table__spinner-icon{width:32px;height:32px;animation:spin 1s linear infinite;color:#212427}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.pecb-table__pagination{padding:16px 0;margin-top:16px}.pecb-table__pagination--bordered{padding:16px 18px;background-color:#fff;border:1px solid #ececec;border-radius:6px}.pecb-table__user-cell{display:flex;align-items:center;gap:12px}.pecb-table__user-avatar{width:40px;height:40px;border-radius:9999px;object-fit:cover;flex-shrink:0}.pecb-table__user-info{display:flex;flex-direction:column;min-width:0}.pecb-table__user-name{font-weight:500;color:#313131;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pecb-table__user-secondary{font-size:12px;color:#919191;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pecb-table__action-buttons{display:flex;align-items:center;gap:0;justify-content:center}.pecb-table__action-divider{display:block;width:1px;height:16px;background-color:#c2c2c2;flex-shrink:0}.pecb-table__action-btn{display:flex;align-items:center;justify-content:center;width:30px;height:30px;border:none;background:transparent;color:#212427;cursor:pointer;border-radius:6px;transition:all .15s ease-in-out;padding:0}.pecb-table__action-btn:hover{background-color:#f7f5f5;color:#313131}.pecb-table__action-btn:focus{outline:none;box-shadow:0 0 0 2px #21242733}.pecb-table__action-btn:disabled{opacity:.5;cursor:not-allowed}.pecb-table__action-btn--danger:hover{background-color:#fdecea;color:#dc3545}.pecb-table__action-btn svg{width:18px;height:18px}.pecb-table__custom-icon{display:flex;align-items:center;justify-content:center}.pecb-table__custom-icon svg{width:18px;height:18px}.pecb-table__link{color:#212427;text-decoration:none;font-size:inherit}.pecb-table__link:hover{text-decoration:underline;color:#313131}.pecb-table__link:focus-visible{outline:2px solid #212427;outline-offset:2px;border-radius:2px}@media (min-width: 768px){.pecb-table--md,.pecb-table--lg{font-size:14px}.pecb-table--md .pecb-table__cell,.pecb-table--lg .pecb-table__cell{padding:12px 16px}.pecb-table--md .pecb-table__cell--header,.pecb-table--lg .pecb-table__cell--header{padding:8px 16px}.pecb-table__pagination{padding:12px 16px}}\n"] }]
4531
4533
  }], ctorParameters: () => [{ type: i1$1.DomSanitizer }, { type: i0.ChangeDetectorRef }], propDecorators: { columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], showHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "showHeader", required: false }] }], hoverable: [{ type: i0.Input, args: [{ isSignal: true, alias: "hoverable", required: false }] }], showPagination: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPagination", required: false }] }], totalItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalItems", required: false }] }], currentPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentPage", required: false }] }, { type: i0.Output, args: ["currentPageChange"] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }, { type: i0.Output, args: ["pageSizeChange"] }], pageSizeOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSizeOptions", required: false }] }], showPageSizeSelector: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPageSizeSelector", required: false }] }], paginationVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "paginationVariant", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], selectable: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectable", required: false }] }], selectedIndices: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedIndices", required: false }] }, { type: i0.Output, args: ["selectedIndicesChange"] }], stickyHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "stickyHeader", required: false }] }], maxHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxHeight", required: false }] }], minWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "minWidth", required: false }] }], wrapText: [{ type: i0.Input, args: [{ isSignal: true, alias: "wrapText", required: false }] }], sortColumn: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortColumn", required: false }] }, { type: i0.Output, args: ["sortColumnChange"] }], sortDirection: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortDirection", required: false }] }, { type: i0.Output, args: ["sortDirectionChange"] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-label", required: false }] }], loadingText: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingText", required: false }] }], selectAllAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectAllAriaLabel", required: false }] }], selectRowAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectRowAriaLabel", required: false }] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], rowClick: [{ type: i0.Output, args: ["rowClick"] }], rowAction: [{ type: i0.Output, args: ["rowAction"] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], emptyTemplate: [{
4532
4534
  type: ContentChild,
4533
4535
  args: ['emptyTemplate']
@@ -5591,41 +5593,32 @@ class RightModalComponent {
5591
5593
  this.ariaDescribedBy = input(...(ngDevMode ? [undefined, { debugName: "ariaDescribedBy" }] : []));
5592
5594
  this.closed = output();
5593
5595
  this.opened = output();
5594
- this.modalPanel = viewChild('modalPanel', ...(ngDevMode ? [{ debugName: "modalPanel" }] : []));
5595
- this.cleanupFocusTrap = null;
5596
- this.cleanupBodyScroll = null;
5596
+ this.overlay = inject(Overlay);
5597
+ this.viewContainerRef = inject(ViewContainerRef);
5598
+ // The panel + projected slots live in this template so they can be attached
5599
+ // to a CDK overlay (rendered in the global overlay container on <body>),
5600
+ // which escapes any `transform`/`filter` containing-block trap on ancestors.
5601
+ this.modalTemplate = viewChild('modalTemplate', ...(ngDevMode ? [{ debugName: "modalTemplate" }] : []));
5602
+ this.overlayRef = null;
5603
+ this.portal = null;
5604
+ this.subscriptions = new Subscription();
5597
5605
  this.previousActiveElement = null;
5598
- this.currentlyOpen = false;
5599
- // Drive the open/close side-effects (focus trap, body-scroll lock, focus
5600
- // restoration) reactively from the isOpen model so the modal behaves
5601
- // correctly on every toggle — not just the initial mount.
5606
+ // Open/close the overlay reactively from the isOpen model. The template
5607
+ // signal is read so the effect re-runs once the @ViewChild resolves (e.g.
5608
+ // when a consumer renders the modal already open).
5602
5609
  effect(() => {
5603
5610
  const open = this.isOpen();
5604
- const panel = this.modalPanel();
5605
- if (open) {
5606
- if (!this.currentlyOpen) {
5607
- this.currentlyOpen = true;
5608
- this.onOpen();
5609
- }
5610
- if (panel && !this.cleanupFocusTrap) {
5611
- this.setupFocusTrap(panel.nativeElement);
5612
- }
5611
+ const template = this.modalTemplate();
5612
+ if (open && template) {
5613
+ this.openOverlay(template);
5613
5614
  }
5614
- else if (this.currentlyOpen) {
5615
- this.currentlyOpen = false;
5616
- this.cleanup();
5617
- this.closed.emit();
5615
+ else if (!open) {
5616
+ this.closeOverlay();
5618
5617
  }
5619
5618
  });
5620
5619
  }
5621
5620
  ngOnDestroy() {
5622
- this.cleanup();
5623
- }
5624
- get modalClasses() {
5625
- return [
5626
- 'pecb-right-modal',
5627
- this.isOpen() ? 'pecb-right-modal--open' : ''
5628
- ].filter(Boolean);
5621
+ this.destroyOverlay();
5629
5622
  }
5630
5623
  get panelClasses() {
5631
5624
  return [
@@ -5633,49 +5626,71 @@ class RightModalComponent {
5633
5626
  `pecb-right-modal__panel--${this.size()}`
5634
5627
  ];
5635
5628
  }
5636
- onBackdropClick(event) {
5637
- if (this.closeOnBackdropClick() && event.target === event.currentTarget) {
5638
- this.close();
5639
- }
5640
- }
5641
- onKeydown(event) {
5642
- if (this.closeOnEscape() && event.key === 'Escape') {
5643
- event.preventDefault();
5644
- this.close();
5645
- }
5646
- }
5647
5629
  close() {
5648
- // The effect handles cleanup and the `closed` emit when isOpen flips to false.
5630
+ // The effect tears down the overlay and emits `closed` when isOpen flips.
5649
5631
  this.isOpen.set(false);
5650
5632
  }
5651
- onOpen() {
5633
+ openOverlay(template) {
5634
+ if (this.overlayRef) {
5635
+ return; // already open
5636
+ }
5652
5637
  this.previousActiveElement = document.activeElement;
5653
- this.cleanupBodyScroll = disableBodyScroll();
5638
+ this.overlayRef = this.overlay.create({
5639
+ hasBackdrop: true,
5640
+ backdropClass: 'pecb-right-modal__backdrop',
5641
+ scrollStrategy: this.overlay.scrollStrategies.block(),
5642
+ // Full-height drawer pinned to the right edge of the viewport.
5643
+ positionStrategy: this.overlay.position().global().top('0').right('0')
5644
+ });
5645
+ this.portal = new TemplatePortal(template, this.viewContainerRef);
5646
+ this.overlayRef.attach(this.portal);
5647
+ this.subscriptions = new Subscription();
5648
+ this.subscriptions.add(this.overlayRef.backdropClick().subscribe(() => {
5649
+ if (this.closeOnBackdropClick()) {
5650
+ this.close();
5651
+ }
5652
+ }));
5653
+ this.subscriptions.add(this.overlayRef.keydownEvents().subscribe((event) => {
5654
+ if (this.closeOnEscape() && event.key === 'Escape') {
5655
+ event.preventDefault();
5656
+ this.close();
5657
+ }
5658
+ }));
5659
+ // Focus the dialog panel itself (tabindex="-1") rather than the close
5660
+ // button, so opening the modal doesn't render a focus ring on the × icon.
5661
+ // cdkTrapFocus on the panel keeps Tab navigation contained within it.
5662
+ const panel = this.overlayRef.overlayElement.querySelector('.pecb-right-modal__panel');
5663
+ panel?.focus();
5654
5664
  this.opened.emit();
5655
5665
  }
5656
- setupFocusTrap(panel) {
5657
- this.cleanupFocusTrap = trapFocus(panel);
5658
- }
5659
- cleanup() {
5660
- if (this.cleanupFocusTrap) {
5661
- this.cleanupFocusTrap();
5662
- this.cleanupFocusTrap = null;
5663
- }
5664
- if (this.cleanupBodyScroll) {
5665
- this.cleanupBodyScroll();
5666
- this.cleanupBodyScroll = null;
5666
+ closeOverlay() {
5667
+ if (!this.overlayRef) {
5668
+ return;
5667
5669
  }
5670
+ this.destroyOverlay();
5668
5671
  if (this.previousActiveElement instanceof HTMLElement) {
5669
5672
  this.previousActiveElement.focus();
5670
5673
  }
5674
+ this.closed.emit();
5675
+ }
5676
+ destroyOverlay() {
5677
+ this.subscriptions.unsubscribe();
5678
+ if (this.portal?.isAttached) {
5679
+ this.portal.detach();
5680
+ }
5681
+ this.portal = null;
5682
+ if (this.overlayRef) {
5683
+ this.overlayRef.dispose();
5684
+ this.overlayRef = null;
5685
+ }
5671
5686
  }
5672
5687
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: RightModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
5673
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.3.16", type: RightModalComponent, isStandalone: true, selector: "pecb-right-modal", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, closeOnBackdropClick: { classPropertyName: "closeOnBackdropClick", publicName: "closeOnBackdropClick", isSignal: true, isRequired: false, transformFunction: null }, closeOnEscape: { classPropertyName: "closeOnEscape", publicName: "closeOnEscape", isSignal: true, isRequired: false, transformFunction: null }, showCloseButton: { classPropertyName: "showCloseButton", publicName: "showCloseButton", isSignal: true, isRequired: false, transformFunction: null }, closeAriaLabel: { classPropertyName: "closeAriaLabel", publicName: "closeAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledBy: { classPropertyName: "ariaLabelledBy", publicName: "ariaLabelledBy", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { isOpen: "isOpenChange", closed: "closed", opened: "opened" }, viewQueries: [{ propertyName: "modalPanel", first: true, predicate: ["modalPanel"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n *ngIf=\"isOpen()\"\n [id]=\"id()\"\n [class]=\"modalClasses.join(' ')\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-labelledby]=\"ariaLabelledBy()\"\n [attr.aria-describedby]=\"ariaDescribedBy()\"\n (click)=\"onBackdropClick($event)\"\n (keydown)=\"onKeydown($event)\"\n>\n <div\n #modalPanel\n [class]=\"panelClasses.join(' ')\"\n tabindex=\"-1\"\n >\n <button\n *ngIf=\"showCloseButton()\"\n type=\"button\"\n class=\"pecb-right-modal__close-btn\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"close()\"\n >\n <svg\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M18 6L6 18M6 6L18 18\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n\n <ng-content select=\"[pecbRightModalHeader]\"></ng-content>\n <ng-content select=\"[pecbRightModalContent]\"></ng-content>\n <ng-content></ng-content>\n <ng-content select=\"[pecbRightModalFooter]\"></ng-content>\n </div>\n</div>\n", styles: [".pecb-right-modal{position:fixed;inset:0;z-index:1050;display:flex;justify-content:flex-end;background-color:#00000080;animation:fadeIn .15s ease-in-out forwards}.pecb-right-modal--open .pecb-right-modal__panel{animation:slideInRight .25s ease-in-out forwards}.pecb-right-modal__panel{position:relative;display:inline-flex;min-width:516.23px;max-width:90vw;height:100%;padding:24px;flex-direction:column;align-items:flex-start;gap:16px;border-radius:24px 0 0 24px;background:#fff;box-shadow:0 5px 16px -4px #10182826;overflow:hidden}.pecb-right-modal__panel--sm{min-width:400px}.pecb-right-modal__panel--md{min-width:516.23px}.pecb-right-modal__panel--lg{min-width:700px}.pecb-right-modal__close-btn{padding:0;border:none;background:none;cursor:pointer;font:inherit;color:inherit;position:absolute;top:24px;right:24px;display:flex;align-items:center;justify-content:center;width:24px;height:24px;color:#616161;border-radius:6px;transition-property:color,background-color;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-right-modal__close-btn:focus{outline:none}.pecb-right-modal__close-btn:focus-visible{outline:2px solid #a11e29;outline-offset:2px}.pecb-right-modal__close-btn:hover{color:#313131;background-color:#ececec}.pecb-right-modal__close-btn svg{width:20px;height:20px}.pecb-right-modal__header{position:sticky;top:0;z-index:10;width:100%;padding-right:32px;background:#fff}.pecb-right-modal__content{flex:1;min-height:0;width:100%;overflow-y:auto}.pecb-right-modal__content::-webkit-scrollbar{width:8px;height:8px}.pecb-right-modal__content::-webkit-scrollbar-track{background:#ececec}.pecb-right-modal__content::-webkit-scrollbar-thumb{background:#c2c2c2;border-radius:12px}.pecb-right-modal__content::-webkit-scrollbar-thumb:hover{background:#a9a9a9}.pecb-right-modal__footer{position:sticky;bottom:0;width:100%;margin-top:auto;background:#fff}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes slideInRight{0%{transform:translate(100%)}to{transform:translate(0)}}@media (prefers-reduced-motion: reduce){.pecb-right-modal,.pecb-right-modal__panel{animation:none}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
5688
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.3.16", type: RightModalComponent, isStandalone: true, selector: "pecb-right-modal", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, closeOnBackdropClick: { classPropertyName: "closeOnBackdropClick", publicName: "closeOnBackdropClick", isSignal: true, isRequired: false, transformFunction: null }, closeOnEscape: { classPropertyName: "closeOnEscape", publicName: "closeOnEscape", isSignal: true, isRequired: false, transformFunction: null }, showCloseButton: { classPropertyName: "showCloseButton", publicName: "showCloseButton", isSignal: true, isRequired: false, transformFunction: null }, closeAriaLabel: { classPropertyName: "closeAriaLabel", publicName: "closeAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledBy: { classPropertyName: "ariaLabelledBy", publicName: "ariaLabelledBy", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { isOpen: "isOpenChange", closed: "closed", opened: "opened" }, viewQueries: [{ propertyName: "modalTemplate", first: true, predicate: ["modalTemplate"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-template #modalTemplate>\n <div\n [id]=\"id()\"\n [class]=\"panelClasses.join(' ')\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-labelledby]=\"ariaLabelledBy()\"\n [attr.aria-describedby]=\"ariaDescribedBy()\"\n tabindex=\"-1\"\n cdkTrapFocus\n >\n <button\n *ngIf=\"showCloseButton()\"\n type=\"button\"\n class=\"pecb-right-modal__close-btn\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"close()\"\n >\n <svg\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M18 6L6 18M6 6L18 18\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n\n <ng-content select=\"[pecbRightModalHeader]\"></ng-content>\n <ng-content select=\"[pecbRightModalContent]\"></ng-content>\n <ng-content></ng-content>\n <ng-content select=\"[pecbRightModalFooter]\"></ng-content>\n </div>\n</ng-template>\n", styles: [".pecb-right-modal__backdrop{background-color:#00000080}.pecb-right-modal__panel{position:relative;display:flex;width:516.23px;max-width:100vw;height:100vh;padding:24px;animation:slideInRight .25s ease-in-out forwards;flex-direction:column;align-items:stretch;gap:16px;border-radius:24px 0 0 24px;background:#fff;box-shadow:0 5px 16px -4px #10182826;overflow:hidden}.pecb-right-modal__panel:focus{outline:none}.pecb-right-modal__panel--sm{width:400px}.pecb-right-modal__panel--md{width:516.23px}.pecb-right-modal__panel--lg{width:700px}.pecb-right-modal__close-btn{padding:0;border:none;background:none;cursor:pointer;font:inherit;color:inherit;position:absolute;top:24px;right:24px;z-index:20;display:flex;align-items:center;justify-content:center;width:24px;height:24px;color:#616161;border-radius:6px;transition-property:color,background-color;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-right-modal__close-btn:focus{outline:none}.pecb-right-modal__close-btn:focus-visible{outline:2px solid #a11e29;outline-offset:2px}.pecb-right-modal__close-btn:hover{color:#313131;background-color:#ececec}.pecb-right-modal__close-btn svg{width:20px;height:20px}.pecb-right-modal__header{position:sticky;top:0;z-index:10;width:100%;padding-right:32px;background:#fff}.pecb-right-modal__content{flex:1;min-height:0;min-width:0;width:100%;display:flex;flex-direction:column;align-items:stretch;overflow:auto}.pecb-right-modal__content::-webkit-scrollbar{width:8px;height:8px}.pecb-right-modal__content::-webkit-scrollbar-track{background:#ececec}.pecb-right-modal__content::-webkit-scrollbar-thumb{background:#c2c2c2;border-radius:12px}.pecb-right-modal__content::-webkit-scrollbar-thumb:hover{background:#a9a9a9}.pecb-right-modal__footer{position:sticky;bottom:0;width:100%;margin-top:auto;background:#fff}@keyframes slideInRight{0%{transform:translate(100%)}to{transform:translate(0)}}@media (prefers-reduced-motion: reduce){.pecb-right-modal__panel{animation:none}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
5674
5689
  }
5675
5690
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: RightModalComponent, decorators: [{
5676
5691
  type: Component,
5677
- args: [{ selector: 'pecb-right-modal', imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n *ngIf=\"isOpen()\"\n [id]=\"id()\"\n [class]=\"modalClasses.join(' ')\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-labelledby]=\"ariaLabelledBy()\"\n [attr.aria-describedby]=\"ariaDescribedBy()\"\n (click)=\"onBackdropClick($event)\"\n (keydown)=\"onKeydown($event)\"\n>\n <div\n #modalPanel\n [class]=\"panelClasses.join(' ')\"\n tabindex=\"-1\"\n >\n <button\n *ngIf=\"showCloseButton()\"\n type=\"button\"\n class=\"pecb-right-modal__close-btn\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"close()\"\n >\n <svg\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M18 6L6 18M6 6L18 18\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n\n <ng-content select=\"[pecbRightModalHeader]\"></ng-content>\n <ng-content select=\"[pecbRightModalContent]\"></ng-content>\n <ng-content></ng-content>\n <ng-content select=\"[pecbRightModalFooter]\"></ng-content>\n </div>\n</div>\n", styles: [".pecb-right-modal{position:fixed;inset:0;z-index:1050;display:flex;justify-content:flex-end;background-color:#00000080;animation:fadeIn .15s ease-in-out forwards}.pecb-right-modal--open .pecb-right-modal__panel{animation:slideInRight .25s ease-in-out forwards}.pecb-right-modal__panel{position:relative;display:inline-flex;min-width:516.23px;max-width:90vw;height:100%;padding:24px;flex-direction:column;align-items:flex-start;gap:16px;border-radius:24px 0 0 24px;background:#fff;box-shadow:0 5px 16px -4px #10182826;overflow:hidden}.pecb-right-modal__panel--sm{min-width:400px}.pecb-right-modal__panel--md{min-width:516.23px}.pecb-right-modal__panel--lg{min-width:700px}.pecb-right-modal__close-btn{padding:0;border:none;background:none;cursor:pointer;font:inherit;color:inherit;position:absolute;top:24px;right:24px;display:flex;align-items:center;justify-content:center;width:24px;height:24px;color:#616161;border-radius:6px;transition-property:color,background-color;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-right-modal__close-btn:focus{outline:none}.pecb-right-modal__close-btn:focus-visible{outline:2px solid #a11e29;outline-offset:2px}.pecb-right-modal__close-btn:hover{color:#313131;background-color:#ececec}.pecb-right-modal__close-btn svg{width:20px;height:20px}.pecb-right-modal__header{position:sticky;top:0;z-index:10;width:100%;padding-right:32px;background:#fff}.pecb-right-modal__content{flex:1;min-height:0;width:100%;overflow-y:auto}.pecb-right-modal__content::-webkit-scrollbar{width:8px;height:8px}.pecb-right-modal__content::-webkit-scrollbar-track{background:#ececec}.pecb-right-modal__content::-webkit-scrollbar-thumb{background:#c2c2c2;border-radius:12px}.pecb-right-modal__content::-webkit-scrollbar-thumb:hover{background:#a9a9a9}.pecb-right-modal__footer{position:sticky;bottom:0;width:100%;margin-top:auto;background:#fff}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes slideInRight{0%{transform:translate(100%)}to{transform:translate(0)}}@media (prefers-reduced-motion: reduce){.pecb-right-modal,.pecb-right-modal__panel{animation:none}}\n"] }]
5678
- }], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], isOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "isOpen", required: false }] }, { type: i0.Output, args: ["isOpenChange"] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], closeOnBackdropClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnBackdropClick", required: false }] }], closeOnEscape: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnEscape", required: false }] }], showCloseButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCloseButton", required: false }] }], closeAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeAriaLabel", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], ariaLabelledBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelledBy", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], opened: [{ type: i0.Output, args: ["opened"] }], modalPanel: [{ type: i0.ViewChild, args: ['modalPanel', { isSignal: true }] }] } });
5692
+ args: [{ selector: 'pecb-right-modal', imports: [CommonModule, CdkTrapFocus], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<ng-template #modalTemplate>\n <div\n [id]=\"id()\"\n [class]=\"panelClasses.join(' ')\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-labelledby]=\"ariaLabelledBy()\"\n [attr.aria-describedby]=\"ariaDescribedBy()\"\n tabindex=\"-1\"\n cdkTrapFocus\n >\n <button\n *ngIf=\"showCloseButton()\"\n type=\"button\"\n class=\"pecb-right-modal__close-btn\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"close()\"\n >\n <svg\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M18 6L6 18M6 6L18 18\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n\n <ng-content select=\"[pecbRightModalHeader]\"></ng-content>\n <ng-content select=\"[pecbRightModalContent]\"></ng-content>\n <ng-content></ng-content>\n <ng-content select=\"[pecbRightModalFooter]\"></ng-content>\n </div>\n</ng-template>\n", styles: [".pecb-right-modal__backdrop{background-color:#00000080}.pecb-right-modal__panel{position:relative;display:flex;width:516.23px;max-width:100vw;height:100vh;padding:24px;animation:slideInRight .25s ease-in-out forwards;flex-direction:column;align-items:stretch;gap:16px;border-radius:24px 0 0 24px;background:#fff;box-shadow:0 5px 16px -4px #10182826;overflow:hidden}.pecb-right-modal__panel:focus{outline:none}.pecb-right-modal__panel--sm{width:400px}.pecb-right-modal__panel--md{width:516.23px}.pecb-right-modal__panel--lg{width:700px}.pecb-right-modal__close-btn{padding:0;border:none;background:none;cursor:pointer;font:inherit;color:inherit;position:absolute;top:24px;right:24px;z-index:20;display:flex;align-items:center;justify-content:center;width:24px;height:24px;color:#616161;border-radius:6px;transition-property:color,background-color;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-right-modal__close-btn:focus{outline:none}.pecb-right-modal__close-btn:focus-visible{outline:2px solid #a11e29;outline-offset:2px}.pecb-right-modal__close-btn:hover{color:#313131;background-color:#ececec}.pecb-right-modal__close-btn svg{width:20px;height:20px}.pecb-right-modal__header{position:sticky;top:0;z-index:10;width:100%;padding-right:32px;background:#fff}.pecb-right-modal__content{flex:1;min-height:0;min-width:0;width:100%;display:flex;flex-direction:column;align-items:stretch;overflow:auto}.pecb-right-modal__content::-webkit-scrollbar{width:8px;height:8px}.pecb-right-modal__content::-webkit-scrollbar-track{background:#ececec}.pecb-right-modal__content::-webkit-scrollbar-thumb{background:#c2c2c2;border-radius:12px}.pecb-right-modal__content::-webkit-scrollbar-thumb:hover{background:#a9a9a9}.pecb-right-modal__footer{position:sticky;bottom:0;width:100%;margin-top:auto;background:#fff}@keyframes slideInRight{0%{transform:translate(100%)}to{transform:translate(0)}}@media (prefers-reduced-motion: reduce){.pecb-right-modal__panel{animation:none}}\n"] }]
5693
+ }], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], isOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "isOpen", required: false }] }, { type: i0.Output, args: ["isOpenChange"] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], closeOnBackdropClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnBackdropClick", required: false }] }], closeOnEscape: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnEscape", required: false }] }], showCloseButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCloseButton", required: false }] }], closeAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeAriaLabel", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], ariaLabelledBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelledBy", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], opened: [{ type: i0.Output, args: ["opened"] }], modalTemplate: [{ type: i0.ViewChild, args: ['modalTemplate', { isSignal: true }] }] } });
5679
5694
  class RightModalHeaderDirective {
5680
5695
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: RightModalHeaderDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
5681
5696
  static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.16", type: RightModalHeaderDirective, isStandalone: true, selector: "[pecbRightModalHeader]", host: { classAttribute: "pecb-right-modal__header" }, ngImport: i0 }); }
@@ -5845,31 +5860,29 @@ class ConfirmationComponent {
5845
5860
  this.confirmed = output();
5846
5861
  this.cancelled = output();
5847
5862
  this.closed = output();
5848
- this.cleanupFocusTrap = null;
5849
- this.cleanupBodyScroll = null;
5863
+ this.overlay = inject(Overlay);
5864
+ this.viewContainerRef = inject(ViewContainerRef);
5865
+ // The dialog panel lives in this template so it can be attached to a CDK
5866
+ // overlay (rendered in the global overlay container on <body>), which escapes
5867
+ // any `transform`/`filter` containing-block trap on an ancestor.
5868
+ this.dialogTemplate = viewChild('dialogTemplate', ...(ngDevMode ? [{ debugName: "dialogTemplate" }] : []));
5869
+ this.overlayRef = null;
5870
+ this.portal = null;
5871
+ this.subscriptions = new Subscription();
5850
5872
  this.previousActiveElement = null;
5851
5873
  effect(() => {
5852
- if (this.isOpen()) {
5853
- this.onOpen();
5874
+ const open = this.isOpen();
5875
+ const template = this.dialogTemplate();
5876
+ if (open && template) {
5877
+ this.openOverlay(template);
5854
5878
  }
5855
- else {
5856
- this.cleanup();
5879
+ else if (!open) {
5880
+ this.destroyOverlay();
5857
5881
  }
5858
5882
  });
5859
5883
  }
5860
- ngAfterViewInit() {
5861
- if (this.isOpen()) {
5862
- this.setupFocusTrap();
5863
- }
5864
- }
5865
5884
  ngOnDestroy() {
5866
- this.cleanup();
5867
- }
5868
- get dialogClasses() {
5869
- return [
5870
- 'pecb-confirmation',
5871
- this.isOpen() ? 'pecb-confirmation--open' : ''
5872
- ].filter(Boolean);
5885
+ this.destroyOverlay();
5873
5886
  }
5874
5887
  get iconContainerClass() {
5875
5888
  return `pecb-confirmation__icon pecb-confirmation__icon--${this.iconType()}`;
@@ -5880,17 +5893,6 @@ class ConfirmationComponent {
5880
5893
  get messageId() {
5881
5894
  return `${this.id()}-message`;
5882
5895
  }
5883
- onBackdropClick(event) {
5884
- if (this.closeOnBackdropClick() && event.target === event.currentTarget) {
5885
- this.close();
5886
- }
5887
- }
5888
- onKeydown(event) {
5889
- if (this.closeOnEscape() && event.key === 'Escape') {
5890
- event.preventDefault();
5891
- this.close();
5892
- }
5893
- }
5894
5896
  onConfirm() {
5895
5897
  this.confirmed.emit();
5896
5898
  this.close();
@@ -5903,39 +5905,58 @@ class ConfirmationComponent {
5903
5905
  this.isOpen.set(false);
5904
5906
  this.closed.emit();
5905
5907
  }
5906
- onOpen() {
5907
- this.previousActiveElement = document.activeElement;
5908
- this.cleanupBodyScroll = disableBodyScroll();
5909
- this.setupFocusTrap();
5910
- }
5911
- setupFocusTrap() {
5912
- if (this.dialogPanel?.nativeElement && !this.cleanupFocusTrap) {
5913
- this.cleanupFocusTrap = trapFocus(this.dialogPanel.nativeElement);
5914
- }
5915
- }
5916
- cleanup() {
5917
- if (this.cleanupFocusTrap) {
5918
- this.cleanupFocusTrap();
5919
- this.cleanupFocusTrap = null;
5908
+ openOverlay(template) {
5909
+ if (this.overlayRef) {
5910
+ return; // already open
5920
5911
  }
5921
- if (this.cleanupBodyScroll) {
5922
- this.cleanupBodyScroll();
5923
- this.cleanupBodyScroll = null;
5912
+ this.previousActiveElement = document.activeElement;
5913
+ this.overlayRef = this.overlay.create({
5914
+ hasBackdrop: true,
5915
+ backdropClass: 'pecb-confirmation__backdrop',
5916
+ scrollStrategy: this.overlay.scrollStrategies.block(),
5917
+ // Centered dialog.
5918
+ positionStrategy: this.overlay.position().global().centerHorizontally().centerVertically()
5919
+ });
5920
+ this.portal = new TemplatePortal(template, this.viewContainerRef);
5921
+ this.overlayRef.attach(this.portal);
5922
+ this.subscriptions = new Subscription();
5923
+ this.subscriptions.add(this.overlayRef.backdropClick().subscribe(() => {
5924
+ if (this.closeOnBackdropClick()) {
5925
+ this.close();
5926
+ }
5927
+ }));
5928
+ this.subscriptions.add(this.overlayRef.keydownEvents().subscribe((event) => {
5929
+ if (this.closeOnEscape() && event.key === 'Escape') {
5930
+ event.preventDefault();
5931
+ this.close();
5932
+ }
5933
+ }));
5934
+ // Focus the dialog panel itself (tabindex="-1"); cdkTrapFocus keeps Tab
5935
+ // navigation contained within the dialog.
5936
+ const panel = this.overlayRef.overlayElement.querySelector('.pecb-confirmation__panel');
5937
+ panel?.focus();
5938
+ }
5939
+ destroyOverlay() {
5940
+ this.subscriptions.unsubscribe();
5941
+ if (this.portal?.isAttached) {
5942
+ this.portal.detach();
5924
5943
  }
5925
- if (this.previousActiveElement instanceof HTMLElement) {
5926
- this.previousActiveElement.focus();
5944
+ this.portal = null;
5945
+ if (this.overlayRef) {
5946
+ this.overlayRef.dispose();
5947
+ this.overlayRef = null;
5948
+ if (this.previousActiveElement instanceof HTMLElement) {
5949
+ this.previousActiveElement.focus();
5950
+ }
5927
5951
  }
5928
5952
  }
5929
5953
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ConfirmationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
5930
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: ConfirmationComponent, isStandalone: true, selector: "pecb-confirmation", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, confirmText: { classPropertyName: "confirmText", publicName: "confirmText", isSignal: true, isRequired: false, transformFunction: null }, cancelText: { classPropertyName: "cancelText", publicName: "cancelText", isSignal: true, isRequired: false, transformFunction: null }, iconType: { classPropertyName: "iconType", publicName: "iconType", isSignal: true, isRequired: false, transformFunction: null }, showCloseButton: { classPropertyName: "showCloseButton", publicName: "showCloseButton", isSignal: true, isRequired: false, transformFunction: null }, closeAriaLabel: { classPropertyName: "closeAriaLabel", publicName: "closeAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, closeOnBackdropClick: { classPropertyName: "closeOnBackdropClick", publicName: "closeOnBackdropClick", isSignal: true, isRequired: false, transformFunction: null }, closeOnEscape: { classPropertyName: "closeOnEscape", publicName: "closeOnEscape", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { isOpen: "isOpenChange", confirmed: "confirmed", cancelled: "cancelled", closed: "closed" }, viewQueries: [{ propertyName: "dialogPanel", first: true, predicate: ["dialogPanel"], descendants: true }], ngImport: i0, template: "@if (isOpen()) {\n <div\n [id]=\"id()\"\n [class]=\"dialogClasses.join(' ')\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-labelledby]=\"titleId\"\n [attr.aria-describedby]=\"message() ? messageId : ariaDescribedBy() || null\"\n [attr.aria-label]=\"ariaLabel() || null\"\n (click)=\"onBackdropClick($event)\"\n (keydown)=\"onKeydown($event)\"\n >\n <div\n #dialogPanel\n class=\"pecb-confirmation__panel\"\n tabindex=\"-1\"\n >\n \n @if (showCloseButton()) {\n <div class=\"pecb-confirmation__header\">\n <button\n type=\"button\"\n class=\"pecb-confirmation__close-btn\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"close()\"\n >\n <svg\n width=\"10\"\n height=\"10\"\n viewBox=\"0 0 10 10\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M9 1L1 9M1 1L9 9\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n </div>\n }\n\n \n <div [class]=\"iconContainerClass\" aria-hidden=\"true\">\n @switch (iconType()) {\n @case ('success') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\">\n <path d=\"M0 12C0 5.37258 5.37258 0 12 0H52C58.6274 0 64 5.37258 64 12V52C64 58.6274 58.6274 64 52 64H12C5.37258 64 0 58.6274 0 52V12Z\" fill=\"#E6F4EA\"/>\n <circle cx=\"31.8243\" cy=\"31.8243\" r=\"18.2857\" fill=\"white\"/>\n <path d=\"M31.9991 9.84625C19.7635 9.84625 9.84521 19.7645 9.84521 32.0001C9.84521 44.2357 19.7635 54.1539 31.9991 54.1539C44.2347 54.1539 54.1529 44.2357 54.1529 32.0001C54.1426 19.7645 44.2244 9.84625 31.9991 9.84625ZM43.8227 26.3458L31.8137 41.3725C31.2781 42.0316 30.4954 42.4436 29.6508 42.4951C29.5993 42.4951 29.5375 42.4951 29.486 42.4951C28.693 42.4951 27.9308 42.1861 27.3747 41.6197L20.3917 34.6367C19.2279 33.4729 19.2279 31.5778 20.3917 30.414C21.5555 29.2502 23.4506 29.2502 24.6144 30.414L29.2285 35.0281L39.1571 22.6174C40.187 21.33 42.0615 21.1137 43.3592 22.1539C44.6466 23.1736 44.8526 25.0584 43.8227 26.3458Z\" fill=\"#57BC57\"/>\n </svg>\n }\n @case ('warning') {\n <svg width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M0 12C0 5.37258 5.37258 0 12 0H52C58.6274 0 64 5.37258 64 12V52C64 58.6274 58.6274 64 52 64H12C5.37258 64 0 58.6274 0 52V12Z\" fill=\"#FFF4E5\"/>\n <circle cx=\"32.5303\" cy=\"32.5281\" r=\"18.989\" fill=\"white\"/>\n <path d=\"M32.0012 9.84668C44.2362 9.84668 54.1543 19.765 54.1545 32C54.1545 44.2352 44.2364 54.1543 32.0012 54.1543C19.766 54.1543 9.8479 44.2352 9.8479 32C9.84815 19.765 19.7662 9.84668 32.0012 9.84668ZM32.0823 40.2266C30.0029 40.2266 28.3108 41.9187 28.3108 43.998C28.5007 48.9935 35.6636 48.9922 35.8528 43.998C35.8528 41.9188 34.1615 40.2266 32.0823 40.2266ZM35.7727 18.9727C33.9014 16.6513 30.1067 16.6501 28.2346 18.9727C27.2974 20.0886 26.9044 21.5514 27.1555 22.9863C27.9048 27.2673 28.9622 33.3099 29.4368 36.0234C29.9687 38.8793 34.0404 38.8785 34.5715 36.0234L36.8528 22.9863C37.1039 21.5514 36.7101 20.0887 35.7727 18.9727Z\" fill=\"#FF9C4A\"/>\n </svg>\n }\n @case ('error') {\n <svg width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M0 12C0 5.37258 5.37258 0 12 0H52C58.6274 0 64 5.37258 64 12V52C64 58.6274 58.6274 64 52 64H12C5.37258 64 0 58.6274 0 52V12Z\" fill=\"#FDECEA\"/>\n <circle cx=\"31.8243\" cy=\"31.8243\" r=\"18.2857\" fill=\"white\"/>\n <path d=\"M47.6652 16.3349C56.3168 24.9865 56.3168 39.0136 47.6652 47.6652C39.0136 56.3168 24.9865 56.3168 16.3349 47.6652C7.68329 39.0136 7.68329 24.9865 16.3349 16.3349C24.9865 7.68329 39.0136 7.68329 47.6652 16.3349ZM40.2677 22.862C39.3666 21.9609 37.9053 21.9609 37.0042 22.862L31.4561 28.4101C31.2759 28.5903 30.9836 28.5903 30.8034 28.4101L25.2553 22.862C24.3542 21.9609 22.8929 21.9609 21.9918 22.862C21.0906 23.7632 21.0906 25.2245 21.9918 26.1256L27.5398 31.6737C27.7201 31.8539 27.7201 32.1462 27.5398 32.3264L21.9918 37.8745C21.0906 38.7756 21.0906 40.2369 21.9918 41.138C22.8929 42.0392 24.3542 42.0392 25.2553 41.138L30.8034 35.59C30.9836 35.4097 31.2759 35.4097 31.4561 35.59L37.0042 41.138C37.9053 42.0392 39.3666 42.0392 40.2677 41.138C41.1689 40.2369 41.1689 38.7756 40.2677 37.8745L34.7197 32.3264C34.5394 32.1462 34.5394 31.8539 34.7197 31.6737L40.2677 26.1256C41.1689 25.2245 41.1689 23.7632 40.2677 22.862Z\" fill=\"#FF606F\"/>\n </svg>\n }\n @case ('info') {\n <svg width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M0 12C0 5.37258 5.37258 0 12 0H52C58.6274 0 64 5.37258 64 12V52C64 58.6274 58.6274 64 52 64H12C5.37258 64 0 58.6274 0 52V12Z\" fill=\"#E6F0FA\"/>\n <circle cx=\"32.5283\" cy=\"32.5281\" r=\"18.989\" fill=\"white\"/>\n <path d=\"M31.9995 9.84668C44.2348 9.84668 54.1534 19.7653 54.1534 32.0005C54.1534 44.2358 44.2348 54.1544 31.9995 54.1544C19.7643 54.1544 9.8457 44.2358 9.8457 32.0005C9.8457 19.7653 19.7643 9.84668 31.9995 9.84668ZM28.3072 26.0943C26.8347 26.0945 25.8457 27.0832 25.8457 28.5558C25.8459 30.0282 26.8349 31.0171 28.3072 31.0174H31.758L28.3072 42.5835C28.0603 43.8088 28.3077 45.2811 29.0488 46.2698C29.7903 47.4952 31.0265 47.9896 32.252 47.9897H36.187C37.67 47.9896 38.6486 47.0112 38.6486 45.5282C38.6484 44.0558 37.6595 43.0668 36.187 43.0666H33.2303L36.681 31.5005C36.9281 30.2751 36.6808 28.8028 35.9395 27.8142C35.1979 26.5886 33.9619 26.0943 32.7363 26.0943H28.3072ZM33.4779 16.0005C31.4387 16.0006 29.7904 17.6488 29.7904 19.688C29.7906 21.7271 31.4388 23.3743 33.4779 23.3743C35.5171 23.3743 37.1652 21.7271 37.1654 19.688C37.1654 17.6488 35.5172 16.0005 33.4779 16.0005Z\" fill=\"#5EACFF\"/>\n </svg>\n }\n }\n </div>\n\n \n <div class=\"pecb-confirmation__content\">\n <h2 [id]=\"titleId\" class=\"pecb-confirmation__title\">{{ title() }}</h2>\n\n @if (message()) {\n <p [id]=\"messageId\" class=\"pecb-confirmation__message\">{{ message() }}</p>\n }\n </div>\n\n \n <div class=\"pecb-confirmation__actions\">\n <button\n type=\"button\"\n class=\"pecb-confirmation__btn pecb-confirmation__btn--confirm\"\n (click)=\"onConfirm()\"\n >\n {{ confirmText() }}\n </button>\n <button\n type=\"button\"\n class=\"pecb-confirmation__btn pecb-confirmation__btn--cancel\"\n (click)=\"onCancel()\"\n >\n {{ cancelText() }}\n </button>\n </div>\n </div>\n </div>\n}\n", styles: [".pecb-confirmation{position:fixed;inset:0;z-index:1050;display:flex;align-items:center;justify-content:center;background-color:#00000080;animation:fadeIn .15s ease-in-out forwards}.pecb-confirmation__panel{display:flex;width:616px;max-width:calc(100vw - 32px);padding:22px 20px 30px;flex-direction:column;align-items:center;gap:27px;background:#fff;border-radius:12px;box-shadow:0 10px 10px #0000000d;animation:scaleIn .25s ease-in-out forwards;outline:none}.pecb-confirmation__header{display:flex;justify-content:flex-end;align-items:center;width:100%;height:20px}.pecb-confirmation__close-btn{padding:0;border:none;background:none;cursor:pointer;font:inherit;color:inherit;display:flex;align-items:center;justify-content:center;width:20px;height:20px;color:#616161;border-radius:6px;outline:none;transition-property:color,background-color;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-confirmation__close-btn:focus{outline:none}.pecb-confirmation__close-btn:hover{color:#313131;background-color:#ececec}.pecb-confirmation__close-btn svg{width:10px;height:10px}.pecb-confirmation__icon{display:flex;align-items:center;justify-content:center;width:64px;height:64px;border-radius:12px;flex-shrink:0}.pecb-confirmation__icon--success{background-color:transparent}.pecb-confirmation__icon--warning{background-color:#fff4e5}.pecb-confirmation__icon--error{background-color:#fdecea}.pecb-confirmation__icon--info{background-color:#e6f0fa}.pecb-confirmation__content{display:flex;flex-direction:column;align-items:center;gap:9px;padding:0 17px;width:100%}.pecb-confirmation__title{margin:0;font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:24px;font-weight:700;line-height:1.2;color:#313131;text-align:center}.pecb-confirmation__message{margin:0;font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:16px;font-weight:400;line-height:1.6;color:#616161;text-align:center}.pecb-confirmation__actions{display:flex;align-items:center;justify-content:center;gap:16px;width:100%}.pecb-confirmation__btn{border:none;background:none;font:inherit;color:inherit;display:inline-flex;align-items:center;justify-content:center;height:38px;padding:10px 40px;border-radius:6px;font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;font-style:normal;font-weight:500;line-height:normal;cursor:pointer;white-space:nowrap;transition-property:background-color,border-color,color;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-confirmation__btn:focus{outline:none}.pecb-confirmation__btn:focus-visible{outline:2px solid #a11e29;outline-offset:2px}.pecb-confirmation__btn--confirm{background-color:#212427;color:#fff;border:1px solid #212427}.pecb-confirmation__btn--confirm:hover{background-color:#616161;border-color:#616161}.pecb-confirmation__btn--confirm:active{background-color:#000;border-color:#000}.pecb-confirmation__btn--cancel{background-color:#fff;color:#212427;border:1px solid #c2c2c2}.pecb-confirmation__btn--cancel:hover{background-color:#ececec;border-color:#616161}.pecb-confirmation__btn--cancel:active{background-color:#c2c2c2;border-color:#616161}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes scaleIn{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}@media (prefers-reduced-motion: reduce){.pecb-confirmation,.pecb-confirmation__panel{animation:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
5954
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: ConfirmationComponent, isStandalone: true, selector: "pecb-confirmation", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, confirmText: { classPropertyName: "confirmText", publicName: "confirmText", isSignal: true, isRequired: false, transformFunction: null }, cancelText: { classPropertyName: "cancelText", publicName: "cancelText", isSignal: true, isRequired: false, transformFunction: null }, iconType: { classPropertyName: "iconType", publicName: "iconType", isSignal: true, isRequired: false, transformFunction: null }, showCloseButton: { classPropertyName: "showCloseButton", publicName: "showCloseButton", isSignal: true, isRequired: false, transformFunction: null }, closeAriaLabel: { classPropertyName: "closeAriaLabel", publicName: "closeAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, closeOnBackdropClick: { classPropertyName: "closeOnBackdropClick", publicName: "closeOnBackdropClick", isSignal: true, isRequired: false, transformFunction: null }, closeOnEscape: { classPropertyName: "closeOnEscape", publicName: "closeOnEscape", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { isOpen: "isOpenChange", confirmed: "confirmed", cancelled: "cancelled", closed: "closed" }, viewQueries: [{ propertyName: "dialogTemplate", first: true, predicate: ["dialogTemplate"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-template #dialogTemplate>\n <div\n #dialogPanel\n [id]=\"id()\"\n class=\"pecb-confirmation__panel\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-labelledby]=\"titleId\"\n [attr.aria-describedby]=\"message() ? messageId : ariaDescribedBy() || null\"\n [attr.aria-label]=\"ariaLabel() || null\"\n tabindex=\"-1\"\n cdkTrapFocus\n >\n @if (showCloseButton()) {\n <div class=\"pecb-confirmation__header\">\n <button\n type=\"button\"\n class=\"pecb-confirmation__close-btn\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"close()\"\n >\n <svg\n width=\"10\"\n height=\"10\"\n viewBox=\"0 0 10 10\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M9 1L1 9M1 1L9 9\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n </div>\n }\n\n <div [class]=\"iconContainerClass\" aria-hidden=\"true\">\n @switch (iconType()) {\n @case ('success') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\">\n <path d=\"M0 12C0 5.37258 5.37258 0 12 0H52C58.6274 0 64 5.37258 64 12V52C64 58.6274 58.6274 64 52 64H12C5.37258 64 0 58.6274 0 52V12Z\" fill=\"#E6F4EA\"/>\n <circle cx=\"31.8243\" cy=\"31.8243\" r=\"18.2857\" fill=\"white\"/>\n <path d=\"M31.9991 9.84625C19.7635 9.84625 9.84521 19.7645 9.84521 32.0001C9.84521 44.2357 19.7635 54.1539 31.9991 54.1539C44.2347 54.1539 54.1529 44.2357 54.1529 32.0001C54.1426 19.7645 44.2244 9.84625 31.9991 9.84625ZM43.8227 26.3458L31.8137 41.3725C31.2781 42.0316 30.4954 42.4436 29.6508 42.4951C29.5993 42.4951 29.5375 42.4951 29.486 42.4951C28.693 42.4951 27.9308 42.1861 27.3747 41.6197L20.3917 34.6367C19.2279 33.4729 19.2279 31.5778 20.3917 30.414C21.5555 29.2502 23.4506 29.2502 24.6144 30.414L29.2285 35.0281L39.1571 22.6174C40.187 21.33 42.0615 21.1137 43.3592 22.1539C44.6466 23.1736 44.8526 25.0584 43.8227 26.3458Z\" fill=\"#57BC57\"/>\n </svg>\n }\n @case ('warning') {\n <svg width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M0 12C0 5.37258 5.37258 0 12 0H52C58.6274 0 64 5.37258 64 12V52C64 58.6274 58.6274 64 52 64H12C5.37258 64 0 58.6274 0 52V12Z\" fill=\"#FFF4E5\"/>\n <circle cx=\"32.5303\" cy=\"32.5281\" r=\"18.989\" fill=\"white\"/>\n <path d=\"M32.0012 9.84668C44.2362 9.84668 54.1543 19.765 54.1545 32C54.1545 44.2352 44.2364 54.1543 32.0012 54.1543C19.766 54.1543 9.8479 44.2352 9.8479 32C9.84815 19.765 19.7662 9.84668 32.0012 9.84668ZM32.0823 40.2266C30.0029 40.2266 28.3108 41.9187 28.3108 43.998C28.5007 48.9935 35.6636 48.9922 35.8528 43.998C35.8528 41.9188 34.1615 40.2266 32.0823 40.2266ZM35.7727 18.9727C33.9014 16.6513 30.1067 16.6501 28.2346 18.9727C27.2974 20.0886 26.9044 21.5514 27.1555 22.9863C27.9048 27.2673 28.9622 33.3099 29.4368 36.0234C29.9687 38.8793 34.0404 38.8785 34.5715 36.0234L36.8528 22.9863C37.1039 21.5514 36.7101 20.0887 35.7727 18.9727Z\" fill=\"#FF9C4A\"/>\n </svg>\n }\n @case ('error') {\n <svg width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M0 12C0 5.37258 5.37258 0 12 0H52C58.6274 0 64 5.37258 64 12V52C64 58.6274 58.6274 64 52 64H12C5.37258 64 0 58.6274 0 52V12Z\" fill=\"#FDECEA\"/>\n <circle cx=\"31.8243\" cy=\"31.8243\" r=\"18.2857\" fill=\"white\"/>\n <path d=\"M47.6652 16.3349C56.3168 24.9865 56.3168 39.0136 47.6652 47.6652C39.0136 56.3168 24.9865 56.3168 16.3349 47.6652C7.68329 39.0136 7.68329 24.9865 16.3349 16.3349C24.9865 7.68329 39.0136 7.68329 47.6652 16.3349ZM40.2677 22.862C39.3666 21.9609 37.9053 21.9609 37.0042 22.862L31.4561 28.4101C31.2759 28.5903 30.9836 28.5903 30.8034 28.4101L25.2553 22.862C24.3542 21.9609 22.8929 21.9609 21.9918 22.862C21.0906 23.7632 21.0906 25.2245 21.9918 26.1256L27.5398 31.6737C27.7201 31.8539 27.7201 32.1462 27.5398 32.3264L21.9918 37.8745C21.0906 38.7756 21.0906 40.2369 21.9918 41.138C22.8929 42.0392 24.3542 42.0392 25.2553 41.138L30.8034 35.59C30.9836 35.4097 31.2759 35.4097 31.4561 35.59L37.0042 41.138C37.9053 42.0392 39.3666 42.0392 40.2677 41.138C41.1689 40.2369 41.1689 38.7756 40.2677 37.8745L34.7197 32.3264C34.5394 32.1462 34.5394 31.8539 34.7197 31.6737L40.2677 26.1256C41.1689 25.2245 41.1689 23.7632 40.2677 22.862Z\" fill=\"#FF606F\"/>\n </svg>\n }\n @case ('info') {\n <svg width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M0 12C0 5.37258 5.37258 0 12 0H52C58.6274 0 64 5.37258 64 12V52C64 58.6274 58.6274 64 52 64H12C5.37258 64 0 58.6274 0 52V12Z\" fill=\"#E6F0FA\"/>\n <circle cx=\"32.5283\" cy=\"32.5281\" r=\"18.989\" fill=\"white\"/>\n <path d=\"M31.9995 9.84668C44.2348 9.84668 54.1534 19.7653 54.1534 32.0005C54.1534 44.2358 44.2348 54.1544 31.9995 54.1544C19.7643 54.1544 9.8457 44.2358 9.8457 32.0005C9.8457 19.7653 19.7643 9.84668 31.9995 9.84668ZM28.3072 26.0943C26.8347 26.0945 25.8457 27.0832 25.8457 28.5558C25.8459 30.0282 26.8349 31.0171 28.3072 31.0174H31.758L28.3072 42.5835C28.0603 43.8088 28.3077 45.2811 29.0488 46.2698C29.7903 47.4952 31.0265 47.9896 32.252 47.9897H36.187C37.67 47.9896 38.6486 47.0112 38.6486 45.5282C38.6484 44.0558 37.6595 43.0668 36.187 43.0666H33.2303L36.681 31.5005C36.9281 30.2751 36.6808 28.8028 35.9395 27.8142C35.1979 26.5886 33.9619 26.0943 32.7363 26.0943H28.3072ZM33.4779 16.0005C31.4387 16.0006 29.7904 17.6488 29.7904 19.688C29.7906 21.7271 31.4388 23.3743 33.4779 23.3743C35.5171 23.3743 37.1652 21.7271 37.1654 19.688C37.1654 17.6488 35.5172 16.0005 33.4779 16.0005Z\" fill=\"#5EACFF\"/>\n </svg>\n }\n }\n </div>\n\n <div class=\"pecb-confirmation__content\">\n <h2 [id]=\"titleId\" class=\"pecb-confirmation__title\">{{ title() }}</h2>\n\n @if (message()) {\n <p [id]=\"messageId\" class=\"pecb-confirmation__message\">{{ message() }}</p>\n }\n </div>\n\n <div class=\"pecb-confirmation__actions\">\n <button\n type=\"button\"\n class=\"pecb-confirmation__btn pecb-confirmation__btn--confirm\"\n (click)=\"onConfirm()\"\n >\n {{ confirmText() }}\n </button>\n <button\n type=\"button\"\n class=\"pecb-confirmation__btn pecb-confirmation__btn--cancel\"\n (click)=\"onCancel()\"\n >\n {{ cancelText() }}\n </button>\n </div>\n </div>\n</ng-template>\n", styles: [".pecb-confirmation__backdrop{background-color:#00000080}.pecb-confirmation__panel{display:flex;width:616px;max-width:calc(100vw - 32px);padding:22px 20px 30px;flex-direction:column;align-items:center;gap:27px;background:#fff;border-radius:12px;box-shadow:0 10px 10px #0000000d;animation:scaleIn .25s ease-in-out forwards;outline:none}.pecb-confirmation__header{display:flex;justify-content:flex-end;align-items:center;width:100%;height:20px}.pecb-confirmation__close-btn{padding:0;border:none;background:none;cursor:pointer;font:inherit;color:inherit;display:flex;align-items:center;justify-content:center;width:20px;height:20px;color:#616161;border-radius:6px;outline:none;transition-property:color,background-color;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-confirmation__close-btn:focus{outline:none}.pecb-confirmation__close-btn:hover{color:#313131;background-color:#ececec}.pecb-confirmation__close-btn svg{width:10px;height:10px}.pecb-confirmation__icon{display:flex;align-items:center;justify-content:center;width:64px;height:64px;border-radius:12px;flex-shrink:0}.pecb-confirmation__icon--success{background-color:transparent}.pecb-confirmation__icon--warning{background-color:#fff4e5}.pecb-confirmation__icon--error{background-color:#fdecea}.pecb-confirmation__icon--info{background-color:#e6f0fa}.pecb-confirmation__content{display:flex;flex-direction:column;align-items:center;gap:9px;padding:0 17px;width:100%}.pecb-confirmation__title{margin:0;font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:24px;font-weight:700;line-height:1.2;color:#313131;text-align:center}.pecb-confirmation__message{margin:0;font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:16px;font-weight:400;line-height:1.6;color:#616161;text-align:center}.pecb-confirmation__actions{display:flex;align-items:center;justify-content:center;gap:16px;width:100%}.pecb-confirmation__btn{border:none;background:none;font:inherit;color:inherit;display:inline-flex;align-items:center;justify-content:center;height:38px;padding:10px 40px;border-radius:6px;font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;font-style:normal;font-weight:500;line-height:normal;cursor:pointer;white-space:nowrap;transition-property:background-color,border-color,color;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-confirmation__btn:focus{outline:none}.pecb-confirmation__btn:focus-visible{outline:2px solid #a11e29;outline-offset:2px}.pecb-confirmation__btn--confirm{background-color:#212427;color:#fff;border:1px solid #212427}.pecb-confirmation__btn--confirm:hover{background-color:#616161;border-color:#616161}.pecb-confirmation__btn--confirm:active{background-color:#000;border-color:#000}.pecb-confirmation__btn--cancel{background-color:#fff;color:#212427;border:1px solid #c2c2c2}.pecb-confirmation__btn--cancel:hover{background-color:#ececec;border-color:#616161}.pecb-confirmation__btn--cancel:active{background-color:#c2c2c2;border-color:#616161}@keyframes scaleIn{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}@media (prefers-reduced-motion: reduce){.pecb-confirmation__panel{animation:none}}\n"], dependencies: [{ kind: "directive", type: CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
5931
5955
  }
5932
5956
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ConfirmationComponent, decorators: [{
5933
5957
  type: Component,
5934
- args: [{ selector: 'pecb-confirmation', imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (isOpen()) {\n <div\n [id]=\"id()\"\n [class]=\"dialogClasses.join(' ')\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-labelledby]=\"titleId\"\n [attr.aria-describedby]=\"message() ? messageId : ariaDescribedBy() || null\"\n [attr.aria-label]=\"ariaLabel() || null\"\n (click)=\"onBackdropClick($event)\"\n (keydown)=\"onKeydown($event)\"\n >\n <div\n #dialogPanel\n class=\"pecb-confirmation__panel\"\n tabindex=\"-1\"\n >\n \n @if (showCloseButton()) {\n <div class=\"pecb-confirmation__header\">\n <button\n type=\"button\"\n class=\"pecb-confirmation__close-btn\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"close()\"\n >\n <svg\n width=\"10\"\n height=\"10\"\n viewBox=\"0 0 10 10\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M9 1L1 9M1 1L9 9\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n </div>\n }\n\n \n <div [class]=\"iconContainerClass\" aria-hidden=\"true\">\n @switch (iconType()) {\n @case ('success') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\">\n <path d=\"M0 12C0 5.37258 5.37258 0 12 0H52C58.6274 0 64 5.37258 64 12V52C64 58.6274 58.6274 64 52 64H12C5.37258 64 0 58.6274 0 52V12Z\" fill=\"#E6F4EA\"/>\n <circle cx=\"31.8243\" cy=\"31.8243\" r=\"18.2857\" fill=\"white\"/>\n <path d=\"M31.9991 9.84625C19.7635 9.84625 9.84521 19.7645 9.84521 32.0001C9.84521 44.2357 19.7635 54.1539 31.9991 54.1539C44.2347 54.1539 54.1529 44.2357 54.1529 32.0001C54.1426 19.7645 44.2244 9.84625 31.9991 9.84625ZM43.8227 26.3458L31.8137 41.3725C31.2781 42.0316 30.4954 42.4436 29.6508 42.4951C29.5993 42.4951 29.5375 42.4951 29.486 42.4951C28.693 42.4951 27.9308 42.1861 27.3747 41.6197L20.3917 34.6367C19.2279 33.4729 19.2279 31.5778 20.3917 30.414C21.5555 29.2502 23.4506 29.2502 24.6144 30.414L29.2285 35.0281L39.1571 22.6174C40.187 21.33 42.0615 21.1137 43.3592 22.1539C44.6466 23.1736 44.8526 25.0584 43.8227 26.3458Z\" fill=\"#57BC57\"/>\n </svg>\n }\n @case ('warning') {\n <svg width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M0 12C0 5.37258 5.37258 0 12 0H52C58.6274 0 64 5.37258 64 12V52C64 58.6274 58.6274 64 52 64H12C5.37258 64 0 58.6274 0 52V12Z\" fill=\"#FFF4E5\"/>\n <circle cx=\"32.5303\" cy=\"32.5281\" r=\"18.989\" fill=\"white\"/>\n <path d=\"M32.0012 9.84668C44.2362 9.84668 54.1543 19.765 54.1545 32C54.1545 44.2352 44.2364 54.1543 32.0012 54.1543C19.766 54.1543 9.8479 44.2352 9.8479 32C9.84815 19.765 19.7662 9.84668 32.0012 9.84668ZM32.0823 40.2266C30.0029 40.2266 28.3108 41.9187 28.3108 43.998C28.5007 48.9935 35.6636 48.9922 35.8528 43.998C35.8528 41.9188 34.1615 40.2266 32.0823 40.2266ZM35.7727 18.9727C33.9014 16.6513 30.1067 16.6501 28.2346 18.9727C27.2974 20.0886 26.9044 21.5514 27.1555 22.9863C27.9048 27.2673 28.9622 33.3099 29.4368 36.0234C29.9687 38.8793 34.0404 38.8785 34.5715 36.0234L36.8528 22.9863C37.1039 21.5514 36.7101 20.0887 35.7727 18.9727Z\" fill=\"#FF9C4A\"/>\n </svg>\n }\n @case ('error') {\n <svg width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M0 12C0 5.37258 5.37258 0 12 0H52C58.6274 0 64 5.37258 64 12V52C64 58.6274 58.6274 64 52 64H12C5.37258 64 0 58.6274 0 52V12Z\" fill=\"#FDECEA\"/>\n <circle cx=\"31.8243\" cy=\"31.8243\" r=\"18.2857\" fill=\"white\"/>\n <path d=\"M47.6652 16.3349C56.3168 24.9865 56.3168 39.0136 47.6652 47.6652C39.0136 56.3168 24.9865 56.3168 16.3349 47.6652C7.68329 39.0136 7.68329 24.9865 16.3349 16.3349C24.9865 7.68329 39.0136 7.68329 47.6652 16.3349ZM40.2677 22.862C39.3666 21.9609 37.9053 21.9609 37.0042 22.862L31.4561 28.4101C31.2759 28.5903 30.9836 28.5903 30.8034 28.4101L25.2553 22.862C24.3542 21.9609 22.8929 21.9609 21.9918 22.862C21.0906 23.7632 21.0906 25.2245 21.9918 26.1256L27.5398 31.6737C27.7201 31.8539 27.7201 32.1462 27.5398 32.3264L21.9918 37.8745C21.0906 38.7756 21.0906 40.2369 21.9918 41.138C22.8929 42.0392 24.3542 42.0392 25.2553 41.138L30.8034 35.59C30.9836 35.4097 31.2759 35.4097 31.4561 35.59L37.0042 41.138C37.9053 42.0392 39.3666 42.0392 40.2677 41.138C41.1689 40.2369 41.1689 38.7756 40.2677 37.8745L34.7197 32.3264C34.5394 32.1462 34.5394 31.8539 34.7197 31.6737L40.2677 26.1256C41.1689 25.2245 41.1689 23.7632 40.2677 22.862Z\" fill=\"#FF606F\"/>\n </svg>\n }\n @case ('info') {\n <svg width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M0 12C0 5.37258 5.37258 0 12 0H52C58.6274 0 64 5.37258 64 12V52C64 58.6274 58.6274 64 52 64H12C5.37258 64 0 58.6274 0 52V12Z\" fill=\"#E6F0FA\"/>\n <circle cx=\"32.5283\" cy=\"32.5281\" r=\"18.989\" fill=\"white\"/>\n <path d=\"M31.9995 9.84668C44.2348 9.84668 54.1534 19.7653 54.1534 32.0005C54.1534 44.2358 44.2348 54.1544 31.9995 54.1544C19.7643 54.1544 9.8457 44.2358 9.8457 32.0005C9.8457 19.7653 19.7643 9.84668 31.9995 9.84668ZM28.3072 26.0943C26.8347 26.0945 25.8457 27.0832 25.8457 28.5558C25.8459 30.0282 26.8349 31.0171 28.3072 31.0174H31.758L28.3072 42.5835C28.0603 43.8088 28.3077 45.2811 29.0488 46.2698C29.7903 47.4952 31.0265 47.9896 32.252 47.9897H36.187C37.67 47.9896 38.6486 47.0112 38.6486 45.5282C38.6484 44.0558 37.6595 43.0668 36.187 43.0666H33.2303L36.681 31.5005C36.9281 30.2751 36.6808 28.8028 35.9395 27.8142C35.1979 26.5886 33.9619 26.0943 32.7363 26.0943H28.3072ZM33.4779 16.0005C31.4387 16.0006 29.7904 17.6488 29.7904 19.688C29.7906 21.7271 31.4388 23.3743 33.4779 23.3743C35.5171 23.3743 37.1652 21.7271 37.1654 19.688C37.1654 17.6488 35.5172 16.0005 33.4779 16.0005Z\" fill=\"#5EACFF\"/>\n </svg>\n }\n }\n </div>\n\n \n <div class=\"pecb-confirmation__content\">\n <h2 [id]=\"titleId\" class=\"pecb-confirmation__title\">{{ title() }}</h2>\n\n @if (message()) {\n <p [id]=\"messageId\" class=\"pecb-confirmation__message\">{{ message() }}</p>\n }\n </div>\n\n \n <div class=\"pecb-confirmation__actions\">\n <button\n type=\"button\"\n class=\"pecb-confirmation__btn pecb-confirmation__btn--confirm\"\n (click)=\"onConfirm()\"\n >\n {{ confirmText() }}\n </button>\n <button\n type=\"button\"\n class=\"pecb-confirmation__btn pecb-confirmation__btn--cancel\"\n (click)=\"onCancel()\"\n >\n {{ cancelText() }}\n </button>\n </div>\n </div>\n </div>\n}\n", styles: [".pecb-confirmation{position:fixed;inset:0;z-index:1050;display:flex;align-items:center;justify-content:center;background-color:#00000080;animation:fadeIn .15s ease-in-out forwards}.pecb-confirmation__panel{display:flex;width:616px;max-width:calc(100vw - 32px);padding:22px 20px 30px;flex-direction:column;align-items:center;gap:27px;background:#fff;border-radius:12px;box-shadow:0 10px 10px #0000000d;animation:scaleIn .25s ease-in-out forwards;outline:none}.pecb-confirmation__header{display:flex;justify-content:flex-end;align-items:center;width:100%;height:20px}.pecb-confirmation__close-btn{padding:0;border:none;background:none;cursor:pointer;font:inherit;color:inherit;display:flex;align-items:center;justify-content:center;width:20px;height:20px;color:#616161;border-radius:6px;outline:none;transition-property:color,background-color;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-confirmation__close-btn:focus{outline:none}.pecb-confirmation__close-btn:hover{color:#313131;background-color:#ececec}.pecb-confirmation__close-btn svg{width:10px;height:10px}.pecb-confirmation__icon{display:flex;align-items:center;justify-content:center;width:64px;height:64px;border-radius:12px;flex-shrink:0}.pecb-confirmation__icon--success{background-color:transparent}.pecb-confirmation__icon--warning{background-color:#fff4e5}.pecb-confirmation__icon--error{background-color:#fdecea}.pecb-confirmation__icon--info{background-color:#e6f0fa}.pecb-confirmation__content{display:flex;flex-direction:column;align-items:center;gap:9px;padding:0 17px;width:100%}.pecb-confirmation__title{margin:0;font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:24px;font-weight:700;line-height:1.2;color:#313131;text-align:center}.pecb-confirmation__message{margin:0;font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:16px;font-weight:400;line-height:1.6;color:#616161;text-align:center}.pecb-confirmation__actions{display:flex;align-items:center;justify-content:center;gap:16px;width:100%}.pecb-confirmation__btn{border:none;background:none;font:inherit;color:inherit;display:inline-flex;align-items:center;justify-content:center;height:38px;padding:10px 40px;border-radius:6px;font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;font-style:normal;font-weight:500;line-height:normal;cursor:pointer;white-space:nowrap;transition-property:background-color,border-color,color;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-confirmation__btn:focus{outline:none}.pecb-confirmation__btn:focus-visible{outline:2px solid #a11e29;outline-offset:2px}.pecb-confirmation__btn--confirm{background-color:#212427;color:#fff;border:1px solid #212427}.pecb-confirmation__btn--confirm:hover{background-color:#616161;border-color:#616161}.pecb-confirmation__btn--confirm:active{background-color:#000;border-color:#000}.pecb-confirmation__btn--cancel{background-color:#fff;color:#212427;border:1px solid #c2c2c2}.pecb-confirmation__btn--cancel:hover{background-color:#ececec;border-color:#616161}.pecb-confirmation__btn--cancel:active{background-color:#c2c2c2;border-color:#616161}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes scaleIn{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}@media (prefers-reduced-motion: reduce){.pecb-confirmation,.pecb-confirmation__panel{animation:none}}\n"] }]
5935
- }], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], isOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "isOpen", required: false }] }, { type: i0.Output, args: ["isOpenChange"] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], confirmText: [{ type: i0.Input, args: [{ isSignal: true, alias: "confirmText", required: false }] }], cancelText: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelText", required: false }] }], iconType: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconType", required: false }] }], showCloseButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCloseButton", required: false }] }], closeAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeAriaLabel", required: false }] }], closeOnBackdropClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnBackdropClick", required: false }] }], closeOnEscape: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnEscape", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], confirmed: [{ type: i0.Output, args: ["confirmed"] }], cancelled: [{ type: i0.Output, args: ["cancelled"] }], closed: [{ type: i0.Output, args: ["closed"] }], dialogPanel: [{
5936
- type: ViewChild,
5937
- args: ['dialogPanel']
5938
- }] } });
5958
+ args: [{ selector: 'pecb-confirmation', imports: [CdkTrapFocus], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<ng-template #dialogTemplate>\n <div\n #dialogPanel\n [id]=\"id()\"\n class=\"pecb-confirmation__panel\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-labelledby]=\"titleId\"\n [attr.aria-describedby]=\"message() ? messageId : ariaDescribedBy() || null\"\n [attr.aria-label]=\"ariaLabel() || null\"\n tabindex=\"-1\"\n cdkTrapFocus\n >\n @if (showCloseButton()) {\n <div class=\"pecb-confirmation__header\">\n <button\n type=\"button\"\n class=\"pecb-confirmation__close-btn\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"close()\"\n >\n <svg\n width=\"10\"\n height=\"10\"\n viewBox=\"0 0 10 10\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M9 1L1 9M1 1L9 9\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n </div>\n }\n\n <div [class]=\"iconContainerClass\" aria-hidden=\"true\">\n @switch (iconType()) {\n @case ('success') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\">\n <path d=\"M0 12C0 5.37258 5.37258 0 12 0H52C58.6274 0 64 5.37258 64 12V52C64 58.6274 58.6274 64 52 64H12C5.37258 64 0 58.6274 0 52V12Z\" fill=\"#E6F4EA\"/>\n <circle cx=\"31.8243\" cy=\"31.8243\" r=\"18.2857\" fill=\"white\"/>\n <path d=\"M31.9991 9.84625C19.7635 9.84625 9.84521 19.7645 9.84521 32.0001C9.84521 44.2357 19.7635 54.1539 31.9991 54.1539C44.2347 54.1539 54.1529 44.2357 54.1529 32.0001C54.1426 19.7645 44.2244 9.84625 31.9991 9.84625ZM43.8227 26.3458L31.8137 41.3725C31.2781 42.0316 30.4954 42.4436 29.6508 42.4951C29.5993 42.4951 29.5375 42.4951 29.486 42.4951C28.693 42.4951 27.9308 42.1861 27.3747 41.6197L20.3917 34.6367C19.2279 33.4729 19.2279 31.5778 20.3917 30.414C21.5555 29.2502 23.4506 29.2502 24.6144 30.414L29.2285 35.0281L39.1571 22.6174C40.187 21.33 42.0615 21.1137 43.3592 22.1539C44.6466 23.1736 44.8526 25.0584 43.8227 26.3458Z\" fill=\"#57BC57\"/>\n </svg>\n }\n @case ('warning') {\n <svg width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M0 12C0 5.37258 5.37258 0 12 0H52C58.6274 0 64 5.37258 64 12V52C64 58.6274 58.6274 64 52 64H12C5.37258 64 0 58.6274 0 52V12Z\" fill=\"#FFF4E5\"/>\n <circle cx=\"32.5303\" cy=\"32.5281\" r=\"18.989\" fill=\"white\"/>\n <path d=\"M32.0012 9.84668C44.2362 9.84668 54.1543 19.765 54.1545 32C54.1545 44.2352 44.2364 54.1543 32.0012 54.1543C19.766 54.1543 9.8479 44.2352 9.8479 32C9.84815 19.765 19.7662 9.84668 32.0012 9.84668ZM32.0823 40.2266C30.0029 40.2266 28.3108 41.9187 28.3108 43.998C28.5007 48.9935 35.6636 48.9922 35.8528 43.998C35.8528 41.9188 34.1615 40.2266 32.0823 40.2266ZM35.7727 18.9727C33.9014 16.6513 30.1067 16.6501 28.2346 18.9727C27.2974 20.0886 26.9044 21.5514 27.1555 22.9863C27.9048 27.2673 28.9622 33.3099 29.4368 36.0234C29.9687 38.8793 34.0404 38.8785 34.5715 36.0234L36.8528 22.9863C37.1039 21.5514 36.7101 20.0887 35.7727 18.9727Z\" fill=\"#FF9C4A\"/>\n </svg>\n }\n @case ('error') {\n <svg width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M0 12C0 5.37258 5.37258 0 12 0H52C58.6274 0 64 5.37258 64 12V52C64 58.6274 58.6274 64 52 64H12C5.37258 64 0 58.6274 0 52V12Z\" fill=\"#FDECEA\"/>\n <circle cx=\"31.8243\" cy=\"31.8243\" r=\"18.2857\" fill=\"white\"/>\n <path d=\"M47.6652 16.3349C56.3168 24.9865 56.3168 39.0136 47.6652 47.6652C39.0136 56.3168 24.9865 56.3168 16.3349 47.6652C7.68329 39.0136 7.68329 24.9865 16.3349 16.3349C24.9865 7.68329 39.0136 7.68329 47.6652 16.3349ZM40.2677 22.862C39.3666 21.9609 37.9053 21.9609 37.0042 22.862L31.4561 28.4101C31.2759 28.5903 30.9836 28.5903 30.8034 28.4101L25.2553 22.862C24.3542 21.9609 22.8929 21.9609 21.9918 22.862C21.0906 23.7632 21.0906 25.2245 21.9918 26.1256L27.5398 31.6737C27.7201 31.8539 27.7201 32.1462 27.5398 32.3264L21.9918 37.8745C21.0906 38.7756 21.0906 40.2369 21.9918 41.138C22.8929 42.0392 24.3542 42.0392 25.2553 41.138L30.8034 35.59C30.9836 35.4097 31.2759 35.4097 31.4561 35.59L37.0042 41.138C37.9053 42.0392 39.3666 42.0392 40.2677 41.138C41.1689 40.2369 41.1689 38.7756 40.2677 37.8745L34.7197 32.3264C34.5394 32.1462 34.5394 31.8539 34.7197 31.6737L40.2677 26.1256C41.1689 25.2245 41.1689 23.7632 40.2677 22.862Z\" fill=\"#FF606F\"/>\n </svg>\n }\n @case ('info') {\n <svg width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M0 12C0 5.37258 5.37258 0 12 0H52C58.6274 0 64 5.37258 64 12V52C64 58.6274 58.6274 64 52 64H12C5.37258 64 0 58.6274 0 52V12Z\" fill=\"#E6F0FA\"/>\n <circle cx=\"32.5283\" cy=\"32.5281\" r=\"18.989\" fill=\"white\"/>\n <path d=\"M31.9995 9.84668C44.2348 9.84668 54.1534 19.7653 54.1534 32.0005C54.1534 44.2358 44.2348 54.1544 31.9995 54.1544C19.7643 54.1544 9.8457 44.2358 9.8457 32.0005C9.8457 19.7653 19.7643 9.84668 31.9995 9.84668ZM28.3072 26.0943C26.8347 26.0945 25.8457 27.0832 25.8457 28.5558C25.8459 30.0282 26.8349 31.0171 28.3072 31.0174H31.758L28.3072 42.5835C28.0603 43.8088 28.3077 45.2811 29.0488 46.2698C29.7903 47.4952 31.0265 47.9896 32.252 47.9897H36.187C37.67 47.9896 38.6486 47.0112 38.6486 45.5282C38.6484 44.0558 37.6595 43.0668 36.187 43.0666H33.2303L36.681 31.5005C36.9281 30.2751 36.6808 28.8028 35.9395 27.8142C35.1979 26.5886 33.9619 26.0943 32.7363 26.0943H28.3072ZM33.4779 16.0005C31.4387 16.0006 29.7904 17.6488 29.7904 19.688C29.7906 21.7271 31.4388 23.3743 33.4779 23.3743C35.5171 23.3743 37.1652 21.7271 37.1654 19.688C37.1654 17.6488 35.5172 16.0005 33.4779 16.0005Z\" fill=\"#5EACFF\"/>\n </svg>\n }\n }\n </div>\n\n <div class=\"pecb-confirmation__content\">\n <h2 [id]=\"titleId\" class=\"pecb-confirmation__title\">{{ title() }}</h2>\n\n @if (message()) {\n <p [id]=\"messageId\" class=\"pecb-confirmation__message\">{{ message() }}</p>\n }\n </div>\n\n <div class=\"pecb-confirmation__actions\">\n <button\n type=\"button\"\n class=\"pecb-confirmation__btn pecb-confirmation__btn--confirm\"\n (click)=\"onConfirm()\"\n >\n {{ confirmText() }}\n </button>\n <button\n type=\"button\"\n class=\"pecb-confirmation__btn pecb-confirmation__btn--cancel\"\n (click)=\"onCancel()\"\n >\n {{ cancelText() }}\n </button>\n </div>\n </div>\n</ng-template>\n", styles: [".pecb-confirmation__backdrop{background-color:#00000080}.pecb-confirmation__panel{display:flex;width:616px;max-width:calc(100vw - 32px);padding:22px 20px 30px;flex-direction:column;align-items:center;gap:27px;background:#fff;border-radius:12px;box-shadow:0 10px 10px #0000000d;animation:scaleIn .25s ease-in-out forwards;outline:none}.pecb-confirmation__header{display:flex;justify-content:flex-end;align-items:center;width:100%;height:20px}.pecb-confirmation__close-btn{padding:0;border:none;background:none;cursor:pointer;font:inherit;color:inherit;display:flex;align-items:center;justify-content:center;width:20px;height:20px;color:#616161;border-radius:6px;outline:none;transition-property:color,background-color;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-confirmation__close-btn:focus{outline:none}.pecb-confirmation__close-btn:hover{color:#313131;background-color:#ececec}.pecb-confirmation__close-btn svg{width:10px;height:10px}.pecb-confirmation__icon{display:flex;align-items:center;justify-content:center;width:64px;height:64px;border-radius:12px;flex-shrink:0}.pecb-confirmation__icon--success{background-color:transparent}.pecb-confirmation__icon--warning{background-color:#fff4e5}.pecb-confirmation__icon--error{background-color:#fdecea}.pecb-confirmation__icon--info{background-color:#e6f0fa}.pecb-confirmation__content{display:flex;flex-direction:column;align-items:center;gap:9px;padding:0 17px;width:100%}.pecb-confirmation__title{margin:0;font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:24px;font-weight:700;line-height:1.2;color:#313131;text-align:center}.pecb-confirmation__message{margin:0;font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:16px;font-weight:400;line-height:1.6;color:#616161;text-align:center}.pecb-confirmation__actions{display:flex;align-items:center;justify-content:center;gap:16px;width:100%}.pecb-confirmation__btn{border:none;background:none;font:inherit;color:inherit;display:inline-flex;align-items:center;justify-content:center;height:38px;padding:10px 40px;border-radius:6px;font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;font-style:normal;font-weight:500;line-height:normal;cursor:pointer;white-space:nowrap;transition-property:background-color,border-color,color;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-confirmation__btn:focus{outline:none}.pecb-confirmation__btn:focus-visible{outline:2px solid #a11e29;outline-offset:2px}.pecb-confirmation__btn--confirm{background-color:#212427;color:#fff;border:1px solid #212427}.pecb-confirmation__btn--confirm:hover{background-color:#616161;border-color:#616161}.pecb-confirmation__btn--confirm:active{background-color:#000;border-color:#000}.pecb-confirmation__btn--cancel{background-color:#fff;color:#212427;border:1px solid #c2c2c2}.pecb-confirmation__btn--cancel:hover{background-color:#ececec;border-color:#616161}.pecb-confirmation__btn--cancel:active{background-color:#c2c2c2;border-color:#616161}@keyframes scaleIn{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}@media (prefers-reduced-motion: reduce){.pecb-confirmation__panel{animation:none}}\n"] }]
5959
+ }], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], isOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "isOpen", required: false }] }, { type: i0.Output, args: ["isOpenChange"] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], confirmText: [{ type: i0.Input, args: [{ isSignal: true, alias: "confirmText", required: false }] }], cancelText: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelText", required: false }] }], iconType: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconType", required: false }] }], showCloseButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCloseButton", required: false }] }], closeAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeAriaLabel", required: false }] }], closeOnBackdropClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnBackdropClick", required: false }] }], closeOnEscape: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnEscape", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], confirmed: [{ type: i0.Output, args: ["confirmed"] }], cancelled: [{ type: i0.Output, args: ["cancelled"] }], closed: [{ type: i0.Output, args: ["closed"] }], dialogTemplate: [{ type: i0.ViewChild, args: ['dialogTemplate', { isSignal: true }] }] } });
5939
5960
 
5940
5961
  class AlertComponent {
5941
5962
  // Duration of the leave animation; keep in sync with $transition-base (250ms)
@@ -6328,6 +6349,16 @@ class TooltipComponent {
6328
6349
  this.primaryCta = input(undefined, ...(ngDevMode ? [{ debugName: "primaryCta" }] : []));
6329
6350
  this.secondaryCta = input(undefined, ...(ngDevMode ? [{ debugName: "secondaryCta" }] : []));
6330
6351
  this.showClose = input(true, ...(ngDevMode ? [{ debugName: "showClose" }] : []));
6352
+ /**
6353
+ * Optional explicit width of the tooltip box (any CSS length, e.g. '330px',
6354
+ * '20rem'). Leave undefined to size to content.
6355
+ */
6356
+ this.width = input(undefined, ...(ngDevMode ? [{ debugName: "width" }] : []));
6357
+ /**
6358
+ * Optional explicit height of the tooltip box (any CSS length, e.g. '120px').
6359
+ * Leave undefined to size to content.
6360
+ */
6361
+ this.height = input(undefined, ...(ngDevMode ? [{ debugName: "height" }] : []));
6331
6362
  /**
6332
6363
  * Whether the tooltip is visible. Manages its own state internally —
6333
6364
  * no consumer binding required. Supports [(visible)] for controlled mode.
@@ -6366,12 +6397,12 @@ class TooltipComponent {
6366
6397
  ].join(' ');
6367
6398
  }
6368
6399
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: TooltipComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
6369
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: TooltipComponent, isStandalone: true, selector: "pecb-tooltip", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, pointerPosition: { classPropertyName: "pointerPosition", publicName: "pointerPosition", isSignal: true, isRequired: false, transformFunction: null }, theme: { classPropertyName: "theme", publicName: "theme", isSignal: true, isRequired: false, transformFunction: null }, primaryCta: { classPropertyName: "primaryCta", publicName: "primaryCta", isSignal: true, isRequired: false, transformFunction: null }, secondaryCta: { classPropertyName: "secondaryCta", publicName: "secondaryCta", isSignal: true, isRequired: false, transformFunction: null }, showClose: { classPropertyName: "showClose", publicName: "showClose", isSignal: true, isRequired: false, transformFunction: null }, visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null }, closeAriaLabel: { classPropertyName: "closeAriaLabel", publicName: "closeAriaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { visible: "visibleChange", closed: "closed", primaryCtaClick: "primaryCtaClick", secondaryCtaClick: "secondaryCtaClick" }, ngImport: i0, template: "@if (visible()) {\n <div [class]=\"hostClasses\">\n\n <!-- \u2500\u2500 Pointer TOP (above container) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (pointerSide === 'top') {\n <div class=\"pecb-tooltip__pointer pecb-tooltip__pointer--top\">\n <svg width=\"20\" height=\"12\" viewBox=\"0 0 20 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path [attr.fill]=\"ptrBorderFill\" d=\"M0 12L10 0L20 12Z\"/>\n <path [attr.fill]=\"ptrBodyFill\" d=\"M1.8 12L10 1.8L18.2 12Z\"/>\n </svg>\n </div>\n }\n\n <!-- \u2500\u2500 Pointer LEFT (left of container) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (pointerSide === 'left') {\n <div class=\"pecb-tooltip__pointer pecb-tooltip__pointer--left\">\n <svg width=\"12\" height=\"20\" viewBox=\"0 0 12 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path [attr.fill]=\"ptrBorderFill\" d=\"M12 0L0 10L12 20Z\"/>\n <path [attr.fill]=\"ptrBodyFill\" d=\"M12 1.8L1.8 10L12 18.2Z\"/>\n </svg>\n </div>\n }\n\n <!-- \u2500\u2500 Main container \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"pecb-tooltip__container\">\n <div class=\"pecb-tooltip__body\">\n\n <div class=\"pecb-tooltip__content\">\n <div class=\"pecb-tooltip__texts\">\n @if (title()) {\n <p class=\"pecb-tooltip__title\">{{ title() }}</p>\n }\n <p class=\"pecb-tooltip__message\">{{ message() }}</p>\n </div>\n\n @if (showClose()) {\n <button\n type=\"button\"\n class=\"pecb-tooltip__close\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"close()\"\n >\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path class=\"pecb-tooltip__close-icon\" d=\"M15 5L5 15M5 5L15 15\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </button>\n }\n </div>\n\n @if (hasButtons) {\n <div class=\"pecb-tooltip__buttons\">\n @if (secondaryCta()) {\n <button type=\"button\" class=\"pecb-tooltip__btn pecb-tooltip__btn--secondary\" (click)=\"secondaryCtaClick.emit()\">{{ secondaryCta() }}</button>\n }\n @if (primaryCta()) {\n <button type=\"button\" class=\"pecb-tooltip__btn pecb-tooltip__btn--primary\" (click)=\"primaryCtaClick.emit()\">{{ primaryCta() }}</button>\n }\n </div>\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 Pointer RIGHT (right of container) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (pointerSide === 'right') {\n <div class=\"pecb-tooltip__pointer pecb-tooltip__pointer--right\">\n <svg width=\"12\" height=\"20\" viewBox=\"0 0 12 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path [attr.fill]=\"ptrBorderFill\" d=\"M0 0L12 10L0 20Z\"/>\n <path [attr.fill]=\"ptrBodyFill\" d=\"M0 1.8L10.2 10L0 18.2Z\"/>\n </svg>\n </div>\n }\n\n <!-- \u2500\u2500 Pointer BOTTOM (below container) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (pointerSide === 'bottom') {\n <div class=\"pecb-tooltip__pointer pecb-tooltip__pointer--bottom\">\n <svg width=\"20\" height=\"12\" viewBox=\"0 0 20 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path [attr.fill]=\"ptrBorderFill\" d=\"M0 0L10 12L20 0Z\"/>\n <path [attr.fill]=\"ptrBodyFill\" d=\"M1.8 0L10 10.2L18.2 0Z\"/>\n </svg>\n </div>\n }\n\n </div>\n}\n", styles: [".pecb-tooltip{display:inline-flex;flex-direction:column;width:330px}.pecb-tooltip--left-top,.pecb-tooltip--left-center,.pecb-tooltip--left-bottom,.pecb-tooltip--right-top,.pecb-tooltip--right-center,.pecb-tooltip--right-bottom{flex-direction:row;align-items:stretch;width:auto}.pecb-tooltip--left-top>.pecb-tooltip__container,.pecb-tooltip--left-center>.pecb-tooltip__container,.pecb-tooltip--left-bottom>.pecb-tooltip__container,.pecb-tooltip--right-top>.pecb-tooltip__container,.pecb-tooltip--right-center>.pecb-tooltip__container,.pecb-tooltip--right-bottom>.pecb-tooltip__container{width:330px}.pecb-tooltip__pointer--top{display:flex;align-items:flex-end;padding:0 16px;position:relative;z-index:2;margin-bottom:-1px}.pecb-tooltip--top-left>.pecb-tooltip__pointer--top{justify-content:flex-start}.pecb-tooltip--top-center>.pecb-tooltip__pointer--top{justify-content:center}.pecb-tooltip--top-right>.pecb-tooltip__pointer--top{justify-content:flex-end}.pecb-tooltip__pointer--bottom{display:flex;align-items:flex-start;padding:0 16px;position:relative;z-index:2;margin-top:-1px}.pecb-tooltip--bottom-left>.pecb-tooltip__pointer--bottom{justify-content:flex-start}.pecb-tooltip--bottom-center>.pecb-tooltip__pointer--bottom{justify-content:center}.pecb-tooltip--bottom-right>.pecb-tooltip__pointer--bottom{justify-content:flex-end}.pecb-tooltip__container{display:flex;flex-direction:row;align-items:stretch;border-radius:8px;position:relative;z-index:1}.pecb-tooltip--dark .pecb-tooltip__container{background-color:#313131}.pecb-tooltip--light .pecb-tooltip__container{background-color:#fff;border:1px solid #ececec}.pecb-tooltip__pointer--left{display:flex;flex-direction:column;align-items:flex-end;align-self:stretch;position:relative;z-index:2;margin-right:-1px}.pecb-tooltip--left-top>.pecb-tooltip__pointer--left{justify-content:flex-start;padding-top:16px}.pecb-tooltip--left-center>.pecb-tooltip__pointer--left{justify-content:center}.pecb-tooltip--left-bottom>.pecb-tooltip__pointer--left{justify-content:flex-end;padding-bottom:16px}.pecb-tooltip__pointer--right{display:flex;flex-direction:column;align-items:flex-start;align-self:stretch;position:relative;z-index:2;margin-left:-1px}.pecb-tooltip--right-top>.pecb-tooltip__pointer--right{justify-content:flex-start;padding-top:16px}.pecb-tooltip--right-center>.pecb-tooltip__pointer--right{justify-content:center}.pecb-tooltip--right-bottom>.pecb-tooltip__pointer--right{justify-content:flex-end;padding-bottom:16px}.pecb-tooltip__body{flex:1;display:flex;flex-direction:column;gap:16px;padding:16px}.pecb-tooltip__content{display:flex;gap:8px;align-items:flex-start;width:100%}.pecb-tooltip__texts{flex:1;display:flex;flex-direction:column;gap:4px}.pecb-tooltip__title{font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;font-style:normal;font-weight:700;line-height:normal;margin:0}.pecb-tooltip--dark .pecb-tooltip__title{color:#fff}.pecb-tooltip--light .pecb-tooltip__title{color:#212427}.pecb-tooltip__message{font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.5;margin:0}.pecb-tooltip--dark .pecb-tooltip__message{color:#ececec}.pecb-tooltip--light .pecb-tooltip__message{color:#919191}.pecb-tooltip__close{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:20px;height:20px;padding:0;background:none;border:none;cursor:pointer;transition:opacity .15s ease-in-out}.pecb-tooltip__close:hover{opacity:.7}.pecb-tooltip__close:focus-visible{outline:2px solid #a11e29;outline-offset:2px;border-radius:2px}.pecb-tooltip__close-icon{stroke:#ececec}.pecb-tooltip--light .pecb-tooltip__close-icon{stroke:#212427}.pecb-tooltip__buttons{display:flex;align-items:center;gap:24px}.pecb-tooltip__btn{display:inline-flex;align-items:center;justify-content:center;padding:0;background:none;border:none;cursor:pointer;font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;font-style:normal;font-weight:500;line-height:normal;transition:opacity .15s ease-in-out}.pecb-tooltip__btn:hover{opacity:.8}.pecb-tooltip__btn:focus-visible{outline:2px solid #a11e29;outline-offset:2px;border-radius:2px}.pecb-tooltip__btn--primary{color:#a11e29}.pecb-tooltip__btn--secondary{color:#919191}.pecb-tooltip--light .pecb-tooltip__btn--secondary{color:#212427}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
6400
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: TooltipComponent, isStandalone: true, selector: "pecb-tooltip", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, pointerPosition: { classPropertyName: "pointerPosition", publicName: "pointerPosition", isSignal: true, isRequired: false, transformFunction: null }, theme: { classPropertyName: "theme", publicName: "theme", isSignal: true, isRequired: false, transformFunction: null }, primaryCta: { classPropertyName: "primaryCta", publicName: "primaryCta", isSignal: true, isRequired: false, transformFunction: null }, secondaryCta: { classPropertyName: "secondaryCta", publicName: "secondaryCta", isSignal: true, isRequired: false, transformFunction: null }, showClose: { classPropertyName: "showClose", publicName: "showClose", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null }, closeAriaLabel: { classPropertyName: "closeAriaLabel", publicName: "closeAriaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { visible: "visibleChange", closed: "closed", primaryCtaClick: "primaryCtaClick", secondaryCtaClick: "secondaryCtaClick" }, ngImport: i0, template: "@if (visible()) {\n <div [class]=\"hostClasses\">\n\n <!-- \u2500\u2500 Pointer TOP (above container) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (pointerSide === 'top') {\n <div class=\"pecb-tooltip__pointer pecb-tooltip__pointer--top\">\n <svg width=\"20\" height=\"12\" viewBox=\"0 0 20 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path [attr.fill]=\"ptrBorderFill\" d=\"M0 12L10 0L20 12Z\"/>\n <path [attr.fill]=\"ptrBodyFill\" d=\"M1.8 12L10 1.8L18.2 12Z\"/>\n </svg>\n </div>\n }\n\n <!-- \u2500\u2500 Pointer LEFT (left of container) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (pointerSide === 'left') {\n <div class=\"pecb-tooltip__pointer pecb-tooltip__pointer--left\">\n <svg width=\"12\" height=\"20\" viewBox=\"0 0 12 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path [attr.fill]=\"ptrBorderFill\" d=\"M12 0L0 10L12 20Z\"/>\n <path [attr.fill]=\"ptrBodyFill\" d=\"M12 1.8L1.8 10L12 18.2Z\"/>\n </svg>\n </div>\n }\n\n <!-- \u2500\u2500 Main container \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"pecb-tooltip__container\" [style.width]=\"width()\" [style.height]=\"height()\">\n <div class=\"pecb-tooltip__body\">\n\n <div class=\"pecb-tooltip__content\">\n <div class=\"pecb-tooltip__texts\">\n @if (title()) {\n <p class=\"pecb-tooltip__title\">{{ title() }}</p>\n }\n <p class=\"pecb-tooltip__message\">{{ message() }}</p>\n </div>\n\n @if (showClose()) {\n <button\n type=\"button\"\n class=\"pecb-tooltip__close\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"close()\"\n >\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path class=\"pecb-tooltip__close-icon\" d=\"M15 5L5 15M5 5L15 15\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </button>\n }\n </div>\n\n @if (hasButtons) {\n <div class=\"pecb-tooltip__buttons\">\n @if (secondaryCta()) {\n <button type=\"button\" class=\"pecb-tooltip__btn pecb-tooltip__btn--secondary\" (click)=\"secondaryCtaClick.emit()\">{{ secondaryCta() }}</button>\n }\n @if (primaryCta()) {\n <button type=\"button\" class=\"pecb-tooltip__btn pecb-tooltip__btn--primary\" (click)=\"primaryCtaClick.emit()\">{{ primaryCta() }}</button>\n }\n </div>\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 Pointer RIGHT (right of container) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (pointerSide === 'right') {\n <div class=\"pecb-tooltip__pointer pecb-tooltip__pointer--right\">\n <svg width=\"12\" height=\"20\" viewBox=\"0 0 12 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path [attr.fill]=\"ptrBorderFill\" d=\"M0 0L12 10L0 20Z\"/>\n <path [attr.fill]=\"ptrBodyFill\" d=\"M0 1.8L10.2 10L0 18.2Z\"/>\n </svg>\n </div>\n }\n\n <!-- \u2500\u2500 Pointer BOTTOM (below container) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (pointerSide === 'bottom') {\n <div class=\"pecb-tooltip__pointer pecb-tooltip__pointer--bottom\">\n <svg width=\"20\" height=\"12\" viewBox=\"0 0 20 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path [attr.fill]=\"ptrBorderFill\" d=\"M0 0L10 12L20 0Z\"/>\n <path [attr.fill]=\"ptrBodyFill\" d=\"M1.8 0L10 10.2L18.2 0Z\"/>\n </svg>\n </div>\n }\n\n </div>\n}\n", styles: [".pecb-tooltip{display:inline-flex;flex-direction:column;width:auto;height:auto}.pecb-tooltip--left-top,.pecb-tooltip--left-center,.pecb-tooltip--left-bottom,.pecb-tooltip--right-top,.pecb-tooltip--right-center,.pecb-tooltip--right-bottom{flex-direction:row;align-items:stretch;width:auto}.pecb-tooltip--left-top>.pecb-tooltip__container,.pecb-tooltip--left-center>.pecb-tooltip__container,.pecb-tooltip--left-bottom>.pecb-tooltip__container,.pecb-tooltip--right-top>.pecb-tooltip__container,.pecb-tooltip--right-center>.pecb-tooltip__container,.pecb-tooltip--right-bottom>.pecb-tooltip__container{width:auto}.pecb-tooltip__pointer--top{display:flex;align-items:flex-end;padding:0 16px;position:relative;z-index:2;margin-bottom:-1px}.pecb-tooltip--top-left>.pecb-tooltip__pointer--top{justify-content:flex-start}.pecb-tooltip--top-center>.pecb-tooltip__pointer--top{justify-content:center}.pecb-tooltip--top-right>.pecb-tooltip__pointer--top{justify-content:flex-end}.pecb-tooltip__pointer--bottom{display:flex;align-items:flex-start;padding:0 16px;position:relative;z-index:2;margin-top:-1px}.pecb-tooltip--bottom-left>.pecb-tooltip__pointer--bottom{justify-content:flex-start}.pecb-tooltip--bottom-center>.pecb-tooltip__pointer--bottom{justify-content:center}.pecb-tooltip--bottom-right>.pecb-tooltip__pointer--bottom{justify-content:flex-end}.pecb-tooltip__container{display:flex;flex-direction:row;align-items:stretch;border-radius:8px;position:relative;z-index:1}.pecb-tooltip--dark .pecb-tooltip__container{background-color:#313131}.pecb-tooltip--light .pecb-tooltip__container{background-color:#fff;border:1px solid #ececec}.pecb-tooltip__pointer--left{display:flex;flex-direction:column;align-items:flex-end;align-self:stretch;position:relative;z-index:2;margin-right:-1px}.pecb-tooltip--left-top>.pecb-tooltip__pointer--left{justify-content:flex-start;padding-top:16px}.pecb-tooltip--left-center>.pecb-tooltip__pointer--left{justify-content:center}.pecb-tooltip--left-bottom>.pecb-tooltip__pointer--left{justify-content:flex-end;padding-bottom:16px}.pecb-tooltip__pointer--right{display:flex;flex-direction:column;align-items:flex-start;align-self:stretch;position:relative;z-index:2;margin-left:-1px}.pecb-tooltip--right-top>.pecb-tooltip__pointer--right{justify-content:flex-start;padding-top:16px}.pecb-tooltip--right-center>.pecb-tooltip__pointer--right{justify-content:center}.pecb-tooltip--right-bottom>.pecb-tooltip__pointer--right{justify-content:flex-end;padding-bottom:16px}.pecb-tooltip__body{flex:1;display:flex;flex-direction:column;gap:16px;padding:16px}.pecb-tooltip__content{display:flex;gap:8px;align-items:flex-start;width:100%}.pecb-tooltip__texts{flex:1;display:flex;flex-direction:column;gap:4px}.pecb-tooltip__title{font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;font-style:normal;font-weight:700;line-height:normal;margin:0}.pecb-tooltip--dark .pecb-tooltip__title{color:#fff}.pecb-tooltip--light .pecb-tooltip__title{color:#212427}.pecb-tooltip__message{font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.5;margin:0}.pecb-tooltip--dark .pecb-tooltip__message{color:#ececec}.pecb-tooltip--light .pecb-tooltip__message{color:#919191}.pecb-tooltip__close{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:20px;height:20px;padding:0;background:none;border:none;cursor:pointer;transition:opacity .15s ease-in-out}.pecb-tooltip__close:hover{opacity:.7}.pecb-tooltip__close:focus-visible{outline:2px solid #a11e29;outline-offset:2px;border-radius:2px}.pecb-tooltip__close-icon{stroke:#ececec}.pecb-tooltip--light .pecb-tooltip__close-icon{stroke:#212427}.pecb-tooltip__buttons{display:flex;align-items:center;gap:24px}.pecb-tooltip__btn{display:inline-flex;align-items:center;justify-content:center;padding:0;background:none;border:none;cursor:pointer;font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;font-style:normal;font-weight:500;line-height:normal;transition:opacity .15s ease-in-out}.pecb-tooltip__btn:hover{opacity:.8}.pecb-tooltip__btn:focus-visible{outline:2px solid #a11e29;outline-offset:2px;border-radius:2px}.pecb-tooltip__btn--primary{color:#a11e29}.pecb-tooltip__btn--secondary{color:#919191}.pecb-tooltip--light .pecb-tooltip__btn--secondary{color:#212427}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
6370
6401
  }
6371
6402
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: TooltipComponent, decorators: [{
6372
6403
  type: Component,
6373
- args: [{ selector: 'pecb-tooltip', imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (visible()) {\n <div [class]=\"hostClasses\">\n\n <!-- \u2500\u2500 Pointer TOP (above container) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (pointerSide === 'top') {\n <div class=\"pecb-tooltip__pointer pecb-tooltip__pointer--top\">\n <svg width=\"20\" height=\"12\" viewBox=\"0 0 20 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path [attr.fill]=\"ptrBorderFill\" d=\"M0 12L10 0L20 12Z\"/>\n <path [attr.fill]=\"ptrBodyFill\" d=\"M1.8 12L10 1.8L18.2 12Z\"/>\n </svg>\n </div>\n }\n\n <!-- \u2500\u2500 Pointer LEFT (left of container) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (pointerSide === 'left') {\n <div class=\"pecb-tooltip__pointer pecb-tooltip__pointer--left\">\n <svg width=\"12\" height=\"20\" viewBox=\"0 0 12 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path [attr.fill]=\"ptrBorderFill\" d=\"M12 0L0 10L12 20Z\"/>\n <path [attr.fill]=\"ptrBodyFill\" d=\"M12 1.8L1.8 10L12 18.2Z\"/>\n </svg>\n </div>\n }\n\n <!-- \u2500\u2500 Main container \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"pecb-tooltip__container\">\n <div class=\"pecb-tooltip__body\">\n\n <div class=\"pecb-tooltip__content\">\n <div class=\"pecb-tooltip__texts\">\n @if (title()) {\n <p class=\"pecb-tooltip__title\">{{ title() }}</p>\n }\n <p class=\"pecb-tooltip__message\">{{ message() }}</p>\n </div>\n\n @if (showClose()) {\n <button\n type=\"button\"\n class=\"pecb-tooltip__close\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"close()\"\n >\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path class=\"pecb-tooltip__close-icon\" d=\"M15 5L5 15M5 5L15 15\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </button>\n }\n </div>\n\n @if (hasButtons) {\n <div class=\"pecb-tooltip__buttons\">\n @if (secondaryCta()) {\n <button type=\"button\" class=\"pecb-tooltip__btn pecb-tooltip__btn--secondary\" (click)=\"secondaryCtaClick.emit()\">{{ secondaryCta() }}</button>\n }\n @if (primaryCta()) {\n <button type=\"button\" class=\"pecb-tooltip__btn pecb-tooltip__btn--primary\" (click)=\"primaryCtaClick.emit()\">{{ primaryCta() }}</button>\n }\n </div>\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 Pointer RIGHT (right of container) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (pointerSide === 'right') {\n <div class=\"pecb-tooltip__pointer pecb-tooltip__pointer--right\">\n <svg width=\"12\" height=\"20\" viewBox=\"0 0 12 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path [attr.fill]=\"ptrBorderFill\" d=\"M0 0L12 10L0 20Z\"/>\n <path [attr.fill]=\"ptrBodyFill\" d=\"M0 1.8L10.2 10L0 18.2Z\"/>\n </svg>\n </div>\n }\n\n <!-- \u2500\u2500 Pointer BOTTOM (below container) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (pointerSide === 'bottom') {\n <div class=\"pecb-tooltip__pointer pecb-tooltip__pointer--bottom\">\n <svg width=\"20\" height=\"12\" viewBox=\"0 0 20 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path [attr.fill]=\"ptrBorderFill\" d=\"M0 0L10 12L20 0Z\"/>\n <path [attr.fill]=\"ptrBodyFill\" d=\"M1.8 0L10 10.2L18.2 0Z\"/>\n </svg>\n </div>\n }\n\n </div>\n}\n", styles: [".pecb-tooltip{display:inline-flex;flex-direction:column;width:330px}.pecb-tooltip--left-top,.pecb-tooltip--left-center,.pecb-tooltip--left-bottom,.pecb-tooltip--right-top,.pecb-tooltip--right-center,.pecb-tooltip--right-bottom{flex-direction:row;align-items:stretch;width:auto}.pecb-tooltip--left-top>.pecb-tooltip__container,.pecb-tooltip--left-center>.pecb-tooltip__container,.pecb-tooltip--left-bottom>.pecb-tooltip__container,.pecb-tooltip--right-top>.pecb-tooltip__container,.pecb-tooltip--right-center>.pecb-tooltip__container,.pecb-tooltip--right-bottom>.pecb-tooltip__container{width:330px}.pecb-tooltip__pointer--top{display:flex;align-items:flex-end;padding:0 16px;position:relative;z-index:2;margin-bottom:-1px}.pecb-tooltip--top-left>.pecb-tooltip__pointer--top{justify-content:flex-start}.pecb-tooltip--top-center>.pecb-tooltip__pointer--top{justify-content:center}.pecb-tooltip--top-right>.pecb-tooltip__pointer--top{justify-content:flex-end}.pecb-tooltip__pointer--bottom{display:flex;align-items:flex-start;padding:0 16px;position:relative;z-index:2;margin-top:-1px}.pecb-tooltip--bottom-left>.pecb-tooltip__pointer--bottom{justify-content:flex-start}.pecb-tooltip--bottom-center>.pecb-tooltip__pointer--bottom{justify-content:center}.pecb-tooltip--bottom-right>.pecb-tooltip__pointer--bottom{justify-content:flex-end}.pecb-tooltip__container{display:flex;flex-direction:row;align-items:stretch;border-radius:8px;position:relative;z-index:1}.pecb-tooltip--dark .pecb-tooltip__container{background-color:#313131}.pecb-tooltip--light .pecb-tooltip__container{background-color:#fff;border:1px solid #ececec}.pecb-tooltip__pointer--left{display:flex;flex-direction:column;align-items:flex-end;align-self:stretch;position:relative;z-index:2;margin-right:-1px}.pecb-tooltip--left-top>.pecb-tooltip__pointer--left{justify-content:flex-start;padding-top:16px}.pecb-tooltip--left-center>.pecb-tooltip__pointer--left{justify-content:center}.pecb-tooltip--left-bottom>.pecb-tooltip__pointer--left{justify-content:flex-end;padding-bottom:16px}.pecb-tooltip__pointer--right{display:flex;flex-direction:column;align-items:flex-start;align-self:stretch;position:relative;z-index:2;margin-left:-1px}.pecb-tooltip--right-top>.pecb-tooltip__pointer--right{justify-content:flex-start;padding-top:16px}.pecb-tooltip--right-center>.pecb-tooltip__pointer--right{justify-content:center}.pecb-tooltip--right-bottom>.pecb-tooltip__pointer--right{justify-content:flex-end;padding-bottom:16px}.pecb-tooltip__body{flex:1;display:flex;flex-direction:column;gap:16px;padding:16px}.pecb-tooltip__content{display:flex;gap:8px;align-items:flex-start;width:100%}.pecb-tooltip__texts{flex:1;display:flex;flex-direction:column;gap:4px}.pecb-tooltip__title{font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;font-style:normal;font-weight:700;line-height:normal;margin:0}.pecb-tooltip--dark .pecb-tooltip__title{color:#fff}.pecb-tooltip--light .pecb-tooltip__title{color:#212427}.pecb-tooltip__message{font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.5;margin:0}.pecb-tooltip--dark .pecb-tooltip__message{color:#ececec}.pecb-tooltip--light .pecb-tooltip__message{color:#919191}.pecb-tooltip__close{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:20px;height:20px;padding:0;background:none;border:none;cursor:pointer;transition:opacity .15s ease-in-out}.pecb-tooltip__close:hover{opacity:.7}.pecb-tooltip__close:focus-visible{outline:2px solid #a11e29;outline-offset:2px;border-radius:2px}.pecb-tooltip__close-icon{stroke:#ececec}.pecb-tooltip--light .pecb-tooltip__close-icon{stroke:#212427}.pecb-tooltip__buttons{display:flex;align-items:center;gap:24px}.pecb-tooltip__btn{display:inline-flex;align-items:center;justify-content:center;padding:0;background:none;border:none;cursor:pointer;font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;font-style:normal;font-weight:500;line-height:normal;transition:opacity .15s ease-in-out}.pecb-tooltip__btn:hover{opacity:.8}.pecb-tooltip__btn:focus-visible{outline:2px solid #a11e29;outline-offset:2px;border-radius:2px}.pecb-tooltip__btn--primary{color:#a11e29}.pecb-tooltip__btn--secondary{color:#919191}.pecb-tooltip--light .pecb-tooltip__btn--secondary{color:#212427}\n"] }]
6374
- }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], pointerPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "pointerPosition", required: false }] }], theme: [{ type: i0.Input, args: [{ isSignal: true, alias: "theme", required: false }] }], primaryCta: [{ type: i0.Input, args: [{ isSignal: true, alias: "primaryCta", required: false }] }], secondaryCta: [{ type: i0.Input, args: [{ isSignal: true, alias: "secondaryCta", required: false }] }], showClose: [{ type: i0.Input, args: [{ isSignal: true, alias: "showClose", required: false }] }], visible: [{ type: i0.Input, args: [{ isSignal: true, alias: "visible", required: false }] }, { type: i0.Output, args: ["visibleChange"] }], closeAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeAriaLabel", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], primaryCtaClick: [{ type: i0.Output, args: ["primaryCtaClick"] }], secondaryCtaClick: [{ type: i0.Output, args: ["secondaryCtaClick"] }] } });
6404
+ args: [{ selector: 'pecb-tooltip', imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (visible()) {\n <div [class]=\"hostClasses\">\n\n <!-- \u2500\u2500 Pointer TOP (above container) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (pointerSide === 'top') {\n <div class=\"pecb-tooltip__pointer pecb-tooltip__pointer--top\">\n <svg width=\"20\" height=\"12\" viewBox=\"0 0 20 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path [attr.fill]=\"ptrBorderFill\" d=\"M0 12L10 0L20 12Z\"/>\n <path [attr.fill]=\"ptrBodyFill\" d=\"M1.8 12L10 1.8L18.2 12Z\"/>\n </svg>\n </div>\n }\n\n <!-- \u2500\u2500 Pointer LEFT (left of container) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (pointerSide === 'left') {\n <div class=\"pecb-tooltip__pointer pecb-tooltip__pointer--left\">\n <svg width=\"12\" height=\"20\" viewBox=\"0 0 12 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path [attr.fill]=\"ptrBorderFill\" d=\"M12 0L0 10L12 20Z\"/>\n <path [attr.fill]=\"ptrBodyFill\" d=\"M12 1.8L1.8 10L12 18.2Z\"/>\n </svg>\n </div>\n }\n\n <!-- \u2500\u2500 Main container \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"pecb-tooltip__container\" [style.width]=\"width()\" [style.height]=\"height()\">\n <div class=\"pecb-tooltip__body\">\n\n <div class=\"pecb-tooltip__content\">\n <div class=\"pecb-tooltip__texts\">\n @if (title()) {\n <p class=\"pecb-tooltip__title\">{{ title() }}</p>\n }\n <p class=\"pecb-tooltip__message\">{{ message() }}</p>\n </div>\n\n @if (showClose()) {\n <button\n type=\"button\"\n class=\"pecb-tooltip__close\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"close()\"\n >\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path class=\"pecb-tooltip__close-icon\" d=\"M15 5L5 15M5 5L15 15\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </button>\n }\n </div>\n\n @if (hasButtons) {\n <div class=\"pecb-tooltip__buttons\">\n @if (secondaryCta()) {\n <button type=\"button\" class=\"pecb-tooltip__btn pecb-tooltip__btn--secondary\" (click)=\"secondaryCtaClick.emit()\">{{ secondaryCta() }}</button>\n }\n @if (primaryCta()) {\n <button type=\"button\" class=\"pecb-tooltip__btn pecb-tooltip__btn--primary\" (click)=\"primaryCtaClick.emit()\">{{ primaryCta() }}</button>\n }\n </div>\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 Pointer RIGHT (right of container) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (pointerSide === 'right') {\n <div class=\"pecb-tooltip__pointer pecb-tooltip__pointer--right\">\n <svg width=\"12\" height=\"20\" viewBox=\"0 0 12 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path [attr.fill]=\"ptrBorderFill\" d=\"M0 0L12 10L0 20Z\"/>\n <path [attr.fill]=\"ptrBodyFill\" d=\"M0 1.8L10.2 10L0 18.2Z\"/>\n </svg>\n </div>\n }\n\n <!-- \u2500\u2500 Pointer BOTTOM (below container) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (pointerSide === 'bottom') {\n <div class=\"pecb-tooltip__pointer pecb-tooltip__pointer--bottom\">\n <svg width=\"20\" height=\"12\" viewBox=\"0 0 20 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path [attr.fill]=\"ptrBorderFill\" d=\"M0 0L10 12L20 0Z\"/>\n <path [attr.fill]=\"ptrBodyFill\" d=\"M1.8 0L10 10.2L18.2 0Z\"/>\n </svg>\n </div>\n }\n\n </div>\n}\n", styles: [".pecb-tooltip{display:inline-flex;flex-direction:column;width:auto;height:auto}.pecb-tooltip--left-top,.pecb-tooltip--left-center,.pecb-tooltip--left-bottom,.pecb-tooltip--right-top,.pecb-tooltip--right-center,.pecb-tooltip--right-bottom{flex-direction:row;align-items:stretch;width:auto}.pecb-tooltip--left-top>.pecb-tooltip__container,.pecb-tooltip--left-center>.pecb-tooltip__container,.pecb-tooltip--left-bottom>.pecb-tooltip__container,.pecb-tooltip--right-top>.pecb-tooltip__container,.pecb-tooltip--right-center>.pecb-tooltip__container,.pecb-tooltip--right-bottom>.pecb-tooltip__container{width:auto}.pecb-tooltip__pointer--top{display:flex;align-items:flex-end;padding:0 16px;position:relative;z-index:2;margin-bottom:-1px}.pecb-tooltip--top-left>.pecb-tooltip__pointer--top{justify-content:flex-start}.pecb-tooltip--top-center>.pecb-tooltip__pointer--top{justify-content:center}.pecb-tooltip--top-right>.pecb-tooltip__pointer--top{justify-content:flex-end}.pecb-tooltip__pointer--bottom{display:flex;align-items:flex-start;padding:0 16px;position:relative;z-index:2;margin-top:-1px}.pecb-tooltip--bottom-left>.pecb-tooltip__pointer--bottom{justify-content:flex-start}.pecb-tooltip--bottom-center>.pecb-tooltip__pointer--bottom{justify-content:center}.pecb-tooltip--bottom-right>.pecb-tooltip__pointer--bottom{justify-content:flex-end}.pecb-tooltip__container{display:flex;flex-direction:row;align-items:stretch;border-radius:8px;position:relative;z-index:1}.pecb-tooltip--dark .pecb-tooltip__container{background-color:#313131}.pecb-tooltip--light .pecb-tooltip__container{background-color:#fff;border:1px solid #ececec}.pecb-tooltip__pointer--left{display:flex;flex-direction:column;align-items:flex-end;align-self:stretch;position:relative;z-index:2;margin-right:-1px}.pecb-tooltip--left-top>.pecb-tooltip__pointer--left{justify-content:flex-start;padding-top:16px}.pecb-tooltip--left-center>.pecb-tooltip__pointer--left{justify-content:center}.pecb-tooltip--left-bottom>.pecb-tooltip__pointer--left{justify-content:flex-end;padding-bottom:16px}.pecb-tooltip__pointer--right{display:flex;flex-direction:column;align-items:flex-start;align-self:stretch;position:relative;z-index:2;margin-left:-1px}.pecb-tooltip--right-top>.pecb-tooltip__pointer--right{justify-content:flex-start;padding-top:16px}.pecb-tooltip--right-center>.pecb-tooltip__pointer--right{justify-content:center}.pecb-tooltip--right-bottom>.pecb-tooltip__pointer--right{justify-content:flex-end;padding-bottom:16px}.pecb-tooltip__body{flex:1;display:flex;flex-direction:column;gap:16px;padding:16px}.pecb-tooltip__content{display:flex;gap:8px;align-items:flex-start;width:100%}.pecb-tooltip__texts{flex:1;display:flex;flex-direction:column;gap:4px}.pecb-tooltip__title{font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;font-style:normal;font-weight:700;line-height:normal;margin:0}.pecb-tooltip--dark .pecb-tooltip__title{color:#fff}.pecb-tooltip--light .pecb-tooltip__title{color:#212427}.pecb-tooltip__message{font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.5;margin:0}.pecb-tooltip--dark .pecb-tooltip__message{color:#ececec}.pecb-tooltip--light .pecb-tooltip__message{color:#919191}.pecb-tooltip__close{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:20px;height:20px;padding:0;background:none;border:none;cursor:pointer;transition:opacity .15s ease-in-out}.pecb-tooltip__close:hover{opacity:.7}.pecb-tooltip__close:focus-visible{outline:2px solid #a11e29;outline-offset:2px;border-radius:2px}.pecb-tooltip__close-icon{stroke:#ececec}.pecb-tooltip--light .pecb-tooltip__close-icon{stroke:#212427}.pecb-tooltip__buttons{display:flex;align-items:center;gap:24px}.pecb-tooltip__btn{display:inline-flex;align-items:center;justify-content:center;padding:0;background:none;border:none;cursor:pointer;font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;font-style:normal;font-weight:500;line-height:normal;transition:opacity .15s ease-in-out}.pecb-tooltip__btn:hover{opacity:.8}.pecb-tooltip__btn:focus-visible{outline:2px solid #a11e29;outline-offset:2px;border-radius:2px}.pecb-tooltip__btn--primary{color:#a11e29}.pecb-tooltip__btn--secondary{color:#919191}.pecb-tooltip--light .pecb-tooltip__btn--secondary{color:#212427}\n"] }]
6405
+ }], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], pointerPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "pointerPosition", required: false }] }], theme: [{ type: i0.Input, args: [{ isSignal: true, alias: "theme", required: false }] }], primaryCta: [{ type: i0.Input, args: [{ isSignal: true, alias: "primaryCta", required: false }] }], secondaryCta: [{ type: i0.Input, args: [{ isSignal: true, alias: "secondaryCta", required: false }] }], showClose: [{ type: i0.Input, args: [{ isSignal: true, alias: "showClose", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], visible: [{ type: i0.Input, args: [{ isSignal: true, alias: "visible", required: false }] }, { type: i0.Output, args: ["visibleChange"] }], closeAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeAriaLabel", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], primaryCtaClick: [{ type: i0.Output, args: ["primaryCtaClick"] }], secondaryCtaClick: [{ type: i0.Output, args: ["secondaryCtaClick"] }] } });
6375
6406
 
6376
6407
  const GAP = 2; // px gap between tooltip pointer tip and the host element
6377
6408
  class TooltipDirective {
@@ -10624,130 +10655,2052 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
10624
10655
  args: [{ selector: 'pecb-media', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div\n [class]=\"hostClasses()\"\n role=\"grid\"\n [attr.aria-label]=\"ariaLabel() ?? defaultAriaLabel()\"\n>\n @for (group of groups(); track group.label) {\n <div class=\"pecb-media__group\">\n @if (group.label) {\n <h3 class=\"pecb-media__group-title\">{{ group.label }}</h3>\n }\n\n <div class=\"pecb-media__grid\">\n @for (item of group.items; track item.name) {\n <button\n class=\"pecb-media__item\"\n type=\"button\"\n [style.width.px]=\"itemSize()\"\n [style.height.px]=\"itemSize()\"\n [attr.aria-label]=\"copyLabel() + ' ' + formatName(item.name)\"\n [attr.title]=\"formatName(item.name)\"\n (click)=\"copyToClipboard(item)\"\n >\n <span\n class=\"pecb-media__svg\"\n [innerHTML]=\"item.safeHtml\"\n ></span>\n\n @if (copiedItem() === item.name) {\n <span class=\"pecb-media__tooltip\">{{ copiedLabel() }}</span>\n }\n </button>\n }\n </div>\n </div>\n }\n</div>\n", styles: [".pecb-media{display:block;width:100%}.pecb-media__group{width:100%}.pecb-media__group+.pecb-media__group{margin-top:20px}.pecb-media__group-title{font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:16px;font-weight:700;line-height:1.4;color:#313131;margin:0 0 16px}.pecb-media__grid{display:flex;flex-wrap:wrap;gap:12px;align-items:center}.pecb-media__item{position:relative;display:inline-flex;align-items:center;justify-content:center;padding:0;border:none;background:transparent;border-radius:6px;cursor:pointer;transition-property:background-color,transform;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-media__item:hover{background-color:#f4f4f4;transform:scale(1.1)}.pecb-media__item:active{transform:scale(.95)}.pecb-media__item:focus-visible{outline:2px solid #007bff;outline-offset:2px}.pecb-media__svg{display:inline-flex;align-items:center;justify-content:center;width:100%;height:100%}.pecb-media__svg svg{display:block;width:100%;height:100%}.pecb-media__tooltip{position:absolute;bottom:calc(100% + 4px);left:50%;transform:translate(-50%);padding:2px 8px;background-color:#212427;color:#fff;font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:12px;font-weight:500;line-height:20px;border-radius:6px;white-space:nowrap;pointer-events:none;animation:pecb-media-fade-in .15s ease-out;z-index:1070}.pecb-media--sm .pecb-media__grid{gap:8px}.pecb-media--lg .pecb-media__grid{gap:16px}.pecb-media--flag .pecb-media__item{overflow:hidden}@keyframes pecb-media-fade-in{0%{opacity:0;transform:translate(-50%) translateY(4px)}to{opacity:1;transform:translate(-50%) translateY(0)}}\n"] }]
10625
10656
  }], propDecorators: { type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], socialStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "socialStyle", required: false }] }], flagShape: [{ type: i0.Input, args: [{ isSignal: true, alias: "flagShape", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], defaultAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultAriaLabel", required: false }] }], copiedLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "copiedLabel", required: false }] }], copyLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "copyLabel", required: false }] }] } });
10626
10657
 
10658
+ const DEFAULT_VIDEO_PLAYER_LABELS = {
10659
+ play: 'Play',
10660
+ pause: 'Pause',
10661
+ replay: 'Replay',
10662
+ skipBack: 'Rewind {n} seconds',
10663
+ mute: 'Mute',
10664
+ unmute: 'Unmute',
10665
+ volume: 'Volume',
10666
+ captionsOn: 'Turn captions on',
10667
+ captionsOff: 'Turn captions off',
10668
+ settings: 'Settings',
10669
+ playbackSpeed: 'Playback speed',
10670
+ normalSpeed: 'Normal',
10671
+ enterFullscreen: 'Enter fullscreen',
10672
+ exitFullscreen: 'Exit fullscreen',
10673
+ seek: 'Seek',
10674
+ player: 'Video player',
10675
+ error: 'This video could not be loaded.',
10676
+ };
10677
+ /** Default playback-rate menu entries. */
10678
+ const DEFAULT_VIDEO_PLAYER_RATES = [0.5, 0.75, 1, 1.25, 1.5, 2];
10627
10679
  /**
10628
- * A stateless, injectable notification service for displaying toast notifications.
10629
- *
10630
- * This service provides a reactive way to manage notifications across the application.
10631
- * It uses RxJS Subjects to emit notification events that can be consumed by notification
10632
- * display components.
10680
+ * Formats seconds as `m:ss`, or `h:mm:ss` once the value reaches one hour.
10681
+ * Non-finite / negative input renders as `0:00`.
10682
+ */
10683
+ function formatVideoTime(seconds) {
10684
+ if (!Number.isFinite(seconds) || seconds < 0) {
10685
+ return '0:00';
10686
+ }
10687
+ const total = Math.floor(seconds);
10688
+ const h = Math.floor(total / 3600);
10689
+ const m = Math.floor((total % 3600) / 60);
10690
+ const s = total % 60;
10691
+ const mm = h > 0 ? String(m).padStart(2, '0') : String(m);
10692
+ const ss = String(s).padStart(2, '0');
10693
+ return h > 0 ? `${h}:${mm}:${ss}` : `${mm}:${ss}`;
10694
+ }
10695
+ // Internal tuning constants.
10696
+ const IDLE_HIDE_DELAY_MS = 2600;
10697
+ const LEAVE_HIDE_DELAY_MS = 700;
10698
+ const SEEK_SYNC_TOLERANCE_S = 0.5;
10699
+ // ─── Component ────────────────────────────────────────────────────────────────
10700
+ /**
10701
+ * PECB course video player.
10633
10702
  *
10634
- * @example
10635
- * ```typescript
10636
- * import { NotificationService } from '@pecb-ui/components';
10703
+ * A fully custom-skinned, accessible HTML5 `<video>` wrapper that reproduces
10704
+ * the PECB "Course Player" design: a rounded dark 16:9 stage, poster, brand
10705
+ * watermark + section label, a blurred centre play button and a bottom
10706
+ * control bar (progress, play/pause, rewind, volume, time, captions, speed
10707
+ * settings, fullscreen).
10637
10708
  *
10638
- * @Component({...})
10639
- * export class MyComponent {
10640
- * constructor(private notificationService: NotificationService) {}
10709
+ * The player is self-contained (no icon-registry / service dependencies) so it
10710
+ * can be dropped into any Angular 17+ app. It is responsive (container
10711
+ * queries), keyboard operable, touch friendly and honours
10712
+ * `prefers-reduced-motion`.
10641
10713
  *
10642
- * showSuccess(): void {
10643
- * this.notificationService.success('Operation completed successfully!');
10644
- * }
10714
+ * @example Minimal
10715
+ * ```html
10716
+ * <pecb-video-player
10717
+ * src="https://cdn.example.com/lesson.mp4"
10718
+ * poster="https://cdn.example.com/lesson.jpg"
10719
+ * label="SECTION 2">
10720
+ * </pecb-video-player>
10721
+ * ```
10645
10722
  *
10646
- * showError(): void {
10647
- * this.notificationService.error('An error occurred', 'Error Title');
10648
- * }
10649
- * }
10723
+ * @example Two-way bound & driven from outside (e.g. a transcript panel)
10724
+ * ```html
10725
+ * <pecb-video-player
10726
+ * [src]="sources"
10727
+ * [tracks]="captions"
10728
+ * [(currentTime)]="at"
10729
+ * [(playing)]="playing"
10730
+ * (playbackEnded)="onLessonFinished()">
10731
+ * </pecb-video-player>
10650
10732
  * ```
10651
10733
  *
10652
- * @publicApi
10734
+ * Theming — override the CSS custom properties on the host:
10735
+ * ```css
10736
+ * pecb-video-player { --pecb-video-radius: 0; --pecb-video-accent: #a11e29; }
10737
+ * ```
10653
10738
  */
10654
- class NotificationService {
10739
+ class VideoPlayerComponent {
10655
10740
  constructor() {
10656
- this.notificationSubject = new Subject();
10657
- this.dismissSubject = new Subject();
10658
- this.clearSubject = new Subject();
10659
- /** Default duration for notifications in milliseconds */
10660
- this.defaultDuration = 5000;
10661
- /** Default position for notifications */
10662
- this.defaultPosition = 'top-right';
10741
+ this.platformId = inject(PLATFORM_ID);
10742
+ this.zone = inject(NgZone);
10743
+ this.destroyRef = inject(DestroyRef);
10744
+ this.isBrowser = isPlatformBrowser(this.platformId);
10745
+ /** Unique id used to wire ARIA relationships inside the template. */
10746
+ this.uid = generateUniqueId('pecb-video');
10747
+ // ── Element refs ────────────────────────────────────────────────────────────
10748
+ this.rootRef = viewChild('root', ...(ngDevMode ? [{ debugName: "rootRef" }] : []));
10749
+ this.videoRef = viewChild('video', ...(ngDevMode ? [{ debugName: "videoRef" }] : []));
10750
+ this.progressRef = viewChild('progress', ...(ngDevMode ? [{ debugName: "progressRef" }] : []));
10751
+ this.settingsMenuRef = viewChild('settingsMenu', ...(ngDevMode ? [{ debugName: "settingsMenuRef" }] : []));
10752
+ // ── Media inputs ────────────────────────────────────────────────────────────
10753
+ /** Media URL, or a list of `<source>` candidates. */
10754
+ this.src = input(undefined, ...(ngDevMode ? [{ debugName: "src" }] : []));
10755
+ /** Poster image shown before playback starts. */
10756
+ this.poster = input(undefined, ...(ngDevMode ? [{ debugName: "poster" }] : []));
10757
+ /** Text tracks (captions / subtitles). The CC button appears when non-empty. */
10758
+ this.tracks = input([], ...(ngDevMode ? [{ debugName: "tracks" }] : []));
10759
+ /** Accessible title of the media, announced as part of the region label. */
10760
+ this.title = input(undefined, ...(ngDevMode ? [{ debugName: "title" }] : []));
10761
+ /** `crossorigin` attribute — required for captions hosted on another origin. */
10762
+ this.crossOrigin = input(undefined, ...(ngDevMode ? [{ debugName: "crossOrigin" }] : []));
10763
+ /** @default 'metadata' */
10764
+ this.preload = input('metadata', ...(ngDevMode ? [{ debugName: "preload" }] : []));
10765
+ /** Attempt to start playback automatically (subject to browser policy). */
10766
+ this.autoplay = input(false, ...(ngDevMode ? [{ debugName: "autoplay", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
10767
+ /** Restart from the beginning when the media ends. */
10768
+ this.loop = input(false, ...(ngDevMode ? [{ debugName: "loop", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
10769
+ /** Play inline on iOS instead of forcing the native fullscreen player. @default true */
10770
+ this.playsInline = input(true, ...(ngDevMode ? [{ debugName: "playsInline", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
10771
+ /** Initial playhead position in seconds (applied once metadata is loaded). */
10772
+ this.startTime = input(0, ...(ngDevMode ? [{ debugName: "startTime", transform: numberAttribute }] : [{ transform: numberAttribute }]));
10773
+ // ── Appearance inputs ───────────────────────────────────────────────────────
10774
+ /** Brand watermark in the bottom-left corner. Empty string hides it. @default 'PECB' */
10775
+ this.watermark = input('PECB', ...(ngDevMode ? [{ debugName: "watermark" }] : []));
10776
+ /** Large label in the bottom-right corner, e.g. `SECTION 2`. */
10777
+ this.label = input(undefined, ...(ngDevMode ? [{ debugName: "label" }] : []));
10778
+ /** CSS `aspect-ratio` of the stage. @default '16 / 9' */
10779
+ this.aspectRatio = input('16 / 9', ...(ngDevMode ? [{ debugName: "aspectRatio" }] : []));
10780
+ /** Show the large centre play/pause button. @default true */
10781
+ this.showCenterButton = input(true, ...(ngDevMode ? [{ debugName: "showCenterButton", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
10782
+ /** Show the rewind button. @default true */
10783
+ this.showSkipBack = input(true, ...(ngDevMode ? [{ debugName: "showSkipBack", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
10784
+ /** Show the mute button + volume slider. @default true */
10785
+ this.showVolume = input(true, ...(ngDevMode ? [{ debugName: "showVolume", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
10786
+ /** Show the elapsed / total time. @default true */
10787
+ this.showTime = input(true, ...(ngDevMode ? [{ debugName: "showTime", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
10788
+ /** Force-show/hide the captions button. `undefined` = show only when `tracks` is non-empty. */
10789
+ this.showCaptions = input(undefined, ...(ngDevMode ? [{ debugName: "showCaptions" }] : []));
10790
+ /** Show the settings (playback speed) button. @default true */
10791
+ this.showSettings = input(true, ...(ngDevMode ? [{ debugName: "showSettings", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
10792
+ /** Show the fullscreen button. @default true */
10793
+ this.showFullscreen = input(true, ...(ngDevMode ? [{ debugName: "showFullscreen", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
10794
+ /** Seconds the rewind button / arrow keys jump. @default 10 */
10795
+ this.seekStep = input(10, ...(ngDevMode ? [{ debugName: "seekStep", transform: numberAttribute }] : [{ transform: numberAttribute }]));
10796
+ /** Playback-rate options listed in the settings menu. */
10797
+ this.playbackRates = input(DEFAULT_VIDEO_PLAYER_RATES, ...(ngDevMode ? [{ debugName: "playbackRates" }] : []));
10798
+ /** Partial override of the user-visible strings (i18n). */
10799
+ this.labels = input({}, ...(ngDevMode ? [{ debugName: "labels" }] : []));
10800
+ // ── Two-way state (models) ──────────────────────────────────────────────────
10801
+ /** Whether the media is playing. Set it to start/stop playback. */
10802
+ this.playing = model(false, ...(ngDevMode ? [{ debugName: "playing" }] : []));
10803
+ /** Playhead position in seconds. Set it to seek. */
10804
+ this.currentTime = model(0, ...(ngDevMode ? [{ debugName: "currentTime" }] : []));
10805
+ /** Volume 0–1. */
10806
+ this.volume = model(1, ...(ngDevMode ? [{ debugName: "volume" }] : []));
10807
+ /** Muted state. */
10808
+ this.muted = model(false, ...(ngDevMode ? [{ debugName: "muted" }] : []));
10809
+ /** Playback rate. */
10810
+ this.playbackRate = model(1, ...(ngDevMode ? [{ debugName: "playbackRate" }] : []));
10811
+ /** Whether the default text track is displayed. */
10812
+ this.captionsEnabled = model(false, ...(ngDevMode ? [{ debugName: "captionsEnabled" }] : []));
10813
+ // ── Outputs ─────────────────────────────────────────────────────────────────
10814
+ /** Fires on every `timeupdate` (~4×/s while playing). */
10815
+ this.timeUpdate = output();
10816
+ /** Duration is known. */
10817
+ this.loadedMetadata = output();
10818
+ /** A seek finished. */
10819
+ this.seekEnd = output();
10820
+ /** Playback reached the end. */
10821
+ this.playbackEnded = output();
10822
+ /** Player entered / left fullscreen. */
10823
+ this.fullscreenChange = output();
10824
+ /** Media failed to load or playback was rejected. */
10825
+ this.playerError = output();
10826
+ // ── Internal state ──────────────────────────────────────────────────────────
10827
+ this.duration = signal(0, ...(ngDevMode ? [{ debugName: "duration" }] : []));
10828
+ this.bufferedEnd = signal(0, ...(ngDevMode ? [{ debugName: "bufferedEnd" }] : []));
10829
+ this.hasEnded = signal(false, ...(ngDevMode ? [{ debugName: "hasEnded" }] : []));
10830
+ this.isBuffering = signal(false, ...(ngDevMode ? [{ debugName: "isBuffering" }] : []));
10831
+ this.hasError = signal(false, ...(ngDevMode ? [{ debugName: "hasError" }] : []));
10832
+ this.isFullscreen = signal(false, ...(ngDevMode ? [{ debugName: "isFullscreen" }] : []));
10833
+ this.isSeeking = signal(false, ...(ngDevMode ? [{ debugName: "isSeeking" }] : []));
10834
+ this.settingsOpen = signal(false, ...(ngDevMode ? [{ debugName: "settingsOpen" }] : []));
10835
+ this.controlsVisible = signal(true, ...(ngDevMode ? [{ debugName: "controlsVisible" }] : []));
10836
+ this.hasStarted = signal(false, ...(ngDevMode ? [{ debugName: "hasStarted" }] : []));
10837
+ this.hideTimer = null;
10838
+ this.lastPointerType = 'mouse';
10839
+ this.startTimeApplied = false;
10840
+ this.initialLoadDone = false;
10841
+ this.pendingPlay = null;
10842
+ // ── Computed ────────────────────────────────────────────────────────────────
10843
+ this.text = computed(() => ({
10844
+ ...DEFAULT_VIDEO_PLAYER_LABELS,
10845
+ ...this.labels(),
10846
+ }), ...(ngDevMode ? [{ debugName: "text" }] : []));
10847
+ this.skipBackLabel = computed(() => this.text().skipBack.replace('{n}', String(this.seekStep())), ...(ngDevMode ? [{ debugName: "skipBackLabel" }] : []));
10848
+ this.sourceList = computed(() => {
10849
+ const s = this.src();
10850
+ if (!s)
10851
+ return [];
10852
+ return typeof s === 'string' ? [{ src: s }] : s;
10853
+ }, ...(ngDevMode ? [{ debugName: "sourceList" }] : []));
10854
+ /** Single-string src is bound directly so the browser can start fetching ASAP. */
10855
+ this.singleSrc = computed(() => {
10856
+ const s = this.src();
10857
+ return typeof s === 'string' ? s : undefined;
10858
+ }, ...(ngDevMode ? [{ debugName: "singleSrc" }] : []));
10859
+ this.progressPct = computed(() => {
10860
+ const d = this.duration();
10861
+ return d > 0 ? Math.min(100, Math.max(0, (this.currentTime() / d) * 100)) : 0;
10862
+ }, ...(ngDevMode ? [{ debugName: "progressPct" }] : []));
10863
+ this.bufferedPct = computed(() => {
10864
+ const d = this.duration();
10865
+ return d > 0 ? Math.min(100, Math.max(0, (this.bufferedEnd() / d) * 100)) : 0;
10866
+ }, ...(ngDevMode ? [{ debugName: "bufferedPct" }] : []));
10867
+ this.currentTimeLabel = computed(() => formatVideoTime(this.currentTime()), ...(ngDevMode ? [{ debugName: "currentTimeLabel" }] : []));
10868
+ this.durationLabel = computed(() => formatVideoTime(this.duration()), ...(ngDevMode ? [{ debugName: "durationLabel" }] : []));
10869
+ this.captionsButtonVisible = computed(() => this.showCaptions() ?? this.tracks().length > 0, ...(ngDevMode ? [{ debugName: "captionsButtonVisible" }] : []));
10870
+ /** Volume as an integer percentage (0 while muted) — drives the slider. */
10871
+ this.volumePct = computed(() => (this.muted() ? 0 : Math.round(this.volume() * 100)), ...(ngDevMode ? [{ debugName: "volumePct" }] : []));
10872
+ this.volumeLevel = computed(() => {
10873
+ if (this.muted() || this.volume() === 0)
10874
+ return 'muted';
10875
+ return this.volume() < 0.5 ? 'low' : 'high';
10876
+ }, ...(ngDevMode ? [{ debugName: "volumeLevel" }] : []));
10877
+ this.regionLabel = computed(() => {
10878
+ const t = this.title();
10879
+ return t ? `${this.text().player}: ${t}` : this.text().player;
10880
+ }, ...(ngDevMode ? [{ debugName: "regionLabel" }] : []));
10881
+ /** Controls are hidden only while playing and idle. */
10882
+ this.controlsHidden = computed(() => this.playing() &&
10883
+ !this.controlsVisible() &&
10884
+ !this.settingsOpen() &&
10885
+ !this.isSeeking() &&
10886
+ !this.hasEnded(), ...(ngDevMode ? [{ debugName: "controlsHidden" }] : []));
10887
+ this.hostClasses = computed(() => [
10888
+ 'pecb-video-player',
10889
+ this.playing() ? 'pecb-video-player--playing' : 'pecb-video-player--paused',
10890
+ this.controlsHidden() ? 'pecb-video-player--idle' : '',
10891
+ this.isFullscreen() ? 'pecb-video-player--fullscreen' : '',
10892
+ this.hasEnded() ? 'pecb-video-player--ended' : '',
10893
+ this.hasError() ? 'pecb-video-player--error' : '',
10894
+ this.isSeeking() ? 'pecb-video-player--seeking' : '',
10895
+ ]
10896
+ .filter(Boolean)
10897
+ .join(' '), ...(ngDevMode ? [{ debugName: "hostClasses" }] : []));
10898
+ // ── model → <video> synchronisation ───────────────────────────────────────
10899
+ effect(() => {
10900
+ const wantPlaying = this.playing();
10901
+ const video = this.videoRef()?.nativeElement;
10902
+ if (!video)
10903
+ return;
10904
+ untracked(() => {
10905
+ if (wantPlaying && video.paused) {
10906
+ this.safePlay(video);
10907
+ }
10908
+ else if (!wantPlaying && !video.paused) {
10909
+ video.pause();
10910
+ }
10911
+ });
10912
+ });
10913
+ effect(() => {
10914
+ const t = this.currentTime();
10915
+ const video = this.videoRef()?.nativeElement;
10916
+ if (!video || !Number.isFinite(t))
10917
+ return;
10918
+ untracked(() => {
10919
+ if (Math.abs(video.currentTime - t) > SEEK_SYNC_TOLERANCE_S) {
10920
+ video.currentTime = t;
10921
+ }
10922
+ });
10923
+ });
10924
+ effect(() => {
10925
+ const v = Math.min(1, Math.max(0, this.volume()));
10926
+ const m = this.muted();
10927
+ const video = this.videoRef()?.nativeElement;
10928
+ if (!video)
10929
+ return;
10930
+ untracked(() => {
10931
+ if (video.volume !== v)
10932
+ video.volume = v;
10933
+ if (video.muted !== m)
10934
+ video.muted = m;
10935
+ });
10936
+ });
10937
+ effect(() => {
10938
+ const rate = this.playbackRate();
10939
+ const video = this.videoRef()?.nativeElement;
10940
+ if (!video || !Number.isFinite(rate) || rate <= 0)
10941
+ return;
10942
+ untracked(() => {
10943
+ if (video.playbackRate !== rate)
10944
+ video.playbackRate = rate;
10945
+ });
10946
+ });
10947
+ effect(() => {
10948
+ const enabled = this.captionsEnabled();
10949
+ // Re-run when tracks change (they are re-rendered by the template).
10950
+ this.tracks();
10951
+ const video = this.videoRef()?.nativeElement;
10952
+ if (!video)
10953
+ return;
10954
+ untracked(() => this.applyCaptionMode(video, enabled));
10955
+ });
10956
+ // Reload the element when the source set changes after first render.
10957
+ effect(() => {
10958
+ this.sourceList();
10959
+ const video = this.videoRef()?.nativeElement;
10960
+ if (!video)
10961
+ return;
10962
+ untracked(() => {
10963
+ if (!this.initialLoadDone) {
10964
+ this.initialLoadDone = true;
10965
+ return;
10966
+ }
10967
+ this.resetForNewSource(video);
10968
+ });
10969
+ });
10970
+ if (this.isBrowser) {
10971
+ afterNextRender(() => this.attachIdleListeners());
10972
+ this.destroyRef.onDestroy(() => this.clearHideTimer());
10973
+ }
10663
10974
  }
10664
- /**
10665
- * Observable stream of notifications
10666
- */
10667
- get notifications$() {
10668
- return this.notificationSubject.asObservable();
10975
+ // ── Public API ──────────────────────────────────────────────────────────────
10976
+ /** Start playback. */
10977
+ play() {
10978
+ this.playing.set(true);
10669
10979
  }
10670
- /**
10671
- * Observable stream of dismissed notification IDs
10672
- */
10673
- get dismiss$() {
10674
- return this.dismissSubject.asObservable();
10980
+ /** Pause playback. */
10981
+ pause() {
10982
+ this.playing.set(false);
10675
10983
  }
10676
- /**
10677
- * Observable stream for clear all events
10678
- */
10679
- get clear$() {
10680
- return this.clearSubject.asObservable();
10984
+ /** Toggle play / pause (replays from the start after the media ended). */
10985
+ togglePlay() {
10986
+ if (this.hasError())
10987
+ return;
10988
+ if (this.hasEnded()) {
10989
+ this.seek(0);
10990
+ this.playing.set(true);
10991
+ return;
10992
+ }
10993
+ this.playing.update((p) => !p);
10994
+ }
10995
+ /** Seek to an absolute position (seconds, clamped to the duration). */
10996
+ seek(seconds) {
10997
+ const d = this.duration();
10998
+ const target = Math.max(0, d > 0 ? Math.min(d, seconds) : seconds);
10999
+ const video = this.videoRef()?.nativeElement;
11000
+ if (video) {
11001
+ video.currentTime = target;
11002
+ }
11003
+ this.currentTime.set(target);
10681
11004
  }
10682
- /**
10683
- * Show a success notification
10684
- * @param message - The notification message
10685
- * @param title - Optional title
10686
- * @param config - Optional additional configuration
10687
- */
10688
- success(message, title, config) {
10689
- return this.show({ ...config, message, title, type: 'success' });
11005
+ /** Seek relative to the current position. */
11006
+ seekBy(deltaSeconds) {
11007
+ this.seek(this.currentTime() + deltaSeconds);
10690
11008
  }
10691
- /**
10692
- * Show an error notification
10693
- * @param message - The notification message
10694
- * @param title - Optional title
10695
- * @param config - Optional additional configuration
10696
- */
10697
- error(message, title, config) {
10698
- return this.show({ ...config, message, title, type: 'error' });
11009
+ /** Rewind by `seekStep` seconds. */
11010
+ skipBack() {
11011
+ this.seekBy(-this.seekStep());
10699
11012
  }
10700
- /**
10701
- * Show a warning notification
10702
- * @param message - The notification message
10703
- * @param title - Optional title
10704
- * @param config - Optional additional configuration
10705
- */
10706
- warning(message, title, config) {
10707
- return this.show({ ...config, message, title, type: 'warning' });
11013
+ /** Toggle mute. Unmuting a zero volume restores it to 50 %. */
11014
+ toggleMute() {
11015
+ const nextMuted = !this.muted();
11016
+ if (!nextMuted && this.volume() === 0) {
11017
+ this.volume.set(0.5);
11018
+ }
11019
+ this.muted.set(nextMuted);
11020
+ }
11021
+ /** Set volume (0–1). A non-zero value also unmutes. */
11022
+ setVolume(value) {
11023
+ const v = Math.min(1, Math.max(0, value));
11024
+ this.volume.set(v);
11025
+ if (v > 0 && this.muted()) {
11026
+ this.muted.set(false);
11027
+ }
10708
11028
  }
10709
- /**
10710
- * Show an info notification
10711
- * @param message - The notification message
10712
- * @param title - Optional title
10713
- * @param config - Optional additional configuration
10714
- */
10715
- info(message, title, config) {
10716
- return this.show({ ...config, message, title, type: 'info' });
11029
+ /** Set the playback rate and close the settings menu. */
11030
+ setPlaybackRate(rate) {
11031
+ this.playbackRate.set(rate);
11032
+ this.closeSettings(true);
10717
11033
  }
10718
- /**
10719
- * Show a notification with custom configuration
10720
- * @param config - Full notification configuration
10721
- * @returns The notification ID
10722
- */
10723
- show(config) {
10724
- const id = config.id || this.generateId();
10725
- const notification = {
10726
- id,
10727
- message: config.message,
10728
- title: config.title,
10729
- type: config.type,
10730
- duration: config.duration ?? this.defaultDuration,
10731
- dismissible: config.dismissible ?? true,
10732
- position: config.position ?? this.defaultPosition,
10733
- customClass: config.customClass,
10734
- timestamp: new Date()
10735
- };
10736
- this.notificationSubject.next(notification);
10737
- return id;
11034
+ /** Toggle captions on the default text track. */
11035
+ toggleCaptions() {
11036
+ this.captionsEnabled.update((c) => !c);
10738
11037
  }
10739
- /**
10740
- * Dismiss a specific notification by ID
10741
- * @param id - The notification ID to dismiss
10742
- */
11038
+ /** Enter / exit fullscreen for the whole player (falls back to the native iOS player). */
11039
+ async toggleFullscreen() {
11040
+ if (!this.isBrowser)
11041
+ return;
11042
+ const root = this.rootRef()?.nativeElement;
11043
+ const video = this.videoRef()?.nativeElement;
11044
+ if (!root || !video)
11045
+ return;
11046
+ try {
11047
+ if (document.fullscreenElement) {
11048
+ await document.exitFullscreen();
11049
+ }
11050
+ else if (root.requestFullscreen) {
11051
+ await root.requestFullscreen();
11052
+ }
11053
+ else {
11054
+ // iOS Safari: no element fullscreen — use the native video fullscreen.
11055
+ const legacy = video;
11056
+ legacy.webkitEnterFullscreen?.();
11057
+ }
11058
+ }
11059
+ catch {
11060
+ // Fullscreen can be denied (iframe sandbox, user-gesture rules) — ignore.
11061
+ }
11062
+ }
11063
+ /** Open / close the settings (playback speed) menu. */
11064
+ toggleSettings() {
11065
+ if (this.settingsOpen()) {
11066
+ this.closeSettings(true);
11067
+ return;
11068
+ }
11069
+ this.settingsOpen.set(true);
11070
+ this.revealControls();
11071
+ // Focus the checked item once the menu is rendered.
11072
+ queueMicrotask(() => {
11073
+ const menu = this.settingsMenuRef()?.nativeElement;
11074
+ const target = menu?.querySelector('[aria-checked="true"]') ??
11075
+ menu?.querySelector('[role="menuitemradio"]');
11076
+ target?.focus();
11077
+ });
11078
+ }
11079
+ /** Close the settings menu; optionally move focus back to its trigger. */
11080
+ closeSettings(restoreFocus = false) {
11081
+ if (!this.settingsOpen())
11082
+ return;
11083
+ this.settingsOpen.set(false);
11084
+ if (restoreFocus) {
11085
+ this.rootRef()
11086
+ ?.nativeElement.querySelector(`#${this.uid}-settings-btn`)
11087
+ ?.focus();
11088
+ }
11089
+ this.scheduleHide();
11090
+ }
11091
+ // ── <video> event handlers ──────────────────────────────────────────────────
11092
+ onLoadedMetadata(video) {
11093
+ this.duration.set(Number.isFinite(video.duration) ? video.duration : 0);
11094
+ this.hasError.set(false);
11095
+ if (!this.startTimeApplied) {
11096
+ this.startTimeApplied = true;
11097
+ const start = this.startTime() > 0 ? this.startTime() : this.currentTime();
11098
+ if (start > 0) {
11099
+ video.currentTime = Math.min(start, video.duration || start);
11100
+ }
11101
+ }
11102
+ // Re-apply model values in case the effects ran before the element existed.
11103
+ video.volume = Math.min(1, Math.max(0, this.volume()));
11104
+ video.muted = this.muted();
11105
+ video.playbackRate = this.playbackRate();
11106
+ this.applyCaptionMode(video, this.captionsEnabled());
11107
+ this.loadedMetadata.emit({ currentTime: video.currentTime, duration: video.duration });
11108
+ }
11109
+ onDurationChange(video) {
11110
+ this.duration.set(Number.isFinite(video.duration) ? video.duration : 0);
11111
+ }
11112
+ onTimeUpdate(video) {
11113
+ if (!this.isSeeking()) {
11114
+ this.currentTime.set(video.currentTime);
11115
+ }
11116
+ this.timeUpdate.emit({ currentTime: video.currentTime, duration: video.duration });
11117
+ }
11118
+ onProgress(video) {
11119
+ const ranges = video.buffered;
11120
+ if (!ranges || ranges.length === 0)
11121
+ return;
11122
+ // Prefer the range containing the playhead, else the last one.
11123
+ let end = ranges.end(ranges.length - 1);
11124
+ for (let i = 0; i < ranges.length; i++) {
11125
+ if (ranges.start(i) <= video.currentTime && video.currentTime <= ranges.end(i)) {
11126
+ end = ranges.end(i);
11127
+ break;
11128
+ }
11129
+ }
11130
+ this.bufferedEnd.set(end);
11131
+ }
11132
+ onPlay() {
11133
+ this.hasEnded.set(false);
11134
+ this.hasStarted.set(true);
11135
+ this.playing.set(true);
11136
+ this.scheduleHide();
11137
+ }
11138
+ onPause() {
11139
+ this.playing.set(false);
11140
+ this.isBuffering.set(false);
11141
+ this.revealControls(false);
11142
+ }
11143
+ onEnded() {
11144
+ this.hasEnded.set(true);
11145
+ this.playing.set(false);
11146
+ this.revealControls(false);
11147
+ this.playbackEnded.emit();
11148
+ }
11149
+ onWaiting() {
11150
+ this.isBuffering.set(true);
11151
+ }
11152
+ onPlaying() {
11153
+ this.isBuffering.set(false);
11154
+ }
11155
+ onSeeked(video) {
11156
+ this.isBuffering.set(false);
11157
+ if (this.hasEnded() && video.currentTime < video.duration) {
11158
+ this.hasEnded.set(false);
11159
+ }
11160
+ this.seekEnd.emit({ currentTime: video.currentTime, duration: video.duration });
11161
+ }
11162
+ onVolumeChange(video) {
11163
+ this.volume.set(video.volume);
11164
+ this.muted.set(video.muted);
11165
+ }
11166
+ onRateChange(video) {
11167
+ this.playbackRate.set(video.playbackRate);
11168
+ }
11169
+ onMediaError(video) {
11170
+ // Ignore the spurious error fired when the element is reset with no source.
11171
+ if (!video.currentSrc && this.sourceList().length === 0)
11172
+ return;
11173
+ this.hasError.set(true);
11174
+ this.isBuffering.set(false);
11175
+ this.playing.set(false);
11176
+ this.playerError.emit({
11177
+ code: 'media',
11178
+ message: video.error?.message || 'Media error',
11179
+ originalError: video.error ?? undefined,
11180
+ });
11181
+ }
11182
+ // ── Stage interaction ───────────────────────────────────────────────────────
11183
+ onStagePointerDown(event) {
11184
+ this.lastPointerType = event.pointerType || 'mouse';
11185
+ }
11186
+ /** Click on the video surface: toggles play on mouse; on touch, first reveals the controls. */
11187
+ onStageClick() {
11188
+ if (this.settingsOpen()) {
11189
+ this.closeSettings();
11190
+ return;
11191
+ }
11192
+ if (this.lastPointerType === 'touch' && this.controlsHidden()) {
11193
+ this.revealControls();
11194
+ return;
11195
+ }
11196
+ this.togglePlay();
11197
+ }
11198
+ onStageDoubleClick() {
11199
+ if (this.lastPointerType !== 'touch' && this.showFullscreen()) {
11200
+ void this.toggleFullscreen();
11201
+ }
11202
+ }
11203
+ /** Keyboard shortcuts on the player region. */
11204
+ onRootKeydown(event) {
11205
+ const target = event.target;
11206
+ const root = this.rootRef()?.nativeElement;
11207
+ if (event.key === 'Escape' && this.settingsOpen()) {
11208
+ event.preventDefault();
11209
+ this.closeSettings(true);
11210
+ return;
11211
+ }
11212
+ // Let native controls (buttons, sliders, menu) handle their own keys.
11213
+ const onInteractive = !!target &&
11214
+ target !== root &&
11215
+ (target.matches('button, input, [role="slider"], [role="menuitemradio"]') ||
11216
+ !!target.closest('[role="menu"]'));
11217
+ const nativeKey = event.key === ' ' ||
11218
+ event.key === 'Enter' ||
11219
+ event.key.startsWith('Arrow') ||
11220
+ event.key === 'Home' ||
11221
+ event.key === 'End';
11222
+ if (onInteractive && nativeKey) {
11223
+ this.revealControls();
11224
+ return;
11225
+ }
11226
+ let handled = true;
11227
+ switch (event.key) {
11228
+ case ' ':
11229
+ case 'k':
11230
+ case 'K':
11231
+ this.togglePlay();
11232
+ break;
11233
+ case 'ArrowLeft':
11234
+ case 'j':
11235
+ case 'J':
11236
+ this.seekBy(-this.seekStep());
11237
+ break;
11238
+ case 'ArrowRight':
11239
+ case 'l':
11240
+ case 'L':
11241
+ this.seekBy(this.seekStep());
11242
+ break;
11243
+ case 'ArrowUp':
11244
+ if (this.showVolume())
11245
+ this.setVolume(this.volume() + 0.1);
11246
+ break;
11247
+ case 'ArrowDown':
11248
+ if (this.showVolume())
11249
+ this.setVolume(this.volume() - 0.1);
11250
+ break;
11251
+ case 'Home':
11252
+ this.seek(0);
11253
+ break;
11254
+ case 'End':
11255
+ this.seek(this.duration());
11256
+ break;
11257
+ case 'm':
11258
+ case 'M':
11259
+ if (this.showVolume())
11260
+ this.toggleMute();
11261
+ break;
11262
+ case 'f':
11263
+ case 'F':
11264
+ if (this.showFullscreen())
11265
+ void this.toggleFullscreen();
11266
+ break;
11267
+ case 'c':
11268
+ case 'C':
11269
+ if (this.captionsButtonVisible())
11270
+ this.toggleCaptions();
11271
+ break;
11272
+ default:
11273
+ handled = false;
11274
+ }
11275
+ if (handled) {
11276
+ event.preventDefault();
11277
+ this.revealControls();
11278
+ }
11279
+ }
11280
+ // ── Progress / scrubbing ────────────────────────────────────────────────────
11281
+ onProgressPointerDown(event) {
11282
+ if (event.pointerType === 'mouse' && event.button !== 0)
11283
+ return;
11284
+ const bar = this.progressRef()?.nativeElement;
11285
+ if (!bar || this.duration() <= 0)
11286
+ return;
11287
+ event.preventDefault();
11288
+ bar.setPointerCapture(event.pointerId);
11289
+ this.isSeeking.set(true);
11290
+ this.seekToPointer(event, bar);
11291
+ }
11292
+ onProgressPointerMove(event) {
11293
+ if (!this.isSeeking())
11294
+ return;
11295
+ this.seekToPointer(event, this.progressRef()?.nativeElement);
11296
+ }
11297
+ onProgressPointerUp(event) {
11298
+ if (!this.isSeeking())
11299
+ return;
11300
+ const bar = this.progressRef()?.nativeElement;
11301
+ if (bar?.hasPointerCapture(event.pointerId)) {
11302
+ bar.releasePointerCapture(event.pointerId);
11303
+ }
11304
+ this.seekToPointer(event, bar);
11305
+ this.isSeeking.set(false);
11306
+ this.scheduleHide();
11307
+ }
11308
+ onProgressKeydown(event) {
11309
+ const d = this.duration();
11310
+ if (d <= 0)
11311
+ return;
11312
+ let handled = true;
11313
+ switch (event.key) {
11314
+ case 'ArrowLeft':
11315
+ case 'ArrowDown':
11316
+ this.seekBy(-this.seekStep());
11317
+ break;
11318
+ case 'ArrowRight':
11319
+ case 'ArrowUp':
11320
+ this.seekBy(this.seekStep());
11321
+ break;
11322
+ case 'PageDown':
11323
+ this.seekBy(-d * 0.1);
11324
+ break;
11325
+ case 'PageUp':
11326
+ this.seekBy(d * 0.1);
11327
+ break;
11328
+ case 'Home':
11329
+ this.seek(0);
11330
+ break;
11331
+ case 'End':
11332
+ this.seek(d);
11333
+ break;
11334
+ default:
11335
+ handled = false;
11336
+ }
11337
+ if (handled) {
11338
+ event.preventDefault();
11339
+ event.stopPropagation();
11340
+ this.revealControls();
11341
+ }
11342
+ }
11343
+ seekToPointer(event, bar) {
11344
+ if (!bar)
11345
+ return;
11346
+ const rect = bar.getBoundingClientRect();
11347
+ if (rect.width <= 0)
11348
+ return;
11349
+ const fraction = Math.min(1, Math.max(0, (event.clientX - rect.left) / rect.width));
11350
+ this.seek(fraction * this.duration());
11351
+ }
11352
+ // ── Volume slider ───────────────────────────────────────────────────────────
11353
+ onVolumeInput(event) {
11354
+ const value = Number(event.target.value);
11355
+ this.setVolume(value / 100);
11356
+ }
11357
+ // ── Settings menu ───────────────────────────────────────────────────────────
11358
+ onSettingsMenuKeydown(event) {
11359
+ const menu = this.settingsMenuRef()?.nativeElement;
11360
+ if (!menu)
11361
+ return;
11362
+ const items = Array.from(menu.querySelectorAll('[role="menuitemradio"]'));
11363
+ if (items.length === 0)
11364
+ return;
11365
+ const index = items.indexOf(document.activeElement);
11366
+ let next;
11367
+ switch (event.key) {
11368
+ case 'ArrowDown':
11369
+ next = (index + 1) % items.length;
11370
+ break;
11371
+ case 'ArrowUp':
11372
+ next = (index - 1 + items.length) % items.length;
11373
+ break;
11374
+ case 'Home':
11375
+ next = 0;
11376
+ break;
11377
+ case 'End':
11378
+ next = items.length - 1;
11379
+ break;
11380
+ case 'Tab':
11381
+ this.closeSettings();
11382
+ return;
11383
+ default:
11384
+ return;
11385
+ }
11386
+ event.preventDefault();
11387
+ event.stopPropagation();
11388
+ items[next]?.focus();
11389
+ }
11390
+ onRootFocusOut(event) {
11391
+ const root = this.rootRef()?.nativeElement;
11392
+ const next = event.relatedTarget;
11393
+ if (this.settingsOpen() && root && (!next || !root.contains(next))) {
11394
+ this.closeSettings();
11395
+ }
11396
+ }
11397
+ onDocumentFullscreenChange() {
11398
+ if (!this.isBrowser)
11399
+ return;
11400
+ const root = this.rootRef()?.nativeElement;
11401
+ const active = !!root && document.fullscreenElement === root;
11402
+ if (active !== this.isFullscreen()) {
11403
+ this.isFullscreen.set(active);
11404
+ this.fullscreenChange.emit(active);
11405
+ }
11406
+ }
11407
+ onDocumentPointerDown(event) {
11408
+ if (!this.settingsOpen())
11409
+ return;
11410
+ const root = this.rootRef()?.nativeElement;
11411
+ const menu = this.settingsMenuRef()?.nativeElement;
11412
+ const target = event.target;
11413
+ const settingsBtn = root?.querySelector(`#${this.uid}-settings-btn`);
11414
+ if (!menu?.contains(target) && !settingsBtn?.contains(target)) {
11415
+ this.closeSettings();
11416
+ }
11417
+ }
11418
+ // ── Controls visibility ─────────────────────────────────────────────────────
11419
+ /** Show the controls and (if playing) restart the idle timer. */
11420
+ revealControls(reschedule = true) {
11421
+ if (!this.controlsVisible()) {
11422
+ this.controlsVisible.set(true);
11423
+ }
11424
+ if (reschedule) {
11425
+ this.scheduleHide();
11426
+ }
11427
+ else {
11428
+ this.clearHideTimer();
11429
+ }
11430
+ }
11431
+ scheduleHide(delay = IDLE_HIDE_DELAY_MS) {
11432
+ this.clearHideTimer();
11433
+ if (!this.isBrowser || !this.playing())
11434
+ return;
11435
+ this.zone.runOutsideAngular(() => {
11436
+ this.hideTimer = setTimeout(() => {
11437
+ this.hideTimer = null;
11438
+ if (!this.playing() || this.settingsOpen() || this.isSeeking())
11439
+ return;
11440
+ // Keep controls visible while a control (not the root itself) has focus.
11441
+ const root = this.rootRef()?.nativeElement;
11442
+ const active = document.activeElement;
11443
+ if (root && active && active !== root && root.contains(active))
11444
+ return;
11445
+ this.zone.run(() => this.controlsVisible.set(false));
11446
+ }, delay);
11447
+ });
11448
+ }
11449
+ clearHideTimer() {
11450
+ if (this.hideTimer !== null) {
11451
+ clearTimeout(this.hideTimer);
11452
+ this.hideTimer = null;
11453
+ }
11454
+ }
11455
+ /**
11456
+ * High-frequency pointer listeners are registered outside the Angular zone so
11457
+ * mouse movement over the stage doesn't trigger change detection; we only
11458
+ * re-enter the zone when the visibility state actually flips.
11459
+ */
11460
+ attachIdleListeners() {
11461
+ const root = this.rootRef()?.nativeElement;
11462
+ if (!root)
11463
+ return;
11464
+ this.zone.runOutsideAngular(() => {
11465
+ const onMove = () => {
11466
+ if (this.controlsVisible()) {
11467
+ this.scheduleHide();
11468
+ }
11469
+ else {
11470
+ this.zone.run(() => this.revealControls());
11471
+ }
11472
+ };
11473
+ const onLeave = () => {
11474
+ if (this.playing())
11475
+ this.scheduleHide(LEAVE_HIDE_DELAY_MS);
11476
+ };
11477
+ root.addEventListener('pointermove', onMove, { passive: true });
11478
+ root.addEventListener('pointerleave', onLeave, { passive: true });
11479
+ this.destroyRef.onDestroy(() => {
11480
+ root.removeEventListener('pointermove', onMove);
11481
+ root.removeEventListener('pointerleave', onLeave);
11482
+ });
11483
+ });
11484
+ }
11485
+ // ── Helpers ─────────────────────────────────────────────────────────────────
11486
+ safePlay(video) {
11487
+ if (this.pendingPlay)
11488
+ return;
11489
+ const attempt = video.play();
11490
+ if (!attempt || typeof attempt.then !== 'function')
11491
+ return;
11492
+ this.pendingPlay = attempt
11493
+ .catch((err) => {
11494
+ this.playing.set(false);
11495
+ this.playerError.emit({
11496
+ code: 'play-rejected',
11497
+ message: err instanceof Error ? err.message : 'Playback was rejected',
11498
+ originalError: err,
11499
+ });
11500
+ })
11501
+ .finally(() => {
11502
+ this.pendingPlay = null;
11503
+ });
11504
+ }
11505
+ applyCaptionMode(video, enabled) {
11506
+ const list = video.textTracks;
11507
+ if (!list || list.length === 0)
11508
+ return;
11509
+ const configured = this.tracks();
11510
+ const defaultIndex = Math.max(0, configured.findIndex((t) => t.default));
11511
+ for (let i = 0; i < list.length; i++) {
11512
+ const track = list[i];
11513
+ if (track.kind !== 'captions' && track.kind !== 'subtitles')
11514
+ continue;
11515
+ track.mode = enabled && i === defaultIndex ? 'showing' : 'hidden';
11516
+ }
11517
+ }
11518
+ resetForNewSource(video) {
11519
+ this.hasError.set(false);
11520
+ this.hasEnded.set(false);
11521
+ this.hasStarted.set(false);
11522
+ this.isBuffering.set(false);
11523
+ this.bufferedEnd.set(0);
11524
+ this.duration.set(0);
11525
+ this.startTimeApplied = false;
11526
+ // Adopt the new source's start position straight away (it is re-applied for
11527
+ // real once metadata lands) so consumers driving `src` + `startTime`
11528
+ // together — a course player switching lessons — never flash 0:00.
11529
+ this.currentTime.set(Math.max(0, this.startTime()));
11530
+ video.load();
11531
+ if (this.playing()) {
11532
+ this.safePlay(video);
11533
+ }
11534
+ }
11535
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: VideoPlayerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
11536
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: VideoPlayerComponent, isStandalone: true, selector: "pecb-video-player", inputs: { src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: false, transformFunction: null }, poster: { classPropertyName: "poster", publicName: "poster", isSignal: true, isRequired: false, transformFunction: null }, tracks: { classPropertyName: "tracks", publicName: "tracks", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, crossOrigin: { classPropertyName: "crossOrigin", publicName: "crossOrigin", isSignal: true, isRequired: false, transformFunction: null }, preload: { classPropertyName: "preload", publicName: "preload", isSignal: true, isRequired: false, transformFunction: null }, autoplay: { classPropertyName: "autoplay", publicName: "autoplay", isSignal: true, isRequired: false, transformFunction: null }, loop: { classPropertyName: "loop", publicName: "loop", isSignal: true, isRequired: false, transformFunction: null }, playsInline: { classPropertyName: "playsInline", publicName: "playsInline", isSignal: true, isRequired: false, transformFunction: null }, startTime: { classPropertyName: "startTime", publicName: "startTime", isSignal: true, isRequired: false, transformFunction: null }, watermark: { classPropertyName: "watermark", publicName: "watermark", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, aspectRatio: { classPropertyName: "aspectRatio", publicName: "aspectRatio", isSignal: true, isRequired: false, transformFunction: null }, showCenterButton: { classPropertyName: "showCenterButton", publicName: "showCenterButton", isSignal: true, isRequired: false, transformFunction: null }, showSkipBack: { classPropertyName: "showSkipBack", publicName: "showSkipBack", isSignal: true, isRequired: false, transformFunction: null }, showVolume: { classPropertyName: "showVolume", publicName: "showVolume", isSignal: true, isRequired: false, transformFunction: null }, showTime: { classPropertyName: "showTime", publicName: "showTime", isSignal: true, isRequired: false, transformFunction: null }, showCaptions: { classPropertyName: "showCaptions", publicName: "showCaptions", isSignal: true, isRequired: false, transformFunction: null }, showSettings: { classPropertyName: "showSettings", publicName: "showSettings", isSignal: true, isRequired: false, transformFunction: null }, showFullscreen: { classPropertyName: "showFullscreen", publicName: "showFullscreen", isSignal: true, isRequired: false, transformFunction: null }, seekStep: { classPropertyName: "seekStep", publicName: "seekStep", isSignal: true, isRequired: false, transformFunction: null }, playbackRates: { classPropertyName: "playbackRates", publicName: "playbackRates", isSignal: true, isRequired: false, transformFunction: null }, labels: { classPropertyName: "labels", publicName: "labels", isSignal: true, isRequired: false, transformFunction: null }, playing: { classPropertyName: "playing", publicName: "playing", isSignal: true, isRequired: false, transformFunction: null }, currentTime: { classPropertyName: "currentTime", publicName: "currentTime", isSignal: true, isRequired: false, transformFunction: null }, volume: { classPropertyName: "volume", publicName: "volume", isSignal: true, isRequired: false, transformFunction: null }, muted: { classPropertyName: "muted", publicName: "muted", isSignal: true, isRequired: false, transformFunction: null }, playbackRate: { classPropertyName: "playbackRate", publicName: "playbackRate", isSignal: true, isRequired: false, transformFunction: null }, captionsEnabled: { classPropertyName: "captionsEnabled", publicName: "captionsEnabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { playing: "playingChange", currentTime: "currentTimeChange", volume: "volumeChange", muted: "mutedChange", playbackRate: "playbackRateChange", captionsEnabled: "captionsEnabledChange", timeUpdate: "timeUpdate", loadedMetadata: "loadedMetadata", seekEnd: "seekEnd", playbackEnded: "playbackEnded", fullscreenChange: "fullscreenChange", playerError: "playerError" }, host: { listeners: { "document:fullscreenchange": "onDocumentFullscreenChange()", "document:pointerdown": "onDocumentPointerDown($event)" }, properties: { "attr.title": "null", "style.--pecb-video-aspect-ratio": "aspectRatio()" }, classAttribute: "pecb-video-player-host" }, viewQueries: [{ propertyName: "rootRef", first: true, predicate: ["root"], descendants: true, isSignal: true }, { propertyName: "videoRef", first: true, predicate: ["video"], descendants: true, isSignal: true }, { propertyName: "progressRef", first: true, predicate: ["progress"], descendants: true, isSignal: true }, { propertyName: "settingsMenuRef", first: true, predicate: ["settingsMenu"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n #root\n [class]=\"hostClasses()\"\n role=\"region\"\n [attr.aria-label]=\"regionLabel()\"\n aria-roledescription=\"video player\"\n tabindex=\"0\"\n (keydown)=\"onRootKeydown($event)\"\n (focusout)=\"onRootFocusOut($event)\"\n>\n <!-- \u2500\u2500 Media \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <video\n #video\n class=\"pecb-video-player__media\"\n [attr.src]=\"singleSrc() ?? null\"\n [attr.poster]=\"poster() ?? null\"\n [attr.preload]=\"preload()\"\n [attr.crossorigin]=\"crossOrigin() ?? null\"\n [attr.playsinline]=\"playsInline() ? '' : null\"\n [attr.webkit-playsinline]=\"playsInline() ? '' : null\"\n [attr.autoplay]=\"autoplay() ? '' : null\"\n [attr.loop]=\"loop() ? '' : null\"\n [attr.aria-label]=\"title() ?? null\"\n (loadedmetadata)=\"onLoadedMetadata(video)\"\n (durationchange)=\"onDurationChange(video)\"\n (timeupdate)=\"onTimeUpdate(video)\"\n (progress)=\"onProgress(video)\"\n (play)=\"onPlay()\"\n (pause)=\"onPause()\"\n (ended)=\"onEnded()\"\n (waiting)=\"onWaiting()\"\n (playing)=\"onPlaying()\"\n (seeked)=\"onSeeked(video)\"\n (volumechange)=\"onVolumeChange(video)\"\n (ratechange)=\"onRateChange(video)\"\n (error)=\"onMediaError(video)\"\n (pointerdown)=\"onStagePointerDown($event)\"\n (click)=\"onStageClick()\"\n (dblclick)=\"onStageDoubleClick()\"\n >\n @if (!singleSrc()) {\n @for (s of sourceList(); track s.src) {\n <source [attr.src]=\"s.src\" [attr.type]=\"s.type ?? null\" [attr.media]=\"s.media ?? null\" />\n }\n }\n @for (t of tracks(); track t.src) {\n <track\n [attr.src]=\"t.src\"\n [attr.kind]=\"t.kind ?? 'captions'\"\n [attr.srclang]=\"t.srclang\"\n [attr.label]=\"t.label ?? null\"\n [attr.default]=\"t.default ? '' : null\"\n />\n }\n </video>\n\n <!-- \u2500\u2500 Gradient scrim + branding \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"pecb-video-player__scrim\" aria-hidden=\"true\"></div>\n\n @if (watermark()) {\n <div class=\"pecb-video-player__watermark\" aria-hidden=\"true\">{{ watermark() }}</div>\n }\n @if (label()) {\n <div class=\"pecb-video-player__label\" aria-hidden=\"true\">{{ label() }}</div>\n }\n\n <!-- Custom overlays (quizzes, end screens\u2026) projected by the consumer -->\n <ng-content select=\"[pecbVideoOverlay]\"></ng-content>\n\n <!-- \u2500\u2500 Centre state: error / buffering / play-pause \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (hasError()) {\n <div class=\"pecb-video-player__error\" role=\"alert\">\n <svg class=\"pecb-video-player__error-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <path d=\"M12 8v4.5\" />\n <circle cx=\"12\" cy=\"16\" r=\".6\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n <span>{{ text().error }}</span>\n </div>\n } @else if (isBuffering() && playing()) {\n <div class=\"pecb-video-player__spinner\" aria-hidden=\"true\"></div>\n } @else if (showCenterButton()) {\n <button\n type=\"button\"\n class=\"pecb-video-player__center\"\n [attr.aria-label]=\"hasEnded() ? text().replay : playing() ? text().pause : text().play\"\n (click)=\"togglePlay()\"\n >\n @if (hasEnded()) {\n <svg width=\"26\" height=\"26\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M3 12a9 9 0 1 0 3-6.7\" />\n <path d=\"M3 4v5h5\" />\n </svg>\n } @else if (playing()) {\n <svg width=\"26\" height=\"26\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <rect x=\"6\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n <rect x=\"14\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n </svg>\n } @else {\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <polygon points=\"6 4 20 12 6 20 6 4\" />\n </svg>\n }\n </button>\n }\n\n <!-- \u2500\u2500 Control bar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"pecb-video-player__controls\" [attr.aria-hidden]=\"controlsHidden() ? 'true' : null\">\n <!-- Progress / seek bar -->\n <div\n #progress\n class=\"pecb-video-player__progress\"\n role=\"slider\"\n [attr.tabindex]=\"controlsHidden() ? -1 : 0\"\n [attr.aria-label]=\"text().seek\"\n aria-valuemin=\"0\"\n [attr.aria-valuemax]=\"duration()\"\n [attr.aria-valuenow]=\"currentTime()\"\n [attr.aria-valuetext]=\"currentTimeLabel() + ' / ' + durationLabel()\"\n [attr.aria-disabled]=\"duration() > 0 ? null : 'true'\"\n (pointerdown)=\"onProgressPointerDown($event)\"\n (pointermove)=\"onProgressPointerMove($event)\"\n (pointerup)=\"onProgressPointerUp($event)\"\n (pointercancel)=\"onProgressPointerUp($event)\"\n (keydown)=\"onProgressKeydown($event)\"\n >\n <div class=\"pecb-video-player__track\">\n <span class=\"pecb-video-player__buffered\" [style.width.%]=\"bufferedPct()\"></span>\n <span class=\"pecb-video-player__fill\" [style.width.%]=\"progressPct()\"></span>\n <span class=\"pecb-video-player__knob\" [style.left.%]=\"progressPct()\"></span>\n </div>\n </div>\n\n <!-- Buttons row -->\n <div class=\"pecb-video-player__bar\">\n <button\n type=\"button\"\n class=\"pecb-video-player__btn\"\n [attr.aria-label]=\"hasEnded() ? text().replay : playing() ? text().pause : text().play\"\n [attr.tabindex]=\"controlsHidden() ? -1 : 0\"\n (click)=\"togglePlay()\"\n >\n @if (playing()) {\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <rect x=\"6\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n <rect x=\"14\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n </svg>\n } @else {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <polygon points=\"6 4 20 12 6 20 6 4\" />\n </svg>\n }\n </button>\n\n @if (showSkipBack()) {\n <button\n type=\"button\"\n class=\"pecb-video-player__btn\"\n [attr.aria-label]=\"skipBackLabel()\"\n [attr.tabindex]=\"controlsHidden() ? -1 : 0\"\n (click)=\"skipBack()\"\n >\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <polygon points=\"19 5 9 12 19 19 19 5\" fill=\"currentColor\" stroke=\"none\" />\n <line x1=\"6\" y1=\"5\" x2=\"6\" y2=\"19\" />\n </svg>\n </button>\n }\n\n @if (showVolume()) {\n <div class=\"pecb-video-player__volume\">\n <button\n type=\"button\"\n class=\"pecb-video-player__btn\"\n [attr.aria-label]=\"muted() ? text().unmute : text().mute\"\n [attr.aria-pressed]=\"muted()\"\n [attr.tabindex]=\"controlsHidden() ? -1 : 0\"\n (click)=\"toggleMute()\"\n >\n @switch (volumeLevel()) {\n @case ('muted') {\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <polygon points=\"4 9 8 9 12 5 12 19 8 15 4 15 4 9\" fill=\"currentColor\" stroke=\"none\" />\n <path d=\"m16 9.5 5 5M21 9.5l-5 5\" />\n </svg>\n }\n @case ('low') {\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <polygon points=\"4 9 8 9 12 5 12 19 8 15 4 15 4 9\" fill=\"currentColor\" stroke=\"none\" />\n <path d=\"M16 9a4 4 0 0 1 0 6\" />\n </svg>\n }\n @default {\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <polygon points=\"4 9 8 9 12 5 12 19 8 15 4 15 4 9\" fill=\"currentColor\" stroke=\"none\" />\n <path d=\"M16 9a4 4 0 0 1 0 6\" />\n <path d=\"M18.5 6.5a8 8 0 0 1 0 11\" />\n </svg>\n }\n }\n </button>\n <input\n class=\"pecb-video-player__volume-slider\"\n type=\"range\"\n min=\"0\"\n max=\"100\"\n step=\"1\"\n [value]=\"volumePct()\"\n [attr.aria-label]=\"text().volume\"\n [attr.aria-valuetext]=\"volumePct() + '%'\"\n [attr.tabindex]=\"controlsHidden() ? -1 : 0\"\n [style.--pecb-video-volume-pct]=\"volumePct() + '%'\"\n (input)=\"onVolumeInput($event)\"\n />\n </div>\n }\n\n @if (showTime()) {\n <span class=\"pecb-video-player__time\" aria-live=\"off\">\n <span class=\"pecb-video-player__time-current\">{{ currentTimeLabel() }}</span>\n <span class=\"pecb-video-player__time-sep\" aria-hidden=\"true\"> / </span>\n <span class=\"pecb-video-player__time-total\">{{ durationLabel() }}</span>\n </span>\n }\n\n <div class=\"pecb-video-player__spacer\"></div>\n\n @if (captionsButtonVisible()) {\n <button\n type=\"button\"\n class=\"pecb-video-player__btn\"\n [class.pecb-video-player__btn--active]=\"captionsEnabled()\"\n [attr.aria-label]=\"captionsEnabled() ? text().captionsOff : text().captionsOn\"\n [attr.aria-pressed]=\"captionsEnabled()\"\n [attr.tabindex]=\"controlsHidden() ? -1 : 0\"\n (click)=\"toggleCaptions()\"\n >\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"2\" />\n <path d=\"M10 10a2 2 0 1 0 0 4M17 10a2 2 0 1 0 0 4\" />\n </svg>\n </button>\n }\n\n @if (showSettings()) {\n <div class=\"pecb-video-player__settings\">\n <button\n type=\"button\"\n class=\"pecb-video-player__btn\"\n [id]=\"uid + '-settings-btn'\"\n [class.pecb-video-player__btn--active]=\"settingsOpen()\"\n [attr.aria-label]=\"text().settings\"\n aria-haspopup=\"menu\"\n [attr.aria-expanded]=\"settingsOpen()\"\n [attr.aria-controls]=\"uid + '-settings-menu'\"\n [attr.tabindex]=\"controlsHidden() ? -1 : 0\"\n (click)=\"toggleSettings()\"\n >\n <svg width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"3\" />\n <path d=\"M19.4 13.5a1.6 1.6 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.6 1.6 0 0 0-2.7 1.1V21a2 2 0 0 1-4 0v-.2A1.6 1.6 0 0 0 6.6 19l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1A1.6 1.6 0 0 0 3 13.5H3a2 2 0 0 1 0-4h.1a1.6 1.6 0 0 0 1.5-1.1 1.6 1.6 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.6 1.6 0 0 0 1.8.3H9.6A1.6 1.6 0 0 0 10.6 3V3a2 2 0 0 1 4 0v.1a1.6 1.6 0 0 0 2.7 1.1l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.6 1.6 0 0 0-.3 1.8v.1a1.6 1.6 0 0 0 1.5 1H21a2 2 0 0 1 0 4h-.1a1.6 1.6 0 0 0-1.5 1Z\" />\n </svg>\n </button>\n\n @if (settingsOpen()) {\n <div\n #settingsMenu\n class=\"pecb-video-player__menu\"\n role=\"menu\"\n [id]=\"uid + '-settings-menu'\"\n [attr.aria-label]=\"text().playbackSpeed\"\n (keydown)=\"onSettingsMenuKeydown($event)\"\n >\n <div class=\"pecb-video-player__menu-title\">{{ text().playbackSpeed }}</div>\n @for (rate of playbackRates(); track rate) {\n <button\n type=\"button\"\n class=\"pecb-video-player__menu-item\"\n role=\"menuitemradio\"\n [attr.aria-checked]=\"rate === playbackRate()\"\n tabindex=\"-1\"\n (click)=\"setPlaybackRate(rate)\"\n >\n <span class=\"pecb-video-player__menu-check\" aria-hidden=\"true\">\n @if (rate === playbackRate()) {\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n }\n </span>\n <span>{{ rate === 1 ? text().normalSpeed : rate + '\u00D7' }}</span>\n </button>\n }\n </div>\n }\n </div>\n }\n\n @if (showFullscreen()) {\n <button\n type=\"button\"\n class=\"pecb-video-player__btn\"\n [attr.aria-label]=\"isFullscreen() ? text().exitFullscreen : text().enterFullscreen\"\n [attr.aria-pressed]=\"isFullscreen()\"\n [attr.tabindex]=\"controlsHidden() ? -1 : 0\"\n (click)=\"toggleFullscreen()\"\n >\n @if (isFullscreen()) {\n <svg width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3M3 16h3a2 2 0 0 1 2 2v3m13-5h-3a2 2 0 0 0-2 2v3\" />\n </svg>\n } @else {\n <svg width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3M3 16v3a2 2 0 0 0 2 2h3m13-5v3a2 2 0 0 1-2 2h-3\" />\n </svg>\n }\n </button>\n }\n </div>\n </div>\n</div>\n", styles: [":host{display:block;width:100%;max-width:100%;container-type:inline-size;container-name:pecb-video-player;--pecb-video-radius: 12px;--pecb-video-bg: #0b0b0d;--pecb-video-accent: #ffffff;--pecb-video-controls-color: rgba(255, 255, 255, .92);--pecb-video-font-display: Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif;--pecb-video-font-body: \"Inter\", Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif;--pecb-video-focus-ring: #ffffff}.pecb-video-player{position:relative;width:100%;aspect-ratio:var(--pecb-video-aspect-ratio, 16/9);max-height:100%;background:var(--pecb-video-bg);border-radius:var(--pecb-video-radius);overflow:hidden;color:var(--pecb-video-controls-color);font-family:var(--pecb-video-font-display);outline:none;-webkit-tap-highlight-color:transparent;-webkit-user-select:none;user-select:none}.pecb-video-player:focus-visible{box-shadow:0 0 0 2px var(--pecb-video-bg),0 0 0 4px var(--pecb-video-focus-ring)}.pecb-video-player__media{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;background:var(--pecb-video-bg);display:block;cursor:pointer}.pecb-video-player__media::cue{font-family:var(--pecb-video-font-body);font-size:.95em;background:#0009}.pecb-video-player__scrim{position:absolute;inset:0;pointer-events:none;background:linear-gradient(180deg,#0000001f,#0000 30% 60%,#0000008c)}.pecb-video-player__watermark,.pecb-video-player__label{position:absolute;bottom:64px;pointer-events:none;font-family:var(--pecb-video-font-display);font-weight:800;color:#ffffffeb;white-space:nowrap;max-width:45%;overflow:hidden;text-overflow:ellipsis;text-shadow:0 1px 2px rgba(0,0,0,.25);transition:opacity .22s ease}.pecb-video-player__watermark{left:24px;font-size:22px;letter-spacing:.04em}.pecb-video-player__label{right:24px;font-size:26px;letter-spacing:.05em;text-transform:uppercase}.pecb-video-player__center{padding:0;border:none;background:none;font:inherit;color:inherit;position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:64px;height:64px;border-radius:50%;background:#1414168c;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);color:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:background .15s ease-in-out,transform .15s ease-in-out,opacity .22s ease}.pecb-video-player__center:focus{outline:none}.pecb-video-player__center:hover{background:#141416b8}.pecb-video-player__center:active{transform:translate(-50%,-50%) scale(.96)}.pecb-video-player__center:focus-visible{outline:2px solid var(--pecb-video-focus-ring);outline-offset:2px}.pecb-video-player__center svg polygon{transform:translate(1px)}.pecb-video-player__spinner{position:absolute;left:50%;top:50%;width:48px;height:48px;margin:-24px 0 0 -24px;border-radius:50%;border:3px solid rgba(255,255,255,.25);border-top-color:#fff;animation:pecb-video-spin .9s linear infinite;pointer-events:none}.pecb-video-player__error{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:16px;text-align:center;font-family:var(--pecb-video-font-body);font-size:14px;color:#ffffffd9;background:#00000073}.pecb-video-player__error-icon{width:36px;height:36px;opacity:.9}.pecb-video-player__controls{position:absolute;left:0;right:0;bottom:0;display:flex;flex-direction:column;gap:8px;padding:10px 16px 12px;background:linear-gradient(0deg,#0009,#0000);transition:opacity .22s ease,transform .22s ease}.pecb-video-player__progress{position:relative;padding:6px 0;margin:-6px 0;cursor:pointer;touch-action:none;border-radius:6px;outline:none}.pecb-video-player__progress[aria-disabled=true]{cursor:default}.pecb-video-player__progress:focus-visible .pecb-video-player__track{box-shadow:0 0 0 2px #0009,0 0 0 4px var(--pecb-video-focus-ring)}.pecb-video-player__progress:hover .pecb-video-player__knob,.pecb-video-player__progress:focus-visible .pecb-video-player__knob{transform:translate(-50%,-50%) scale(1.15)}.pecb-video-player__track{position:relative;height:4px;border-radius:4px;background:#ffffff47;overflow:visible}.pecb-video-player__buffered,.pecb-video-player__fill{position:absolute;left:0;top:0;bottom:0;border-radius:4px;pointer-events:none}.pecb-video-player__buffered{background:#ffffff38}.pecb-video-player__fill{background:var(--pecb-video-accent)}.pecb-video-player__knob{position:absolute;top:50%;width:11px;height:11px;border-radius:50%;background:var(--pecb-video-accent);transform:translate(-50%,-50%);pointer-events:none;box-shadow:0 1px 3px #00000059;transition:transform .15s ease-in-out}.pecb-video-player__bar{display:flex;align-items:center;gap:6px;color:var(--pecb-video-controls-color);min-height:28px}.pecb-video-player__btn{padding:0;border:none;background:none;font:inherit;flex:0 0 auto;display:flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:6px;color:inherit;opacity:.92;cursor:pointer;transition:opacity .15s ease-in-out,background .15s ease-in-out}.pecb-video-player__btn:focus{outline:none}.pecb-video-player__btn:hover{opacity:1;background:#ffffff1a}.pecb-video-player__btn:focus-visible{opacity:1;outline:2px solid var(--pecb-video-focus-ring);outline-offset:1px}.pecb-video-player__btn--active{opacity:1;position:relative}.pecb-video-player__btn--active:after{content:\"\";position:absolute;left:8px;right:8px;bottom:2px;height:2px;border-radius:2px;background:var(--pecb-video-accent)}.pecb-video-player__btn svg{display:block;flex:0 0 auto}.pecb-video-player__spacer{flex:1 1 auto;min-width:0}.pecb-video-player__time{font-family:var(--pecb-video-font-body);font-size:12.5px;font-weight:500;font-variant-numeric:tabular-nums;white-space:nowrap;margin-left:4px;opacity:.95}.pecb-video-player__volume{display:flex;align-items:center;flex:0 0 auto}.pecb-video-player__volume-slider{width:0;opacity:0;margin:0;height:28px;cursor:pointer;appearance:none;-webkit-appearance:none;background:transparent;transition:width .15s ease-in-out,opacity .15s ease-in-out,margin .15s ease-in-out}.pecb-video-player__volume-slider:focus-visible{outline:2px solid var(--pecb-video-focus-ring);outline-offset:1px;border-radius:6px}.pecb-video-player__volume-slider::-webkit-slider-runnable-track{height:4px;border-radius:4px;background:linear-gradient(to right,var(--pecb-video-accent) 0 var(--pecb-video-volume-pct, 100%),rgba(255,255,255,.28) var(--pecb-video-volume-pct, 100%) 100%)}.pecb-video-player__volume-slider::-moz-range-track{height:4px;border-radius:4px;background:#ffffff47}.pecb-video-player__volume-slider::-moz-range-progress{height:4px;border-radius:4px;background:var(--pecb-video-accent)}.pecb-video-player__volume-slider::-webkit-slider-thumb{-webkit-appearance:none;width:11px;height:11px;margin-top:-3.5px;border-radius:50%;border:none;background:var(--pecb-video-accent)}.pecb-video-player__volume-slider::-moz-range-thumb{width:11px;height:11px;border-radius:50%;border:none;background:var(--pecb-video-accent)}.pecb-video-player__volume:hover .pecb-video-player__volume-slider,.pecb-video-player__volume:focus-within .pecb-video-player__volume-slider{width:72px;opacity:1;margin-left:4px;margin-right:6px}.pecb-video-player__settings{position:relative;flex:0 0 auto}.pecb-video-player__menu{position:absolute;right:0;bottom:calc(100% + 10px);min-width:168px;padding:6px;border-radius:8px;background:#18181bf5;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -2px #0000000d;color:#fff;z-index:2;font-family:var(--pecb-video-font-body);animation:pecb-video-menu-in .14s ease-out}.pecb-video-player__menu-title{padding:6px 10px 8px;font-size:12px;font-weight:500;color:#fff9;letter-spacing:.02em}.pecb-video-player__menu-item{border:none;background:none;font:inherit;display:flex;align-items:center;gap:8px;width:100%;padding:7px 10px;border-radius:6px;font-size:13px;font-weight:500;color:inherit;text-align:left;cursor:pointer}.pecb-video-player__menu-item:focus{outline:none}.pecb-video-player__menu-item:hover,.pecb-video-player__menu-item:focus-visible{background:#ffffff1a;outline:none}.pecb-video-player__menu-item[aria-checked=true]{font-weight:700}.pecb-video-player__menu-check{width:14px;height:14px;display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto}.pecb-video-player--idle,.pecb-video-player--idle .pecb-video-player__media{cursor:none}.pecb-video-player--idle .pecb-video-player__controls,.pecb-video-player--idle .pecb-video-player__center{opacity:0;pointer-events:none}.pecb-video-player--idle .pecb-video-player__controls{transform:translateY(6px)}.pecb-video-player--fullscreen{width:100%;height:100%;aspect-ratio:auto;border-radius:0;max-height:none}.pecb-video-player--fullscreen .pecb-video-player__media{object-fit:contain}.pecb-video-player--error .pecb-video-player__media{cursor:default}@container pecb-video-player (max-width: 640px){.pecb-video-player__watermark,.pecb-video-player__label{bottom:56px}.pecb-video-player__watermark{left:16px;font-size:17px}.pecb-video-player__label{right:16px;font-size:20px}.pecb-video-player__center{width:54px;height:54px}.pecb-video-player__center svg{width:22px;height:22px}.pecb-video-player__controls{padding:8px 12px 10px;gap:6px}.pecb-video-player__bar{gap:4px}.pecb-video-player__time{font-size:11.5px}.pecb-video-player__volume:hover .pecb-video-player__volume-slider,.pecb-video-player__volume:focus-within .pecb-video-player__volume-slider{width:56px}}@container pecb-video-player (max-width: 400px){.pecb-video-player__watermark,.pecb-video-player__label{bottom:50px}.pecb-video-player__watermark{font-size:14px}.pecb-video-player__label{font-size:16px}.pecb-video-player__center{width:46px;height:46px}.pecb-video-player__center svg{width:19px;height:19px}.pecb-video-player__bar{gap:2px}.pecb-video-player__time{font-size:11px;margin-left:2px}.pecb-video-player__time-sep,.pecb-video-player__time-total{display:none}.pecb-video-player__menu{min-width:148px}}@media (hover: none) and (pointer: coarse){.pecb-video-player__volume-slider{display:none}}@media (prefers-reduced-motion: reduce){.pecb-video-player,.pecb-video-player *,.pecb-video-player *:before,.pecb-video-player *:after{transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}.pecb-video-player__spinner{animation:none;border-top-color:#ffffff40}}@keyframes pecb-video-spin{to{transform:rotate(360deg)}}@keyframes pecb-video-menu-in{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
11537
+ }
11538
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: VideoPlayerComponent, decorators: [{
11539
+ type: Component,
11540
+ args: [{ selector: 'pecb-video-player', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, host: {
11541
+ class: 'pecb-video-player-host',
11542
+ // `title` is a component input — keep it off the host so the browser doesn't
11543
+ // show a native tooltip over the whole stage.
11544
+ '[attr.title]': 'null',
11545
+ '[style.--pecb-video-aspect-ratio]': 'aspectRatio()',
11546
+ '(document:fullscreenchange)': 'onDocumentFullscreenChange()',
11547
+ '(document:pointerdown)': 'onDocumentPointerDown($event)',
11548
+ }, template: "<div\n #root\n [class]=\"hostClasses()\"\n role=\"region\"\n [attr.aria-label]=\"regionLabel()\"\n aria-roledescription=\"video player\"\n tabindex=\"0\"\n (keydown)=\"onRootKeydown($event)\"\n (focusout)=\"onRootFocusOut($event)\"\n>\n <!-- \u2500\u2500 Media \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <video\n #video\n class=\"pecb-video-player__media\"\n [attr.src]=\"singleSrc() ?? null\"\n [attr.poster]=\"poster() ?? null\"\n [attr.preload]=\"preload()\"\n [attr.crossorigin]=\"crossOrigin() ?? null\"\n [attr.playsinline]=\"playsInline() ? '' : null\"\n [attr.webkit-playsinline]=\"playsInline() ? '' : null\"\n [attr.autoplay]=\"autoplay() ? '' : null\"\n [attr.loop]=\"loop() ? '' : null\"\n [attr.aria-label]=\"title() ?? null\"\n (loadedmetadata)=\"onLoadedMetadata(video)\"\n (durationchange)=\"onDurationChange(video)\"\n (timeupdate)=\"onTimeUpdate(video)\"\n (progress)=\"onProgress(video)\"\n (play)=\"onPlay()\"\n (pause)=\"onPause()\"\n (ended)=\"onEnded()\"\n (waiting)=\"onWaiting()\"\n (playing)=\"onPlaying()\"\n (seeked)=\"onSeeked(video)\"\n (volumechange)=\"onVolumeChange(video)\"\n (ratechange)=\"onRateChange(video)\"\n (error)=\"onMediaError(video)\"\n (pointerdown)=\"onStagePointerDown($event)\"\n (click)=\"onStageClick()\"\n (dblclick)=\"onStageDoubleClick()\"\n >\n @if (!singleSrc()) {\n @for (s of sourceList(); track s.src) {\n <source [attr.src]=\"s.src\" [attr.type]=\"s.type ?? null\" [attr.media]=\"s.media ?? null\" />\n }\n }\n @for (t of tracks(); track t.src) {\n <track\n [attr.src]=\"t.src\"\n [attr.kind]=\"t.kind ?? 'captions'\"\n [attr.srclang]=\"t.srclang\"\n [attr.label]=\"t.label ?? null\"\n [attr.default]=\"t.default ? '' : null\"\n />\n }\n </video>\n\n <!-- \u2500\u2500 Gradient scrim + branding \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"pecb-video-player__scrim\" aria-hidden=\"true\"></div>\n\n @if (watermark()) {\n <div class=\"pecb-video-player__watermark\" aria-hidden=\"true\">{{ watermark() }}</div>\n }\n @if (label()) {\n <div class=\"pecb-video-player__label\" aria-hidden=\"true\">{{ label() }}</div>\n }\n\n <!-- Custom overlays (quizzes, end screens\u2026) projected by the consumer -->\n <ng-content select=\"[pecbVideoOverlay]\"></ng-content>\n\n <!-- \u2500\u2500 Centre state: error / buffering / play-pause \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (hasError()) {\n <div class=\"pecb-video-player__error\" role=\"alert\">\n <svg class=\"pecb-video-player__error-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <path d=\"M12 8v4.5\" />\n <circle cx=\"12\" cy=\"16\" r=\".6\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n <span>{{ text().error }}</span>\n </div>\n } @else if (isBuffering() && playing()) {\n <div class=\"pecb-video-player__spinner\" aria-hidden=\"true\"></div>\n } @else if (showCenterButton()) {\n <button\n type=\"button\"\n class=\"pecb-video-player__center\"\n [attr.aria-label]=\"hasEnded() ? text().replay : playing() ? text().pause : text().play\"\n (click)=\"togglePlay()\"\n >\n @if (hasEnded()) {\n <svg width=\"26\" height=\"26\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M3 12a9 9 0 1 0 3-6.7\" />\n <path d=\"M3 4v5h5\" />\n </svg>\n } @else if (playing()) {\n <svg width=\"26\" height=\"26\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <rect x=\"6\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n <rect x=\"14\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n </svg>\n } @else {\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <polygon points=\"6 4 20 12 6 20 6 4\" />\n </svg>\n }\n </button>\n }\n\n <!-- \u2500\u2500 Control bar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"pecb-video-player__controls\" [attr.aria-hidden]=\"controlsHidden() ? 'true' : null\">\n <!-- Progress / seek bar -->\n <div\n #progress\n class=\"pecb-video-player__progress\"\n role=\"slider\"\n [attr.tabindex]=\"controlsHidden() ? -1 : 0\"\n [attr.aria-label]=\"text().seek\"\n aria-valuemin=\"0\"\n [attr.aria-valuemax]=\"duration()\"\n [attr.aria-valuenow]=\"currentTime()\"\n [attr.aria-valuetext]=\"currentTimeLabel() + ' / ' + durationLabel()\"\n [attr.aria-disabled]=\"duration() > 0 ? null : 'true'\"\n (pointerdown)=\"onProgressPointerDown($event)\"\n (pointermove)=\"onProgressPointerMove($event)\"\n (pointerup)=\"onProgressPointerUp($event)\"\n (pointercancel)=\"onProgressPointerUp($event)\"\n (keydown)=\"onProgressKeydown($event)\"\n >\n <div class=\"pecb-video-player__track\">\n <span class=\"pecb-video-player__buffered\" [style.width.%]=\"bufferedPct()\"></span>\n <span class=\"pecb-video-player__fill\" [style.width.%]=\"progressPct()\"></span>\n <span class=\"pecb-video-player__knob\" [style.left.%]=\"progressPct()\"></span>\n </div>\n </div>\n\n <!-- Buttons row -->\n <div class=\"pecb-video-player__bar\">\n <button\n type=\"button\"\n class=\"pecb-video-player__btn\"\n [attr.aria-label]=\"hasEnded() ? text().replay : playing() ? text().pause : text().play\"\n [attr.tabindex]=\"controlsHidden() ? -1 : 0\"\n (click)=\"togglePlay()\"\n >\n @if (playing()) {\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <rect x=\"6\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n <rect x=\"14\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n </svg>\n } @else {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <polygon points=\"6 4 20 12 6 20 6 4\" />\n </svg>\n }\n </button>\n\n @if (showSkipBack()) {\n <button\n type=\"button\"\n class=\"pecb-video-player__btn\"\n [attr.aria-label]=\"skipBackLabel()\"\n [attr.tabindex]=\"controlsHidden() ? -1 : 0\"\n (click)=\"skipBack()\"\n >\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <polygon points=\"19 5 9 12 19 19 19 5\" fill=\"currentColor\" stroke=\"none\" />\n <line x1=\"6\" y1=\"5\" x2=\"6\" y2=\"19\" />\n </svg>\n </button>\n }\n\n @if (showVolume()) {\n <div class=\"pecb-video-player__volume\">\n <button\n type=\"button\"\n class=\"pecb-video-player__btn\"\n [attr.aria-label]=\"muted() ? text().unmute : text().mute\"\n [attr.aria-pressed]=\"muted()\"\n [attr.tabindex]=\"controlsHidden() ? -1 : 0\"\n (click)=\"toggleMute()\"\n >\n @switch (volumeLevel()) {\n @case ('muted') {\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <polygon points=\"4 9 8 9 12 5 12 19 8 15 4 15 4 9\" fill=\"currentColor\" stroke=\"none\" />\n <path d=\"m16 9.5 5 5M21 9.5l-5 5\" />\n </svg>\n }\n @case ('low') {\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <polygon points=\"4 9 8 9 12 5 12 19 8 15 4 15 4 9\" fill=\"currentColor\" stroke=\"none\" />\n <path d=\"M16 9a4 4 0 0 1 0 6\" />\n </svg>\n }\n @default {\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <polygon points=\"4 9 8 9 12 5 12 19 8 15 4 15 4 9\" fill=\"currentColor\" stroke=\"none\" />\n <path d=\"M16 9a4 4 0 0 1 0 6\" />\n <path d=\"M18.5 6.5a8 8 0 0 1 0 11\" />\n </svg>\n }\n }\n </button>\n <input\n class=\"pecb-video-player__volume-slider\"\n type=\"range\"\n min=\"0\"\n max=\"100\"\n step=\"1\"\n [value]=\"volumePct()\"\n [attr.aria-label]=\"text().volume\"\n [attr.aria-valuetext]=\"volumePct() + '%'\"\n [attr.tabindex]=\"controlsHidden() ? -1 : 0\"\n [style.--pecb-video-volume-pct]=\"volumePct() + '%'\"\n (input)=\"onVolumeInput($event)\"\n />\n </div>\n }\n\n @if (showTime()) {\n <span class=\"pecb-video-player__time\" aria-live=\"off\">\n <span class=\"pecb-video-player__time-current\">{{ currentTimeLabel() }}</span>\n <span class=\"pecb-video-player__time-sep\" aria-hidden=\"true\"> / </span>\n <span class=\"pecb-video-player__time-total\">{{ durationLabel() }}</span>\n </span>\n }\n\n <div class=\"pecb-video-player__spacer\"></div>\n\n @if (captionsButtonVisible()) {\n <button\n type=\"button\"\n class=\"pecb-video-player__btn\"\n [class.pecb-video-player__btn--active]=\"captionsEnabled()\"\n [attr.aria-label]=\"captionsEnabled() ? text().captionsOff : text().captionsOn\"\n [attr.aria-pressed]=\"captionsEnabled()\"\n [attr.tabindex]=\"controlsHidden() ? -1 : 0\"\n (click)=\"toggleCaptions()\"\n >\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"2\" />\n <path d=\"M10 10a2 2 0 1 0 0 4M17 10a2 2 0 1 0 0 4\" />\n </svg>\n </button>\n }\n\n @if (showSettings()) {\n <div class=\"pecb-video-player__settings\">\n <button\n type=\"button\"\n class=\"pecb-video-player__btn\"\n [id]=\"uid + '-settings-btn'\"\n [class.pecb-video-player__btn--active]=\"settingsOpen()\"\n [attr.aria-label]=\"text().settings\"\n aria-haspopup=\"menu\"\n [attr.aria-expanded]=\"settingsOpen()\"\n [attr.aria-controls]=\"uid + '-settings-menu'\"\n [attr.tabindex]=\"controlsHidden() ? -1 : 0\"\n (click)=\"toggleSettings()\"\n >\n <svg width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"3\" />\n <path d=\"M19.4 13.5a1.6 1.6 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.6 1.6 0 0 0-2.7 1.1V21a2 2 0 0 1-4 0v-.2A1.6 1.6 0 0 0 6.6 19l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1A1.6 1.6 0 0 0 3 13.5H3a2 2 0 0 1 0-4h.1a1.6 1.6 0 0 0 1.5-1.1 1.6 1.6 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.6 1.6 0 0 0 1.8.3H9.6A1.6 1.6 0 0 0 10.6 3V3a2 2 0 0 1 4 0v.1a1.6 1.6 0 0 0 2.7 1.1l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.6 1.6 0 0 0-.3 1.8v.1a1.6 1.6 0 0 0 1.5 1H21a2 2 0 0 1 0 4h-.1a1.6 1.6 0 0 0-1.5 1Z\" />\n </svg>\n </button>\n\n @if (settingsOpen()) {\n <div\n #settingsMenu\n class=\"pecb-video-player__menu\"\n role=\"menu\"\n [id]=\"uid + '-settings-menu'\"\n [attr.aria-label]=\"text().playbackSpeed\"\n (keydown)=\"onSettingsMenuKeydown($event)\"\n >\n <div class=\"pecb-video-player__menu-title\">{{ text().playbackSpeed }}</div>\n @for (rate of playbackRates(); track rate) {\n <button\n type=\"button\"\n class=\"pecb-video-player__menu-item\"\n role=\"menuitemradio\"\n [attr.aria-checked]=\"rate === playbackRate()\"\n tabindex=\"-1\"\n (click)=\"setPlaybackRate(rate)\"\n >\n <span class=\"pecb-video-player__menu-check\" aria-hidden=\"true\">\n @if (rate === playbackRate()) {\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n }\n </span>\n <span>{{ rate === 1 ? text().normalSpeed : rate + '\u00D7' }}</span>\n </button>\n }\n </div>\n }\n </div>\n }\n\n @if (showFullscreen()) {\n <button\n type=\"button\"\n class=\"pecb-video-player__btn\"\n [attr.aria-label]=\"isFullscreen() ? text().exitFullscreen : text().enterFullscreen\"\n [attr.aria-pressed]=\"isFullscreen()\"\n [attr.tabindex]=\"controlsHidden() ? -1 : 0\"\n (click)=\"toggleFullscreen()\"\n >\n @if (isFullscreen()) {\n <svg width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3M3 16h3a2 2 0 0 1 2 2v3m13-5h-3a2 2 0 0 0-2 2v3\" />\n </svg>\n } @else {\n <svg width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3M3 16v3a2 2 0 0 0 2 2h3m13-5v3a2 2 0 0 1-2 2h-3\" />\n </svg>\n }\n </button>\n }\n </div>\n </div>\n</div>\n", styles: [":host{display:block;width:100%;max-width:100%;container-type:inline-size;container-name:pecb-video-player;--pecb-video-radius: 12px;--pecb-video-bg: #0b0b0d;--pecb-video-accent: #ffffff;--pecb-video-controls-color: rgba(255, 255, 255, .92);--pecb-video-font-display: Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif;--pecb-video-font-body: \"Inter\", Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif;--pecb-video-focus-ring: #ffffff}.pecb-video-player{position:relative;width:100%;aspect-ratio:var(--pecb-video-aspect-ratio, 16/9);max-height:100%;background:var(--pecb-video-bg);border-radius:var(--pecb-video-radius);overflow:hidden;color:var(--pecb-video-controls-color);font-family:var(--pecb-video-font-display);outline:none;-webkit-tap-highlight-color:transparent;-webkit-user-select:none;user-select:none}.pecb-video-player:focus-visible{box-shadow:0 0 0 2px var(--pecb-video-bg),0 0 0 4px var(--pecb-video-focus-ring)}.pecb-video-player__media{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;background:var(--pecb-video-bg);display:block;cursor:pointer}.pecb-video-player__media::cue{font-family:var(--pecb-video-font-body);font-size:.95em;background:#0009}.pecb-video-player__scrim{position:absolute;inset:0;pointer-events:none;background:linear-gradient(180deg,#0000001f,#0000 30% 60%,#0000008c)}.pecb-video-player__watermark,.pecb-video-player__label{position:absolute;bottom:64px;pointer-events:none;font-family:var(--pecb-video-font-display);font-weight:800;color:#ffffffeb;white-space:nowrap;max-width:45%;overflow:hidden;text-overflow:ellipsis;text-shadow:0 1px 2px rgba(0,0,0,.25);transition:opacity .22s ease}.pecb-video-player__watermark{left:24px;font-size:22px;letter-spacing:.04em}.pecb-video-player__label{right:24px;font-size:26px;letter-spacing:.05em;text-transform:uppercase}.pecb-video-player__center{padding:0;border:none;background:none;font:inherit;color:inherit;position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:64px;height:64px;border-radius:50%;background:#1414168c;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);color:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:background .15s ease-in-out,transform .15s ease-in-out,opacity .22s ease}.pecb-video-player__center:focus{outline:none}.pecb-video-player__center:hover{background:#141416b8}.pecb-video-player__center:active{transform:translate(-50%,-50%) scale(.96)}.pecb-video-player__center:focus-visible{outline:2px solid var(--pecb-video-focus-ring);outline-offset:2px}.pecb-video-player__center svg polygon{transform:translate(1px)}.pecb-video-player__spinner{position:absolute;left:50%;top:50%;width:48px;height:48px;margin:-24px 0 0 -24px;border-radius:50%;border:3px solid rgba(255,255,255,.25);border-top-color:#fff;animation:pecb-video-spin .9s linear infinite;pointer-events:none}.pecb-video-player__error{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:16px;text-align:center;font-family:var(--pecb-video-font-body);font-size:14px;color:#ffffffd9;background:#00000073}.pecb-video-player__error-icon{width:36px;height:36px;opacity:.9}.pecb-video-player__controls{position:absolute;left:0;right:0;bottom:0;display:flex;flex-direction:column;gap:8px;padding:10px 16px 12px;background:linear-gradient(0deg,#0009,#0000);transition:opacity .22s ease,transform .22s ease}.pecb-video-player__progress{position:relative;padding:6px 0;margin:-6px 0;cursor:pointer;touch-action:none;border-radius:6px;outline:none}.pecb-video-player__progress[aria-disabled=true]{cursor:default}.pecb-video-player__progress:focus-visible .pecb-video-player__track{box-shadow:0 0 0 2px #0009,0 0 0 4px var(--pecb-video-focus-ring)}.pecb-video-player__progress:hover .pecb-video-player__knob,.pecb-video-player__progress:focus-visible .pecb-video-player__knob{transform:translate(-50%,-50%) scale(1.15)}.pecb-video-player__track{position:relative;height:4px;border-radius:4px;background:#ffffff47;overflow:visible}.pecb-video-player__buffered,.pecb-video-player__fill{position:absolute;left:0;top:0;bottom:0;border-radius:4px;pointer-events:none}.pecb-video-player__buffered{background:#ffffff38}.pecb-video-player__fill{background:var(--pecb-video-accent)}.pecb-video-player__knob{position:absolute;top:50%;width:11px;height:11px;border-radius:50%;background:var(--pecb-video-accent);transform:translate(-50%,-50%);pointer-events:none;box-shadow:0 1px 3px #00000059;transition:transform .15s ease-in-out}.pecb-video-player__bar{display:flex;align-items:center;gap:6px;color:var(--pecb-video-controls-color);min-height:28px}.pecb-video-player__btn{padding:0;border:none;background:none;font:inherit;flex:0 0 auto;display:flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:6px;color:inherit;opacity:.92;cursor:pointer;transition:opacity .15s ease-in-out,background .15s ease-in-out}.pecb-video-player__btn:focus{outline:none}.pecb-video-player__btn:hover{opacity:1;background:#ffffff1a}.pecb-video-player__btn:focus-visible{opacity:1;outline:2px solid var(--pecb-video-focus-ring);outline-offset:1px}.pecb-video-player__btn--active{opacity:1;position:relative}.pecb-video-player__btn--active:after{content:\"\";position:absolute;left:8px;right:8px;bottom:2px;height:2px;border-radius:2px;background:var(--pecb-video-accent)}.pecb-video-player__btn svg{display:block;flex:0 0 auto}.pecb-video-player__spacer{flex:1 1 auto;min-width:0}.pecb-video-player__time{font-family:var(--pecb-video-font-body);font-size:12.5px;font-weight:500;font-variant-numeric:tabular-nums;white-space:nowrap;margin-left:4px;opacity:.95}.pecb-video-player__volume{display:flex;align-items:center;flex:0 0 auto}.pecb-video-player__volume-slider{width:0;opacity:0;margin:0;height:28px;cursor:pointer;appearance:none;-webkit-appearance:none;background:transparent;transition:width .15s ease-in-out,opacity .15s ease-in-out,margin .15s ease-in-out}.pecb-video-player__volume-slider:focus-visible{outline:2px solid var(--pecb-video-focus-ring);outline-offset:1px;border-radius:6px}.pecb-video-player__volume-slider::-webkit-slider-runnable-track{height:4px;border-radius:4px;background:linear-gradient(to right,var(--pecb-video-accent) 0 var(--pecb-video-volume-pct, 100%),rgba(255,255,255,.28) var(--pecb-video-volume-pct, 100%) 100%)}.pecb-video-player__volume-slider::-moz-range-track{height:4px;border-radius:4px;background:#ffffff47}.pecb-video-player__volume-slider::-moz-range-progress{height:4px;border-radius:4px;background:var(--pecb-video-accent)}.pecb-video-player__volume-slider::-webkit-slider-thumb{-webkit-appearance:none;width:11px;height:11px;margin-top:-3.5px;border-radius:50%;border:none;background:var(--pecb-video-accent)}.pecb-video-player__volume-slider::-moz-range-thumb{width:11px;height:11px;border-radius:50%;border:none;background:var(--pecb-video-accent)}.pecb-video-player__volume:hover .pecb-video-player__volume-slider,.pecb-video-player__volume:focus-within .pecb-video-player__volume-slider{width:72px;opacity:1;margin-left:4px;margin-right:6px}.pecb-video-player__settings{position:relative;flex:0 0 auto}.pecb-video-player__menu{position:absolute;right:0;bottom:calc(100% + 10px);min-width:168px;padding:6px;border-radius:8px;background:#18181bf5;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -2px #0000000d;color:#fff;z-index:2;font-family:var(--pecb-video-font-body);animation:pecb-video-menu-in .14s ease-out}.pecb-video-player__menu-title{padding:6px 10px 8px;font-size:12px;font-weight:500;color:#fff9;letter-spacing:.02em}.pecb-video-player__menu-item{border:none;background:none;font:inherit;display:flex;align-items:center;gap:8px;width:100%;padding:7px 10px;border-radius:6px;font-size:13px;font-weight:500;color:inherit;text-align:left;cursor:pointer}.pecb-video-player__menu-item:focus{outline:none}.pecb-video-player__menu-item:hover,.pecb-video-player__menu-item:focus-visible{background:#ffffff1a;outline:none}.pecb-video-player__menu-item[aria-checked=true]{font-weight:700}.pecb-video-player__menu-check{width:14px;height:14px;display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto}.pecb-video-player--idle,.pecb-video-player--idle .pecb-video-player__media{cursor:none}.pecb-video-player--idle .pecb-video-player__controls,.pecb-video-player--idle .pecb-video-player__center{opacity:0;pointer-events:none}.pecb-video-player--idle .pecb-video-player__controls{transform:translateY(6px)}.pecb-video-player--fullscreen{width:100%;height:100%;aspect-ratio:auto;border-radius:0;max-height:none}.pecb-video-player--fullscreen .pecb-video-player__media{object-fit:contain}.pecb-video-player--error .pecb-video-player__media{cursor:default}@container pecb-video-player (max-width: 640px){.pecb-video-player__watermark,.pecb-video-player__label{bottom:56px}.pecb-video-player__watermark{left:16px;font-size:17px}.pecb-video-player__label{right:16px;font-size:20px}.pecb-video-player__center{width:54px;height:54px}.pecb-video-player__center svg{width:22px;height:22px}.pecb-video-player__controls{padding:8px 12px 10px;gap:6px}.pecb-video-player__bar{gap:4px}.pecb-video-player__time{font-size:11.5px}.pecb-video-player__volume:hover .pecb-video-player__volume-slider,.pecb-video-player__volume:focus-within .pecb-video-player__volume-slider{width:56px}}@container pecb-video-player (max-width: 400px){.pecb-video-player__watermark,.pecb-video-player__label{bottom:50px}.pecb-video-player__watermark{font-size:14px}.pecb-video-player__label{font-size:16px}.pecb-video-player__center{width:46px;height:46px}.pecb-video-player__center svg{width:19px;height:19px}.pecb-video-player__bar{gap:2px}.pecb-video-player__time{font-size:11px;margin-left:2px}.pecb-video-player__time-sep,.pecb-video-player__time-total{display:none}.pecb-video-player__menu{min-width:148px}}@media (hover: none) and (pointer: coarse){.pecb-video-player__volume-slider{display:none}}@media (prefers-reduced-motion: reduce){.pecb-video-player,.pecb-video-player *,.pecb-video-player *:before,.pecb-video-player *:after{transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}.pecb-video-player__spinner{animation:none;border-top-color:#ffffff40}}@keyframes pecb-video-spin{to{transform:rotate(360deg)}}@keyframes pecb-video-menu-in{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}\n"] }]
11549
+ }], ctorParameters: () => [], propDecorators: { rootRef: [{ type: i0.ViewChild, args: ['root', { isSignal: true }] }], videoRef: [{ type: i0.ViewChild, args: ['video', { isSignal: true }] }], progressRef: [{ type: i0.ViewChild, args: ['progress', { isSignal: true }] }], settingsMenuRef: [{ type: i0.ViewChild, args: ['settingsMenu', { isSignal: true }] }], src: [{ type: i0.Input, args: [{ isSignal: true, alias: "src", required: false }] }], poster: [{ type: i0.Input, args: [{ isSignal: true, alias: "poster", required: false }] }], tracks: [{ type: i0.Input, args: [{ isSignal: true, alias: "tracks", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], crossOrigin: [{ type: i0.Input, args: [{ isSignal: true, alias: "crossOrigin", required: false }] }], preload: [{ type: i0.Input, args: [{ isSignal: true, alias: "preload", required: false }] }], autoplay: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoplay", required: false }] }], loop: [{ type: i0.Input, args: [{ isSignal: true, alias: "loop", required: false }] }], playsInline: [{ type: i0.Input, args: [{ isSignal: true, alias: "playsInline", required: false }] }], startTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "startTime", required: false }] }], watermark: [{ type: i0.Input, args: [{ isSignal: true, alias: "watermark", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], aspectRatio: [{ type: i0.Input, args: [{ isSignal: true, alias: "aspectRatio", required: false }] }], showCenterButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCenterButton", required: false }] }], showSkipBack: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSkipBack", required: false }] }], showVolume: [{ type: i0.Input, args: [{ isSignal: true, alias: "showVolume", required: false }] }], showTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTime", required: false }] }], showCaptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCaptions", required: false }] }], showSettings: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSettings", required: false }] }], showFullscreen: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFullscreen", required: false }] }], seekStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "seekStep", required: false }] }], playbackRates: [{ type: i0.Input, args: [{ isSignal: true, alias: "playbackRates", required: false }] }], labels: [{ type: i0.Input, args: [{ isSignal: true, alias: "labels", required: false }] }], playing: [{ type: i0.Input, args: [{ isSignal: true, alias: "playing", required: false }] }, { type: i0.Output, args: ["playingChange"] }], currentTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentTime", required: false }] }, { type: i0.Output, args: ["currentTimeChange"] }], volume: [{ type: i0.Input, args: [{ isSignal: true, alias: "volume", required: false }] }, { type: i0.Output, args: ["volumeChange"] }], muted: [{ type: i0.Input, args: [{ isSignal: true, alias: "muted", required: false }] }, { type: i0.Output, args: ["mutedChange"] }], playbackRate: [{ type: i0.Input, args: [{ isSignal: true, alias: "playbackRate", required: false }] }, { type: i0.Output, args: ["playbackRateChange"] }], captionsEnabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "captionsEnabled", required: false }] }, { type: i0.Output, args: ["captionsEnabledChange"] }], timeUpdate: [{ type: i0.Output, args: ["timeUpdate"] }], loadedMetadata: [{ type: i0.Output, args: ["loadedMetadata"] }], seekEnd: [{ type: i0.Output, args: ["seekEnd"] }], playbackEnded: [{ type: i0.Output, args: ["playbackEnded"] }], fullscreenChange: [{ type: i0.Output, args: ["fullscreenChange"] }], playerError: [{ type: i0.Output, args: ["playerError"] }] } });
11550
+
11551
+ /*
11552
+ * Shared data model for the PECB course components
11553
+ * (`pecb-course-content-panel` + `pecb-course-player`).
11554
+ *
11555
+ * Source of truth: "Course Player — Content Redesign" (data.jsx / panel.jsx).
11556
+ */
11557
+ // ─── Formatting helpers ───────────────────────────────────────────────────────
11558
+ /**
11559
+ * Human-readable lesson length: `"9 min"`, `"12 min 4 sec"`, `"1 hr 3 min"`.
11560
+ * Non-finite / negative input renders as `"0 min"`.
11561
+ */
11562
+ function formatCourseDuration(seconds) {
11563
+ if (!Number.isFinite(seconds) || seconds < 0)
11564
+ return '0 min';
11565
+ const total = Math.floor(seconds);
11566
+ const minutes = Math.floor(total / 60);
11567
+ const rest = total % 60;
11568
+ if (minutes >= 60) {
11569
+ const hours = Math.floor(minutes / 60);
11570
+ return `${hours} hr ${minutes % 60} min`;
11571
+ }
11572
+ return `${minutes} min${rest ? ` ${rest} sec` : ''}`;
11573
+ }
11574
+ /**
11575
+ * Clock reading: `m:ss`, or `h:mm:ss` from one hour up.
11576
+ * Non-finite / negative input renders as `0:00`.
11577
+ */
11578
+ function formatCourseTime(seconds) {
11579
+ if (!Number.isFinite(seconds) || seconds < 0)
11580
+ return '0:00';
11581
+ const total = Math.floor(seconds);
11582
+ const h = Math.floor(total / 3600);
11583
+ const m = Math.floor((total % 3600) / 60);
11584
+ const s = total % 60;
11585
+ const mm = h > 0 ? String(m).padStart(2, '0') : String(m);
11586
+ return h > 0 ? `${h}:${mm}:${String(s).padStart(2, '0')}` : `${mm}:${String(s).padStart(2, '0')}`;
11587
+ }
11588
+ // ─── Derivation helpers ───────────────────────────────────────────────────────
11589
+ const PART_MARKER = ' — Part ';
11590
+ /**
11591
+ * Splits `"Base — Part 3"` into `{ base: 'Base', part: 'Part 3' }`.
11592
+ * A title without the marker yields `{ base: title, part: null }`.
11593
+ */
11594
+ function parseLessonPartTitle(title) {
11595
+ const index = title.indexOf(PART_MARKER);
11596
+ if (index === -1)
11597
+ return { base: title, part: null };
11598
+ return { base: title.slice(0, index), part: `Part ${title.slice(index + PART_MARKER.length).trim()}` };
11599
+ }
11600
+ /**
11601
+ * Resolves the status actually rendered for a lesson.
11602
+ *
11603
+ * The active video lesson follows live playback (`playing` while the player is
11604
+ * running, otherwise `started` — unless it is already `completed`), and a
11605
+ * `playing` status on any *other* lesson degrades to `started`, so exactly one
11606
+ * row can ever show the "now playing" treatment. `locked` always wins.
11607
+ */
11608
+ function resolveLessonStatus(lesson, active, livePlaying) {
11609
+ const raw = lesson.status ?? 'available';
11610
+ if (raw === 'locked')
11611
+ return 'locked';
11612
+ if (active && (lesson.type ?? 'video') === 'video') {
11613
+ return livePlaying ? 'playing' : raw === 'completed' ? 'completed' : 'started';
11614
+ }
11615
+ if (!active && raw === 'playing')
11616
+ return 'started';
11617
+ return raw;
11618
+ }
11619
+ /** Counters for one module, computed from already-resolved lesson statuses. */
11620
+ function courseModuleStats(module, resolve = (l) => l.status ?? 'available') {
11621
+ const total = module.lessons.length;
11622
+ let done = 0;
11623
+ let playing = false;
11624
+ let started = false;
11625
+ let durationSeconds = 0;
11626
+ for (const lesson of module.lessons) {
11627
+ const status = resolve(lesson);
11628
+ if (status === 'completed')
11629
+ done++;
11630
+ if (status === 'playing')
11631
+ playing = true;
11632
+ if (status === 'started')
11633
+ started = true;
11634
+ durationSeconds += lesson.durationSeconds ?? 0;
11635
+ }
11636
+ return {
11637
+ total,
11638
+ done,
11639
+ playing,
11640
+ started,
11641
+ durationSeconds,
11642
+ percent: total > 0 ? Math.round((done / total) * 100) : 0,
11643
+ };
11644
+ }
11645
+ /** Counters for a whole course. */
11646
+ function courseProgress(modules, resolve = (l) => l.status ?? 'available') {
11647
+ const lessons = modules.flatMap((m) => m.lessons);
11648
+ const total = lessons.length;
11649
+ let done = 0;
11650
+ let remainingSeconds = 0;
11651
+ for (const lesson of lessons) {
11652
+ const status = resolve(lesson);
11653
+ if (status === 'completed') {
11654
+ done++;
11655
+ }
11656
+ else if ((lesson.type ?? 'video') === 'video') {
11657
+ remainingSeconds += lesson.durationSeconds ?? 0;
11658
+ }
11659
+ }
11660
+ return {
11661
+ total,
11662
+ done,
11663
+ percent: total > 0 ? Math.round((done / total) * 100) : 0,
11664
+ remainingSeconds,
11665
+ };
11666
+ }
11667
+ /** Finds the module a lesson belongs to. */
11668
+ function findCourseModule(modules, lessonId) {
11669
+ if (!lessonId)
11670
+ return undefined;
11671
+ return modules.find((m) => m.lessons.some((l) => l.id === lessonId));
11672
+ }
11673
+ /** Finds a lesson by id across all modules. */
11674
+ function findCourseLesson(modules, lessonId) {
11675
+ if (!lessonId)
11676
+ return undefined;
11677
+ for (const module of modules) {
11678
+ const lesson = module.lessons.find((l) => l.id === lessonId);
11679
+ if (lesson)
11680
+ return lesson;
11681
+ }
11682
+ return undefined;
11683
+ }
11684
+
11685
+ const DEFAULT_COURSE_CONTENT_PANEL_LABELS = {
11686
+ contentTab: 'Content',
11687
+ transcriptTab: 'Transcript',
11688
+ searchLessons: 'Search lessons and quizzes',
11689
+ searchTranscript: 'Search transcript',
11690
+ clearSearch: 'Clear search',
11691
+ quiz: 'Quiz',
11692
+ questions: '{n} questions',
11693
+ passed: 'Passed · {done}/{total}',
11694
+ answered: '{done} of {total} answered',
11695
+ resume: 'Resume',
11696
+ moduleDone: '{done}/{total} done',
11697
+ section: 'Section {n}',
11698
+ noLessons: 'No lessons match “{query}”.',
11699
+ noTranscript: 'No transcript lines match “{query}”.',
11700
+ autoscroll: 'Autoscroll',
11701
+ courseProgress: 'Course progress',
11702
+ courseProgressDetail: '{done} of {total} lessons · {duration} left',
11703
+ certificate: 'Certificate',
11704
+ locked: 'Locked',
11705
+ lessonList: 'Course content',
11706
+ };
11707
+ // Disc geometry per density (design: 30 / 26 px).
11708
+ const DISC_SIZE = { comfortable: 30, compact: 26 };
11709
+ const RING_STROKE = 3;
11710
+ // ─── Component ────────────────────────────────────────────────────────────────
11711
+ /**
11712
+ * PECB course **Content** panel — the sidebar that sits next to the course
11713
+ * video player.
11714
+ *
11715
+ * Reproduces the "Course Player — Content Redesign" panel exactly: the
11716
+ * Content / Transcript tabs, lesson search, the collapsible **Section**
11717
+ * accordion (one section open at a time, auto-opening the section that owns the
11718
+ * active lesson), status discs (completed / now playing / in-progress ring /
11719
+ * not started / locked), quiz rows with badge + score, per-lesson progress bars,
11720
+ * the "Part 1 / Part 2 …" timeline rail for multi-part lessons, an alternative
11721
+ * flat timeline layout, and the searchable transcript with click-to-seek,
11722
+ * live cue highlighting and an autoscroll toggle.
11723
+ *
11724
+ * It is standalone and data-driven — no player dependency — so it can be used
11725
+ * next to `pecb-video-player`, inside `pecb-course-player`, or on its own.
11726
+ *
11727
+ * @example
11728
+ * ```html
11729
+ * <pecb-course-content-panel
11730
+ * [modules]="modules"
11731
+ * [transcript]="cues"
11732
+ * [(activeLessonId)]="lessonId"
11733
+ * [playing]="playing()"
11734
+ * [currentTime]="currentTime()"
11735
+ * (lessonSelect)="openLesson($event)"
11736
+ * (transcriptSeek)="seekTo($event)">
11737
+ * </pecb-course-content-panel>
11738
+ * ```
11739
+ *
11740
+ * Theming — override the CSS custom properties on the host:
11741
+ * ```css
11742
+ * pecb-course-content-panel { --pecb-course-panel-accent: #a11e29; }
11743
+ * ```
11744
+ */
11745
+ class CourseContentPanelComponent {
11746
+ constructor() {
11747
+ this.platformId = inject(PLATFORM_ID);
11748
+ this.injector = inject(Injector);
11749
+ this.isBrowser = isPlatformBrowser(this.platformId);
11750
+ /** Unique id prefix used to wire ARIA relationships. */
11751
+ this.uid = generateUniqueId('pecb-course-panel');
11752
+ this.transcriptScrollRef = viewChild('transcriptScroll', ...(ngDevMode ? [{ debugName: "transcriptScrollRef" }] : []));
11753
+ // ── Data inputs ─────────────────────────────────────────────────────────────
11754
+ /** Course structure rendered by the Content tab. */
11755
+ this.modules = input([], ...(ngDevMode ? [{ debugName: "modules" }] : []));
11756
+ /**
11757
+ * Transcript cues for the active lesson. A `transcript` on the active
11758
+ * `CourseLesson` takes precedence over this input.
11759
+ */
11760
+ this.transcript = input([], ...(ngDevMode ? [{ debugName: "transcript" }] : []));
11761
+ // ── State (two-way) ─────────────────────────────────────────────────────────
11762
+ /** Currently selected lesson. */
11763
+ this.activeLessonId = model(null, ...(ngDevMode ? [{ debugName: "activeLessonId" }] : []));
11764
+ /** Visible tab. */
11765
+ this.tab = model('content', ...(ngDevMode ? [{ debugName: "tab" }] : []));
11766
+ /**
11767
+ * Id of the expanded section. `null` collapses all of them.
11768
+ * Left `undefined` the panel picks one itself (the active lesson's section,
11769
+ * otherwise the first one).
11770
+ */
11771
+ this.openModuleId = model(undefined, ...(ngDevMode ? [{ debugName: "openModuleId" }] : []));
11772
+ /** Whether the transcript follows playback. */
11773
+ this.autoscroll = model(true, ...(ngDevMode ? [{ debugName: "autoscroll" }] : []));
11774
+ /** Lesson search text (Content tab). */
11775
+ this.query = model('', ...(ngDevMode ? [{ debugName: "query" }] : []));
11776
+ /** Transcript search text. */
11777
+ this.transcriptQuery = model('', ...(ngDevMode ? [{ debugName: "transcriptQuery" }] : []));
11778
+ // ── Live playback state ─────────────────────────────────────────────────────
11779
+ /** Whether the player is currently playing — drives the "now playing" row. */
11780
+ this.playing = input(false, ...(ngDevMode ? [{ debugName: "playing", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
11781
+ /**
11782
+ * Live playhead position in seconds. Used to highlight the transcript cue and
11783
+ * to draw live progress on the active lesson row; falls back to each lesson's
11784
+ * `resumeSeconds` when omitted.
11785
+ */
11786
+ this.currentTime = input(undefined, ...(ngDevMode ? [{ debugName: "currentTime" }] : []));
11787
+ // ── Appearance ──────────────────────────────────────────────────────────────
11788
+ /** @default 'grouped' */
11789
+ this.layout = input('grouped', ...(ngDevMode ? [{ debugName: "layout" }] : []));
11790
+ /** @default 'comfortable' */
11791
+ this.density = input('comfortable', ...(ngDevMode ? [{ debugName: "density" }] : []));
11792
+ /** Colour of the "now playing" treatment. @default 'charcoal' */
11793
+ this.accent = input('charcoal', ...(ngDevMode ? [{ debugName: "accent" }] : []));
11794
+ /** Show the Content / Transcript tab strip. @default true */
11795
+ this.showTabs = input(true, ...(ngDevMode ? [{ debugName: "showTabs", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
11796
+ /** Show the lesson search box. @default true */
11797
+ this.showSearch = input(true, ...(ngDevMode ? [{ debugName: "showSearch", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
11798
+ /** Show the transcript search box. @default true */
11799
+ this.showTranscriptSearch = input(true, ...(ngDevMode ? [{ debugName: "showTranscriptSearch", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
11800
+ /** Show the autoscroll footer on the Transcript tab. @default true */
11801
+ this.showAutoscroll = input(true, ...(ngDevMode ? [{ debugName: "showAutoscroll", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
11802
+ /** Show the circular course-progress header above the list. @default false */
11803
+ this.showProgress = input(false, ...(ngDevMode ? [{ debugName: "showProgress", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
11804
+ /** Show the "Certificate" pill inside the progress header. @default true */
11805
+ this.showCertificate = input(true, ...(ngDevMode ? [{ debugName: "showCertificate", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
11806
+ /**
11807
+ * Render a module that holds exactly one lesson as a single direct-play row
11808
+ * instead of a collapsible section. @default false
11809
+ */
11810
+ this.soloSingleLessonModules = input(false, ...(ngDevMode ? [{ debugName: "soloSingleLessonModules", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
11811
+ /** Partial override of the user-visible strings (i18n). */
11812
+ this.labels = input({}, ...(ngDevMode ? [{ debugName: "labels" }] : []));
11813
+ // ── Outputs ─────────────────────────────────────────────────────────────────
11814
+ /** A lesson row was activated. */
11815
+ this.lessonSelect = output();
11816
+ /** A transcript cue was clicked — seek the player to this position (seconds). */
11817
+ this.transcriptSeek = output();
11818
+ /** A section was expanded or collapsed. */
11819
+ this.moduleToggle = output();
11820
+ /** The "Certificate" pill was clicked. */
11821
+ this.certificateClick = output();
11822
+ // ── Labels ──────────────────────────────────────────────────────────────────
11823
+ this.text = computed(() => ({
11824
+ ...DEFAULT_COURSE_CONTENT_PANEL_LABELS,
11825
+ ...this.labels(),
11826
+ }), ...(ngDevMode ? [{ debugName: "text" }] : []));
11827
+ // ── Theming ─────────────────────────────────────────────────────────────────
11828
+ this.hostClasses = computed(() => [
11829
+ 'pecb-course-panel',
11830
+ `pecb-course-panel--${this.density()}`,
11831
+ `pecb-course-panel--${this.layout()}`,
11832
+ `pecb-course-panel--accent-${this.accent()}`,
11833
+ ].join(' '), ...(ngDevMode ? [{ debugName: "hostClasses" }] : []));
11834
+ // ── Derived data ────────────────────────────────────────────────────────────
11835
+ this.compact = computed(() => this.density() === 'compact', ...(ngDevMode ? [{ debugName: "compact" }] : []));
11836
+ /** Resolved status of every lesson, keyed by lesson id. */
11837
+ this.statusById = computed(() => {
11838
+ const activeId = this.activeLessonId();
11839
+ const livePlaying = this.playing();
11840
+ const map = new Map();
11841
+ for (const module of this.modules()) {
11842
+ for (const lesson of module.lessons) {
11843
+ map.set(lesson.id, resolveLessonStatus(lesson, lesson.id === activeId, livePlaying));
11844
+ }
11845
+ }
11846
+ return map;
11847
+ }, ...(ngDevMode ? [{ debugName: "statusById" }] : []));
11848
+ this.resolveStatus = computed(() => {
11849
+ const map = this.statusById();
11850
+ return (lesson) => map.get(lesson.id) ?? (lesson.status ?? 'available');
11851
+ }, ...(ngDevMode ? [{ debugName: "resolveStatus" }] : []));
11852
+ /** Modules narrowed by the search box (design: match lesson titles, else module title). */
11853
+ this.filteredModules = computed(() => {
11854
+ const term = this.query().trim().toLowerCase();
11855
+ const modules = this.modules();
11856
+ if (!term)
11857
+ return modules;
11858
+ return modules
11859
+ .map((module) => ({
11860
+ ...module,
11861
+ lessons: module.lessons.filter((lesson) => lesson.title.toLowerCase().includes(term)),
11862
+ }))
11863
+ .filter((module) => module.lessons.length > 0 || module.title.toLowerCase().includes(term));
11864
+ }, ...(ngDevMode ? [{ debugName: "filteredModules" }] : []));
11865
+ this.moduleViews = computed(() => {
11866
+ const all = this.modules();
11867
+ const activeId = this.activeLessonId();
11868
+ const solo = this.soloSingleLessonModules();
11869
+ const text = this.text();
11870
+ const livePlaying = this.playing();
11871
+ return this.filteredModules().map((module) => {
11872
+ const index = all.findIndex((m) => m.id === module.id);
11873
+ const active = module.lessons.some((lesson) => lesson.id === activeId);
11874
+ const isSolo = solo && module.lessons.length === 1;
11875
+ // Section counters come from the stored statuses (design behaviour), with
11876
+ // live playback folded in so the badge reacts while the video runs.
11877
+ const raw = courseModuleStats(module);
11878
+ const stats = {
11879
+ ...raw,
11880
+ playing: raw.playing || (active && livePlaying),
11881
+ };
11882
+ return {
11883
+ module,
11884
+ id: module.id,
11885
+ number: (index === -1 ? all.length : index) + 1,
11886
+ title: module.title,
11887
+ stats,
11888
+ statsLabel: format(text.moduleDone, { done: stats.done, total: stats.total }),
11889
+ durationLabel: formatCourseDuration(stats.durationSeconds),
11890
+ allDone: stats.total > 0 && stats.done === stats.total,
11891
+ active,
11892
+ runs: isSolo ? [] : this.buildRuns(module.lessons),
11893
+ solo: isSolo ? this.buildLessonView(module.lessons[0], { titleOverride: module.title }) : null,
11894
+ };
11895
+ });
11896
+ }, ...(ngDevMode ? [{ debugName: "moduleViews" }] : []));
11897
+ this.timelineRows = computed(() => {
11898
+ const all = this.modules();
11899
+ const activeId = this.activeLessonId();
11900
+ const livePlaying = this.playing();
11901
+ const rows = [];
11902
+ for (const module of this.filteredModules()) {
11903
+ const index = all.findIndex((m) => m.id === module.id);
11904
+ const rawStats = courseModuleStats(module);
11905
+ rows.push({
11906
+ key: `h-${module.id}`,
11907
+ header: {
11908
+ number: (index === -1 ? all.length : index) + 1,
11909
+ title: module.title,
11910
+ stats: {
11911
+ ...rawStats,
11912
+ playing: rawStats.playing ||
11913
+ (livePlaying && module.lessons.some((l) => l.id === activeId)),
11914
+ },
11915
+ },
11916
+ lesson: null,
11917
+ });
11918
+ module.lessons.forEach((lesson, i) => {
11919
+ rows.push({
11920
+ key: lesson.id,
11921
+ header: null,
11922
+ lesson: this.buildLessonView(lesson, { connector: i < module.lessons.length - 1 }),
11923
+ });
11924
+ });
11925
+ }
11926
+ return rows;
11927
+ }, ...(ngDevMode ? [{ debugName: "timelineRows" }] : []));
11928
+ this.isEmpty = computed(() => this.filteredModules().length === 0, ...(ngDevMode ? [{ debugName: "isEmpty" }] : []));
11929
+ // ── Progress header ─────────────────────────────────────────────────────────
11930
+ this.progress = computed(() => courseProgress(this.modules()), ...(ngDevMode ? [{ debugName: "progress" }] : []));
11931
+ /** Geometry of the 56 px progress ring (design: r = 22, stroke = 5). */
11932
+ this.progressRing = computed(() => {
11933
+ const radius = 22;
11934
+ const circumference = 2 * Math.PI * radius;
11935
+ return {
11936
+ radius,
11937
+ circumference,
11938
+ offset: circumference * (1 - this.progress().percent / 100),
11939
+ };
11940
+ }, ...(ngDevMode ? [{ debugName: "progressRing" }] : []));
11941
+ this.progressDetail = computed(() => {
11942
+ const stats = this.progress();
11943
+ return format(this.text().courseProgressDetail, {
11944
+ done: stats.done,
11945
+ total: stats.total,
11946
+ duration: formatCourseDuration(stats.remainingSeconds),
11947
+ });
11948
+ }, ...(ngDevMode ? [{ debugName: "progressDetail" }] : []));
11949
+ // ── Transcript ──────────────────────────────────────────────────────────────
11950
+ /** Cues of the active lesson, falling back to the panel-level `transcript`. */
11951
+ this.activeCues = computed(() => {
11952
+ const activeId = this.activeLessonId();
11953
+ for (const module of this.modules()) {
11954
+ const lesson = module.lessons.find((l) => l.id === activeId);
11955
+ if (lesson?.transcript?.length)
11956
+ return lesson.transcript;
11957
+ }
11958
+ return this.transcript();
11959
+ }, ...(ngDevMode ? [{ debugName: "activeCues" }] : []));
11960
+ /** Index of the cue covering the playhead, or `-1`. */
11961
+ this.activeCueIndex = computed(() => {
11962
+ const cues = this.activeCues();
11963
+ const at = this.currentTime();
11964
+ if (at === undefined || cues.length === 0)
11965
+ return -1;
11966
+ let found = -1;
11967
+ for (let i = 0; i < cues.length; i++) {
11968
+ const next = cues[i + 1];
11969
+ if (at >= cues[i].time && (!next || at < next.time)) {
11970
+ found = i;
11971
+ break;
11972
+ }
11973
+ }
11974
+ return found;
11975
+ }, ...(ngDevMode ? [{ debugName: "activeCueIndex" }] : []));
11976
+ this.transcriptRows = computed(() => {
11977
+ const cues = this.activeCues();
11978
+ const term = this.transcriptQuery().trim();
11979
+ const lower = term.toLowerCase();
11980
+ const activeIndex = this.activeCueIndex();
11981
+ const rows = [];
11982
+ cues.forEach((cue, index) => {
11983
+ const matchIndex = lower ? cue.text.toLowerCase().indexOf(lower) : -1;
11984
+ if (lower && matchIndex === -1)
11985
+ return;
11986
+ rows.push({
11987
+ key: `${index}-${cue.time}`,
11988
+ cue,
11989
+ timeLabel: formatCourseTime(cue.time),
11990
+ // While searching, the design drops the "current cue" highlight.
11991
+ current: !lower && index === activeIndex,
11992
+ before: matchIndex === -1 ? cue.text : cue.text.slice(0, matchIndex),
11993
+ match: matchIndex === -1 ? '' : cue.text.slice(matchIndex, matchIndex + term.length),
11994
+ after: matchIndex === -1 ? '' : cue.text.slice(matchIndex + term.length),
11995
+ });
11996
+ });
11997
+ return rows;
11998
+ }, ...(ngDevMode ? [{ debugName: "transcriptRows" }] : []));
11999
+ this.transcriptEmpty = computed(() => this.transcriptRows().length === 0, ...(ngDevMode ? [{ debugName: "transcriptEmpty" }] : []));
12000
+ // ── Messages ────────────────────────────────────────────────────────────────
12001
+ this.noLessonsLabel = computed(() => format(this.text().noLessons, { query: this.query().trim() }), ...(ngDevMode ? [{ debugName: "noLessonsLabel" }] : []));
12002
+ this.noTranscriptLabel = computed(() => format(this.text().noTranscript, { query: this.transcriptQuery().trim() }), ...(ngDevMode ? [{ debugName: "noTranscriptLabel" }] : []));
12003
+ // Keep the section holding the active lesson expanded. Tracks `activeLessonId`
12004
+ // only — so collapsing the current section by hand stays collapsed until the
12005
+ // selection actually changes (design behaviour).
12006
+ effect(() => {
12007
+ const activeId = this.activeLessonId();
12008
+ untracked(() => {
12009
+ if (!activeId)
12010
+ return;
12011
+ const module = findCourseModule(this.modules(), activeId);
12012
+ if (module)
12013
+ this.openModuleId.set(module.id);
12014
+ });
12015
+ });
12016
+ // First time modules are available and nothing has been decided yet, expand
12017
+ // the active lesson's section, else the first one.
12018
+ let autoOpened = false;
12019
+ effect(() => {
12020
+ const modules = this.modules();
12021
+ untracked(() => {
12022
+ if (autoOpened || modules.length === 0)
12023
+ return;
12024
+ autoOpened = true;
12025
+ if (this.openModuleId() !== undefined)
12026
+ return;
12027
+ const module = findCourseModule(modules, this.activeLessonId());
12028
+ this.openModuleId.set(module?.id ?? modules[0].id);
12029
+ });
12030
+ });
12031
+ // Follow the playhead in the transcript.
12032
+ effect(() => {
12033
+ const index = this.activeCueIndex();
12034
+ const auto = this.autoscroll();
12035
+ const tab = this.tab();
12036
+ const searching = this.transcriptQuery().trim().length > 0;
12037
+ untracked(() => {
12038
+ if (!this.isBrowser || !auto || searching || tab !== 'transcript' || index < 0)
12039
+ return;
12040
+ afterNextRender(() => this.scrollActiveCueIntoView(), { injector: this.injector });
12041
+ });
12042
+ });
12043
+ }
12044
+ // ── Template helpers ────────────────────────────────────────────────────────
12045
+ /** `"Section 3"` — the `{n}` placeholder resolved against the label set. */
12046
+ sectionLabel(index) {
12047
+ return format(this.text().section, { n: index });
12048
+ }
12049
+ /** Whether a section is expanded. */
12050
+ isModuleOpen(moduleId) {
12051
+ return this.openModuleId() === moduleId;
12052
+ }
12053
+ /** Expand a section, collapsing the one that was open (single-open accordion). */
12054
+ toggleModule(view) {
12055
+ const open = !this.isModuleOpen(view.id);
12056
+ this.openModuleId.set(open ? view.id : null);
12057
+ this.moduleToggle.emit({ module: view.module, open });
12058
+ }
12059
+ /** Roving keyboard support between section headers (WAI-ARIA accordion). */
12060
+ onModuleHeaderKeydown(event) {
12061
+ const key = event.key;
12062
+ if (key !== 'ArrowDown' && key !== 'ArrowUp' && key !== 'Home' && key !== 'End')
12063
+ return;
12064
+ const target = event.target;
12065
+ const root = target.closest('.pecb-course-panel__list');
12066
+ if (!root)
12067
+ return;
12068
+ const headers = Array.from(root.querySelectorAll('.pecb-course-panel__section-head'));
12069
+ if (headers.length === 0)
12070
+ return;
12071
+ const index = headers.indexOf(target);
12072
+ if (index === -1)
12073
+ return;
12074
+ let next;
12075
+ switch (key) {
12076
+ case 'ArrowDown':
12077
+ next = (index + 1) % headers.length;
12078
+ break;
12079
+ case 'ArrowUp':
12080
+ next = (index - 1 + headers.length) % headers.length;
12081
+ break;
12082
+ case 'Home':
12083
+ next = 0;
12084
+ break;
12085
+ default:
12086
+ next = headers.length - 1;
12087
+ }
12088
+ event.preventDefault();
12089
+ headers[next].focus();
12090
+ }
12091
+ /** Activate a lesson (ignored when locked). */
12092
+ selectLesson(lesson) {
12093
+ if ((lesson.status ?? 'available') === 'locked')
12094
+ return;
12095
+ this.activeLessonId.set(lesson.id);
12096
+ this.lessonSelect.emit(lesson);
12097
+ }
12098
+ /** Seek the player to a transcript cue. */
12099
+ seekToCue(cue) {
12100
+ this.transcriptSeek.emit(cue.time);
12101
+ }
12102
+ setTab(tab) {
12103
+ this.tab.set(tab);
12104
+ }
12105
+ onTabKeydown(event) {
12106
+ if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight')
12107
+ return;
12108
+ event.preventDefault();
12109
+ this.setTab(this.tab() === 'content' ? 'transcript' : 'content');
12110
+ const next = event.target
12111
+ .closest('[role="tablist"]')
12112
+ ?.querySelector('[aria-selected="false"]');
12113
+ next?.focus();
12114
+ }
12115
+ onQueryInput(event) {
12116
+ this.query.set(event.target.value);
12117
+ }
12118
+ onTranscriptQueryInput(event) {
12119
+ this.transcriptQuery.set(event.target.value);
12120
+ }
12121
+ clearTranscriptQuery() {
12122
+ this.transcriptQuery.set('');
12123
+ }
12124
+ toggleAutoscroll() {
12125
+ this.autoscroll.update((value) => !value);
12126
+ }
12127
+ onAutoscrollChange(event) {
12128
+ this.autoscroll.set(event.target.checked);
12129
+ }
12130
+ // ── Internals ───────────────────────────────────────────────────────────────
12131
+ /**
12132
+ * Splits a module's lessons into runs of consecutive lessons sharing a base
12133
+ * title. A run of two or more where at least one carries a `— Part n` suffix
12134
+ * renders as a sub-heading plus a connected rail (design behaviour).
12135
+ */
12136
+ buildRuns(lessons) {
12137
+ const runs = [];
12138
+ let i = 0;
12139
+ while (i < lessons.length) {
12140
+ const base = parseLessonPartTitle(lessons[i].title).base;
12141
+ const run = [];
12142
+ let j = i;
12143
+ while (j < lessons.length && parseLessonPartTitle(lessons[j].title).base === base) {
12144
+ run.push(lessons[j]);
12145
+ j++;
12146
+ }
12147
+ const hasParts = run.some((lesson) => parseLessonPartTitle(lesson.title).part !== null);
12148
+ if (hasParts && run.length > 1) {
12149
+ runs.push({
12150
+ key: `parts-${run[0].id}`,
12151
+ parts: true,
12152
+ base,
12153
+ lessons: run.map((lesson) => {
12154
+ const { part } = parseLessonPartTitle(lesson.title);
12155
+ // Inside a parts rail the quiz shows only its badge (design).
12156
+ const titleOverride = (lesson.type ?? 'video') === 'quiz' ? null : part ?? lesson.title;
12157
+ return this.buildLessonView(lesson, { titleOverride });
12158
+ }),
12159
+ });
12160
+ }
12161
+ else {
12162
+ runs.push({
12163
+ key: `plain-${run[0].id}`,
12164
+ parts: false,
12165
+ base,
12166
+ lessons: run.map((lesson) => this.buildLessonView(lesson)),
12167
+ });
12168
+ }
12169
+ i = j;
12170
+ }
12171
+ return runs;
12172
+ }
12173
+ buildLessonView(lesson, options = {}) {
12174
+ const text = this.text();
12175
+ const compact = this.compact();
12176
+ const active = lesson.id === this.activeLessonId();
12177
+ const status = this.resolveStatus()(lesson);
12178
+ const isQuiz = (lesson.type ?? 'video') === 'quiz';
12179
+ const isPlaying = status === 'playing';
12180
+ const isStarted = status === 'started';
12181
+ const isCompleted = status === 'completed';
12182
+ const isLocked = status === 'locked';
12183
+ const duration = lesson.durationSeconds ?? 0;
12184
+ const questions = lesson.questionCount ?? 0;
12185
+ const answered = lesson.answeredCount ?? 0;
12186
+ // The active lesson follows the live playhead; everything else uses its
12187
+ // stored resume position.
12188
+ const live = this.currentTime();
12189
+ const at = active && live !== undefined ? live : lesson.resumeSeconds ?? 0;
12190
+ const fraction = isQuiz
12191
+ ? questions
12192
+ ? Math.min(1, answered / questions)
12193
+ : 0.4
12194
+ : duration
12195
+ ? Math.min(1, at / duration)
12196
+ : 0.4;
12197
+ const discSize = compact ? DISC_SIZE.compact : DISC_SIZE.comfortable;
12198
+ const ringRadius = discSize / 2 - RING_STROKE / 2;
12199
+ const ringCircumference = 2 * Math.PI * ringRadius;
12200
+ const showBar = (isPlaying || isStarted) && (isQuiz ? questions > 0 : duration > 0);
12201
+ const barPercent = isQuiz
12202
+ ? questions
12203
+ ? (answered / questions) * 100
12204
+ : 0
12205
+ : duration
12206
+ ? Math.min(100, (at / duration) * 100)
12207
+ : 0;
12208
+ let quizResultLabel = null;
12209
+ if (isQuiz && isCompleted) {
12210
+ quizResultLabel = format(text.passed, { done: lesson.correctCount ?? 0, total: questions });
12211
+ }
12212
+ else if (isQuiz && isStarted) {
12213
+ quizResultLabel = format(text.answered, { done: answered, total: questions });
12214
+ }
12215
+ const timeLabel = !isQuiz && (isPlaying || isStarted) && duration
12216
+ ? `${isStarted ? `${text.resume} ` : ''}${formatCourseTime(at)} / ${formatCourseTime(duration)}`
12217
+ : null;
12218
+ const displayTitle = options.titleOverride !== undefined ? options.titleOverride : lesson.title;
12219
+ return {
12220
+ lesson,
12221
+ id: lesson.id,
12222
+ active,
12223
+ status,
12224
+ isQuiz,
12225
+ isPlaying,
12226
+ isStarted,
12227
+ isCompleted,
12228
+ isLocked,
12229
+ displayTitle,
12230
+ ariaLabel: isLocked ? `${lesson.title} — ${text.locked}` : lesson.title,
12231
+ durationLabel: formatCourseDuration(duration),
12232
+ questionsLabel: format(text.questions, { n: questions }),
12233
+ quizResultLabel,
12234
+ timeLabel,
12235
+ showBar,
12236
+ barPercent,
12237
+ discSize,
12238
+ discCenter: discSize / 2,
12239
+ ringRadius,
12240
+ ringCircumference,
12241
+ ringOffset: ringCircumference * (1 - fraction),
12242
+ ringViewBox: `0 0 ${discSize} ${discSize}`,
12243
+ checkSize: compact ? 14 : 16,
12244
+ pauseSize: compact ? 11 : 12,
12245
+ // The in-progress ring leaves a touch less room than the plain outline.
12246
+ playIconSize: isStarted ? (compact ? 9 : 10) : compact ? 10 : 11,
12247
+ quizIconSize: isStarted ? (compact ? 13 : 14) : compact ? 14 : 15,
12248
+ connector: options.connector ?? false,
12249
+ connectorDone: isCompleted,
12250
+ };
12251
+ }
12252
+ scrollActiveCueIntoView() {
12253
+ const container = this.transcriptScrollRef()?.nativeElement;
12254
+ if (!container)
12255
+ return;
12256
+ const row = container.querySelector('[data-current="true"]');
12257
+ if (!row)
12258
+ return;
12259
+ const rowRect = row.getBoundingClientRect();
12260
+ const boxRect = container.getBoundingClientRect();
12261
+ const top = container.scrollTop + (rowRect.top - boxRect.top) - (boxRect.height / 2 - rowRect.height / 2);
12262
+ const reduceMotion = typeof window !== 'undefined' &&
12263
+ typeof window.matchMedia === 'function' &&
12264
+ window.matchMedia('(prefers-reduced-motion: reduce)').matches;
12265
+ container.scrollTo({ top, behavior: reduceMotion ? 'auto' : 'smooth' });
12266
+ }
12267
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: CourseContentPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
12268
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: CourseContentPanelComponent, isStandalone: true, selector: "pecb-course-content-panel", inputs: { modules: { classPropertyName: "modules", publicName: "modules", isSignal: true, isRequired: false, transformFunction: null }, transcript: { classPropertyName: "transcript", publicName: "transcript", isSignal: true, isRequired: false, transformFunction: null }, activeLessonId: { classPropertyName: "activeLessonId", publicName: "activeLessonId", isSignal: true, isRequired: false, transformFunction: null }, tab: { classPropertyName: "tab", publicName: "tab", isSignal: true, isRequired: false, transformFunction: null }, openModuleId: { classPropertyName: "openModuleId", publicName: "openModuleId", isSignal: true, isRequired: false, transformFunction: null }, autoscroll: { classPropertyName: "autoscroll", publicName: "autoscroll", isSignal: true, isRequired: false, transformFunction: null }, query: { classPropertyName: "query", publicName: "query", isSignal: true, isRequired: false, transformFunction: null }, transcriptQuery: { classPropertyName: "transcriptQuery", publicName: "transcriptQuery", isSignal: true, isRequired: false, transformFunction: null }, playing: { classPropertyName: "playing", publicName: "playing", isSignal: true, isRequired: false, transformFunction: null }, currentTime: { classPropertyName: "currentTime", publicName: "currentTime", isSignal: true, isRequired: false, transformFunction: null }, layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null }, density: { classPropertyName: "density", publicName: "density", isSignal: true, isRequired: false, transformFunction: null }, accent: { classPropertyName: "accent", publicName: "accent", isSignal: true, isRequired: false, transformFunction: null }, showTabs: { classPropertyName: "showTabs", publicName: "showTabs", isSignal: true, isRequired: false, transformFunction: null }, showSearch: { classPropertyName: "showSearch", publicName: "showSearch", isSignal: true, isRequired: false, transformFunction: null }, showTranscriptSearch: { classPropertyName: "showTranscriptSearch", publicName: "showTranscriptSearch", isSignal: true, isRequired: false, transformFunction: null }, showAutoscroll: { classPropertyName: "showAutoscroll", publicName: "showAutoscroll", isSignal: true, isRequired: false, transformFunction: null }, showProgress: { classPropertyName: "showProgress", publicName: "showProgress", isSignal: true, isRequired: false, transformFunction: null }, showCertificate: { classPropertyName: "showCertificate", publicName: "showCertificate", isSignal: true, isRequired: false, transformFunction: null }, soloSingleLessonModules: { classPropertyName: "soloSingleLessonModules", publicName: "soloSingleLessonModules", isSignal: true, isRequired: false, transformFunction: null }, labels: { classPropertyName: "labels", publicName: "labels", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activeLessonId: "activeLessonIdChange", tab: "tabChange", openModuleId: "openModuleIdChange", autoscroll: "autoscrollChange", query: "queryChange", transcriptQuery: "transcriptQueryChange", lessonSelect: "lessonSelect", transcriptSeek: "transcriptSeek", moduleToggle: "moduleToggle", certificateClick: "certificateClick" }, host: { classAttribute: "pecb-course-panel-host" }, viewQueries: [{ propertyName: "transcriptScrollRef", first: true, predicate: ["transcriptScroll"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n Lesson row \u2014 one <button> per lesson, shared by the grouped, parts-rail,\n timeline and solo renderings.\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n<ng-template #lessonRowTpl let-vm let-solo=\"solo\">\n <button\n type=\"button\"\n class=\"pecb-course-panel__row\"\n [class.pecb-course-panel__row--active]=\"vm.active\"\n [class.pecb-course-panel__row--quiz]=\"vm.isQuiz\"\n [class.pecb-course-panel__row--playing]=\"vm.isPlaying\"\n [class.pecb-course-panel__row--started]=\"vm.isStarted\"\n [class.pecb-course-panel__row--completed]=\"vm.isCompleted\"\n [class.pecb-course-panel__row--locked]=\"vm.isLocked\"\n [class.pecb-course-panel__row--solo]=\"!!solo\"\n [disabled]=\"vm.isLocked\"\n [attr.aria-current]=\"vm.active ? 'true' : null\"\n [attr.aria-label]=\"vm.ariaLabel\"\n (click)=\"selectLesson(vm.lesson)\"\n >\n @if (vm.active && vm.isPlaying) {\n <span class=\"pecb-course-panel__row-marker\" aria-hidden=\"true\"></span>\n }\n\n <!-- Status disc (+ optional timeline connector) -->\n <span class=\"pecb-course-panel__disc-col\">\n <span\n class=\"pecb-course-panel__disc\"\n [class.pecb-course-panel__disc--completed]=\"vm.isCompleted\"\n [class.pecb-course-panel__disc--playing]=\"vm.isPlaying\"\n [class.pecb-course-panel__disc--started]=\"vm.isStarted\"\n [class.pecb-course-panel__disc--locked]=\"vm.isLocked\"\n [class.pecb-course-panel__disc--plain]=\"!vm.isCompleted && !vm.isPlaying && !vm.isStarted\"\n [style.width.px]=\"vm.discSize\"\n [style.height.px]=\"vm.discSize\"\n aria-hidden=\"true\"\n >\n @if (vm.isCompleted) {\n <svg [attr.width]=\"vm.checkSize\" [attr.height]=\"vm.checkSize\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n } @else if (vm.isPlaying) {\n <svg [attr.width]=\"vm.pauseSize\" [attr.height]=\"vm.pauseSize\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <rect x=\"6\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n <rect x=\"14\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n </svg>\n } @else if (vm.isStarted) {\n <svg class=\"pecb-course-panel__ring\" [attr.width]=\"vm.discSize\" [attr.height]=\"vm.discSize\" [attr.viewBox]=\"vm.ringViewBox\">\n <circle\n class=\"pecb-course-panel__ring-track\"\n [attr.cx]=\"vm.discCenter\" [attr.cy]=\"vm.discCenter\" [attr.r]=\"vm.ringRadius\"\n fill=\"none\" stroke-width=\"3\" />\n <circle\n class=\"pecb-course-panel__ring-value\"\n [attr.cx]=\"vm.discCenter\" [attr.cy]=\"vm.discCenter\" [attr.r]=\"vm.ringRadius\"\n fill=\"none\" stroke-width=\"3\" stroke-linecap=\"round\"\n [attr.stroke-dasharray]=\"vm.ringCircumference\"\n [attr.stroke-dashoffset]=\"vm.ringOffset\" />\n </svg>\n <span class=\"pecb-course-panel__disc-glyph\">\n @if (vm.isQuiz) {\n <svg [attr.width]=\"vm.quizIconSize\" [attr.height]=\"vm.quizIconSize\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <path d=\"M9.5 9.2a2.5 2.5 0 1 1 3.4 2.3c-.6.3-.9.7-.9 1.4v.4\" />\n <circle cx=\"12\" cy=\"16.3\" r=\".6\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n } @else {\n <svg [attr.width]=\"vm.playIconSize\" [attr.height]=\"vm.playIconSize\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <polygon points=\"6 4 20 12 6 20 6 4\" />\n </svg>\n }\n </span>\n } @else if (vm.isLocked) {\n <svg [attr.width]=\"vm.checkSize\" [attr.height]=\"vm.checkSize\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"5\" y=\"11\" width=\"14\" height=\"9\" rx=\"2\" />\n <path d=\"M8 11V8a4 4 0 0 1 8 0v3\" />\n </svg>\n } @else if (vm.isQuiz) {\n <svg [attr.width]=\"vm.quizIconSize\" [attr.height]=\"vm.quizIconSize\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <path d=\"M9.5 9.2a2.5 2.5 0 1 1 3.4 2.3c-.6.3-.9.7-.9 1.4v.4\" />\n <circle cx=\"12\" cy=\"16.3\" r=\".6\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n } @else {\n <svg [attr.width]=\"vm.playIconSize\" [attr.height]=\"vm.playIconSize\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <polygon points=\"6 4 20 12 6 20 6 4\" />\n </svg>\n }\n </span>\n\n @if (vm.connector) {\n <span\n class=\"pecb-course-panel__connector\"\n [class.pecb-course-panel__connector--done]=\"vm.connectorDone\"\n aria-hidden=\"true\"\n ></span>\n }\n </span>\n\n <!-- Body -->\n <span class=\"pecb-course-panel__row-body\">\n @if (vm.isQuiz) {\n <span class=\"pecb-course-panel__quiz-badge-line\">\n <span class=\"pecb-course-panel__quiz-badge\">{{ text().quiz }}</span>\n </span>\n }\n\n @if (vm.displayTitle) {\n <span class=\"pecb-course-panel__row-title\">{{ vm.displayTitle }}</span>\n }\n\n <span class=\"pecb-course-panel__row-meta\">\n <span class=\"pecb-course-panel__meta-primary\">\n @if (vm.isQuiz) {\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <path d=\"M9.5 9.2a2.5 2.5 0 1 1 3.4 2.3c-.6.3-.9.7-.9 1.4v.4\" />\n <circle cx=\"12\" cy=\"16.3\" r=\".6\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n {{ vm.questionsLabel }}\n } @else {\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <polyline points=\"12 7 12 12 15.5 14\" />\n </svg>\n {{ vm.durationLabel }}\n }\n </span>\n\n @if (vm.quizResultLabel) {\n <span\n class=\"pecb-course-panel__meta-result\"\n [class.pecb-course-panel__meta-result--passed]=\"vm.isCompleted\"\n >\n @if (vm.isCompleted) {\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n }\n {{ vm.quizResultLabel }}\n </span>\n }\n\n @if (vm.timeLabel) {\n <span class=\"pecb-course-panel__meta-time\">\n @if (vm.isStarted) {\n <svg width=\"10\" height=\"10\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <polygon points=\"6 4 20 12 6 20 6 4\" />\n </svg>\n }\n {{ vm.timeLabel }}\n </span>\n }\n </span>\n\n @if (vm.showBar) {\n <span class=\"pecb-course-panel__bar\" aria-hidden=\"true\">\n <span class=\"pecb-course-panel__bar-fill\" [style.width.%]=\"vm.barPercent\"></span>\n </span>\n }\n </span>\n\n @if (solo) {\n <span class=\"pecb-course-panel__solo-play\" aria-hidden=\"true\">\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <polygon points=\"6 4 20 12 6 20 6 4\" />\n </svg>\n </span>\n }\n </button>\n</ng-template>\n\n<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n Panel\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n<div [class]=\"hostClasses()\">\n @if (showTabs()) {\n <div class=\"pecb-course-panel__tabs\" role=\"tablist\" [attr.aria-label]=\"text().lessonList\">\n <button\n type=\"button\"\n role=\"tab\"\n class=\"pecb-course-panel__tab\"\n [class.pecb-course-panel__tab--active]=\"tab() === 'content'\"\n [id]=\"uid + '-tab-content'\"\n [attr.aria-selected]=\"tab() === 'content'\"\n [attr.aria-controls]=\"uid + '-panel-content'\"\n [attr.tabindex]=\"tab() === 'content' ? 0 : -1\"\n (click)=\"setTab('content')\"\n (keydown)=\"onTabKeydown($event)\"\n >\n {{ text().contentTab }}\n @if (tab() === 'content') {\n <span class=\"pecb-course-panel__tab-indicator\" aria-hidden=\"true\"></span>\n }\n </button>\n <button\n type=\"button\"\n role=\"tab\"\n class=\"pecb-course-panel__tab\"\n [class.pecb-course-panel__tab--active]=\"tab() === 'transcript'\"\n [id]=\"uid + '-tab-transcript'\"\n [attr.aria-selected]=\"tab() === 'transcript'\"\n [attr.aria-controls]=\"uid + '-panel-transcript'\"\n [attr.tabindex]=\"tab() === 'transcript' ? 0 : -1\"\n (click)=\"setTab('transcript')\"\n (keydown)=\"onTabKeydown($event)\"\n >\n {{ text().transcriptTab }}\n @if (tab() === 'transcript') {\n <span class=\"pecb-course-panel__tab-indicator\" aria-hidden=\"true\"></span>\n }\n </button>\n </div>\n }\n\n <!-- \u2500\u2500 Content tab \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (tab() === 'content') {\n <div\n class=\"pecb-course-panel__body\"\n role=\"tabpanel\"\n [id]=\"uid + '-panel-content'\"\n [attr.aria-labelledby]=\"showTabs() ? uid + '-tab-content' : null\"\n >\n @if (showProgress()) {\n <div class=\"pecb-course-panel__progress\">\n <span class=\"pecb-course-panel__progress-ring\">\n <svg width=\"56\" height=\"56\" viewBox=\"0 0 56 56\" aria-hidden=\"true\">\n <circle class=\"pecb-course-panel__progress-track\" cx=\"28\" cy=\"28\" [attr.r]=\"progressRing().radius\" fill=\"none\" stroke-width=\"5\" />\n <circle\n class=\"pecb-course-panel__progress-value\"\n cx=\"28\" cy=\"28\" [attr.r]=\"progressRing().radius\" fill=\"none\" stroke-width=\"5\" stroke-linecap=\"round\"\n [attr.stroke-dasharray]=\"progressRing().circumference\"\n [attr.stroke-dashoffset]=\"progressRing().offset\" />\n </svg>\n <span class=\"pecb-course-panel__progress-percent\">{{ progress().percent }}%</span>\n </span>\n <span class=\"pecb-course-panel__progress-text\">\n <span class=\"pecb-course-panel__progress-title\">{{ text().courseProgress }}</span>\n <span class=\"pecb-course-panel__progress-detail\">{{ progressDetail() }}</span>\n </span>\n @if (showCertificate()) {\n <button type=\"button\" class=\"pecb-course-panel__certificate\" (click)=\"certificateClick.emit()\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"8\" r=\"5\" />\n <path d=\"M8.5 12.5 7 21l5-2.5L17 21l-1.5-8.5\" />\n </svg>\n {{ text().certificate }}\n </button>\n }\n </div>\n }\n\n @if (showSearch()) {\n <div class=\"pecb-course-panel__search\">\n <div class=\"pecb-course-panel__search-box\">\n <span class=\"pecb-course-panel__search-icon\" aria-hidden=\"true\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"7\" />\n <path d=\"m20 20-3.2-3.2\" />\n </svg>\n </span>\n <input\n type=\"search\"\n class=\"pecb-course-panel__search-input\"\n [value]=\"query()\"\n [attr.placeholder]=\"text().searchLessons\"\n [attr.aria-label]=\"text().searchLessons\"\n (input)=\"onQueryInput($event)\"\n />\n </div>\n </div>\n }\n\n <div class=\"pecb-course-panel__scroll pecb-course-panel__list\">\n @if (isEmpty()) {\n <p class=\"pecb-course-panel__empty\">{{ noLessonsLabel() }}</p>\n } @else if (layout() === 'timeline') {\n <div class=\"pecb-course-panel__timeline\">\n @for (row of timelineRows(); track row.key) {\n @if (row.header; as header) {\n <div class=\"pecb-course-panel__timeline-head\">\n <span class=\"pecb-course-panel__timeline-label\">{{ sectionLabel(header.number) }}</span>\n <span class=\"pecb-course-panel__timeline-rule\" aria-hidden=\"true\"></span>\n <span class=\"pecb-course-panel__timeline-count\">{{ header.stats.done }}/{{ header.stats.total }}</span>\n </div>\n } @else {\n <ng-container *ngTemplateOutlet=\"lessonRowTpl; context: { $implicit: row.lesson }\"></ng-container>\n }\n }\n </div>\n } @else {\n @for (view of moduleViews(); track view.id) {\n @if (view.solo; as soloView) {\n <div class=\"pecb-course-panel__section pecb-course-panel__section--solo\">\n <ng-container\n *ngTemplateOutlet=\"lessonRowTpl; context: { $implicit: soloView, solo: true }\"\n ></ng-container>\n </div>\n } @else {\n <div\n class=\"pecb-course-panel__section\"\n [class.pecb-course-panel__section--open]=\"isModuleOpen(view.id)\"\n >\n <button\n type=\"button\"\n class=\"pecb-course-panel__section-head\"\n [class.pecb-course-panel__section-head--open]=\"isModuleOpen(view.id)\"\n [id]=\"uid + '-head-' + view.id\"\n [attr.aria-expanded]=\"isModuleOpen(view.id)\"\n [attr.aria-controls]=\"uid + '-region-' + view.id\"\n (click)=\"toggleModule(view)\"\n (keydown)=\"onModuleHeaderKeydown($event)\"\n >\n <span\n class=\"pecb-course-panel__section-badge\"\n [class.pecb-course-panel__section-badge--done]=\"view.allDone\"\n [class.pecb-course-panel__section-badge--active]=\"!view.allDone && (view.stats.playing || view.stats.started)\"\n aria-hidden=\"true\"\n >\n @if (view.allDone) {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n } @else if (view.stats.playing) {\n <svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <rect x=\"6\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n <rect x=\"14\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n </svg>\n } @else {\n <svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <polygon points=\"6 4 20 12 6 20 6 4\" />\n </svg>\n }\n </span>\n\n <span class=\"pecb-course-panel__section-text\">\n <span class=\"pecb-course-panel__section-title\">{{ sectionLabel(view.number) }}</span>\n <span class=\"pecb-course-panel__section-meta\">\n <span>{{ view.statsLabel }}</span>\n @if (view.stats.durationSeconds > 0) {\n <span class=\"pecb-course-panel__section-duration\">\n <svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <polyline points=\"12 7 12 12 15.5 14\" />\n </svg>\n {{ view.durationLabel }}\n </span>\n }\n </span>\n </span>\n\n <span class=\"pecb-course-panel__section-chevron\" aria-hidden=\"true\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"6 9 12 15 18 9\" />\n </svg>\n </span>\n </button>\n\n <div class=\"pecb-course-panel__section-wrap\">\n <div\n class=\"pecb-course-panel__section-clip\"\n [id]=\"uid + '-region-' + view.id\"\n role=\"region\"\n [attr.aria-labelledby]=\"uid + '-head-' + view.id\"\n [attr.inert]=\"isModuleOpen(view.id) ? null : ''\"\n >\n <div class=\"pecb-course-panel__section-body\">\n @for (run of view.runs; track run.key) {\n @if (run.parts) {\n <p class=\"pecb-course-panel__run-title\">{{ run.base }}</p>\n <div class=\"pecb-course-panel__rail\">\n <span class=\"pecb-course-panel__rail-line\" aria-hidden=\"true\"></span>\n @for (lessonView of run.lessons; track lessonView.id) {\n <ng-container *ngTemplateOutlet=\"lessonRowTpl; context: { $implicit: lessonView }\"></ng-container>\n }\n </div>\n } @else {\n @for (lessonView of run.lessons; track lessonView.id) {\n <ng-container *ngTemplateOutlet=\"lessonRowTpl; context: { $implicit: lessonView }\"></ng-container>\n }\n }\n }\n </div>\n </div>\n </div>\n </div>\n }\n }\n }\n </div>\n </div>\n } @else {\n <!-- \u2500\u2500 Transcript tab \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div\n class=\"pecb-course-panel__body\"\n role=\"tabpanel\"\n [id]=\"uid + '-panel-transcript'\"\n [attr.aria-labelledby]=\"showTabs() ? uid + '-tab-transcript' : null\"\n >\n @if (showTranscriptSearch()) {\n <div class=\"pecb-course-panel__search\">\n <div class=\"pecb-course-panel__search-box\">\n <span class=\"pecb-course-panel__search-icon\" aria-hidden=\"true\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"7\" />\n <path d=\"m20 20-3.2-3.2\" />\n </svg>\n </span>\n <input\n type=\"search\"\n class=\"pecb-course-panel__search-input\"\n [value]=\"transcriptQuery()\"\n [attr.placeholder]=\"text().searchTranscript\"\n [attr.aria-label]=\"text().searchTranscript\"\n (input)=\"onTranscriptQueryInput($event)\"\n />\n @if (transcriptQuery()) {\n <button\n type=\"button\"\n class=\"pecb-course-panel__search-clear\"\n [attr.aria-label]=\"text().clearSearch\"\n (click)=\"clearTranscriptQuery()\"\n >\u00D7</button>\n }\n </div>\n </div>\n }\n\n <div class=\"pecb-course-panel__scroll pecb-course-panel__transcript\" #transcriptScroll>\n @if (transcriptEmpty()) {\n <p class=\"pecb-course-panel__empty\">{{ noTranscriptLabel() }}</p>\n } @else {\n <div class=\"pecb-course-panel__cues\">\n @for (row of transcriptRows(); track row.key) {\n <button\n type=\"button\"\n class=\"pecb-course-panel__cue\"\n [class.pecb-course-panel__cue--current]=\"row.current\"\n [attr.data-current]=\"row.current\"\n [attr.aria-current]=\"row.current ? 'true' : null\"\n (click)=\"seekToCue(row.cue)\"\n >\n <span class=\"pecb-course-panel__cue-time\">{{ row.timeLabel }}</span>\n <span class=\"pecb-course-panel__cue-text\">{{ row.before }}@if (row.match) {<mark class=\"pecb-course-panel__mark\">{{ row.match }}</mark>}{{ row.after }}</span>\n </button>\n }\n </div>\n }\n </div>\n\n @if (showAutoscroll()) {\n <div class=\"pecb-course-panel__footer\">\n <label class=\"pecb-course-panel__autoscroll\">\n <input\n type=\"checkbox\"\n class=\"pecb-course-panel__autoscroll-input\"\n [checked]=\"autoscroll()\"\n (change)=\"onAutoscrollChange($event)\"\n />\n <span class=\"pecb-course-panel__autoscroll-box\" aria-hidden=\"true\">\n @if (autoscroll()) {\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n }\n </span>\n <span class=\"pecb-course-panel__autoscroll-label\">{{ text().autoscroll }}</span>\n </label>\n </div>\n }\n </div>\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%;min-height:0;container-type:inline-size;container-name:pecb-course-panel}.pecb-course-panel{--_row-pad-y: 11px;--_row-pad-left: 14px;--_row-pad-right: 16px;--_row-gap: 13px;--_disc: 30px;--_section-pad-y: 15px;--_title-size: 13.5px;display:flex;flex-direction:column;height:100%;min-height:0;box-sizing:border-box;background:var(--pecb-course-panel-bg, #ffffff);font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);line-height:normal;color:#212427;overflow:hidden}.pecb-course-panel *,.pecb-course-panel *:before,.pecb-course-panel *:after{box-sizing:border-box}.pecb-course-panel--compact{--_row-pad-y: 8px;--_row-pad-left: 12px;--_row-pad-right: 14px;--_row-gap: 11px;--_disc: 26px;--_section-pad-y: 12px;--_title-size: 12.5px}.pecb-course-panel--accent-red{--pecb-course-panel-accent: #a11e29;--pecb-course-panel-accent-soft: rgba(161, 30, 41, .27)}.pecb-course-panel__tabs{display:flex;flex:0 0 auto;padding:0 20px;border-bottom:1px solid var(--pecb-course-panel-border, #ececec)}.pecb-course-panel__tab{border:none;background:none;font:inherit;color:inherit;position:relative;flex:1;padding:16px 0 14px;font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:14px;font-weight:500;color:#616161;cursor:pointer;transition:color .15s ease-in-out}.pecb-course-panel__tab:focus{outline:none}.pecb-course-panel__tab:hover{color:#212427}.pecb-course-panel__tab:focus-visible{outline:2px solid #a11e29;outline-offset:-3px;border-radius:6px}.pecb-course-panel__tab--active{font-weight:700;color:#a11e29}.pecb-course-panel__tab-indicator{position:absolute;left:0;right:0;bottom:-1px;height:2.5px;border-radius:3px;background:#a11e29}.pecb-course-panel__body{display:flex;flex:1 1 auto;flex-direction:column;min-height:0}.pecb-course-panel__scroll{flex:1 1 auto;min-height:0;overflow-y:auto;overflow-x:hidden;overscroll-behavior:contain;scrollbar-width:thin;scrollbar-color:#ececec transparent}.pecb-course-panel__scroll::-webkit-scrollbar{width:8px}.pecb-course-panel__scroll::-webkit-scrollbar-thumb{background:#ececec;border-radius:8px}.pecb-course-panel__scroll::-webkit-scrollbar-track{background:transparent}.pecb-course-panel__empty{margin:0;padding:40px;text-align:center;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:13px;color:#919191}.pecb-course-panel__progress{display:flex;flex:0 0 auto;align-items:center;gap:16px;padding:16px 20px;border-bottom:1px solid var(--pecb-course-panel-border, #ececec)}.pecb-course-panel__progress-ring{position:relative;flex:0 0 auto;width:56px;height:56px}.pecb-course-panel__progress-ring svg{display:block;transform:rotate(-90deg)}.pecb-course-panel__progress-track{stroke:#ececec}.pecb-course-panel__progress-value{stroke:var(--pecb-course-panel-accent, #212427);transition:stroke-dashoffset .5s ease}.pecb-course-panel__progress-percent{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:14px;font-weight:700;color:#212427}.pecb-course-panel__progress-text{display:flex;flex:1 1 auto;flex-direction:column;min-width:0}.pecb-course-panel__progress-title{font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:14px;font-weight:700;color:#212427}.pecb-course-panel__progress-detail{margin-top:3px;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:12px;color:#616161}.pecb-course-panel__certificate{border:none;background:none;font:inherit;color:inherit;display:inline-flex;flex:0 0 auto;align-items:center;gap:6px;padding:6px 12px;border-radius:9999px;background:#212427;color:#fff;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:12px;font-weight:600;cursor:pointer;transition:background .15s ease-in-out}.pecb-course-panel__certificate:focus{outline:none}.pecb-course-panel__certificate:hover{background:#313131}.pecb-course-panel__certificate:focus-visible{outline:2px solid #a11e29;outline-offset:2px}.pecb-course-panel__search{flex:0 0 auto;padding:12px 20px 10px}.pecb-course-panel__search-box{display:flex;align-items:center;gap:9px;min-height:40px;padding:9px 12px;border:1px solid #c2c2c2;border-radius:6px;background:#fff;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}.pecb-course-panel__search-box:focus-within{border-color:#212427;box-shadow:0 0 0 3px #21242714}.pecb-course-panel__search-icon{display:flex;flex:0 0 auto;color:#919191}.pecb-course-panel__search-input{flex:1 1 auto;min-width:0;border:none;outline:none;background:transparent;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:13px;color:#212427;appearance:none}.pecb-course-panel__search-input::placeholder{color:#919191}.pecb-course-panel__search-input::-webkit-search-cancel-button,.pecb-course-panel__search-input::-webkit-search-decoration{appearance:none;display:none}.pecb-course-panel__search-clear{padding:0;border:none;background:none;font:inherit;color:inherit;display:flex;flex:0 0 auto;align-items:center;justify-content:center;width:18px;height:18px;border-radius:50%;color:#919191;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:17px;line-height:1;cursor:pointer}.pecb-course-panel__search-clear:focus{outline:none}.pecb-course-panel__search-clear:hover{background:#f4f4f4;color:#212427}.pecb-course-panel__search-clear:focus-visible{outline:2px solid #a11e29;outline-offset:1px}.pecb-course-panel__section{border-bottom:1px solid var(--pecb-course-panel-border, #ececec);background:transparent}.pecb-course-panel__section--open{background:#fff}.pecb-course-panel__section--solo{border-bottom:1px solid var(--pecb-course-panel-border, #ececec)}.pecb-course-panel__section-head{padding:0;border:none;background:none;font:inherit;color:inherit;display:flex;align-items:center;gap:12px;width:100%;padding:var(--_section-pad-y) 16px;text-align:left;background:#f4f4f4;cursor:pointer;transition:background .15s ease-in-out}.pecb-course-panel__section-head:focus{outline:none}.pecb-course-panel__section-head:hover{background:#fff}.pecb-course-panel__section-head:focus-visible{outline:2px solid #a11e29;outline-offset:-2px}.pecb-course-panel__section-head--open{background:#ececec}.pecb-course-panel__section-head--open:hover{background:#f4f4f4}.pecb-course-panel__section-badge{display:flex;flex:0 0 auto;align-items:center;justify-content:center;width:30px;height:30px;border-radius:8px;background:#c2c2c2;color:#fff;font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:13px;font-weight:700}.pecb-course-panel__section-badge--active{background:var(--pecb-course-panel-accent, #212427)}.pecb-course-panel__section-badge--done{background:#e6f4ea;color:green}.pecb-course-panel__section-text{display:flex;flex:1 1 auto;flex-direction:column;min-width:0}.pecb-course-panel__section-title{font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:13.5px;font-weight:600;line-height:1.3;color:#212427}.pecb-course-panel__section-meta{display:flex;align-items:center;gap:10px;margin-top:4px;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:11.5px;color:#919191}.pecb-course-panel__section-duration{display:inline-flex;align-items:center;gap:4px}.pecb-course-panel__section-chevron{display:flex;flex:0 0 auto;color:#919191;transform:rotate(-90deg);transition:transform .18s ease}.pecb-course-panel__section-head--open .pecb-course-panel__section-chevron{transform:rotate(0)}.pecb-course-panel__section-wrap{display:grid;grid-template-rows:0fr;transition:grid-template-rows .22s ease}.pecb-course-panel__section--open .pecb-course-panel__section-wrap{grid-template-rows:1fr}.pecb-course-panel__section-clip{overflow:hidden;min-height:0}.pecb-course-panel__section-body{display:flex;flex-direction:column;gap:2px;padding:6px 12px 10px var(--_row-pad-left)}.pecb-course-panel--compact .pecb-course-panel__section-body{padding:4px 10px 8px var(--_row-pad-left)}.pecb-course-panel__run-title{margin:0;padding:9px 8px 5px 12px;font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:13px;font-weight:600;line-height:1.35;color:#212427;text-wrap:pretty}.pecb-course-panel__rail{position:relative}.pecb-course-panel__rail-line{position:absolute;left:calc(var(--_row-pad-left) + var(--_disc) / 2 - 1px);top:calc(var(--_row-pad-y) + var(--_disc) / 2);bottom:calc(var(--_row-pad-y) + var(--_disc) / 2);z-index:1;width:2px;border-radius:2px;background:#ececec}.pecb-course-panel__row{padding:0;border:none;background:none;font:inherit;color:inherit;position:relative;display:flex;align-items:center;gap:var(--_row-gap);width:100%;padding:var(--_row-pad-y) var(--_row-pad-right) var(--_row-pad-y) var(--_row-pad-left);border-radius:8px;text-align:left;background:transparent;cursor:pointer;transition:background .14s ease,box-shadow .14s ease}.pecb-course-panel__row:focus{outline:none}.pecb-course-panel__row:hover:not(:disabled):not(.pecb-course-panel__row--active){background:#fafafa}.pecb-course-panel__row:focus-visible{outline:2px solid #a11e29;outline-offset:-2px}.pecb-course-panel__row:disabled{cursor:not-allowed}.pecb-course-panel__row--locked{opacity:.72}.pecb-course-panel__row--active{background:#fff;box-shadow:inset 0 0 0 1px var(--pecb-course-panel-border, #ececec)}.pecb-course-panel__row--active.pecb-course-panel__row--completed{background:#e6f4ea}.pecb-course-panel__row--active.pecb-course-panel__row--started{background:#fff4e5}.pecb-course-panel__row--active.pecb-course-panel__row--playing{background:#fbeaec;box-shadow:inset 0 0 0 1px var(--pecb-course-panel-accent-soft, rgba(33, 36, 39, .27))}.pecb-course-panel__row--active.pecb-course-panel__row--quiz{background:#eee8ff}.pecb-course-panel__row--solo{gap:12px;padding:var(--_section-pad-y) 16px;border-radius:0}.pecb-course-panel__row-marker{position:absolute;left:0;top:8px;bottom:8px;width:3px;border-radius:3px;background:var(--pecb-course-panel-accent, #212427)}.pecb-course-panel__row--solo .pecb-course-panel__row-marker{top:10px;bottom:10px}.pecb-course-panel__row--solo .pecb-course-panel__row-title{font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:13.5px;font-weight:600;line-height:1.3}.pecb-course-panel__solo-play{display:flex;flex:0 0 auto;align-items:center;justify-content:center;width:30px;height:30px;border-radius:50%;background:#f4f4f4;color:#616161}.pecb-course-panel__disc-col{position:relative;display:flex;flex:0 0 auto;flex-direction:column;align-items:center;align-self:stretch}.pecb-course-panel__disc{position:relative;z-index:2;display:flex;flex:0 0 auto;align-items:center;justify-content:center;border-radius:50%;background:#fff;border:none;color:#919191}.pecb-course-panel__disc svg{display:block}.pecb-course-panel__disc--plain{border:1.5px solid #ececec}.pecb-course-panel__disc--completed{background:#57bc57;color:#fff}.pecb-course-panel__disc--playing{background:var(--pecb-course-panel-accent, #212427);color:#fff;box-shadow:0 0 0 4px var(--pecb-course-panel-accent-soft, rgba(33, 36, 39, .27))}.pecb-course-panel__disc--started{background:#fff;color:#ff9c4a}.pecb-course-panel__disc--locked{color:#c2c2c2}.pecb-course-panel__ring{position:absolute;inset:0;transform:rotate(-90deg)}.pecb-course-panel__ring-track{stroke:#ececec}.pecb-course-panel__ring-value{stroke:#ff9c4a;transition:stroke-dashoffset .3s ease}.pecb-course-panel__disc-glyph{position:relative;display:flex;color:#ff9c4a}.pecb-course-panel__connector{flex:1 1 auto;width:2px;margin-top:2px;border-radius:2px;background:#ececec}.pecb-course-panel__connector--done{background:#57bc57}.pecb-course-panel__row-body{display:block;flex:1 1 auto;min-width:0}.pecb-course-panel__quiz-badge-line{display:flex;align-items:center;min-height:18px;margin-bottom:5px}.pecb-course-panel__quiz-badge{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:9999px;background:#8e6df0;color:#fff;font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:9.5px;font-weight:700;letter-spacing:.05em;text-transform:uppercase}.pecb-course-panel__row-title{display:block;margin-bottom:4px;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:var(--_title-size);font-weight:500;line-height:1.35;color:#212427;text-wrap:pretty;overflow-wrap:anywhere}.pecb-course-panel--compact .pecb-course-panel__row-title{margin-bottom:2px}.pecb-course-panel__row--active .pecb-course-panel__row-title{font-weight:600}.pecb-course-panel__row-meta{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.pecb-course-panel__meta-primary{display:inline-flex;align-items:center;gap:5px;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:11.5px;font-weight:500;color:#919191}.pecb-course-panel__row--quiz .pecb-course-panel__meta-primary{color:#8e6df0}.pecb-course-panel__meta-result{display:inline-flex;align-items:center;gap:4px;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:11px;font-weight:600;color:#ff9c4a}.pecb-course-panel__meta-result--passed{color:#57bc57}.pecb-course-panel__meta-time{display:inline-flex;align-items:center;gap:5px;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:11px;font-weight:600;color:#ff9c4a}.pecb-course-panel__row--playing .pecb-course-panel__meta-time{color:var(--pecb-course-panel-accent, #212427)}.pecb-course-panel__bar{display:block;margin-top:7px;height:4px;border-radius:4px;background:#ececec;overflow:hidden}.pecb-course-panel__bar-fill{display:block;height:100%;border-radius:4px;background:#ff9c4a;transition:width .2s linear}.pecb-course-panel__row--playing .pecb-course-panel__bar-fill{background:var(--pecb-course-panel-accent, #212427)}.pecb-course-panel__timeline{padding:6px 14px 16px}.pecb-course-panel--compact .pecb-course-panel__timeline{padding:4px 12px 12px}.pecb-course-panel__timeline-head{display:flex;align-items:center;gap:10px;padding:18px 2px 8px}.pecb-course-panel--compact .pecb-course-panel__timeline-head{padding:14px 2px 6px}.pecb-course-panel__timeline-label{font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:10.5px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;color:#919191}.pecb-course-panel__timeline-rule{flex:1 1 auto;height:1px;background:var(--pecb-course-panel-border, #ececec)}.pecb-course-panel__timeline-count{font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:11px;color:#919191}.pecb-course-panel__transcript{padding:0 14px}.pecb-course-panel__cues{padding:8px 6px 20px}.pecb-course-panel__cue{border:none;background:none;font:inherit;color:inherit;display:flex;gap:14px;width:100%;padding:10px 12px;border-radius:8px;text-align:left;background:transparent;cursor:pointer;transition:background .14s ease}.pecb-course-panel__cue:focus{outline:none}.pecb-course-panel__cue:hover:not(.pecb-course-panel__cue--current){background:#fafafa}.pecb-course-panel__cue:focus-visible{outline:2px solid #a11e29;outline-offset:-2px}.pecb-course-panel__cue--current{background:#fbeaec}.pecb-course-panel__cue-time{flex:0 0 auto;width:38px;padding-top:1px;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:12px;font-weight:600;font-variant-numeric:tabular-nums;color:#919191}.pecb-course-panel__cue--current .pecb-course-panel__cue-time{color:#a11e29}.pecb-course-panel__cue-text{font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:13.5px;line-height:1.55;color:#313131;text-wrap:pretty;overflow-wrap:anywhere}.pecb-course-panel__cue--current .pecb-course-panel__cue-text{color:#212427}.pecb-course-panel__mark{padding:0 1px;border-radius:3px;background:#fce3b4;color:inherit}.pecb-course-panel__footer{flex:0 0 auto;padding:12px 20px;border-top:1px solid var(--pecb-course-panel-border, #ececec);background:var(--pecb-course-panel-bg, #ffffff)}.pecb-course-panel__autoscroll{display:flex;align-items:center;gap:10px;cursor:pointer;-webkit-user-select:none;user-select:none}.pecb-course-panel__autoscroll-input{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none}.pecb-course-panel__autoscroll-box{display:flex;flex:0 0 auto;align-items:center;justify-content:center;width:18px;height:18px;border:1.5px solid #c2c2c2;border-radius:5px;background:#fff;color:#fff;transition:all .14s ease}.pecb-course-panel__autoscroll-input:checked+.pecb-course-panel__autoscroll-box{border:1px solid #a11e29;background:#a11e29}.pecb-course-panel__autoscroll-input:focus-visible+.pecb-course-panel__autoscroll-box{outline:2px solid #a11e29;outline-offset:2px}.pecb-course-panel__autoscroll-label{font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:13px;font-weight:500;color:#616161}@container pecb-course-panel (max-width: 380px){.pecb-course-panel{--_row-pad-right: 12px}.pecb-course-panel__tabs{padding:0 12px}.pecb-course-panel__tab{padding:12px 0 10px;font-size:13px}.pecb-course-panel__search{padding:10px 12px 8px}.pecb-course-panel__progress{flex-wrap:wrap;gap:12px;padding:12px 16px}.pecb-course-panel__section-head{gap:10px;padding:12px}.pecb-course-panel__section-body{padding-right:8px}.pecb-course-panel__transcript{padding:0 8px}.pecb-course-panel__cue{gap:10px;padding:8px 10px}.pecb-course-panel__cue-text{font-size:13px}.pecb-course-panel__footer{padding:10px 16px}.pecb-course-panel__timeline{padding:4px 10px 12px}}@media (hover: none) and (pointer: coarse){.pecb-course-panel__row,.pecb-course-panel__cue{min-height:44px}}@media (prefers-reduced-motion: reduce){.pecb-course-panel *,.pecb-course-panel *:before,.pecb-course-panel *:after{transition-duration:.01ms!important;animation-duration:.01ms!important}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
12269
+ }
12270
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: CourseContentPanelComponent, decorators: [{
12271
+ type: Component,
12272
+ args: [{ selector: 'pecb-course-content-panel', standalone: true, imports: [NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'pecb-course-panel-host' }, template: "<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n Lesson row \u2014 one <button> per lesson, shared by the grouped, parts-rail,\n timeline and solo renderings.\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n<ng-template #lessonRowTpl let-vm let-solo=\"solo\">\n <button\n type=\"button\"\n class=\"pecb-course-panel__row\"\n [class.pecb-course-panel__row--active]=\"vm.active\"\n [class.pecb-course-panel__row--quiz]=\"vm.isQuiz\"\n [class.pecb-course-panel__row--playing]=\"vm.isPlaying\"\n [class.pecb-course-panel__row--started]=\"vm.isStarted\"\n [class.pecb-course-panel__row--completed]=\"vm.isCompleted\"\n [class.pecb-course-panel__row--locked]=\"vm.isLocked\"\n [class.pecb-course-panel__row--solo]=\"!!solo\"\n [disabled]=\"vm.isLocked\"\n [attr.aria-current]=\"vm.active ? 'true' : null\"\n [attr.aria-label]=\"vm.ariaLabel\"\n (click)=\"selectLesson(vm.lesson)\"\n >\n @if (vm.active && vm.isPlaying) {\n <span class=\"pecb-course-panel__row-marker\" aria-hidden=\"true\"></span>\n }\n\n <!-- Status disc (+ optional timeline connector) -->\n <span class=\"pecb-course-panel__disc-col\">\n <span\n class=\"pecb-course-panel__disc\"\n [class.pecb-course-panel__disc--completed]=\"vm.isCompleted\"\n [class.pecb-course-panel__disc--playing]=\"vm.isPlaying\"\n [class.pecb-course-panel__disc--started]=\"vm.isStarted\"\n [class.pecb-course-panel__disc--locked]=\"vm.isLocked\"\n [class.pecb-course-panel__disc--plain]=\"!vm.isCompleted && !vm.isPlaying && !vm.isStarted\"\n [style.width.px]=\"vm.discSize\"\n [style.height.px]=\"vm.discSize\"\n aria-hidden=\"true\"\n >\n @if (vm.isCompleted) {\n <svg [attr.width]=\"vm.checkSize\" [attr.height]=\"vm.checkSize\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n } @else if (vm.isPlaying) {\n <svg [attr.width]=\"vm.pauseSize\" [attr.height]=\"vm.pauseSize\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <rect x=\"6\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n <rect x=\"14\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n </svg>\n } @else if (vm.isStarted) {\n <svg class=\"pecb-course-panel__ring\" [attr.width]=\"vm.discSize\" [attr.height]=\"vm.discSize\" [attr.viewBox]=\"vm.ringViewBox\">\n <circle\n class=\"pecb-course-panel__ring-track\"\n [attr.cx]=\"vm.discCenter\" [attr.cy]=\"vm.discCenter\" [attr.r]=\"vm.ringRadius\"\n fill=\"none\" stroke-width=\"3\" />\n <circle\n class=\"pecb-course-panel__ring-value\"\n [attr.cx]=\"vm.discCenter\" [attr.cy]=\"vm.discCenter\" [attr.r]=\"vm.ringRadius\"\n fill=\"none\" stroke-width=\"3\" stroke-linecap=\"round\"\n [attr.stroke-dasharray]=\"vm.ringCircumference\"\n [attr.stroke-dashoffset]=\"vm.ringOffset\" />\n </svg>\n <span class=\"pecb-course-panel__disc-glyph\">\n @if (vm.isQuiz) {\n <svg [attr.width]=\"vm.quizIconSize\" [attr.height]=\"vm.quizIconSize\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <path d=\"M9.5 9.2a2.5 2.5 0 1 1 3.4 2.3c-.6.3-.9.7-.9 1.4v.4\" />\n <circle cx=\"12\" cy=\"16.3\" r=\".6\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n } @else {\n <svg [attr.width]=\"vm.playIconSize\" [attr.height]=\"vm.playIconSize\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <polygon points=\"6 4 20 12 6 20 6 4\" />\n </svg>\n }\n </span>\n } @else if (vm.isLocked) {\n <svg [attr.width]=\"vm.checkSize\" [attr.height]=\"vm.checkSize\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"5\" y=\"11\" width=\"14\" height=\"9\" rx=\"2\" />\n <path d=\"M8 11V8a4 4 0 0 1 8 0v3\" />\n </svg>\n } @else if (vm.isQuiz) {\n <svg [attr.width]=\"vm.quizIconSize\" [attr.height]=\"vm.quizIconSize\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <path d=\"M9.5 9.2a2.5 2.5 0 1 1 3.4 2.3c-.6.3-.9.7-.9 1.4v.4\" />\n <circle cx=\"12\" cy=\"16.3\" r=\".6\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n } @else {\n <svg [attr.width]=\"vm.playIconSize\" [attr.height]=\"vm.playIconSize\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <polygon points=\"6 4 20 12 6 20 6 4\" />\n </svg>\n }\n </span>\n\n @if (vm.connector) {\n <span\n class=\"pecb-course-panel__connector\"\n [class.pecb-course-panel__connector--done]=\"vm.connectorDone\"\n aria-hidden=\"true\"\n ></span>\n }\n </span>\n\n <!-- Body -->\n <span class=\"pecb-course-panel__row-body\">\n @if (vm.isQuiz) {\n <span class=\"pecb-course-panel__quiz-badge-line\">\n <span class=\"pecb-course-panel__quiz-badge\">{{ text().quiz }}</span>\n </span>\n }\n\n @if (vm.displayTitle) {\n <span class=\"pecb-course-panel__row-title\">{{ vm.displayTitle }}</span>\n }\n\n <span class=\"pecb-course-panel__row-meta\">\n <span class=\"pecb-course-panel__meta-primary\">\n @if (vm.isQuiz) {\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <path d=\"M9.5 9.2a2.5 2.5 0 1 1 3.4 2.3c-.6.3-.9.7-.9 1.4v.4\" />\n <circle cx=\"12\" cy=\"16.3\" r=\".6\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n {{ vm.questionsLabel }}\n } @else {\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <polyline points=\"12 7 12 12 15.5 14\" />\n </svg>\n {{ vm.durationLabel }}\n }\n </span>\n\n @if (vm.quizResultLabel) {\n <span\n class=\"pecb-course-panel__meta-result\"\n [class.pecb-course-panel__meta-result--passed]=\"vm.isCompleted\"\n >\n @if (vm.isCompleted) {\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n }\n {{ vm.quizResultLabel }}\n </span>\n }\n\n @if (vm.timeLabel) {\n <span class=\"pecb-course-panel__meta-time\">\n @if (vm.isStarted) {\n <svg width=\"10\" height=\"10\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <polygon points=\"6 4 20 12 6 20 6 4\" />\n </svg>\n }\n {{ vm.timeLabel }}\n </span>\n }\n </span>\n\n @if (vm.showBar) {\n <span class=\"pecb-course-panel__bar\" aria-hidden=\"true\">\n <span class=\"pecb-course-panel__bar-fill\" [style.width.%]=\"vm.barPercent\"></span>\n </span>\n }\n </span>\n\n @if (solo) {\n <span class=\"pecb-course-panel__solo-play\" aria-hidden=\"true\">\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <polygon points=\"6 4 20 12 6 20 6 4\" />\n </svg>\n </span>\n }\n </button>\n</ng-template>\n\n<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n Panel\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n<div [class]=\"hostClasses()\">\n @if (showTabs()) {\n <div class=\"pecb-course-panel__tabs\" role=\"tablist\" [attr.aria-label]=\"text().lessonList\">\n <button\n type=\"button\"\n role=\"tab\"\n class=\"pecb-course-panel__tab\"\n [class.pecb-course-panel__tab--active]=\"tab() === 'content'\"\n [id]=\"uid + '-tab-content'\"\n [attr.aria-selected]=\"tab() === 'content'\"\n [attr.aria-controls]=\"uid + '-panel-content'\"\n [attr.tabindex]=\"tab() === 'content' ? 0 : -1\"\n (click)=\"setTab('content')\"\n (keydown)=\"onTabKeydown($event)\"\n >\n {{ text().contentTab }}\n @if (tab() === 'content') {\n <span class=\"pecb-course-panel__tab-indicator\" aria-hidden=\"true\"></span>\n }\n </button>\n <button\n type=\"button\"\n role=\"tab\"\n class=\"pecb-course-panel__tab\"\n [class.pecb-course-panel__tab--active]=\"tab() === 'transcript'\"\n [id]=\"uid + '-tab-transcript'\"\n [attr.aria-selected]=\"tab() === 'transcript'\"\n [attr.aria-controls]=\"uid + '-panel-transcript'\"\n [attr.tabindex]=\"tab() === 'transcript' ? 0 : -1\"\n (click)=\"setTab('transcript')\"\n (keydown)=\"onTabKeydown($event)\"\n >\n {{ text().transcriptTab }}\n @if (tab() === 'transcript') {\n <span class=\"pecb-course-panel__tab-indicator\" aria-hidden=\"true\"></span>\n }\n </button>\n </div>\n }\n\n <!-- \u2500\u2500 Content tab \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (tab() === 'content') {\n <div\n class=\"pecb-course-panel__body\"\n role=\"tabpanel\"\n [id]=\"uid + '-panel-content'\"\n [attr.aria-labelledby]=\"showTabs() ? uid + '-tab-content' : null\"\n >\n @if (showProgress()) {\n <div class=\"pecb-course-panel__progress\">\n <span class=\"pecb-course-panel__progress-ring\">\n <svg width=\"56\" height=\"56\" viewBox=\"0 0 56 56\" aria-hidden=\"true\">\n <circle class=\"pecb-course-panel__progress-track\" cx=\"28\" cy=\"28\" [attr.r]=\"progressRing().radius\" fill=\"none\" stroke-width=\"5\" />\n <circle\n class=\"pecb-course-panel__progress-value\"\n cx=\"28\" cy=\"28\" [attr.r]=\"progressRing().radius\" fill=\"none\" stroke-width=\"5\" stroke-linecap=\"round\"\n [attr.stroke-dasharray]=\"progressRing().circumference\"\n [attr.stroke-dashoffset]=\"progressRing().offset\" />\n </svg>\n <span class=\"pecb-course-panel__progress-percent\">{{ progress().percent }}%</span>\n </span>\n <span class=\"pecb-course-panel__progress-text\">\n <span class=\"pecb-course-panel__progress-title\">{{ text().courseProgress }}</span>\n <span class=\"pecb-course-panel__progress-detail\">{{ progressDetail() }}</span>\n </span>\n @if (showCertificate()) {\n <button type=\"button\" class=\"pecb-course-panel__certificate\" (click)=\"certificateClick.emit()\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"8\" r=\"5\" />\n <path d=\"M8.5 12.5 7 21l5-2.5L17 21l-1.5-8.5\" />\n </svg>\n {{ text().certificate }}\n </button>\n }\n </div>\n }\n\n @if (showSearch()) {\n <div class=\"pecb-course-panel__search\">\n <div class=\"pecb-course-panel__search-box\">\n <span class=\"pecb-course-panel__search-icon\" aria-hidden=\"true\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"7\" />\n <path d=\"m20 20-3.2-3.2\" />\n </svg>\n </span>\n <input\n type=\"search\"\n class=\"pecb-course-panel__search-input\"\n [value]=\"query()\"\n [attr.placeholder]=\"text().searchLessons\"\n [attr.aria-label]=\"text().searchLessons\"\n (input)=\"onQueryInput($event)\"\n />\n </div>\n </div>\n }\n\n <div class=\"pecb-course-panel__scroll pecb-course-panel__list\">\n @if (isEmpty()) {\n <p class=\"pecb-course-panel__empty\">{{ noLessonsLabel() }}</p>\n } @else if (layout() === 'timeline') {\n <div class=\"pecb-course-panel__timeline\">\n @for (row of timelineRows(); track row.key) {\n @if (row.header; as header) {\n <div class=\"pecb-course-panel__timeline-head\">\n <span class=\"pecb-course-panel__timeline-label\">{{ sectionLabel(header.number) }}</span>\n <span class=\"pecb-course-panel__timeline-rule\" aria-hidden=\"true\"></span>\n <span class=\"pecb-course-panel__timeline-count\">{{ header.stats.done }}/{{ header.stats.total }}</span>\n </div>\n } @else {\n <ng-container *ngTemplateOutlet=\"lessonRowTpl; context: { $implicit: row.lesson }\"></ng-container>\n }\n }\n </div>\n } @else {\n @for (view of moduleViews(); track view.id) {\n @if (view.solo; as soloView) {\n <div class=\"pecb-course-panel__section pecb-course-panel__section--solo\">\n <ng-container\n *ngTemplateOutlet=\"lessonRowTpl; context: { $implicit: soloView, solo: true }\"\n ></ng-container>\n </div>\n } @else {\n <div\n class=\"pecb-course-panel__section\"\n [class.pecb-course-panel__section--open]=\"isModuleOpen(view.id)\"\n >\n <button\n type=\"button\"\n class=\"pecb-course-panel__section-head\"\n [class.pecb-course-panel__section-head--open]=\"isModuleOpen(view.id)\"\n [id]=\"uid + '-head-' + view.id\"\n [attr.aria-expanded]=\"isModuleOpen(view.id)\"\n [attr.aria-controls]=\"uid + '-region-' + view.id\"\n (click)=\"toggleModule(view)\"\n (keydown)=\"onModuleHeaderKeydown($event)\"\n >\n <span\n class=\"pecb-course-panel__section-badge\"\n [class.pecb-course-panel__section-badge--done]=\"view.allDone\"\n [class.pecb-course-panel__section-badge--active]=\"!view.allDone && (view.stats.playing || view.stats.started)\"\n aria-hidden=\"true\"\n >\n @if (view.allDone) {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n } @else if (view.stats.playing) {\n <svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <rect x=\"6\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n <rect x=\"14\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n </svg>\n } @else {\n <svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <polygon points=\"6 4 20 12 6 20 6 4\" />\n </svg>\n }\n </span>\n\n <span class=\"pecb-course-panel__section-text\">\n <span class=\"pecb-course-panel__section-title\">{{ sectionLabel(view.number) }}</span>\n <span class=\"pecb-course-panel__section-meta\">\n <span>{{ view.statsLabel }}</span>\n @if (view.stats.durationSeconds > 0) {\n <span class=\"pecb-course-panel__section-duration\">\n <svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <polyline points=\"12 7 12 12 15.5 14\" />\n </svg>\n {{ view.durationLabel }}\n </span>\n }\n </span>\n </span>\n\n <span class=\"pecb-course-panel__section-chevron\" aria-hidden=\"true\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"6 9 12 15 18 9\" />\n </svg>\n </span>\n </button>\n\n <div class=\"pecb-course-panel__section-wrap\">\n <div\n class=\"pecb-course-panel__section-clip\"\n [id]=\"uid + '-region-' + view.id\"\n role=\"region\"\n [attr.aria-labelledby]=\"uid + '-head-' + view.id\"\n [attr.inert]=\"isModuleOpen(view.id) ? null : ''\"\n >\n <div class=\"pecb-course-panel__section-body\">\n @for (run of view.runs; track run.key) {\n @if (run.parts) {\n <p class=\"pecb-course-panel__run-title\">{{ run.base }}</p>\n <div class=\"pecb-course-panel__rail\">\n <span class=\"pecb-course-panel__rail-line\" aria-hidden=\"true\"></span>\n @for (lessonView of run.lessons; track lessonView.id) {\n <ng-container *ngTemplateOutlet=\"lessonRowTpl; context: { $implicit: lessonView }\"></ng-container>\n }\n </div>\n } @else {\n @for (lessonView of run.lessons; track lessonView.id) {\n <ng-container *ngTemplateOutlet=\"lessonRowTpl; context: { $implicit: lessonView }\"></ng-container>\n }\n }\n }\n </div>\n </div>\n </div>\n </div>\n }\n }\n }\n </div>\n </div>\n } @else {\n <!-- \u2500\u2500 Transcript tab \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div\n class=\"pecb-course-panel__body\"\n role=\"tabpanel\"\n [id]=\"uid + '-panel-transcript'\"\n [attr.aria-labelledby]=\"showTabs() ? uid + '-tab-transcript' : null\"\n >\n @if (showTranscriptSearch()) {\n <div class=\"pecb-course-panel__search\">\n <div class=\"pecb-course-panel__search-box\">\n <span class=\"pecb-course-panel__search-icon\" aria-hidden=\"true\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"7\" />\n <path d=\"m20 20-3.2-3.2\" />\n </svg>\n </span>\n <input\n type=\"search\"\n class=\"pecb-course-panel__search-input\"\n [value]=\"transcriptQuery()\"\n [attr.placeholder]=\"text().searchTranscript\"\n [attr.aria-label]=\"text().searchTranscript\"\n (input)=\"onTranscriptQueryInput($event)\"\n />\n @if (transcriptQuery()) {\n <button\n type=\"button\"\n class=\"pecb-course-panel__search-clear\"\n [attr.aria-label]=\"text().clearSearch\"\n (click)=\"clearTranscriptQuery()\"\n >\u00D7</button>\n }\n </div>\n </div>\n }\n\n <div class=\"pecb-course-panel__scroll pecb-course-panel__transcript\" #transcriptScroll>\n @if (transcriptEmpty()) {\n <p class=\"pecb-course-panel__empty\">{{ noTranscriptLabel() }}</p>\n } @else {\n <div class=\"pecb-course-panel__cues\">\n @for (row of transcriptRows(); track row.key) {\n <button\n type=\"button\"\n class=\"pecb-course-panel__cue\"\n [class.pecb-course-panel__cue--current]=\"row.current\"\n [attr.data-current]=\"row.current\"\n [attr.aria-current]=\"row.current ? 'true' : null\"\n (click)=\"seekToCue(row.cue)\"\n >\n <span class=\"pecb-course-panel__cue-time\">{{ row.timeLabel }}</span>\n <span class=\"pecb-course-panel__cue-text\">{{ row.before }}@if (row.match) {<mark class=\"pecb-course-panel__mark\">{{ row.match }}</mark>}{{ row.after }}</span>\n </button>\n }\n </div>\n }\n </div>\n\n @if (showAutoscroll()) {\n <div class=\"pecb-course-panel__footer\">\n <label class=\"pecb-course-panel__autoscroll\">\n <input\n type=\"checkbox\"\n class=\"pecb-course-panel__autoscroll-input\"\n [checked]=\"autoscroll()\"\n (change)=\"onAutoscrollChange($event)\"\n />\n <span class=\"pecb-course-panel__autoscroll-box\" aria-hidden=\"true\">\n @if (autoscroll()) {\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n }\n </span>\n <span class=\"pecb-course-panel__autoscroll-label\">{{ text().autoscroll }}</span>\n </label>\n </div>\n }\n </div>\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%;min-height:0;container-type:inline-size;container-name:pecb-course-panel}.pecb-course-panel{--_row-pad-y: 11px;--_row-pad-left: 14px;--_row-pad-right: 16px;--_row-gap: 13px;--_disc: 30px;--_section-pad-y: 15px;--_title-size: 13.5px;display:flex;flex-direction:column;height:100%;min-height:0;box-sizing:border-box;background:var(--pecb-course-panel-bg, #ffffff);font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);line-height:normal;color:#212427;overflow:hidden}.pecb-course-panel *,.pecb-course-panel *:before,.pecb-course-panel *:after{box-sizing:border-box}.pecb-course-panel--compact{--_row-pad-y: 8px;--_row-pad-left: 12px;--_row-pad-right: 14px;--_row-gap: 11px;--_disc: 26px;--_section-pad-y: 12px;--_title-size: 12.5px}.pecb-course-panel--accent-red{--pecb-course-panel-accent: #a11e29;--pecb-course-panel-accent-soft: rgba(161, 30, 41, .27)}.pecb-course-panel__tabs{display:flex;flex:0 0 auto;padding:0 20px;border-bottom:1px solid var(--pecb-course-panel-border, #ececec)}.pecb-course-panel__tab{border:none;background:none;font:inherit;color:inherit;position:relative;flex:1;padding:16px 0 14px;font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:14px;font-weight:500;color:#616161;cursor:pointer;transition:color .15s ease-in-out}.pecb-course-panel__tab:focus{outline:none}.pecb-course-panel__tab:hover{color:#212427}.pecb-course-panel__tab:focus-visible{outline:2px solid #a11e29;outline-offset:-3px;border-radius:6px}.pecb-course-panel__tab--active{font-weight:700;color:#a11e29}.pecb-course-panel__tab-indicator{position:absolute;left:0;right:0;bottom:-1px;height:2.5px;border-radius:3px;background:#a11e29}.pecb-course-panel__body{display:flex;flex:1 1 auto;flex-direction:column;min-height:0}.pecb-course-panel__scroll{flex:1 1 auto;min-height:0;overflow-y:auto;overflow-x:hidden;overscroll-behavior:contain;scrollbar-width:thin;scrollbar-color:#ececec transparent}.pecb-course-panel__scroll::-webkit-scrollbar{width:8px}.pecb-course-panel__scroll::-webkit-scrollbar-thumb{background:#ececec;border-radius:8px}.pecb-course-panel__scroll::-webkit-scrollbar-track{background:transparent}.pecb-course-panel__empty{margin:0;padding:40px;text-align:center;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:13px;color:#919191}.pecb-course-panel__progress{display:flex;flex:0 0 auto;align-items:center;gap:16px;padding:16px 20px;border-bottom:1px solid var(--pecb-course-panel-border, #ececec)}.pecb-course-panel__progress-ring{position:relative;flex:0 0 auto;width:56px;height:56px}.pecb-course-panel__progress-ring svg{display:block;transform:rotate(-90deg)}.pecb-course-panel__progress-track{stroke:#ececec}.pecb-course-panel__progress-value{stroke:var(--pecb-course-panel-accent, #212427);transition:stroke-dashoffset .5s ease}.pecb-course-panel__progress-percent{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:14px;font-weight:700;color:#212427}.pecb-course-panel__progress-text{display:flex;flex:1 1 auto;flex-direction:column;min-width:0}.pecb-course-panel__progress-title{font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:14px;font-weight:700;color:#212427}.pecb-course-panel__progress-detail{margin-top:3px;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:12px;color:#616161}.pecb-course-panel__certificate{border:none;background:none;font:inherit;color:inherit;display:inline-flex;flex:0 0 auto;align-items:center;gap:6px;padding:6px 12px;border-radius:9999px;background:#212427;color:#fff;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:12px;font-weight:600;cursor:pointer;transition:background .15s ease-in-out}.pecb-course-panel__certificate:focus{outline:none}.pecb-course-panel__certificate:hover{background:#313131}.pecb-course-panel__certificate:focus-visible{outline:2px solid #a11e29;outline-offset:2px}.pecb-course-panel__search{flex:0 0 auto;padding:12px 20px 10px}.pecb-course-panel__search-box{display:flex;align-items:center;gap:9px;min-height:40px;padding:9px 12px;border:1px solid #c2c2c2;border-radius:6px;background:#fff;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}.pecb-course-panel__search-box:focus-within{border-color:#212427;box-shadow:0 0 0 3px #21242714}.pecb-course-panel__search-icon{display:flex;flex:0 0 auto;color:#919191}.pecb-course-panel__search-input{flex:1 1 auto;min-width:0;border:none;outline:none;background:transparent;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:13px;color:#212427;appearance:none}.pecb-course-panel__search-input::placeholder{color:#919191}.pecb-course-panel__search-input::-webkit-search-cancel-button,.pecb-course-panel__search-input::-webkit-search-decoration{appearance:none;display:none}.pecb-course-panel__search-clear{padding:0;border:none;background:none;font:inherit;color:inherit;display:flex;flex:0 0 auto;align-items:center;justify-content:center;width:18px;height:18px;border-radius:50%;color:#919191;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:17px;line-height:1;cursor:pointer}.pecb-course-panel__search-clear:focus{outline:none}.pecb-course-panel__search-clear:hover{background:#f4f4f4;color:#212427}.pecb-course-panel__search-clear:focus-visible{outline:2px solid #a11e29;outline-offset:1px}.pecb-course-panel__section{border-bottom:1px solid var(--pecb-course-panel-border, #ececec);background:transparent}.pecb-course-panel__section--open{background:#fff}.pecb-course-panel__section--solo{border-bottom:1px solid var(--pecb-course-panel-border, #ececec)}.pecb-course-panel__section-head{padding:0;border:none;background:none;font:inherit;color:inherit;display:flex;align-items:center;gap:12px;width:100%;padding:var(--_section-pad-y) 16px;text-align:left;background:#f4f4f4;cursor:pointer;transition:background .15s ease-in-out}.pecb-course-panel__section-head:focus{outline:none}.pecb-course-panel__section-head:hover{background:#fff}.pecb-course-panel__section-head:focus-visible{outline:2px solid #a11e29;outline-offset:-2px}.pecb-course-panel__section-head--open{background:#ececec}.pecb-course-panel__section-head--open:hover{background:#f4f4f4}.pecb-course-panel__section-badge{display:flex;flex:0 0 auto;align-items:center;justify-content:center;width:30px;height:30px;border-radius:8px;background:#c2c2c2;color:#fff;font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:13px;font-weight:700}.pecb-course-panel__section-badge--active{background:var(--pecb-course-panel-accent, #212427)}.pecb-course-panel__section-badge--done{background:#e6f4ea;color:green}.pecb-course-panel__section-text{display:flex;flex:1 1 auto;flex-direction:column;min-width:0}.pecb-course-panel__section-title{font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:13.5px;font-weight:600;line-height:1.3;color:#212427}.pecb-course-panel__section-meta{display:flex;align-items:center;gap:10px;margin-top:4px;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:11.5px;color:#919191}.pecb-course-panel__section-duration{display:inline-flex;align-items:center;gap:4px}.pecb-course-panel__section-chevron{display:flex;flex:0 0 auto;color:#919191;transform:rotate(-90deg);transition:transform .18s ease}.pecb-course-panel__section-head--open .pecb-course-panel__section-chevron{transform:rotate(0)}.pecb-course-panel__section-wrap{display:grid;grid-template-rows:0fr;transition:grid-template-rows .22s ease}.pecb-course-panel__section--open .pecb-course-panel__section-wrap{grid-template-rows:1fr}.pecb-course-panel__section-clip{overflow:hidden;min-height:0}.pecb-course-panel__section-body{display:flex;flex-direction:column;gap:2px;padding:6px 12px 10px var(--_row-pad-left)}.pecb-course-panel--compact .pecb-course-panel__section-body{padding:4px 10px 8px var(--_row-pad-left)}.pecb-course-panel__run-title{margin:0;padding:9px 8px 5px 12px;font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:13px;font-weight:600;line-height:1.35;color:#212427;text-wrap:pretty}.pecb-course-panel__rail{position:relative}.pecb-course-panel__rail-line{position:absolute;left:calc(var(--_row-pad-left) + var(--_disc) / 2 - 1px);top:calc(var(--_row-pad-y) + var(--_disc) / 2);bottom:calc(var(--_row-pad-y) + var(--_disc) / 2);z-index:1;width:2px;border-radius:2px;background:#ececec}.pecb-course-panel__row{padding:0;border:none;background:none;font:inherit;color:inherit;position:relative;display:flex;align-items:center;gap:var(--_row-gap);width:100%;padding:var(--_row-pad-y) var(--_row-pad-right) var(--_row-pad-y) var(--_row-pad-left);border-radius:8px;text-align:left;background:transparent;cursor:pointer;transition:background .14s ease,box-shadow .14s ease}.pecb-course-panel__row:focus{outline:none}.pecb-course-panel__row:hover:not(:disabled):not(.pecb-course-panel__row--active){background:#fafafa}.pecb-course-panel__row:focus-visible{outline:2px solid #a11e29;outline-offset:-2px}.pecb-course-panel__row:disabled{cursor:not-allowed}.pecb-course-panel__row--locked{opacity:.72}.pecb-course-panel__row--active{background:#fff;box-shadow:inset 0 0 0 1px var(--pecb-course-panel-border, #ececec)}.pecb-course-panel__row--active.pecb-course-panel__row--completed{background:#e6f4ea}.pecb-course-panel__row--active.pecb-course-panel__row--started{background:#fff4e5}.pecb-course-panel__row--active.pecb-course-panel__row--playing{background:#fbeaec;box-shadow:inset 0 0 0 1px var(--pecb-course-panel-accent-soft, rgba(33, 36, 39, .27))}.pecb-course-panel__row--active.pecb-course-panel__row--quiz{background:#eee8ff}.pecb-course-panel__row--solo{gap:12px;padding:var(--_section-pad-y) 16px;border-radius:0}.pecb-course-panel__row-marker{position:absolute;left:0;top:8px;bottom:8px;width:3px;border-radius:3px;background:var(--pecb-course-panel-accent, #212427)}.pecb-course-panel__row--solo .pecb-course-panel__row-marker{top:10px;bottom:10px}.pecb-course-panel__row--solo .pecb-course-panel__row-title{font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:13.5px;font-weight:600;line-height:1.3}.pecb-course-panel__solo-play{display:flex;flex:0 0 auto;align-items:center;justify-content:center;width:30px;height:30px;border-radius:50%;background:#f4f4f4;color:#616161}.pecb-course-panel__disc-col{position:relative;display:flex;flex:0 0 auto;flex-direction:column;align-items:center;align-self:stretch}.pecb-course-panel__disc{position:relative;z-index:2;display:flex;flex:0 0 auto;align-items:center;justify-content:center;border-radius:50%;background:#fff;border:none;color:#919191}.pecb-course-panel__disc svg{display:block}.pecb-course-panel__disc--plain{border:1.5px solid #ececec}.pecb-course-panel__disc--completed{background:#57bc57;color:#fff}.pecb-course-panel__disc--playing{background:var(--pecb-course-panel-accent, #212427);color:#fff;box-shadow:0 0 0 4px var(--pecb-course-panel-accent-soft, rgba(33, 36, 39, .27))}.pecb-course-panel__disc--started{background:#fff;color:#ff9c4a}.pecb-course-panel__disc--locked{color:#c2c2c2}.pecb-course-panel__ring{position:absolute;inset:0;transform:rotate(-90deg)}.pecb-course-panel__ring-track{stroke:#ececec}.pecb-course-panel__ring-value{stroke:#ff9c4a;transition:stroke-dashoffset .3s ease}.pecb-course-panel__disc-glyph{position:relative;display:flex;color:#ff9c4a}.pecb-course-panel__connector{flex:1 1 auto;width:2px;margin-top:2px;border-radius:2px;background:#ececec}.pecb-course-panel__connector--done{background:#57bc57}.pecb-course-panel__row-body{display:block;flex:1 1 auto;min-width:0}.pecb-course-panel__quiz-badge-line{display:flex;align-items:center;min-height:18px;margin-bottom:5px}.pecb-course-panel__quiz-badge{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:9999px;background:#8e6df0;color:#fff;font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:9.5px;font-weight:700;letter-spacing:.05em;text-transform:uppercase}.pecb-course-panel__row-title{display:block;margin-bottom:4px;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:var(--_title-size);font-weight:500;line-height:1.35;color:#212427;text-wrap:pretty;overflow-wrap:anywhere}.pecb-course-panel--compact .pecb-course-panel__row-title{margin-bottom:2px}.pecb-course-panel__row--active .pecb-course-panel__row-title{font-weight:600}.pecb-course-panel__row-meta{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.pecb-course-panel__meta-primary{display:inline-flex;align-items:center;gap:5px;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:11.5px;font-weight:500;color:#919191}.pecb-course-panel__row--quiz .pecb-course-panel__meta-primary{color:#8e6df0}.pecb-course-panel__meta-result{display:inline-flex;align-items:center;gap:4px;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:11px;font-weight:600;color:#ff9c4a}.pecb-course-panel__meta-result--passed{color:#57bc57}.pecb-course-panel__meta-time{display:inline-flex;align-items:center;gap:5px;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:11px;font-weight:600;color:#ff9c4a}.pecb-course-panel__row--playing .pecb-course-panel__meta-time{color:var(--pecb-course-panel-accent, #212427)}.pecb-course-panel__bar{display:block;margin-top:7px;height:4px;border-radius:4px;background:#ececec;overflow:hidden}.pecb-course-panel__bar-fill{display:block;height:100%;border-radius:4px;background:#ff9c4a;transition:width .2s linear}.pecb-course-panel__row--playing .pecb-course-panel__bar-fill{background:var(--pecb-course-panel-accent, #212427)}.pecb-course-panel__timeline{padding:6px 14px 16px}.pecb-course-panel--compact .pecb-course-panel__timeline{padding:4px 12px 12px}.pecb-course-panel__timeline-head{display:flex;align-items:center;gap:10px;padding:18px 2px 8px}.pecb-course-panel--compact .pecb-course-panel__timeline-head{padding:14px 2px 6px}.pecb-course-panel__timeline-label{font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:10.5px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;color:#919191}.pecb-course-panel__timeline-rule{flex:1 1 auto;height:1px;background:var(--pecb-course-panel-border, #ececec)}.pecb-course-panel__timeline-count{font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:11px;color:#919191}.pecb-course-panel__transcript{padding:0 14px}.pecb-course-panel__cues{padding:8px 6px 20px}.pecb-course-panel__cue{border:none;background:none;font:inherit;color:inherit;display:flex;gap:14px;width:100%;padding:10px 12px;border-radius:8px;text-align:left;background:transparent;cursor:pointer;transition:background .14s ease}.pecb-course-panel__cue:focus{outline:none}.pecb-course-panel__cue:hover:not(.pecb-course-panel__cue--current){background:#fafafa}.pecb-course-panel__cue:focus-visible{outline:2px solid #a11e29;outline-offset:-2px}.pecb-course-panel__cue--current{background:#fbeaec}.pecb-course-panel__cue-time{flex:0 0 auto;width:38px;padding-top:1px;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:12px;font-weight:600;font-variant-numeric:tabular-nums;color:#919191}.pecb-course-panel__cue--current .pecb-course-panel__cue-time{color:#a11e29}.pecb-course-panel__cue-text{font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:13.5px;line-height:1.55;color:#313131;text-wrap:pretty;overflow-wrap:anywhere}.pecb-course-panel__cue--current .pecb-course-panel__cue-text{color:#212427}.pecb-course-panel__mark{padding:0 1px;border-radius:3px;background:#fce3b4;color:inherit}.pecb-course-panel__footer{flex:0 0 auto;padding:12px 20px;border-top:1px solid var(--pecb-course-panel-border, #ececec);background:var(--pecb-course-panel-bg, #ffffff)}.pecb-course-panel__autoscroll{display:flex;align-items:center;gap:10px;cursor:pointer;-webkit-user-select:none;user-select:none}.pecb-course-panel__autoscroll-input{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none}.pecb-course-panel__autoscroll-box{display:flex;flex:0 0 auto;align-items:center;justify-content:center;width:18px;height:18px;border:1.5px solid #c2c2c2;border-radius:5px;background:#fff;color:#fff;transition:all .14s ease}.pecb-course-panel__autoscroll-input:checked+.pecb-course-panel__autoscroll-box{border:1px solid #a11e29;background:#a11e29}.pecb-course-panel__autoscroll-input:focus-visible+.pecb-course-panel__autoscroll-box{outline:2px solid #a11e29;outline-offset:2px}.pecb-course-panel__autoscroll-label{font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:13px;font-weight:500;color:#616161}@container pecb-course-panel (max-width: 380px){.pecb-course-panel{--_row-pad-right: 12px}.pecb-course-panel__tabs{padding:0 12px}.pecb-course-panel__tab{padding:12px 0 10px;font-size:13px}.pecb-course-panel__search{padding:10px 12px 8px}.pecb-course-panel__progress{flex-wrap:wrap;gap:12px;padding:12px 16px}.pecb-course-panel__section-head{gap:10px;padding:12px}.pecb-course-panel__section-body{padding-right:8px}.pecb-course-panel__transcript{padding:0 8px}.pecb-course-panel__cue{gap:10px;padding:8px 10px}.pecb-course-panel__cue-text{font-size:13px}.pecb-course-panel__footer{padding:10px 16px}.pecb-course-panel__timeline{padding:4px 10px 12px}}@media (hover: none) and (pointer: coarse){.pecb-course-panel__row,.pecb-course-panel__cue{min-height:44px}}@media (prefers-reduced-motion: reduce){.pecb-course-panel *,.pecb-course-panel *:before,.pecb-course-panel *:after{transition-duration:.01ms!important;animation-duration:.01ms!important}}\n"] }]
12273
+ }], ctorParameters: () => [], propDecorators: { transcriptScrollRef: [{ type: i0.ViewChild, args: ['transcriptScroll', { isSignal: true }] }], modules: [{ type: i0.Input, args: [{ isSignal: true, alias: "modules", required: false }] }], transcript: [{ type: i0.Input, args: [{ isSignal: true, alias: "transcript", required: false }] }], activeLessonId: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeLessonId", required: false }] }, { type: i0.Output, args: ["activeLessonIdChange"] }], tab: [{ type: i0.Input, args: [{ isSignal: true, alias: "tab", required: false }] }, { type: i0.Output, args: ["tabChange"] }], openModuleId: [{ type: i0.Input, args: [{ isSignal: true, alias: "openModuleId", required: false }] }, { type: i0.Output, args: ["openModuleIdChange"] }], autoscroll: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoscroll", required: false }] }, { type: i0.Output, args: ["autoscrollChange"] }], query: [{ type: i0.Input, args: [{ isSignal: true, alias: "query", required: false }] }, { type: i0.Output, args: ["queryChange"] }], transcriptQuery: [{ type: i0.Input, args: [{ isSignal: true, alias: "transcriptQuery", required: false }] }, { type: i0.Output, args: ["transcriptQueryChange"] }], playing: [{ type: i0.Input, args: [{ isSignal: true, alias: "playing", required: false }] }], currentTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentTime", required: false }] }], layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }], density: [{ type: i0.Input, args: [{ isSignal: true, alias: "density", required: false }] }], accent: [{ type: i0.Input, args: [{ isSignal: true, alias: "accent", required: false }] }], showTabs: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTabs", required: false }] }], showSearch: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSearch", required: false }] }], showTranscriptSearch: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTranscriptSearch", required: false }] }], showAutoscroll: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAutoscroll", required: false }] }], showProgress: [{ type: i0.Input, args: [{ isSignal: true, alias: "showProgress", required: false }] }], showCertificate: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCertificate", required: false }] }], soloSingleLessonModules: [{ type: i0.Input, args: [{ isSignal: true, alias: "soloSingleLessonModules", required: false }] }], labels: [{ type: i0.Input, args: [{ isSignal: true, alias: "labels", required: false }] }], lessonSelect: [{ type: i0.Output, args: ["lessonSelect"] }], transcriptSeek: [{ type: i0.Output, args: ["transcriptSeek"] }], moduleToggle: [{ type: i0.Output, args: ["moduleToggle"] }], certificateClick: [{ type: i0.Output, args: ["certificateClick"] }] } });
12274
+ /** Replaces `{token}` placeholders in a label. */
12275
+ function format(template, values) {
12276
+ return template.replace(/\{(\w+)\}/g, (match, key) => key in values ? String(values[key]) : match);
12277
+ }
12278
+
12279
+ /**
12280
+ * PECB course player — the video stage with the **Content** sidebar attached,
12281
+ * exactly as laid out in "Course Player — Content Redesign".
12282
+ *
12283
+ * Composes {@link VideoPlayerComponent} and {@link CourseContentPanelComponent}
12284
+ * and wires them together: picking a lesson loads its media and resumes at its
12285
+ * saved position, clicking a transcript cue seeks the video, and the panel
12286
+ * follows playback live (now-playing row, progress bars, transcript highlight).
12287
+ *
12288
+ * The two-column grid collapses to a single stacked column when the space it is
12289
+ * given gets narrow, so the same markup works in a page, a drawer or a phone.
12290
+ *
12291
+ * @example
12292
+ * ```html
12293
+ * <pecb-course-player
12294
+ * [modules]="modules"
12295
+ * [(activeLessonId)]="lessonId"
12296
+ * (lessonSelect)="track($event)"
12297
+ * (lessonEnded)="markComplete($event)">
12298
+ * </pecb-course-player>
12299
+ * ```
12300
+ *
12301
+ * Layout hooks:
12302
+ * ```css
12303
+ * pecb-course-player {
12304
+ * --pecb-course-player-panel-width: 408px;
12305
+ * --pecb-course-player-gap: 20px;
12306
+ * --pecb-course-player-panel-stacked-height: 460px;
12307
+ * }
12308
+ * ```
12309
+ */
12310
+ class CoursePlayerComponent {
12311
+ constructor() {
12312
+ // ── Data ────────────────────────────────────────────────────────────────────
12313
+ /** Course structure. */
12314
+ this.modules = input([], ...(ngDevMode ? [{ debugName: "modules" }] : []));
12315
+ /** Fallback transcript, used when the active lesson has no `transcript`. */
12316
+ this.transcript = input([], ...(ngDevMode ? [{ debugName: "transcript" }] : []));
12317
+ // ── State (two-way) ─────────────────────────────────────────────────────────
12318
+ /** Selected lesson. */
12319
+ this.activeLessonId = model(null, ...(ngDevMode ? [{ debugName: "activeLessonId" }] : []));
12320
+ /** Playback state of the video. */
12321
+ this.playing = model(false, ...(ngDevMode ? [{ debugName: "playing" }] : []));
12322
+ /** Playhead position, in seconds. */
12323
+ this.currentTime = model(0, ...(ngDevMode ? [{ debugName: "currentTime" }] : []));
12324
+ /** Which panel tab is showing. */
12325
+ this.panelTab = model('content', ...(ngDevMode ? [{ debugName: "panelTab" }] : []));
12326
+ // ── Video options ───────────────────────────────────────────────────────────
12327
+ /** Poster used when the active lesson has none. */
12328
+ this.poster = input(undefined, ...(ngDevMode ? [{ debugName: "poster" }] : []));
12329
+ /** Media used when the active lesson has no `src` (e.g. a single-video course). */
12330
+ this.src = input(undefined, ...(ngDevMode ? [{ debugName: "src" }] : []));
12331
+ /** Caption tracks used when the active lesson has none. */
12332
+ this.tracks = input([], ...(ngDevMode ? [{ debugName: "tracks" }] : []));
12333
+ /** Stage label used when the active lesson has no `stageLabel`. */
12334
+ this.stageLabel = input(undefined, ...(ngDevMode ? [{ debugName: "stageLabel" }] : []));
12335
+ /** Brand watermark on the video stage. @default 'PECB' */
12336
+ this.watermark = input('PECB', ...(ngDevMode ? [{ debugName: "watermark" }] : []));
12337
+ /** @default '16 / 9' */
12338
+ this.aspectRatio = input('16 / 9', ...(ngDevMode ? [{ debugName: "aspectRatio" }] : []));
12339
+ /** @default 'metadata' */
12340
+ this.preload = input('metadata', ...(ngDevMode ? [{ debugName: "preload" }] : []));
12341
+ /** `crossorigin` for the media element. */
12342
+ this.crossOrigin = input(undefined, ...(ngDevMode ? [{ debugName: "crossOrigin" }] : []));
12343
+ /** Seconds the rewind button / arrow keys jump. @default 10 */
12344
+ this.seekStep = input(10, ...(ngDevMode ? [{ debugName: "seekStep", transform: numberAttribute }] : [{ transform: numberAttribute }]));
12345
+ /** i18n overrides for the video player. */
12346
+ this.videoLabels = input({}, ...(ngDevMode ? [{ debugName: "videoLabels" }] : []));
12347
+ /** Start playing as soon as a video lesson is picked. @default true */
12348
+ this.autoPlayOnSelect = input(true, ...(ngDevMode ? [{ debugName: "autoPlayOnSelect", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
12349
+ /** Show the active lesson's title under the video. @default true */
12350
+ this.showLessonTitle = input(true, ...(ngDevMode ? [{ debugName: "showLessonTitle", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
12351
+ // ── Panel options ───────────────────────────────────────────────────────────
12352
+ /** Which side the panel sits on. @default 'end' */
12353
+ this.panelPosition = input('end', ...(ngDevMode ? [{ debugName: "panelPosition" }] : []));
12354
+ /** @default 'grouped' */
12355
+ this.layout = input('grouped', ...(ngDevMode ? [{ debugName: "layout" }] : []));
12356
+ /** @default 'comfortable' */
12357
+ this.density = input('comfortable', ...(ngDevMode ? [{ debugName: "density" }] : []));
12358
+ /** @default 'charcoal' */
12359
+ this.accent = input('charcoal', ...(ngDevMode ? [{ debugName: "accent" }] : []));
12360
+ /** Show the panel's tab strip. @default true */
12361
+ this.showTabs = input(true, ...(ngDevMode ? [{ debugName: "showTabs", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
12362
+ /** Show the lesson search box. @default true */
12363
+ this.showSearch = input(true, ...(ngDevMode ? [{ debugName: "showSearch", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
12364
+ /** Show the circular course-progress header. @default false */
12365
+ this.showProgress = input(false, ...(ngDevMode ? [{ debugName: "showProgress", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
12366
+ /** Render single-lesson sections as direct-play rows. @default false */
12367
+ this.soloSingleLessonModules = input(false, ...(ngDevMode ? [{ debugName: "soloSingleLessonModules", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
12368
+ /** i18n overrides for the panel. */
12369
+ this.panelLabels = input({}, ...(ngDevMode ? [{ debugName: "panelLabels" }] : []));
12370
+ // ── Outputs ─────────────────────────────────────────────────────────────────
12371
+ /** A lesson row was picked (fires for quizzes too). */
12372
+ this.lessonSelect = output();
12373
+ /** The video of a lesson played to the end. */
12374
+ this.lessonEnded = output();
12375
+ /** Playhead moved — use it to persist progress. */
12376
+ this.timeUpdate = output();
12377
+ /** The panel's "Certificate" pill was clicked. */
12378
+ this.certificateClick = output();
12379
+ /** The media failed to load, or playback was rejected. */
12380
+ this.playerError = output();
12381
+ // ── Derived ─────────────────────────────────────────────────────────────────
12382
+ /** The lesson currently selected in the panel (may be a quiz). */
12383
+ this.activeLesson = computed(() => findCourseLesson(this.modules(), this.activeLessonId()), ...(ngDevMode ? [{ debugName: "activeLesson" }] : []));
12384
+ /**
12385
+ * Id of the last *video* lesson that was selected. Picking a quiz keeps the
12386
+ * video on the stage (design behaviour) instead of blanking it.
12387
+ */
12388
+ this.videoLessonId = signal(null, ...(ngDevMode ? [{ debugName: "videoLessonId" }] : []));
12389
+ this.videoLesson = computed(() => {
12390
+ const explicit = findCourseLesson(this.modules(), this.videoLessonId());
12391
+ if (explicit)
12392
+ return explicit;
12393
+ // Fall back to the first video lesson so the stage is never empty.
12394
+ for (const module of this.modules()) {
12395
+ const lesson = module.lessons.find((l) => (l.type ?? 'video') === 'video');
12396
+ if (lesson)
12397
+ return lesson;
12398
+ }
12399
+ return undefined;
12400
+ }, ...(ngDevMode ? [{ debugName: "videoLesson" }] : []));
12401
+ this.videoSrc = computed(() => this.videoLesson()?.src ?? this.src(), ...(ngDevMode ? [{ debugName: "videoSrc" }] : []));
12402
+ this.videoPoster = computed(() => this.videoLesson()?.poster ?? this.poster(), ...(ngDevMode ? [{ debugName: "videoPoster" }] : []));
12403
+ this.videoTracks = computed(() => this.videoLesson()?.tracks ?? this.tracks(), ...(ngDevMode ? [{ debugName: "videoTracks" }] : []));
12404
+ this.videoStageLabel = computed(() => this.videoLesson()?.stageLabel ?? this.stageLabel(), ...(ngDevMode ? [{ debugName: "videoStageLabel" }] : []));
12405
+ this.videoTitle = computed(() => this.videoLesson()?.title, ...(ngDevMode ? [{ debugName: "videoTitle" }] : []));
12406
+ this.lessonTitle = computed(() => this.activeLesson()?.title ?? '', ...(ngDevMode ? [{ debugName: "lessonTitle" }] : []));
12407
+ /** Resume position handed to the player whenever the source changes. */
12408
+ this.startTime = computed(() => this.videoLesson()?.resumeSeconds ?? 0, ...(ngDevMode ? [{ debugName: "startTime" }] : []));
12409
+ this.hostClasses = computed(() => [
12410
+ 'pecb-course-player',
12411
+ `pecb-course-player--panel-${this.panelPosition()}`,
12412
+ ].join(' '), ...(ngDevMode ? [{ debugName: "hostClasses" }] : []));
12413
+ /** Id of the lesson whose resume position has already been pushed into `currentTime`. */
12414
+ this.resumeAppliedFor = null;
12415
+ // Track the newest video lesson, including the initial selection.
12416
+ effect(() => {
12417
+ const lesson = this.activeLesson();
12418
+ untracked(() => {
12419
+ if (lesson && (lesson.type ?? 'video') === 'video') {
12420
+ this.videoLessonId.set(lesson.id);
12421
+ }
12422
+ });
12423
+ });
12424
+ // Seed the playhead from the lesson's stored position whenever the loaded
12425
+ // video changes — including the very first one, which arrives as an input
12426
+ // rather than through a click. Without this the panel would show 0:00 for a
12427
+ // half-watched lesson until the media reported its own time.
12428
+ effect(() => {
12429
+ const lesson = this.videoLesson();
12430
+ untracked(() => {
12431
+ if (!lesson || lesson.id === this.resumeAppliedFor)
12432
+ return;
12433
+ this.resumeAppliedFor = lesson.id;
12434
+ this.currentTime.set(lesson.resumeSeconds ?? 0);
12435
+ });
12436
+ });
12437
+ }
12438
+ // ── Wiring ──────────────────────────────────────────────────────────────────
12439
+ /** Panel → player: load a lesson, resuming where it left off. */
12440
+ onLessonSelect(lesson) {
12441
+ const isVideo = (lesson.type ?? 'video') === 'video';
12442
+ if (isVideo) {
12443
+ this.videoLessonId.set(lesson.id);
12444
+ this.currentTime.set(lesson.resumeSeconds ?? 0);
12445
+ this.playing.set(this.autoPlayOnSelect());
12446
+ }
12447
+ else {
12448
+ // A quiz has nothing to play — stop the video and leave it on the stage.
12449
+ this.playing.set(false);
12450
+ }
12451
+ this.lessonSelect.emit(lesson);
12452
+ }
12453
+ /** Panel → player: jump to a transcript cue and keep playing. */
12454
+ onTranscriptSeek(seconds) {
12455
+ this.currentTime.set(seconds);
12456
+ this.playing.set(true);
12457
+ }
12458
+ onPlaybackEnded() {
12459
+ const lesson = this.videoLesson();
12460
+ if (lesson)
12461
+ this.lessonEnded.emit(lesson);
12462
+ }
12463
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: CoursePlayerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
12464
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: CoursePlayerComponent, isStandalone: true, selector: "pecb-course-player", inputs: { modules: { classPropertyName: "modules", publicName: "modules", isSignal: true, isRequired: false, transformFunction: null }, transcript: { classPropertyName: "transcript", publicName: "transcript", isSignal: true, isRequired: false, transformFunction: null }, activeLessonId: { classPropertyName: "activeLessonId", publicName: "activeLessonId", isSignal: true, isRequired: false, transformFunction: null }, playing: { classPropertyName: "playing", publicName: "playing", isSignal: true, isRequired: false, transformFunction: null }, currentTime: { classPropertyName: "currentTime", publicName: "currentTime", isSignal: true, isRequired: false, transformFunction: null }, panelTab: { classPropertyName: "panelTab", publicName: "panelTab", isSignal: true, isRequired: false, transformFunction: null }, poster: { classPropertyName: "poster", publicName: "poster", isSignal: true, isRequired: false, transformFunction: null }, src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: false, transformFunction: null }, tracks: { classPropertyName: "tracks", publicName: "tracks", isSignal: true, isRequired: false, transformFunction: null }, stageLabel: { classPropertyName: "stageLabel", publicName: "stageLabel", isSignal: true, isRequired: false, transformFunction: null }, watermark: { classPropertyName: "watermark", publicName: "watermark", isSignal: true, isRequired: false, transformFunction: null }, aspectRatio: { classPropertyName: "aspectRatio", publicName: "aspectRatio", isSignal: true, isRequired: false, transformFunction: null }, preload: { classPropertyName: "preload", publicName: "preload", isSignal: true, isRequired: false, transformFunction: null }, crossOrigin: { classPropertyName: "crossOrigin", publicName: "crossOrigin", isSignal: true, isRequired: false, transformFunction: null }, seekStep: { classPropertyName: "seekStep", publicName: "seekStep", isSignal: true, isRequired: false, transformFunction: null }, videoLabels: { classPropertyName: "videoLabels", publicName: "videoLabels", isSignal: true, isRequired: false, transformFunction: null }, autoPlayOnSelect: { classPropertyName: "autoPlayOnSelect", publicName: "autoPlayOnSelect", isSignal: true, isRequired: false, transformFunction: null }, showLessonTitle: { classPropertyName: "showLessonTitle", publicName: "showLessonTitle", isSignal: true, isRequired: false, transformFunction: null }, panelPosition: { classPropertyName: "panelPosition", publicName: "panelPosition", isSignal: true, isRequired: false, transformFunction: null }, layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null }, density: { classPropertyName: "density", publicName: "density", isSignal: true, isRequired: false, transformFunction: null }, accent: { classPropertyName: "accent", publicName: "accent", isSignal: true, isRequired: false, transformFunction: null }, showTabs: { classPropertyName: "showTabs", publicName: "showTabs", isSignal: true, isRequired: false, transformFunction: null }, showSearch: { classPropertyName: "showSearch", publicName: "showSearch", isSignal: true, isRequired: false, transformFunction: null }, showProgress: { classPropertyName: "showProgress", publicName: "showProgress", isSignal: true, isRequired: false, transformFunction: null }, soloSingleLessonModules: { classPropertyName: "soloSingleLessonModules", publicName: "soloSingleLessonModules", isSignal: true, isRequired: false, transformFunction: null }, panelLabels: { classPropertyName: "panelLabels", publicName: "panelLabels", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activeLessonId: "activeLessonIdChange", playing: "playingChange", currentTime: "currentTimeChange", panelTab: "panelTabChange", lessonSelect: "lessonSelect", lessonEnded: "lessonEnded", timeUpdate: "timeUpdate", certificateClick: "certificateClick", playerError: "playerError" }, host: { classAttribute: "pecb-course-player-host" }, ngImport: i0, template: "<div [class]=\"hostClasses()\">\n <!-- \u2500\u2500 Video stage \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"pecb-course-player__stage\">\n <pecb-video-player\n class=\"pecb-course-player__video\"\n [src]=\"videoSrc()\"\n [poster]=\"videoPoster()\"\n [tracks]=\"videoTracks()\"\n [title]=\"videoTitle()\"\n [label]=\"videoStageLabel()\"\n [watermark]=\"watermark()\"\n [aspectRatio]=\"aspectRatio()\"\n [preload]=\"preload()\"\n [crossOrigin]=\"crossOrigin()\"\n [seekStep]=\"seekStep()\"\n [startTime]=\"startTime()\"\n [labels]=\"videoLabels()\"\n [(playing)]=\"playing\"\n [(currentTime)]=\"currentTime\"\n (timeUpdate)=\"timeUpdate.emit($event)\"\n (playbackEnded)=\"onPlaybackEnded()\"\n (playerError)=\"playerError.emit($event)\"\n ></pecb-video-player>\n\n @if (showLessonTitle() && lessonTitle()) {\n <h2 class=\"pecb-course-player__title\">{{ lessonTitle() }}</h2>\n }\n </div>\n\n <!-- \u2500\u2500 Content sidebar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"pecb-course-player__panel\">\n <div class=\"pecb-course-player__panel-inner\">\n <pecb-course-content-panel\n [modules]=\"modules()\"\n [transcript]=\"transcript()\"\n [(activeLessonId)]=\"activeLessonId\"\n [(tab)]=\"panelTab\"\n [playing]=\"playing()\"\n [currentTime]=\"currentTime()\"\n [layout]=\"layout()\"\n [density]=\"density()\"\n [accent]=\"accent()\"\n [showTabs]=\"showTabs()\"\n [showSearch]=\"showSearch()\"\n [showProgress]=\"showProgress()\"\n [soloSingleLessonModules]=\"soloSingleLessonModules()\"\n [labels]=\"panelLabels()\"\n (lessonSelect)=\"onLessonSelect($event)\"\n (transcriptSeek)=\"onTranscriptSeek($event)\"\n (certificateClick)=\"certificateClick.emit()\"\n ></pecb-course-content-panel>\n </div>\n </div>\n</div>\n", styles: [":host{display:block;width:100%;height:100%;min-height:0;container-type:inline-size;container-name:pecb-course-player}.pecb-course-player{display:grid;grid-template-columns:minmax(0,1fr) var(--pecb-course-player-panel-width, 408px);gap:var(--pecb-course-player-gap, 20px);align-items:stretch;height:100%;min-height:0}.pecb-course-player--panel-start{grid-template-columns:var(--pecb-course-player-panel-width, 408px) minmax(0,1fr)}.pecb-course-player--panel-start .pecb-course-player__stage{order:2}.pecb-course-player--panel-start .pecb-course-player__panel{order:1}.pecb-course-player__stage{display:flex;flex-direction:column;gap:14px;min-width:0;min-height:0}.pecb-course-player__video{flex:0 0 auto}.pecb-course-player__title{flex:0 0 auto;margin:0;padding:0 2px;font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:16px;font-weight:700;line-height:1.35;color:#212427;text-wrap:pretty}.pecb-course-player__panel{position:relative;min-width:0;min-height:240px}.pecb-course-player__panel-inner{position:absolute;inset:0;display:flex;overflow:hidden;border:1px solid #ececec;border-radius:var(--pecb-course-player-radius, 12px);background:#fff}.pecb-course-player pecb-course-content-panel{flex:1 1 auto;min-width:0}@container pecb-course-player (max-width: 900px){.pecb-course-player,.pecb-course-player--panel-start{grid-template-columns:minmax(0,1fr);height:auto}.pecb-course-player__stage,.pecb-course-player--panel-start .pecb-course-player__stage{order:1}.pecb-course-player__panel,.pecb-course-player--panel-start .pecb-course-player__panel{order:2;position:static;min-height:0;height:var(--pecb-course-player-panel-stacked-height, 460px)}.pecb-course-player__panel-inner{position:static;height:100%}}@container pecb-course-player (max-width: 560px){.pecb-course-player{gap:16px}.pecb-course-player__panel{height:var(--pecb-course-player-panel-stacked-height, 400px)}.pecb-course-player__title{font-size:14px}}\n"], dependencies: [{ kind: "component", type: VideoPlayerComponent, selector: "pecb-video-player", inputs: ["src", "poster", "tracks", "title", "crossOrigin", "preload", "autoplay", "loop", "playsInline", "startTime", "watermark", "label", "aspectRatio", "showCenterButton", "showSkipBack", "showVolume", "showTime", "showCaptions", "showSettings", "showFullscreen", "seekStep", "playbackRates", "labels", "playing", "currentTime", "volume", "muted", "playbackRate", "captionsEnabled"], outputs: ["playingChange", "currentTimeChange", "volumeChange", "mutedChange", "playbackRateChange", "captionsEnabledChange", "timeUpdate", "loadedMetadata", "seekEnd", "playbackEnded", "fullscreenChange", "playerError"] }, { kind: "component", type: CourseContentPanelComponent, selector: "pecb-course-content-panel", inputs: ["modules", "transcript", "activeLessonId", "tab", "openModuleId", "autoscroll", "query", "transcriptQuery", "playing", "currentTime", "layout", "density", "accent", "showTabs", "showSearch", "showTranscriptSearch", "showAutoscroll", "showProgress", "showCertificate", "soloSingleLessonModules", "labels"], outputs: ["activeLessonIdChange", "tabChange", "openModuleIdChange", "autoscrollChange", "queryChange", "transcriptQueryChange", "lessonSelect", "transcriptSeek", "moduleToggle", "certificateClick"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
12465
+ }
12466
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: CoursePlayerComponent, decorators: [{
12467
+ type: Component,
12468
+ args: [{ selector: 'pecb-course-player', standalone: true, imports: [VideoPlayerComponent, CourseContentPanelComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'pecb-course-player-host' }, template: "<div [class]=\"hostClasses()\">\n <!-- \u2500\u2500 Video stage \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"pecb-course-player__stage\">\n <pecb-video-player\n class=\"pecb-course-player__video\"\n [src]=\"videoSrc()\"\n [poster]=\"videoPoster()\"\n [tracks]=\"videoTracks()\"\n [title]=\"videoTitle()\"\n [label]=\"videoStageLabel()\"\n [watermark]=\"watermark()\"\n [aspectRatio]=\"aspectRatio()\"\n [preload]=\"preload()\"\n [crossOrigin]=\"crossOrigin()\"\n [seekStep]=\"seekStep()\"\n [startTime]=\"startTime()\"\n [labels]=\"videoLabels()\"\n [(playing)]=\"playing\"\n [(currentTime)]=\"currentTime\"\n (timeUpdate)=\"timeUpdate.emit($event)\"\n (playbackEnded)=\"onPlaybackEnded()\"\n (playerError)=\"playerError.emit($event)\"\n ></pecb-video-player>\n\n @if (showLessonTitle() && lessonTitle()) {\n <h2 class=\"pecb-course-player__title\">{{ lessonTitle() }}</h2>\n }\n </div>\n\n <!-- \u2500\u2500 Content sidebar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"pecb-course-player__panel\">\n <div class=\"pecb-course-player__panel-inner\">\n <pecb-course-content-panel\n [modules]=\"modules()\"\n [transcript]=\"transcript()\"\n [(activeLessonId)]=\"activeLessonId\"\n [(tab)]=\"panelTab\"\n [playing]=\"playing()\"\n [currentTime]=\"currentTime()\"\n [layout]=\"layout()\"\n [density]=\"density()\"\n [accent]=\"accent()\"\n [showTabs]=\"showTabs()\"\n [showSearch]=\"showSearch()\"\n [showProgress]=\"showProgress()\"\n [soloSingleLessonModules]=\"soloSingleLessonModules()\"\n [labels]=\"panelLabels()\"\n (lessonSelect)=\"onLessonSelect($event)\"\n (transcriptSeek)=\"onTranscriptSeek($event)\"\n (certificateClick)=\"certificateClick.emit()\"\n ></pecb-course-content-panel>\n </div>\n </div>\n</div>\n", styles: [":host{display:block;width:100%;height:100%;min-height:0;container-type:inline-size;container-name:pecb-course-player}.pecb-course-player{display:grid;grid-template-columns:minmax(0,1fr) var(--pecb-course-player-panel-width, 408px);gap:var(--pecb-course-player-gap, 20px);align-items:stretch;height:100%;min-height:0}.pecb-course-player--panel-start{grid-template-columns:var(--pecb-course-player-panel-width, 408px) minmax(0,1fr)}.pecb-course-player--panel-start .pecb-course-player__stage{order:2}.pecb-course-player--panel-start .pecb-course-player__panel{order:1}.pecb-course-player__stage{display:flex;flex-direction:column;gap:14px;min-width:0;min-height:0}.pecb-course-player__video{flex:0 0 auto}.pecb-course-player__title{flex:0 0 auto;margin:0;padding:0 2px;font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:16px;font-weight:700;line-height:1.35;color:#212427;text-wrap:pretty}.pecb-course-player__panel{position:relative;min-width:0;min-height:240px}.pecb-course-player__panel-inner{position:absolute;inset:0;display:flex;overflow:hidden;border:1px solid #ececec;border-radius:var(--pecb-course-player-radius, 12px);background:#fff}.pecb-course-player pecb-course-content-panel{flex:1 1 auto;min-width:0}@container pecb-course-player (max-width: 900px){.pecb-course-player,.pecb-course-player--panel-start{grid-template-columns:minmax(0,1fr);height:auto}.pecb-course-player__stage,.pecb-course-player--panel-start .pecb-course-player__stage{order:1}.pecb-course-player__panel,.pecb-course-player--panel-start .pecb-course-player__panel{order:2;position:static;min-height:0;height:var(--pecb-course-player-panel-stacked-height, 460px)}.pecb-course-player__panel-inner{position:static;height:100%}}@container pecb-course-player (max-width: 560px){.pecb-course-player{gap:16px}.pecb-course-player__panel{height:var(--pecb-course-player-panel-stacked-height, 400px)}.pecb-course-player__title{font-size:14px}}\n"] }]
12469
+ }], ctorParameters: () => [], propDecorators: { modules: [{ type: i0.Input, args: [{ isSignal: true, alias: "modules", required: false }] }], transcript: [{ type: i0.Input, args: [{ isSignal: true, alias: "transcript", required: false }] }], activeLessonId: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeLessonId", required: false }] }, { type: i0.Output, args: ["activeLessonIdChange"] }], playing: [{ type: i0.Input, args: [{ isSignal: true, alias: "playing", required: false }] }, { type: i0.Output, args: ["playingChange"] }], currentTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentTime", required: false }] }, { type: i0.Output, args: ["currentTimeChange"] }], panelTab: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelTab", required: false }] }, { type: i0.Output, args: ["panelTabChange"] }], poster: [{ type: i0.Input, args: [{ isSignal: true, alias: "poster", required: false }] }], src: [{ type: i0.Input, args: [{ isSignal: true, alias: "src", required: false }] }], tracks: [{ type: i0.Input, args: [{ isSignal: true, alias: "tracks", required: false }] }], stageLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "stageLabel", required: false }] }], watermark: [{ type: i0.Input, args: [{ isSignal: true, alias: "watermark", required: false }] }], aspectRatio: [{ type: i0.Input, args: [{ isSignal: true, alias: "aspectRatio", required: false }] }], preload: [{ type: i0.Input, args: [{ isSignal: true, alias: "preload", required: false }] }], crossOrigin: [{ type: i0.Input, args: [{ isSignal: true, alias: "crossOrigin", required: false }] }], seekStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "seekStep", required: false }] }], videoLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "videoLabels", required: false }] }], autoPlayOnSelect: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoPlayOnSelect", required: false }] }], showLessonTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "showLessonTitle", required: false }] }], panelPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelPosition", required: false }] }], layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }], density: [{ type: i0.Input, args: [{ isSignal: true, alias: "density", required: false }] }], accent: [{ type: i0.Input, args: [{ isSignal: true, alias: "accent", required: false }] }], showTabs: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTabs", required: false }] }], showSearch: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSearch", required: false }] }], showProgress: [{ type: i0.Input, args: [{ isSignal: true, alias: "showProgress", required: false }] }], soloSingleLessonModules: [{ type: i0.Input, args: [{ isSignal: true, alias: "soloSingleLessonModules", required: false }] }], panelLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelLabels", required: false }] }], lessonSelect: [{ type: i0.Output, args: ["lessonSelect"] }], lessonEnded: [{ type: i0.Output, args: ["lessonEnded"] }], timeUpdate: [{ type: i0.Output, args: ["timeUpdate"] }], certificateClick: [{ type: i0.Output, args: ["certificateClick"] }], playerError: [{ type: i0.Output, args: ["playerError"] }] } });
12470
+
12471
+ /**
12472
+ * An injectable, Toastr-style notification service.
12473
+ *
12474
+ * Calling any of the helpers renders a `pecb-alert` toast in a CDK overlay
12475
+ * stack on `<body>` — no template wiring, host component, or module setup is
12476
+ * required. Just inject and call:
12477
+ *
12478
+ * @example
12479
+ * ```typescript
12480
+ * import { NotificationService } from '@pecb-ui/components';
12481
+ *
12482
+ * @Component({...})
12483
+ * export class MyComponent {
12484
+ * private notify = inject(NotificationService);
12485
+ *
12486
+ * save(): void {
12487
+ * this.notify.success('Saved!');
12488
+ * this.notify.error('Something went wrong', 'Error');
12489
+ * }
12490
+ * }
12491
+ * ```
12492
+ *
12493
+ * @publicApi
12494
+ */
12495
+ class NotificationService {
12496
+ constructor() {
12497
+ this.overlay = inject(Overlay);
12498
+ this.appRef = inject(ApplicationRef);
12499
+ this.environmentInjector = inject(EnvironmentInjector);
12500
+ // Legacy reactive streams — kept for backward compatibility. Rendering is now
12501
+ // handled directly by this service, so subscribing to these is no longer
12502
+ // required to display notifications.
12503
+ this.notificationSubject = new Subject();
12504
+ this.dismissSubject = new Subject();
12505
+ this.clearSubject = new Subject();
12506
+ /** Default duration for notifications in milliseconds */
12507
+ this.defaultDuration = 5000;
12508
+ /** Default position for notifications */
12509
+ this.defaultPosition = 'top-right';
12510
+ /** Time to keep a toast mounted after dismiss so its leave animation can play. */
12511
+ this.leaveAnimationMs = 300;
12512
+ /** One shared overlay (a vertical stack) per on-screen position. */
12513
+ this.stacks = new Map();
12514
+ /** All live toasts, keyed by their id. */
12515
+ this.toasts = new Map();
12516
+ }
12517
+ /** @deprecated Notifications now render automatically; no need to subscribe. */
12518
+ get notifications$() {
12519
+ return this.notificationSubject.asObservable();
12520
+ }
12521
+ /** @deprecated Notifications now render automatically; no need to subscribe. */
12522
+ get dismiss$() {
12523
+ return this.dismissSubject.asObservable();
12524
+ }
12525
+ /** @deprecated Notifications now render automatically; no need to subscribe. */
12526
+ get clear$() {
12527
+ return this.clearSubject.asObservable();
12528
+ }
12529
+ /**
12530
+ * Show a success notification
12531
+ * @param message - The notification message
12532
+ * @param title - Optional title
12533
+ * @param config - Optional additional configuration
12534
+ */
12535
+ success(message, title, config) {
12536
+ return this.show({ ...config, message, title, type: 'success' });
12537
+ }
12538
+ /**
12539
+ * Show an error notification
12540
+ * @param message - The notification message
12541
+ * @param title - Optional title
12542
+ * @param config - Optional additional configuration
12543
+ */
12544
+ error(message, title, config) {
12545
+ return this.show({ ...config, message, title, type: 'error' });
12546
+ }
12547
+ /**
12548
+ * Show a warning notification
12549
+ * @param message - The notification message
12550
+ * @param title - Optional title
12551
+ * @param config - Optional additional configuration
12552
+ */
12553
+ warning(message, title, config) {
12554
+ return this.show({ ...config, message, title, type: 'warning' });
12555
+ }
12556
+ /**
12557
+ * Show an info notification
12558
+ * @param message - The notification message
12559
+ * @param title - Optional title
12560
+ * @param config - Optional additional configuration
12561
+ */
12562
+ info(message, title, config) {
12563
+ return this.show({ ...config, message, title, type: 'info' });
12564
+ }
12565
+ /**
12566
+ * Show a notification with custom configuration
12567
+ * @param config - Full notification configuration
12568
+ * @returns The notification ID
12569
+ */
12570
+ show(config) {
12571
+ const id = config.id || this.generateId();
12572
+ const position = config.position ?? this.defaultPosition;
12573
+ const duration = config.duration ?? this.defaultDuration;
12574
+ const dismissible = config.dismissible ?? true;
12575
+ // Legacy stream for any pre-existing subscribers.
12576
+ this.notificationSubject.next({
12577
+ id,
12578
+ message: config.message,
12579
+ title: config.title,
12580
+ type: config.type,
12581
+ duration,
12582
+ dismissible,
12583
+ position,
12584
+ customClass: config.customClass,
12585
+ timestamp: new Date()
12586
+ });
12587
+ const overlayRef = this.getStack(position);
12588
+ // Build the alert imperatively and drop it into the stack's pane.
12589
+ const componentRef = createComponent(AlertComponent, {
12590
+ environmentInjector: this.environmentInjector
12591
+ });
12592
+ componentRef.setInput('type', config.type);
12593
+ componentRef.setInput('variant', config.variant ?? 'banner');
12594
+ componentRef.setInput('title', config.title ?? '');
12595
+ componentRef.setInput('message', config.message);
12596
+ componentRef.setInput('dismissible', dismissible);
12597
+ componentRef.setInput('primaryAction', config.primaryAction);
12598
+ componentRef.setInput('secondaryAction', config.secondaryAction);
12599
+ const host = componentRef.location.nativeElement;
12600
+ host.style.pointerEvents = 'auto';
12601
+ if (config.customClass) {
12602
+ host.classList.add(config.customClass);
12603
+ }
12604
+ this.appRef.attachView(componentRef.hostView);
12605
+ overlayRef.overlayElement.appendChild(host);
12606
+ // User clicked the × — start the removal (leave animation + unmount).
12607
+ componentRef.instance.dismissed.subscribe(() => this.remove(id));
12608
+ // Action buttons: run the callback, then dismiss unless it returns false.
12609
+ componentRef.instance.primaryActionClick.subscribe(() => {
12610
+ if (config.onPrimaryAction?.() !== false) {
12611
+ this.remove(id);
12612
+ }
12613
+ });
12614
+ componentRef.instance.secondaryActionClick.subscribe(() => {
12615
+ if (config.onSecondaryAction?.() !== false) {
12616
+ this.remove(id);
12617
+ }
12618
+ });
12619
+ const toast = { componentRef, position };
12620
+ this.toasts.set(id, toast);
12621
+ if (duration > 0) {
12622
+ toast.removeTimer = setTimeout(() => this.remove(id), duration);
12623
+ }
12624
+ return id;
12625
+ }
12626
+ /**
12627
+ * Dismiss a specific notification by ID
12628
+ * @param id - The notification ID to dismiss
12629
+ */
10743
12630
  dismiss(id) {
10744
12631
  this.dismissSubject.next(id);
12632
+ this.remove(id);
10745
12633
  }
10746
12634
  /**
10747
12635
  * Clear all notifications
10748
12636
  */
10749
12637
  clearAll() {
10750
12638
  this.clearSubject.next();
12639
+ for (const id of [...this.toasts.keys()]) {
12640
+ this.remove(id);
12641
+ }
12642
+ }
12643
+ /** Get (or lazily create) the overlay stack for a screen position. */
12644
+ getStack(position) {
12645
+ let overlayRef = this.stacks.get(position);
12646
+ if (overlayRef) {
12647
+ return overlayRef;
12648
+ }
12649
+ const [vertical, horizontal] = position.split('-');
12650
+ overlayRef = this.overlay.create({
12651
+ // Toasts must never block the page underneath.
12652
+ hasBackdrop: false,
12653
+ scrollStrategy: this.overlay.scrollStrategies.noop()
12654
+ });
12655
+ // We position the stack manually rather than via a CDK position strategy:
12656
+ // the strategy's apply() only runs once a portal is attached, but we append
12657
+ // toast elements straight into the pane (so several can share one stack).
12658
+ // The `cdk-global-overlay-wrapper` class (normally added by the global
12659
+ // strategy) makes the host a full-viewport flexbox; aligning it then places
12660
+ // the pane in the chosen corner.
12661
+ const wrapper = overlayRef.hostElement;
12662
+ wrapper.classList.add('cdk-global-overlay-wrapper');
12663
+ wrapper.style.justifyContent =
12664
+ horizontal === 'center' ? 'center' : horizontal === 'right' ? 'flex-end' : 'flex-start';
12665
+ wrapper.style.alignItems = vertical === 'top' ? 'flex-start' : 'flex-end';
12666
+ // Lay the toasts out as a vertical, non-blocking column. Bottom positions
12667
+ // grow upward so the newest toast sits nearest the edge.
12668
+ const pane = overlayRef.overlayElement;
12669
+ pane.style.position = 'static'; // let the flex wrapper place it
12670
+ pane.style.margin = '16px';
12671
+ pane.style.display = 'flex';
12672
+ pane.style.flexDirection = vertical === 'top' ? 'column' : 'column-reverse';
12673
+ pane.style.gap = '12px';
12674
+ pane.style.width = '380px';
12675
+ pane.style.maxWidth = 'calc(100vw - 32px)';
12676
+ pane.style.pointerEvents = 'none';
12677
+ this.stacks.set(position, overlayRef);
12678
+ return overlayRef;
12679
+ }
12680
+ /** Play the leave animation, then unmount the toast and tidy up its stack. */
12681
+ remove(id) {
12682
+ const toast = this.toasts.get(id);
12683
+ if (!toast) {
12684
+ return;
12685
+ }
12686
+ this.toasts.delete(id);
12687
+ clearTimeout(toast.removeTimer);
12688
+ // Triggers the alert's leave animation (see AlertComponent.visible/leaving).
12689
+ toast.componentRef.instance.visible.set(false);
12690
+ setTimeout(() => {
12691
+ this.appRef.detachView(toast.componentRef.hostView);
12692
+ toast.componentRef.destroy();
12693
+ this.disposeStackIfEmpty(toast.position);
12694
+ }, this.leaveAnimationMs);
12695
+ }
12696
+ /** Dispose a position's overlay once it holds no more toasts. */
12697
+ disposeStackIfEmpty(position) {
12698
+ const stillUsed = [...this.toasts.values()].some((t) => t.position === position);
12699
+ if (stillUsed) {
12700
+ return;
12701
+ }
12702
+ this.stacks.get(position)?.dispose();
12703
+ this.stacks.delete(position);
10751
12704
  }
10752
12705
  /**
10753
12706
  * Generate a unique notification ID
@@ -17157,5 +19110,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
17157
19110
  * Generated bundle index. Do not edit.
17158
19111
  */
17159
19112
 
17160
- export { AccordionItemComponent, AccordionSmallComponent, AddButtonComponent, AdminHeaderComponent, AdminHeaderFieldTemplateDirective, AffixComponent, AlertComponent, AnchorComponent, ApplicationStatusBarComponent, AuditorStatusComponent, AuthorDateTimeComponent, BackToTopComponent, BadgeComponent, BlurDirective, BottomSheetComponent, BreadcrumbsComponent, ButtonComponent, ButtonGroupComponent, ButtonGroupItemComponent, CancelUpdateButtonsComponent, CardBodyComponent, CardComponent, CardFooterComponent, CardHeaderComponent, CertificateUploadBarComponent, CheckDeleteIconComponent, CheckboxComponent, CheckboxDisplayComponent, CodeInputComponent, CodeSnippetComponent, ColorPaletteComponent, ConfirmationComponent, ContentTypeTagComponent, DEFAULT_LANGUAGES, DashboardGridComponent, DatepickerComponent, DividerComponent, DropdownComponent, EMPTY_STATE_MAX_BUTTONS, EditButtonComponent, EditCoverPhotoComponent, EmptyStateComponent, ExpandableRowTableComponent, FileUploadComponent, FilterColumnsComponent, FloatButtonComponent, FloatButtonItemComponent, FullscreenModalComponent, FullscreenModalContentDirective, FullscreenModalFooterDirective, FullscreenModalHeaderDirective, GeneralComponent, GridComponent, GridItemComponent, HeaderActionsComponent, HeaderComponent, HeaderDividerComponent, HeaderLanguageComponent, HeaderSearchComponent, HeaderUserComponent, HierarchicalTableComponent, HorizontalStepsComponent, IconComponent, IconRegistry, IconTagComponent, InformationBoxComponent, InputComponent, LanguageDropdownComponent, LinkButtonComponent, LoadingService, MediaComponent, MessageBubbleComponent, MessageItemComponent, MetricsCardComponent, MiniIconButtonComponent, NoResultsComponent, NoteSidebarItemComponent, NotesPanelComponent, NotificationService, NotificationStatusLinkComponent, PECB_COLOR_PALETTE, PECB_CUSTOM_ICONS, PECB_FONT_STYLES, PECB_ICONS, PECB_TYPE_SCALE, PaginationComponent, PriceMethodComponent, ProfileComponent, ProfileElementsCardComponent, ProfileGroupComponent, ProgressBarComponent, ProgressCircleComponent, ProjectLayoutComponent, QuantitySelectorComponent, QuestionTypeTagComponent, RadioComponent, RadioDisplayComponent, RatingNumberComponent, ReasonForReturnComponent, RequestSentByComponent, RequestStatusBarComponent, ResultPageComponent, RightModalComponent, RightModalContentDirective, RightModalFooterDirective, RightModalHeaderDirective, ShadowDirective, SidebarComponent, SkeletonComponent, SlidePointsComponent, SpacerComponent, SpinnerComponent, StandardsPdfCardComponent, StatisticsCardComponent, StatusComponent, StepperComponent, TabComponent, TableComponent, TagComponent, TestimonialComponent, TextFormFieldComponent, ThemeService, ToggleComponent, ToolbarBarComponent, TooltipComponent, TooltipDirective, TourComponent, TranscriptLineComponent, TypographyComponent, UserWithEmailComponent, VerifyChecklistComponent, VideoUploadBarComponent, VirtualTableComponent, addClass, announceToScreenReader, capitalize, closestElement, copyToClipboard, createAuthError, createAuthorizationError, createConfigError, createError, createNetworkError, createValidationError, disableBodyScroll, escapeHtml, formatCount, formatErrorForLog, formatErrorMessage, generateLinkedIds, generateUniqueId, getAriaCurrent, getButtonAriaAttributes, getComputedStyleValue, getDialogAriaAttributes, getFocusableElements, getInitials, getInputAriaAttributes, getOptionAriaAttributes, getProgressAriaAttributes, getScrollParent, getTabAriaAttributes, getVisuallyHiddenStyles, handleError, hasClass, isBlank, isBrowser, isElementVisible, isNotBlank, isPecbError, isRecoverableError, matchesSelector, pluralize, prefersHighContrast, prefersReducedMotion, registerErrorHandler, removeClass, scrollIntoView, slugify, stripHtml, toCamelCase, toKebabCase, toPascalCase, toSnakeCase, toggleClass, trapFocus, truncate, tryAsync, trySync, wrapError };
19113
+ export { AccordionItemComponent, AccordionSmallComponent, AddButtonComponent, AdminHeaderComponent, AdminHeaderFieldTemplateDirective, AffixComponent, AlertComponent, AnchorComponent, ApplicationStatusBarComponent, AuditorStatusComponent, AuthorDateTimeComponent, BackToTopComponent, BadgeComponent, BlurDirective, BottomSheetComponent, BreadcrumbsComponent, ButtonComponent, ButtonGroupComponent, ButtonGroupItemComponent, CancelUpdateButtonsComponent, CardBodyComponent, CardComponent, CardFooterComponent, CardHeaderComponent, CertificateUploadBarComponent, CheckDeleteIconComponent, CheckboxComponent, CheckboxDisplayComponent, CodeInputComponent, CodeSnippetComponent, ColorPaletteComponent, ConfirmationComponent, ContentTypeTagComponent, CourseContentPanelComponent, CoursePlayerComponent, DEFAULT_COURSE_CONTENT_PANEL_LABELS, DEFAULT_LANGUAGES, DEFAULT_VIDEO_PLAYER_LABELS, DEFAULT_VIDEO_PLAYER_RATES, DashboardGridComponent, DatepickerComponent, DividerComponent, DropdownComponent, EMPTY_STATE_MAX_BUTTONS, EditButtonComponent, EditCoverPhotoComponent, EmptyStateComponent, ExpandableRowTableComponent, FileUploadComponent, FilterColumnsComponent, FloatButtonComponent, FloatButtonItemComponent, FullscreenModalComponent, FullscreenModalContentDirective, FullscreenModalFooterDirective, FullscreenModalHeaderDirective, GeneralComponent, GridComponent, GridItemComponent, HeaderActionsComponent, HeaderComponent, HeaderDividerComponent, HeaderLanguageComponent, HeaderSearchComponent, HeaderUserComponent, HierarchicalTableComponent, HorizontalStepsComponent, IconComponent, IconRegistry, IconTagComponent, InformationBoxComponent, InputComponent, LanguageDropdownComponent, LinkButtonComponent, LoadingService, MediaComponent, MessageBubbleComponent, MessageItemComponent, MetricsCardComponent, MiniIconButtonComponent, NoResultsComponent, NoteSidebarItemComponent, NotesPanelComponent, NotificationService, NotificationStatusLinkComponent, PECB_COLOR_PALETTE, PECB_CUSTOM_ICONS, PECB_FONT_STYLES, PECB_ICONS, PECB_TYPE_SCALE, PaginationComponent, PriceMethodComponent, ProfileComponent, ProfileElementsCardComponent, ProfileGroupComponent, ProgressBarComponent, ProgressCircleComponent, ProjectLayoutComponent, QuantitySelectorComponent, QuestionTypeTagComponent, RadioComponent, RadioDisplayComponent, RatingNumberComponent, ReasonForReturnComponent, RequestSentByComponent, RequestStatusBarComponent, ResultPageComponent, RightModalComponent, RightModalContentDirective, RightModalFooterDirective, RightModalHeaderDirective, ShadowDirective, SidebarComponent, SkeletonComponent, SlidePointsComponent, SpacerComponent, SpinnerComponent, StandardsPdfCardComponent, StatisticsCardComponent, StatusComponent, StepperComponent, TabComponent, TableComponent, TagComponent, TestimonialComponent, TextFormFieldComponent, ThemeService, ToggleComponent, ToolbarBarComponent, TooltipComponent, TooltipDirective, TourComponent, TranscriptLineComponent, TypographyComponent, UserWithEmailComponent, VerifyChecklistComponent, VideoPlayerComponent, VideoUploadBarComponent, VirtualTableComponent, addClass, announceToScreenReader, capitalize, closestElement, copyToClipboard, courseModuleStats, courseProgress, createAuthError, createAuthorizationError, createConfigError, createError, createNetworkError, createValidationError, disableBodyScroll, escapeHtml, findCourseLesson, findCourseModule, formatCount, formatCourseDuration, formatCourseTime, formatErrorForLog, formatErrorMessage, formatVideoTime, generateLinkedIds, generateUniqueId, getAriaCurrent, getButtonAriaAttributes, getComputedStyleValue, getDialogAriaAttributes, getFocusableElements, getInitials, getInputAriaAttributes, getOptionAriaAttributes, getProgressAriaAttributes, getScrollParent, getTabAriaAttributes, getVisuallyHiddenStyles, handleError, hasClass, isBlank, isBrowser, isElementVisible, isNotBlank, isPecbError, isRecoverableError, matchesSelector, parseLessonPartTitle, pluralize, prefersHighContrast, prefersReducedMotion, registerErrorHandler, removeClass, resolveLessonStatus, scrollIntoView, slugify, stripHtml, toCamelCase, toKebabCase, toPascalCase, toSnakeCase, toggleClass, trapFocus, truncate, tryAsync, trySync, wrapError };
17161
19114
  //# sourceMappingURL=pecb-ui-components.mjs.map