@pecb-ui/components 1.1.10 → 1.1.12

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,7 +1,7 @@
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, ViewContainerRef, viewChild, ViewEncapsulation, Directive, untracked, ElementRef, ApplicationRef, EnvironmentInjector, createComponent, contentChildren, 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';
@@ -4525,11 +4525,11 @@ class TableComponent {
4525
4525
  this.selectionChange.emit({ selectedRows, selectedIndices: this.selectedIndices() });
4526
4526
  }
4527
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 }); }
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}.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 }); }
4529
4529
  }
4530
4530
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: TableComponent, decorators: [{
4531
4531
  type: Component,
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}.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"] }]
4533
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: [{
4534
4534
  type: ContentChild,
4535
4535
  args: ['emptyTemplate']
@@ -5400,6 +5400,13 @@ class DropdownComponent {
5400
5400
  this.searchQuery = signal('', ...(ngDevMode ? [{ debugName: "searchQuery" }] : []));
5401
5401
  this.selectedValues = signal([], ...(ngDevMode ? [{ debugName: "selectedValues" }] : []));
5402
5402
  this.focusedIndex = signal(-1, ...(ngDevMode ? [{ debugName: "focusedIndex" }] : []));
5403
+ /**
5404
+ * Width the overlay panel is pinned to, so it lines up edge-to-edge with the
5405
+ * trigger. Measured with `getBoundingClientRect()` rather than `offsetWidth`
5406
+ * because the latter rounds to whole pixels and leaves the panel a hair off in
5407
+ * fluid layouts.
5408
+ */
5409
+ this.triggerWidth = signal(0, ...(ngDevMode ? [{ debugName: "triggerWidth" }] : []));
5403
5410
  /** Overlay positions: prefer below the trigger, flip above when there's no room. */
5404
5411
  this.overlayPositions = [
5405
5412
  { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4 },
@@ -5449,11 +5456,23 @@ class DropdownComponent {
5449
5456
  this.onChange(cleared);
5450
5457
  this.selectionChange.emit(cleared);
5451
5458
  }
5459
+ /** Re-measure the trigger so a resize doesn't leave an open panel misaligned. */
5460
+ syncTriggerWidth() {
5461
+ if (!this.isOpen())
5462
+ return;
5463
+ this.measureTrigger();
5464
+ }
5465
+ measureTrigger() {
5466
+ const width = this.triggerEl?.nativeElement.getBoundingClientRect().width;
5467
+ if (width)
5468
+ this.triggerWidth.set(width);
5469
+ }
5452
5470
  toggleDropdown() {
5453
5471
  if (this.disabled())
5454
5472
  return;
5455
5473
  this.isOpen.set(!this.isOpen());
5456
5474
  if (this.isOpen()) {
5475
+ this.measureTrigger();
5457
5476
  this.onTouched();
5458
5477
  this.focusedIndex.set(-1);
5459
5478
  if (this.searchable()) {
@@ -5556,13 +5575,13 @@ class DropdownComponent {
5556
5575
  registerOnTouched(fn) { this.onTouched = fn; }
5557
5576
  setDisabledState(isDisabled) { this.disabled.set(isDisabled); }
5558
5577
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
5559
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.16", type: DropdownComponent, isStandalone: true, selector: "pecb-dropdown", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, errorMessage: { classPropertyName: "errorMessage", publicName: "errorMessage", isSignal: true, isRequired: false, transformFunction: null }, helperText: { classPropertyName: "helperText", publicName: "helperText", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, clearSelectionAriaLabel: { classPropertyName: "clearSelectionAriaLabel", publicName: "clearSelectionAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, noResultsText: { classPropertyName: "noResultsText", publicName: "noResultsText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { disabled: "disabledChange", selectionChange: "selectionChange" }, providers: [
5578
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.16", type: DropdownComponent, isStandalone: true, selector: "pecb-dropdown", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, errorMessage: { classPropertyName: "errorMessage", publicName: "errorMessage", isSignal: true, isRequired: false, transformFunction: null }, helperText: { classPropertyName: "helperText", publicName: "helperText", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, clearSelectionAriaLabel: { classPropertyName: "clearSelectionAriaLabel", publicName: "clearSelectionAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, noResultsText: { classPropertyName: "noResultsText", publicName: "noResultsText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { disabled: "disabledChange", selectionChange: "selectionChange" }, host: { listeners: { "window:resize": "syncTriggerWidth()" } }, providers: [
5560
5579
  {
5561
5580
  provide: NG_VALUE_ACCESSOR,
5562
5581
  useExisting: forwardRef(() => DropdownComponent),
5563
5582
  multi: true
5564
5583
  }
5565
- ], viewQueries: [{ propertyName: "searchInput", first: true, predicate: ["searchInput"], descendants: true }], ngImport: i0, template: "<div [class]=\"wrapperClasses.join(' ')\">\n <!-- Field title \u2014 sits on the trigger's top border (Figma \"Field - Master\") -->\n <label\n *ngIf=\"label()\"\n [attr.for]=\"id()\"\n class=\"pecb-dropdown__label\"\n >\n {{ label() }}<span *ngIf=\"required()\" class=\"pecb-dropdown__required\">*</span>\n </label>\n\n <!-- Dropdown Trigger -->\n <div\n class=\"pecb-dropdown__trigger\"\n cdkOverlayOrigin\n #ddTrigger=\"cdkOverlayOrigin\"\n #ddTriggerEl\n [attr.tabindex]=\"disabled() ? -1 : 0\"\n [attr.aria-label]=\"label()\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-haspopup]=\"true\"\n [attr.aria-disabled]=\"disabled()\"\n (click)=\"toggleDropdown()\"\n (keydown)=\"onKeyDown($event)\"\n >\n <span class=\"pecb-dropdown__value\">{{ displayText || placeholder() }}</span>\n\n <!-- Clear button (shown whenever a value is set, single or multi) -->\n <span *ngIf=\"hasValue && !disabled()\" class=\"pecb-dropdown__clear\" (click)=\"clearAll($event)\" [attr.aria-label]=\"clearSelectionAriaLabel()\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M11 1L1 11M1 1L11 11\" stroke=\"#616161\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </span>\n\n <!-- Dropdown Arrow -->\n <span class=\"pecb-dropdown__arrow\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 18 18\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M4.5 6.75L9 11.25L13.5 6.75\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </span>\n </div>\n\n <!-- Dropdown Panel \u2014 CDK overlay so it flips near edges and escapes overflow. -->\n <ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"ddTrigger\"\n [cdkConnectedOverlayOpen]=\"isOpen()\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayWidth]=\"ddTriggerEl.offsetWidth\"\n [cdkConnectedOverlayHasBackdrop]=\"true\"\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayPush]=\"true\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n (backdropClick)=\"closeDropdown()\"\n (detach)=\"closeDropdown()\"\n >\n <div\n class=\"pecb-dropdown__panel\"\n [style.max-height.px]=\"maxHeight()\"\n >\n <!-- Search Input -->\n <div *ngIf=\"searchable()\" class=\"pecb-dropdown__search\">\n <svg class=\"pecb-dropdown__search-icon\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"currentColor\">\n <path d=\"M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z\"/>\n </svg>\n <input\n #searchInput\n type=\"text\"\n class=\"pecb-dropdown__search-input\"\n [placeholder]=\"searchPlaceholder()\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($event)\"\n (keydown)=\"onKeyDown($event)\"\n />\n </div>\n\n <!-- Options List -->\n <div class=\"pecb-dropdown__options\">\n <div\n *ngFor=\"let option of filteredOptions; let i = index\"\n class=\"pecb-dropdown__option\"\n [class.pecb-dropdown__option--selected]=\"isSelected(option)\"\n [class.pecb-dropdown__option--disabled]=\"option.disabled\"\n [class.pecb-dropdown__option--focused]=\"i === focusedIndex()\"\n (click)=\"selectOption(option)\"\n >\n <!-- Checkbox for multi-select -->\n <span *ngIf=\"multiple()\" class=\"pecb-dropdown__checkbox\">\n <span class=\"pecb-dropdown__checkbox-control\" [class.pecb-dropdown__checkbox-control--checked]=\"isSelected(option)\">\n <svg *ngIf=\"isSelected(option)\" class=\"pecb-dropdown__checkbox-icon\" width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\">\n <path d=\"M10 3L4.5 8.5L2 6\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </span>\n </span>\n\n <!-- Option Label -->\n <span class=\"pecb-dropdown__option-label\">{{ option.label }}</span>\n </div>\n\n <!-- No results message -->\n <div *ngIf=\"filteredOptions.length === 0\" class=\"pecb-dropdown__no-results\">\n {{ noResultsText() }}\n </div>\n </div>\n </div>\n </ng-template>\n\n <!-- Helper Text / Error Message -->\n <div\n *ngIf=\"helperText() || errorMessage()\"\n [id]=\"id() + '-description'\"\n class=\"pecb-dropdown__description\"\n [class.pecb-dropdown__description--error]=\"error() && errorMessage()\"\n >\n <span *ngIf=\"error() && errorMessage()\">{{ errorMessage() }}</span>\n <span *ngIf=\"!error() && helperText()\">{{ helperText() }}</span>\n </div>\n</div>\n", styles: [".pecb-dropdown{position:relative;display:inline-block;width:100%}.pecb-dropdown__trigger{position:relative;display:flex;align-items:center;width:100%;min-height:40px;padding:12px 32px 12px 16px;font-family:inherit;font-size:14px;color:#313131;background-color:#fff;border:1px solid #c2c2c2;border-radius:8px;cursor:pointer;-webkit-user-select:none;user-select:none;outline:none;box-sizing:border-box;transition-property:border-color,box-shadow;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-dropdown__trigger:hover:not([aria-disabled=true]){border-color:#919191}.pecb-dropdown__trigger:focus-visible{border-color:#212427;box-shadow:0 0 0 3px #2124271a}.pecb-dropdown__value{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#616161}.pecb-dropdown__label{position:absolute;left:16px;top:0;transform:translateY(-50%);font-family:inherit;font-size:12px;font-weight:400;color:#212427;background-color:#fff;padding:0 6px;pointer-events:none;z-index:1;white-space:nowrap}.pecb-dropdown__required{color:inherit;margin-left:2px}.pecb-dropdown__arrow{position:absolute;right:12px;display:flex;align-items:center;justify-content:center;color:#919191;transition-property:transform;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-dropdown__arrow svg{display:block}.pecb-dropdown__panel{background-color:#fff;border:1px solid #c2c2c2;border-radius:8px;box-shadow:0 4px 12px #0000001a;overflow:hidden;box-sizing:border-box;display:flex;flex-direction:column}.pecb-dropdown__search{position:relative;padding:12px 12px 8px;border-bottom:1px solid #f7f5f5;flex-shrink:0}.pecb-dropdown__search-icon{position:absolute;left:18px;top:50%;transform:translateY(-50%);color:#c2c2c2;pointer-events:none;z-index:1}.pecb-dropdown__search-input{width:100%;padding:8px 12px 8px 32px;font-family:inherit;font-size:14px;color:#313131;background-color:#fff;border:1px solid #f7f5f5;border-radius:8px;outline:none;box-sizing:border-box;transition-property:border-color,box-shadow;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-dropdown__search-input::placeholder{color:#c2c2c2;font-size:14px}.pecb-dropdown__search-input:hover{border-color:#ececec}.pecb-dropdown__search-input:focus{border-color:#c2c2c2;box-shadow:0 0 0 1px #c2c2c21a}.pecb-dropdown__options{flex:1;overflow-y:auto;overflow-x:hidden;padding:8px 0;min-height:0}.pecb-dropdown__options::-webkit-scrollbar{width:6px}.pecb-dropdown__options::-webkit-scrollbar-track{background-color:#f4f4f4}.pecb-dropdown__options::-webkit-scrollbar-thumb{background-color:#212427;border-radius:3px}.pecb-dropdown__options::-webkit-scrollbar-thumb:hover{background-color:#0e1011}.pecb-dropdown__option{display:flex;align-items:center;gap:12px;padding:12px 16px;font-family:inherit;font-size:14px;color:#616161;cursor:pointer;transition-property:background-color;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-dropdown__option:hover:not(.pecb-dropdown__option--disabled){background-color:#f4f4f4}.pecb-dropdown__option--selected{background-color:#f7f5f5}.pecb-dropdown__option--selected:hover{background-color:#ececec}.pecb-dropdown__option--focused{background-color:#f4f4f4}.pecb-dropdown__option--disabled{color:#c2c2c2;cursor:not-allowed;opacity:.5}.pecb-dropdown__option-label{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pecb-dropdown__checkbox{display:flex;align-items:center;flex-shrink:0}.pecb-dropdown__checkbox-control{position:relative;display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border:1.5px solid #c2c2c2;border-radius:6px;background-color:#fff;transition-property:border-color,background-color;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-dropdown__checkbox-control--checked{border-color:#212427;background-color:#212427}.pecb-dropdown__checkbox-icon{color:#fff}.pecb-dropdown__clear{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;position:absolute;right:32px;color:#919191;cursor:pointer;transition-property:color;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-dropdown__clear:hover{color:#313131}.pecb-dropdown--has-value .pecb-dropdown__trigger{padding-right:48px}.pecb-dropdown__no-results{padding:16px;text-align:center;font-family:inherit;font-size:14px;color:#c2c2c2}.pecb-dropdown__description{margin-top:8px;font-family:inherit;font-size:12px;color:#919191}.pecb-dropdown__description--error{color:#a11e29}.pecb-dropdown--open .pecb-dropdown__trigger{border-color:#212427;box-shadow:0 0 0 3px #2124271a}.pecb-dropdown--open .pecb-dropdown__arrow{transform:rotate(180deg)}.pecb-dropdown--open.pecb-dropdown--multiple .pecb-dropdown__trigger{border-color:#c2c2c2;box-shadow:none}.pecb-dropdown--open.pecb-dropdown--multiple .pecb-dropdown__panel{border-radius:8px;border-color:#c2c2c2;box-shadow:0 4px 12px #0000001a}.pecb-dropdown--error .pecb-dropdown__trigger{border-color:#a11e29}.pecb-dropdown--error .pecb-dropdown__trigger:focus-visible{box-shadow:0 0 0 3px #a11e291a}.pecb-dropdown--error .pecb-dropdown__label{color:#a11e29}.pecb-dropdown--disabled .pecb-dropdown__trigger{background-color:#f4f4f4;border-color:#ececec;cursor:not-allowed;opacity:.6}.pecb-dropdown--disabled .pecb-dropdown__value{color:#c2c2c2}.pecb-dropdown--disabled .pecb-dropdown__arrow{color:#ececec}.pecb-dropdown--sm .pecb-dropdown__trigger{min-height:32px;padding:8px 24px 8px 12px;font-size:12px}.pecb-dropdown--sm .pecb-dropdown__label{left:12px}.pecb-dropdown--sm .pecb-dropdown__arrow{right:8px}.pecb-dropdown--sm .pecb-dropdown__option{padding:8px 12px;font-size:12px}.pecb-dropdown--md .pecb-dropdown__trigger{min-height:40px}.pecb-dropdown--lg .pecb-dropdown__trigger{min-height:48px;padding:16px 40px 16px 18px;font-size:16px}.pecb-dropdown--lg .pecb-dropdown__label{left:18px}.pecb-dropdown--lg .pecb-dropdown__arrow{right:16px}.pecb-dropdown--lg .pecb-dropdown__option{padding:12px 18px;font-size:16px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i2.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i2.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
5584
+ ], viewQueries: [{ propertyName: "searchInput", first: true, predicate: ["searchInput"], descendants: true }, { propertyName: "triggerEl", first: true, predicate: ["ddTriggerEl"], descendants: true }], ngImport: i0, template: "<div [class]=\"wrapperClasses.join(' ')\">\n <!-- Field title \u2014 sits on the trigger's top border (Figma \"Field - Master\") -->\n <label\n *ngIf=\"label()\"\n [attr.for]=\"id()\"\n class=\"pecb-dropdown__label\"\n >\n {{ label() }}<span *ngIf=\"required()\" class=\"pecb-dropdown__required\">*</span>\n </label>\n\n <!-- Dropdown Trigger -->\n <div\n class=\"pecb-dropdown__trigger\"\n cdkOverlayOrigin\n #ddTrigger=\"cdkOverlayOrigin\"\n #ddTriggerEl\n [attr.tabindex]=\"disabled() ? -1 : 0\"\n [attr.aria-label]=\"label()\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-haspopup]=\"true\"\n [attr.aria-disabled]=\"disabled()\"\n (click)=\"toggleDropdown()\"\n (keydown)=\"onKeyDown($event)\"\n >\n <span class=\"pecb-dropdown__value\">{{ displayText || placeholder() }}</span>\n\n <!-- Clear button (shown whenever a value is set, single or multi) -->\n <span *ngIf=\"hasValue && !disabled()\" class=\"pecb-dropdown__clear\" (click)=\"clearAll($event)\" [attr.aria-label]=\"clearSelectionAriaLabel()\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M11 1L1 11M1 1L11 11\" stroke=\"#616161\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </span>\n\n <!-- Dropdown Arrow -->\n <span class=\"pecb-dropdown__arrow\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 18 18\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M4.5 6.75L9 11.25L13.5 6.75\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </span>\n </div>\n\n <!-- Dropdown Panel \u2014 CDK overlay so it flips near edges and escapes overflow. -->\n <ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"ddTrigger\"\n [cdkConnectedOverlayOpen]=\"isOpen()\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayWidth]=\"triggerWidth()\"\n [cdkConnectedOverlayHasBackdrop]=\"true\"\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayPush]=\"true\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n (backdropClick)=\"closeDropdown()\"\n (detach)=\"closeDropdown()\"\n >\n <div\n class=\"pecb-dropdown__panel\"\n [style.max-height.px]=\"maxHeight()\"\n >\n <!-- Search Input -->\n <div *ngIf=\"searchable()\" class=\"pecb-dropdown__search\">\n <svg class=\"pecb-dropdown__search-icon\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"currentColor\">\n <path d=\"M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z\"/>\n </svg>\n <input\n #searchInput\n type=\"text\"\n class=\"pecb-dropdown__search-input\"\n [placeholder]=\"searchPlaceholder()\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($event)\"\n (keydown)=\"onKeyDown($event)\"\n />\n </div>\n\n <!-- Options List -->\n <div class=\"pecb-dropdown__options\">\n <div\n *ngFor=\"let option of filteredOptions; let i = index\"\n class=\"pecb-dropdown__option\"\n [class.pecb-dropdown__option--selected]=\"isSelected(option)\"\n [class.pecb-dropdown__option--disabled]=\"option.disabled\"\n [class.pecb-dropdown__option--focused]=\"i === focusedIndex()\"\n (click)=\"selectOption(option)\"\n >\n <!-- Checkbox for multi-select -->\n <span *ngIf=\"multiple()\" class=\"pecb-dropdown__checkbox\">\n <span class=\"pecb-dropdown__checkbox-control\" [class.pecb-dropdown__checkbox-control--checked]=\"isSelected(option)\">\n <svg *ngIf=\"isSelected(option)\" class=\"pecb-dropdown__checkbox-icon\" width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\">\n <path d=\"M10 3L4.5 8.5L2 6\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </span>\n </span>\n\n <!-- Option Label -->\n <span class=\"pecb-dropdown__option-label\">{{ option.label }}</span>\n </div>\n\n <!-- No results message -->\n <div *ngIf=\"filteredOptions.length === 0\" class=\"pecb-dropdown__no-results\">\n {{ noResultsText() }}\n </div>\n </div>\n </div>\n </ng-template>\n\n <!-- Helper Text / Error Message -->\n <div\n *ngIf=\"helperText() || errorMessage()\"\n [id]=\"id() + '-description'\"\n class=\"pecb-dropdown__description\"\n [class.pecb-dropdown__description--error]=\"error() && errorMessage()\"\n >\n <span *ngIf=\"error() && errorMessage()\">{{ errorMessage() }}</span>\n <span *ngIf=\"!error() && helperText()\">{{ helperText() }}</span>\n </div>\n</div>\n", styles: [".pecb-dropdown{position:relative;display:inline-block;width:100%}.pecb-dropdown__trigger{position:relative;display:flex;align-items:center;width:100%;min-height:40px;padding:12px 32px 12px 16px;font-family:inherit;font-size:14px;color:#313131;background-color:#fff;border:1px solid #c2c2c2;border-radius:8px;cursor:pointer;-webkit-user-select:none;user-select:none;outline:none;box-sizing:border-box;transition-property:border-color,box-shadow;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-dropdown__trigger:hover:not([aria-disabled=true]){border-color:#919191}.pecb-dropdown__trigger:focus-visible{border-color:#212427;box-shadow:0 0 0 3px #2124271a}.pecb-dropdown__value{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#616161}.pecb-dropdown__label{position:absolute;left:16px;top:0;transform:translateY(-50%);font-family:inherit;font-size:12px;font-weight:400;color:#212427;background-color:#fff;padding:0 6px;pointer-events:none;z-index:1;white-space:nowrap}.pecb-dropdown__required{color:inherit;margin-left:2px}.pecb-dropdown__arrow{position:absolute;right:12px;display:flex;align-items:center;justify-content:center;color:#919191;transition-property:transform;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-dropdown__arrow svg{display:block}.pecb-dropdown__panel{width:100%;background-color:#fff;border:1px solid #c2c2c2;border-radius:8px;box-shadow:0 4px 12px #0000001a;overflow:hidden;box-sizing:border-box;display:flex;flex-direction:column}.pecb-dropdown__search{position:relative;padding:12px 12px 8px;border-bottom:1px solid #f7f5f5;flex-shrink:0}.pecb-dropdown__search-icon{position:absolute;left:18px;top:50%;transform:translateY(-50%);color:#c2c2c2;pointer-events:none;z-index:1}.pecb-dropdown__search-input{width:100%;padding:8px 12px 8px 32px;font-family:inherit;font-size:14px;color:#313131;background-color:#fff;border:1px solid #f7f5f5;border-radius:8px;outline:none;box-sizing:border-box;transition-property:border-color,box-shadow;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-dropdown__search-input::placeholder{color:#c2c2c2;font-size:14px}.pecb-dropdown__search-input:hover{border-color:#ececec}.pecb-dropdown__search-input:focus{border-color:#c2c2c2;box-shadow:0 0 0 1px #c2c2c21a}.pecb-dropdown__options{flex:1;overflow-y:auto;overflow-x:hidden;padding:8px 0;min-height:0}.pecb-dropdown__options::-webkit-scrollbar{width:6px}.pecb-dropdown__options::-webkit-scrollbar-track{background-color:#f4f4f4}.pecb-dropdown__options::-webkit-scrollbar-thumb{background-color:#212427;border-radius:3px}.pecb-dropdown__options::-webkit-scrollbar-thumb:hover{background-color:#0e1011}.pecb-dropdown__option{display:flex;align-items:center;gap:12px;padding:12px 16px;font-family:inherit;font-size:14px;color:#616161;cursor:pointer;transition-property:background-color;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-dropdown__option:hover:not(.pecb-dropdown__option--disabled){background-color:#f4f4f4}.pecb-dropdown__option--selected{background-color:#f7f5f5}.pecb-dropdown__option--selected:hover{background-color:#ececec}.pecb-dropdown__option--focused{background-color:#f4f4f4}.pecb-dropdown__option--disabled{color:#c2c2c2;cursor:not-allowed;opacity:.5}.pecb-dropdown__option-label{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pecb-dropdown__checkbox{display:flex;align-items:center;flex-shrink:0}.pecb-dropdown__checkbox-control{position:relative;display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border:1.5px solid #c2c2c2;border-radius:6px;background-color:#fff;transition-property:border-color,background-color;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-dropdown__checkbox-control--checked{border-color:#212427;background-color:#212427}.pecb-dropdown__checkbox-icon{color:#fff}.pecb-dropdown__clear{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;position:absolute;right:32px;color:#919191;cursor:pointer;transition-property:color;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-dropdown__clear:hover{color:#313131}.pecb-dropdown--has-value .pecb-dropdown__trigger{padding-right:48px}.pecb-dropdown__no-results{padding:16px;text-align:center;font-family:inherit;font-size:14px;color:#c2c2c2}.pecb-dropdown__description{margin-top:8px;font-family:inherit;font-size:12px;color:#919191}.pecb-dropdown__description--error{color:#a11e29}.pecb-dropdown--open .pecb-dropdown__trigger{border-color:#212427;box-shadow:0 0 0 3px #2124271a}.pecb-dropdown--open .pecb-dropdown__arrow{transform:rotate(180deg)}.pecb-dropdown--open.pecb-dropdown--multiple .pecb-dropdown__trigger{border-color:#c2c2c2;box-shadow:none}.pecb-dropdown--open.pecb-dropdown--multiple .pecb-dropdown__panel{border-radius:8px;border-color:#c2c2c2;box-shadow:0 4px 12px #0000001a}.pecb-dropdown--error .pecb-dropdown__trigger{border-color:#a11e29}.pecb-dropdown--error .pecb-dropdown__trigger:focus-visible{box-shadow:0 0 0 3px #a11e291a}.pecb-dropdown--error .pecb-dropdown__label{color:#a11e29}.pecb-dropdown--disabled .pecb-dropdown__trigger{background-color:#f4f4f4;border-color:#ececec;cursor:not-allowed;opacity:.6}.pecb-dropdown--disabled .pecb-dropdown__value{color:#c2c2c2}.pecb-dropdown--disabled .pecb-dropdown__arrow{color:#ececec}.pecb-dropdown--sm .pecb-dropdown__trigger{min-height:32px;padding:8px 24px 8px 12px;font-size:12px}.pecb-dropdown--sm .pecb-dropdown__label{left:12px}.pecb-dropdown--sm .pecb-dropdown__arrow{right:8px}.pecb-dropdown--sm .pecb-dropdown__option{padding:8px 12px;font-size:12px}.pecb-dropdown--md .pecb-dropdown__trigger{min-height:40px}.pecb-dropdown--lg .pecb-dropdown__trigger{min-height:48px;padding:16px 40px 16px 18px;font-size:16px}.pecb-dropdown--lg .pecb-dropdown__label{left:18px}.pecb-dropdown--lg .pecb-dropdown__arrow{right:16px}.pecb-dropdown--lg .pecb-dropdown__option{padding:12px 18px;font-size:16px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i2.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i2.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
5566
5585
  }
5567
5586
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DropdownComponent, decorators: [{
5568
5587
  type: Component,
@@ -5572,11 +5591,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
5572
5591
  useExisting: forwardRef(() => DropdownComponent),
5573
5592
  multi: true
5574
5593
  }
5575
- ], template: "<div [class]=\"wrapperClasses.join(' ')\">\n <!-- Field title \u2014 sits on the trigger's top border (Figma \"Field - Master\") -->\n <label\n *ngIf=\"label()\"\n [attr.for]=\"id()\"\n class=\"pecb-dropdown__label\"\n >\n {{ label() }}<span *ngIf=\"required()\" class=\"pecb-dropdown__required\">*</span>\n </label>\n\n <!-- Dropdown Trigger -->\n <div\n class=\"pecb-dropdown__trigger\"\n cdkOverlayOrigin\n #ddTrigger=\"cdkOverlayOrigin\"\n #ddTriggerEl\n [attr.tabindex]=\"disabled() ? -1 : 0\"\n [attr.aria-label]=\"label()\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-haspopup]=\"true\"\n [attr.aria-disabled]=\"disabled()\"\n (click)=\"toggleDropdown()\"\n (keydown)=\"onKeyDown($event)\"\n >\n <span class=\"pecb-dropdown__value\">{{ displayText || placeholder() }}</span>\n\n <!-- Clear button (shown whenever a value is set, single or multi) -->\n <span *ngIf=\"hasValue && !disabled()\" class=\"pecb-dropdown__clear\" (click)=\"clearAll($event)\" [attr.aria-label]=\"clearSelectionAriaLabel()\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M11 1L1 11M1 1L11 11\" stroke=\"#616161\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </span>\n\n <!-- Dropdown Arrow -->\n <span class=\"pecb-dropdown__arrow\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 18 18\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M4.5 6.75L9 11.25L13.5 6.75\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </span>\n </div>\n\n <!-- Dropdown Panel \u2014 CDK overlay so it flips near edges and escapes overflow. -->\n <ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"ddTrigger\"\n [cdkConnectedOverlayOpen]=\"isOpen()\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayWidth]=\"ddTriggerEl.offsetWidth\"\n [cdkConnectedOverlayHasBackdrop]=\"true\"\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayPush]=\"true\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n (backdropClick)=\"closeDropdown()\"\n (detach)=\"closeDropdown()\"\n >\n <div\n class=\"pecb-dropdown__panel\"\n [style.max-height.px]=\"maxHeight()\"\n >\n <!-- Search Input -->\n <div *ngIf=\"searchable()\" class=\"pecb-dropdown__search\">\n <svg class=\"pecb-dropdown__search-icon\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"currentColor\">\n <path d=\"M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z\"/>\n </svg>\n <input\n #searchInput\n type=\"text\"\n class=\"pecb-dropdown__search-input\"\n [placeholder]=\"searchPlaceholder()\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($event)\"\n (keydown)=\"onKeyDown($event)\"\n />\n </div>\n\n <!-- Options List -->\n <div class=\"pecb-dropdown__options\">\n <div\n *ngFor=\"let option of filteredOptions; let i = index\"\n class=\"pecb-dropdown__option\"\n [class.pecb-dropdown__option--selected]=\"isSelected(option)\"\n [class.pecb-dropdown__option--disabled]=\"option.disabled\"\n [class.pecb-dropdown__option--focused]=\"i === focusedIndex()\"\n (click)=\"selectOption(option)\"\n >\n <!-- Checkbox for multi-select -->\n <span *ngIf=\"multiple()\" class=\"pecb-dropdown__checkbox\">\n <span class=\"pecb-dropdown__checkbox-control\" [class.pecb-dropdown__checkbox-control--checked]=\"isSelected(option)\">\n <svg *ngIf=\"isSelected(option)\" class=\"pecb-dropdown__checkbox-icon\" width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\">\n <path d=\"M10 3L4.5 8.5L2 6\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </span>\n </span>\n\n <!-- Option Label -->\n <span class=\"pecb-dropdown__option-label\">{{ option.label }}</span>\n </div>\n\n <!-- No results message -->\n <div *ngIf=\"filteredOptions.length === 0\" class=\"pecb-dropdown__no-results\">\n {{ noResultsText() }}\n </div>\n </div>\n </div>\n </ng-template>\n\n <!-- Helper Text / Error Message -->\n <div\n *ngIf=\"helperText() || errorMessage()\"\n [id]=\"id() + '-description'\"\n class=\"pecb-dropdown__description\"\n [class.pecb-dropdown__description--error]=\"error() && errorMessage()\"\n >\n <span *ngIf=\"error() && errorMessage()\">{{ errorMessage() }}</span>\n <span *ngIf=\"!error() && helperText()\">{{ helperText() }}</span>\n </div>\n</div>\n", styles: [".pecb-dropdown{position:relative;display:inline-block;width:100%}.pecb-dropdown__trigger{position:relative;display:flex;align-items:center;width:100%;min-height:40px;padding:12px 32px 12px 16px;font-family:inherit;font-size:14px;color:#313131;background-color:#fff;border:1px solid #c2c2c2;border-radius:8px;cursor:pointer;-webkit-user-select:none;user-select:none;outline:none;box-sizing:border-box;transition-property:border-color,box-shadow;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-dropdown__trigger:hover:not([aria-disabled=true]){border-color:#919191}.pecb-dropdown__trigger:focus-visible{border-color:#212427;box-shadow:0 0 0 3px #2124271a}.pecb-dropdown__value{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#616161}.pecb-dropdown__label{position:absolute;left:16px;top:0;transform:translateY(-50%);font-family:inherit;font-size:12px;font-weight:400;color:#212427;background-color:#fff;padding:0 6px;pointer-events:none;z-index:1;white-space:nowrap}.pecb-dropdown__required{color:inherit;margin-left:2px}.pecb-dropdown__arrow{position:absolute;right:12px;display:flex;align-items:center;justify-content:center;color:#919191;transition-property:transform;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-dropdown__arrow svg{display:block}.pecb-dropdown__panel{background-color:#fff;border:1px solid #c2c2c2;border-radius:8px;box-shadow:0 4px 12px #0000001a;overflow:hidden;box-sizing:border-box;display:flex;flex-direction:column}.pecb-dropdown__search{position:relative;padding:12px 12px 8px;border-bottom:1px solid #f7f5f5;flex-shrink:0}.pecb-dropdown__search-icon{position:absolute;left:18px;top:50%;transform:translateY(-50%);color:#c2c2c2;pointer-events:none;z-index:1}.pecb-dropdown__search-input{width:100%;padding:8px 12px 8px 32px;font-family:inherit;font-size:14px;color:#313131;background-color:#fff;border:1px solid #f7f5f5;border-radius:8px;outline:none;box-sizing:border-box;transition-property:border-color,box-shadow;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-dropdown__search-input::placeholder{color:#c2c2c2;font-size:14px}.pecb-dropdown__search-input:hover{border-color:#ececec}.pecb-dropdown__search-input:focus{border-color:#c2c2c2;box-shadow:0 0 0 1px #c2c2c21a}.pecb-dropdown__options{flex:1;overflow-y:auto;overflow-x:hidden;padding:8px 0;min-height:0}.pecb-dropdown__options::-webkit-scrollbar{width:6px}.pecb-dropdown__options::-webkit-scrollbar-track{background-color:#f4f4f4}.pecb-dropdown__options::-webkit-scrollbar-thumb{background-color:#212427;border-radius:3px}.pecb-dropdown__options::-webkit-scrollbar-thumb:hover{background-color:#0e1011}.pecb-dropdown__option{display:flex;align-items:center;gap:12px;padding:12px 16px;font-family:inherit;font-size:14px;color:#616161;cursor:pointer;transition-property:background-color;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-dropdown__option:hover:not(.pecb-dropdown__option--disabled){background-color:#f4f4f4}.pecb-dropdown__option--selected{background-color:#f7f5f5}.pecb-dropdown__option--selected:hover{background-color:#ececec}.pecb-dropdown__option--focused{background-color:#f4f4f4}.pecb-dropdown__option--disabled{color:#c2c2c2;cursor:not-allowed;opacity:.5}.pecb-dropdown__option-label{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pecb-dropdown__checkbox{display:flex;align-items:center;flex-shrink:0}.pecb-dropdown__checkbox-control{position:relative;display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border:1.5px solid #c2c2c2;border-radius:6px;background-color:#fff;transition-property:border-color,background-color;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-dropdown__checkbox-control--checked{border-color:#212427;background-color:#212427}.pecb-dropdown__checkbox-icon{color:#fff}.pecb-dropdown__clear{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;position:absolute;right:32px;color:#919191;cursor:pointer;transition-property:color;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-dropdown__clear:hover{color:#313131}.pecb-dropdown--has-value .pecb-dropdown__trigger{padding-right:48px}.pecb-dropdown__no-results{padding:16px;text-align:center;font-family:inherit;font-size:14px;color:#c2c2c2}.pecb-dropdown__description{margin-top:8px;font-family:inherit;font-size:12px;color:#919191}.pecb-dropdown__description--error{color:#a11e29}.pecb-dropdown--open .pecb-dropdown__trigger{border-color:#212427;box-shadow:0 0 0 3px #2124271a}.pecb-dropdown--open .pecb-dropdown__arrow{transform:rotate(180deg)}.pecb-dropdown--open.pecb-dropdown--multiple .pecb-dropdown__trigger{border-color:#c2c2c2;box-shadow:none}.pecb-dropdown--open.pecb-dropdown--multiple .pecb-dropdown__panel{border-radius:8px;border-color:#c2c2c2;box-shadow:0 4px 12px #0000001a}.pecb-dropdown--error .pecb-dropdown__trigger{border-color:#a11e29}.pecb-dropdown--error .pecb-dropdown__trigger:focus-visible{box-shadow:0 0 0 3px #a11e291a}.pecb-dropdown--error .pecb-dropdown__label{color:#a11e29}.pecb-dropdown--disabled .pecb-dropdown__trigger{background-color:#f4f4f4;border-color:#ececec;cursor:not-allowed;opacity:.6}.pecb-dropdown--disabled .pecb-dropdown__value{color:#c2c2c2}.pecb-dropdown--disabled .pecb-dropdown__arrow{color:#ececec}.pecb-dropdown--sm .pecb-dropdown__trigger{min-height:32px;padding:8px 24px 8px 12px;font-size:12px}.pecb-dropdown--sm .pecb-dropdown__label{left:12px}.pecb-dropdown--sm .pecb-dropdown__arrow{right:8px}.pecb-dropdown--sm .pecb-dropdown__option{padding:8px 12px;font-size:12px}.pecb-dropdown--md .pecb-dropdown__trigger{min-height:40px}.pecb-dropdown--lg .pecb-dropdown__trigger{min-height:48px;padding:16px 40px 16px 18px;font-size:16px}.pecb-dropdown--lg .pecb-dropdown__label{left:18px}.pecb-dropdown--lg .pecb-dropdown__arrow{right:16px}.pecb-dropdown--lg .pecb-dropdown__option{padding:12px 18px;font-size:16px}\n"] }]
5594
+ ], template: "<div [class]=\"wrapperClasses.join(' ')\">\n <!-- Field title \u2014 sits on the trigger's top border (Figma \"Field - Master\") -->\n <label\n *ngIf=\"label()\"\n [attr.for]=\"id()\"\n class=\"pecb-dropdown__label\"\n >\n {{ label() }}<span *ngIf=\"required()\" class=\"pecb-dropdown__required\">*</span>\n </label>\n\n <!-- Dropdown Trigger -->\n <div\n class=\"pecb-dropdown__trigger\"\n cdkOverlayOrigin\n #ddTrigger=\"cdkOverlayOrigin\"\n #ddTriggerEl\n [attr.tabindex]=\"disabled() ? -1 : 0\"\n [attr.aria-label]=\"label()\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-haspopup]=\"true\"\n [attr.aria-disabled]=\"disabled()\"\n (click)=\"toggleDropdown()\"\n (keydown)=\"onKeyDown($event)\"\n >\n <span class=\"pecb-dropdown__value\">{{ displayText || placeholder() }}</span>\n\n <!-- Clear button (shown whenever a value is set, single or multi) -->\n <span *ngIf=\"hasValue && !disabled()\" class=\"pecb-dropdown__clear\" (click)=\"clearAll($event)\" [attr.aria-label]=\"clearSelectionAriaLabel()\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M11 1L1 11M1 1L11 11\" stroke=\"#616161\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </span>\n\n <!-- Dropdown Arrow -->\n <span class=\"pecb-dropdown__arrow\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 18 18\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M4.5 6.75L9 11.25L13.5 6.75\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </span>\n </div>\n\n <!-- Dropdown Panel \u2014 CDK overlay so it flips near edges and escapes overflow. -->\n <ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"ddTrigger\"\n [cdkConnectedOverlayOpen]=\"isOpen()\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayWidth]=\"triggerWidth()\"\n [cdkConnectedOverlayHasBackdrop]=\"true\"\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n [cdkConnectedOverlayPush]=\"true\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n (backdropClick)=\"closeDropdown()\"\n (detach)=\"closeDropdown()\"\n >\n <div\n class=\"pecb-dropdown__panel\"\n [style.max-height.px]=\"maxHeight()\"\n >\n <!-- Search Input -->\n <div *ngIf=\"searchable()\" class=\"pecb-dropdown__search\">\n <svg class=\"pecb-dropdown__search-icon\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"currentColor\">\n <path d=\"M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z\"/>\n </svg>\n <input\n #searchInput\n type=\"text\"\n class=\"pecb-dropdown__search-input\"\n [placeholder]=\"searchPlaceholder()\"\n [value]=\"searchQuery()\"\n (input)=\"onSearchInput($event)\"\n (keydown)=\"onKeyDown($event)\"\n />\n </div>\n\n <!-- Options List -->\n <div class=\"pecb-dropdown__options\">\n <div\n *ngFor=\"let option of filteredOptions; let i = index\"\n class=\"pecb-dropdown__option\"\n [class.pecb-dropdown__option--selected]=\"isSelected(option)\"\n [class.pecb-dropdown__option--disabled]=\"option.disabled\"\n [class.pecb-dropdown__option--focused]=\"i === focusedIndex()\"\n (click)=\"selectOption(option)\"\n >\n <!-- Checkbox for multi-select -->\n <span *ngIf=\"multiple()\" class=\"pecb-dropdown__checkbox\">\n <span class=\"pecb-dropdown__checkbox-control\" [class.pecb-dropdown__checkbox-control--checked]=\"isSelected(option)\">\n <svg *ngIf=\"isSelected(option)\" class=\"pecb-dropdown__checkbox-icon\" width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\">\n <path d=\"M10 3L4.5 8.5L2 6\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </span>\n </span>\n\n <!-- Option Label -->\n <span class=\"pecb-dropdown__option-label\">{{ option.label }}</span>\n </div>\n\n <!-- No results message -->\n <div *ngIf=\"filteredOptions.length === 0\" class=\"pecb-dropdown__no-results\">\n {{ noResultsText() }}\n </div>\n </div>\n </div>\n </ng-template>\n\n <!-- Helper Text / Error Message -->\n <div\n *ngIf=\"helperText() || errorMessage()\"\n [id]=\"id() + '-description'\"\n class=\"pecb-dropdown__description\"\n [class.pecb-dropdown__description--error]=\"error() && errorMessage()\"\n >\n <span *ngIf=\"error() && errorMessage()\">{{ errorMessage() }}</span>\n <span *ngIf=\"!error() && helperText()\">{{ helperText() }}</span>\n </div>\n</div>\n", styles: [".pecb-dropdown{position:relative;display:inline-block;width:100%}.pecb-dropdown__trigger{position:relative;display:flex;align-items:center;width:100%;min-height:40px;padding:12px 32px 12px 16px;font-family:inherit;font-size:14px;color:#313131;background-color:#fff;border:1px solid #c2c2c2;border-radius:8px;cursor:pointer;-webkit-user-select:none;user-select:none;outline:none;box-sizing:border-box;transition-property:border-color,box-shadow;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-dropdown__trigger:hover:not([aria-disabled=true]){border-color:#919191}.pecb-dropdown__trigger:focus-visible{border-color:#212427;box-shadow:0 0 0 3px #2124271a}.pecb-dropdown__value{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#616161}.pecb-dropdown__label{position:absolute;left:16px;top:0;transform:translateY(-50%);font-family:inherit;font-size:12px;font-weight:400;color:#212427;background-color:#fff;padding:0 6px;pointer-events:none;z-index:1;white-space:nowrap}.pecb-dropdown__required{color:inherit;margin-left:2px}.pecb-dropdown__arrow{position:absolute;right:12px;display:flex;align-items:center;justify-content:center;color:#919191;transition-property:transform;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-dropdown__arrow svg{display:block}.pecb-dropdown__panel{width:100%;background-color:#fff;border:1px solid #c2c2c2;border-radius:8px;box-shadow:0 4px 12px #0000001a;overflow:hidden;box-sizing:border-box;display:flex;flex-direction:column}.pecb-dropdown__search{position:relative;padding:12px 12px 8px;border-bottom:1px solid #f7f5f5;flex-shrink:0}.pecb-dropdown__search-icon{position:absolute;left:18px;top:50%;transform:translateY(-50%);color:#c2c2c2;pointer-events:none;z-index:1}.pecb-dropdown__search-input{width:100%;padding:8px 12px 8px 32px;font-family:inherit;font-size:14px;color:#313131;background-color:#fff;border:1px solid #f7f5f5;border-radius:8px;outline:none;box-sizing:border-box;transition-property:border-color,box-shadow;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-dropdown__search-input::placeholder{color:#c2c2c2;font-size:14px}.pecb-dropdown__search-input:hover{border-color:#ececec}.pecb-dropdown__search-input:focus{border-color:#c2c2c2;box-shadow:0 0 0 1px #c2c2c21a}.pecb-dropdown__options{flex:1;overflow-y:auto;overflow-x:hidden;padding:8px 0;min-height:0}.pecb-dropdown__options::-webkit-scrollbar{width:6px}.pecb-dropdown__options::-webkit-scrollbar-track{background-color:#f4f4f4}.pecb-dropdown__options::-webkit-scrollbar-thumb{background-color:#212427;border-radius:3px}.pecb-dropdown__options::-webkit-scrollbar-thumb:hover{background-color:#0e1011}.pecb-dropdown__option{display:flex;align-items:center;gap:12px;padding:12px 16px;font-family:inherit;font-size:14px;color:#616161;cursor:pointer;transition-property:background-color;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-dropdown__option:hover:not(.pecb-dropdown__option--disabled){background-color:#f4f4f4}.pecb-dropdown__option--selected{background-color:#f7f5f5}.pecb-dropdown__option--selected:hover{background-color:#ececec}.pecb-dropdown__option--focused{background-color:#f4f4f4}.pecb-dropdown__option--disabled{color:#c2c2c2;cursor:not-allowed;opacity:.5}.pecb-dropdown__option-label{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pecb-dropdown__checkbox{display:flex;align-items:center;flex-shrink:0}.pecb-dropdown__checkbox-control{position:relative;display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border:1.5px solid #c2c2c2;border-radius:6px;background-color:#fff;transition-property:border-color,background-color;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-dropdown__checkbox-control--checked{border-color:#212427;background-color:#212427}.pecb-dropdown__checkbox-icon{color:#fff}.pecb-dropdown__clear{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;position:absolute;right:32px;color:#919191;cursor:pointer;transition-property:color;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-dropdown__clear:hover{color:#313131}.pecb-dropdown--has-value .pecb-dropdown__trigger{padding-right:48px}.pecb-dropdown__no-results{padding:16px;text-align:center;font-family:inherit;font-size:14px;color:#c2c2c2}.pecb-dropdown__description{margin-top:8px;font-family:inherit;font-size:12px;color:#919191}.pecb-dropdown__description--error{color:#a11e29}.pecb-dropdown--open .pecb-dropdown__trigger{border-color:#212427;box-shadow:0 0 0 3px #2124271a}.pecb-dropdown--open .pecb-dropdown__arrow{transform:rotate(180deg)}.pecb-dropdown--open.pecb-dropdown--multiple .pecb-dropdown__trigger{border-color:#c2c2c2;box-shadow:none}.pecb-dropdown--open.pecb-dropdown--multiple .pecb-dropdown__panel{border-radius:8px;border-color:#c2c2c2;box-shadow:0 4px 12px #0000001a}.pecb-dropdown--error .pecb-dropdown__trigger{border-color:#a11e29}.pecb-dropdown--error .pecb-dropdown__trigger:focus-visible{box-shadow:0 0 0 3px #a11e291a}.pecb-dropdown--error .pecb-dropdown__label{color:#a11e29}.pecb-dropdown--disabled .pecb-dropdown__trigger{background-color:#f4f4f4;border-color:#ececec;cursor:not-allowed;opacity:.6}.pecb-dropdown--disabled .pecb-dropdown__value{color:#c2c2c2}.pecb-dropdown--disabled .pecb-dropdown__arrow{color:#ececec}.pecb-dropdown--sm .pecb-dropdown__trigger{min-height:32px;padding:8px 24px 8px 12px;font-size:12px}.pecb-dropdown--sm .pecb-dropdown__label{left:12px}.pecb-dropdown--sm .pecb-dropdown__arrow{right:8px}.pecb-dropdown--sm .pecb-dropdown__option{padding:8px 12px;font-size:12px}.pecb-dropdown--md .pecb-dropdown__trigger{min-height:40px}.pecb-dropdown--lg .pecb-dropdown__trigger{min-height:48px;padding:16px 40px 16px 18px;font-size:16px}.pecb-dropdown--lg .pecb-dropdown__label{left:18px}.pecb-dropdown--lg .pecb-dropdown__arrow{right:16px}.pecb-dropdown--lg .pecb-dropdown__option{padding:12px 18px;font-size:16px}\n"] }]
5576
5595
  }], propDecorators: { searchInput: [{
5577
5596
  type: ViewChild,
5578
5597
  args: ['searchInput']
5579
- }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], maxHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxHeight", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], errorMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessage", required: false }] }], helperText: [{ type: i0.Input, args: [{ isSignal: true, alias: "helperText", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], clearSelectionAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearSelectionAriaLabel", required: false }] }], noResultsText: [{ type: i0.Input, args: [{ isSignal: true, alias: "noResultsText", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }] } });
5598
+ }], triggerEl: [{
5599
+ type: ViewChild,
5600
+ args: ['ddTriggerEl']
5601
+ }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], maxHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxHeight", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], errorMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessage", required: false }] }], helperText: [{ type: i0.Input, args: [{ isSignal: true, alias: "helperText", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], clearSelectionAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearSelectionAriaLabel", required: false }] }], noResultsText: [{ type: i0.Input, args: [{ isSignal: true, alias: "noResultsText", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], syncTriggerWidth: [{
5602
+ type: HostListener,
5603
+ args: ['window:resize']
5604
+ }] } });
5580
5605
 
5581
5606
  class RightModalComponent {
5582
5607
  constructor() {
@@ -10655,6 +10680,1819 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
10655
10680
  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"] }]
10656
10681
  }], 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 }] }] } });
10657
10682
 
10683
+ const DEFAULT_VIDEO_PLAYER_LABELS = {
10684
+ play: 'Play',
10685
+ pause: 'Pause',
10686
+ replay: 'Replay',
10687
+ skipBack: 'Rewind {n} seconds',
10688
+ mute: 'Mute',
10689
+ unmute: 'Unmute',
10690
+ volume: 'Volume',
10691
+ captionsOn: 'Turn captions on',
10692
+ captionsOff: 'Turn captions off',
10693
+ settings: 'Settings',
10694
+ playbackSpeed: 'Playback speed',
10695
+ normalSpeed: 'Normal',
10696
+ enterFullscreen: 'Enter fullscreen',
10697
+ exitFullscreen: 'Exit fullscreen',
10698
+ seek: 'Seek',
10699
+ player: 'Video player',
10700
+ error: 'This video could not be loaded.',
10701
+ };
10702
+ /** Default playback-rate menu entries. */
10703
+ const DEFAULT_VIDEO_PLAYER_RATES = [0.5, 0.75, 1, 1.25, 1.5, 2];
10704
+ /**
10705
+ * Formats seconds as `m:ss`, or `h:mm:ss` once the value reaches one hour.
10706
+ * Non-finite / negative input renders as `0:00`.
10707
+ */
10708
+ function formatVideoTime(seconds) {
10709
+ if (!Number.isFinite(seconds) || seconds < 0) {
10710
+ return '0:00';
10711
+ }
10712
+ const total = Math.floor(seconds);
10713
+ const h = Math.floor(total / 3600);
10714
+ const m = Math.floor((total % 3600) / 60);
10715
+ const s = total % 60;
10716
+ const mm = h > 0 ? String(m).padStart(2, '0') : String(m);
10717
+ const ss = String(s).padStart(2, '0');
10718
+ return h > 0 ? `${h}:${mm}:${ss}` : `${mm}:${ss}`;
10719
+ }
10720
+ // Internal tuning constants.
10721
+ const IDLE_HIDE_DELAY_MS = 2600;
10722
+ const LEAVE_HIDE_DELAY_MS = 700;
10723
+ const SEEK_SYNC_TOLERANCE_S = 0.5;
10724
+ // ─── Component ────────────────────────────────────────────────────────────────
10725
+ /**
10726
+ * PECB course video player.
10727
+ *
10728
+ * A fully custom-skinned, accessible HTML5 `<video>` wrapper that reproduces
10729
+ * the PECB "Course Player" design: a rounded dark 16:9 stage, poster, brand
10730
+ * watermark + section label, a blurred centre play button and a bottom
10731
+ * control bar (progress, play/pause, rewind, volume, time, captions, speed
10732
+ * settings, fullscreen).
10733
+ *
10734
+ * The player is self-contained (no icon-registry / service dependencies) so it
10735
+ * can be dropped into any Angular 17+ app. It is responsive (container
10736
+ * queries), keyboard operable, touch friendly and honours
10737
+ * `prefers-reduced-motion`.
10738
+ *
10739
+ * @example Minimal
10740
+ * ```html
10741
+ * <pecb-video-player
10742
+ * src="https://cdn.example.com/lesson.mp4"
10743
+ * poster="https://cdn.example.com/lesson.jpg"
10744
+ * label="SECTION 2">
10745
+ * </pecb-video-player>
10746
+ * ```
10747
+ *
10748
+ * @example Two-way bound & driven from outside (e.g. a transcript panel)
10749
+ * ```html
10750
+ * <pecb-video-player
10751
+ * [src]="sources"
10752
+ * [tracks]="captions"
10753
+ * [(currentTime)]="at"
10754
+ * [(playing)]="playing"
10755
+ * (playbackEnded)="onLessonFinished()">
10756
+ * </pecb-video-player>
10757
+ * ```
10758
+ *
10759
+ * Theming — override the CSS custom properties on the host:
10760
+ * ```css
10761
+ * pecb-video-player { --pecb-video-radius: 0; --pecb-video-accent: #a11e29; }
10762
+ * ```
10763
+ */
10764
+ class VideoPlayerComponent {
10765
+ constructor() {
10766
+ this.platformId = inject(PLATFORM_ID);
10767
+ this.zone = inject(NgZone);
10768
+ this.destroyRef = inject(DestroyRef);
10769
+ this.isBrowser = isPlatformBrowser(this.platformId);
10770
+ /** Unique id used to wire ARIA relationships inside the template. */
10771
+ this.uid = generateUniqueId('pecb-video');
10772
+ // ── Element refs ────────────────────────────────────────────────────────────
10773
+ this.rootRef = viewChild('root', ...(ngDevMode ? [{ debugName: "rootRef" }] : []));
10774
+ this.videoRef = viewChild('video', ...(ngDevMode ? [{ debugName: "videoRef" }] : []));
10775
+ this.progressRef = viewChild('progress', ...(ngDevMode ? [{ debugName: "progressRef" }] : []));
10776
+ this.settingsMenuRef = viewChild('settingsMenu', ...(ngDevMode ? [{ debugName: "settingsMenuRef" }] : []));
10777
+ // ── Media inputs ────────────────────────────────────────────────────────────
10778
+ /** Media URL, or a list of `<source>` candidates. */
10779
+ this.src = input(undefined, ...(ngDevMode ? [{ debugName: "src" }] : []));
10780
+ /** Poster image shown before playback starts. */
10781
+ this.poster = input(undefined, ...(ngDevMode ? [{ debugName: "poster" }] : []));
10782
+ /** Text tracks (captions / subtitles). The CC button appears when non-empty. */
10783
+ this.tracks = input([], ...(ngDevMode ? [{ debugName: "tracks" }] : []));
10784
+ /** Accessible title of the media, announced as part of the region label. */
10785
+ this.title = input(undefined, ...(ngDevMode ? [{ debugName: "title" }] : []));
10786
+ /** `crossorigin` attribute — required for captions hosted on another origin. */
10787
+ this.crossOrigin = input(undefined, ...(ngDevMode ? [{ debugName: "crossOrigin" }] : []));
10788
+ /** @default 'metadata' */
10789
+ this.preload = input('metadata', ...(ngDevMode ? [{ debugName: "preload" }] : []));
10790
+ /** Attempt to start playback automatically (subject to browser policy). */
10791
+ this.autoplay = input(false, ...(ngDevMode ? [{ debugName: "autoplay", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
10792
+ /** Restart from the beginning when the media ends. */
10793
+ this.loop = input(false, ...(ngDevMode ? [{ debugName: "loop", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
10794
+ /** Play inline on iOS instead of forcing the native fullscreen player. @default true */
10795
+ this.playsInline = input(true, ...(ngDevMode ? [{ debugName: "playsInline", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
10796
+ /** Initial playhead position in seconds (applied once metadata is loaded). */
10797
+ this.startTime = input(0, ...(ngDevMode ? [{ debugName: "startTime", transform: numberAttribute }] : [{ transform: numberAttribute }]));
10798
+ // ── Appearance inputs ───────────────────────────────────────────────────────
10799
+ /** Brand watermark in the bottom-left corner. Empty string hides it. @default 'PECB' */
10800
+ this.watermark = input('PECB', ...(ngDevMode ? [{ debugName: "watermark" }] : []));
10801
+ /** Large label in the bottom-right corner, e.g. `SECTION 2`. */
10802
+ this.label = input(undefined, ...(ngDevMode ? [{ debugName: "label" }] : []));
10803
+ /** CSS `aspect-ratio` of the stage. @default '16 / 9' */
10804
+ this.aspectRatio = input('16 / 9', ...(ngDevMode ? [{ debugName: "aspectRatio" }] : []));
10805
+ /** Show the large centre play/pause button. @default true */
10806
+ this.showCenterButton = input(true, ...(ngDevMode ? [{ debugName: "showCenterButton", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
10807
+ /** Show the rewind button. @default true */
10808
+ this.showSkipBack = input(true, ...(ngDevMode ? [{ debugName: "showSkipBack", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
10809
+ /** Show the mute button + volume slider. @default true */
10810
+ this.showVolume = input(true, ...(ngDevMode ? [{ debugName: "showVolume", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
10811
+ /** Show the elapsed / total time. @default true */
10812
+ this.showTime = input(true, ...(ngDevMode ? [{ debugName: "showTime", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
10813
+ /** Force-show/hide the captions button. `undefined` = show only when `tracks` is non-empty. */
10814
+ this.showCaptions = input(undefined, ...(ngDevMode ? [{ debugName: "showCaptions" }] : []));
10815
+ /** Show the settings (playback speed) button. @default true */
10816
+ this.showSettings = input(true, ...(ngDevMode ? [{ debugName: "showSettings", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
10817
+ /** Show the fullscreen button. @default true */
10818
+ this.showFullscreen = input(true, ...(ngDevMode ? [{ debugName: "showFullscreen", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
10819
+ /** Seconds the rewind button / arrow keys jump. @default 10 */
10820
+ this.seekStep = input(10, ...(ngDevMode ? [{ debugName: "seekStep", transform: numberAttribute }] : [{ transform: numberAttribute }]));
10821
+ /** Playback-rate options listed in the settings menu. */
10822
+ this.playbackRates = input(DEFAULT_VIDEO_PLAYER_RATES, ...(ngDevMode ? [{ debugName: "playbackRates" }] : []));
10823
+ /** Partial override of the user-visible strings (i18n). */
10824
+ this.labels = input({}, ...(ngDevMode ? [{ debugName: "labels" }] : []));
10825
+ // ── Two-way state (models) ──────────────────────────────────────────────────
10826
+ /** Whether the media is playing. Set it to start/stop playback. */
10827
+ this.playing = model(false, ...(ngDevMode ? [{ debugName: "playing" }] : []));
10828
+ /** Playhead position in seconds. Set it to seek. */
10829
+ this.currentTime = model(0, ...(ngDevMode ? [{ debugName: "currentTime" }] : []));
10830
+ /** Volume 0–1. */
10831
+ this.volume = model(1, ...(ngDevMode ? [{ debugName: "volume" }] : []));
10832
+ /** Muted state. */
10833
+ this.muted = model(false, ...(ngDevMode ? [{ debugName: "muted" }] : []));
10834
+ /** Playback rate. */
10835
+ this.playbackRate = model(1, ...(ngDevMode ? [{ debugName: "playbackRate" }] : []));
10836
+ /** Whether the default text track is displayed. */
10837
+ this.captionsEnabled = model(false, ...(ngDevMode ? [{ debugName: "captionsEnabled" }] : []));
10838
+ // ── Outputs ─────────────────────────────────────────────────────────────────
10839
+ /** Fires on every `timeupdate` (~4×/s while playing). */
10840
+ this.timeUpdate = output();
10841
+ /** Duration is known. */
10842
+ this.loadedMetadata = output();
10843
+ /** A seek finished. */
10844
+ this.seekEnd = output();
10845
+ /** Playback reached the end. */
10846
+ this.playbackEnded = output();
10847
+ /** Player entered / left fullscreen. */
10848
+ this.fullscreenChange = output();
10849
+ /** Media failed to load or playback was rejected. */
10850
+ this.playerError = output();
10851
+ // ── Internal state ──────────────────────────────────────────────────────────
10852
+ this.duration = signal(0, ...(ngDevMode ? [{ debugName: "duration" }] : []));
10853
+ this.bufferedEnd = signal(0, ...(ngDevMode ? [{ debugName: "bufferedEnd" }] : []));
10854
+ this.hasEnded = signal(false, ...(ngDevMode ? [{ debugName: "hasEnded" }] : []));
10855
+ this.isBuffering = signal(false, ...(ngDevMode ? [{ debugName: "isBuffering" }] : []));
10856
+ this.hasError = signal(false, ...(ngDevMode ? [{ debugName: "hasError" }] : []));
10857
+ this.isFullscreen = signal(false, ...(ngDevMode ? [{ debugName: "isFullscreen" }] : []));
10858
+ this.isSeeking = signal(false, ...(ngDevMode ? [{ debugName: "isSeeking" }] : []));
10859
+ this.settingsOpen = signal(false, ...(ngDevMode ? [{ debugName: "settingsOpen" }] : []));
10860
+ this.controlsVisible = signal(true, ...(ngDevMode ? [{ debugName: "controlsVisible" }] : []));
10861
+ this.hasStarted = signal(false, ...(ngDevMode ? [{ debugName: "hasStarted" }] : []));
10862
+ this.hideTimer = null;
10863
+ this.lastPointerType = 'mouse';
10864
+ this.startTimeApplied = false;
10865
+ this.initialLoadDone = false;
10866
+ this.pendingPlay = null;
10867
+ // ── Computed ────────────────────────────────────────────────────────────────
10868
+ this.text = computed(() => ({
10869
+ ...DEFAULT_VIDEO_PLAYER_LABELS,
10870
+ ...this.labels(),
10871
+ }), ...(ngDevMode ? [{ debugName: "text" }] : []));
10872
+ this.skipBackLabel = computed(() => this.text().skipBack.replace('{n}', String(this.seekStep())), ...(ngDevMode ? [{ debugName: "skipBackLabel" }] : []));
10873
+ this.sourceList = computed(() => {
10874
+ const s = this.src();
10875
+ if (!s)
10876
+ return [];
10877
+ return typeof s === 'string' ? [{ src: s }] : s;
10878
+ }, ...(ngDevMode ? [{ debugName: "sourceList" }] : []));
10879
+ /** Single-string src is bound directly so the browser can start fetching ASAP. */
10880
+ this.singleSrc = computed(() => {
10881
+ const s = this.src();
10882
+ return typeof s === 'string' ? s : undefined;
10883
+ }, ...(ngDevMode ? [{ debugName: "singleSrc" }] : []));
10884
+ this.progressPct = computed(() => {
10885
+ const d = this.duration();
10886
+ return d > 0 ? Math.min(100, Math.max(0, (this.currentTime() / d) * 100)) : 0;
10887
+ }, ...(ngDevMode ? [{ debugName: "progressPct" }] : []));
10888
+ this.bufferedPct = computed(() => {
10889
+ const d = this.duration();
10890
+ return d > 0 ? Math.min(100, Math.max(0, (this.bufferedEnd() / d) * 100)) : 0;
10891
+ }, ...(ngDevMode ? [{ debugName: "bufferedPct" }] : []));
10892
+ this.currentTimeLabel = computed(() => formatVideoTime(this.currentTime()), ...(ngDevMode ? [{ debugName: "currentTimeLabel" }] : []));
10893
+ this.durationLabel = computed(() => formatVideoTime(this.duration()), ...(ngDevMode ? [{ debugName: "durationLabel" }] : []));
10894
+ this.captionsButtonVisible = computed(() => this.showCaptions() ?? this.tracks().length > 0, ...(ngDevMode ? [{ debugName: "captionsButtonVisible" }] : []));
10895
+ /** Volume as an integer percentage (0 while muted) — drives the slider. */
10896
+ this.volumePct = computed(() => (this.muted() ? 0 : Math.round(this.volume() * 100)), ...(ngDevMode ? [{ debugName: "volumePct" }] : []));
10897
+ this.volumeLevel = computed(() => {
10898
+ if (this.muted() || this.volume() === 0)
10899
+ return 'muted';
10900
+ return this.volume() < 0.5 ? 'low' : 'high';
10901
+ }, ...(ngDevMode ? [{ debugName: "volumeLevel" }] : []));
10902
+ this.regionLabel = computed(() => {
10903
+ const t = this.title();
10904
+ return t ? `${this.text().player}: ${t}` : this.text().player;
10905
+ }, ...(ngDevMode ? [{ debugName: "regionLabel" }] : []));
10906
+ /** Controls are hidden only while playing and idle. */
10907
+ this.controlsHidden = computed(() => this.playing() &&
10908
+ !this.controlsVisible() &&
10909
+ !this.settingsOpen() &&
10910
+ !this.isSeeking() &&
10911
+ !this.hasEnded(), ...(ngDevMode ? [{ debugName: "controlsHidden" }] : []));
10912
+ this.hostClasses = computed(() => [
10913
+ 'pecb-video-player',
10914
+ this.playing() ? 'pecb-video-player--playing' : 'pecb-video-player--paused',
10915
+ this.controlsHidden() ? 'pecb-video-player--idle' : '',
10916
+ this.isFullscreen() ? 'pecb-video-player--fullscreen' : '',
10917
+ this.hasEnded() ? 'pecb-video-player--ended' : '',
10918
+ this.hasError() ? 'pecb-video-player--error' : '',
10919
+ this.isSeeking() ? 'pecb-video-player--seeking' : '',
10920
+ ]
10921
+ .filter(Boolean)
10922
+ .join(' '), ...(ngDevMode ? [{ debugName: "hostClasses" }] : []));
10923
+ // ── model → <video> synchronisation ───────────────────────────────────────
10924
+ effect(() => {
10925
+ const wantPlaying = this.playing();
10926
+ const video = this.videoRef()?.nativeElement;
10927
+ if (!video)
10928
+ return;
10929
+ untracked(() => {
10930
+ if (wantPlaying && video.paused) {
10931
+ this.safePlay(video);
10932
+ }
10933
+ else if (!wantPlaying && !video.paused) {
10934
+ video.pause();
10935
+ }
10936
+ });
10937
+ });
10938
+ effect(() => {
10939
+ const t = this.currentTime();
10940
+ const video = this.videoRef()?.nativeElement;
10941
+ if (!video || !Number.isFinite(t))
10942
+ return;
10943
+ untracked(() => {
10944
+ if (Math.abs(video.currentTime - t) > SEEK_SYNC_TOLERANCE_S) {
10945
+ video.currentTime = t;
10946
+ }
10947
+ });
10948
+ });
10949
+ effect(() => {
10950
+ const v = Math.min(1, Math.max(0, this.volume()));
10951
+ const m = this.muted();
10952
+ const video = this.videoRef()?.nativeElement;
10953
+ if (!video)
10954
+ return;
10955
+ untracked(() => {
10956
+ if (video.volume !== v)
10957
+ video.volume = v;
10958
+ if (video.muted !== m)
10959
+ video.muted = m;
10960
+ });
10961
+ });
10962
+ effect(() => {
10963
+ const rate = this.playbackRate();
10964
+ const video = this.videoRef()?.nativeElement;
10965
+ if (!video || !Number.isFinite(rate) || rate <= 0)
10966
+ return;
10967
+ untracked(() => {
10968
+ if (video.playbackRate !== rate)
10969
+ video.playbackRate = rate;
10970
+ });
10971
+ });
10972
+ effect(() => {
10973
+ const enabled = this.captionsEnabled();
10974
+ // Re-run when tracks change (they are re-rendered by the template).
10975
+ this.tracks();
10976
+ const video = this.videoRef()?.nativeElement;
10977
+ if (!video)
10978
+ return;
10979
+ untracked(() => this.applyCaptionMode(video, enabled));
10980
+ });
10981
+ // Reload the element when the source set changes after first render.
10982
+ effect(() => {
10983
+ this.sourceList();
10984
+ const video = this.videoRef()?.nativeElement;
10985
+ if (!video)
10986
+ return;
10987
+ untracked(() => {
10988
+ if (!this.initialLoadDone) {
10989
+ this.initialLoadDone = true;
10990
+ return;
10991
+ }
10992
+ this.resetForNewSource(video);
10993
+ });
10994
+ });
10995
+ if (this.isBrowser) {
10996
+ afterNextRender(() => this.attachIdleListeners());
10997
+ this.destroyRef.onDestroy(() => this.clearHideTimer());
10998
+ }
10999
+ }
11000
+ // ── Public API ──────────────────────────────────────────────────────────────
11001
+ /** Start playback. */
11002
+ play() {
11003
+ this.playing.set(true);
11004
+ }
11005
+ /** Pause playback. */
11006
+ pause() {
11007
+ this.playing.set(false);
11008
+ }
11009
+ /** Toggle play / pause (replays from the start after the media ended). */
11010
+ togglePlay() {
11011
+ if (this.hasError())
11012
+ return;
11013
+ if (this.hasEnded()) {
11014
+ this.seek(0);
11015
+ this.playing.set(true);
11016
+ return;
11017
+ }
11018
+ this.playing.update((p) => !p);
11019
+ }
11020
+ /** Seek to an absolute position (seconds, clamped to the duration). */
11021
+ seek(seconds) {
11022
+ const d = this.duration();
11023
+ const target = Math.max(0, d > 0 ? Math.min(d, seconds) : seconds);
11024
+ const video = this.videoRef()?.nativeElement;
11025
+ if (video) {
11026
+ video.currentTime = target;
11027
+ }
11028
+ this.currentTime.set(target);
11029
+ }
11030
+ /** Seek relative to the current position. */
11031
+ seekBy(deltaSeconds) {
11032
+ this.seek(this.currentTime() + deltaSeconds);
11033
+ }
11034
+ /** Rewind by `seekStep` seconds. */
11035
+ skipBack() {
11036
+ this.seekBy(-this.seekStep());
11037
+ }
11038
+ /** Toggle mute. Unmuting a zero volume restores it to 50 %. */
11039
+ toggleMute() {
11040
+ const nextMuted = !this.muted();
11041
+ if (!nextMuted && this.volume() === 0) {
11042
+ this.volume.set(0.5);
11043
+ }
11044
+ this.muted.set(nextMuted);
11045
+ }
11046
+ /** Set volume (0–1). A non-zero value also unmutes. */
11047
+ setVolume(value) {
11048
+ const v = Math.min(1, Math.max(0, value));
11049
+ this.volume.set(v);
11050
+ if (v > 0 && this.muted()) {
11051
+ this.muted.set(false);
11052
+ }
11053
+ }
11054
+ /** Set the playback rate and close the settings menu. */
11055
+ setPlaybackRate(rate) {
11056
+ this.playbackRate.set(rate);
11057
+ this.closeSettings(true);
11058
+ }
11059
+ /** Toggle captions on the default text track. */
11060
+ toggleCaptions() {
11061
+ this.captionsEnabled.update((c) => !c);
11062
+ }
11063
+ /** Enter / exit fullscreen for the whole player (falls back to the native iOS player). */
11064
+ async toggleFullscreen() {
11065
+ if (!this.isBrowser)
11066
+ return;
11067
+ const root = this.rootRef()?.nativeElement;
11068
+ const video = this.videoRef()?.nativeElement;
11069
+ if (!root || !video)
11070
+ return;
11071
+ try {
11072
+ if (document.fullscreenElement) {
11073
+ await document.exitFullscreen();
11074
+ }
11075
+ else if (root.requestFullscreen) {
11076
+ await root.requestFullscreen();
11077
+ }
11078
+ else {
11079
+ // iOS Safari: no element fullscreen — use the native video fullscreen.
11080
+ const legacy = video;
11081
+ legacy.webkitEnterFullscreen?.();
11082
+ }
11083
+ }
11084
+ catch {
11085
+ // Fullscreen can be denied (iframe sandbox, user-gesture rules) — ignore.
11086
+ }
11087
+ }
11088
+ /** Open / close the settings (playback speed) menu. */
11089
+ toggleSettings() {
11090
+ if (this.settingsOpen()) {
11091
+ this.closeSettings(true);
11092
+ return;
11093
+ }
11094
+ this.settingsOpen.set(true);
11095
+ this.revealControls();
11096
+ // Focus the checked item once the menu is rendered.
11097
+ queueMicrotask(() => {
11098
+ const menu = this.settingsMenuRef()?.nativeElement;
11099
+ const target = menu?.querySelector('[aria-checked="true"]') ??
11100
+ menu?.querySelector('[role="menuitemradio"]');
11101
+ target?.focus();
11102
+ });
11103
+ }
11104
+ /** Close the settings menu; optionally move focus back to its trigger. */
11105
+ closeSettings(restoreFocus = false) {
11106
+ if (!this.settingsOpen())
11107
+ return;
11108
+ this.settingsOpen.set(false);
11109
+ if (restoreFocus) {
11110
+ this.rootRef()
11111
+ ?.nativeElement.querySelector(`#${this.uid}-settings-btn`)
11112
+ ?.focus();
11113
+ }
11114
+ this.scheduleHide();
11115
+ }
11116
+ // ── <video> event handlers ──────────────────────────────────────────────────
11117
+ onLoadedMetadata(video) {
11118
+ this.duration.set(Number.isFinite(video.duration) ? video.duration : 0);
11119
+ this.hasError.set(false);
11120
+ if (!this.startTimeApplied) {
11121
+ this.startTimeApplied = true;
11122
+ const start = this.startTime() > 0 ? this.startTime() : this.currentTime();
11123
+ if (start > 0) {
11124
+ video.currentTime = Math.min(start, video.duration || start);
11125
+ }
11126
+ }
11127
+ // Re-apply model values in case the effects ran before the element existed.
11128
+ video.volume = Math.min(1, Math.max(0, this.volume()));
11129
+ video.muted = this.muted();
11130
+ video.playbackRate = this.playbackRate();
11131
+ this.applyCaptionMode(video, this.captionsEnabled());
11132
+ this.loadedMetadata.emit({ currentTime: video.currentTime, duration: video.duration });
11133
+ }
11134
+ onDurationChange(video) {
11135
+ this.duration.set(Number.isFinite(video.duration) ? video.duration : 0);
11136
+ }
11137
+ onTimeUpdate(video) {
11138
+ if (!this.isSeeking()) {
11139
+ this.currentTime.set(video.currentTime);
11140
+ }
11141
+ this.timeUpdate.emit({ currentTime: video.currentTime, duration: video.duration });
11142
+ }
11143
+ onProgress(video) {
11144
+ const ranges = video.buffered;
11145
+ if (!ranges || ranges.length === 0)
11146
+ return;
11147
+ // Prefer the range containing the playhead, else the last one.
11148
+ let end = ranges.end(ranges.length - 1);
11149
+ for (let i = 0; i < ranges.length; i++) {
11150
+ if (ranges.start(i) <= video.currentTime && video.currentTime <= ranges.end(i)) {
11151
+ end = ranges.end(i);
11152
+ break;
11153
+ }
11154
+ }
11155
+ this.bufferedEnd.set(end);
11156
+ }
11157
+ onPlay() {
11158
+ this.hasEnded.set(false);
11159
+ this.hasStarted.set(true);
11160
+ this.playing.set(true);
11161
+ this.scheduleHide();
11162
+ }
11163
+ onPause() {
11164
+ this.playing.set(false);
11165
+ this.isBuffering.set(false);
11166
+ this.revealControls(false);
11167
+ }
11168
+ onEnded() {
11169
+ this.hasEnded.set(true);
11170
+ this.playing.set(false);
11171
+ this.revealControls(false);
11172
+ this.playbackEnded.emit();
11173
+ }
11174
+ onWaiting() {
11175
+ this.isBuffering.set(true);
11176
+ }
11177
+ onPlaying() {
11178
+ this.isBuffering.set(false);
11179
+ }
11180
+ onSeeked(video) {
11181
+ this.isBuffering.set(false);
11182
+ if (this.hasEnded() && video.currentTime < video.duration) {
11183
+ this.hasEnded.set(false);
11184
+ }
11185
+ this.seekEnd.emit({ currentTime: video.currentTime, duration: video.duration });
11186
+ }
11187
+ onVolumeChange(video) {
11188
+ this.volume.set(video.volume);
11189
+ this.muted.set(video.muted);
11190
+ }
11191
+ onRateChange(video) {
11192
+ this.playbackRate.set(video.playbackRate);
11193
+ }
11194
+ onMediaError(video) {
11195
+ // Ignore the spurious error fired when the element is reset with no source.
11196
+ if (!video.currentSrc && this.sourceList().length === 0)
11197
+ return;
11198
+ this.hasError.set(true);
11199
+ this.isBuffering.set(false);
11200
+ this.playing.set(false);
11201
+ this.playerError.emit({
11202
+ code: 'media',
11203
+ message: video.error?.message || 'Media error',
11204
+ originalError: video.error ?? undefined,
11205
+ });
11206
+ }
11207
+ // ── Stage interaction ───────────────────────────────────────────────────────
11208
+ onStagePointerDown(event) {
11209
+ this.lastPointerType = event.pointerType || 'mouse';
11210
+ }
11211
+ /** Click on the video surface: toggles play on mouse; on touch, first reveals the controls. */
11212
+ onStageClick() {
11213
+ if (this.settingsOpen()) {
11214
+ this.closeSettings();
11215
+ return;
11216
+ }
11217
+ if (this.lastPointerType === 'touch' && this.controlsHidden()) {
11218
+ this.revealControls();
11219
+ return;
11220
+ }
11221
+ this.togglePlay();
11222
+ }
11223
+ onStageDoubleClick() {
11224
+ if (this.lastPointerType !== 'touch' && this.showFullscreen()) {
11225
+ void this.toggleFullscreen();
11226
+ }
11227
+ }
11228
+ /** Keyboard shortcuts on the player region. */
11229
+ onRootKeydown(event) {
11230
+ const target = event.target;
11231
+ const root = this.rootRef()?.nativeElement;
11232
+ if (event.key === 'Escape' && this.settingsOpen()) {
11233
+ event.preventDefault();
11234
+ this.closeSettings(true);
11235
+ return;
11236
+ }
11237
+ // Let native controls (buttons, sliders, menu) handle their own keys.
11238
+ const onInteractive = !!target &&
11239
+ target !== root &&
11240
+ (target.matches('button, input, [role="slider"], [role="menuitemradio"]') ||
11241
+ !!target.closest('[role="menu"]'));
11242
+ const nativeKey = event.key === ' ' ||
11243
+ event.key === 'Enter' ||
11244
+ event.key.startsWith('Arrow') ||
11245
+ event.key === 'Home' ||
11246
+ event.key === 'End';
11247
+ if (onInteractive && nativeKey) {
11248
+ this.revealControls();
11249
+ return;
11250
+ }
11251
+ let handled = true;
11252
+ switch (event.key) {
11253
+ case ' ':
11254
+ case 'k':
11255
+ case 'K':
11256
+ this.togglePlay();
11257
+ break;
11258
+ case 'ArrowLeft':
11259
+ case 'j':
11260
+ case 'J':
11261
+ this.seekBy(-this.seekStep());
11262
+ break;
11263
+ case 'ArrowRight':
11264
+ case 'l':
11265
+ case 'L':
11266
+ this.seekBy(this.seekStep());
11267
+ break;
11268
+ case 'ArrowUp':
11269
+ if (this.showVolume())
11270
+ this.setVolume(this.volume() + 0.1);
11271
+ break;
11272
+ case 'ArrowDown':
11273
+ if (this.showVolume())
11274
+ this.setVolume(this.volume() - 0.1);
11275
+ break;
11276
+ case 'Home':
11277
+ this.seek(0);
11278
+ break;
11279
+ case 'End':
11280
+ this.seek(this.duration());
11281
+ break;
11282
+ case 'm':
11283
+ case 'M':
11284
+ if (this.showVolume())
11285
+ this.toggleMute();
11286
+ break;
11287
+ case 'f':
11288
+ case 'F':
11289
+ if (this.showFullscreen())
11290
+ void this.toggleFullscreen();
11291
+ break;
11292
+ case 'c':
11293
+ case 'C':
11294
+ if (this.captionsButtonVisible())
11295
+ this.toggleCaptions();
11296
+ break;
11297
+ default:
11298
+ handled = false;
11299
+ }
11300
+ if (handled) {
11301
+ event.preventDefault();
11302
+ this.revealControls();
11303
+ }
11304
+ }
11305
+ // ── Progress / scrubbing ────────────────────────────────────────────────────
11306
+ onProgressPointerDown(event) {
11307
+ if (event.pointerType === 'mouse' && event.button !== 0)
11308
+ return;
11309
+ const bar = this.progressRef()?.nativeElement;
11310
+ if (!bar || this.duration() <= 0)
11311
+ return;
11312
+ event.preventDefault();
11313
+ bar.setPointerCapture(event.pointerId);
11314
+ this.isSeeking.set(true);
11315
+ this.seekToPointer(event, bar);
11316
+ }
11317
+ onProgressPointerMove(event) {
11318
+ if (!this.isSeeking())
11319
+ return;
11320
+ this.seekToPointer(event, this.progressRef()?.nativeElement);
11321
+ }
11322
+ onProgressPointerUp(event) {
11323
+ if (!this.isSeeking())
11324
+ return;
11325
+ const bar = this.progressRef()?.nativeElement;
11326
+ if (bar?.hasPointerCapture(event.pointerId)) {
11327
+ bar.releasePointerCapture(event.pointerId);
11328
+ }
11329
+ this.seekToPointer(event, bar);
11330
+ this.isSeeking.set(false);
11331
+ this.scheduleHide();
11332
+ }
11333
+ onProgressKeydown(event) {
11334
+ const d = this.duration();
11335
+ if (d <= 0)
11336
+ return;
11337
+ let handled = true;
11338
+ switch (event.key) {
11339
+ case 'ArrowLeft':
11340
+ case 'ArrowDown':
11341
+ this.seekBy(-this.seekStep());
11342
+ break;
11343
+ case 'ArrowRight':
11344
+ case 'ArrowUp':
11345
+ this.seekBy(this.seekStep());
11346
+ break;
11347
+ case 'PageDown':
11348
+ this.seekBy(-d * 0.1);
11349
+ break;
11350
+ case 'PageUp':
11351
+ this.seekBy(d * 0.1);
11352
+ break;
11353
+ case 'Home':
11354
+ this.seek(0);
11355
+ break;
11356
+ case 'End':
11357
+ this.seek(d);
11358
+ break;
11359
+ default:
11360
+ handled = false;
11361
+ }
11362
+ if (handled) {
11363
+ event.preventDefault();
11364
+ event.stopPropagation();
11365
+ this.revealControls();
11366
+ }
11367
+ }
11368
+ seekToPointer(event, bar) {
11369
+ if (!bar)
11370
+ return;
11371
+ const rect = bar.getBoundingClientRect();
11372
+ if (rect.width <= 0)
11373
+ return;
11374
+ const fraction = Math.min(1, Math.max(0, (event.clientX - rect.left) / rect.width));
11375
+ this.seek(fraction * this.duration());
11376
+ }
11377
+ // ── Volume slider ───────────────────────────────────────────────────────────
11378
+ onVolumeInput(event) {
11379
+ const value = Number(event.target.value);
11380
+ this.setVolume(value / 100);
11381
+ }
11382
+ // ── Settings menu ───────────────────────────────────────────────────────────
11383
+ onSettingsMenuKeydown(event) {
11384
+ const menu = this.settingsMenuRef()?.nativeElement;
11385
+ if (!menu)
11386
+ return;
11387
+ const items = Array.from(menu.querySelectorAll('[role="menuitemradio"]'));
11388
+ if (items.length === 0)
11389
+ return;
11390
+ const index = items.indexOf(document.activeElement);
11391
+ let next;
11392
+ switch (event.key) {
11393
+ case 'ArrowDown':
11394
+ next = (index + 1) % items.length;
11395
+ break;
11396
+ case 'ArrowUp':
11397
+ next = (index - 1 + items.length) % items.length;
11398
+ break;
11399
+ case 'Home':
11400
+ next = 0;
11401
+ break;
11402
+ case 'End':
11403
+ next = items.length - 1;
11404
+ break;
11405
+ case 'Tab':
11406
+ this.closeSettings();
11407
+ return;
11408
+ default:
11409
+ return;
11410
+ }
11411
+ event.preventDefault();
11412
+ event.stopPropagation();
11413
+ items[next]?.focus();
11414
+ }
11415
+ onRootFocusOut(event) {
11416
+ const root = this.rootRef()?.nativeElement;
11417
+ const next = event.relatedTarget;
11418
+ if (this.settingsOpen() && root && (!next || !root.contains(next))) {
11419
+ this.closeSettings();
11420
+ }
11421
+ }
11422
+ onDocumentFullscreenChange() {
11423
+ if (!this.isBrowser)
11424
+ return;
11425
+ const root = this.rootRef()?.nativeElement;
11426
+ const active = !!root && document.fullscreenElement === root;
11427
+ if (active !== this.isFullscreen()) {
11428
+ this.isFullscreen.set(active);
11429
+ this.fullscreenChange.emit(active);
11430
+ }
11431
+ }
11432
+ onDocumentPointerDown(event) {
11433
+ if (!this.settingsOpen())
11434
+ return;
11435
+ const root = this.rootRef()?.nativeElement;
11436
+ const menu = this.settingsMenuRef()?.nativeElement;
11437
+ const target = event.target;
11438
+ const settingsBtn = root?.querySelector(`#${this.uid}-settings-btn`);
11439
+ if (!menu?.contains(target) && !settingsBtn?.contains(target)) {
11440
+ this.closeSettings();
11441
+ }
11442
+ }
11443
+ // ── Controls visibility ─────────────────────────────────────────────────────
11444
+ /** Show the controls and (if playing) restart the idle timer. */
11445
+ revealControls(reschedule = true) {
11446
+ if (!this.controlsVisible()) {
11447
+ this.controlsVisible.set(true);
11448
+ }
11449
+ if (reschedule) {
11450
+ this.scheduleHide();
11451
+ }
11452
+ else {
11453
+ this.clearHideTimer();
11454
+ }
11455
+ }
11456
+ scheduleHide(delay = IDLE_HIDE_DELAY_MS) {
11457
+ this.clearHideTimer();
11458
+ if (!this.isBrowser || !this.playing())
11459
+ return;
11460
+ this.zone.runOutsideAngular(() => {
11461
+ this.hideTimer = setTimeout(() => {
11462
+ this.hideTimer = null;
11463
+ if (!this.playing() || this.settingsOpen() || this.isSeeking())
11464
+ return;
11465
+ // Keep controls visible while a control (not the root itself) has focus.
11466
+ const root = this.rootRef()?.nativeElement;
11467
+ const active = document.activeElement;
11468
+ if (root && active && active !== root && root.contains(active))
11469
+ return;
11470
+ this.zone.run(() => this.controlsVisible.set(false));
11471
+ }, delay);
11472
+ });
11473
+ }
11474
+ clearHideTimer() {
11475
+ if (this.hideTimer !== null) {
11476
+ clearTimeout(this.hideTimer);
11477
+ this.hideTimer = null;
11478
+ }
11479
+ }
11480
+ /**
11481
+ * High-frequency pointer listeners are registered outside the Angular zone so
11482
+ * mouse movement over the stage doesn't trigger change detection; we only
11483
+ * re-enter the zone when the visibility state actually flips.
11484
+ */
11485
+ attachIdleListeners() {
11486
+ const root = this.rootRef()?.nativeElement;
11487
+ if (!root)
11488
+ return;
11489
+ this.zone.runOutsideAngular(() => {
11490
+ const onMove = () => {
11491
+ if (this.controlsVisible()) {
11492
+ this.scheduleHide();
11493
+ }
11494
+ else {
11495
+ this.zone.run(() => this.revealControls());
11496
+ }
11497
+ };
11498
+ const onLeave = () => {
11499
+ if (this.playing())
11500
+ this.scheduleHide(LEAVE_HIDE_DELAY_MS);
11501
+ };
11502
+ root.addEventListener('pointermove', onMove, { passive: true });
11503
+ root.addEventListener('pointerleave', onLeave, { passive: true });
11504
+ this.destroyRef.onDestroy(() => {
11505
+ root.removeEventListener('pointermove', onMove);
11506
+ root.removeEventListener('pointerleave', onLeave);
11507
+ });
11508
+ });
11509
+ }
11510
+ // ── Helpers ─────────────────────────────────────────────────────────────────
11511
+ safePlay(video) {
11512
+ if (this.pendingPlay)
11513
+ return;
11514
+ const attempt = video.play();
11515
+ if (!attempt || typeof attempt.then !== 'function')
11516
+ return;
11517
+ this.pendingPlay = attempt
11518
+ .catch((err) => {
11519
+ this.playing.set(false);
11520
+ this.playerError.emit({
11521
+ code: 'play-rejected',
11522
+ message: err instanceof Error ? err.message : 'Playback was rejected',
11523
+ originalError: err,
11524
+ });
11525
+ })
11526
+ .finally(() => {
11527
+ this.pendingPlay = null;
11528
+ });
11529
+ }
11530
+ applyCaptionMode(video, enabled) {
11531
+ const list = video.textTracks;
11532
+ if (!list || list.length === 0)
11533
+ return;
11534
+ const configured = this.tracks();
11535
+ const defaultIndex = Math.max(0, configured.findIndex((t) => t.default));
11536
+ for (let i = 0; i < list.length; i++) {
11537
+ const track = list[i];
11538
+ if (track.kind !== 'captions' && track.kind !== 'subtitles')
11539
+ continue;
11540
+ track.mode = enabled && i === defaultIndex ? 'showing' : 'hidden';
11541
+ }
11542
+ }
11543
+ resetForNewSource(video) {
11544
+ this.hasError.set(false);
11545
+ this.hasEnded.set(false);
11546
+ this.hasStarted.set(false);
11547
+ this.isBuffering.set(false);
11548
+ this.bufferedEnd.set(0);
11549
+ this.duration.set(0);
11550
+ this.startTimeApplied = false;
11551
+ // Adopt the new source's start position straight away (it is re-applied for
11552
+ // real once metadata lands) so consumers driving `src` + `startTime`
11553
+ // together — a course player switching lessons — never flash 0:00.
11554
+ this.currentTime.set(Math.max(0, this.startTime()));
11555
+ video.load();
11556
+ if (this.playing()) {
11557
+ this.safePlay(video);
11558
+ }
11559
+ }
11560
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: VideoPlayerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
11561
+ 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 }); }
11562
+ }
11563
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: VideoPlayerComponent, decorators: [{
11564
+ type: Component,
11565
+ args: [{ selector: 'pecb-video-player', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, host: {
11566
+ class: 'pecb-video-player-host',
11567
+ // `title` is a component input — keep it off the host so the browser doesn't
11568
+ // show a native tooltip over the whole stage.
11569
+ '[attr.title]': 'null',
11570
+ '[style.--pecb-video-aspect-ratio]': 'aspectRatio()',
11571
+ '(document:fullscreenchange)': 'onDocumentFullscreenChange()',
11572
+ '(document:pointerdown)': 'onDocumentPointerDown($event)',
11573
+ }, 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"] }]
11574
+ }], 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"] }] } });
11575
+
11576
+ /*
11577
+ * Shared data model for the PECB course components
11578
+ * (`pecb-course-content-panel` + `pecb-course-player`).
11579
+ *
11580
+ * Source of truth: "Course Player — Content Redesign" (data.jsx / panel.jsx).
11581
+ */
11582
+ // ─── Formatting helpers ───────────────────────────────────────────────────────
11583
+ /**
11584
+ * Human-readable lesson length: `"9 min"`, `"12 min 4 sec"`, `"1 hr 3 min"`.
11585
+ * Non-finite / negative input renders as `"0 min"`.
11586
+ */
11587
+ function formatCourseDuration(seconds) {
11588
+ if (!Number.isFinite(seconds) || seconds < 0)
11589
+ return '0 min';
11590
+ const total = Math.floor(seconds);
11591
+ const minutes = Math.floor(total / 60);
11592
+ const rest = total % 60;
11593
+ if (minutes >= 60) {
11594
+ const hours = Math.floor(minutes / 60);
11595
+ return `${hours} hr ${minutes % 60} min`;
11596
+ }
11597
+ return `${minutes} min${rest ? ` ${rest} sec` : ''}`;
11598
+ }
11599
+ /**
11600
+ * Clock reading: `m:ss`, or `h:mm:ss` from one hour up.
11601
+ * Non-finite / negative input renders as `0:00`.
11602
+ */
11603
+ function formatCourseTime(seconds) {
11604
+ if (!Number.isFinite(seconds) || seconds < 0)
11605
+ return '0:00';
11606
+ const total = Math.floor(seconds);
11607
+ const h = Math.floor(total / 3600);
11608
+ const m = Math.floor((total % 3600) / 60);
11609
+ const s = total % 60;
11610
+ const mm = h > 0 ? String(m).padStart(2, '0') : String(m);
11611
+ return h > 0 ? `${h}:${mm}:${String(s).padStart(2, '0')}` : `${mm}:${String(s).padStart(2, '0')}`;
11612
+ }
11613
+ // ─── Derivation helpers ───────────────────────────────────────────────────────
11614
+ const PART_MARKER = ' — Part ';
11615
+ /**
11616
+ * Splits `"Base — Part 3"` into `{ base: 'Base', part: 'Part 3' }`.
11617
+ * A title without the marker yields `{ base: title, part: null }`.
11618
+ */
11619
+ function parseLessonPartTitle(title) {
11620
+ const index = title.indexOf(PART_MARKER);
11621
+ if (index === -1)
11622
+ return { base: title, part: null };
11623
+ return { base: title.slice(0, index), part: `Part ${title.slice(index + PART_MARKER.length).trim()}` };
11624
+ }
11625
+ /**
11626
+ * Resolves the status actually rendered for a lesson.
11627
+ *
11628
+ * The active video lesson follows live playback (`playing` while the player is
11629
+ * running, otherwise `started` — unless it is already `completed`), and a
11630
+ * `playing` status on any *other* lesson degrades to `started`, so exactly one
11631
+ * row can ever show the "now playing" treatment. `locked` always wins.
11632
+ */
11633
+ function resolveLessonStatus(lesson, active, livePlaying) {
11634
+ const raw = lesson.status ?? 'available';
11635
+ if (raw === 'locked')
11636
+ return 'locked';
11637
+ if (active && (lesson.type ?? 'video') === 'video') {
11638
+ return livePlaying ? 'playing' : raw === 'completed' ? 'completed' : 'started';
11639
+ }
11640
+ if (!active && raw === 'playing')
11641
+ return 'started';
11642
+ return raw;
11643
+ }
11644
+ /** Counters for one module, computed from already-resolved lesson statuses. */
11645
+ function courseModuleStats(module, resolve = (l) => l.status ?? 'available') {
11646
+ const total = module.lessons.length;
11647
+ let done = 0;
11648
+ let playing = false;
11649
+ let started = false;
11650
+ let durationSeconds = 0;
11651
+ for (const lesson of module.lessons) {
11652
+ const status = resolve(lesson);
11653
+ if (status === 'completed')
11654
+ done++;
11655
+ if (status === 'playing')
11656
+ playing = true;
11657
+ if (status === 'started')
11658
+ started = true;
11659
+ durationSeconds += lesson.durationSeconds ?? 0;
11660
+ }
11661
+ return {
11662
+ total,
11663
+ done,
11664
+ playing,
11665
+ started,
11666
+ durationSeconds,
11667
+ percent: total > 0 ? Math.round((done / total) * 100) : 0,
11668
+ };
11669
+ }
11670
+ /** Counters for a whole course. */
11671
+ function courseProgress(modules, resolve = (l) => l.status ?? 'available') {
11672
+ const lessons = modules.flatMap((m) => m.lessons);
11673
+ const total = lessons.length;
11674
+ let done = 0;
11675
+ let remainingSeconds = 0;
11676
+ for (const lesson of lessons) {
11677
+ const status = resolve(lesson);
11678
+ if (status === 'completed') {
11679
+ done++;
11680
+ }
11681
+ else if ((lesson.type ?? 'video') === 'video') {
11682
+ remainingSeconds += lesson.durationSeconds ?? 0;
11683
+ }
11684
+ }
11685
+ return {
11686
+ total,
11687
+ done,
11688
+ percent: total > 0 ? Math.round((done / total) * 100) : 0,
11689
+ remainingSeconds,
11690
+ };
11691
+ }
11692
+ /** Finds the module a lesson belongs to. */
11693
+ function findCourseModule(modules, lessonId) {
11694
+ if (!lessonId)
11695
+ return undefined;
11696
+ return modules.find((m) => m.lessons.some((l) => l.id === lessonId));
11697
+ }
11698
+ /** Finds a lesson by id across all modules. */
11699
+ function findCourseLesson(modules, lessonId) {
11700
+ if (!lessonId)
11701
+ return undefined;
11702
+ for (const module of modules) {
11703
+ const lesson = module.lessons.find((l) => l.id === lessonId);
11704
+ if (lesson)
11705
+ return lesson;
11706
+ }
11707
+ return undefined;
11708
+ }
11709
+
11710
+ const DEFAULT_COURSE_CONTENT_PANEL_LABELS = {
11711
+ contentTab: 'Content',
11712
+ transcriptTab: 'Transcript',
11713
+ searchLessons: 'Search lessons and quizzes',
11714
+ searchTranscript: 'Search transcript',
11715
+ clearSearch: 'Clear search',
11716
+ quiz: 'Quiz',
11717
+ questions: '{n} questions',
11718
+ passed: 'Passed · {done}/{total}',
11719
+ answered: '{done} of {total} answered',
11720
+ resume: 'Resume',
11721
+ moduleDone: '{done}/{total} done',
11722
+ section: 'Section {n}',
11723
+ noLessons: 'No lessons match “{query}”.',
11724
+ noTranscript: 'No transcript lines match “{query}”.',
11725
+ autoscroll: 'Autoscroll',
11726
+ courseProgress: 'Course progress',
11727
+ courseProgressDetail: '{done} of {total} lessons · {duration} left',
11728
+ certificate: 'Certificate',
11729
+ locked: 'Locked',
11730
+ lessonList: 'Course content',
11731
+ };
11732
+ // Disc geometry per density (design: 30 / 26 px).
11733
+ const DISC_SIZE = { comfortable: 30, compact: 26 };
11734
+ const RING_STROKE = 3;
11735
+ // ─── Component ────────────────────────────────────────────────────────────────
11736
+ /**
11737
+ * PECB course **Content** panel — the sidebar that sits next to the course
11738
+ * video player.
11739
+ *
11740
+ * Reproduces the "Course Player — Content Redesign" panel exactly: the
11741
+ * Content / Transcript tabs, lesson search, the collapsible **Section**
11742
+ * accordion (one section open at a time, auto-opening the section that owns the
11743
+ * active lesson), status discs (completed / now playing / in-progress ring /
11744
+ * not started / locked), quiz rows with badge + score, per-lesson progress bars,
11745
+ * the "Part 1 / Part 2 …" timeline rail for multi-part lessons, an alternative
11746
+ * flat timeline layout, and the searchable transcript with click-to-seek,
11747
+ * live cue highlighting and an autoscroll toggle.
11748
+ *
11749
+ * It is standalone and data-driven — no player dependency — so it can be used
11750
+ * next to `pecb-video-player`, inside `pecb-course-player`, or on its own.
11751
+ *
11752
+ * @example
11753
+ * ```html
11754
+ * <pecb-course-content-panel
11755
+ * [modules]="modules"
11756
+ * [transcript]="cues"
11757
+ * [(activeLessonId)]="lessonId"
11758
+ * [playing]="playing()"
11759
+ * [currentTime]="currentTime()"
11760
+ * (lessonSelect)="openLesson($event)"
11761
+ * (transcriptSeek)="seekTo($event)">
11762
+ * </pecb-course-content-panel>
11763
+ * ```
11764
+ *
11765
+ * Theming — override the CSS custom properties on the host:
11766
+ * ```css
11767
+ * pecb-course-content-panel { --pecb-course-panel-accent: #a11e29; }
11768
+ * ```
11769
+ */
11770
+ class CourseContentPanelComponent {
11771
+ constructor() {
11772
+ this.platformId = inject(PLATFORM_ID);
11773
+ this.injector = inject(Injector);
11774
+ this.isBrowser = isPlatformBrowser(this.platformId);
11775
+ /** Unique id prefix used to wire ARIA relationships. */
11776
+ this.uid = generateUniqueId('pecb-course-panel');
11777
+ this.transcriptScrollRef = viewChild('transcriptScroll', ...(ngDevMode ? [{ debugName: "transcriptScrollRef" }] : []));
11778
+ // ── Data inputs ─────────────────────────────────────────────────────────────
11779
+ /** Course structure rendered by the Content tab. */
11780
+ this.modules = input([], ...(ngDevMode ? [{ debugName: "modules" }] : []));
11781
+ /**
11782
+ * Transcript cues for the active lesson. A `transcript` on the active
11783
+ * `CourseLesson` takes precedence over this input.
11784
+ */
11785
+ this.transcript = input([], ...(ngDevMode ? [{ debugName: "transcript" }] : []));
11786
+ // ── State (two-way) ─────────────────────────────────────────────────────────
11787
+ /** Currently selected lesson. */
11788
+ this.activeLessonId = model(null, ...(ngDevMode ? [{ debugName: "activeLessonId" }] : []));
11789
+ /** Visible tab. */
11790
+ this.tab = model('content', ...(ngDevMode ? [{ debugName: "tab" }] : []));
11791
+ /**
11792
+ * Id of the expanded section. `null` collapses all of them.
11793
+ * Left `undefined` the panel picks one itself (the active lesson's section,
11794
+ * otherwise the first one).
11795
+ */
11796
+ this.openModuleId = model(undefined, ...(ngDevMode ? [{ debugName: "openModuleId" }] : []));
11797
+ /** Whether the transcript follows playback. */
11798
+ this.autoscroll = model(true, ...(ngDevMode ? [{ debugName: "autoscroll" }] : []));
11799
+ /** Lesson search text (Content tab). */
11800
+ this.query = model('', ...(ngDevMode ? [{ debugName: "query" }] : []));
11801
+ /** Transcript search text. */
11802
+ this.transcriptQuery = model('', ...(ngDevMode ? [{ debugName: "transcriptQuery" }] : []));
11803
+ // ── Live playback state ─────────────────────────────────────────────────────
11804
+ /** Whether the player is currently playing — drives the "now playing" row. */
11805
+ this.playing = input(false, ...(ngDevMode ? [{ debugName: "playing", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
11806
+ /**
11807
+ * Live playhead position in seconds. Used to highlight the transcript cue and
11808
+ * to draw live progress on the active lesson row; falls back to each lesson's
11809
+ * `resumeSeconds` when omitted.
11810
+ */
11811
+ this.currentTime = input(undefined, ...(ngDevMode ? [{ debugName: "currentTime" }] : []));
11812
+ // ── Appearance ──────────────────────────────────────────────────────────────
11813
+ /** @default 'grouped' */
11814
+ this.layout = input('grouped', ...(ngDevMode ? [{ debugName: "layout" }] : []));
11815
+ /** @default 'comfortable' */
11816
+ this.density = input('comfortable', ...(ngDevMode ? [{ debugName: "density" }] : []));
11817
+ /** Colour of the "now playing" treatment. @default 'charcoal' */
11818
+ this.accent = input('charcoal', ...(ngDevMode ? [{ debugName: "accent" }] : []));
11819
+ /** Show the Content / Transcript tab strip. @default true */
11820
+ this.showTabs = input(true, ...(ngDevMode ? [{ debugName: "showTabs", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
11821
+ /** Show the lesson search box. @default true */
11822
+ this.showSearch = input(true, ...(ngDevMode ? [{ debugName: "showSearch", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
11823
+ /** Show the transcript search box. @default true */
11824
+ this.showTranscriptSearch = input(true, ...(ngDevMode ? [{ debugName: "showTranscriptSearch", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
11825
+ /** Show the autoscroll footer on the Transcript tab. @default true */
11826
+ this.showAutoscroll = input(true, ...(ngDevMode ? [{ debugName: "showAutoscroll", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
11827
+ /** Show the circular course-progress header above the list. @default false */
11828
+ this.showProgress = input(false, ...(ngDevMode ? [{ debugName: "showProgress", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
11829
+ /** Show the "Certificate" pill inside the progress header. @default true */
11830
+ this.showCertificate = input(true, ...(ngDevMode ? [{ debugName: "showCertificate", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
11831
+ /**
11832
+ * Render a module that holds exactly one lesson as a single direct-play row
11833
+ * instead of a collapsible section. @default false
11834
+ */
11835
+ this.soloSingleLessonModules = input(false, ...(ngDevMode ? [{ debugName: "soloSingleLessonModules", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
11836
+ /** Partial override of the user-visible strings (i18n). */
11837
+ this.labels = input({}, ...(ngDevMode ? [{ debugName: "labels" }] : []));
11838
+ // ── Outputs ─────────────────────────────────────────────────────────────────
11839
+ /** A lesson row was activated. */
11840
+ this.lessonSelect = output();
11841
+ /** A transcript cue was clicked — seek the player to this position (seconds). */
11842
+ this.transcriptSeek = output();
11843
+ /** A section was expanded or collapsed. */
11844
+ this.moduleToggle = output();
11845
+ /** The "Certificate" pill was clicked. */
11846
+ this.certificateClick = output();
11847
+ // ── Labels ──────────────────────────────────────────────────────────────────
11848
+ this.text = computed(() => ({
11849
+ ...DEFAULT_COURSE_CONTENT_PANEL_LABELS,
11850
+ ...this.labels(),
11851
+ }), ...(ngDevMode ? [{ debugName: "text" }] : []));
11852
+ // ── Theming ─────────────────────────────────────────────────────────────────
11853
+ this.hostClasses = computed(() => [
11854
+ 'pecb-course-panel',
11855
+ `pecb-course-panel--${this.density()}`,
11856
+ `pecb-course-panel--${this.layout()}`,
11857
+ `pecb-course-panel--accent-${this.accent()}`,
11858
+ ].join(' '), ...(ngDevMode ? [{ debugName: "hostClasses" }] : []));
11859
+ // ── Derived data ────────────────────────────────────────────────────────────
11860
+ this.compact = computed(() => this.density() === 'compact', ...(ngDevMode ? [{ debugName: "compact" }] : []));
11861
+ /** Resolved status of every lesson, keyed by lesson id. */
11862
+ this.statusById = computed(() => {
11863
+ const activeId = this.activeLessonId();
11864
+ const livePlaying = this.playing();
11865
+ const map = new Map();
11866
+ for (const module of this.modules()) {
11867
+ for (const lesson of module.lessons) {
11868
+ map.set(lesson.id, resolveLessonStatus(lesson, lesson.id === activeId, livePlaying));
11869
+ }
11870
+ }
11871
+ return map;
11872
+ }, ...(ngDevMode ? [{ debugName: "statusById" }] : []));
11873
+ this.resolveStatus = computed(() => {
11874
+ const map = this.statusById();
11875
+ return (lesson) => map.get(lesson.id) ?? (lesson.status ?? 'available');
11876
+ }, ...(ngDevMode ? [{ debugName: "resolveStatus" }] : []));
11877
+ /** Modules narrowed by the search box (design: match lesson titles, else module title). */
11878
+ this.filteredModules = computed(() => {
11879
+ const term = this.query().trim().toLowerCase();
11880
+ const modules = this.modules();
11881
+ if (!term)
11882
+ return modules;
11883
+ return modules
11884
+ .map((module) => ({
11885
+ ...module,
11886
+ lessons: module.lessons.filter((lesson) => lesson.title.toLowerCase().includes(term)),
11887
+ }))
11888
+ .filter((module) => module.lessons.length > 0 || module.title.toLowerCase().includes(term));
11889
+ }, ...(ngDevMode ? [{ debugName: "filteredModules" }] : []));
11890
+ this.moduleViews = computed(() => {
11891
+ const all = this.modules();
11892
+ const activeId = this.activeLessonId();
11893
+ const solo = this.soloSingleLessonModules();
11894
+ const text = this.text();
11895
+ const livePlaying = this.playing();
11896
+ return this.filteredModules().map((module) => {
11897
+ const index = all.findIndex((m) => m.id === module.id);
11898
+ const active = module.lessons.some((lesson) => lesson.id === activeId);
11899
+ const isSolo = solo && module.lessons.length === 1;
11900
+ // Section counters come from the stored statuses (design behaviour), with
11901
+ // live playback folded in so the badge reacts while the video runs.
11902
+ const raw = courseModuleStats(module);
11903
+ const stats = {
11904
+ ...raw,
11905
+ playing: raw.playing || (active && livePlaying),
11906
+ };
11907
+ return {
11908
+ module,
11909
+ id: module.id,
11910
+ number: (index === -1 ? all.length : index) + 1,
11911
+ title: module.title,
11912
+ stats,
11913
+ statsLabel: format(text.moduleDone, { done: stats.done, total: stats.total }),
11914
+ durationLabel: formatCourseDuration(stats.durationSeconds),
11915
+ allDone: stats.total > 0 && stats.done === stats.total,
11916
+ active,
11917
+ runs: isSolo ? [] : this.buildRuns(module.lessons),
11918
+ solo: isSolo ? this.buildLessonView(module.lessons[0], { titleOverride: module.title }) : null,
11919
+ };
11920
+ });
11921
+ }, ...(ngDevMode ? [{ debugName: "moduleViews" }] : []));
11922
+ this.timelineRows = computed(() => {
11923
+ const all = this.modules();
11924
+ const activeId = this.activeLessonId();
11925
+ const livePlaying = this.playing();
11926
+ const rows = [];
11927
+ for (const module of this.filteredModules()) {
11928
+ const index = all.findIndex((m) => m.id === module.id);
11929
+ const rawStats = courseModuleStats(module);
11930
+ rows.push({
11931
+ key: `h-${module.id}`,
11932
+ header: {
11933
+ number: (index === -1 ? all.length : index) + 1,
11934
+ title: module.title,
11935
+ stats: {
11936
+ ...rawStats,
11937
+ playing: rawStats.playing ||
11938
+ (livePlaying && module.lessons.some((l) => l.id === activeId)),
11939
+ },
11940
+ },
11941
+ lesson: null,
11942
+ });
11943
+ module.lessons.forEach((lesson, i) => {
11944
+ rows.push({
11945
+ key: lesson.id,
11946
+ header: null,
11947
+ lesson: this.buildLessonView(lesson, { connector: i < module.lessons.length - 1 }),
11948
+ });
11949
+ });
11950
+ }
11951
+ return rows;
11952
+ }, ...(ngDevMode ? [{ debugName: "timelineRows" }] : []));
11953
+ this.isEmpty = computed(() => this.filteredModules().length === 0, ...(ngDevMode ? [{ debugName: "isEmpty" }] : []));
11954
+ // ── Progress header ─────────────────────────────────────────────────────────
11955
+ this.progress = computed(() => courseProgress(this.modules()), ...(ngDevMode ? [{ debugName: "progress" }] : []));
11956
+ /** Geometry of the 56 px progress ring (design: r = 22, stroke = 5). */
11957
+ this.progressRing = computed(() => {
11958
+ const radius = 22;
11959
+ const circumference = 2 * Math.PI * radius;
11960
+ return {
11961
+ radius,
11962
+ circumference,
11963
+ offset: circumference * (1 - this.progress().percent / 100),
11964
+ };
11965
+ }, ...(ngDevMode ? [{ debugName: "progressRing" }] : []));
11966
+ this.progressDetail = computed(() => {
11967
+ const stats = this.progress();
11968
+ return format(this.text().courseProgressDetail, {
11969
+ done: stats.done,
11970
+ total: stats.total,
11971
+ duration: formatCourseDuration(stats.remainingSeconds),
11972
+ });
11973
+ }, ...(ngDevMode ? [{ debugName: "progressDetail" }] : []));
11974
+ // ── Transcript ──────────────────────────────────────────────────────────────
11975
+ /** Cues of the active lesson, falling back to the panel-level `transcript`. */
11976
+ this.activeCues = computed(() => {
11977
+ const activeId = this.activeLessonId();
11978
+ for (const module of this.modules()) {
11979
+ const lesson = module.lessons.find((l) => l.id === activeId);
11980
+ if (lesson?.transcript?.length)
11981
+ return lesson.transcript;
11982
+ }
11983
+ return this.transcript();
11984
+ }, ...(ngDevMode ? [{ debugName: "activeCues" }] : []));
11985
+ /** Index of the cue covering the playhead, or `-1`. */
11986
+ this.activeCueIndex = computed(() => {
11987
+ const cues = this.activeCues();
11988
+ const at = this.currentTime();
11989
+ if (at === undefined || cues.length === 0)
11990
+ return -1;
11991
+ let found = -1;
11992
+ for (let i = 0; i < cues.length; i++) {
11993
+ const next = cues[i + 1];
11994
+ if (at >= cues[i].time && (!next || at < next.time)) {
11995
+ found = i;
11996
+ break;
11997
+ }
11998
+ }
11999
+ return found;
12000
+ }, ...(ngDevMode ? [{ debugName: "activeCueIndex" }] : []));
12001
+ this.transcriptRows = computed(() => {
12002
+ const cues = this.activeCues();
12003
+ const term = this.transcriptQuery().trim();
12004
+ const lower = term.toLowerCase();
12005
+ const activeIndex = this.activeCueIndex();
12006
+ const rows = [];
12007
+ cues.forEach((cue, index) => {
12008
+ const matchIndex = lower ? cue.text.toLowerCase().indexOf(lower) : -1;
12009
+ if (lower && matchIndex === -1)
12010
+ return;
12011
+ rows.push({
12012
+ key: `${index}-${cue.time}`,
12013
+ cue,
12014
+ timeLabel: formatCourseTime(cue.time),
12015
+ // While searching, the design drops the "current cue" highlight.
12016
+ current: !lower && index === activeIndex,
12017
+ before: matchIndex === -1 ? cue.text : cue.text.slice(0, matchIndex),
12018
+ match: matchIndex === -1 ? '' : cue.text.slice(matchIndex, matchIndex + term.length),
12019
+ after: matchIndex === -1 ? '' : cue.text.slice(matchIndex + term.length),
12020
+ });
12021
+ });
12022
+ return rows;
12023
+ }, ...(ngDevMode ? [{ debugName: "transcriptRows" }] : []));
12024
+ this.transcriptEmpty = computed(() => this.transcriptRows().length === 0, ...(ngDevMode ? [{ debugName: "transcriptEmpty" }] : []));
12025
+ // ── Messages ────────────────────────────────────────────────────────────────
12026
+ this.noLessonsLabel = computed(() => format(this.text().noLessons, { query: this.query().trim() }), ...(ngDevMode ? [{ debugName: "noLessonsLabel" }] : []));
12027
+ this.noTranscriptLabel = computed(() => format(this.text().noTranscript, { query: this.transcriptQuery().trim() }), ...(ngDevMode ? [{ debugName: "noTranscriptLabel" }] : []));
12028
+ // Keep the section holding the active lesson expanded. Tracks `activeLessonId`
12029
+ // only — so collapsing the current section by hand stays collapsed until the
12030
+ // selection actually changes (design behaviour).
12031
+ effect(() => {
12032
+ const activeId = this.activeLessonId();
12033
+ untracked(() => {
12034
+ if (!activeId)
12035
+ return;
12036
+ const module = findCourseModule(this.modules(), activeId);
12037
+ if (module)
12038
+ this.openModuleId.set(module.id);
12039
+ });
12040
+ });
12041
+ // First time modules are available and nothing has been decided yet, expand
12042
+ // the active lesson's section, else the first one.
12043
+ let autoOpened = false;
12044
+ effect(() => {
12045
+ const modules = this.modules();
12046
+ untracked(() => {
12047
+ if (autoOpened || modules.length === 0)
12048
+ return;
12049
+ autoOpened = true;
12050
+ if (this.openModuleId() !== undefined)
12051
+ return;
12052
+ const module = findCourseModule(modules, this.activeLessonId());
12053
+ this.openModuleId.set(module?.id ?? modules[0].id);
12054
+ });
12055
+ });
12056
+ // Follow the playhead in the transcript.
12057
+ effect(() => {
12058
+ const index = this.activeCueIndex();
12059
+ const auto = this.autoscroll();
12060
+ const tab = this.tab();
12061
+ const searching = this.transcriptQuery().trim().length > 0;
12062
+ untracked(() => {
12063
+ if (!this.isBrowser || !auto || searching || tab !== 'transcript' || index < 0)
12064
+ return;
12065
+ afterNextRender(() => this.scrollActiveCueIntoView(), { injector: this.injector });
12066
+ });
12067
+ });
12068
+ }
12069
+ // ── Template helpers ────────────────────────────────────────────────────────
12070
+ /** `"Section 3"` — the `{n}` placeholder resolved against the label set. */
12071
+ sectionLabel(index) {
12072
+ return format(this.text().section, { n: index });
12073
+ }
12074
+ /** Whether a section is expanded. */
12075
+ isModuleOpen(moduleId) {
12076
+ return this.openModuleId() === moduleId;
12077
+ }
12078
+ /** Expand a section, collapsing the one that was open (single-open accordion). */
12079
+ toggleModule(view) {
12080
+ const open = !this.isModuleOpen(view.id);
12081
+ this.openModuleId.set(open ? view.id : null);
12082
+ this.moduleToggle.emit({ module: view.module, open });
12083
+ }
12084
+ /** Roving keyboard support between section headers (WAI-ARIA accordion). */
12085
+ onModuleHeaderKeydown(event) {
12086
+ const key = event.key;
12087
+ if (key !== 'ArrowDown' && key !== 'ArrowUp' && key !== 'Home' && key !== 'End')
12088
+ return;
12089
+ const target = event.target;
12090
+ const root = target.closest('.pecb-course-panel__list');
12091
+ if (!root)
12092
+ return;
12093
+ const headers = Array.from(root.querySelectorAll('.pecb-course-panel__section-head'));
12094
+ if (headers.length === 0)
12095
+ return;
12096
+ const index = headers.indexOf(target);
12097
+ if (index === -1)
12098
+ return;
12099
+ let next;
12100
+ switch (key) {
12101
+ case 'ArrowDown':
12102
+ next = (index + 1) % headers.length;
12103
+ break;
12104
+ case 'ArrowUp':
12105
+ next = (index - 1 + headers.length) % headers.length;
12106
+ break;
12107
+ case 'Home':
12108
+ next = 0;
12109
+ break;
12110
+ default:
12111
+ next = headers.length - 1;
12112
+ }
12113
+ event.preventDefault();
12114
+ headers[next].focus();
12115
+ }
12116
+ /** Activate a lesson (ignored when locked). */
12117
+ selectLesson(lesson) {
12118
+ if ((lesson.status ?? 'available') === 'locked')
12119
+ return;
12120
+ this.activeLessonId.set(lesson.id);
12121
+ this.lessonSelect.emit(lesson);
12122
+ }
12123
+ /** Seek the player to a transcript cue. */
12124
+ seekToCue(cue) {
12125
+ this.transcriptSeek.emit(cue.time);
12126
+ }
12127
+ setTab(tab) {
12128
+ this.tab.set(tab);
12129
+ }
12130
+ onTabKeydown(event) {
12131
+ if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight')
12132
+ return;
12133
+ event.preventDefault();
12134
+ this.setTab(this.tab() === 'content' ? 'transcript' : 'content');
12135
+ const next = event.target
12136
+ .closest('[role="tablist"]')
12137
+ ?.querySelector('[aria-selected="false"]');
12138
+ next?.focus();
12139
+ }
12140
+ onQueryInput(event) {
12141
+ this.query.set(event.target.value);
12142
+ }
12143
+ onTranscriptQueryInput(event) {
12144
+ this.transcriptQuery.set(event.target.value);
12145
+ }
12146
+ clearTranscriptQuery() {
12147
+ this.transcriptQuery.set('');
12148
+ }
12149
+ toggleAutoscroll() {
12150
+ this.autoscroll.update((value) => !value);
12151
+ }
12152
+ onAutoscrollChange(event) {
12153
+ this.autoscroll.set(event.target.checked);
12154
+ }
12155
+ // ── Internals ───────────────────────────────────────────────────────────────
12156
+ /**
12157
+ * Splits a module's lessons into runs of consecutive lessons sharing a base
12158
+ * title. A run of two or more where at least one carries a `— Part n` suffix
12159
+ * renders as a sub-heading plus a connected rail (design behaviour).
12160
+ */
12161
+ buildRuns(lessons) {
12162
+ const runs = [];
12163
+ let i = 0;
12164
+ while (i < lessons.length) {
12165
+ const base = parseLessonPartTitle(lessons[i].title).base;
12166
+ const run = [];
12167
+ let j = i;
12168
+ while (j < lessons.length && parseLessonPartTitle(lessons[j].title).base === base) {
12169
+ run.push(lessons[j]);
12170
+ j++;
12171
+ }
12172
+ const hasParts = run.some((lesson) => parseLessonPartTitle(lesson.title).part !== null);
12173
+ if (hasParts && run.length > 1) {
12174
+ runs.push({
12175
+ key: `parts-${run[0].id}`,
12176
+ parts: true,
12177
+ base,
12178
+ lessons: run.map((lesson) => {
12179
+ const { part } = parseLessonPartTitle(lesson.title);
12180
+ // Inside a parts rail the quiz shows only its badge (design).
12181
+ const titleOverride = (lesson.type ?? 'video') === 'quiz' ? null : part ?? lesson.title;
12182
+ return this.buildLessonView(lesson, { titleOverride });
12183
+ }),
12184
+ });
12185
+ }
12186
+ else {
12187
+ runs.push({
12188
+ key: `plain-${run[0].id}`,
12189
+ parts: false,
12190
+ base,
12191
+ lessons: run.map((lesson) => this.buildLessonView(lesson)),
12192
+ });
12193
+ }
12194
+ i = j;
12195
+ }
12196
+ return runs;
12197
+ }
12198
+ buildLessonView(lesson, options = {}) {
12199
+ const text = this.text();
12200
+ const compact = this.compact();
12201
+ const active = lesson.id === this.activeLessonId();
12202
+ const status = this.resolveStatus()(lesson);
12203
+ const isQuiz = (lesson.type ?? 'video') === 'quiz';
12204
+ const isPlaying = status === 'playing';
12205
+ const isStarted = status === 'started';
12206
+ const isCompleted = status === 'completed';
12207
+ const isLocked = status === 'locked';
12208
+ const duration = lesson.durationSeconds ?? 0;
12209
+ const questions = lesson.questionCount ?? 0;
12210
+ const answered = lesson.answeredCount ?? 0;
12211
+ // The active lesson follows the live playhead; everything else uses its
12212
+ // stored resume position.
12213
+ const live = this.currentTime();
12214
+ const at = active && live !== undefined ? live : lesson.resumeSeconds ?? 0;
12215
+ const fraction = isQuiz
12216
+ ? questions
12217
+ ? Math.min(1, answered / questions)
12218
+ : 0.4
12219
+ : duration
12220
+ ? Math.min(1, at / duration)
12221
+ : 0.4;
12222
+ const discSize = compact ? DISC_SIZE.compact : DISC_SIZE.comfortable;
12223
+ const ringRadius = discSize / 2 - RING_STROKE / 2;
12224
+ const ringCircumference = 2 * Math.PI * ringRadius;
12225
+ const showBar = (isPlaying || isStarted) && (isQuiz ? questions > 0 : duration > 0);
12226
+ const barPercent = isQuiz
12227
+ ? questions
12228
+ ? (answered / questions) * 100
12229
+ : 0
12230
+ : duration
12231
+ ? Math.min(100, (at / duration) * 100)
12232
+ : 0;
12233
+ let quizResultLabel = null;
12234
+ if (isQuiz && isCompleted) {
12235
+ quizResultLabel = format(text.passed, { done: lesson.correctCount ?? 0, total: questions });
12236
+ }
12237
+ else if (isQuiz && isStarted) {
12238
+ quizResultLabel = format(text.answered, { done: answered, total: questions });
12239
+ }
12240
+ const timeLabel = !isQuiz && (isPlaying || isStarted) && duration
12241
+ ? `${isStarted ? `${text.resume} ` : ''}${formatCourseTime(at)} / ${formatCourseTime(duration)}`
12242
+ : null;
12243
+ const displayTitle = options.titleOverride !== undefined ? options.titleOverride : lesson.title;
12244
+ return {
12245
+ lesson,
12246
+ id: lesson.id,
12247
+ active,
12248
+ status,
12249
+ isQuiz,
12250
+ isPlaying,
12251
+ isStarted,
12252
+ isCompleted,
12253
+ isLocked,
12254
+ displayTitle,
12255
+ ariaLabel: isLocked ? `${lesson.title} — ${text.locked}` : lesson.title,
12256
+ durationLabel: formatCourseDuration(duration),
12257
+ questionsLabel: format(text.questions, { n: questions }),
12258
+ quizResultLabel,
12259
+ timeLabel,
12260
+ showBar,
12261
+ barPercent,
12262
+ discSize,
12263
+ discCenter: discSize / 2,
12264
+ ringRadius,
12265
+ ringCircumference,
12266
+ ringOffset: ringCircumference * (1 - fraction),
12267
+ ringViewBox: `0 0 ${discSize} ${discSize}`,
12268
+ checkSize: compact ? 14 : 16,
12269
+ pauseSize: compact ? 11 : 12,
12270
+ // The in-progress ring leaves a touch less room than the plain outline.
12271
+ playIconSize: isStarted ? (compact ? 9 : 10) : compact ? 10 : 11,
12272
+ quizIconSize: isStarted ? (compact ? 13 : 14) : compact ? 14 : 15,
12273
+ connector: options.connector ?? false,
12274
+ connectorDone: isCompleted,
12275
+ };
12276
+ }
12277
+ scrollActiveCueIntoView() {
12278
+ const container = this.transcriptScrollRef()?.nativeElement;
12279
+ if (!container)
12280
+ return;
12281
+ const row = container.querySelector('[data-current="true"]');
12282
+ if (!row)
12283
+ return;
12284
+ const rowRect = row.getBoundingClientRect();
12285
+ const boxRect = container.getBoundingClientRect();
12286
+ const top = container.scrollTop + (rowRect.top - boxRect.top) - (boxRect.height / 2 - rowRect.height / 2);
12287
+ const reduceMotion = typeof window !== 'undefined' &&
12288
+ typeof window.matchMedia === 'function' &&
12289
+ window.matchMedia('(prefers-reduced-motion: reduce)').matches;
12290
+ container.scrollTo({ top, behavior: reduceMotion ? 'auto' : 'smooth' });
12291
+ }
12292
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: CourseContentPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
12293
+ 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 }); }
12294
+ }
12295
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: CourseContentPanelComponent, decorators: [{
12296
+ type: Component,
12297
+ 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"] }]
12298
+ }], 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"] }] } });
12299
+ /** Replaces `{token}` placeholders in a label. */
12300
+ function format(template, values) {
12301
+ return template.replace(/\{(\w+)\}/g, (match, key) => key in values ? String(values[key]) : match);
12302
+ }
12303
+
12304
+ /**
12305
+ * PECB course player — the video stage with the **Content** sidebar attached,
12306
+ * exactly as laid out in "Course Player — Content Redesign".
12307
+ *
12308
+ * Composes {@link VideoPlayerComponent} and {@link CourseContentPanelComponent}
12309
+ * and wires them together: picking a lesson loads its media and resumes at its
12310
+ * saved position, clicking a transcript cue seeks the video, and the panel
12311
+ * follows playback live (now-playing row, progress bars, transcript highlight).
12312
+ *
12313
+ * The two-column grid collapses to a single stacked column when the space it is
12314
+ * given gets narrow, so the same markup works in a page, a drawer or a phone.
12315
+ *
12316
+ * @example
12317
+ * ```html
12318
+ * <pecb-course-player
12319
+ * [modules]="modules"
12320
+ * [(activeLessonId)]="lessonId"
12321
+ * (lessonSelect)="track($event)"
12322
+ * (lessonEnded)="markComplete($event)">
12323
+ * </pecb-course-player>
12324
+ * ```
12325
+ *
12326
+ * Layout hooks:
12327
+ * ```css
12328
+ * pecb-course-player {
12329
+ * --pecb-course-player-panel-width: 408px;
12330
+ * --pecb-course-player-gap: 20px;
12331
+ * --pecb-course-player-panel-stacked-height: 460px;
12332
+ * }
12333
+ * ```
12334
+ */
12335
+ class CoursePlayerComponent {
12336
+ constructor() {
12337
+ // ── Data ────────────────────────────────────────────────────────────────────
12338
+ /** Course structure. */
12339
+ this.modules = input([], ...(ngDevMode ? [{ debugName: "modules" }] : []));
12340
+ /** Fallback transcript, used when the active lesson has no `transcript`. */
12341
+ this.transcript = input([], ...(ngDevMode ? [{ debugName: "transcript" }] : []));
12342
+ // ── State (two-way) ─────────────────────────────────────────────────────────
12343
+ /** Selected lesson. */
12344
+ this.activeLessonId = model(null, ...(ngDevMode ? [{ debugName: "activeLessonId" }] : []));
12345
+ /** Playback state of the video. */
12346
+ this.playing = model(false, ...(ngDevMode ? [{ debugName: "playing" }] : []));
12347
+ /** Playhead position, in seconds. */
12348
+ this.currentTime = model(0, ...(ngDevMode ? [{ debugName: "currentTime" }] : []));
12349
+ /** Which panel tab is showing. */
12350
+ this.panelTab = model('content', ...(ngDevMode ? [{ debugName: "panelTab" }] : []));
12351
+ // ── Video options ───────────────────────────────────────────────────────────
12352
+ /** Poster used when the active lesson has none. */
12353
+ this.poster = input(undefined, ...(ngDevMode ? [{ debugName: "poster" }] : []));
12354
+ /** Media used when the active lesson has no `src` (e.g. a single-video course). */
12355
+ this.src = input(undefined, ...(ngDevMode ? [{ debugName: "src" }] : []));
12356
+ /** Caption tracks used when the active lesson has none. */
12357
+ this.tracks = input([], ...(ngDevMode ? [{ debugName: "tracks" }] : []));
12358
+ /** Stage label used when the active lesson has no `stageLabel`. */
12359
+ this.stageLabel = input(undefined, ...(ngDevMode ? [{ debugName: "stageLabel" }] : []));
12360
+ /** Brand watermark on the video stage. @default 'PECB' */
12361
+ this.watermark = input('PECB', ...(ngDevMode ? [{ debugName: "watermark" }] : []));
12362
+ /** @default '16 / 9' */
12363
+ this.aspectRatio = input('16 / 9', ...(ngDevMode ? [{ debugName: "aspectRatio" }] : []));
12364
+ /** @default 'metadata' */
12365
+ this.preload = input('metadata', ...(ngDevMode ? [{ debugName: "preload" }] : []));
12366
+ /** `crossorigin` for the media element. */
12367
+ this.crossOrigin = input(undefined, ...(ngDevMode ? [{ debugName: "crossOrigin" }] : []));
12368
+ /** Seconds the rewind button / arrow keys jump. @default 10 */
12369
+ this.seekStep = input(10, ...(ngDevMode ? [{ debugName: "seekStep", transform: numberAttribute }] : [{ transform: numberAttribute }]));
12370
+ /** i18n overrides for the video player. */
12371
+ this.videoLabels = input({}, ...(ngDevMode ? [{ debugName: "videoLabels" }] : []));
12372
+ /** Start playing as soon as a video lesson is picked. @default true */
12373
+ this.autoPlayOnSelect = input(true, ...(ngDevMode ? [{ debugName: "autoPlayOnSelect", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
12374
+ /** Show the active lesson's title under the video. @default true */
12375
+ this.showLessonTitle = input(true, ...(ngDevMode ? [{ debugName: "showLessonTitle", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
12376
+ // ── Panel options ───────────────────────────────────────────────────────────
12377
+ /** Which side the panel sits on. @default 'end' */
12378
+ this.panelPosition = input('end', ...(ngDevMode ? [{ debugName: "panelPosition" }] : []));
12379
+ /** @default 'grouped' */
12380
+ this.layout = input('grouped', ...(ngDevMode ? [{ debugName: "layout" }] : []));
12381
+ /** @default 'comfortable' */
12382
+ this.density = input('comfortable', ...(ngDevMode ? [{ debugName: "density" }] : []));
12383
+ /** @default 'charcoal' */
12384
+ this.accent = input('charcoal', ...(ngDevMode ? [{ debugName: "accent" }] : []));
12385
+ /** Show the panel's tab strip. @default true */
12386
+ this.showTabs = input(true, ...(ngDevMode ? [{ debugName: "showTabs", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
12387
+ /** Show the lesson search box. @default true */
12388
+ this.showSearch = input(true, ...(ngDevMode ? [{ debugName: "showSearch", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
12389
+ /** Show the circular course-progress header. @default false */
12390
+ this.showProgress = input(false, ...(ngDevMode ? [{ debugName: "showProgress", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
12391
+ /** Render single-lesson sections as direct-play rows. @default false */
12392
+ this.soloSingleLessonModules = input(false, ...(ngDevMode ? [{ debugName: "soloSingleLessonModules", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
12393
+ /** i18n overrides for the panel. */
12394
+ this.panelLabels = input({}, ...(ngDevMode ? [{ debugName: "panelLabels" }] : []));
12395
+ // ── Outputs ─────────────────────────────────────────────────────────────────
12396
+ /** A lesson row was picked (fires for quizzes too). */
12397
+ this.lessonSelect = output();
12398
+ /** The video of a lesson played to the end. */
12399
+ this.lessonEnded = output();
12400
+ /** Playhead moved — use it to persist progress. */
12401
+ this.timeUpdate = output();
12402
+ /** The panel's "Certificate" pill was clicked. */
12403
+ this.certificateClick = output();
12404
+ /** The media failed to load, or playback was rejected. */
12405
+ this.playerError = output();
12406
+ // ── Derived ─────────────────────────────────────────────────────────────────
12407
+ /** The lesson currently selected in the panel (may be a quiz). */
12408
+ this.activeLesson = computed(() => findCourseLesson(this.modules(), this.activeLessonId()), ...(ngDevMode ? [{ debugName: "activeLesson" }] : []));
12409
+ /**
12410
+ * Id of the last *video* lesson that was selected. Picking a quiz keeps the
12411
+ * video on the stage (design behaviour) instead of blanking it.
12412
+ */
12413
+ this.videoLessonId = signal(null, ...(ngDevMode ? [{ debugName: "videoLessonId" }] : []));
12414
+ this.videoLesson = computed(() => {
12415
+ const explicit = findCourseLesson(this.modules(), this.videoLessonId());
12416
+ if (explicit)
12417
+ return explicit;
12418
+ // Fall back to the first video lesson so the stage is never empty.
12419
+ for (const module of this.modules()) {
12420
+ const lesson = module.lessons.find((l) => (l.type ?? 'video') === 'video');
12421
+ if (lesson)
12422
+ return lesson;
12423
+ }
12424
+ return undefined;
12425
+ }, ...(ngDevMode ? [{ debugName: "videoLesson" }] : []));
12426
+ this.videoSrc = computed(() => this.videoLesson()?.src ?? this.src(), ...(ngDevMode ? [{ debugName: "videoSrc" }] : []));
12427
+ this.videoPoster = computed(() => this.videoLesson()?.poster ?? this.poster(), ...(ngDevMode ? [{ debugName: "videoPoster" }] : []));
12428
+ this.videoTracks = computed(() => this.videoLesson()?.tracks ?? this.tracks(), ...(ngDevMode ? [{ debugName: "videoTracks" }] : []));
12429
+ this.videoStageLabel = computed(() => this.videoLesson()?.stageLabel ?? this.stageLabel(), ...(ngDevMode ? [{ debugName: "videoStageLabel" }] : []));
12430
+ this.videoTitle = computed(() => this.videoLesson()?.title, ...(ngDevMode ? [{ debugName: "videoTitle" }] : []));
12431
+ this.lessonTitle = computed(() => this.activeLesson()?.title ?? '', ...(ngDevMode ? [{ debugName: "lessonTitle" }] : []));
12432
+ /** Resume position handed to the player whenever the source changes. */
12433
+ this.startTime = computed(() => this.videoLesson()?.resumeSeconds ?? 0, ...(ngDevMode ? [{ debugName: "startTime" }] : []));
12434
+ this.hostClasses = computed(() => [
12435
+ 'pecb-course-player',
12436
+ `pecb-course-player--panel-${this.panelPosition()}`,
12437
+ ].join(' '), ...(ngDevMode ? [{ debugName: "hostClasses" }] : []));
12438
+ /** Id of the lesson whose resume position has already been pushed into `currentTime`. */
12439
+ this.resumeAppliedFor = null;
12440
+ // Track the newest video lesson, including the initial selection.
12441
+ effect(() => {
12442
+ const lesson = this.activeLesson();
12443
+ untracked(() => {
12444
+ if (lesson && (lesson.type ?? 'video') === 'video') {
12445
+ this.videoLessonId.set(lesson.id);
12446
+ }
12447
+ });
12448
+ });
12449
+ // Seed the playhead from the lesson's stored position whenever the loaded
12450
+ // video changes — including the very first one, which arrives as an input
12451
+ // rather than through a click. Without this the panel would show 0:00 for a
12452
+ // half-watched lesson until the media reported its own time.
12453
+ effect(() => {
12454
+ const lesson = this.videoLesson();
12455
+ untracked(() => {
12456
+ if (!lesson || lesson.id === this.resumeAppliedFor)
12457
+ return;
12458
+ this.resumeAppliedFor = lesson.id;
12459
+ this.currentTime.set(lesson.resumeSeconds ?? 0);
12460
+ });
12461
+ });
12462
+ }
12463
+ // ── Wiring ──────────────────────────────────────────────────────────────────
12464
+ /** Panel → player: load a lesson, resuming where it left off. */
12465
+ onLessonSelect(lesson) {
12466
+ const isVideo = (lesson.type ?? 'video') === 'video';
12467
+ if (isVideo) {
12468
+ this.videoLessonId.set(lesson.id);
12469
+ this.currentTime.set(lesson.resumeSeconds ?? 0);
12470
+ this.playing.set(this.autoPlayOnSelect());
12471
+ }
12472
+ else {
12473
+ // A quiz has nothing to play — stop the video and leave it on the stage.
12474
+ this.playing.set(false);
12475
+ }
12476
+ this.lessonSelect.emit(lesson);
12477
+ }
12478
+ /** Panel → player: jump to a transcript cue and keep playing. */
12479
+ onTranscriptSeek(seconds) {
12480
+ this.currentTime.set(seconds);
12481
+ this.playing.set(true);
12482
+ }
12483
+ onPlaybackEnded() {
12484
+ const lesson = this.videoLesson();
12485
+ if (lesson)
12486
+ this.lessonEnded.emit(lesson);
12487
+ }
12488
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: CoursePlayerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
12489
+ 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 }); }
12490
+ }
12491
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: CoursePlayerComponent, decorators: [{
12492
+ type: Component,
12493
+ 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"] }]
12494
+ }], 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"] }] } });
12495
+
10658
12496
  /**
10659
12497
  * An injectable, Toastr-style notification service.
10660
12498
  *
@@ -17297,5 +19135,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
17297
19135
  * Generated bundle index. Do not edit.
17298
19136
  */
17299
19137
 
17300
- 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 };
19138
+ 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 };
17301
19139
  //# sourceMappingURL=pecb-ui-components.mjs.map