@pecb-ui/components 1.1.9 → 1.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +257 -70
- package/data-display/index.d.ts +1 -1
- package/fesm2022/pecb-ui-components-data-display.mjs +2 -2
- package/fesm2022/pecb-ui-components-data-display.mjs.map +1 -1
- package/fesm2022/pecb-ui-components.mjs +2169 -216
- package/fesm2022/pecb-ui-components.mjs.map +1 -1
- package/index.d.ts +906 -45
- package/package.json +1 -1
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { Injectable, inject, input, output, HostBinding, Component, ChangeDetectionStrategy, model, computed, signal, HostListener, ContentChild, effect, forwardRef, ViewChild, viewChild, Directive, untracked, ElementRef, ApplicationRef, EnvironmentInjector, createComponent, contentChildren,
|
|
2
|
+
import { Injectable, inject, input, output, HostBinding, Component, ChangeDetectionStrategy, model, computed, signal, HostListener, ContentChild, effect, forwardRef, ViewChild, ViewContainerRef, viewChild, ViewEncapsulation, Directive, untracked, ElementRef, ApplicationRef, EnvironmentInjector, createComponent, contentChildren, PLATFORM_ID, NgZone, DestroyRef, booleanAttribute, numberAttribute, afterNextRender, Injector, Inject, ViewChildren, Renderer2 } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/common';
|
|
4
|
-
import { CommonModule, NgOptimizedImage, isPlatformBrowser } from '@angular/common';
|
|
4
|
+
import { CommonModule, NgOptimizedImage, isPlatformBrowser, NgTemplateOutlet } from '@angular/common';
|
|
5
5
|
import * as i1$1 from '@angular/platform-browser';
|
|
6
6
|
import { DomSanitizer } from '@angular/platform-browser';
|
|
7
7
|
import * as i2 from '@angular/cdk/overlay';
|
|
8
8
|
import { Overlay, OverlayModule } from '@angular/cdk/overlay';
|
|
9
9
|
import * as i1$2 from '@angular/forms';
|
|
10
10
|
import { NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms';
|
|
11
|
-
import {
|
|
11
|
+
import { CdkTrapFocus } from '@angular/cdk/a11y';
|
|
12
|
+
import { TemplatePortal } from '@angular/cdk/portal';
|
|
13
|
+
import { Subscription, Subject, BehaviorSubject } from 'rxjs';
|
|
12
14
|
import { map } from 'rxjs/operators';
|
|
13
15
|
|
|
14
16
|
/**
|
|
@@ -4523,11 +4525,11 @@ class TableComponent {
|
|
|
4523
4525
|
this.selectionChange.emit({ selectedRows, selectedIndices: this.selectedIndices() });
|
|
4524
4526
|
}
|
|
4525
4527
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: TableComponent, deps: [{ token: i1$1.DomSanitizer }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
4526
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.16", type: TableComponent, isStandalone: true, selector: "pecb-table", inputs: { columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, showHeader: { classPropertyName: "showHeader", publicName: "showHeader", isSignal: true, isRequired: false, transformFunction: null }, hoverable: { classPropertyName: "hoverable", publicName: "hoverable", isSignal: true, isRequired: false, transformFunction: null }, showPagination: { classPropertyName: "showPagination", publicName: "showPagination", isSignal: true, isRequired: false, transformFunction: null }, totalItems: { classPropertyName: "totalItems", publicName: "totalItems", isSignal: true, isRequired: false, transformFunction: null }, currentPage: { classPropertyName: "currentPage", publicName: "currentPage", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, pageSizeOptions: { classPropertyName: "pageSizeOptions", publicName: "pageSizeOptions", isSignal: true, isRequired: false, transformFunction: null }, showPageSizeSelector: { classPropertyName: "showPageSizeSelector", publicName: "showPageSizeSelector", isSignal: true, isRequired: false, transformFunction: null }, paginationVariant: { classPropertyName: "paginationVariant", publicName: "paginationVariant", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, selectedIndices: { classPropertyName: "selectedIndices", publicName: "selectedIndices", isSignal: true, isRequired: false, transformFunction: null }, stickyHeader: { classPropertyName: "stickyHeader", publicName: "stickyHeader", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, minWidth: { classPropertyName: "minWidth", publicName: "minWidth", isSignal: true, isRequired: false, transformFunction: null }, wrapText: { classPropertyName: "wrapText", publicName: "wrapText", isSignal: true, isRequired: false, transformFunction: null }, sortColumn: { classPropertyName: "sortColumn", publicName: "sortColumn", isSignal: true, isRequired: false, transformFunction: null }, sortDirection: { classPropertyName: "sortDirection", publicName: "sortDirection", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, loadingText: { classPropertyName: "loadingText", publicName: "loadingText", isSignal: true, isRequired: false, transformFunction: null }, selectAllAriaLabel: { classPropertyName: "selectAllAriaLabel", publicName: "selectAllAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, selectRowAriaLabel: { classPropertyName: "selectRowAriaLabel", publicName: "selectRowAriaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { currentPage: "currentPageChange", pageSize: "pageSizeChange", selectedIndices: "selectedIndicesChange", sortColumn: "sortColumnChange", sortDirection: "sortDirectionChange", pageChange: "pageChange", rowClick: "rowClick", rowAction: "rowAction", sortChange: "sortChange", selectionChange: "selectionChange" }, queries: [{ propertyName: "emptyTemplate", first: true, predicate: ["emptyTemplate"], descendants: true }, { propertyName: "loadingTemplate", first: true, predicate: ["loadingTemplate"], descendants: true }], ngImport: i0, template: "<div class=\"pecb-table-wrapper\">\n <div [class]=\"containerClasses\" [style.maxHeight]=\"maxHeight()\">\n <!-- Loading State -->\n <div *ngIf=\"loading()\" class=\"pecb-table__loading\">\n <ng-container *ngIf=\"loadingTemplate; else defaultLoading\">\n <ng-container *ngTemplateOutlet=\"loadingTemplate\"></ng-container>\n </ng-container>\n <ng-template #defaultLoading>\n <div class=\"pecb-table__loading-spinner\">\n <svg class=\"pecb-table__spinner-icon\" viewBox=\"0 0 24 24\" fill=\"none\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-dasharray=\"31.4 31.4\" />\n </svg>\n <span>{{ loadingText() }}</span>\n </div>\n </ng-template>\n </div>\n\n <!-- Table -->\n <table\n *ngIf=\"!loading()\"\n [class]=\"tableClasses\"\n [style.min-width]=\"minWidth()\"\n [attr.aria-label]=\"ariaLabel()\"\n role=\"table\">\n\n <!-- Table Header -->\n <thead *ngIf=\"showHeader()\" class=\"pecb-table__head\" [class.pecb-table__head--sticky]=\"stickyHeader()\">\n <tr class=\"pecb-table__row pecb-table__row--header\" role=\"row\">\n <!-- Selection Checkbox Column -->\n <th\n *ngIf=\"selectable()\"\n class=\"pecb-table__cell pecb-table__cell--header pecb-table__cell--checkbox\"\n role=\"columnheader\"\n scope=\"col\">\n <label class=\"pecb-table__checkbox-wrapper\">\n <input\n type=\"checkbox\"\n class=\"pecb-table__checkbox\"\n [checked]=\"isAllSelected()\"\n [indeterminate]=\"isSomeSelected()\"\n (change)=\"toggleAllSelection()\"\n [attr.aria-label]=\"selectAllAriaLabel()\" />\n <span class=\"pecb-table__checkbox-custom\"></span>\n </label>\n </th>\n\n <!-- Data Columns -->\n <th\n *ngFor=\"let column of columns(); trackBy: trackByKey\"\n class=\"pecb-table__cell pecb-table__cell--header\"\n [class.pecb-table__cell--sortable]=\"column.sortable\"\n [class]=\"getAlignmentClass(column)\"\n [ngClass]=\"column.cssClass\"\n [style.width]=\"column.width\"\n [attr.aria-sort]=\"column.sortable ? (sortColumn() === column.key ? (sortDirection() === 'asc' ? 'ascending' : 'descending') : 'none') : null\"\n role=\"columnheader\"\n scope=\"col\"\n (click)=\"onHeaderClick(column)\"\n (keydown.enter)=\"onHeaderClick(column)\"\n (keydown.space)=\"onHeaderClick(column); $event.preventDefault()\"\n [attr.tabindex]=\"column.sortable ? 0 : null\">\n <div class=\"pecb-table__header-content\">\n <ng-container *ngIf=\"column.headerTemplate; else defaultHeader\">\n <ng-container *ngTemplateOutlet=\"column.headerTemplate; context: { $implicit: column }\"></ng-container>\n </ng-container>\n <ng-template #defaultHeader>\n <span class=\"pecb-table__header-text\">{{ column.label }}</span>\n </ng-template>\n\n <!-- Sort Icon -->\n <span *ngIf=\"column.sortable\" class=\"pecb-table__sort-icon\" [attr.data-sort]=\"getSortState(column)\">\n <svg *ngIf=\"getSortState(column) === 'none'\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n <path d=\"M8 3L12 7H4L8 3Z\" fill=\"currentColor\" opacity=\"0.4\"/>\n <path d=\"M8 13L4 9H12L8 13Z\" fill=\"currentColor\" opacity=\"0.4\"/>\n </svg>\n <svg *ngIf=\"getSortState(column) === 'asc'\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n <path d=\"M8 3L12 7H4L8 3Z\" fill=\"currentColor\"/>\n <path d=\"M8 13L4 9H12L8 13Z\" fill=\"currentColor\" opacity=\"0.4\"/>\n </svg>\n <svg *ngIf=\"getSortState(column) === 'desc'\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n <path d=\"M8 3L12 7H4L8 3Z\" fill=\"currentColor\" opacity=\"0.4\"/>\n <path d=\"M8 13L4 9H12L8 13Z\" fill=\"currentColor\"/>\n </svg>\n </span>\n </div>\n </th>\n </tr>\n </thead>\n\n <!-- Table Body -->\n <tbody class=\"pecb-table__body\">\n <!-- Empty State -->\n <tr *ngIf=\"data().length === 0\" class=\"pecb-table__row pecb-table__row--empty\">\n <td\n [attr.colspan]=\"selectable() ? columns().length + 1 : columns().length\"\n class=\"pecb-table__cell pecb-table__cell--empty\">\n <ng-container *ngIf=\"emptyTemplate; else defaultEmpty\">\n <ng-container *ngTemplateOutlet=\"emptyTemplate\"></ng-container>\n </ng-container>\n <ng-template #defaultEmpty>\n <div class=\"pecb-table__empty-content\">\n <span class=\"pecb-table__empty-message\">{{ emptyMessage() }}</span>\n </div>\n </ng-template>\n </td>\n </tr>\n\n <!-- Data Rows -->\n <tr\n *ngFor=\"let row of data(); let i = index; trackBy: trackByIndex\"\n class=\"pecb-table__row pecb-table__row--data\"\n [class.pecb-table__row--selected]=\"isRowSelected(i)\"\n role=\"row\"\n (click)=\"onRowClick(row, i)\"\n [attr.tabindex]=\"0\"\n (keydown.enter)=\"onRowClick(row, i)\">\n\n <!-- Selection Checkbox -->\n <td\n *ngIf=\"selectable()\"\n class=\"pecb-table__cell pecb-table__cell--checkbox\"\n role=\"cell\"\n (click)=\"$event.stopPropagation()\">\n <label class=\"pecb-table__checkbox-wrapper\">\n <input\n type=\"checkbox\"\n class=\"pecb-table__checkbox\"\n [checked]=\"isRowSelected(i)\"\n (change)=\"toggleRowSelection(i)\"\n [attr.aria-label]=\"selectRowAriaLabel() + ' ' + (i + 1)\" />\n <span class=\"pecb-table__checkbox-custom\"></span>\n </label>\n </td>\n\n <!-- Data Cells -->\n <td\n *ngFor=\"let column of columns(); trackBy: trackByKey\"\n class=\"pecb-table__cell\"\n [class]=\"getAlignmentClass(column)\"\n [ngClass]=\"column.cssClass\"\n [class.pecb-table__cell--wrap]=\"isCellWrapped(column)\"\n [class.pecb-table__cell--truncate]=\"!isCellWrapped(column)\"\n [style.max-width]=\"column.maxWidth || column.width\"\n [attr.title]=\"getCellTitle(row, column)\"\n role=\"cell\">\n\n <!-- Custom Template -->\n <ng-container *ngIf=\"column.cellTemplate\">\n <ng-container *ngTemplateOutlet=\"column.cellTemplate; context: { $implicit: getCellValue(row, column.key), row: row, column: column, index: i }\"></ng-container>\n </ng-container>\n\n <!-- Dynamic Component - renders any component passed via dynamicComponent -->\n <ng-container *ngIf=\"!column.cellTemplate && hasDynamicComponent(column)\">\n <ng-container\n *ngComponentOutlet=\"column.dynamicComponent!; inputs: getDynamicComponentInputs(row, column, i)\">\n </ng-container>\n </ng-container>\n\n <!-- Status Component - renders pecb-status -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'status'\">\n <pecb-status\n [type]=\"getStatusType(getCellValue(row, column.key), column)\"\n [variant]=\"getStatusVariant(column)\"\n [size]=\"getStatusSize(column)\">\n {{ getCellValue(row, column.key) }}\n </pecb-status>\n </ng-container>\n\n <!-- User Component - renders avatar + name + email -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'user'\">\n <div class=\"pecb-table__user-cell\">\n <img\n *ngIf=\"getUserData(row, column).avatar\"\n [src]=\"getUserData(row, column).avatar\"\n alt=\"\"\n class=\"pecb-table__user-avatar\">\n <div class=\"pecb-table__user-info\">\n <div class=\"pecb-table__user-name\">{{ getUserData(row, column).name }}</div>\n <div *ngIf=\"getUserData(row, column).secondary\" class=\"pecb-table__user-secondary\">\n {{ getUserData(row, column).secondary }}\n </div>\n </div>\n </div>\n </ng-container>\n\n <!-- Action Component - renders action buttons with icons -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'action'\">\n <div class=\"pecb-table__action-buttons\">\n <ng-container *ngFor=\"let action of getActions(column)\">\n <!-- Divider before this action (consumer-controlled grouping) -->\n <span *ngIf=\"action.dividerBefore\" class=\"pecb-table__action-divider\"></span>\n <button\n class=\"pecb-table__action-btn\"\n [class.pecb-table__action-btn--danger]=\"action.variant === 'danger'\"\n [title]=\"action.tooltip || action.id\"\n [disabled]=\"action.disabled\"\n (click)=\"onActionClick(action, row, i, $event)\">\n <!-- View Icon -->\n <svg *ngIf=\"action.icon === 'view'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z\"></path>\n <circle cx=\"12\" cy=\"12\" r=\"3\"></circle>\n </svg>\n <!-- Edit Icon -->\n <svg *ngIf=\"action.icon === 'edit'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\"></path>\n <path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\"></path>\n </svg>\n <!-- Delete Icon -->\n <svg *ngIf=\"action.icon === 'delete'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"3 6 5 6 21 6\"></polyline>\n <path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"></path>\n </svg>\n <!-- Download Icon -->\n <svg *ngIf=\"action.icon === 'download'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"></path>\n <polyline points=\"7 10 12 15 17 10\"></polyline>\n <line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\"></line>\n </svg>\n <!-- Copy Icon -->\n <svg *ngIf=\"action.icon === 'copy'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\"></rect>\n <path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"></path>\n </svg>\n <!-- Custom Icon (SVG string) -->\n <span *ngIf=\"action.icon === 'custom' && action.customIcon\" class=\"pecb-table__custom-icon\" [innerHTML]=\"getSafeIcon(action.customIcon)\"></span>\n </button>\n </ng-container>\n </div>\n </ng-container>\n\n <!-- Link Cell -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'link'\">\n <a\n [href]=\"getLinkHref(getCellValue(row, column.key))\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"pecb-table__link\"\n (click)=\"$event.stopPropagation()\">\n {{ getCellValue(row, column.key) }}\n </a>\n </ng-container>\n\n <!-- Default Text -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'text'\">\n {{ getCellValue(row, column.key) }}\n </ng-container>\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <!-- Pagination (outside table container) -->\n <div *ngIf=\"showPagination() && !loading() && data().length > 0\" [class]=\"paginationClasses\">\n <pecb-pagination\n [totalItems]=\"totalItems()\"\n [pageSize]=\"pageSize()\"\n [currentPage]=\"currentPage()\"\n [pageSizeOptions]=\"pageSizeOptions()\"\n [showPageSizeSelector]=\"showPageSizeSelector()\"\n [variant]=\"paginationVariant()\"\n size=\"md\"\n (pageChange)=\"onPageChange($event)\"\n (pageSizeChange)=\"onPageSizeChange($event)\">\n </pecb-pagination>\n </div>\n</div>\n", styles: [".pecb-table-wrapper{width:100%}.pecb-table-container{width:100%;background-color:#fff;border-radius:12px;overflow-x:auto;overflow-y:hidden;border:1px solid #ececec}.pecb-table-container--sticky{position:relative;overflow:auto}.pecb-table{width:100%;border-collapse:collapse;border-spacing:0;font-family:inherit;font-size:14px;color:#313131}.pecb-table--sm{font-size:12px}.pecb-table--sm .pecb-table__cell,.pecb-table--sm .pecb-table__cell--header{padding:8px 12px}.pecb-table--md{font-size:14px}.pecb-table--md .pecb-table__cell{padding:16px 18px}.pecb-table--md .pecb-table__cell--header{padding:12px 18px}.pecb-table--lg{font-size:16px}.pecb-table--lg .pecb-table__cell{padding:18px 20px}.pecb-table--lg .pecb-table__cell--header{padding:16px 20px}.pecb-table--default .pecb-table__row--data{border-bottom:1px solid #ececec}.pecb-table--default .pecb-table__row--data:last-child{border-bottom:none}.pecb-table--default .pecb-table__row--data:last-child .pecb-table__cell{border-bottom:none}.pecb-table--default .pecb-table__row--data:nth-child(2n){background-color:#fafafa}.pecb-table--striped .pecb-table__row--data:nth-child(2n){background-color:#fafafa}.pecb-table--bordered .pecb-table__cell{border:1px solid #f7f5f5}.pecb-table--hoverable .pecb-table__row--data{transition:background-color .15s ease-in-out}.pecb-table--selectable .pecb-table__row--data{cursor:pointer}.pecb-table__head{background-color:#212427}.pecb-table__head--sticky{position:sticky;top:0;z-index:1020}.pecb-table__row--header{text-transform:uppercase;height:52px}.pecb-table__row--data{background-color:#fff;height:58px}.pecb-table__row--data:focus,.pecb-table__row--data:focus-visible{outline:none}.pecb-table__row--selected{background-color:#f4f4f4}.pecb-table__row--selected:hover{background-color:#f7f5f5}.pecb-table__row--empty{background-color:#fff}.pecb-table__cell{text-align:left;vertical-align:middle;white-space:nowrap;border-bottom:1px solid #ececec}.pecb-table__cell--header{font-weight:500;font-size:12px;color:#fff;letter-spacing:.02em;-webkit-user-select:none;user-select:none;border-bottom:none}.pecb-table__cell--sortable{cursor:pointer;transition:background-color .15s ease-in-out}.pecb-table__cell--sortable:hover{background-color:#ffffff1a}.pecb-table__cell--sortable:focus,.pecb-table__cell--sortable:focus-visible{outline:none;box-shadow:inset 0 0 0 2px #fff}.pecb-table__cell--checkbox{width:48px;text-align:center}.pecb-table__cell--center{text-align:center}.pecb-table__cell--right{text-align:right}.pecb-table__cell--empty{text-align:center;padding:24px 0}.pecb-table__cell--truncate{overflow:hidden;text-overflow:ellipsis}.pecb-table__cell--wrap{white-space:normal;overflow-wrap:anywhere;word-break:break-word}.pecb-table__header-content{display:flex;align-items:center;gap:8px}.pecb-table__header-text{flex:1}.pecb-table__sort-icon{display:flex;align-items:center;justify-content:center;color:#ffffff80;transition:color .15s ease-in-out}.pecb-table__sort-icon svg{width:16px;height:16px}.pecb-table__sort-icon[data-sort=asc],.pecb-table__sort-icon[data-sort=desc]{color:#fff}.pecb-table__checkbox-wrapper{display:inline-flex;align-items:center;justify-content:center;position:relative;cursor:pointer}.pecb-table__checkbox{position:absolute;opacity:0;width:0;height:0}.pecb-table__checkbox:checked+.pecb-table__checkbox-custom{background-color:#212427;border-color:#212427}.pecb-table__checkbox:checked+.pecb-table__checkbox-custom:after{display:block}.pecb-table__checkbox:indeterminate+.pecb-table__checkbox-custom{background-color:#212427;border-color:#212427}.pecb-table__checkbox:indeterminate+.pecb-table__checkbox-custom:before{display:block}.pecb-table__checkbox:indeterminate+.pecb-table__checkbox-custom:after{display:none}.pecb-table__checkbox:focus+.pecb-table__checkbox-custom{box-shadow:0 0 0 2px #21242733}.pecb-table__checkbox-custom{width:18px;height:18px;border:1.5px solid #c2c2c2;border-radius:6px;background-color:#fff;position:relative;transition:all .15s ease-in-out}.pecb-table__checkbox-custom:after{content:\"\";display:none;position:absolute;left:5px;top:2px;width:4px;height:8px;border:solid #ffffff;border-width:0 2px 2px 0;transform:rotate(45deg)}.pecb-table__checkbox-custom:before{content:\"\";display:none;position:absolute;left:3px;top:6px;width:8px;height:2px;background-color:#fff}.pecb-table__empty-content{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:16px 0}.pecb-table__empty-message{color:#919191;font-size:14px}.pecb-table__loading{display:flex;align-items:center;justify-content:center;padding:48px;min-height:200px}.pecb-table__loading-spinner{display:flex;flex-direction:column;align-items:center;gap:12px;color:#616161;font-size:14px;font-family:inherit;font-weight:400}.pecb-table__spinner-icon{width:32px;height:32px;animation:spin 1s linear infinite;color:#212427}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.pecb-table__pagination{padding:16px 0;margin-top:16px}.pecb-table__pagination--bordered{padding:16px 18px;background-color:#fff;border:1px solid #ececec;border-radius:6px}.pecb-table__user-cell{display:flex;align-items:center;gap:12px}.pecb-table__user-avatar{width:40px;height:40px;border-radius:9999px;object-fit:cover;flex-shrink:0}.pecb-table__user-info{display:flex;flex-direction:column;min-width:0}.pecb-table__user-name{font-weight:500;color:#313131;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pecb-table__user-secondary{font-size:12px;color:#919191;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pecb-table__action-buttons{display:flex;align-items:center;gap:0;justify-content:center}.pecb-table__action-divider{display:block;width:1px;height:16px;background-color:#c2c2c2;flex-shrink:0}.pecb-table__action-btn{display:flex;align-items:center;justify-content:center;width:30px;height:30px;border:none;background:transparent;color:#212427;cursor:pointer;border-radius:6px;transition:all .15s ease-in-out;padding:0}.pecb-table__action-btn:hover{background-color:#f7f5f5;color:#313131}.pecb-table__action-btn:focus{outline:none;box-shadow:0 0 0 2px #21242733}.pecb-table__action-btn:disabled{opacity:.5;cursor:not-allowed}.pecb-table__action-btn--danger:hover{background-color:#fdecea;color:#dc3545}.pecb-table__action-btn svg{width:18px;height:18px}.pecb-table__custom-icon{display:flex;align-items:center;justify-content:center}.pecb-table__custom-icon svg{width:18px;height:18px}.pecb-table__link{color:#212427;text-decoration:none;font-size:inherit}.pecb-table__link:hover{text-decoration:underline;color:#313131}.pecb-table__link:focus-visible{outline:2px solid #212427;outline-offset:2px;border-radius:2px}@media (min-width: 768px){.pecb-table--md,.pecb-table--lg{font-size:14px}.pecb-table--md .pecb-table__cell,.pecb-table--lg .pecb-table__cell{padding:12px 16px}.pecb-table--md .pecb-table__cell--header,.pecb-table--lg .pecb-table__cell--header{padding:8px 16px}.pecb-table__pagination{padding:12px 16px}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: PaginationComponent, selector: "pecb-pagination", inputs: ["totalItems", "pageSize", "currentPage", "pageSizeOptions", "size", "variant", "showPageSizeSelector", "showItemsInfo", "maxVisiblePages", "disabled", "aria-label", "previousPageAriaLabel", "nextPageAriaLabel", "pageNavigationAriaLabel", "pageAriaLabel", "pageSizeListboxAriaLabel", "itemsPerPageLabel", "itemsInfoTemplate", "itemsInfoCompactTemplate"], outputs: ["pageSizeChange", "currentPageChange", "pageChange"] }, { kind: "component", type: StatusComponent, selector: "pecb-status", inputs: ["type", "variant", "size", "ariaLabel", "text", "customClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
4528
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.16", type: TableComponent, isStandalone: true, selector: "pecb-table", inputs: { columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, showHeader: { classPropertyName: "showHeader", publicName: "showHeader", isSignal: true, isRequired: false, transformFunction: null }, hoverable: { classPropertyName: "hoverable", publicName: "hoverable", isSignal: true, isRequired: false, transformFunction: null }, showPagination: { classPropertyName: "showPagination", publicName: "showPagination", isSignal: true, isRequired: false, transformFunction: null }, totalItems: { classPropertyName: "totalItems", publicName: "totalItems", isSignal: true, isRequired: false, transformFunction: null }, currentPage: { classPropertyName: "currentPage", publicName: "currentPage", isSignal: true, isRequired: false, transformFunction: null }, pageSize: { classPropertyName: "pageSize", publicName: "pageSize", isSignal: true, isRequired: false, transformFunction: null }, pageSizeOptions: { classPropertyName: "pageSizeOptions", publicName: "pageSizeOptions", isSignal: true, isRequired: false, transformFunction: null }, showPageSizeSelector: { classPropertyName: "showPageSizeSelector", publicName: "showPageSizeSelector", isSignal: true, isRequired: false, transformFunction: null }, paginationVariant: { classPropertyName: "paginationVariant", publicName: "paginationVariant", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, selectable: { classPropertyName: "selectable", publicName: "selectable", isSignal: true, isRequired: false, transformFunction: null }, selectedIndices: { classPropertyName: "selectedIndices", publicName: "selectedIndices", isSignal: true, isRequired: false, transformFunction: null }, stickyHeader: { classPropertyName: "stickyHeader", publicName: "stickyHeader", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, minWidth: { classPropertyName: "minWidth", publicName: "minWidth", isSignal: true, isRequired: false, transformFunction: null }, wrapText: { classPropertyName: "wrapText", publicName: "wrapText", isSignal: true, isRequired: false, transformFunction: null }, sortColumn: { classPropertyName: "sortColumn", publicName: "sortColumn", isSignal: true, isRequired: false, transformFunction: null }, sortDirection: { classPropertyName: "sortDirection", publicName: "sortDirection", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, loadingText: { classPropertyName: "loadingText", publicName: "loadingText", isSignal: true, isRequired: false, transformFunction: null }, selectAllAriaLabel: { classPropertyName: "selectAllAriaLabel", publicName: "selectAllAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, selectRowAriaLabel: { classPropertyName: "selectRowAriaLabel", publicName: "selectRowAriaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { currentPage: "currentPageChange", pageSize: "pageSizeChange", selectedIndices: "selectedIndicesChange", sortColumn: "sortColumnChange", sortDirection: "sortDirectionChange", pageChange: "pageChange", rowClick: "rowClick", rowAction: "rowAction", sortChange: "sortChange", selectionChange: "selectionChange" }, queries: [{ propertyName: "emptyTemplate", first: true, predicate: ["emptyTemplate"], descendants: true }, { propertyName: "loadingTemplate", first: true, predicate: ["loadingTemplate"], descendants: true }], ngImport: i0, template: "<div class=\"pecb-table-wrapper\">\n <div [class]=\"containerClasses\" [style.maxHeight]=\"maxHeight()\">\n <!-- Loading State -->\n <div *ngIf=\"loading()\" class=\"pecb-table__loading\">\n <ng-container *ngIf=\"loadingTemplate; else defaultLoading\">\n <ng-container *ngTemplateOutlet=\"loadingTemplate\"></ng-container>\n </ng-container>\n <ng-template #defaultLoading>\n <div class=\"pecb-table__loading-spinner\">\n <svg class=\"pecb-table__spinner-icon\" viewBox=\"0 0 24 24\" fill=\"none\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-dasharray=\"31.4 31.4\" />\n </svg>\n <span>{{ loadingText() }}</span>\n </div>\n </ng-template>\n </div>\n\n <!-- Table -->\n <table\n *ngIf=\"!loading()\"\n [class]=\"tableClasses\"\n [style.min-width]=\"minWidth()\"\n [attr.aria-label]=\"ariaLabel()\"\n role=\"table\">\n\n <!-- Table Header -->\n <thead *ngIf=\"showHeader()\" class=\"pecb-table__head\" [class.pecb-table__head--sticky]=\"stickyHeader()\">\n <tr class=\"pecb-table__row pecb-table__row--header\" role=\"row\">\n <!-- Selection Checkbox Column -->\n <th\n *ngIf=\"selectable()\"\n class=\"pecb-table__cell pecb-table__cell--header pecb-table__cell--checkbox\"\n role=\"columnheader\"\n scope=\"col\">\n <label class=\"pecb-table__checkbox-wrapper\">\n <input\n type=\"checkbox\"\n class=\"pecb-table__checkbox\"\n [checked]=\"isAllSelected()\"\n [indeterminate]=\"isSomeSelected()\"\n (change)=\"toggleAllSelection()\"\n [attr.aria-label]=\"selectAllAriaLabel()\" />\n <span class=\"pecb-table__checkbox-custom\"></span>\n </label>\n </th>\n\n <!-- Data Columns -->\n <th\n *ngFor=\"let column of columns(); trackBy: trackByKey\"\n class=\"pecb-table__cell pecb-table__cell--header\"\n [class.pecb-table__cell--sortable]=\"column.sortable\"\n [class]=\"getAlignmentClass(column)\"\n [ngClass]=\"column.cssClass\"\n [style.width]=\"column.width\"\n [attr.aria-sort]=\"column.sortable ? (sortColumn() === column.key ? (sortDirection() === 'asc' ? 'ascending' : 'descending') : 'none') : null\"\n role=\"columnheader\"\n scope=\"col\"\n (click)=\"onHeaderClick(column)\"\n (keydown.enter)=\"onHeaderClick(column)\"\n (keydown.space)=\"onHeaderClick(column); $event.preventDefault()\"\n [attr.tabindex]=\"column.sortable ? 0 : null\">\n <div class=\"pecb-table__header-content\">\n <ng-container *ngIf=\"column.headerTemplate; else defaultHeader\">\n <ng-container *ngTemplateOutlet=\"column.headerTemplate; context: { $implicit: column }\"></ng-container>\n </ng-container>\n <ng-template #defaultHeader>\n <span class=\"pecb-table__header-text\">{{ column.label }}</span>\n </ng-template>\n\n <!-- Sort Icon -->\n <span *ngIf=\"column.sortable\" class=\"pecb-table__sort-icon\" [attr.data-sort]=\"getSortState(column)\">\n <svg *ngIf=\"getSortState(column) === 'none'\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n <path d=\"M8 3L12 7H4L8 3Z\" fill=\"currentColor\" opacity=\"0.4\"/>\n <path d=\"M8 13L4 9H12L8 13Z\" fill=\"currentColor\" opacity=\"0.4\"/>\n </svg>\n <svg *ngIf=\"getSortState(column) === 'asc'\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n <path d=\"M8 3L12 7H4L8 3Z\" fill=\"currentColor\"/>\n <path d=\"M8 13L4 9H12L8 13Z\" fill=\"currentColor\" opacity=\"0.4\"/>\n </svg>\n <svg *ngIf=\"getSortState(column) === 'desc'\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n <path d=\"M8 3L12 7H4L8 3Z\" fill=\"currentColor\" opacity=\"0.4\"/>\n <path d=\"M8 13L4 9H12L8 13Z\" fill=\"currentColor\"/>\n </svg>\n </span>\n </div>\n </th>\n </tr>\n </thead>\n\n <!-- Table Body -->\n <tbody class=\"pecb-table__body\">\n <!-- Empty State -->\n <tr *ngIf=\"data().length === 0\" class=\"pecb-table__row pecb-table__row--empty\">\n <td\n [attr.colspan]=\"selectable() ? columns().length + 1 : columns().length\"\n class=\"pecb-table__cell pecb-table__cell--empty\">\n <ng-container *ngIf=\"emptyTemplate; else defaultEmpty\">\n <ng-container *ngTemplateOutlet=\"emptyTemplate\"></ng-container>\n </ng-container>\n <ng-template #defaultEmpty>\n <div class=\"pecb-table__empty-content\">\n <span class=\"pecb-table__empty-message\">{{ emptyMessage() }}</span>\n </div>\n </ng-template>\n </td>\n </tr>\n\n <!-- Data Rows -->\n <tr\n *ngFor=\"let row of data(); let i = index; trackBy: trackByIndex\"\n class=\"pecb-table__row pecb-table__row--data\"\n [class.pecb-table__row--selected]=\"isRowSelected(i)\"\n role=\"row\"\n (click)=\"onRowClick(row, i)\"\n [attr.tabindex]=\"0\"\n (keydown.enter)=\"onRowClick(row, i)\">\n\n <!-- Selection Checkbox -->\n <td\n *ngIf=\"selectable()\"\n class=\"pecb-table__cell pecb-table__cell--checkbox\"\n role=\"cell\"\n (click)=\"$event.stopPropagation()\">\n <label class=\"pecb-table__checkbox-wrapper\">\n <input\n type=\"checkbox\"\n class=\"pecb-table__checkbox\"\n [checked]=\"isRowSelected(i)\"\n (change)=\"toggleRowSelection(i)\"\n [attr.aria-label]=\"selectRowAriaLabel() + ' ' + (i + 1)\" />\n <span class=\"pecb-table__checkbox-custom\"></span>\n </label>\n </td>\n\n <!-- Data Cells -->\n <td\n *ngFor=\"let column of columns(); trackBy: trackByKey\"\n class=\"pecb-table__cell\"\n [class]=\"getAlignmentClass(column)\"\n [ngClass]=\"column.cssClass\"\n [class.pecb-table__cell--wrap]=\"isCellWrapped(column)\"\n [class.pecb-table__cell--truncate]=\"!isCellWrapped(column)\"\n [style.max-width]=\"column.maxWidth || column.width\"\n [attr.title]=\"getCellTitle(row, column)\"\n role=\"cell\">\n\n <!-- Custom Template -->\n <ng-container *ngIf=\"column.cellTemplate\">\n <ng-container *ngTemplateOutlet=\"column.cellTemplate; context: { $implicit: getCellValue(row, column.key), row: row, column: column, index: i }\"></ng-container>\n </ng-container>\n\n <!-- Dynamic Component - renders any component passed via dynamicComponent -->\n <ng-container *ngIf=\"!column.cellTemplate && hasDynamicComponent(column)\">\n <ng-container\n *ngComponentOutlet=\"column.dynamicComponent!; inputs: getDynamicComponentInputs(row, column, i)\">\n </ng-container>\n </ng-container>\n\n <!-- Status Component - renders pecb-status -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'status'\">\n <pecb-status\n [type]=\"getStatusType(getCellValue(row, column.key), column)\"\n [variant]=\"getStatusVariant(column)\"\n [size]=\"getStatusSize(column)\">\n {{ getCellValue(row, column.key) }}\n </pecb-status>\n </ng-container>\n\n <!-- User Component - renders avatar + name + email -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'user'\">\n <div class=\"pecb-table__user-cell\">\n <img\n *ngIf=\"getUserData(row, column).avatar\"\n [src]=\"getUserData(row, column).avatar\"\n alt=\"\"\n class=\"pecb-table__user-avatar\">\n <div class=\"pecb-table__user-info\">\n <div class=\"pecb-table__user-name\">{{ getUserData(row, column).name }}</div>\n <div *ngIf=\"getUserData(row, column).secondary\" class=\"pecb-table__user-secondary\">\n {{ getUserData(row, column).secondary }}\n </div>\n </div>\n </div>\n </ng-container>\n\n <!-- Action Component - renders action buttons with icons -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'action'\">\n <div class=\"pecb-table__action-buttons\">\n <ng-container *ngFor=\"let action of getActions(column)\">\n <!-- Divider before this action (consumer-controlled grouping) -->\n <span *ngIf=\"action.dividerBefore\" class=\"pecb-table__action-divider\"></span>\n <button\n class=\"pecb-table__action-btn\"\n [class.pecb-table__action-btn--danger]=\"action.variant === 'danger'\"\n [title]=\"action.tooltip || action.id\"\n [disabled]=\"action.disabled\"\n (click)=\"onActionClick(action, row, i, $event)\">\n <!-- View Icon -->\n <svg *ngIf=\"action.icon === 'view'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z\"></path>\n <circle cx=\"12\" cy=\"12\" r=\"3\"></circle>\n </svg>\n <!-- Edit Icon -->\n <svg *ngIf=\"action.icon === 'edit'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\"></path>\n <path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\"></path>\n </svg>\n <!-- Delete Icon -->\n <svg *ngIf=\"action.icon === 'delete'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"3 6 5 6 21 6\"></polyline>\n <path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"></path>\n </svg>\n <!-- Download Icon -->\n <svg *ngIf=\"action.icon === 'download'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"></path>\n <polyline points=\"7 10 12 15 17 10\"></polyline>\n <line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\"></line>\n </svg>\n <!-- Copy Icon -->\n <svg *ngIf=\"action.icon === 'copy'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\"></rect>\n <path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"></path>\n </svg>\n <!-- Custom Icon (SVG string) -->\n <span *ngIf=\"action.icon === 'custom' && action.customIcon\" class=\"pecb-table__custom-icon\" [innerHTML]=\"getSafeIcon(action.customIcon)\"></span>\n </button>\n </ng-container>\n </div>\n </ng-container>\n\n <!-- Link Cell -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'link'\">\n <a\n [href]=\"getLinkHref(getCellValue(row, column.key))\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"pecb-table__link\"\n (click)=\"$event.stopPropagation()\">\n {{ getCellValue(row, column.key) }}\n </a>\n </ng-container>\n\n <!-- Default Text -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'text'\">\n {{ getCellValue(row, column.key) }}\n </ng-container>\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <!-- Pagination (outside table container) -->\n <div *ngIf=\"showPagination() && !loading() && data().length > 0\" [class]=\"paginationClasses\">\n <pecb-pagination\n [totalItems]=\"totalItems()\"\n [pageSize]=\"pageSize()\"\n [currentPage]=\"currentPage()\"\n [pageSizeOptions]=\"pageSizeOptions()\"\n [showPageSizeSelector]=\"showPageSizeSelector()\"\n [variant]=\"paginationVariant()\"\n size=\"md\"\n (pageChange)=\"onPageChange($event)\"\n (pageSizeChange)=\"onPageSizeChange($event)\">\n </pecb-pagination>\n </div>\n</div>\n", styles: [".pecb-table-wrapper{width:100%}.pecb-table-container{width:100%;background-color:#fff;border-radius:12px;overflow-x:auto;overflow-y:hidden;border:1px solid #ececec}.pecb-table-container--sticky{position:relative;overflow:auto}.pecb-table{width:100%;border-collapse:collapse;border-spacing:0;font-family:inherit;font-size:14px;color:#313131}.pecb-table--sm{font-size:12px}.pecb-table--sm .pecb-table__cell,.pecb-table--sm .pecb-table__cell--header{padding:8px 12px}.pecb-table--md{font-size:14px}.pecb-table--md .pecb-table__cell{padding:16px 18px}.pecb-table--md .pecb-table__cell--header{padding:12px 18px}.pecb-table--lg{font-size:16px}.pecb-table--lg .pecb-table__cell{padding:18px 20px}.pecb-table--lg .pecb-table__cell--header{padding:16px 20px}.pecb-table--default .pecb-table__row--data{border-bottom:1px solid #ececec}.pecb-table--default .pecb-table__row--data:last-child{border-bottom:none}.pecb-table--default .pecb-table__row--data:last-child .pecb-table__cell{border-bottom:none}.pecb-table--default .pecb-table__row--data:nth-child(2n){background-color:#fafafa}.pecb-table--striped .pecb-table__row--data:nth-child(2n){background-color:#fafafa}.pecb-table--bordered .pecb-table__cell{border:1px solid #f7f5f5}.pecb-table--hoverable .pecb-table__row--data{transition:background-color .15s ease-in-out}.pecb-table--selectable .pecb-table__row--data{cursor:pointer}.pecb-table__head{background-color:#212427}.pecb-table__head--sticky{position:sticky;top:0;z-index:1020}.pecb-table__row--header{text-transform:uppercase;height:52px}.pecb-table__row--data{background-color:#fff;height:58px}.pecb-table__row--data:focus,.pecb-table__row--data:focus-visible{outline:none}.pecb-table__row--selected{background-color:#f4f4f4}.pecb-table__row--selected:hover{background-color:#f7f5f5}.pecb-table__row--empty{background-color:#fff}.pecb-table__cell{text-align:left;vertical-align:middle;white-space:nowrap;border-bottom:1px solid #ececec}.pecb-table__cell--header{font-weight:500;font-size:12px;color:#fff;letter-spacing:.02em;-webkit-user-select:none;user-select:none;border-bottom:none;background-color:#212427}.pecb-table__cell--sortable{cursor:pointer;transition:background-color .15s ease-in-out}.pecb-table__cell--sortable:hover{background-color:#373a3d}.pecb-table__cell--sortable:focus,.pecb-table__cell--sortable:focus-visible{outline:none;box-shadow:inset 0 0 0 2px #fff}.pecb-table__cell--checkbox{width:48px;text-align:center}.pecb-table__cell--center{text-align:center}.pecb-table__cell--right{text-align:right}.pecb-table__cell--empty{text-align:center;padding:24px 0}.pecb-table__cell--truncate{overflow:hidden;text-overflow:ellipsis}.pecb-table__cell--wrap{white-space:normal;overflow-wrap:anywhere;word-break:break-word}.pecb-table__header-content{display:flex;align-items:center;gap:8px}.pecb-table__header-text{flex:1}.pecb-table__sort-icon{display:flex;align-items:center;justify-content:center;color:#ffffff80;transition:color .15s ease-in-out}.pecb-table__sort-icon svg{width:16px;height:16px}.pecb-table__sort-icon[data-sort=asc],.pecb-table__sort-icon[data-sort=desc]{color:#fff}.pecb-table__checkbox-wrapper{display:inline-flex;align-items:center;justify-content:center;position:relative;cursor:pointer}.pecb-table__checkbox{position:absolute;opacity:0;width:0;height:0}.pecb-table__checkbox:checked+.pecb-table__checkbox-custom{background-color:#212427;border-color:#212427}.pecb-table__checkbox:checked+.pecb-table__checkbox-custom:after{display:block}.pecb-table__checkbox:indeterminate+.pecb-table__checkbox-custom{background-color:#212427;border-color:#212427}.pecb-table__checkbox:indeterminate+.pecb-table__checkbox-custom:before{display:block}.pecb-table__checkbox:indeterminate+.pecb-table__checkbox-custom:after{display:none}.pecb-table__checkbox:focus+.pecb-table__checkbox-custom{box-shadow:0 0 0 2px #21242733}.pecb-table__checkbox-custom{width:18px;height:18px;border:1.5px solid #c2c2c2;border-radius:6px;background-color:#fff;position:relative;transition:all .15s ease-in-out}.pecb-table__checkbox-custom:after{content:\"\";display:none;position:absolute;left:5px;top:2px;width:4px;height:8px;border:solid #ffffff;border-width:0 2px 2px 0;transform:rotate(45deg)}.pecb-table__checkbox-custom:before{content:\"\";display:none;position:absolute;left:3px;top:6px;width:8px;height:2px;background-color:#fff}.pecb-table__empty-content{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:16px 0}.pecb-table__empty-message{color:#919191;font-size:14px}.pecb-table__loading{display:flex;align-items:center;justify-content:center;padding:48px;min-height:200px}.pecb-table__loading-spinner{display:flex;flex-direction:column;align-items:center;gap:12px;color:#616161;font-size:14px;font-family:inherit;font-weight:400}.pecb-table__spinner-icon{width:32px;height:32px;animation:spin 1s linear infinite;color:#212427}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.pecb-table__pagination{padding:16px 0;margin-top:16px}.pecb-table__pagination--bordered{padding:16px 18px;background-color:#fff;border:1px solid #ececec;border-radius:6px}.pecb-table__user-cell{display:flex;align-items:center;gap:12px}.pecb-table__user-avatar{width:40px;height:40px;border-radius:9999px;object-fit:cover;flex-shrink:0}.pecb-table__user-info{display:flex;flex-direction:column;min-width:0}.pecb-table__user-name{font-weight:500;color:#313131;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pecb-table__user-secondary{font-size:12px;color:#919191;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pecb-table__action-buttons{display:flex;align-items:center;gap:0;justify-content:center}.pecb-table__action-divider{display:block;width:1px;height:16px;background-color:#c2c2c2;flex-shrink:0}.pecb-table__action-btn{display:flex;align-items:center;justify-content:center;width:30px;height:30px;border:none;background:transparent;color:#212427;cursor:pointer;border-radius:6px;transition:all .15s ease-in-out;padding:0}.pecb-table__action-btn:hover{background-color:#f7f5f5;color:#313131}.pecb-table__action-btn:focus{outline:none;box-shadow:0 0 0 2px #21242733}.pecb-table__action-btn:disabled{opacity:.5;cursor:not-allowed}.pecb-table__action-btn--danger:hover{background-color:#fdecea;color:#dc3545}.pecb-table__action-btn svg{width:18px;height:18px}.pecb-table__custom-icon{display:flex;align-items:center;justify-content:center}.pecb-table__custom-icon svg{width:18px;height:18px}.pecb-table__link{color:#212427;text-decoration:none;font-size:inherit}.pecb-table__link:hover{text-decoration:underline;color:#313131}.pecb-table__link:focus-visible{outline:2px solid #212427;outline-offset:2px;border-radius:2px}@media (min-width: 768px){.pecb-table--md,.pecb-table--lg{font-size:14px}.pecb-table--md .pecb-table__cell,.pecb-table--lg .pecb-table__cell{padding:12px 16px}.pecb-table--md .pecb-table__cell--header,.pecb-table--lg .pecb-table__cell--header{padding:8px 16px}.pecb-table__pagination{padding:12px 16px}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: PaginationComponent, selector: "pecb-pagination", inputs: ["totalItems", "pageSize", "currentPage", "pageSizeOptions", "size", "variant", "showPageSizeSelector", "showItemsInfo", "maxVisiblePages", "disabled", "aria-label", "previousPageAriaLabel", "nextPageAriaLabel", "pageNavigationAriaLabel", "pageAriaLabel", "pageSizeListboxAriaLabel", "itemsPerPageLabel", "itemsInfoTemplate", "itemsInfoCompactTemplate"], outputs: ["pageSizeChange", "currentPageChange", "pageChange"] }, { kind: "component", type: StatusComponent, selector: "pecb-status", inputs: ["type", "variant", "size", "ariaLabel", "text", "customClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
4527
4529
|
}
|
|
4528
4530
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: TableComponent, decorators: [{
|
|
4529
4531
|
type: Component,
|
|
4530
|
-
args: [{ selector: 'pecb-table', imports: [CommonModule, PaginationComponent, StatusComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"pecb-table-wrapper\">\n <div [class]=\"containerClasses\" [style.maxHeight]=\"maxHeight()\">\n <!-- Loading State -->\n <div *ngIf=\"loading()\" class=\"pecb-table__loading\">\n <ng-container *ngIf=\"loadingTemplate; else defaultLoading\">\n <ng-container *ngTemplateOutlet=\"loadingTemplate\"></ng-container>\n </ng-container>\n <ng-template #defaultLoading>\n <div class=\"pecb-table__loading-spinner\">\n <svg class=\"pecb-table__spinner-icon\" viewBox=\"0 0 24 24\" fill=\"none\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-dasharray=\"31.4 31.4\" />\n </svg>\n <span>{{ loadingText() }}</span>\n </div>\n </ng-template>\n </div>\n\n <!-- Table -->\n <table\n *ngIf=\"!loading()\"\n [class]=\"tableClasses\"\n [style.min-width]=\"minWidth()\"\n [attr.aria-label]=\"ariaLabel()\"\n role=\"table\">\n\n <!-- Table Header -->\n <thead *ngIf=\"showHeader()\" class=\"pecb-table__head\" [class.pecb-table__head--sticky]=\"stickyHeader()\">\n <tr class=\"pecb-table__row pecb-table__row--header\" role=\"row\">\n <!-- Selection Checkbox Column -->\n <th\n *ngIf=\"selectable()\"\n class=\"pecb-table__cell pecb-table__cell--header pecb-table__cell--checkbox\"\n role=\"columnheader\"\n scope=\"col\">\n <label class=\"pecb-table__checkbox-wrapper\">\n <input\n type=\"checkbox\"\n class=\"pecb-table__checkbox\"\n [checked]=\"isAllSelected()\"\n [indeterminate]=\"isSomeSelected()\"\n (change)=\"toggleAllSelection()\"\n [attr.aria-label]=\"selectAllAriaLabel()\" />\n <span class=\"pecb-table__checkbox-custom\"></span>\n </label>\n </th>\n\n <!-- Data Columns -->\n <th\n *ngFor=\"let column of columns(); trackBy: trackByKey\"\n class=\"pecb-table__cell pecb-table__cell--header\"\n [class.pecb-table__cell--sortable]=\"column.sortable\"\n [class]=\"getAlignmentClass(column)\"\n [ngClass]=\"column.cssClass\"\n [style.width]=\"column.width\"\n [attr.aria-sort]=\"column.sortable ? (sortColumn() === column.key ? (sortDirection() === 'asc' ? 'ascending' : 'descending') : 'none') : null\"\n role=\"columnheader\"\n scope=\"col\"\n (click)=\"onHeaderClick(column)\"\n (keydown.enter)=\"onHeaderClick(column)\"\n (keydown.space)=\"onHeaderClick(column); $event.preventDefault()\"\n [attr.tabindex]=\"column.sortable ? 0 : null\">\n <div class=\"pecb-table__header-content\">\n <ng-container *ngIf=\"column.headerTemplate; else defaultHeader\">\n <ng-container *ngTemplateOutlet=\"column.headerTemplate; context: { $implicit: column }\"></ng-container>\n </ng-container>\n <ng-template #defaultHeader>\n <span class=\"pecb-table__header-text\">{{ column.label }}</span>\n </ng-template>\n\n <!-- Sort Icon -->\n <span *ngIf=\"column.sortable\" class=\"pecb-table__sort-icon\" [attr.data-sort]=\"getSortState(column)\">\n <svg *ngIf=\"getSortState(column) === 'none'\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n <path d=\"M8 3L12 7H4L8 3Z\" fill=\"currentColor\" opacity=\"0.4\"/>\n <path d=\"M8 13L4 9H12L8 13Z\" fill=\"currentColor\" opacity=\"0.4\"/>\n </svg>\n <svg *ngIf=\"getSortState(column) === 'asc'\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n <path d=\"M8 3L12 7H4L8 3Z\" fill=\"currentColor\"/>\n <path d=\"M8 13L4 9H12L8 13Z\" fill=\"currentColor\" opacity=\"0.4\"/>\n </svg>\n <svg *ngIf=\"getSortState(column) === 'desc'\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n <path d=\"M8 3L12 7H4L8 3Z\" fill=\"currentColor\" opacity=\"0.4\"/>\n <path d=\"M8 13L4 9H12L8 13Z\" fill=\"currentColor\"/>\n </svg>\n </span>\n </div>\n </th>\n </tr>\n </thead>\n\n <!-- Table Body -->\n <tbody class=\"pecb-table__body\">\n <!-- Empty State -->\n <tr *ngIf=\"data().length === 0\" class=\"pecb-table__row pecb-table__row--empty\">\n <td\n [attr.colspan]=\"selectable() ? columns().length + 1 : columns().length\"\n class=\"pecb-table__cell pecb-table__cell--empty\">\n <ng-container *ngIf=\"emptyTemplate; else defaultEmpty\">\n <ng-container *ngTemplateOutlet=\"emptyTemplate\"></ng-container>\n </ng-container>\n <ng-template #defaultEmpty>\n <div class=\"pecb-table__empty-content\">\n <span class=\"pecb-table__empty-message\">{{ emptyMessage() }}</span>\n </div>\n </ng-template>\n </td>\n </tr>\n\n <!-- Data Rows -->\n <tr\n *ngFor=\"let row of data(); let i = index; trackBy: trackByIndex\"\n class=\"pecb-table__row pecb-table__row--data\"\n [class.pecb-table__row--selected]=\"isRowSelected(i)\"\n role=\"row\"\n (click)=\"onRowClick(row, i)\"\n [attr.tabindex]=\"0\"\n (keydown.enter)=\"onRowClick(row, i)\">\n\n <!-- Selection Checkbox -->\n <td\n *ngIf=\"selectable()\"\n class=\"pecb-table__cell pecb-table__cell--checkbox\"\n role=\"cell\"\n (click)=\"$event.stopPropagation()\">\n <label class=\"pecb-table__checkbox-wrapper\">\n <input\n type=\"checkbox\"\n class=\"pecb-table__checkbox\"\n [checked]=\"isRowSelected(i)\"\n (change)=\"toggleRowSelection(i)\"\n [attr.aria-label]=\"selectRowAriaLabel() + ' ' + (i + 1)\" />\n <span class=\"pecb-table__checkbox-custom\"></span>\n </label>\n </td>\n\n <!-- Data Cells -->\n <td\n *ngFor=\"let column of columns(); trackBy: trackByKey\"\n class=\"pecb-table__cell\"\n [class]=\"getAlignmentClass(column)\"\n [ngClass]=\"column.cssClass\"\n [class.pecb-table__cell--wrap]=\"isCellWrapped(column)\"\n [class.pecb-table__cell--truncate]=\"!isCellWrapped(column)\"\n [style.max-width]=\"column.maxWidth || column.width\"\n [attr.title]=\"getCellTitle(row, column)\"\n role=\"cell\">\n\n <!-- Custom Template -->\n <ng-container *ngIf=\"column.cellTemplate\">\n <ng-container *ngTemplateOutlet=\"column.cellTemplate; context: { $implicit: getCellValue(row, column.key), row: row, column: column, index: i }\"></ng-container>\n </ng-container>\n\n <!-- Dynamic Component - renders any component passed via dynamicComponent -->\n <ng-container *ngIf=\"!column.cellTemplate && hasDynamicComponent(column)\">\n <ng-container\n *ngComponentOutlet=\"column.dynamicComponent!; inputs: getDynamicComponentInputs(row, column, i)\">\n </ng-container>\n </ng-container>\n\n <!-- Status Component - renders pecb-status -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'status'\">\n <pecb-status\n [type]=\"getStatusType(getCellValue(row, column.key), column)\"\n [variant]=\"getStatusVariant(column)\"\n [size]=\"getStatusSize(column)\">\n {{ getCellValue(row, column.key) }}\n </pecb-status>\n </ng-container>\n\n <!-- User Component - renders avatar + name + email -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'user'\">\n <div class=\"pecb-table__user-cell\">\n <img\n *ngIf=\"getUserData(row, column).avatar\"\n [src]=\"getUserData(row, column).avatar\"\n alt=\"\"\n class=\"pecb-table__user-avatar\">\n <div class=\"pecb-table__user-info\">\n <div class=\"pecb-table__user-name\">{{ getUserData(row, column).name }}</div>\n <div *ngIf=\"getUserData(row, column).secondary\" class=\"pecb-table__user-secondary\">\n {{ getUserData(row, column).secondary }}\n </div>\n </div>\n </div>\n </ng-container>\n\n <!-- Action Component - renders action buttons with icons -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'action'\">\n <div class=\"pecb-table__action-buttons\">\n <ng-container *ngFor=\"let action of getActions(column)\">\n <!-- Divider before this action (consumer-controlled grouping) -->\n <span *ngIf=\"action.dividerBefore\" class=\"pecb-table__action-divider\"></span>\n <button\n class=\"pecb-table__action-btn\"\n [class.pecb-table__action-btn--danger]=\"action.variant === 'danger'\"\n [title]=\"action.tooltip || action.id\"\n [disabled]=\"action.disabled\"\n (click)=\"onActionClick(action, row, i, $event)\">\n <!-- View Icon -->\n <svg *ngIf=\"action.icon === 'view'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z\"></path>\n <circle cx=\"12\" cy=\"12\" r=\"3\"></circle>\n </svg>\n <!-- Edit Icon -->\n <svg *ngIf=\"action.icon === 'edit'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\"></path>\n <path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\"></path>\n </svg>\n <!-- Delete Icon -->\n <svg *ngIf=\"action.icon === 'delete'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"3 6 5 6 21 6\"></polyline>\n <path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"></path>\n </svg>\n <!-- Download Icon -->\n <svg *ngIf=\"action.icon === 'download'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"></path>\n <polyline points=\"7 10 12 15 17 10\"></polyline>\n <line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\"></line>\n </svg>\n <!-- Copy Icon -->\n <svg *ngIf=\"action.icon === 'copy'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\"></rect>\n <path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"></path>\n </svg>\n <!-- Custom Icon (SVG string) -->\n <span *ngIf=\"action.icon === 'custom' && action.customIcon\" class=\"pecb-table__custom-icon\" [innerHTML]=\"getSafeIcon(action.customIcon)\"></span>\n </button>\n </ng-container>\n </div>\n </ng-container>\n\n <!-- Link Cell -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'link'\">\n <a\n [href]=\"getLinkHref(getCellValue(row, column.key))\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"pecb-table__link\"\n (click)=\"$event.stopPropagation()\">\n {{ getCellValue(row, column.key) }}\n </a>\n </ng-container>\n\n <!-- Default Text -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'text'\">\n {{ getCellValue(row, column.key) }}\n </ng-container>\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <!-- Pagination (outside table container) -->\n <div *ngIf=\"showPagination() && !loading() && data().length > 0\" [class]=\"paginationClasses\">\n <pecb-pagination\n [totalItems]=\"totalItems()\"\n [pageSize]=\"pageSize()\"\n [currentPage]=\"currentPage()\"\n [pageSizeOptions]=\"pageSizeOptions()\"\n [showPageSizeSelector]=\"showPageSizeSelector()\"\n [variant]=\"paginationVariant()\"\n size=\"md\"\n (pageChange)=\"onPageChange($event)\"\n (pageSizeChange)=\"onPageSizeChange($event)\">\n </pecb-pagination>\n </div>\n</div>\n", styles: [".pecb-table-wrapper{width:100%}.pecb-table-container{width:100%;background-color:#fff;border-radius:12px;overflow-x:auto;overflow-y:hidden;border:1px solid #ececec}.pecb-table-container--sticky{position:relative;overflow:auto}.pecb-table{width:100%;border-collapse:collapse;border-spacing:0;font-family:inherit;font-size:14px;color:#313131}.pecb-table--sm{font-size:12px}.pecb-table--sm .pecb-table__cell,.pecb-table--sm .pecb-table__cell--header{padding:8px 12px}.pecb-table--md{font-size:14px}.pecb-table--md .pecb-table__cell{padding:16px 18px}.pecb-table--md .pecb-table__cell--header{padding:12px 18px}.pecb-table--lg{font-size:16px}.pecb-table--lg .pecb-table__cell{padding:18px 20px}.pecb-table--lg .pecb-table__cell--header{padding:16px 20px}.pecb-table--default .pecb-table__row--data{border-bottom:1px solid #ececec}.pecb-table--default .pecb-table__row--data:last-child{border-bottom:none}.pecb-table--default .pecb-table__row--data:last-child .pecb-table__cell{border-bottom:none}.pecb-table--default .pecb-table__row--data:nth-child(2n){background-color:#fafafa}.pecb-table--striped .pecb-table__row--data:nth-child(2n){background-color:#fafafa}.pecb-table--bordered .pecb-table__cell{border:1px solid #f7f5f5}.pecb-table--hoverable .pecb-table__row--data{transition:background-color .15s ease-in-out}.pecb-table--selectable .pecb-table__row--data{cursor:pointer}.pecb-table__head{background-color:#212427}.pecb-table__head--sticky{position:sticky;top:0;z-index:1020}.pecb-table__row--header{text-transform:uppercase;height:52px}.pecb-table__row--data{background-color:#fff;height:58px}.pecb-table__row--data:focus,.pecb-table__row--data:focus-visible{outline:none}.pecb-table__row--selected{background-color:#f4f4f4}.pecb-table__row--selected:hover{background-color:#f7f5f5}.pecb-table__row--empty{background-color:#fff}.pecb-table__cell{text-align:left;vertical-align:middle;white-space:nowrap;border-bottom:1px solid #ececec}.pecb-table__cell--header{font-weight:500;font-size:12px;color:#fff;letter-spacing:.02em;-webkit-user-select:none;user-select:none;border-bottom:none}.pecb-table__cell--sortable{cursor:pointer;transition:background-color .15s ease-in-out}.pecb-table__cell--sortable:hover{background-color:#ffffff1a}.pecb-table__cell--sortable:focus,.pecb-table__cell--sortable:focus-visible{outline:none;box-shadow:inset 0 0 0 2px #fff}.pecb-table__cell--checkbox{width:48px;text-align:center}.pecb-table__cell--center{text-align:center}.pecb-table__cell--right{text-align:right}.pecb-table__cell--empty{text-align:center;padding:24px 0}.pecb-table__cell--truncate{overflow:hidden;text-overflow:ellipsis}.pecb-table__cell--wrap{white-space:normal;overflow-wrap:anywhere;word-break:break-word}.pecb-table__header-content{display:flex;align-items:center;gap:8px}.pecb-table__header-text{flex:1}.pecb-table__sort-icon{display:flex;align-items:center;justify-content:center;color:#ffffff80;transition:color .15s ease-in-out}.pecb-table__sort-icon svg{width:16px;height:16px}.pecb-table__sort-icon[data-sort=asc],.pecb-table__sort-icon[data-sort=desc]{color:#fff}.pecb-table__checkbox-wrapper{display:inline-flex;align-items:center;justify-content:center;position:relative;cursor:pointer}.pecb-table__checkbox{position:absolute;opacity:0;width:0;height:0}.pecb-table__checkbox:checked+.pecb-table__checkbox-custom{background-color:#212427;border-color:#212427}.pecb-table__checkbox:checked+.pecb-table__checkbox-custom:after{display:block}.pecb-table__checkbox:indeterminate+.pecb-table__checkbox-custom{background-color:#212427;border-color:#212427}.pecb-table__checkbox:indeterminate+.pecb-table__checkbox-custom:before{display:block}.pecb-table__checkbox:indeterminate+.pecb-table__checkbox-custom:after{display:none}.pecb-table__checkbox:focus+.pecb-table__checkbox-custom{box-shadow:0 0 0 2px #21242733}.pecb-table__checkbox-custom{width:18px;height:18px;border:1.5px solid #c2c2c2;border-radius:6px;background-color:#fff;position:relative;transition:all .15s ease-in-out}.pecb-table__checkbox-custom:after{content:\"\";display:none;position:absolute;left:5px;top:2px;width:4px;height:8px;border:solid #ffffff;border-width:0 2px 2px 0;transform:rotate(45deg)}.pecb-table__checkbox-custom:before{content:\"\";display:none;position:absolute;left:3px;top:6px;width:8px;height:2px;background-color:#fff}.pecb-table__empty-content{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:16px 0}.pecb-table__empty-message{color:#919191;font-size:14px}.pecb-table__loading{display:flex;align-items:center;justify-content:center;padding:48px;min-height:200px}.pecb-table__loading-spinner{display:flex;flex-direction:column;align-items:center;gap:12px;color:#616161;font-size:14px;font-family:inherit;font-weight:400}.pecb-table__spinner-icon{width:32px;height:32px;animation:spin 1s linear infinite;color:#212427}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.pecb-table__pagination{padding:16px 0;margin-top:16px}.pecb-table__pagination--bordered{padding:16px 18px;background-color:#fff;border:1px solid #ececec;border-radius:6px}.pecb-table__user-cell{display:flex;align-items:center;gap:12px}.pecb-table__user-avatar{width:40px;height:40px;border-radius:9999px;object-fit:cover;flex-shrink:0}.pecb-table__user-info{display:flex;flex-direction:column;min-width:0}.pecb-table__user-name{font-weight:500;color:#313131;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pecb-table__user-secondary{font-size:12px;color:#919191;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pecb-table__action-buttons{display:flex;align-items:center;gap:0;justify-content:center}.pecb-table__action-divider{display:block;width:1px;height:16px;background-color:#c2c2c2;flex-shrink:0}.pecb-table__action-btn{display:flex;align-items:center;justify-content:center;width:30px;height:30px;border:none;background:transparent;color:#212427;cursor:pointer;border-radius:6px;transition:all .15s ease-in-out;padding:0}.pecb-table__action-btn:hover{background-color:#f7f5f5;color:#313131}.pecb-table__action-btn:focus{outline:none;box-shadow:0 0 0 2px #21242733}.pecb-table__action-btn:disabled{opacity:.5;cursor:not-allowed}.pecb-table__action-btn--danger:hover{background-color:#fdecea;color:#dc3545}.pecb-table__action-btn svg{width:18px;height:18px}.pecb-table__custom-icon{display:flex;align-items:center;justify-content:center}.pecb-table__custom-icon svg{width:18px;height:18px}.pecb-table__link{color:#212427;text-decoration:none;font-size:inherit}.pecb-table__link:hover{text-decoration:underline;color:#313131}.pecb-table__link:focus-visible{outline:2px solid #212427;outline-offset:2px;border-radius:2px}@media (min-width: 768px){.pecb-table--md,.pecb-table--lg{font-size:14px}.pecb-table--md .pecb-table__cell,.pecb-table--lg .pecb-table__cell{padding:12px 16px}.pecb-table--md .pecb-table__cell--header,.pecb-table--lg .pecb-table__cell--header{padding:8px 16px}.pecb-table__pagination{padding:12px 16px}}\n"] }]
|
|
4532
|
+
args: [{ selector: 'pecb-table', imports: [CommonModule, PaginationComponent, StatusComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"pecb-table-wrapper\">\n <div [class]=\"containerClasses\" [style.maxHeight]=\"maxHeight()\">\n <!-- Loading State -->\n <div *ngIf=\"loading()\" class=\"pecb-table__loading\">\n <ng-container *ngIf=\"loadingTemplate; else defaultLoading\">\n <ng-container *ngTemplateOutlet=\"loadingTemplate\"></ng-container>\n </ng-container>\n <ng-template #defaultLoading>\n <div class=\"pecb-table__loading-spinner\">\n <svg class=\"pecb-table__spinner-icon\" viewBox=\"0 0 24 24\" fill=\"none\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-dasharray=\"31.4 31.4\" />\n </svg>\n <span>{{ loadingText() }}</span>\n </div>\n </ng-template>\n </div>\n\n <!-- Table -->\n <table\n *ngIf=\"!loading()\"\n [class]=\"tableClasses\"\n [style.min-width]=\"minWidth()\"\n [attr.aria-label]=\"ariaLabel()\"\n role=\"table\">\n\n <!-- Table Header -->\n <thead *ngIf=\"showHeader()\" class=\"pecb-table__head\" [class.pecb-table__head--sticky]=\"stickyHeader()\">\n <tr class=\"pecb-table__row pecb-table__row--header\" role=\"row\">\n <!-- Selection Checkbox Column -->\n <th\n *ngIf=\"selectable()\"\n class=\"pecb-table__cell pecb-table__cell--header pecb-table__cell--checkbox\"\n role=\"columnheader\"\n scope=\"col\">\n <label class=\"pecb-table__checkbox-wrapper\">\n <input\n type=\"checkbox\"\n class=\"pecb-table__checkbox\"\n [checked]=\"isAllSelected()\"\n [indeterminate]=\"isSomeSelected()\"\n (change)=\"toggleAllSelection()\"\n [attr.aria-label]=\"selectAllAriaLabel()\" />\n <span class=\"pecb-table__checkbox-custom\"></span>\n </label>\n </th>\n\n <!-- Data Columns -->\n <th\n *ngFor=\"let column of columns(); trackBy: trackByKey\"\n class=\"pecb-table__cell pecb-table__cell--header\"\n [class.pecb-table__cell--sortable]=\"column.sortable\"\n [class]=\"getAlignmentClass(column)\"\n [ngClass]=\"column.cssClass\"\n [style.width]=\"column.width\"\n [attr.aria-sort]=\"column.sortable ? (sortColumn() === column.key ? (sortDirection() === 'asc' ? 'ascending' : 'descending') : 'none') : null\"\n role=\"columnheader\"\n scope=\"col\"\n (click)=\"onHeaderClick(column)\"\n (keydown.enter)=\"onHeaderClick(column)\"\n (keydown.space)=\"onHeaderClick(column); $event.preventDefault()\"\n [attr.tabindex]=\"column.sortable ? 0 : null\">\n <div class=\"pecb-table__header-content\">\n <ng-container *ngIf=\"column.headerTemplate; else defaultHeader\">\n <ng-container *ngTemplateOutlet=\"column.headerTemplate; context: { $implicit: column }\"></ng-container>\n </ng-container>\n <ng-template #defaultHeader>\n <span class=\"pecb-table__header-text\">{{ column.label }}</span>\n </ng-template>\n\n <!-- Sort Icon -->\n <span *ngIf=\"column.sortable\" class=\"pecb-table__sort-icon\" [attr.data-sort]=\"getSortState(column)\">\n <svg *ngIf=\"getSortState(column) === 'none'\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n <path d=\"M8 3L12 7H4L8 3Z\" fill=\"currentColor\" opacity=\"0.4\"/>\n <path d=\"M8 13L4 9H12L8 13Z\" fill=\"currentColor\" opacity=\"0.4\"/>\n </svg>\n <svg *ngIf=\"getSortState(column) === 'asc'\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n <path d=\"M8 3L12 7H4L8 3Z\" fill=\"currentColor\"/>\n <path d=\"M8 13L4 9H12L8 13Z\" fill=\"currentColor\" opacity=\"0.4\"/>\n </svg>\n <svg *ngIf=\"getSortState(column) === 'desc'\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n <path d=\"M8 3L12 7H4L8 3Z\" fill=\"currentColor\" opacity=\"0.4\"/>\n <path d=\"M8 13L4 9H12L8 13Z\" fill=\"currentColor\"/>\n </svg>\n </span>\n </div>\n </th>\n </tr>\n </thead>\n\n <!-- Table Body -->\n <tbody class=\"pecb-table__body\">\n <!-- Empty State -->\n <tr *ngIf=\"data().length === 0\" class=\"pecb-table__row pecb-table__row--empty\">\n <td\n [attr.colspan]=\"selectable() ? columns().length + 1 : columns().length\"\n class=\"pecb-table__cell pecb-table__cell--empty\">\n <ng-container *ngIf=\"emptyTemplate; else defaultEmpty\">\n <ng-container *ngTemplateOutlet=\"emptyTemplate\"></ng-container>\n </ng-container>\n <ng-template #defaultEmpty>\n <div class=\"pecb-table__empty-content\">\n <span class=\"pecb-table__empty-message\">{{ emptyMessage() }}</span>\n </div>\n </ng-template>\n </td>\n </tr>\n\n <!-- Data Rows -->\n <tr\n *ngFor=\"let row of data(); let i = index; trackBy: trackByIndex\"\n class=\"pecb-table__row pecb-table__row--data\"\n [class.pecb-table__row--selected]=\"isRowSelected(i)\"\n role=\"row\"\n (click)=\"onRowClick(row, i)\"\n [attr.tabindex]=\"0\"\n (keydown.enter)=\"onRowClick(row, i)\">\n\n <!-- Selection Checkbox -->\n <td\n *ngIf=\"selectable()\"\n class=\"pecb-table__cell pecb-table__cell--checkbox\"\n role=\"cell\"\n (click)=\"$event.stopPropagation()\">\n <label class=\"pecb-table__checkbox-wrapper\">\n <input\n type=\"checkbox\"\n class=\"pecb-table__checkbox\"\n [checked]=\"isRowSelected(i)\"\n (change)=\"toggleRowSelection(i)\"\n [attr.aria-label]=\"selectRowAriaLabel() + ' ' + (i + 1)\" />\n <span class=\"pecb-table__checkbox-custom\"></span>\n </label>\n </td>\n\n <!-- Data Cells -->\n <td\n *ngFor=\"let column of columns(); trackBy: trackByKey\"\n class=\"pecb-table__cell\"\n [class]=\"getAlignmentClass(column)\"\n [ngClass]=\"column.cssClass\"\n [class.pecb-table__cell--wrap]=\"isCellWrapped(column)\"\n [class.pecb-table__cell--truncate]=\"!isCellWrapped(column)\"\n [style.max-width]=\"column.maxWidth || column.width\"\n [attr.title]=\"getCellTitle(row, column)\"\n role=\"cell\">\n\n <!-- Custom Template -->\n <ng-container *ngIf=\"column.cellTemplate\">\n <ng-container *ngTemplateOutlet=\"column.cellTemplate; context: { $implicit: getCellValue(row, column.key), row: row, column: column, index: i }\"></ng-container>\n </ng-container>\n\n <!-- Dynamic Component - renders any component passed via dynamicComponent -->\n <ng-container *ngIf=\"!column.cellTemplate && hasDynamicComponent(column)\">\n <ng-container\n *ngComponentOutlet=\"column.dynamicComponent!; inputs: getDynamicComponentInputs(row, column, i)\">\n </ng-container>\n </ng-container>\n\n <!-- Status Component - renders pecb-status -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'status'\">\n <pecb-status\n [type]=\"getStatusType(getCellValue(row, column.key), column)\"\n [variant]=\"getStatusVariant(column)\"\n [size]=\"getStatusSize(column)\">\n {{ getCellValue(row, column.key) }}\n </pecb-status>\n </ng-container>\n\n <!-- User Component - renders avatar + name + email -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'user'\">\n <div class=\"pecb-table__user-cell\">\n <img\n *ngIf=\"getUserData(row, column).avatar\"\n [src]=\"getUserData(row, column).avatar\"\n alt=\"\"\n class=\"pecb-table__user-avatar\">\n <div class=\"pecb-table__user-info\">\n <div class=\"pecb-table__user-name\">{{ getUserData(row, column).name }}</div>\n <div *ngIf=\"getUserData(row, column).secondary\" class=\"pecb-table__user-secondary\">\n {{ getUserData(row, column).secondary }}\n </div>\n </div>\n </div>\n </ng-container>\n\n <!-- Action Component - renders action buttons with icons -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'action'\">\n <div class=\"pecb-table__action-buttons\">\n <ng-container *ngFor=\"let action of getActions(column)\">\n <!-- Divider before this action (consumer-controlled grouping) -->\n <span *ngIf=\"action.dividerBefore\" class=\"pecb-table__action-divider\"></span>\n <button\n class=\"pecb-table__action-btn\"\n [class.pecb-table__action-btn--danger]=\"action.variant === 'danger'\"\n [title]=\"action.tooltip || action.id\"\n [disabled]=\"action.disabled\"\n (click)=\"onActionClick(action, row, i, $event)\">\n <!-- View Icon -->\n <svg *ngIf=\"action.icon === 'view'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z\"></path>\n <circle cx=\"12\" cy=\"12\" r=\"3\"></circle>\n </svg>\n <!-- Edit Icon -->\n <svg *ngIf=\"action.icon === 'edit'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7\"></path>\n <path d=\"M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z\"></path>\n </svg>\n <!-- Delete Icon -->\n <svg *ngIf=\"action.icon === 'delete'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"3 6 5 6 21 6\"></polyline>\n <path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"></path>\n </svg>\n <!-- Download Icon -->\n <svg *ngIf=\"action.icon === 'download'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"></path>\n <polyline points=\"7 10 12 15 17 10\"></polyline>\n <line x1=\"12\" y1=\"15\" x2=\"12\" y2=\"3\"></line>\n </svg>\n <!-- Copy Icon -->\n <svg *ngIf=\"action.icon === 'copy'\" width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\"></rect>\n <path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"></path>\n </svg>\n <!-- Custom Icon (SVG string) -->\n <span *ngIf=\"action.icon === 'custom' && action.customIcon\" class=\"pecb-table__custom-icon\" [innerHTML]=\"getSafeIcon(action.customIcon)\"></span>\n </button>\n </ng-container>\n </div>\n </ng-container>\n\n <!-- Link Cell -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'link'\">\n <a\n [href]=\"getLinkHref(getCellValue(row, column.key))\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n class=\"pecb-table__link\"\n (click)=\"$event.stopPropagation()\">\n {{ getCellValue(row, column.key) }}\n </a>\n </ng-container>\n\n <!-- Default Text -->\n <ng-container *ngIf=\"!column.cellTemplate && !hasDynamicComponent(column) && getColumnType(column) === 'text'\">\n {{ getCellValue(row, column.key) }}\n </ng-container>\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n\n <!-- Pagination (outside table container) -->\n <div *ngIf=\"showPagination() && !loading() && data().length > 0\" [class]=\"paginationClasses\">\n <pecb-pagination\n [totalItems]=\"totalItems()\"\n [pageSize]=\"pageSize()\"\n [currentPage]=\"currentPage()\"\n [pageSizeOptions]=\"pageSizeOptions()\"\n [showPageSizeSelector]=\"showPageSizeSelector()\"\n [variant]=\"paginationVariant()\"\n size=\"md\"\n (pageChange)=\"onPageChange($event)\"\n (pageSizeChange)=\"onPageSizeChange($event)\">\n </pecb-pagination>\n </div>\n</div>\n", styles: [".pecb-table-wrapper{width:100%}.pecb-table-container{width:100%;background-color:#fff;border-radius:12px;overflow-x:auto;overflow-y:hidden;border:1px solid #ececec}.pecb-table-container--sticky{position:relative;overflow:auto}.pecb-table{width:100%;border-collapse:collapse;border-spacing:0;font-family:inherit;font-size:14px;color:#313131}.pecb-table--sm{font-size:12px}.pecb-table--sm .pecb-table__cell,.pecb-table--sm .pecb-table__cell--header{padding:8px 12px}.pecb-table--md{font-size:14px}.pecb-table--md .pecb-table__cell{padding:16px 18px}.pecb-table--md .pecb-table__cell--header{padding:12px 18px}.pecb-table--lg{font-size:16px}.pecb-table--lg .pecb-table__cell{padding:18px 20px}.pecb-table--lg .pecb-table__cell--header{padding:16px 20px}.pecb-table--default .pecb-table__row--data{border-bottom:1px solid #ececec}.pecb-table--default .pecb-table__row--data:last-child{border-bottom:none}.pecb-table--default .pecb-table__row--data:last-child .pecb-table__cell{border-bottom:none}.pecb-table--default .pecb-table__row--data:nth-child(2n){background-color:#fafafa}.pecb-table--striped .pecb-table__row--data:nth-child(2n){background-color:#fafafa}.pecb-table--bordered .pecb-table__cell{border:1px solid #f7f5f5}.pecb-table--hoverable .pecb-table__row--data{transition:background-color .15s ease-in-out}.pecb-table--selectable .pecb-table__row--data{cursor:pointer}.pecb-table__head{background-color:#212427}.pecb-table__head--sticky{position:sticky;top:0;z-index:1020}.pecb-table__row--header{text-transform:uppercase;height:52px}.pecb-table__row--data{background-color:#fff;height:58px}.pecb-table__row--data:focus,.pecb-table__row--data:focus-visible{outline:none}.pecb-table__row--selected{background-color:#f4f4f4}.pecb-table__row--selected:hover{background-color:#f7f5f5}.pecb-table__row--empty{background-color:#fff}.pecb-table__cell{text-align:left;vertical-align:middle;white-space:nowrap;border-bottom:1px solid #ececec}.pecb-table__cell--header{font-weight:500;font-size:12px;color:#fff;letter-spacing:.02em;-webkit-user-select:none;user-select:none;border-bottom:none;background-color:#212427}.pecb-table__cell--sortable{cursor:pointer;transition:background-color .15s ease-in-out}.pecb-table__cell--sortable:hover{background-color:#373a3d}.pecb-table__cell--sortable:focus,.pecb-table__cell--sortable:focus-visible{outline:none;box-shadow:inset 0 0 0 2px #fff}.pecb-table__cell--checkbox{width:48px;text-align:center}.pecb-table__cell--center{text-align:center}.pecb-table__cell--right{text-align:right}.pecb-table__cell--empty{text-align:center;padding:24px 0}.pecb-table__cell--truncate{overflow:hidden;text-overflow:ellipsis}.pecb-table__cell--wrap{white-space:normal;overflow-wrap:anywhere;word-break:break-word}.pecb-table__header-content{display:flex;align-items:center;gap:8px}.pecb-table__header-text{flex:1}.pecb-table__sort-icon{display:flex;align-items:center;justify-content:center;color:#ffffff80;transition:color .15s ease-in-out}.pecb-table__sort-icon svg{width:16px;height:16px}.pecb-table__sort-icon[data-sort=asc],.pecb-table__sort-icon[data-sort=desc]{color:#fff}.pecb-table__checkbox-wrapper{display:inline-flex;align-items:center;justify-content:center;position:relative;cursor:pointer}.pecb-table__checkbox{position:absolute;opacity:0;width:0;height:0}.pecb-table__checkbox:checked+.pecb-table__checkbox-custom{background-color:#212427;border-color:#212427}.pecb-table__checkbox:checked+.pecb-table__checkbox-custom:after{display:block}.pecb-table__checkbox:indeterminate+.pecb-table__checkbox-custom{background-color:#212427;border-color:#212427}.pecb-table__checkbox:indeterminate+.pecb-table__checkbox-custom:before{display:block}.pecb-table__checkbox:indeterminate+.pecb-table__checkbox-custom:after{display:none}.pecb-table__checkbox:focus+.pecb-table__checkbox-custom{box-shadow:0 0 0 2px #21242733}.pecb-table__checkbox-custom{width:18px;height:18px;border:1.5px solid #c2c2c2;border-radius:6px;background-color:#fff;position:relative;transition:all .15s ease-in-out}.pecb-table__checkbox-custom:after{content:\"\";display:none;position:absolute;left:5px;top:2px;width:4px;height:8px;border:solid #ffffff;border-width:0 2px 2px 0;transform:rotate(45deg)}.pecb-table__checkbox-custom:before{content:\"\";display:none;position:absolute;left:3px;top:6px;width:8px;height:2px;background-color:#fff}.pecb-table__empty-content{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:16px 0}.pecb-table__empty-message{color:#919191;font-size:14px}.pecb-table__loading{display:flex;align-items:center;justify-content:center;padding:48px;min-height:200px}.pecb-table__loading-spinner{display:flex;flex-direction:column;align-items:center;gap:12px;color:#616161;font-size:14px;font-family:inherit;font-weight:400}.pecb-table__spinner-icon{width:32px;height:32px;animation:spin 1s linear infinite;color:#212427}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.pecb-table__pagination{padding:16px 0;margin-top:16px}.pecb-table__pagination--bordered{padding:16px 18px;background-color:#fff;border:1px solid #ececec;border-radius:6px}.pecb-table__user-cell{display:flex;align-items:center;gap:12px}.pecb-table__user-avatar{width:40px;height:40px;border-radius:9999px;object-fit:cover;flex-shrink:0}.pecb-table__user-info{display:flex;flex-direction:column;min-width:0}.pecb-table__user-name{font-weight:500;color:#313131;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pecb-table__user-secondary{font-size:12px;color:#919191;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pecb-table__action-buttons{display:flex;align-items:center;gap:0;justify-content:center}.pecb-table__action-divider{display:block;width:1px;height:16px;background-color:#c2c2c2;flex-shrink:0}.pecb-table__action-btn{display:flex;align-items:center;justify-content:center;width:30px;height:30px;border:none;background:transparent;color:#212427;cursor:pointer;border-radius:6px;transition:all .15s ease-in-out;padding:0}.pecb-table__action-btn:hover{background-color:#f7f5f5;color:#313131}.pecb-table__action-btn:focus{outline:none;box-shadow:0 0 0 2px #21242733}.pecb-table__action-btn:disabled{opacity:.5;cursor:not-allowed}.pecb-table__action-btn--danger:hover{background-color:#fdecea;color:#dc3545}.pecb-table__action-btn svg{width:18px;height:18px}.pecb-table__custom-icon{display:flex;align-items:center;justify-content:center}.pecb-table__custom-icon svg{width:18px;height:18px}.pecb-table__link{color:#212427;text-decoration:none;font-size:inherit}.pecb-table__link:hover{text-decoration:underline;color:#313131}.pecb-table__link:focus-visible{outline:2px solid #212427;outline-offset:2px;border-radius:2px}@media (min-width: 768px){.pecb-table--md,.pecb-table--lg{font-size:14px}.pecb-table--md .pecb-table__cell,.pecb-table--lg .pecb-table__cell{padding:12px 16px}.pecb-table--md .pecb-table__cell--header,.pecb-table--lg .pecb-table__cell--header{padding:8px 16px}.pecb-table__pagination{padding:12px 16px}}\n"] }]
|
|
4531
4533
|
}], ctorParameters: () => [{ type: i1$1.DomSanitizer }, { type: i0.ChangeDetectorRef }], propDecorators: { columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], showHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "showHeader", required: false }] }], hoverable: [{ type: i0.Input, args: [{ isSignal: true, alias: "hoverable", required: false }] }], showPagination: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPagination", required: false }] }], totalItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalItems", required: false }] }], currentPage: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentPage", required: false }] }, { type: i0.Output, args: ["currentPageChange"] }], pageSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSize", required: false }] }, { type: i0.Output, args: ["pageSizeChange"] }], pageSizeOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageSizeOptions", required: false }] }], showPageSizeSelector: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPageSizeSelector", required: false }] }], paginationVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "paginationVariant", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], selectable: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectable", required: false }] }], selectedIndices: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedIndices", required: false }] }, { type: i0.Output, args: ["selectedIndicesChange"] }], stickyHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "stickyHeader", required: false }] }], maxHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxHeight", required: false }] }], minWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "minWidth", required: false }] }], wrapText: [{ type: i0.Input, args: [{ isSignal: true, alias: "wrapText", required: false }] }], sortColumn: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortColumn", required: false }] }, { type: i0.Output, args: ["sortColumnChange"] }], sortDirection: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortDirection", required: false }] }, { type: i0.Output, args: ["sortDirectionChange"] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-label", required: false }] }], loadingText: [{ type: i0.Input, args: [{ isSignal: true, alias: "loadingText", required: false }] }], selectAllAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectAllAriaLabel", required: false }] }], selectRowAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectRowAriaLabel", required: false }] }], pageChange: [{ type: i0.Output, args: ["pageChange"] }], rowClick: [{ type: i0.Output, args: ["rowClick"] }], rowAction: [{ type: i0.Output, args: ["rowAction"] }], sortChange: [{ type: i0.Output, args: ["sortChange"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], emptyTemplate: [{
|
|
4532
4534
|
type: ContentChild,
|
|
4533
4535
|
args: ['emptyTemplate']
|
|
@@ -5591,41 +5593,32 @@ class RightModalComponent {
|
|
|
5591
5593
|
this.ariaDescribedBy = input(...(ngDevMode ? [undefined, { debugName: "ariaDescribedBy" }] : []));
|
|
5592
5594
|
this.closed = output();
|
|
5593
5595
|
this.opened = output();
|
|
5594
|
-
this.
|
|
5595
|
-
this.
|
|
5596
|
-
this
|
|
5596
|
+
this.overlay = inject(Overlay);
|
|
5597
|
+
this.viewContainerRef = inject(ViewContainerRef);
|
|
5598
|
+
// The panel + projected slots live in this template so they can be attached
|
|
5599
|
+
// to a CDK overlay (rendered in the global overlay container on <body>),
|
|
5600
|
+
// which escapes any `transform`/`filter` containing-block trap on ancestors.
|
|
5601
|
+
this.modalTemplate = viewChild('modalTemplate', ...(ngDevMode ? [{ debugName: "modalTemplate" }] : []));
|
|
5602
|
+
this.overlayRef = null;
|
|
5603
|
+
this.portal = null;
|
|
5604
|
+
this.subscriptions = new Subscription();
|
|
5597
5605
|
this.previousActiveElement = null;
|
|
5598
|
-
|
|
5599
|
-
//
|
|
5600
|
-
//
|
|
5601
|
-
// correctly on every toggle — not just the initial mount.
|
|
5606
|
+
// Open/close the overlay reactively from the isOpen model. The template
|
|
5607
|
+
// signal is read so the effect re-runs once the @ViewChild resolves (e.g.
|
|
5608
|
+
// when a consumer renders the modal already open).
|
|
5602
5609
|
effect(() => {
|
|
5603
5610
|
const open = this.isOpen();
|
|
5604
|
-
const
|
|
5605
|
-
if (open) {
|
|
5606
|
-
|
|
5607
|
-
this.currentlyOpen = true;
|
|
5608
|
-
this.onOpen();
|
|
5609
|
-
}
|
|
5610
|
-
if (panel && !this.cleanupFocusTrap) {
|
|
5611
|
-
this.setupFocusTrap(panel.nativeElement);
|
|
5612
|
-
}
|
|
5611
|
+
const template = this.modalTemplate();
|
|
5612
|
+
if (open && template) {
|
|
5613
|
+
this.openOverlay(template);
|
|
5613
5614
|
}
|
|
5614
|
-
else if (
|
|
5615
|
-
this.
|
|
5616
|
-
this.cleanup();
|
|
5617
|
-
this.closed.emit();
|
|
5615
|
+
else if (!open) {
|
|
5616
|
+
this.closeOverlay();
|
|
5618
5617
|
}
|
|
5619
5618
|
});
|
|
5620
5619
|
}
|
|
5621
5620
|
ngOnDestroy() {
|
|
5622
|
-
this.
|
|
5623
|
-
}
|
|
5624
|
-
get modalClasses() {
|
|
5625
|
-
return [
|
|
5626
|
-
'pecb-right-modal',
|
|
5627
|
-
this.isOpen() ? 'pecb-right-modal--open' : ''
|
|
5628
|
-
].filter(Boolean);
|
|
5621
|
+
this.destroyOverlay();
|
|
5629
5622
|
}
|
|
5630
5623
|
get panelClasses() {
|
|
5631
5624
|
return [
|
|
@@ -5633,49 +5626,71 @@ class RightModalComponent {
|
|
|
5633
5626
|
`pecb-right-modal__panel--${this.size()}`
|
|
5634
5627
|
];
|
|
5635
5628
|
}
|
|
5636
|
-
onBackdropClick(event) {
|
|
5637
|
-
if (this.closeOnBackdropClick() && event.target === event.currentTarget) {
|
|
5638
|
-
this.close();
|
|
5639
|
-
}
|
|
5640
|
-
}
|
|
5641
|
-
onKeydown(event) {
|
|
5642
|
-
if (this.closeOnEscape() && event.key === 'Escape') {
|
|
5643
|
-
event.preventDefault();
|
|
5644
|
-
this.close();
|
|
5645
|
-
}
|
|
5646
|
-
}
|
|
5647
5629
|
close() {
|
|
5648
|
-
// The effect
|
|
5630
|
+
// The effect tears down the overlay and emits `closed` when isOpen flips.
|
|
5649
5631
|
this.isOpen.set(false);
|
|
5650
5632
|
}
|
|
5651
|
-
|
|
5633
|
+
openOverlay(template) {
|
|
5634
|
+
if (this.overlayRef) {
|
|
5635
|
+
return; // already open
|
|
5636
|
+
}
|
|
5652
5637
|
this.previousActiveElement = document.activeElement;
|
|
5653
|
-
this.
|
|
5638
|
+
this.overlayRef = this.overlay.create({
|
|
5639
|
+
hasBackdrop: true,
|
|
5640
|
+
backdropClass: 'pecb-right-modal__backdrop',
|
|
5641
|
+
scrollStrategy: this.overlay.scrollStrategies.block(),
|
|
5642
|
+
// Full-height drawer pinned to the right edge of the viewport.
|
|
5643
|
+
positionStrategy: this.overlay.position().global().top('0').right('0')
|
|
5644
|
+
});
|
|
5645
|
+
this.portal = new TemplatePortal(template, this.viewContainerRef);
|
|
5646
|
+
this.overlayRef.attach(this.portal);
|
|
5647
|
+
this.subscriptions = new Subscription();
|
|
5648
|
+
this.subscriptions.add(this.overlayRef.backdropClick().subscribe(() => {
|
|
5649
|
+
if (this.closeOnBackdropClick()) {
|
|
5650
|
+
this.close();
|
|
5651
|
+
}
|
|
5652
|
+
}));
|
|
5653
|
+
this.subscriptions.add(this.overlayRef.keydownEvents().subscribe((event) => {
|
|
5654
|
+
if (this.closeOnEscape() && event.key === 'Escape') {
|
|
5655
|
+
event.preventDefault();
|
|
5656
|
+
this.close();
|
|
5657
|
+
}
|
|
5658
|
+
}));
|
|
5659
|
+
// Focus the dialog panel itself (tabindex="-1") rather than the close
|
|
5660
|
+
// button, so opening the modal doesn't render a focus ring on the × icon.
|
|
5661
|
+
// cdkTrapFocus on the panel keeps Tab navigation contained within it.
|
|
5662
|
+
const panel = this.overlayRef.overlayElement.querySelector('.pecb-right-modal__panel');
|
|
5663
|
+
panel?.focus();
|
|
5654
5664
|
this.opened.emit();
|
|
5655
5665
|
}
|
|
5656
|
-
|
|
5657
|
-
this.
|
|
5658
|
-
|
|
5659
|
-
cleanup() {
|
|
5660
|
-
if (this.cleanupFocusTrap) {
|
|
5661
|
-
this.cleanupFocusTrap();
|
|
5662
|
-
this.cleanupFocusTrap = null;
|
|
5663
|
-
}
|
|
5664
|
-
if (this.cleanupBodyScroll) {
|
|
5665
|
-
this.cleanupBodyScroll();
|
|
5666
|
-
this.cleanupBodyScroll = null;
|
|
5666
|
+
closeOverlay() {
|
|
5667
|
+
if (!this.overlayRef) {
|
|
5668
|
+
return;
|
|
5667
5669
|
}
|
|
5670
|
+
this.destroyOverlay();
|
|
5668
5671
|
if (this.previousActiveElement instanceof HTMLElement) {
|
|
5669
5672
|
this.previousActiveElement.focus();
|
|
5670
5673
|
}
|
|
5674
|
+
this.closed.emit();
|
|
5675
|
+
}
|
|
5676
|
+
destroyOverlay() {
|
|
5677
|
+
this.subscriptions.unsubscribe();
|
|
5678
|
+
if (this.portal?.isAttached) {
|
|
5679
|
+
this.portal.detach();
|
|
5680
|
+
}
|
|
5681
|
+
this.portal = null;
|
|
5682
|
+
if (this.overlayRef) {
|
|
5683
|
+
this.overlayRef.dispose();
|
|
5684
|
+
this.overlayRef = null;
|
|
5685
|
+
}
|
|
5671
5686
|
}
|
|
5672
5687
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: RightModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
5673
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.3.16", type: RightModalComponent, isStandalone: true, selector: "pecb-right-modal", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, closeOnBackdropClick: { classPropertyName: "closeOnBackdropClick", publicName: "closeOnBackdropClick", isSignal: true, isRequired: false, transformFunction: null }, closeOnEscape: { classPropertyName: "closeOnEscape", publicName: "closeOnEscape", isSignal: true, isRequired: false, transformFunction: null }, showCloseButton: { classPropertyName: "showCloseButton", publicName: "showCloseButton", isSignal: true, isRequired: false, transformFunction: null }, closeAriaLabel: { classPropertyName: "closeAriaLabel", publicName: "closeAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledBy: { classPropertyName: "ariaLabelledBy", publicName: "ariaLabelledBy", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { isOpen: "isOpenChange", closed: "closed", opened: "opened" }, viewQueries: [{ propertyName: "
|
|
5688
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.3.16", type: RightModalComponent, isStandalone: true, selector: "pecb-right-modal", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, closeOnBackdropClick: { classPropertyName: "closeOnBackdropClick", publicName: "closeOnBackdropClick", isSignal: true, isRequired: false, transformFunction: null }, closeOnEscape: { classPropertyName: "closeOnEscape", publicName: "closeOnEscape", isSignal: true, isRequired: false, transformFunction: null }, showCloseButton: { classPropertyName: "showCloseButton", publicName: "showCloseButton", isSignal: true, isRequired: false, transformFunction: null }, closeAriaLabel: { classPropertyName: "closeAriaLabel", publicName: "closeAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledBy: { classPropertyName: "ariaLabelledBy", publicName: "ariaLabelledBy", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { isOpen: "isOpenChange", closed: "closed", opened: "opened" }, viewQueries: [{ propertyName: "modalTemplate", first: true, predicate: ["modalTemplate"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-template #modalTemplate>\n <div\n [id]=\"id()\"\n [class]=\"panelClasses.join(' ')\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-labelledby]=\"ariaLabelledBy()\"\n [attr.aria-describedby]=\"ariaDescribedBy()\"\n tabindex=\"-1\"\n cdkTrapFocus\n >\n <button\n *ngIf=\"showCloseButton()\"\n type=\"button\"\n class=\"pecb-right-modal__close-btn\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"close()\"\n >\n <svg\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M18 6L6 18M6 6L18 18\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n\n <ng-content select=\"[pecbRightModalHeader]\"></ng-content>\n <ng-content select=\"[pecbRightModalContent]\"></ng-content>\n <ng-content></ng-content>\n <ng-content select=\"[pecbRightModalFooter]\"></ng-content>\n </div>\n</ng-template>\n", styles: [".pecb-right-modal__backdrop{background-color:#00000080}.pecb-right-modal__panel{position:relative;display:flex;width:516.23px;max-width:100vw;height:100vh;padding:24px;animation:slideInRight .25s ease-in-out forwards;flex-direction:column;align-items:stretch;gap:16px;border-radius:24px 0 0 24px;background:#fff;box-shadow:0 5px 16px -4px #10182826;overflow:hidden}.pecb-right-modal__panel:focus{outline:none}.pecb-right-modal__panel--sm{width:400px}.pecb-right-modal__panel--md{width:516.23px}.pecb-right-modal__panel--lg{width:700px}.pecb-right-modal__close-btn{padding:0;border:none;background:none;cursor:pointer;font:inherit;color:inherit;position:absolute;top:24px;right:24px;z-index:20;display:flex;align-items:center;justify-content:center;width:24px;height:24px;color:#616161;border-radius:6px;transition-property:color,background-color;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-right-modal__close-btn:focus{outline:none}.pecb-right-modal__close-btn:focus-visible{outline:2px solid #a11e29;outline-offset:2px}.pecb-right-modal__close-btn:hover{color:#313131;background-color:#ececec}.pecb-right-modal__close-btn svg{width:20px;height:20px}.pecb-right-modal__header{position:sticky;top:0;z-index:10;width:100%;padding-right:32px;background:#fff}.pecb-right-modal__content{flex:1;min-height:0;min-width:0;width:100%;display:flex;flex-direction:column;align-items:stretch;overflow:auto}.pecb-right-modal__content::-webkit-scrollbar{width:8px;height:8px}.pecb-right-modal__content::-webkit-scrollbar-track{background:#ececec}.pecb-right-modal__content::-webkit-scrollbar-thumb{background:#c2c2c2;border-radius:12px}.pecb-right-modal__content::-webkit-scrollbar-thumb:hover{background:#a9a9a9}.pecb-right-modal__footer{position:sticky;bottom:0;width:100%;margin-top:auto;background:#fff}@keyframes slideInRight{0%{transform:translate(100%)}to{transform:translate(0)}}@media (prefers-reduced-motion: reduce){.pecb-right-modal__panel{animation:none}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
|
|
5674
5689
|
}
|
|
5675
5690
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: RightModalComponent, decorators: [{
|
|
5676
5691
|
type: Component,
|
|
5677
|
-
args: [{ selector: 'pecb-right-modal', imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<
|
|
5678
|
-
}], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], isOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "isOpen", required: false }] }, { type: i0.Output, args: ["isOpenChange"] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], closeOnBackdropClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnBackdropClick", required: false }] }], closeOnEscape: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnEscape", required: false }] }], showCloseButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCloseButton", required: false }] }], closeAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeAriaLabel", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], ariaLabelledBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelledBy", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], opened: [{ type: i0.Output, args: ["opened"] }],
|
|
5692
|
+
args: [{ selector: 'pecb-right-modal', imports: [CommonModule, CdkTrapFocus], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<ng-template #modalTemplate>\n <div\n [id]=\"id()\"\n [class]=\"panelClasses.join(' ')\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-labelledby]=\"ariaLabelledBy()\"\n [attr.aria-describedby]=\"ariaDescribedBy()\"\n tabindex=\"-1\"\n cdkTrapFocus\n >\n <button\n *ngIf=\"showCloseButton()\"\n type=\"button\"\n class=\"pecb-right-modal__close-btn\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"close()\"\n >\n <svg\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M18 6L6 18M6 6L18 18\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n\n <ng-content select=\"[pecbRightModalHeader]\"></ng-content>\n <ng-content select=\"[pecbRightModalContent]\"></ng-content>\n <ng-content></ng-content>\n <ng-content select=\"[pecbRightModalFooter]\"></ng-content>\n </div>\n</ng-template>\n", styles: [".pecb-right-modal__backdrop{background-color:#00000080}.pecb-right-modal__panel{position:relative;display:flex;width:516.23px;max-width:100vw;height:100vh;padding:24px;animation:slideInRight .25s ease-in-out forwards;flex-direction:column;align-items:stretch;gap:16px;border-radius:24px 0 0 24px;background:#fff;box-shadow:0 5px 16px -4px #10182826;overflow:hidden}.pecb-right-modal__panel:focus{outline:none}.pecb-right-modal__panel--sm{width:400px}.pecb-right-modal__panel--md{width:516.23px}.pecb-right-modal__panel--lg{width:700px}.pecb-right-modal__close-btn{padding:0;border:none;background:none;cursor:pointer;font:inherit;color:inherit;position:absolute;top:24px;right:24px;z-index:20;display:flex;align-items:center;justify-content:center;width:24px;height:24px;color:#616161;border-radius:6px;transition-property:color,background-color;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-right-modal__close-btn:focus{outline:none}.pecb-right-modal__close-btn:focus-visible{outline:2px solid #a11e29;outline-offset:2px}.pecb-right-modal__close-btn:hover{color:#313131;background-color:#ececec}.pecb-right-modal__close-btn svg{width:20px;height:20px}.pecb-right-modal__header{position:sticky;top:0;z-index:10;width:100%;padding-right:32px;background:#fff}.pecb-right-modal__content{flex:1;min-height:0;min-width:0;width:100%;display:flex;flex-direction:column;align-items:stretch;overflow:auto}.pecb-right-modal__content::-webkit-scrollbar{width:8px;height:8px}.pecb-right-modal__content::-webkit-scrollbar-track{background:#ececec}.pecb-right-modal__content::-webkit-scrollbar-thumb{background:#c2c2c2;border-radius:12px}.pecb-right-modal__content::-webkit-scrollbar-thumb:hover{background:#a9a9a9}.pecb-right-modal__footer{position:sticky;bottom:0;width:100%;margin-top:auto;background:#fff}@keyframes slideInRight{0%{transform:translate(100%)}to{transform:translate(0)}}@media (prefers-reduced-motion: reduce){.pecb-right-modal__panel{animation:none}}\n"] }]
|
|
5693
|
+
}], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], isOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "isOpen", required: false }] }, { type: i0.Output, args: ["isOpenChange"] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], closeOnBackdropClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnBackdropClick", required: false }] }], closeOnEscape: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnEscape", required: false }] }], showCloseButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCloseButton", required: false }] }], closeAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeAriaLabel", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], ariaLabelledBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelledBy", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], opened: [{ type: i0.Output, args: ["opened"] }], modalTemplate: [{ type: i0.ViewChild, args: ['modalTemplate', { isSignal: true }] }] } });
|
|
5679
5694
|
class RightModalHeaderDirective {
|
|
5680
5695
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: RightModalHeaderDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
5681
5696
|
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.16", type: RightModalHeaderDirective, isStandalone: true, selector: "[pecbRightModalHeader]", host: { classAttribute: "pecb-right-modal__header" }, ngImport: i0 }); }
|
|
@@ -5845,31 +5860,29 @@ class ConfirmationComponent {
|
|
|
5845
5860
|
this.confirmed = output();
|
|
5846
5861
|
this.cancelled = output();
|
|
5847
5862
|
this.closed = output();
|
|
5848
|
-
this.
|
|
5849
|
-
this.
|
|
5863
|
+
this.overlay = inject(Overlay);
|
|
5864
|
+
this.viewContainerRef = inject(ViewContainerRef);
|
|
5865
|
+
// The dialog panel lives in this template so it can be attached to a CDK
|
|
5866
|
+
// overlay (rendered in the global overlay container on <body>), which escapes
|
|
5867
|
+
// any `transform`/`filter` containing-block trap on an ancestor.
|
|
5868
|
+
this.dialogTemplate = viewChild('dialogTemplate', ...(ngDevMode ? [{ debugName: "dialogTemplate" }] : []));
|
|
5869
|
+
this.overlayRef = null;
|
|
5870
|
+
this.portal = null;
|
|
5871
|
+
this.subscriptions = new Subscription();
|
|
5850
5872
|
this.previousActiveElement = null;
|
|
5851
5873
|
effect(() => {
|
|
5852
|
-
|
|
5853
|
-
|
|
5874
|
+
const open = this.isOpen();
|
|
5875
|
+
const template = this.dialogTemplate();
|
|
5876
|
+
if (open && template) {
|
|
5877
|
+
this.openOverlay(template);
|
|
5854
5878
|
}
|
|
5855
|
-
else {
|
|
5856
|
-
this.
|
|
5879
|
+
else if (!open) {
|
|
5880
|
+
this.destroyOverlay();
|
|
5857
5881
|
}
|
|
5858
5882
|
});
|
|
5859
5883
|
}
|
|
5860
|
-
ngAfterViewInit() {
|
|
5861
|
-
if (this.isOpen()) {
|
|
5862
|
-
this.setupFocusTrap();
|
|
5863
|
-
}
|
|
5864
|
-
}
|
|
5865
5884
|
ngOnDestroy() {
|
|
5866
|
-
this.
|
|
5867
|
-
}
|
|
5868
|
-
get dialogClasses() {
|
|
5869
|
-
return [
|
|
5870
|
-
'pecb-confirmation',
|
|
5871
|
-
this.isOpen() ? 'pecb-confirmation--open' : ''
|
|
5872
|
-
].filter(Boolean);
|
|
5885
|
+
this.destroyOverlay();
|
|
5873
5886
|
}
|
|
5874
5887
|
get iconContainerClass() {
|
|
5875
5888
|
return `pecb-confirmation__icon pecb-confirmation__icon--${this.iconType()}`;
|
|
@@ -5880,17 +5893,6 @@ class ConfirmationComponent {
|
|
|
5880
5893
|
get messageId() {
|
|
5881
5894
|
return `${this.id()}-message`;
|
|
5882
5895
|
}
|
|
5883
|
-
onBackdropClick(event) {
|
|
5884
|
-
if (this.closeOnBackdropClick() && event.target === event.currentTarget) {
|
|
5885
|
-
this.close();
|
|
5886
|
-
}
|
|
5887
|
-
}
|
|
5888
|
-
onKeydown(event) {
|
|
5889
|
-
if (this.closeOnEscape() && event.key === 'Escape') {
|
|
5890
|
-
event.preventDefault();
|
|
5891
|
-
this.close();
|
|
5892
|
-
}
|
|
5893
|
-
}
|
|
5894
5896
|
onConfirm() {
|
|
5895
5897
|
this.confirmed.emit();
|
|
5896
5898
|
this.close();
|
|
@@ -5903,39 +5905,58 @@ class ConfirmationComponent {
|
|
|
5903
5905
|
this.isOpen.set(false);
|
|
5904
5906
|
this.closed.emit();
|
|
5905
5907
|
}
|
|
5906
|
-
|
|
5907
|
-
this.
|
|
5908
|
-
|
|
5909
|
-
this.setupFocusTrap();
|
|
5910
|
-
}
|
|
5911
|
-
setupFocusTrap() {
|
|
5912
|
-
if (this.dialogPanel?.nativeElement && !this.cleanupFocusTrap) {
|
|
5913
|
-
this.cleanupFocusTrap = trapFocus(this.dialogPanel.nativeElement);
|
|
5914
|
-
}
|
|
5915
|
-
}
|
|
5916
|
-
cleanup() {
|
|
5917
|
-
if (this.cleanupFocusTrap) {
|
|
5918
|
-
this.cleanupFocusTrap();
|
|
5919
|
-
this.cleanupFocusTrap = null;
|
|
5908
|
+
openOverlay(template) {
|
|
5909
|
+
if (this.overlayRef) {
|
|
5910
|
+
return; // already open
|
|
5920
5911
|
}
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5912
|
+
this.previousActiveElement = document.activeElement;
|
|
5913
|
+
this.overlayRef = this.overlay.create({
|
|
5914
|
+
hasBackdrop: true,
|
|
5915
|
+
backdropClass: 'pecb-confirmation__backdrop',
|
|
5916
|
+
scrollStrategy: this.overlay.scrollStrategies.block(),
|
|
5917
|
+
// Centered dialog.
|
|
5918
|
+
positionStrategy: this.overlay.position().global().centerHorizontally().centerVertically()
|
|
5919
|
+
});
|
|
5920
|
+
this.portal = new TemplatePortal(template, this.viewContainerRef);
|
|
5921
|
+
this.overlayRef.attach(this.portal);
|
|
5922
|
+
this.subscriptions = new Subscription();
|
|
5923
|
+
this.subscriptions.add(this.overlayRef.backdropClick().subscribe(() => {
|
|
5924
|
+
if (this.closeOnBackdropClick()) {
|
|
5925
|
+
this.close();
|
|
5926
|
+
}
|
|
5927
|
+
}));
|
|
5928
|
+
this.subscriptions.add(this.overlayRef.keydownEvents().subscribe((event) => {
|
|
5929
|
+
if (this.closeOnEscape() && event.key === 'Escape') {
|
|
5930
|
+
event.preventDefault();
|
|
5931
|
+
this.close();
|
|
5932
|
+
}
|
|
5933
|
+
}));
|
|
5934
|
+
// Focus the dialog panel itself (tabindex="-1"); cdkTrapFocus keeps Tab
|
|
5935
|
+
// navigation contained within the dialog.
|
|
5936
|
+
const panel = this.overlayRef.overlayElement.querySelector('.pecb-confirmation__panel');
|
|
5937
|
+
panel?.focus();
|
|
5938
|
+
}
|
|
5939
|
+
destroyOverlay() {
|
|
5940
|
+
this.subscriptions.unsubscribe();
|
|
5941
|
+
if (this.portal?.isAttached) {
|
|
5942
|
+
this.portal.detach();
|
|
5924
5943
|
}
|
|
5925
|
-
|
|
5926
|
-
|
|
5944
|
+
this.portal = null;
|
|
5945
|
+
if (this.overlayRef) {
|
|
5946
|
+
this.overlayRef.dispose();
|
|
5947
|
+
this.overlayRef = null;
|
|
5948
|
+
if (this.previousActiveElement instanceof HTMLElement) {
|
|
5949
|
+
this.previousActiveElement.focus();
|
|
5950
|
+
}
|
|
5927
5951
|
}
|
|
5928
5952
|
}
|
|
5929
5953
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ConfirmationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
5930
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: ConfirmationComponent, isStandalone: true, selector: "pecb-confirmation", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, confirmText: { classPropertyName: "confirmText", publicName: "confirmText", isSignal: true, isRequired: false, transformFunction: null }, cancelText: { classPropertyName: "cancelText", publicName: "cancelText", isSignal: true, isRequired: false, transformFunction: null }, iconType: { classPropertyName: "iconType", publicName: "iconType", isSignal: true, isRequired: false, transformFunction: null }, showCloseButton: { classPropertyName: "showCloseButton", publicName: "showCloseButton", isSignal: true, isRequired: false, transformFunction: null }, closeAriaLabel: { classPropertyName: "closeAriaLabel", publicName: "closeAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, closeOnBackdropClick: { classPropertyName: "closeOnBackdropClick", publicName: "closeOnBackdropClick", isSignal: true, isRequired: false, transformFunction: null }, closeOnEscape: { classPropertyName: "closeOnEscape", publicName: "closeOnEscape", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { isOpen: "isOpenChange", confirmed: "confirmed", cancelled: "cancelled", closed: "closed" }, viewQueries: [{ propertyName: "dialogPanel", first: true, predicate: ["dialogPanel"], descendants: true }], ngImport: i0, template: "@if (isOpen()) {\n <div\n [id]=\"id()\"\n [class]=\"dialogClasses.join(' ')\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-labelledby]=\"titleId\"\n [attr.aria-describedby]=\"message() ? messageId : ariaDescribedBy() || null\"\n [attr.aria-label]=\"ariaLabel() || null\"\n (click)=\"onBackdropClick($event)\"\n (keydown)=\"onKeydown($event)\"\n >\n <div\n #dialogPanel\n class=\"pecb-confirmation__panel\"\n tabindex=\"-1\"\n >\n \n @if (showCloseButton()) {\n <div class=\"pecb-confirmation__header\">\n <button\n type=\"button\"\n class=\"pecb-confirmation__close-btn\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"close()\"\n >\n <svg\n width=\"10\"\n height=\"10\"\n viewBox=\"0 0 10 10\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M9 1L1 9M1 1L9 9\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n </div>\n }\n\n \n <div [class]=\"iconContainerClass\" aria-hidden=\"true\">\n @switch (iconType()) {\n @case ('success') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\">\n <path d=\"M0 12C0 5.37258 5.37258 0 12 0H52C58.6274 0 64 5.37258 64 12V52C64 58.6274 58.6274 64 52 64H12C5.37258 64 0 58.6274 0 52V12Z\" fill=\"#E6F4EA\"/>\n <circle cx=\"31.8243\" cy=\"31.8243\" r=\"18.2857\" fill=\"white\"/>\n <path d=\"M31.9991 9.84625C19.7635 9.84625 9.84521 19.7645 9.84521 32.0001C9.84521 44.2357 19.7635 54.1539 31.9991 54.1539C44.2347 54.1539 54.1529 44.2357 54.1529 32.0001C54.1426 19.7645 44.2244 9.84625 31.9991 9.84625ZM43.8227 26.3458L31.8137 41.3725C31.2781 42.0316 30.4954 42.4436 29.6508 42.4951C29.5993 42.4951 29.5375 42.4951 29.486 42.4951C28.693 42.4951 27.9308 42.1861 27.3747 41.6197L20.3917 34.6367C19.2279 33.4729 19.2279 31.5778 20.3917 30.414C21.5555 29.2502 23.4506 29.2502 24.6144 30.414L29.2285 35.0281L39.1571 22.6174C40.187 21.33 42.0615 21.1137 43.3592 22.1539C44.6466 23.1736 44.8526 25.0584 43.8227 26.3458Z\" fill=\"#57BC57\"/>\n </svg>\n }\n @case ('warning') {\n <svg width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M0 12C0 5.37258 5.37258 0 12 0H52C58.6274 0 64 5.37258 64 12V52C64 58.6274 58.6274 64 52 64H12C5.37258 64 0 58.6274 0 52V12Z\" fill=\"#FFF4E5\"/>\n <circle cx=\"32.5303\" cy=\"32.5281\" r=\"18.989\" fill=\"white\"/>\n <path d=\"M32.0012 9.84668C44.2362 9.84668 54.1543 19.765 54.1545 32C54.1545 44.2352 44.2364 54.1543 32.0012 54.1543C19.766 54.1543 9.8479 44.2352 9.8479 32C9.84815 19.765 19.7662 9.84668 32.0012 9.84668ZM32.0823 40.2266C30.0029 40.2266 28.3108 41.9187 28.3108 43.998C28.5007 48.9935 35.6636 48.9922 35.8528 43.998C35.8528 41.9188 34.1615 40.2266 32.0823 40.2266ZM35.7727 18.9727C33.9014 16.6513 30.1067 16.6501 28.2346 18.9727C27.2974 20.0886 26.9044 21.5514 27.1555 22.9863C27.9048 27.2673 28.9622 33.3099 29.4368 36.0234C29.9687 38.8793 34.0404 38.8785 34.5715 36.0234L36.8528 22.9863C37.1039 21.5514 36.7101 20.0887 35.7727 18.9727Z\" fill=\"#FF9C4A\"/>\n </svg>\n }\n @case ('error') {\n <svg width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M0 12C0 5.37258 5.37258 0 12 0H52C58.6274 0 64 5.37258 64 12V52C64 58.6274 58.6274 64 52 64H12C5.37258 64 0 58.6274 0 52V12Z\" fill=\"#FDECEA\"/>\n <circle cx=\"31.8243\" cy=\"31.8243\" r=\"18.2857\" fill=\"white\"/>\n <path d=\"M47.6652 16.3349C56.3168 24.9865 56.3168 39.0136 47.6652 47.6652C39.0136 56.3168 24.9865 56.3168 16.3349 47.6652C7.68329 39.0136 7.68329 24.9865 16.3349 16.3349C24.9865 7.68329 39.0136 7.68329 47.6652 16.3349ZM40.2677 22.862C39.3666 21.9609 37.9053 21.9609 37.0042 22.862L31.4561 28.4101C31.2759 28.5903 30.9836 28.5903 30.8034 28.4101L25.2553 22.862C24.3542 21.9609 22.8929 21.9609 21.9918 22.862C21.0906 23.7632 21.0906 25.2245 21.9918 26.1256L27.5398 31.6737C27.7201 31.8539 27.7201 32.1462 27.5398 32.3264L21.9918 37.8745C21.0906 38.7756 21.0906 40.2369 21.9918 41.138C22.8929 42.0392 24.3542 42.0392 25.2553 41.138L30.8034 35.59C30.9836 35.4097 31.2759 35.4097 31.4561 35.59L37.0042 41.138C37.9053 42.0392 39.3666 42.0392 40.2677 41.138C41.1689 40.2369 41.1689 38.7756 40.2677 37.8745L34.7197 32.3264C34.5394 32.1462 34.5394 31.8539 34.7197 31.6737L40.2677 26.1256C41.1689 25.2245 41.1689 23.7632 40.2677 22.862Z\" fill=\"#FF606F\"/>\n </svg>\n }\n @case ('info') {\n <svg width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M0 12C0 5.37258 5.37258 0 12 0H52C58.6274 0 64 5.37258 64 12V52C64 58.6274 58.6274 64 52 64H12C5.37258 64 0 58.6274 0 52V12Z\" fill=\"#E6F0FA\"/>\n <circle cx=\"32.5283\" cy=\"32.5281\" r=\"18.989\" fill=\"white\"/>\n <path d=\"M31.9995 9.84668C44.2348 9.84668 54.1534 19.7653 54.1534 32.0005C54.1534 44.2358 44.2348 54.1544 31.9995 54.1544C19.7643 54.1544 9.8457 44.2358 9.8457 32.0005C9.8457 19.7653 19.7643 9.84668 31.9995 9.84668ZM28.3072 26.0943C26.8347 26.0945 25.8457 27.0832 25.8457 28.5558C25.8459 30.0282 26.8349 31.0171 28.3072 31.0174H31.758L28.3072 42.5835C28.0603 43.8088 28.3077 45.2811 29.0488 46.2698C29.7903 47.4952 31.0265 47.9896 32.252 47.9897H36.187C37.67 47.9896 38.6486 47.0112 38.6486 45.5282C38.6484 44.0558 37.6595 43.0668 36.187 43.0666H33.2303L36.681 31.5005C36.9281 30.2751 36.6808 28.8028 35.9395 27.8142C35.1979 26.5886 33.9619 26.0943 32.7363 26.0943H28.3072ZM33.4779 16.0005C31.4387 16.0006 29.7904 17.6488 29.7904 19.688C29.7906 21.7271 31.4388 23.3743 33.4779 23.3743C35.5171 23.3743 37.1652 21.7271 37.1654 19.688C37.1654 17.6488 35.5172 16.0005 33.4779 16.0005Z\" fill=\"#5EACFF\"/>\n </svg>\n }\n }\n </div>\n\n \n <div class=\"pecb-confirmation__content\">\n <h2 [id]=\"titleId\" class=\"pecb-confirmation__title\">{{ title() }}</h2>\n\n @if (message()) {\n <p [id]=\"messageId\" class=\"pecb-confirmation__message\">{{ message() }}</p>\n }\n </div>\n\n \n <div class=\"pecb-confirmation__actions\">\n <button\n type=\"button\"\n class=\"pecb-confirmation__btn pecb-confirmation__btn--confirm\"\n (click)=\"onConfirm()\"\n >\n {{ confirmText() }}\n </button>\n <button\n type=\"button\"\n class=\"pecb-confirmation__btn pecb-confirmation__btn--cancel\"\n (click)=\"onCancel()\"\n >\n {{ cancelText() }}\n </button>\n </div>\n </div>\n </div>\n}\n", styles: [".pecb-confirmation{position:fixed;inset:0;z-index:1050;display:flex;align-items:center;justify-content:center;background-color:#00000080;animation:fadeIn .15s ease-in-out forwards}.pecb-confirmation__panel{display:flex;width:616px;max-width:calc(100vw - 32px);padding:22px 20px 30px;flex-direction:column;align-items:center;gap:27px;background:#fff;border-radius:12px;box-shadow:0 10px 10px #0000000d;animation:scaleIn .25s ease-in-out forwards;outline:none}.pecb-confirmation__header{display:flex;justify-content:flex-end;align-items:center;width:100%;height:20px}.pecb-confirmation__close-btn{padding:0;border:none;background:none;cursor:pointer;font:inherit;color:inherit;display:flex;align-items:center;justify-content:center;width:20px;height:20px;color:#616161;border-radius:6px;outline:none;transition-property:color,background-color;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-confirmation__close-btn:focus{outline:none}.pecb-confirmation__close-btn:hover{color:#313131;background-color:#ececec}.pecb-confirmation__close-btn svg{width:10px;height:10px}.pecb-confirmation__icon{display:flex;align-items:center;justify-content:center;width:64px;height:64px;border-radius:12px;flex-shrink:0}.pecb-confirmation__icon--success{background-color:transparent}.pecb-confirmation__icon--warning{background-color:#fff4e5}.pecb-confirmation__icon--error{background-color:#fdecea}.pecb-confirmation__icon--info{background-color:#e6f0fa}.pecb-confirmation__content{display:flex;flex-direction:column;align-items:center;gap:9px;padding:0 17px;width:100%}.pecb-confirmation__title{margin:0;font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:24px;font-weight:700;line-height:1.2;color:#313131;text-align:center}.pecb-confirmation__message{margin:0;font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:16px;font-weight:400;line-height:1.6;color:#616161;text-align:center}.pecb-confirmation__actions{display:flex;align-items:center;justify-content:center;gap:16px;width:100%}.pecb-confirmation__btn{border:none;background:none;font:inherit;color:inherit;display:inline-flex;align-items:center;justify-content:center;height:38px;padding:10px 40px;border-radius:6px;font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;font-style:normal;font-weight:500;line-height:normal;cursor:pointer;white-space:nowrap;transition-property:background-color,border-color,color;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-confirmation__btn:focus{outline:none}.pecb-confirmation__btn:focus-visible{outline:2px solid #a11e29;outline-offset:2px}.pecb-confirmation__btn--confirm{background-color:#212427;color:#fff;border:1px solid #212427}.pecb-confirmation__btn--confirm:hover{background-color:#616161;border-color:#616161}.pecb-confirmation__btn--confirm:active{background-color:#000;border-color:#000}.pecb-confirmation__btn--cancel{background-color:#fff;color:#212427;border:1px solid #c2c2c2}.pecb-confirmation__btn--cancel:hover{background-color:#ececec;border-color:#616161}.pecb-confirmation__btn--cancel:active{background-color:#c2c2c2;border-color:#616161}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes scaleIn{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}@media (prefers-reduced-motion: reduce){.pecb-confirmation,.pecb-confirmation__panel{animation:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
5954
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: ConfirmationComponent, isStandalone: true, selector: "pecb-confirmation", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, confirmText: { classPropertyName: "confirmText", publicName: "confirmText", isSignal: true, isRequired: false, transformFunction: null }, cancelText: { classPropertyName: "cancelText", publicName: "cancelText", isSignal: true, isRequired: false, transformFunction: null }, iconType: { classPropertyName: "iconType", publicName: "iconType", isSignal: true, isRequired: false, transformFunction: null }, showCloseButton: { classPropertyName: "showCloseButton", publicName: "showCloseButton", isSignal: true, isRequired: false, transformFunction: null }, closeAriaLabel: { classPropertyName: "closeAriaLabel", publicName: "closeAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, closeOnBackdropClick: { classPropertyName: "closeOnBackdropClick", publicName: "closeOnBackdropClick", isSignal: true, isRequired: false, transformFunction: null }, closeOnEscape: { classPropertyName: "closeOnEscape", publicName: "closeOnEscape", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaDescribedBy: { classPropertyName: "ariaDescribedBy", publicName: "ariaDescribedBy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { isOpen: "isOpenChange", confirmed: "confirmed", cancelled: "cancelled", closed: "closed" }, viewQueries: [{ propertyName: "dialogTemplate", first: true, predicate: ["dialogTemplate"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-template #dialogTemplate>\n <div\n #dialogPanel\n [id]=\"id()\"\n class=\"pecb-confirmation__panel\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-labelledby]=\"titleId\"\n [attr.aria-describedby]=\"message() ? messageId : ariaDescribedBy() || null\"\n [attr.aria-label]=\"ariaLabel() || null\"\n tabindex=\"-1\"\n cdkTrapFocus\n >\n @if (showCloseButton()) {\n <div class=\"pecb-confirmation__header\">\n <button\n type=\"button\"\n class=\"pecb-confirmation__close-btn\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"close()\"\n >\n <svg\n width=\"10\"\n height=\"10\"\n viewBox=\"0 0 10 10\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M9 1L1 9M1 1L9 9\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n </div>\n }\n\n <div [class]=\"iconContainerClass\" aria-hidden=\"true\">\n @switch (iconType()) {\n @case ('success') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\">\n <path d=\"M0 12C0 5.37258 5.37258 0 12 0H52C58.6274 0 64 5.37258 64 12V52C64 58.6274 58.6274 64 52 64H12C5.37258 64 0 58.6274 0 52V12Z\" fill=\"#E6F4EA\"/>\n <circle cx=\"31.8243\" cy=\"31.8243\" r=\"18.2857\" fill=\"white\"/>\n <path d=\"M31.9991 9.84625C19.7635 9.84625 9.84521 19.7645 9.84521 32.0001C9.84521 44.2357 19.7635 54.1539 31.9991 54.1539C44.2347 54.1539 54.1529 44.2357 54.1529 32.0001C54.1426 19.7645 44.2244 9.84625 31.9991 9.84625ZM43.8227 26.3458L31.8137 41.3725C31.2781 42.0316 30.4954 42.4436 29.6508 42.4951C29.5993 42.4951 29.5375 42.4951 29.486 42.4951C28.693 42.4951 27.9308 42.1861 27.3747 41.6197L20.3917 34.6367C19.2279 33.4729 19.2279 31.5778 20.3917 30.414C21.5555 29.2502 23.4506 29.2502 24.6144 30.414L29.2285 35.0281L39.1571 22.6174C40.187 21.33 42.0615 21.1137 43.3592 22.1539C44.6466 23.1736 44.8526 25.0584 43.8227 26.3458Z\" fill=\"#57BC57\"/>\n </svg>\n }\n @case ('warning') {\n <svg width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M0 12C0 5.37258 5.37258 0 12 0H52C58.6274 0 64 5.37258 64 12V52C64 58.6274 58.6274 64 52 64H12C5.37258 64 0 58.6274 0 52V12Z\" fill=\"#FFF4E5\"/>\n <circle cx=\"32.5303\" cy=\"32.5281\" r=\"18.989\" fill=\"white\"/>\n <path d=\"M32.0012 9.84668C44.2362 9.84668 54.1543 19.765 54.1545 32C54.1545 44.2352 44.2364 54.1543 32.0012 54.1543C19.766 54.1543 9.8479 44.2352 9.8479 32C9.84815 19.765 19.7662 9.84668 32.0012 9.84668ZM32.0823 40.2266C30.0029 40.2266 28.3108 41.9187 28.3108 43.998C28.5007 48.9935 35.6636 48.9922 35.8528 43.998C35.8528 41.9188 34.1615 40.2266 32.0823 40.2266ZM35.7727 18.9727C33.9014 16.6513 30.1067 16.6501 28.2346 18.9727C27.2974 20.0886 26.9044 21.5514 27.1555 22.9863C27.9048 27.2673 28.9622 33.3099 29.4368 36.0234C29.9687 38.8793 34.0404 38.8785 34.5715 36.0234L36.8528 22.9863C37.1039 21.5514 36.7101 20.0887 35.7727 18.9727Z\" fill=\"#FF9C4A\"/>\n </svg>\n }\n @case ('error') {\n <svg width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M0 12C0 5.37258 5.37258 0 12 0H52C58.6274 0 64 5.37258 64 12V52C64 58.6274 58.6274 64 52 64H12C5.37258 64 0 58.6274 0 52V12Z\" fill=\"#FDECEA\"/>\n <circle cx=\"31.8243\" cy=\"31.8243\" r=\"18.2857\" fill=\"white\"/>\n <path d=\"M47.6652 16.3349C56.3168 24.9865 56.3168 39.0136 47.6652 47.6652C39.0136 56.3168 24.9865 56.3168 16.3349 47.6652C7.68329 39.0136 7.68329 24.9865 16.3349 16.3349C24.9865 7.68329 39.0136 7.68329 47.6652 16.3349ZM40.2677 22.862C39.3666 21.9609 37.9053 21.9609 37.0042 22.862L31.4561 28.4101C31.2759 28.5903 30.9836 28.5903 30.8034 28.4101L25.2553 22.862C24.3542 21.9609 22.8929 21.9609 21.9918 22.862C21.0906 23.7632 21.0906 25.2245 21.9918 26.1256L27.5398 31.6737C27.7201 31.8539 27.7201 32.1462 27.5398 32.3264L21.9918 37.8745C21.0906 38.7756 21.0906 40.2369 21.9918 41.138C22.8929 42.0392 24.3542 42.0392 25.2553 41.138L30.8034 35.59C30.9836 35.4097 31.2759 35.4097 31.4561 35.59L37.0042 41.138C37.9053 42.0392 39.3666 42.0392 40.2677 41.138C41.1689 40.2369 41.1689 38.7756 40.2677 37.8745L34.7197 32.3264C34.5394 32.1462 34.5394 31.8539 34.7197 31.6737L40.2677 26.1256C41.1689 25.2245 41.1689 23.7632 40.2677 22.862Z\" fill=\"#FF606F\"/>\n </svg>\n }\n @case ('info') {\n <svg width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M0 12C0 5.37258 5.37258 0 12 0H52C58.6274 0 64 5.37258 64 12V52C64 58.6274 58.6274 64 52 64H12C5.37258 64 0 58.6274 0 52V12Z\" fill=\"#E6F0FA\"/>\n <circle cx=\"32.5283\" cy=\"32.5281\" r=\"18.989\" fill=\"white\"/>\n <path d=\"M31.9995 9.84668C44.2348 9.84668 54.1534 19.7653 54.1534 32.0005C54.1534 44.2358 44.2348 54.1544 31.9995 54.1544C19.7643 54.1544 9.8457 44.2358 9.8457 32.0005C9.8457 19.7653 19.7643 9.84668 31.9995 9.84668ZM28.3072 26.0943C26.8347 26.0945 25.8457 27.0832 25.8457 28.5558C25.8459 30.0282 26.8349 31.0171 28.3072 31.0174H31.758L28.3072 42.5835C28.0603 43.8088 28.3077 45.2811 29.0488 46.2698C29.7903 47.4952 31.0265 47.9896 32.252 47.9897H36.187C37.67 47.9896 38.6486 47.0112 38.6486 45.5282C38.6484 44.0558 37.6595 43.0668 36.187 43.0666H33.2303L36.681 31.5005C36.9281 30.2751 36.6808 28.8028 35.9395 27.8142C35.1979 26.5886 33.9619 26.0943 32.7363 26.0943H28.3072ZM33.4779 16.0005C31.4387 16.0006 29.7904 17.6488 29.7904 19.688C29.7906 21.7271 31.4388 23.3743 33.4779 23.3743C35.5171 23.3743 37.1652 21.7271 37.1654 19.688C37.1654 17.6488 35.5172 16.0005 33.4779 16.0005Z\" fill=\"#5EACFF\"/>\n </svg>\n }\n }\n </div>\n\n <div class=\"pecb-confirmation__content\">\n <h2 [id]=\"titleId\" class=\"pecb-confirmation__title\">{{ title() }}</h2>\n\n @if (message()) {\n <p [id]=\"messageId\" class=\"pecb-confirmation__message\">{{ message() }}</p>\n }\n </div>\n\n <div class=\"pecb-confirmation__actions\">\n <button\n type=\"button\"\n class=\"pecb-confirmation__btn pecb-confirmation__btn--confirm\"\n (click)=\"onConfirm()\"\n >\n {{ confirmText() }}\n </button>\n <button\n type=\"button\"\n class=\"pecb-confirmation__btn pecb-confirmation__btn--cancel\"\n (click)=\"onCancel()\"\n >\n {{ cancelText() }}\n </button>\n </div>\n </div>\n</ng-template>\n", styles: [".pecb-confirmation__backdrop{background-color:#00000080}.pecb-confirmation__panel{display:flex;width:616px;max-width:calc(100vw - 32px);padding:22px 20px 30px;flex-direction:column;align-items:center;gap:27px;background:#fff;border-radius:12px;box-shadow:0 10px 10px #0000000d;animation:scaleIn .25s ease-in-out forwards;outline:none}.pecb-confirmation__header{display:flex;justify-content:flex-end;align-items:center;width:100%;height:20px}.pecb-confirmation__close-btn{padding:0;border:none;background:none;cursor:pointer;font:inherit;color:inherit;display:flex;align-items:center;justify-content:center;width:20px;height:20px;color:#616161;border-radius:6px;outline:none;transition-property:color,background-color;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-confirmation__close-btn:focus{outline:none}.pecb-confirmation__close-btn:hover{color:#313131;background-color:#ececec}.pecb-confirmation__close-btn svg{width:10px;height:10px}.pecb-confirmation__icon{display:flex;align-items:center;justify-content:center;width:64px;height:64px;border-radius:12px;flex-shrink:0}.pecb-confirmation__icon--success{background-color:transparent}.pecb-confirmation__icon--warning{background-color:#fff4e5}.pecb-confirmation__icon--error{background-color:#fdecea}.pecb-confirmation__icon--info{background-color:#e6f0fa}.pecb-confirmation__content{display:flex;flex-direction:column;align-items:center;gap:9px;padding:0 17px;width:100%}.pecb-confirmation__title{margin:0;font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:24px;font-weight:700;line-height:1.2;color:#313131;text-align:center}.pecb-confirmation__message{margin:0;font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:16px;font-weight:400;line-height:1.6;color:#616161;text-align:center}.pecb-confirmation__actions{display:flex;align-items:center;justify-content:center;gap:16px;width:100%}.pecb-confirmation__btn{border:none;background:none;font:inherit;color:inherit;display:inline-flex;align-items:center;justify-content:center;height:38px;padding:10px 40px;border-radius:6px;font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;font-style:normal;font-weight:500;line-height:normal;cursor:pointer;white-space:nowrap;transition-property:background-color,border-color,color;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-confirmation__btn:focus{outline:none}.pecb-confirmation__btn:focus-visible{outline:2px solid #a11e29;outline-offset:2px}.pecb-confirmation__btn--confirm{background-color:#212427;color:#fff;border:1px solid #212427}.pecb-confirmation__btn--confirm:hover{background-color:#616161;border-color:#616161}.pecb-confirmation__btn--confirm:active{background-color:#000;border-color:#000}.pecb-confirmation__btn--cancel{background-color:#fff;color:#212427;border:1px solid #c2c2c2}.pecb-confirmation__btn--cancel:hover{background-color:#ececec;border-color:#616161}.pecb-confirmation__btn--cancel:active{background-color:#c2c2c2;border-color:#616161}@keyframes scaleIn{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}@media (prefers-reduced-motion: reduce){.pecb-confirmation__panel{animation:none}}\n"], dependencies: [{ kind: "directive", type: CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
|
|
5931
5955
|
}
|
|
5932
5956
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ConfirmationComponent, decorators: [{
|
|
5933
5957
|
type: Component,
|
|
5934
|
-
args: [{ selector: 'pecb-confirmation', imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (isOpen()) {\n <div\n [id]=\"id()\"\n [class]=\"dialogClasses.join(' ')\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-labelledby]=\"titleId\"\n [attr.aria-describedby]=\"message() ? messageId : ariaDescribedBy() || null\"\n [attr.aria-label]=\"ariaLabel() || null\"\n (click)=\"onBackdropClick($event)\"\n (keydown)=\"onKeydown($event)\"\n >\n <div\n #dialogPanel\n class=\"pecb-confirmation__panel\"\n tabindex=\"-1\"\n >\n \n @if (showCloseButton()) {\n <div class=\"pecb-confirmation__header\">\n <button\n type=\"button\"\n class=\"pecb-confirmation__close-btn\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"close()\"\n >\n <svg\n width=\"10\"\n height=\"10\"\n viewBox=\"0 0 10 10\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M9 1L1 9M1 1L9 9\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n </div>\n }\n\n \n <div [class]=\"iconContainerClass\" aria-hidden=\"true\">\n @switch (iconType()) {\n @case ('success') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\">\n <path d=\"M0 12C0 5.37258 5.37258 0 12 0H52C58.6274 0 64 5.37258 64 12V52C64 58.6274 58.6274 64 52 64H12C5.37258 64 0 58.6274 0 52V12Z\" fill=\"#E6F4EA\"/>\n <circle cx=\"31.8243\" cy=\"31.8243\" r=\"18.2857\" fill=\"white\"/>\n <path d=\"M31.9991 9.84625C19.7635 9.84625 9.84521 19.7645 9.84521 32.0001C9.84521 44.2357 19.7635 54.1539 31.9991 54.1539C44.2347 54.1539 54.1529 44.2357 54.1529 32.0001C54.1426 19.7645 44.2244 9.84625 31.9991 9.84625ZM43.8227 26.3458L31.8137 41.3725C31.2781 42.0316 30.4954 42.4436 29.6508 42.4951C29.5993 42.4951 29.5375 42.4951 29.486 42.4951C28.693 42.4951 27.9308 42.1861 27.3747 41.6197L20.3917 34.6367C19.2279 33.4729 19.2279 31.5778 20.3917 30.414C21.5555 29.2502 23.4506 29.2502 24.6144 30.414L29.2285 35.0281L39.1571 22.6174C40.187 21.33 42.0615 21.1137 43.3592 22.1539C44.6466 23.1736 44.8526 25.0584 43.8227 26.3458Z\" fill=\"#57BC57\"/>\n </svg>\n }\n @case ('warning') {\n <svg width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M0 12C0 5.37258 5.37258 0 12 0H52C58.6274 0 64 5.37258 64 12V52C64 58.6274 58.6274 64 52 64H12C5.37258 64 0 58.6274 0 52V12Z\" fill=\"#FFF4E5\"/>\n <circle cx=\"32.5303\" cy=\"32.5281\" r=\"18.989\" fill=\"white\"/>\n <path d=\"M32.0012 9.84668C44.2362 9.84668 54.1543 19.765 54.1545 32C54.1545 44.2352 44.2364 54.1543 32.0012 54.1543C19.766 54.1543 9.8479 44.2352 9.8479 32C9.84815 19.765 19.7662 9.84668 32.0012 9.84668ZM32.0823 40.2266C30.0029 40.2266 28.3108 41.9187 28.3108 43.998C28.5007 48.9935 35.6636 48.9922 35.8528 43.998C35.8528 41.9188 34.1615 40.2266 32.0823 40.2266ZM35.7727 18.9727C33.9014 16.6513 30.1067 16.6501 28.2346 18.9727C27.2974 20.0886 26.9044 21.5514 27.1555 22.9863C27.9048 27.2673 28.9622 33.3099 29.4368 36.0234C29.9687 38.8793 34.0404 38.8785 34.5715 36.0234L36.8528 22.9863C37.1039 21.5514 36.7101 20.0887 35.7727 18.9727Z\" fill=\"#FF9C4A\"/>\n </svg>\n }\n @case ('error') {\n <svg width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M0 12C0 5.37258 5.37258 0 12 0H52C58.6274 0 64 5.37258 64 12V52C64 58.6274 58.6274 64 52 64H12C5.37258 64 0 58.6274 0 52V12Z\" fill=\"#FDECEA\"/>\n <circle cx=\"31.8243\" cy=\"31.8243\" r=\"18.2857\" fill=\"white\"/>\n <path d=\"M47.6652 16.3349C56.3168 24.9865 56.3168 39.0136 47.6652 47.6652C39.0136 56.3168 24.9865 56.3168 16.3349 47.6652C7.68329 39.0136 7.68329 24.9865 16.3349 16.3349C24.9865 7.68329 39.0136 7.68329 47.6652 16.3349ZM40.2677 22.862C39.3666 21.9609 37.9053 21.9609 37.0042 22.862L31.4561 28.4101C31.2759 28.5903 30.9836 28.5903 30.8034 28.4101L25.2553 22.862C24.3542 21.9609 22.8929 21.9609 21.9918 22.862C21.0906 23.7632 21.0906 25.2245 21.9918 26.1256L27.5398 31.6737C27.7201 31.8539 27.7201 32.1462 27.5398 32.3264L21.9918 37.8745C21.0906 38.7756 21.0906 40.2369 21.9918 41.138C22.8929 42.0392 24.3542 42.0392 25.2553 41.138L30.8034 35.59C30.9836 35.4097 31.2759 35.4097 31.4561 35.59L37.0042 41.138C37.9053 42.0392 39.3666 42.0392 40.2677 41.138C41.1689 40.2369 41.1689 38.7756 40.2677 37.8745L34.7197 32.3264C34.5394 32.1462 34.5394 31.8539 34.7197 31.6737L40.2677 26.1256C41.1689 25.2245 41.1689 23.7632 40.2677 22.862Z\" fill=\"#FF606F\"/>\n </svg>\n }\n @case ('info') {\n <svg width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M0 12C0 5.37258 5.37258 0 12 0H52C58.6274 0 64 5.37258 64 12V52C64 58.6274 58.6274 64 52 64H12C5.37258 64 0 58.6274 0 52V12Z\" fill=\"#E6F0FA\"/>\n <circle cx=\"32.5283\" cy=\"32.5281\" r=\"18.989\" fill=\"white\"/>\n <path d=\"M31.9995 9.84668C44.2348 9.84668 54.1534 19.7653 54.1534 32.0005C54.1534 44.2358 44.2348 54.1544 31.9995 54.1544C19.7643 54.1544 9.8457 44.2358 9.8457 32.0005C9.8457 19.7653 19.7643 9.84668 31.9995 9.84668ZM28.3072 26.0943C26.8347 26.0945 25.8457 27.0832 25.8457 28.5558C25.8459 30.0282 26.8349 31.0171 28.3072 31.0174H31.758L28.3072 42.5835C28.0603 43.8088 28.3077 45.2811 29.0488 46.2698C29.7903 47.4952 31.0265 47.9896 32.252 47.9897H36.187C37.67 47.9896 38.6486 47.0112 38.6486 45.5282C38.6484 44.0558 37.6595 43.0668 36.187 43.0666H33.2303L36.681 31.5005C36.9281 30.2751 36.6808 28.8028 35.9395 27.8142C35.1979 26.5886 33.9619 26.0943 32.7363 26.0943H28.3072ZM33.4779 16.0005C31.4387 16.0006 29.7904 17.6488 29.7904 19.688C29.7906 21.7271 31.4388 23.3743 33.4779 23.3743C35.5171 23.3743 37.1652 21.7271 37.1654 19.688C37.1654 17.6488 35.5172 16.0005 33.4779 16.0005Z\" fill=\"#5EACFF\"/>\n </svg>\n }\n }\n </div>\n\n \n <div class=\"pecb-confirmation__content\">\n <h2 [id]=\"titleId\" class=\"pecb-confirmation__title\">{{ title() }}</h2>\n\n @if (message()) {\n <p [id]=\"messageId\" class=\"pecb-confirmation__message\">{{ message() }}</p>\n }\n </div>\n\n \n <div class=\"pecb-confirmation__actions\">\n <button\n type=\"button\"\n class=\"pecb-confirmation__btn pecb-confirmation__btn--confirm\"\n (click)=\"onConfirm()\"\n >\n {{ confirmText() }}\n </button>\n <button\n type=\"button\"\n class=\"pecb-confirmation__btn pecb-confirmation__btn--cancel\"\n (click)=\"onCancel()\"\n >\n {{ cancelText() }}\n </button>\n </div>\n </div>\n </div>\n}\n", styles: [".pecb-confirmation{position:fixed;inset:0;z-index:1050;display:flex;align-items:center;justify-content:center;background-color:#00000080;animation:fadeIn .15s ease-in-out forwards}.pecb-confirmation__panel{display:flex;width:616px;max-width:calc(100vw - 32px);padding:22px 20px 30px;flex-direction:column;align-items:center;gap:27px;background:#fff;border-radius:12px;box-shadow:0 10px 10px #0000000d;animation:scaleIn .25s ease-in-out forwards;outline:none}.pecb-confirmation__header{display:flex;justify-content:flex-end;align-items:center;width:100%;height:20px}.pecb-confirmation__close-btn{padding:0;border:none;background:none;cursor:pointer;font:inherit;color:inherit;display:flex;align-items:center;justify-content:center;width:20px;height:20px;color:#616161;border-radius:6px;outline:none;transition-property:color,background-color;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-confirmation__close-btn:focus{outline:none}.pecb-confirmation__close-btn:hover{color:#313131;background-color:#ececec}.pecb-confirmation__close-btn svg{width:10px;height:10px}.pecb-confirmation__icon{display:flex;align-items:center;justify-content:center;width:64px;height:64px;border-radius:12px;flex-shrink:0}.pecb-confirmation__icon--success{background-color:transparent}.pecb-confirmation__icon--warning{background-color:#fff4e5}.pecb-confirmation__icon--error{background-color:#fdecea}.pecb-confirmation__icon--info{background-color:#e6f0fa}.pecb-confirmation__content{display:flex;flex-direction:column;align-items:center;gap:9px;padding:0 17px;width:100%}.pecb-confirmation__title{margin:0;font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:24px;font-weight:700;line-height:1.2;color:#313131;text-align:center}.pecb-confirmation__message{margin:0;font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:16px;font-weight:400;line-height:1.6;color:#616161;text-align:center}.pecb-confirmation__actions{display:flex;align-items:center;justify-content:center;gap:16px;width:100%}.pecb-confirmation__btn{border:none;background:none;font:inherit;color:inherit;display:inline-flex;align-items:center;justify-content:center;height:38px;padding:10px 40px;border-radius:6px;font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;font-style:normal;font-weight:500;line-height:normal;cursor:pointer;white-space:nowrap;transition-property:background-color,border-color,color;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-confirmation__btn:focus{outline:none}.pecb-confirmation__btn:focus-visible{outline:2px solid #a11e29;outline-offset:2px}.pecb-confirmation__btn--confirm{background-color:#212427;color:#fff;border:1px solid #212427}.pecb-confirmation__btn--confirm:hover{background-color:#616161;border-color:#616161}.pecb-confirmation__btn--confirm:active{background-color:#000;border-color:#000}.pecb-confirmation__btn--cancel{background-color:#fff;color:#212427;border:1px solid #c2c2c2}.pecb-confirmation__btn--cancel:hover{background-color:#ececec;border-color:#616161}.pecb-confirmation__btn--cancel:active{background-color:#c2c2c2;border-color:#616161}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes scaleIn{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}@media (prefers-reduced-motion: reduce){.pecb-confirmation,.pecb-confirmation__panel{animation:none}}\n"] }]
|
|
5935
|
-
}], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], isOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "isOpen", required: false }] }, { type: i0.Output, args: ["isOpenChange"] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], confirmText: [{ type: i0.Input, args: [{ isSignal: true, alias: "confirmText", required: false }] }], cancelText: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelText", required: false }] }], iconType: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconType", required: false }] }], showCloseButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCloseButton", required: false }] }], closeAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeAriaLabel", required: false }] }], closeOnBackdropClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnBackdropClick", required: false }] }], closeOnEscape: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnEscape", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], confirmed: [{ type: i0.Output, args: ["confirmed"] }], cancelled: [{ type: i0.Output, args: ["cancelled"] }], closed: [{ type: i0.Output, args: ["closed"] }],
|
|
5936
|
-
type: ViewChild,
|
|
5937
|
-
args: ['dialogPanel']
|
|
5938
|
-
}] } });
|
|
5958
|
+
args: [{ selector: 'pecb-confirmation', imports: [CdkTrapFocus], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<ng-template #dialogTemplate>\n <div\n #dialogPanel\n [id]=\"id()\"\n class=\"pecb-confirmation__panel\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-labelledby]=\"titleId\"\n [attr.aria-describedby]=\"message() ? messageId : ariaDescribedBy() || null\"\n [attr.aria-label]=\"ariaLabel() || null\"\n tabindex=\"-1\"\n cdkTrapFocus\n >\n @if (showCloseButton()) {\n <div class=\"pecb-confirmation__header\">\n <button\n type=\"button\"\n class=\"pecb-confirmation__close-btn\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"close()\"\n >\n <svg\n width=\"10\"\n height=\"10\"\n viewBox=\"0 0 10 10\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M9 1L1 9M1 1L9 9\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n </div>\n }\n\n <div [class]=\"iconContainerClass\" aria-hidden=\"true\">\n @switch (iconType()) {\n @case ('success') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\">\n <path d=\"M0 12C0 5.37258 5.37258 0 12 0H52C58.6274 0 64 5.37258 64 12V52C64 58.6274 58.6274 64 52 64H12C5.37258 64 0 58.6274 0 52V12Z\" fill=\"#E6F4EA\"/>\n <circle cx=\"31.8243\" cy=\"31.8243\" r=\"18.2857\" fill=\"white\"/>\n <path d=\"M31.9991 9.84625C19.7635 9.84625 9.84521 19.7645 9.84521 32.0001C9.84521 44.2357 19.7635 54.1539 31.9991 54.1539C44.2347 54.1539 54.1529 44.2357 54.1529 32.0001C54.1426 19.7645 44.2244 9.84625 31.9991 9.84625ZM43.8227 26.3458L31.8137 41.3725C31.2781 42.0316 30.4954 42.4436 29.6508 42.4951C29.5993 42.4951 29.5375 42.4951 29.486 42.4951C28.693 42.4951 27.9308 42.1861 27.3747 41.6197L20.3917 34.6367C19.2279 33.4729 19.2279 31.5778 20.3917 30.414C21.5555 29.2502 23.4506 29.2502 24.6144 30.414L29.2285 35.0281L39.1571 22.6174C40.187 21.33 42.0615 21.1137 43.3592 22.1539C44.6466 23.1736 44.8526 25.0584 43.8227 26.3458Z\" fill=\"#57BC57\"/>\n </svg>\n }\n @case ('warning') {\n <svg width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M0 12C0 5.37258 5.37258 0 12 0H52C58.6274 0 64 5.37258 64 12V52C64 58.6274 58.6274 64 52 64H12C5.37258 64 0 58.6274 0 52V12Z\" fill=\"#FFF4E5\"/>\n <circle cx=\"32.5303\" cy=\"32.5281\" r=\"18.989\" fill=\"white\"/>\n <path d=\"M32.0012 9.84668C44.2362 9.84668 54.1543 19.765 54.1545 32C54.1545 44.2352 44.2364 54.1543 32.0012 54.1543C19.766 54.1543 9.8479 44.2352 9.8479 32C9.84815 19.765 19.7662 9.84668 32.0012 9.84668ZM32.0823 40.2266C30.0029 40.2266 28.3108 41.9187 28.3108 43.998C28.5007 48.9935 35.6636 48.9922 35.8528 43.998C35.8528 41.9188 34.1615 40.2266 32.0823 40.2266ZM35.7727 18.9727C33.9014 16.6513 30.1067 16.6501 28.2346 18.9727C27.2974 20.0886 26.9044 21.5514 27.1555 22.9863C27.9048 27.2673 28.9622 33.3099 29.4368 36.0234C29.9687 38.8793 34.0404 38.8785 34.5715 36.0234L36.8528 22.9863C37.1039 21.5514 36.7101 20.0887 35.7727 18.9727Z\" fill=\"#FF9C4A\"/>\n </svg>\n }\n @case ('error') {\n <svg width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M0 12C0 5.37258 5.37258 0 12 0H52C58.6274 0 64 5.37258 64 12V52C64 58.6274 58.6274 64 52 64H12C5.37258 64 0 58.6274 0 52V12Z\" fill=\"#FDECEA\"/>\n <circle cx=\"31.8243\" cy=\"31.8243\" r=\"18.2857\" fill=\"white\"/>\n <path d=\"M47.6652 16.3349C56.3168 24.9865 56.3168 39.0136 47.6652 47.6652C39.0136 56.3168 24.9865 56.3168 16.3349 47.6652C7.68329 39.0136 7.68329 24.9865 16.3349 16.3349C24.9865 7.68329 39.0136 7.68329 47.6652 16.3349ZM40.2677 22.862C39.3666 21.9609 37.9053 21.9609 37.0042 22.862L31.4561 28.4101C31.2759 28.5903 30.9836 28.5903 30.8034 28.4101L25.2553 22.862C24.3542 21.9609 22.8929 21.9609 21.9918 22.862C21.0906 23.7632 21.0906 25.2245 21.9918 26.1256L27.5398 31.6737C27.7201 31.8539 27.7201 32.1462 27.5398 32.3264L21.9918 37.8745C21.0906 38.7756 21.0906 40.2369 21.9918 41.138C22.8929 42.0392 24.3542 42.0392 25.2553 41.138L30.8034 35.59C30.9836 35.4097 31.2759 35.4097 31.4561 35.59L37.0042 41.138C37.9053 42.0392 39.3666 42.0392 40.2677 41.138C41.1689 40.2369 41.1689 38.7756 40.2677 37.8745L34.7197 32.3264C34.5394 32.1462 34.5394 31.8539 34.7197 31.6737L40.2677 26.1256C41.1689 25.2245 41.1689 23.7632 40.2677 22.862Z\" fill=\"#FF606F\"/>\n </svg>\n }\n @case ('info') {\n <svg width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d=\"M0 12C0 5.37258 5.37258 0 12 0H52C58.6274 0 64 5.37258 64 12V52C64 58.6274 58.6274 64 52 64H12C5.37258 64 0 58.6274 0 52V12Z\" fill=\"#E6F0FA\"/>\n <circle cx=\"32.5283\" cy=\"32.5281\" r=\"18.989\" fill=\"white\"/>\n <path d=\"M31.9995 9.84668C44.2348 9.84668 54.1534 19.7653 54.1534 32.0005C54.1534 44.2358 44.2348 54.1544 31.9995 54.1544C19.7643 54.1544 9.8457 44.2358 9.8457 32.0005C9.8457 19.7653 19.7643 9.84668 31.9995 9.84668ZM28.3072 26.0943C26.8347 26.0945 25.8457 27.0832 25.8457 28.5558C25.8459 30.0282 26.8349 31.0171 28.3072 31.0174H31.758L28.3072 42.5835C28.0603 43.8088 28.3077 45.2811 29.0488 46.2698C29.7903 47.4952 31.0265 47.9896 32.252 47.9897H36.187C37.67 47.9896 38.6486 47.0112 38.6486 45.5282C38.6484 44.0558 37.6595 43.0668 36.187 43.0666H33.2303L36.681 31.5005C36.9281 30.2751 36.6808 28.8028 35.9395 27.8142C35.1979 26.5886 33.9619 26.0943 32.7363 26.0943H28.3072ZM33.4779 16.0005C31.4387 16.0006 29.7904 17.6488 29.7904 19.688C29.7906 21.7271 31.4388 23.3743 33.4779 23.3743C35.5171 23.3743 37.1652 21.7271 37.1654 19.688C37.1654 17.6488 35.5172 16.0005 33.4779 16.0005Z\" fill=\"#5EACFF\"/>\n </svg>\n }\n }\n </div>\n\n <div class=\"pecb-confirmation__content\">\n <h2 [id]=\"titleId\" class=\"pecb-confirmation__title\">{{ title() }}</h2>\n\n @if (message()) {\n <p [id]=\"messageId\" class=\"pecb-confirmation__message\">{{ message() }}</p>\n }\n </div>\n\n <div class=\"pecb-confirmation__actions\">\n <button\n type=\"button\"\n class=\"pecb-confirmation__btn pecb-confirmation__btn--confirm\"\n (click)=\"onConfirm()\"\n >\n {{ confirmText() }}\n </button>\n <button\n type=\"button\"\n class=\"pecb-confirmation__btn pecb-confirmation__btn--cancel\"\n (click)=\"onCancel()\"\n >\n {{ cancelText() }}\n </button>\n </div>\n </div>\n</ng-template>\n", styles: [".pecb-confirmation__backdrop{background-color:#00000080}.pecb-confirmation__panel{display:flex;width:616px;max-width:calc(100vw - 32px);padding:22px 20px 30px;flex-direction:column;align-items:center;gap:27px;background:#fff;border-radius:12px;box-shadow:0 10px 10px #0000000d;animation:scaleIn .25s ease-in-out forwards;outline:none}.pecb-confirmation__header{display:flex;justify-content:flex-end;align-items:center;width:100%;height:20px}.pecb-confirmation__close-btn{padding:0;border:none;background:none;cursor:pointer;font:inherit;color:inherit;display:flex;align-items:center;justify-content:center;width:20px;height:20px;color:#616161;border-radius:6px;outline:none;transition-property:color,background-color;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-confirmation__close-btn:focus{outline:none}.pecb-confirmation__close-btn:hover{color:#313131;background-color:#ececec}.pecb-confirmation__close-btn svg{width:10px;height:10px}.pecb-confirmation__icon{display:flex;align-items:center;justify-content:center;width:64px;height:64px;border-radius:12px;flex-shrink:0}.pecb-confirmation__icon--success{background-color:transparent}.pecb-confirmation__icon--warning{background-color:#fff4e5}.pecb-confirmation__icon--error{background-color:#fdecea}.pecb-confirmation__icon--info{background-color:#e6f0fa}.pecb-confirmation__content{display:flex;flex-direction:column;align-items:center;gap:9px;padding:0 17px;width:100%}.pecb-confirmation__title{margin:0;font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:24px;font-weight:700;line-height:1.2;color:#313131;text-align:center}.pecb-confirmation__message{margin:0;font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:16px;font-weight:400;line-height:1.6;color:#616161;text-align:center}.pecb-confirmation__actions{display:flex;align-items:center;justify-content:center;gap:16px;width:100%}.pecb-confirmation__btn{border:none;background:none;font:inherit;color:inherit;display:inline-flex;align-items:center;justify-content:center;height:38px;padding:10px 40px;border-radius:6px;font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;font-style:normal;font-weight:500;line-height:normal;cursor:pointer;white-space:nowrap;transition-property:background-color,border-color,color;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-confirmation__btn:focus{outline:none}.pecb-confirmation__btn:focus-visible{outline:2px solid #a11e29;outline-offset:2px}.pecb-confirmation__btn--confirm{background-color:#212427;color:#fff;border:1px solid #212427}.pecb-confirmation__btn--confirm:hover{background-color:#616161;border-color:#616161}.pecb-confirmation__btn--confirm:active{background-color:#000;border-color:#000}.pecb-confirmation__btn--cancel{background-color:#fff;color:#212427;border:1px solid #c2c2c2}.pecb-confirmation__btn--cancel:hover{background-color:#ececec;border-color:#616161}.pecb-confirmation__btn--cancel:active{background-color:#c2c2c2;border-color:#616161}@keyframes scaleIn{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}@media (prefers-reduced-motion: reduce){.pecb-confirmation__panel{animation:none}}\n"] }]
|
|
5959
|
+
}], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], isOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "isOpen", required: false }] }, { type: i0.Output, args: ["isOpenChange"] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], confirmText: [{ type: i0.Input, args: [{ isSignal: true, alias: "confirmText", required: false }] }], cancelText: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelText", required: false }] }], iconType: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconType", required: false }] }], showCloseButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCloseButton", required: false }] }], closeAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeAriaLabel", required: false }] }], closeOnBackdropClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnBackdropClick", required: false }] }], closeOnEscape: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnEscape", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], ariaDescribedBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaDescribedBy", required: false }] }], confirmed: [{ type: i0.Output, args: ["confirmed"] }], cancelled: [{ type: i0.Output, args: ["cancelled"] }], closed: [{ type: i0.Output, args: ["closed"] }], dialogTemplate: [{ type: i0.ViewChild, args: ['dialogTemplate', { isSignal: true }] }] } });
|
|
5939
5960
|
|
|
5940
5961
|
class AlertComponent {
|
|
5941
5962
|
// Duration of the leave animation; keep in sync with $transition-base (250ms)
|
|
@@ -6328,6 +6349,16 @@ class TooltipComponent {
|
|
|
6328
6349
|
this.primaryCta = input(undefined, ...(ngDevMode ? [{ debugName: "primaryCta" }] : []));
|
|
6329
6350
|
this.secondaryCta = input(undefined, ...(ngDevMode ? [{ debugName: "secondaryCta" }] : []));
|
|
6330
6351
|
this.showClose = input(true, ...(ngDevMode ? [{ debugName: "showClose" }] : []));
|
|
6352
|
+
/**
|
|
6353
|
+
* Optional explicit width of the tooltip box (any CSS length, e.g. '330px',
|
|
6354
|
+
* '20rem'). Leave undefined to size to content.
|
|
6355
|
+
*/
|
|
6356
|
+
this.width = input(undefined, ...(ngDevMode ? [{ debugName: "width" }] : []));
|
|
6357
|
+
/**
|
|
6358
|
+
* Optional explicit height of the tooltip box (any CSS length, e.g. '120px').
|
|
6359
|
+
* Leave undefined to size to content.
|
|
6360
|
+
*/
|
|
6361
|
+
this.height = input(undefined, ...(ngDevMode ? [{ debugName: "height" }] : []));
|
|
6331
6362
|
/**
|
|
6332
6363
|
* Whether the tooltip is visible. Manages its own state internally —
|
|
6333
6364
|
* no consumer binding required. Supports [(visible)] for controlled mode.
|
|
@@ -6366,12 +6397,12 @@ class TooltipComponent {
|
|
|
6366
6397
|
].join(' ');
|
|
6367
6398
|
}
|
|
6368
6399
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: TooltipComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6369
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: TooltipComponent, isStandalone: true, selector: "pecb-tooltip", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, pointerPosition: { classPropertyName: "pointerPosition", publicName: "pointerPosition", isSignal: true, isRequired: false, transformFunction: null }, theme: { classPropertyName: "theme", publicName: "theme", isSignal: true, isRequired: false, transformFunction: null }, primaryCta: { classPropertyName: "primaryCta", publicName: "primaryCta", isSignal: true, isRequired: false, transformFunction: null }, secondaryCta: { classPropertyName: "secondaryCta", publicName: "secondaryCta", isSignal: true, isRequired: false, transformFunction: null }, showClose: { classPropertyName: "showClose", publicName: "showClose", isSignal: true, isRequired: false, transformFunction: null }, visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null }, closeAriaLabel: { classPropertyName: "closeAriaLabel", publicName: "closeAriaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { visible: "visibleChange", closed: "closed", primaryCtaClick: "primaryCtaClick", secondaryCtaClick: "secondaryCtaClick" }, ngImport: i0, template: "@if (visible()) {\n <div [class]=\"hostClasses\">\n\n <!-- \u2500\u2500 Pointer TOP (above container) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (pointerSide === 'top') {\n <div class=\"pecb-tooltip__pointer pecb-tooltip__pointer--top\">\n <svg width=\"20\" height=\"12\" viewBox=\"0 0 20 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path [attr.fill]=\"ptrBorderFill\" d=\"M0 12L10 0L20 12Z\"/>\n <path [attr.fill]=\"ptrBodyFill\" d=\"M1.8 12L10 1.8L18.2 12Z\"/>\n </svg>\n </div>\n }\n\n <!-- \u2500\u2500 Pointer LEFT (left of container) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (pointerSide === 'left') {\n <div class=\"pecb-tooltip__pointer pecb-tooltip__pointer--left\">\n <svg width=\"12\" height=\"20\" viewBox=\"0 0 12 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path [attr.fill]=\"ptrBorderFill\" d=\"M12 0L0 10L12 20Z\"/>\n <path [attr.fill]=\"ptrBodyFill\" d=\"M12 1.8L1.8 10L12 18.2Z\"/>\n </svg>\n </div>\n }\n\n <!-- \u2500\u2500 Main container \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"pecb-tooltip__container\">\n <div class=\"pecb-tooltip__body\">\n\n <div class=\"pecb-tooltip__content\">\n <div class=\"pecb-tooltip__texts\">\n @if (title()) {\n <p class=\"pecb-tooltip__title\">{{ title() }}</p>\n }\n <p class=\"pecb-tooltip__message\">{{ message() }}</p>\n </div>\n\n @if (showClose()) {\n <button\n type=\"button\"\n class=\"pecb-tooltip__close\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"close()\"\n >\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path class=\"pecb-tooltip__close-icon\" d=\"M15 5L5 15M5 5L15 15\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </button>\n }\n </div>\n\n @if (hasButtons) {\n <div class=\"pecb-tooltip__buttons\">\n @if (secondaryCta()) {\n <button type=\"button\" class=\"pecb-tooltip__btn pecb-tooltip__btn--secondary\" (click)=\"secondaryCtaClick.emit()\">{{ secondaryCta() }}</button>\n }\n @if (primaryCta()) {\n <button type=\"button\" class=\"pecb-tooltip__btn pecb-tooltip__btn--primary\" (click)=\"primaryCtaClick.emit()\">{{ primaryCta() }}</button>\n }\n </div>\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 Pointer RIGHT (right of container) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (pointerSide === 'right') {\n <div class=\"pecb-tooltip__pointer pecb-tooltip__pointer--right\">\n <svg width=\"12\" height=\"20\" viewBox=\"0 0 12 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path [attr.fill]=\"ptrBorderFill\" d=\"M0 0L12 10L0 20Z\"/>\n <path [attr.fill]=\"ptrBodyFill\" d=\"M0 1.8L10.2 10L0 18.2Z\"/>\n </svg>\n </div>\n }\n\n <!-- \u2500\u2500 Pointer BOTTOM (below container) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (pointerSide === 'bottom') {\n <div class=\"pecb-tooltip__pointer pecb-tooltip__pointer--bottom\">\n <svg width=\"20\" height=\"12\" viewBox=\"0 0 20 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path [attr.fill]=\"ptrBorderFill\" d=\"M0 0L10 12L20 0Z\"/>\n <path [attr.fill]=\"ptrBodyFill\" d=\"M1.8 0L10 10.2L18.2 0Z\"/>\n </svg>\n </div>\n }\n\n </div>\n}\n", styles: [".pecb-tooltip{display:inline-flex;flex-direction:column;width:330px}.pecb-tooltip--left-top,.pecb-tooltip--left-center,.pecb-tooltip--left-bottom,.pecb-tooltip--right-top,.pecb-tooltip--right-center,.pecb-tooltip--right-bottom{flex-direction:row;align-items:stretch;width:auto}.pecb-tooltip--left-top>.pecb-tooltip__container,.pecb-tooltip--left-center>.pecb-tooltip__container,.pecb-tooltip--left-bottom>.pecb-tooltip__container,.pecb-tooltip--right-top>.pecb-tooltip__container,.pecb-tooltip--right-center>.pecb-tooltip__container,.pecb-tooltip--right-bottom>.pecb-tooltip__container{width:330px}.pecb-tooltip__pointer--top{display:flex;align-items:flex-end;padding:0 16px;position:relative;z-index:2;margin-bottom:-1px}.pecb-tooltip--top-left>.pecb-tooltip__pointer--top{justify-content:flex-start}.pecb-tooltip--top-center>.pecb-tooltip__pointer--top{justify-content:center}.pecb-tooltip--top-right>.pecb-tooltip__pointer--top{justify-content:flex-end}.pecb-tooltip__pointer--bottom{display:flex;align-items:flex-start;padding:0 16px;position:relative;z-index:2;margin-top:-1px}.pecb-tooltip--bottom-left>.pecb-tooltip__pointer--bottom{justify-content:flex-start}.pecb-tooltip--bottom-center>.pecb-tooltip__pointer--bottom{justify-content:center}.pecb-tooltip--bottom-right>.pecb-tooltip__pointer--bottom{justify-content:flex-end}.pecb-tooltip__container{display:flex;flex-direction:row;align-items:stretch;border-radius:8px;position:relative;z-index:1}.pecb-tooltip--dark .pecb-tooltip__container{background-color:#313131}.pecb-tooltip--light .pecb-tooltip__container{background-color:#fff;border:1px solid #ececec}.pecb-tooltip__pointer--left{display:flex;flex-direction:column;align-items:flex-end;align-self:stretch;position:relative;z-index:2;margin-right:-1px}.pecb-tooltip--left-top>.pecb-tooltip__pointer--left{justify-content:flex-start;padding-top:16px}.pecb-tooltip--left-center>.pecb-tooltip__pointer--left{justify-content:center}.pecb-tooltip--left-bottom>.pecb-tooltip__pointer--left{justify-content:flex-end;padding-bottom:16px}.pecb-tooltip__pointer--right{display:flex;flex-direction:column;align-items:flex-start;align-self:stretch;position:relative;z-index:2;margin-left:-1px}.pecb-tooltip--right-top>.pecb-tooltip__pointer--right{justify-content:flex-start;padding-top:16px}.pecb-tooltip--right-center>.pecb-tooltip__pointer--right{justify-content:center}.pecb-tooltip--right-bottom>.pecb-tooltip__pointer--right{justify-content:flex-end;padding-bottom:16px}.pecb-tooltip__body{flex:1;display:flex;flex-direction:column;gap:16px;padding:16px}.pecb-tooltip__content{display:flex;gap:8px;align-items:flex-start;width:100%}.pecb-tooltip__texts{flex:1;display:flex;flex-direction:column;gap:4px}.pecb-tooltip__title{font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;font-style:normal;font-weight:700;line-height:normal;margin:0}.pecb-tooltip--dark .pecb-tooltip__title{color:#fff}.pecb-tooltip--light .pecb-tooltip__title{color:#212427}.pecb-tooltip__message{font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.5;margin:0}.pecb-tooltip--dark .pecb-tooltip__message{color:#ececec}.pecb-tooltip--light .pecb-tooltip__message{color:#919191}.pecb-tooltip__close{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:20px;height:20px;padding:0;background:none;border:none;cursor:pointer;transition:opacity .15s ease-in-out}.pecb-tooltip__close:hover{opacity:.7}.pecb-tooltip__close:focus-visible{outline:2px solid #a11e29;outline-offset:2px;border-radius:2px}.pecb-tooltip__close-icon{stroke:#ececec}.pecb-tooltip--light .pecb-tooltip__close-icon{stroke:#212427}.pecb-tooltip__buttons{display:flex;align-items:center;gap:24px}.pecb-tooltip__btn{display:inline-flex;align-items:center;justify-content:center;padding:0;background:none;border:none;cursor:pointer;font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;font-style:normal;font-weight:500;line-height:normal;transition:opacity .15s ease-in-out}.pecb-tooltip__btn:hover{opacity:.8}.pecb-tooltip__btn:focus-visible{outline:2px solid #a11e29;outline-offset:2px;border-radius:2px}.pecb-tooltip__btn--primary{color:#a11e29}.pecb-tooltip__btn--secondary{color:#919191}.pecb-tooltip--light .pecb-tooltip__btn--secondary{color:#212427}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
6400
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: TooltipComponent, isStandalone: true, selector: "pecb-tooltip", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, pointerPosition: { classPropertyName: "pointerPosition", publicName: "pointerPosition", isSignal: true, isRequired: false, transformFunction: null }, theme: { classPropertyName: "theme", publicName: "theme", isSignal: true, isRequired: false, transformFunction: null }, primaryCta: { classPropertyName: "primaryCta", publicName: "primaryCta", isSignal: true, isRequired: false, transformFunction: null }, secondaryCta: { classPropertyName: "secondaryCta", publicName: "secondaryCta", isSignal: true, isRequired: false, transformFunction: null }, showClose: { classPropertyName: "showClose", publicName: "showClose", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: false, transformFunction: null }, closeAriaLabel: { classPropertyName: "closeAriaLabel", publicName: "closeAriaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { visible: "visibleChange", closed: "closed", primaryCtaClick: "primaryCtaClick", secondaryCtaClick: "secondaryCtaClick" }, ngImport: i0, template: "@if (visible()) {\n <div [class]=\"hostClasses\">\n\n <!-- \u2500\u2500 Pointer TOP (above container) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (pointerSide === 'top') {\n <div class=\"pecb-tooltip__pointer pecb-tooltip__pointer--top\">\n <svg width=\"20\" height=\"12\" viewBox=\"0 0 20 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path [attr.fill]=\"ptrBorderFill\" d=\"M0 12L10 0L20 12Z\"/>\n <path [attr.fill]=\"ptrBodyFill\" d=\"M1.8 12L10 1.8L18.2 12Z\"/>\n </svg>\n </div>\n }\n\n <!-- \u2500\u2500 Pointer LEFT (left of container) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (pointerSide === 'left') {\n <div class=\"pecb-tooltip__pointer pecb-tooltip__pointer--left\">\n <svg width=\"12\" height=\"20\" viewBox=\"0 0 12 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path [attr.fill]=\"ptrBorderFill\" d=\"M12 0L0 10L12 20Z\"/>\n <path [attr.fill]=\"ptrBodyFill\" d=\"M12 1.8L1.8 10L12 18.2Z\"/>\n </svg>\n </div>\n }\n\n <!-- \u2500\u2500 Main container \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"pecb-tooltip__container\" [style.width]=\"width()\" [style.height]=\"height()\">\n <div class=\"pecb-tooltip__body\">\n\n <div class=\"pecb-tooltip__content\">\n <div class=\"pecb-tooltip__texts\">\n @if (title()) {\n <p class=\"pecb-tooltip__title\">{{ title() }}</p>\n }\n <p class=\"pecb-tooltip__message\">{{ message() }}</p>\n </div>\n\n @if (showClose()) {\n <button\n type=\"button\"\n class=\"pecb-tooltip__close\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"close()\"\n >\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path class=\"pecb-tooltip__close-icon\" d=\"M15 5L5 15M5 5L15 15\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </button>\n }\n </div>\n\n @if (hasButtons) {\n <div class=\"pecb-tooltip__buttons\">\n @if (secondaryCta()) {\n <button type=\"button\" class=\"pecb-tooltip__btn pecb-tooltip__btn--secondary\" (click)=\"secondaryCtaClick.emit()\">{{ secondaryCta() }}</button>\n }\n @if (primaryCta()) {\n <button type=\"button\" class=\"pecb-tooltip__btn pecb-tooltip__btn--primary\" (click)=\"primaryCtaClick.emit()\">{{ primaryCta() }}</button>\n }\n </div>\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 Pointer RIGHT (right of container) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (pointerSide === 'right') {\n <div class=\"pecb-tooltip__pointer pecb-tooltip__pointer--right\">\n <svg width=\"12\" height=\"20\" viewBox=\"0 0 12 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path [attr.fill]=\"ptrBorderFill\" d=\"M0 0L12 10L0 20Z\"/>\n <path [attr.fill]=\"ptrBodyFill\" d=\"M0 1.8L10.2 10L0 18.2Z\"/>\n </svg>\n </div>\n }\n\n <!-- \u2500\u2500 Pointer BOTTOM (below container) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (pointerSide === 'bottom') {\n <div class=\"pecb-tooltip__pointer pecb-tooltip__pointer--bottom\">\n <svg width=\"20\" height=\"12\" viewBox=\"0 0 20 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path [attr.fill]=\"ptrBorderFill\" d=\"M0 0L10 12L20 0Z\"/>\n <path [attr.fill]=\"ptrBodyFill\" d=\"M1.8 0L10 10.2L18.2 0Z\"/>\n </svg>\n </div>\n }\n\n </div>\n}\n", styles: [".pecb-tooltip{display:inline-flex;flex-direction:column;width:auto;height:auto}.pecb-tooltip--left-top,.pecb-tooltip--left-center,.pecb-tooltip--left-bottom,.pecb-tooltip--right-top,.pecb-tooltip--right-center,.pecb-tooltip--right-bottom{flex-direction:row;align-items:stretch;width:auto}.pecb-tooltip--left-top>.pecb-tooltip__container,.pecb-tooltip--left-center>.pecb-tooltip__container,.pecb-tooltip--left-bottom>.pecb-tooltip__container,.pecb-tooltip--right-top>.pecb-tooltip__container,.pecb-tooltip--right-center>.pecb-tooltip__container,.pecb-tooltip--right-bottom>.pecb-tooltip__container{width:auto}.pecb-tooltip__pointer--top{display:flex;align-items:flex-end;padding:0 16px;position:relative;z-index:2;margin-bottom:-1px}.pecb-tooltip--top-left>.pecb-tooltip__pointer--top{justify-content:flex-start}.pecb-tooltip--top-center>.pecb-tooltip__pointer--top{justify-content:center}.pecb-tooltip--top-right>.pecb-tooltip__pointer--top{justify-content:flex-end}.pecb-tooltip__pointer--bottom{display:flex;align-items:flex-start;padding:0 16px;position:relative;z-index:2;margin-top:-1px}.pecb-tooltip--bottom-left>.pecb-tooltip__pointer--bottom{justify-content:flex-start}.pecb-tooltip--bottom-center>.pecb-tooltip__pointer--bottom{justify-content:center}.pecb-tooltip--bottom-right>.pecb-tooltip__pointer--bottom{justify-content:flex-end}.pecb-tooltip__container{display:flex;flex-direction:row;align-items:stretch;border-radius:8px;position:relative;z-index:1}.pecb-tooltip--dark .pecb-tooltip__container{background-color:#313131}.pecb-tooltip--light .pecb-tooltip__container{background-color:#fff;border:1px solid #ececec}.pecb-tooltip__pointer--left{display:flex;flex-direction:column;align-items:flex-end;align-self:stretch;position:relative;z-index:2;margin-right:-1px}.pecb-tooltip--left-top>.pecb-tooltip__pointer--left{justify-content:flex-start;padding-top:16px}.pecb-tooltip--left-center>.pecb-tooltip__pointer--left{justify-content:center}.pecb-tooltip--left-bottom>.pecb-tooltip__pointer--left{justify-content:flex-end;padding-bottom:16px}.pecb-tooltip__pointer--right{display:flex;flex-direction:column;align-items:flex-start;align-self:stretch;position:relative;z-index:2;margin-left:-1px}.pecb-tooltip--right-top>.pecb-tooltip__pointer--right{justify-content:flex-start;padding-top:16px}.pecb-tooltip--right-center>.pecb-tooltip__pointer--right{justify-content:center}.pecb-tooltip--right-bottom>.pecb-tooltip__pointer--right{justify-content:flex-end;padding-bottom:16px}.pecb-tooltip__body{flex:1;display:flex;flex-direction:column;gap:16px;padding:16px}.pecb-tooltip__content{display:flex;gap:8px;align-items:flex-start;width:100%}.pecb-tooltip__texts{flex:1;display:flex;flex-direction:column;gap:4px}.pecb-tooltip__title{font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;font-style:normal;font-weight:700;line-height:normal;margin:0}.pecb-tooltip--dark .pecb-tooltip__title{color:#fff}.pecb-tooltip--light .pecb-tooltip__title{color:#212427}.pecb-tooltip__message{font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.5;margin:0}.pecb-tooltip--dark .pecb-tooltip__message{color:#ececec}.pecb-tooltip--light .pecb-tooltip__message{color:#919191}.pecb-tooltip__close{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:20px;height:20px;padding:0;background:none;border:none;cursor:pointer;transition:opacity .15s ease-in-out}.pecb-tooltip__close:hover{opacity:.7}.pecb-tooltip__close:focus-visible{outline:2px solid #a11e29;outline-offset:2px;border-radius:2px}.pecb-tooltip__close-icon{stroke:#ececec}.pecb-tooltip--light .pecb-tooltip__close-icon{stroke:#212427}.pecb-tooltip__buttons{display:flex;align-items:center;gap:24px}.pecb-tooltip__btn{display:inline-flex;align-items:center;justify-content:center;padding:0;background:none;border:none;cursor:pointer;font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;font-style:normal;font-weight:500;line-height:normal;transition:opacity .15s ease-in-out}.pecb-tooltip__btn:hover{opacity:.8}.pecb-tooltip__btn:focus-visible{outline:2px solid #a11e29;outline-offset:2px;border-radius:2px}.pecb-tooltip__btn--primary{color:#a11e29}.pecb-tooltip__btn--secondary{color:#919191}.pecb-tooltip--light .pecb-tooltip__btn--secondary{color:#212427}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
6370
6401
|
}
|
|
6371
6402
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: TooltipComponent, decorators: [{
|
|
6372
6403
|
type: Component,
|
|
6373
|
-
args: [{ selector: 'pecb-tooltip', imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (visible()) {\n <div [class]=\"hostClasses\">\n\n <!-- \u2500\u2500 Pointer TOP (above container) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (pointerSide === 'top') {\n <div class=\"pecb-tooltip__pointer pecb-tooltip__pointer--top\">\n <svg width=\"20\" height=\"12\" viewBox=\"0 0 20 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path [attr.fill]=\"ptrBorderFill\" d=\"M0 12L10 0L20 12Z\"/>\n <path [attr.fill]=\"ptrBodyFill\" d=\"M1.8 12L10 1.8L18.2 12Z\"/>\n </svg>\n </div>\n }\n\n <!-- \u2500\u2500 Pointer LEFT (left of container) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (pointerSide === 'left') {\n <div class=\"pecb-tooltip__pointer pecb-tooltip__pointer--left\">\n <svg width=\"12\" height=\"20\" viewBox=\"0 0 12 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path [attr.fill]=\"ptrBorderFill\" d=\"M12 0L0 10L12 20Z\"/>\n <path [attr.fill]=\"ptrBodyFill\" d=\"M12 1.8L1.8 10L12 18.2Z\"/>\n </svg>\n </div>\n }\n\n <!-- \u2500\u2500 Main container \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"pecb-tooltip__container\">\n <div class=\"pecb-tooltip__body\">\n\n <div class=\"pecb-tooltip__content\">\n <div class=\"pecb-tooltip__texts\">\n @if (title()) {\n <p class=\"pecb-tooltip__title\">{{ title() }}</p>\n }\n <p class=\"pecb-tooltip__message\">{{ message() }}</p>\n </div>\n\n @if (showClose()) {\n <button\n type=\"button\"\n class=\"pecb-tooltip__close\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"close()\"\n >\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path class=\"pecb-tooltip__close-icon\" d=\"M15 5L5 15M5 5L15 15\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </button>\n }\n </div>\n\n @if (hasButtons) {\n <div class=\"pecb-tooltip__buttons\">\n @if (secondaryCta()) {\n <button type=\"button\" class=\"pecb-tooltip__btn pecb-tooltip__btn--secondary\" (click)=\"secondaryCtaClick.emit()\">{{ secondaryCta() }}</button>\n }\n @if (primaryCta()) {\n <button type=\"button\" class=\"pecb-tooltip__btn pecb-tooltip__btn--primary\" (click)=\"primaryCtaClick.emit()\">{{ primaryCta() }}</button>\n }\n </div>\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 Pointer RIGHT (right of container) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (pointerSide === 'right') {\n <div class=\"pecb-tooltip__pointer pecb-tooltip__pointer--right\">\n <svg width=\"12\" height=\"20\" viewBox=\"0 0 12 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path [attr.fill]=\"ptrBorderFill\" d=\"M0 0L12 10L0 20Z\"/>\n <path [attr.fill]=\"ptrBodyFill\" d=\"M0 1.8L10.2 10L0 18.2Z\"/>\n </svg>\n </div>\n }\n\n <!-- \u2500\u2500 Pointer BOTTOM (below container) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (pointerSide === 'bottom') {\n <div class=\"pecb-tooltip__pointer pecb-tooltip__pointer--bottom\">\n <svg width=\"20\" height=\"12\" viewBox=\"0 0 20 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path [attr.fill]=\"ptrBorderFill\" d=\"M0 0L10 12L20 0Z\"/>\n <path [attr.fill]=\"ptrBodyFill\" d=\"M1.8 0L10 10.2L18.2 0Z\"/>\n </svg>\n </div>\n }\n\n </div>\n}\n", styles: [".pecb-tooltip{display:inline-flex;flex-direction:column;width:
|
|
6374
|
-
}], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], pointerPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "pointerPosition", required: false }] }], theme: [{ type: i0.Input, args: [{ isSignal: true, alias: "theme", required: false }] }], primaryCta: [{ type: i0.Input, args: [{ isSignal: true, alias: "primaryCta", required: false }] }], secondaryCta: [{ type: i0.Input, args: [{ isSignal: true, alias: "secondaryCta", required: false }] }], showClose: [{ type: i0.Input, args: [{ isSignal: true, alias: "showClose", required: false }] }], visible: [{ type: i0.Input, args: [{ isSignal: true, alias: "visible", required: false }] }, { type: i0.Output, args: ["visibleChange"] }], closeAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeAriaLabel", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], primaryCtaClick: [{ type: i0.Output, args: ["primaryCtaClick"] }], secondaryCtaClick: [{ type: i0.Output, args: ["secondaryCtaClick"] }] } });
|
|
6404
|
+
args: [{ selector: 'pecb-tooltip', imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (visible()) {\n <div [class]=\"hostClasses\">\n\n <!-- \u2500\u2500 Pointer TOP (above container) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (pointerSide === 'top') {\n <div class=\"pecb-tooltip__pointer pecb-tooltip__pointer--top\">\n <svg width=\"20\" height=\"12\" viewBox=\"0 0 20 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path [attr.fill]=\"ptrBorderFill\" d=\"M0 12L10 0L20 12Z\"/>\n <path [attr.fill]=\"ptrBodyFill\" d=\"M1.8 12L10 1.8L18.2 12Z\"/>\n </svg>\n </div>\n }\n\n <!-- \u2500\u2500 Pointer LEFT (left of container) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (pointerSide === 'left') {\n <div class=\"pecb-tooltip__pointer pecb-tooltip__pointer--left\">\n <svg width=\"12\" height=\"20\" viewBox=\"0 0 12 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path [attr.fill]=\"ptrBorderFill\" d=\"M12 0L0 10L12 20Z\"/>\n <path [attr.fill]=\"ptrBodyFill\" d=\"M12 1.8L1.8 10L12 18.2Z\"/>\n </svg>\n </div>\n }\n\n <!-- \u2500\u2500 Main container \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"pecb-tooltip__container\" [style.width]=\"width()\" [style.height]=\"height()\">\n <div class=\"pecb-tooltip__body\">\n\n <div class=\"pecb-tooltip__content\">\n <div class=\"pecb-tooltip__texts\">\n @if (title()) {\n <p class=\"pecb-tooltip__title\">{{ title() }}</p>\n }\n <p class=\"pecb-tooltip__message\">{{ message() }}</p>\n </div>\n\n @if (showClose()) {\n <button\n type=\"button\"\n class=\"pecb-tooltip__close\"\n [attr.aria-label]=\"closeAriaLabel()\"\n (click)=\"close()\"\n >\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path class=\"pecb-tooltip__close-icon\" d=\"M15 5L5 15M5 5L15 15\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </button>\n }\n </div>\n\n @if (hasButtons) {\n <div class=\"pecb-tooltip__buttons\">\n @if (secondaryCta()) {\n <button type=\"button\" class=\"pecb-tooltip__btn pecb-tooltip__btn--secondary\" (click)=\"secondaryCtaClick.emit()\">{{ secondaryCta() }}</button>\n }\n @if (primaryCta()) {\n <button type=\"button\" class=\"pecb-tooltip__btn pecb-tooltip__btn--primary\" (click)=\"primaryCtaClick.emit()\">{{ primaryCta() }}</button>\n }\n </div>\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 Pointer RIGHT (right of container) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (pointerSide === 'right') {\n <div class=\"pecb-tooltip__pointer pecb-tooltip__pointer--right\">\n <svg width=\"12\" height=\"20\" viewBox=\"0 0 12 20\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path [attr.fill]=\"ptrBorderFill\" d=\"M0 0L12 10L0 20Z\"/>\n <path [attr.fill]=\"ptrBodyFill\" d=\"M0 1.8L10.2 10L0 18.2Z\"/>\n </svg>\n </div>\n }\n\n <!-- \u2500\u2500 Pointer BOTTOM (below container) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (pointerSide === 'bottom') {\n <div class=\"pecb-tooltip__pointer pecb-tooltip__pointer--bottom\">\n <svg width=\"20\" height=\"12\" viewBox=\"0 0 20 12\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path [attr.fill]=\"ptrBorderFill\" d=\"M0 0L10 12L20 0Z\"/>\n <path [attr.fill]=\"ptrBodyFill\" d=\"M1.8 0L10 10.2L18.2 0Z\"/>\n </svg>\n </div>\n }\n\n </div>\n}\n", styles: [".pecb-tooltip{display:inline-flex;flex-direction:column;width:auto;height:auto}.pecb-tooltip--left-top,.pecb-tooltip--left-center,.pecb-tooltip--left-bottom,.pecb-tooltip--right-top,.pecb-tooltip--right-center,.pecb-tooltip--right-bottom{flex-direction:row;align-items:stretch;width:auto}.pecb-tooltip--left-top>.pecb-tooltip__container,.pecb-tooltip--left-center>.pecb-tooltip__container,.pecb-tooltip--left-bottom>.pecb-tooltip__container,.pecb-tooltip--right-top>.pecb-tooltip__container,.pecb-tooltip--right-center>.pecb-tooltip__container,.pecb-tooltip--right-bottom>.pecb-tooltip__container{width:auto}.pecb-tooltip__pointer--top{display:flex;align-items:flex-end;padding:0 16px;position:relative;z-index:2;margin-bottom:-1px}.pecb-tooltip--top-left>.pecb-tooltip__pointer--top{justify-content:flex-start}.pecb-tooltip--top-center>.pecb-tooltip__pointer--top{justify-content:center}.pecb-tooltip--top-right>.pecb-tooltip__pointer--top{justify-content:flex-end}.pecb-tooltip__pointer--bottom{display:flex;align-items:flex-start;padding:0 16px;position:relative;z-index:2;margin-top:-1px}.pecb-tooltip--bottom-left>.pecb-tooltip__pointer--bottom{justify-content:flex-start}.pecb-tooltip--bottom-center>.pecb-tooltip__pointer--bottom{justify-content:center}.pecb-tooltip--bottom-right>.pecb-tooltip__pointer--bottom{justify-content:flex-end}.pecb-tooltip__container{display:flex;flex-direction:row;align-items:stretch;border-radius:8px;position:relative;z-index:1}.pecb-tooltip--dark .pecb-tooltip__container{background-color:#313131}.pecb-tooltip--light .pecb-tooltip__container{background-color:#fff;border:1px solid #ececec}.pecb-tooltip__pointer--left{display:flex;flex-direction:column;align-items:flex-end;align-self:stretch;position:relative;z-index:2;margin-right:-1px}.pecb-tooltip--left-top>.pecb-tooltip__pointer--left{justify-content:flex-start;padding-top:16px}.pecb-tooltip--left-center>.pecb-tooltip__pointer--left{justify-content:center}.pecb-tooltip--left-bottom>.pecb-tooltip__pointer--left{justify-content:flex-end;padding-bottom:16px}.pecb-tooltip__pointer--right{display:flex;flex-direction:column;align-items:flex-start;align-self:stretch;position:relative;z-index:2;margin-left:-1px}.pecb-tooltip--right-top>.pecb-tooltip__pointer--right{justify-content:flex-start;padding-top:16px}.pecb-tooltip--right-center>.pecb-tooltip__pointer--right{justify-content:center}.pecb-tooltip--right-bottom>.pecb-tooltip__pointer--right{justify-content:flex-end;padding-bottom:16px}.pecb-tooltip__body{flex:1;display:flex;flex-direction:column;gap:16px;padding:16px}.pecb-tooltip__content{display:flex;gap:8px;align-items:flex-start;width:100%}.pecb-tooltip__texts{flex:1;display:flex;flex-direction:column;gap:4px}.pecb-tooltip__title{font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;font-style:normal;font-weight:700;line-height:normal;margin:0}.pecb-tooltip--dark .pecb-tooltip__title{color:#fff}.pecb-tooltip--light .pecb-tooltip__title{color:#212427}.pecb-tooltip__message{font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.5;margin:0}.pecb-tooltip--dark .pecb-tooltip__message{color:#ececec}.pecb-tooltip--light .pecb-tooltip__message{color:#919191}.pecb-tooltip__close{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:20px;height:20px;padding:0;background:none;border:none;cursor:pointer;transition:opacity .15s ease-in-out}.pecb-tooltip__close:hover{opacity:.7}.pecb-tooltip__close:focus-visible{outline:2px solid #a11e29;outline-offset:2px;border-radius:2px}.pecb-tooltip__close-icon{stroke:#ececec}.pecb-tooltip--light .pecb-tooltip__close-icon{stroke:#212427}.pecb-tooltip__buttons{display:flex;align-items:center;gap:24px}.pecb-tooltip__btn{display:inline-flex;align-items:center;justify-content:center;padding:0;background:none;border:none;cursor:pointer;font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;font-style:normal;font-weight:500;line-height:normal;transition:opacity .15s ease-in-out}.pecb-tooltip__btn:hover{opacity:.8}.pecb-tooltip__btn:focus-visible{outline:2px solid #a11e29;outline-offset:2px;border-radius:2px}.pecb-tooltip__btn--primary{color:#a11e29}.pecb-tooltip__btn--secondary{color:#919191}.pecb-tooltip--light .pecb-tooltip__btn--secondary{color:#212427}\n"] }]
|
|
6405
|
+
}], propDecorators: { title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], pointerPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "pointerPosition", required: false }] }], theme: [{ type: i0.Input, args: [{ isSignal: true, alias: "theme", required: false }] }], primaryCta: [{ type: i0.Input, args: [{ isSignal: true, alias: "primaryCta", required: false }] }], secondaryCta: [{ type: i0.Input, args: [{ isSignal: true, alias: "secondaryCta", required: false }] }], showClose: [{ type: i0.Input, args: [{ isSignal: true, alias: "showClose", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], visible: [{ type: i0.Input, args: [{ isSignal: true, alias: "visible", required: false }] }, { type: i0.Output, args: ["visibleChange"] }], closeAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeAriaLabel", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], primaryCtaClick: [{ type: i0.Output, args: ["primaryCtaClick"] }], secondaryCtaClick: [{ type: i0.Output, args: ["secondaryCtaClick"] }] } });
|
|
6375
6406
|
|
|
6376
6407
|
const GAP = 2; // px gap between tooltip pointer tip and the host element
|
|
6377
6408
|
class TooltipDirective {
|
|
@@ -10624,130 +10655,2052 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
10624
10655
|
args: [{ selector: 'pecb-media', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div\n [class]=\"hostClasses()\"\n role=\"grid\"\n [attr.aria-label]=\"ariaLabel() ?? defaultAriaLabel()\"\n>\n @for (group of groups(); track group.label) {\n <div class=\"pecb-media__group\">\n @if (group.label) {\n <h3 class=\"pecb-media__group-title\">{{ group.label }}</h3>\n }\n\n <div class=\"pecb-media__grid\">\n @for (item of group.items; track item.name) {\n <button\n class=\"pecb-media__item\"\n type=\"button\"\n [style.width.px]=\"itemSize()\"\n [style.height.px]=\"itemSize()\"\n [attr.aria-label]=\"copyLabel() + ' ' + formatName(item.name)\"\n [attr.title]=\"formatName(item.name)\"\n (click)=\"copyToClipboard(item)\"\n >\n <span\n class=\"pecb-media__svg\"\n [innerHTML]=\"item.safeHtml\"\n ></span>\n\n @if (copiedItem() === item.name) {\n <span class=\"pecb-media__tooltip\">{{ copiedLabel() }}</span>\n }\n </button>\n }\n </div>\n </div>\n }\n</div>\n", styles: [".pecb-media{display:block;width:100%}.pecb-media__group{width:100%}.pecb-media__group+.pecb-media__group{margin-top:20px}.pecb-media__group-title{font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:16px;font-weight:700;line-height:1.4;color:#313131;margin:0 0 16px}.pecb-media__grid{display:flex;flex-wrap:wrap;gap:12px;align-items:center}.pecb-media__item{position:relative;display:inline-flex;align-items:center;justify-content:center;padding:0;border:none;background:transparent;border-radius:6px;cursor:pointer;transition-property:background-color,transform;transition-duration:.25s ease-in-out;transition-timing-function:ease-in-out}.pecb-media__item:hover{background-color:#f4f4f4;transform:scale(1.1)}.pecb-media__item:active{transform:scale(.95)}.pecb-media__item:focus-visible{outline:2px solid #007bff;outline-offset:2px}.pecb-media__svg{display:inline-flex;align-items:center;justify-content:center;width:100%;height:100%}.pecb-media__svg svg{display:block;width:100%;height:100%}.pecb-media__tooltip{position:absolute;bottom:calc(100% + 4px);left:50%;transform:translate(-50%);padding:2px 8px;background-color:#212427;color:#fff;font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:12px;font-weight:500;line-height:20px;border-radius:6px;white-space:nowrap;pointer-events:none;animation:pecb-media-fade-in .15s ease-out;z-index:1070}.pecb-media--sm .pecb-media__grid{gap:8px}.pecb-media--lg .pecb-media__grid{gap:16px}.pecb-media--flag .pecb-media__item{overflow:hidden}@keyframes pecb-media-fade-in{0%{opacity:0;transform:translate(-50%) translateY(4px)}to{opacity:1;transform:translate(-50%) translateY(0)}}\n"] }]
|
|
10625
10656
|
}], propDecorators: { type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], socialStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "socialStyle", required: false }] }], flagShape: [{ type: i0.Input, args: [{ isSignal: true, alias: "flagShape", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], defaultAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultAriaLabel", required: false }] }], copiedLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "copiedLabel", required: false }] }], copyLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "copyLabel", required: false }] }] } });
|
|
10626
10657
|
|
|
10658
|
+
const DEFAULT_VIDEO_PLAYER_LABELS = {
|
|
10659
|
+
play: 'Play',
|
|
10660
|
+
pause: 'Pause',
|
|
10661
|
+
replay: 'Replay',
|
|
10662
|
+
skipBack: 'Rewind {n} seconds',
|
|
10663
|
+
mute: 'Mute',
|
|
10664
|
+
unmute: 'Unmute',
|
|
10665
|
+
volume: 'Volume',
|
|
10666
|
+
captionsOn: 'Turn captions on',
|
|
10667
|
+
captionsOff: 'Turn captions off',
|
|
10668
|
+
settings: 'Settings',
|
|
10669
|
+
playbackSpeed: 'Playback speed',
|
|
10670
|
+
normalSpeed: 'Normal',
|
|
10671
|
+
enterFullscreen: 'Enter fullscreen',
|
|
10672
|
+
exitFullscreen: 'Exit fullscreen',
|
|
10673
|
+
seek: 'Seek',
|
|
10674
|
+
player: 'Video player',
|
|
10675
|
+
error: 'This video could not be loaded.',
|
|
10676
|
+
};
|
|
10677
|
+
/** Default playback-rate menu entries. */
|
|
10678
|
+
const DEFAULT_VIDEO_PLAYER_RATES = [0.5, 0.75, 1, 1.25, 1.5, 2];
|
|
10627
10679
|
/**
|
|
10628
|
-
*
|
|
10629
|
-
*
|
|
10630
|
-
|
|
10631
|
-
|
|
10632
|
-
|
|
10680
|
+
* Formats seconds as `m:ss`, or `h:mm:ss` once the value reaches one hour.
|
|
10681
|
+
* Non-finite / negative input renders as `0:00`.
|
|
10682
|
+
*/
|
|
10683
|
+
function formatVideoTime(seconds) {
|
|
10684
|
+
if (!Number.isFinite(seconds) || seconds < 0) {
|
|
10685
|
+
return '0:00';
|
|
10686
|
+
}
|
|
10687
|
+
const total = Math.floor(seconds);
|
|
10688
|
+
const h = Math.floor(total / 3600);
|
|
10689
|
+
const m = Math.floor((total % 3600) / 60);
|
|
10690
|
+
const s = total % 60;
|
|
10691
|
+
const mm = h > 0 ? String(m).padStart(2, '0') : String(m);
|
|
10692
|
+
const ss = String(s).padStart(2, '0');
|
|
10693
|
+
return h > 0 ? `${h}:${mm}:${ss}` : `${mm}:${ss}`;
|
|
10694
|
+
}
|
|
10695
|
+
// Internal tuning constants.
|
|
10696
|
+
const IDLE_HIDE_DELAY_MS = 2600;
|
|
10697
|
+
const LEAVE_HIDE_DELAY_MS = 700;
|
|
10698
|
+
const SEEK_SYNC_TOLERANCE_S = 0.5;
|
|
10699
|
+
// ─── Component ────────────────────────────────────────────────────────────────
|
|
10700
|
+
/**
|
|
10701
|
+
* PECB course video player.
|
|
10633
10702
|
*
|
|
10634
|
-
*
|
|
10635
|
-
*
|
|
10636
|
-
*
|
|
10703
|
+
* A fully custom-skinned, accessible HTML5 `<video>` wrapper that reproduces
|
|
10704
|
+
* the PECB "Course Player" design: a rounded dark 16:9 stage, poster, brand
|
|
10705
|
+
* watermark + section label, a blurred centre play button and a bottom
|
|
10706
|
+
* control bar (progress, play/pause, rewind, volume, time, captions, speed
|
|
10707
|
+
* settings, fullscreen).
|
|
10637
10708
|
*
|
|
10638
|
-
*
|
|
10639
|
-
*
|
|
10640
|
-
*
|
|
10709
|
+
* The player is self-contained (no icon-registry / service dependencies) so it
|
|
10710
|
+
* can be dropped into any Angular 17+ app. It is responsive (container
|
|
10711
|
+
* queries), keyboard operable, touch friendly and honours
|
|
10712
|
+
* `prefers-reduced-motion`.
|
|
10641
10713
|
*
|
|
10642
|
-
*
|
|
10643
|
-
*
|
|
10644
|
-
*
|
|
10714
|
+
* @example Minimal
|
|
10715
|
+
* ```html
|
|
10716
|
+
* <pecb-video-player
|
|
10717
|
+
* src="https://cdn.example.com/lesson.mp4"
|
|
10718
|
+
* poster="https://cdn.example.com/lesson.jpg"
|
|
10719
|
+
* label="SECTION 2">
|
|
10720
|
+
* </pecb-video-player>
|
|
10721
|
+
* ```
|
|
10645
10722
|
*
|
|
10646
|
-
*
|
|
10647
|
-
*
|
|
10648
|
-
*
|
|
10649
|
-
*
|
|
10723
|
+
* @example Two-way bound & driven from outside (e.g. a transcript panel)
|
|
10724
|
+
* ```html
|
|
10725
|
+
* <pecb-video-player
|
|
10726
|
+
* [src]="sources"
|
|
10727
|
+
* [tracks]="captions"
|
|
10728
|
+
* [(currentTime)]="at"
|
|
10729
|
+
* [(playing)]="playing"
|
|
10730
|
+
* (playbackEnded)="onLessonFinished()">
|
|
10731
|
+
* </pecb-video-player>
|
|
10650
10732
|
* ```
|
|
10651
10733
|
*
|
|
10652
|
-
*
|
|
10734
|
+
* Theming — override the CSS custom properties on the host:
|
|
10735
|
+
* ```css
|
|
10736
|
+
* pecb-video-player { --pecb-video-radius: 0; --pecb-video-accent: #a11e29; }
|
|
10737
|
+
* ```
|
|
10653
10738
|
*/
|
|
10654
|
-
class
|
|
10739
|
+
class VideoPlayerComponent {
|
|
10655
10740
|
constructor() {
|
|
10656
|
-
this.
|
|
10657
|
-
this.
|
|
10658
|
-
this.
|
|
10659
|
-
|
|
10660
|
-
|
|
10661
|
-
|
|
10662
|
-
|
|
10741
|
+
this.platformId = inject(PLATFORM_ID);
|
|
10742
|
+
this.zone = inject(NgZone);
|
|
10743
|
+
this.destroyRef = inject(DestroyRef);
|
|
10744
|
+
this.isBrowser = isPlatformBrowser(this.platformId);
|
|
10745
|
+
/** Unique id used to wire ARIA relationships inside the template. */
|
|
10746
|
+
this.uid = generateUniqueId('pecb-video');
|
|
10747
|
+
// ── Element refs ────────────────────────────────────────────────────────────
|
|
10748
|
+
this.rootRef = viewChild('root', ...(ngDevMode ? [{ debugName: "rootRef" }] : []));
|
|
10749
|
+
this.videoRef = viewChild('video', ...(ngDevMode ? [{ debugName: "videoRef" }] : []));
|
|
10750
|
+
this.progressRef = viewChild('progress', ...(ngDevMode ? [{ debugName: "progressRef" }] : []));
|
|
10751
|
+
this.settingsMenuRef = viewChild('settingsMenu', ...(ngDevMode ? [{ debugName: "settingsMenuRef" }] : []));
|
|
10752
|
+
// ── Media inputs ────────────────────────────────────────────────────────────
|
|
10753
|
+
/** Media URL, or a list of `<source>` candidates. */
|
|
10754
|
+
this.src = input(undefined, ...(ngDevMode ? [{ debugName: "src" }] : []));
|
|
10755
|
+
/** Poster image shown before playback starts. */
|
|
10756
|
+
this.poster = input(undefined, ...(ngDevMode ? [{ debugName: "poster" }] : []));
|
|
10757
|
+
/** Text tracks (captions / subtitles). The CC button appears when non-empty. */
|
|
10758
|
+
this.tracks = input([], ...(ngDevMode ? [{ debugName: "tracks" }] : []));
|
|
10759
|
+
/** Accessible title of the media, announced as part of the region label. */
|
|
10760
|
+
this.title = input(undefined, ...(ngDevMode ? [{ debugName: "title" }] : []));
|
|
10761
|
+
/** `crossorigin` attribute — required for captions hosted on another origin. */
|
|
10762
|
+
this.crossOrigin = input(undefined, ...(ngDevMode ? [{ debugName: "crossOrigin" }] : []));
|
|
10763
|
+
/** @default 'metadata' */
|
|
10764
|
+
this.preload = input('metadata', ...(ngDevMode ? [{ debugName: "preload" }] : []));
|
|
10765
|
+
/** Attempt to start playback automatically (subject to browser policy). */
|
|
10766
|
+
this.autoplay = input(false, ...(ngDevMode ? [{ debugName: "autoplay", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
10767
|
+
/** Restart from the beginning when the media ends. */
|
|
10768
|
+
this.loop = input(false, ...(ngDevMode ? [{ debugName: "loop", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
10769
|
+
/** Play inline on iOS instead of forcing the native fullscreen player. @default true */
|
|
10770
|
+
this.playsInline = input(true, ...(ngDevMode ? [{ debugName: "playsInline", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
10771
|
+
/** Initial playhead position in seconds (applied once metadata is loaded). */
|
|
10772
|
+
this.startTime = input(0, ...(ngDevMode ? [{ debugName: "startTime", transform: numberAttribute }] : [{ transform: numberAttribute }]));
|
|
10773
|
+
// ── Appearance inputs ───────────────────────────────────────────────────────
|
|
10774
|
+
/** Brand watermark in the bottom-left corner. Empty string hides it. @default 'PECB' */
|
|
10775
|
+
this.watermark = input('PECB', ...(ngDevMode ? [{ debugName: "watermark" }] : []));
|
|
10776
|
+
/** Large label in the bottom-right corner, e.g. `SECTION 2`. */
|
|
10777
|
+
this.label = input(undefined, ...(ngDevMode ? [{ debugName: "label" }] : []));
|
|
10778
|
+
/** CSS `aspect-ratio` of the stage. @default '16 / 9' */
|
|
10779
|
+
this.aspectRatio = input('16 / 9', ...(ngDevMode ? [{ debugName: "aspectRatio" }] : []));
|
|
10780
|
+
/** Show the large centre play/pause button. @default true */
|
|
10781
|
+
this.showCenterButton = input(true, ...(ngDevMode ? [{ debugName: "showCenterButton", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
10782
|
+
/** Show the rewind button. @default true */
|
|
10783
|
+
this.showSkipBack = input(true, ...(ngDevMode ? [{ debugName: "showSkipBack", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
10784
|
+
/** Show the mute button + volume slider. @default true */
|
|
10785
|
+
this.showVolume = input(true, ...(ngDevMode ? [{ debugName: "showVolume", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
10786
|
+
/** Show the elapsed / total time. @default true */
|
|
10787
|
+
this.showTime = input(true, ...(ngDevMode ? [{ debugName: "showTime", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
10788
|
+
/** Force-show/hide the captions button. `undefined` = show only when `tracks` is non-empty. */
|
|
10789
|
+
this.showCaptions = input(undefined, ...(ngDevMode ? [{ debugName: "showCaptions" }] : []));
|
|
10790
|
+
/** Show the settings (playback speed) button. @default true */
|
|
10791
|
+
this.showSettings = input(true, ...(ngDevMode ? [{ debugName: "showSettings", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
10792
|
+
/** Show the fullscreen button. @default true */
|
|
10793
|
+
this.showFullscreen = input(true, ...(ngDevMode ? [{ debugName: "showFullscreen", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
10794
|
+
/** Seconds the rewind button / arrow keys jump. @default 10 */
|
|
10795
|
+
this.seekStep = input(10, ...(ngDevMode ? [{ debugName: "seekStep", transform: numberAttribute }] : [{ transform: numberAttribute }]));
|
|
10796
|
+
/** Playback-rate options listed in the settings menu. */
|
|
10797
|
+
this.playbackRates = input(DEFAULT_VIDEO_PLAYER_RATES, ...(ngDevMode ? [{ debugName: "playbackRates" }] : []));
|
|
10798
|
+
/** Partial override of the user-visible strings (i18n). */
|
|
10799
|
+
this.labels = input({}, ...(ngDevMode ? [{ debugName: "labels" }] : []));
|
|
10800
|
+
// ── Two-way state (models) ──────────────────────────────────────────────────
|
|
10801
|
+
/** Whether the media is playing. Set it to start/stop playback. */
|
|
10802
|
+
this.playing = model(false, ...(ngDevMode ? [{ debugName: "playing" }] : []));
|
|
10803
|
+
/** Playhead position in seconds. Set it to seek. */
|
|
10804
|
+
this.currentTime = model(0, ...(ngDevMode ? [{ debugName: "currentTime" }] : []));
|
|
10805
|
+
/** Volume 0–1. */
|
|
10806
|
+
this.volume = model(1, ...(ngDevMode ? [{ debugName: "volume" }] : []));
|
|
10807
|
+
/** Muted state. */
|
|
10808
|
+
this.muted = model(false, ...(ngDevMode ? [{ debugName: "muted" }] : []));
|
|
10809
|
+
/** Playback rate. */
|
|
10810
|
+
this.playbackRate = model(1, ...(ngDevMode ? [{ debugName: "playbackRate" }] : []));
|
|
10811
|
+
/** Whether the default text track is displayed. */
|
|
10812
|
+
this.captionsEnabled = model(false, ...(ngDevMode ? [{ debugName: "captionsEnabled" }] : []));
|
|
10813
|
+
// ── Outputs ─────────────────────────────────────────────────────────────────
|
|
10814
|
+
/** Fires on every `timeupdate` (~4×/s while playing). */
|
|
10815
|
+
this.timeUpdate = output();
|
|
10816
|
+
/** Duration is known. */
|
|
10817
|
+
this.loadedMetadata = output();
|
|
10818
|
+
/** A seek finished. */
|
|
10819
|
+
this.seekEnd = output();
|
|
10820
|
+
/** Playback reached the end. */
|
|
10821
|
+
this.playbackEnded = output();
|
|
10822
|
+
/** Player entered / left fullscreen. */
|
|
10823
|
+
this.fullscreenChange = output();
|
|
10824
|
+
/** Media failed to load or playback was rejected. */
|
|
10825
|
+
this.playerError = output();
|
|
10826
|
+
// ── Internal state ──────────────────────────────────────────────────────────
|
|
10827
|
+
this.duration = signal(0, ...(ngDevMode ? [{ debugName: "duration" }] : []));
|
|
10828
|
+
this.bufferedEnd = signal(0, ...(ngDevMode ? [{ debugName: "bufferedEnd" }] : []));
|
|
10829
|
+
this.hasEnded = signal(false, ...(ngDevMode ? [{ debugName: "hasEnded" }] : []));
|
|
10830
|
+
this.isBuffering = signal(false, ...(ngDevMode ? [{ debugName: "isBuffering" }] : []));
|
|
10831
|
+
this.hasError = signal(false, ...(ngDevMode ? [{ debugName: "hasError" }] : []));
|
|
10832
|
+
this.isFullscreen = signal(false, ...(ngDevMode ? [{ debugName: "isFullscreen" }] : []));
|
|
10833
|
+
this.isSeeking = signal(false, ...(ngDevMode ? [{ debugName: "isSeeking" }] : []));
|
|
10834
|
+
this.settingsOpen = signal(false, ...(ngDevMode ? [{ debugName: "settingsOpen" }] : []));
|
|
10835
|
+
this.controlsVisible = signal(true, ...(ngDevMode ? [{ debugName: "controlsVisible" }] : []));
|
|
10836
|
+
this.hasStarted = signal(false, ...(ngDevMode ? [{ debugName: "hasStarted" }] : []));
|
|
10837
|
+
this.hideTimer = null;
|
|
10838
|
+
this.lastPointerType = 'mouse';
|
|
10839
|
+
this.startTimeApplied = false;
|
|
10840
|
+
this.initialLoadDone = false;
|
|
10841
|
+
this.pendingPlay = null;
|
|
10842
|
+
// ── Computed ────────────────────────────────────────────────────────────────
|
|
10843
|
+
this.text = computed(() => ({
|
|
10844
|
+
...DEFAULT_VIDEO_PLAYER_LABELS,
|
|
10845
|
+
...this.labels(),
|
|
10846
|
+
}), ...(ngDevMode ? [{ debugName: "text" }] : []));
|
|
10847
|
+
this.skipBackLabel = computed(() => this.text().skipBack.replace('{n}', String(this.seekStep())), ...(ngDevMode ? [{ debugName: "skipBackLabel" }] : []));
|
|
10848
|
+
this.sourceList = computed(() => {
|
|
10849
|
+
const s = this.src();
|
|
10850
|
+
if (!s)
|
|
10851
|
+
return [];
|
|
10852
|
+
return typeof s === 'string' ? [{ src: s }] : s;
|
|
10853
|
+
}, ...(ngDevMode ? [{ debugName: "sourceList" }] : []));
|
|
10854
|
+
/** Single-string src is bound directly so the browser can start fetching ASAP. */
|
|
10855
|
+
this.singleSrc = computed(() => {
|
|
10856
|
+
const s = this.src();
|
|
10857
|
+
return typeof s === 'string' ? s : undefined;
|
|
10858
|
+
}, ...(ngDevMode ? [{ debugName: "singleSrc" }] : []));
|
|
10859
|
+
this.progressPct = computed(() => {
|
|
10860
|
+
const d = this.duration();
|
|
10861
|
+
return d > 0 ? Math.min(100, Math.max(0, (this.currentTime() / d) * 100)) : 0;
|
|
10862
|
+
}, ...(ngDevMode ? [{ debugName: "progressPct" }] : []));
|
|
10863
|
+
this.bufferedPct = computed(() => {
|
|
10864
|
+
const d = this.duration();
|
|
10865
|
+
return d > 0 ? Math.min(100, Math.max(0, (this.bufferedEnd() / d) * 100)) : 0;
|
|
10866
|
+
}, ...(ngDevMode ? [{ debugName: "bufferedPct" }] : []));
|
|
10867
|
+
this.currentTimeLabel = computed(() => formatVideoTime(this.currentTime()), ...(ngDevMode ? [{ debugName: "currentTimeLabel" }] : []));
|
|
10868
|
+
this.durationLabel = computed(() => formatVideoTime(this.duration()), ...(ngDevMode ? [{ debugName: "durationLabel" }] : []));
|
|
10869
|
+
this.captionsButtonVisible = computed(() => this.showCaptions() ?? this.tracks().length > 0, ...(ngDevMode ? [{ debugName: "captionsButtonVisible" }] : []));
|
|
10870
|
+
/** Volume as an integer percentage (0 while muted) — drives the slider. */
|
|
10871
|
+
this.volumePct = computed(() => (this.muted() ? 0 : Math.round(this.volume() * 100)), ...(ngDevMode ? [{ debugName: "volumePct" }] : []));
|
|
10872
|
+
this.volumeLevel = computed(() => {
|
|
10873
|
+
if (this.muted() || this.volume() === 0)
|
|
10874
|
+
return 'muted';
|
|
10875
|
+
return this.volume() < 0.5 ? 'low' : 'high';
|
|
10876
|
+
}, ...(ngDevMode ? [{ debugName: "volumeLevel" }] : []));
|
|
10877
|
+
this.regionLabel = computed(() => {
|
|
10878
|
+
const t = this.title();
|
|
10879
|
+
return t ? `${this.text().player}: ${t}` : this.text().player;
|
|
10880
|
+
}, ...(ngDevMode ? [{ debugName: "regionLabel" }] : []));
|
|
10881
|
+
/** Controls are hidden only while playing and idle. */
|
|
10882
|
+
this.controlsHidden = computed(() => this.playing() &&
|
|
10883
|
+
!this.controlsVisible() &&
|
|
10884
|
+
!this.settingsOpen() &&
|
|
10885
|
+
!this.isSeeking() &&
|
|
10886
|
+
!this.hasEnded(), ...(ngDevMode ? [{ debugName: "controlsHidden" }] : []));
|
|
10887
|
+
this.hostClasses = computed(() => [
|
|
10888
|
+
'pecb-video-player',
|
|
10889
|
+
this.playing() ? 'pecb-video-player--playing' : 'pecb-video-player--paused',
|
|
10890
|
+
this.controlsHidden() ? 'pecb-video-player--idle' : '',
|
|
10891
|
+
this.isFullscreen() ? 'pecb-video-player--fullscreen' : '',
|
|
10892
|
+
this.hasEnded() ? 'pecb-video-player--ended' : '',
|
|
10893
|
+
this.hasError() ? 'pecb-video-player--error' : '',
|
|
10894
|
+
this.isSeeking() ? 'pecb-video-player--seeking' : '',
|
|
10895
|
+
]
|
|
10896
|
+
.filter(Boolean)
|
|
10897
|
+
.join(' '), ...(ngDevMode ? [{ debugName: "hostClasses" }] : []));
|
|
10898
|
+
// ── model → <video> synchronisation ───────────────────────────────────────
|
|
10899
|
+
effect(() => {
|
|
10900
|
+
const wantPlaying = this.playing();
|
|
10901
|
+
const video = this.videoRef()?.nativeElement;
|
|
10902
|
+
if (!video)
|
|
10903
|
+
return;
|
|
10904
|
+
untracked(() => {
|
|
10905
|
+
if (wantPlaying && video.paused) {
|
|
10906
|
+
this.safePlay(video);
|
|
10907
|
+
}
|
|
10908
|
+
else if (!wantPlaying && !video.paused) {
|
|
10909
|
+
video.pause();
|
|
10910
|
+
}
|
|
10911
|
+
});
|
|
10912
|
+
});
|
|
10913
|
+
effect(() => {
|
|
10914
|
+
const t = this.currentTime();
|
|
10915
|
+
const video = this.videoRef()?.nativeElement;
|
|
10916
|
+
if (!video || !Number.isFinite(t))
|
|
10917
|
+
return;
|
|
10918
|
+
untracked(() => {
|
|
10919
|
+
if (Math.abs(video.currentTime - t) > SEEK_SYNC_TOLERANCE_S) {
|
|
10920
|
+
video.currentTime = t;
|
|
10921
|
+
}
|
|
10922
|
+
});
|
|
10923
|
+
});
|
|
10924
|
+
effect(() => {
|
|
10925
|
+
const v = Math.min(1, Math.max(0, this.volume()));
|
|
10926
|
+
const m = this.muted();
|
|
10927
|
+
const video = this.videoRef()?.nativeElement;
|
|
10928
|
+
if (!video)
|
|
10929
|
+
return;
|
|
10930
|
+
untracked(() => {
|
|
10931
|
+
if (video.volume !== v)
|
|
10932
|
+
video.volume = v;
|
|
10933
|
+
if (video.muted !== m)
|
|
10934
|
+
video.muted = m;
|
|
10935
|
+
});
|
|
10936
|
+
});
|
|
10937
|
+
effect(() => {
|
|
10938
|
+
const rate = this.playbackRate();
|
|
10939
|
+
const video = this.videoRef()?.nativeElement;
|
|
10940
|
+
if (!video || !Number.isFinite(rate) || rate <= 0)
|
|
10941
|
+
return;
|
|
10942
|
+
untracked(() => {
|
|
10943
|
+
if (video.playbackRate !== rate)
|
|
10944
|
+
video.playbackRate = rate;
|
|
10945
|
+
});
|
|
10946
|
+
});
|
|
10947
|
+
effect(() => {
|
|
10948
|
+
const enabled = this.captionsEnabled();
|
|
10949
|
+
// Re-run when tracks change (they are re-rendered by the template).
|
|
10950
|
+
this.tracks();
|
|
10951
|
+
const video = this.videoRef()?.nativeElement;
|
|
10952
|
+
if (!video)
|
|
10953
|
+
return;
|
|
10954
|
+
untracked(() => this.applyCaptionMode(video, enabled));
|
|
10955
|
+
});
|
|
10956
|
+
// Reload the element when the source set changes after first render.
|
|
10957
|
+
effect(() => {
|
|
10958
|
+
this.sourceList();
|
|
10959
|
+
const video = this.videoRef()?.nativeElement;
|
|
10960
|
+
if (!video)
|
|
10961
|
+
return;
|
|
10962
|
+
untracked(() => {
|
|
10963
|
+
if (!this.initialLoadDone) {
|
|
10964
|
+
this.initialLoadDone = true;
|
|
10965
|
+
return;
|
|
10966
|
+
}
|
|
10967
|
+
this.resetForNewSource(video);
|
|
10968
|
+
});
|
|
10969
|
+
});
|
|
10970
|
+
if (this.isBrowser) {
|
|
10971
|
+
afterNextRender(() => this.attachIdleListeners());
|
|
10972
|
+
this.destroyRef.onDestroy(() => this.clearHideTimer());
|
|
10973
|
+
}
|
|
10663
10974
|
}
|
|
10664
|
-
|
|
10665
|
-
|
|
10666
|
-
|
|
10667
|
-
|
|
10668
|
-
return this.notificationSubject.asObservable();
|
|
10975
|
+
// ── Public API ──────────────────────────────────────────────────────────────
|
|
10976
|
+
/** Start playback. */
|
|
10977
|
+
play() {
|
|
10978
|
+
this.playing.set(true);
|
|
10669
10979
|
}
|
|
10670
|
-
/**
|
|
10671
|
-
|
|
10672
|
-
|
|
10673
|
-
get dismiss$() {
|
|
10674
|
-
return this.dismissSubject.asObservable();
|
|
10980
|
+
/** Pause playback. */
|
|
10981
|
+
pause() {
|
|
10982
|
+
this.playing.set(false);
|
|
10675
10983
|
}
|
|
10676
|
-
/**
|
|
10677
|
-
|
|
10678
|
-
|
|
10679
|
-
|
|
10680
|
-
|
|
10984
|
+
/** Toggle play / pause (replays from the start after the media ended). */
|
|
10985
|
+
togglePlay() {
|
|
10986
|
+
if (this.hasError())
|
|
10987
|
+
return;
|
|
10988
|
+
if (this.hasEnded()) {
|
|
10989
|
+
this.seek(0);
|
|
10990
|
+
this.playing.set(true);
|
|
10991
|
+
return;
|
|
10992
|
+
}
|
|
10993
|
+
this.playing.update((p) => !p);
|
|
10994
|
+
}
|
|
10995
|
+
/** Seek to an absolute position (seconds, clamped to the duration). */
|
|
10996
|
+
seek(seconds) {
|
|
10997
|
+
const d = this.duration();
|
|
10998
|
+
const target = Math.max(0, d > 0 ? Math.min(d, seconds) : seconds);
|
|
10999
|
+
const video = this.videoRef()?.nativeElement;
|
|
11000
|
+
if (video) {
|
|
11001
|
+
video.currentTime = target;
|
|
11002
|
+
}
|
|
11003
|
+
this.currentTime.set(target);
|
|
10681
11004
|
}
|
|
10682
|
-
/**
|
|
10683
|
-
|
|
10684
|
-
|
|
10685
|
-
* @param title - Optional title
|
|
10686
|
-
* @param config - Optional additional configuration
|
|
10687
|
-
*/
|
|
10688
|
-
success(message, title, config) {
|
|
10689
|
-
return this.show({ ...config, message, title, type: 'success' });
|
|
11005
|
+
/** Seek relative to the current position. */
|
|
11006
|
+
seekBy(deltaSeconds) {
|
|
11007
|
+
this.seek(this.currentTime() + deltaSeconds);
|
|
10690
11008
|
}
|
|
10691
|
-
/**
|
|
10692
|
-
|
|
10693
|
-
|
|
10694
|
-
* @param title - Optional title
|
|
10695
|
-
* @param config - Optional additional configuration
|
|
10696
|
-
*/
|
|
10697
|
-
error(message, title, config) {
|
|
10698
|
-
return this.show({ ...config, message, title, type: 'error' });
|
|
11009
|
+
/** Rewind by `seekStep` seconds. */
|
|
11010
|
+
skipBack() {
|
|
11011
|
+
this.seekBy(-this.seekStep());
|
|
10699
11012
|
}
|
|
10700
|
-
/**
|
|
10701
|
-
|
|
10702
|
-
|
|
10703
|
-
|
|
10704
|
-
|
|
10705
|
-
|
|
10706
|
-
|
|
10707
|
-
|
|
11013
|
+
/** Toggle mute. Unmuting a zero volume restores it to 50 %. */
|
|
11014
|
+
toggleMute() {
|
|
11015
|
+
const nextMuted = !this.muted();
|
|
11016
|
+
if (!nextMuted && this.volume() === 0) {
|
|
11017
|
+
this.volume.set(0.5);
|
|
11018
|
+
}
|
|
11019
|
+
this.muted.set(nextMuted);
|
|
11020
|
+
}
|
|
11021
|
+
/** Set volume (0–1). A non-zero value also unmutes. */
|
|
11022
|
+
setVolume(value) {
|
|
11023
|
+
const v = Math.min(1, Math.max(0, value));
|
|
11024
|
+
this.volume.set(v);
|
|
11025
|
+
if (v > 0 && this.muted()) {
|
|
11026
|
+
this.muted.set(false);
|
|
11027
|
+
}
|
|
10708
11028
|
}
|
|
10709
|
-
/**
|
|
10710
|
-
|
|
10711
|
-
|
|
10712
|
-
|
|
10713
|
-
* @param config - Optional additional configuration
|
|
10714
|
-
*/
|
|
10715
|
-
info(message, title, config) {
|
|
10716
|
-
return this.show({ ...config, message, title, type: 'info' });
|
|
11029
|
+
/** Set the playback rate and close the settings menu. */
|
|
11030
|
+
setPlaybackRate(rate) {
|
|
11031
|
+
this.playbackRate.set(rate);
|
|
11032
|
+
this.closeSettings(true);
|
|
10717
11033
|
}
|
|
10718
|
-
/**
|
|
10719
|
-
|
|
10720
|
-
|
|
10721
|
-
* @returns The notification ID
|
|
10722
|
-
*/
|
|
10723
|
-
show(config) {
|
|
10724
|
-
const id = config.id || this.generateId();
|
|
10725
|
-
const notification = {
|
|
10726
|
-
id,
|
|
10727
|
-
message: config.message,
|
|
10728
|
-
title: config.title,
|
|
10729
|
-
type: config.type,
|
|
10730
|
-
duration: config.duration ?? this.defaultDuration,
|
|
10731
|
-
dismissible: config.dismissible ?? true,
|
|
10732
|
-
position: config.position ?? this.defaultPosition,
|
|
10733
|
-
customClass: config.customClass,
|
|
10734
|
-
timestamp: new Date()
|
|
10735
|
-
};
|
|
10736
|
-
this.notificationSubject.next(notification);
|
|
10737
|
-
return id;
|
|
11034
|
+
/** Toggle captions on the default text track. */
|
|
11035
|
+
toggleCaptions() {
|
|
11036
|
+
this.captionsEnabled.update((c) => !c);
|
|
10738
11037
|
}
|
|
10739
|
-
/**
|
|
10740
|
-
|
|
10741
|
-
|
|
10742
|
-
|
|
11038
|
+
/** Enter / exit fullscreen for the whole player (falls back to the native iOS player). */
|
|
11039
|
+
async toggleFullscreen() {
|
|
11040
|
+
if (!this.isBrowser)
|
|
11041
|
+
return;
|
|
11042
|
+
const root = this.rootRef()?.nativeElement;
|
|
11043
|
+
const video = this.videoRef()?.nativeElement;
|
|
11044
|
+
if (!root || !video)
|
|
11045
|
+
return;
|
|
11046
|
+
try {
|
|
11047
|
+
if (document.fullscreenElement) {
|
|
11048
|
+
await document.exitFullscreen();
|
|
11049
|
+
}
|
|
11050
|
+
else if (root.requestFullscreen) {
|
|
11051
|
+
await root.requestFullscreen();
|
|
11052
|
+
}
|
|
11053
|
+
else {
|
|
11054
|
+
// iOS Safari: no element fullscreen — use the native video fullscreen.
|
|
11055
|
+
const legacy = video;
|
|
11056
|
+
legacy.webkitEnterFullscreen?.();
|
|
11057
|
+
}
|
|
11058
|
+
}
|
|
11059
|
+
catch {
|
|
11060
|
+
// Fullscreen can be denied (iframe sandbox, user-gesture rules) — ignore.
|
|
11061
|
+
}
|
|
11062
|
+
}
|
|
11063
|
+
/** Open / close the settings (playback speed) menu. */
|
|
11064
|
+
toggleSettings() {
|
|
11065
|
+
if (this.settingsOpen()) {
|
|
11066
|
+
this.closeSettings(true);
|
|
11067
|
+
return;
|
|
11068
|
+
}
|
|
11069
|
+
this.settingsOpen.set(true);
|
|
11070
|
+
this.revealControls();
|
|
11071
|
+
// Focus the checked item once the menu is rendered.
|
|
11072
|
+
queueMicrotask(() => {
|
|
11073
|
+
const menu = this.settingsMenuRef()?.nativeElement;
|
|
11074
|
+
const target = menu?.querySelector('[aria-checked="true"]') ??
|
|
11075
|
+
menu?.querySelector('[role="menuitemradio"]');
|
|
11076
|
+
target?.focus();
|
|
11077
|
+
});
|
|
11078
|
+
}
|
|
11079
|
+
/** Close the settings menu; optionally move focus back to its trigger. */
|
|
11080
|
+
closeSettings(restoreFocus = false) {
|
|
11081
|
+
if (!this.settingsOpen())
|
|
11082
|
+
return;
|
|
11083
|
+
this.settingsOpen.set(false);
|
|
11084
|
+
if (restoreFocus) {
|
|
11085
|
+
this.rootRef()
|
|
11086
|
+
?.nativeElement.querySelector(`#${this.uid}-settings-btn`)
|
|
11087
|
+
?.focus();
|
|
11088
|
+
}
|
|
11089
|
+
this.scheduleHide();
|
|
11090
|
+
}
|
|
11091
|
+
// ── <video> event handlers ──────────────────────────────────────────────────
|
|
11092
|
+
onLoadedMetadata(video) {
|
|
11093
|
+
this.duration.set(Number.isFinite(video.duration) ? video.duration : 0);
|
|
11094
|
+
this.hasError.set(false);
|
|
11095
|
+
if (!this.startTimeApplied) {
|
|
11096
|
+
this.startTimeApplied = true;
|
|
11097
|
+
const start = this.startTime() > 0 ? this.startTime() : this.currentTime();
|
|
11098
|
+
if (start > 0) {
|
|
11099
|
+
video.currentTime = Math.min(start, video.duration || start);
|
|
11100
|
+
}
|
|
11101
|
+
}
|
|
11102
|
+
// Re-apply model values in case the effects ran before the element existed.
|
|
11103
|
+
video.volume = Math.min(1, Math.max(0, this.volume()));
|
|
11104
|
+
video.muted = this.muted();
|
|
11105
|
+
video.playbackRate = this.playbackRate();
|
|
11106
|
+
this.applyCaptionMode(video, this.captionsEnabled());
|
|
11107
|
+
this.loadedMetadata.emit({ currentTime: video.currentTime, duration: video.duration });
|
|
11108
|
+
}
|
|
11109
|
+
onDurationChange(video) {
|
|
11110
|
+
this.duration.set(Number.isFinite(video.duration) ? video.duration : 0);
|
|
11111
|
+
}
|
|
11112
|
+
onTimeUpdate(video) {
|
|
11113
|
+
if (!this.isSeeking()) {
|
|
11114
|
+
this.currentTime.set(video.currentTime);
|
|
11115
|
+
}
|
|
11116
|
+
this.timeUpdate.emit({ currentTime: video.currentTime, duration: video.duration });
|
|
11117
|
+
}
|
|
11118
|
+
onProgress(video) {
|
|
11119
|
+
const ranges = video.buffered;
|
|
11120
|
+
if (!ranges || ranges.length === 0)
|
|
11121
|
+
return;
|
|
11122
|
+
// Prefer the range containing the playhead, else the last one.
|
|
11123
|
+
let end = ranges.end(ranges.length - 1);
|
|
11124
|
+
for (let i = 0; i < ranges.length; i++) {
|
|
11125
|
+
if (ranges.start(i) <= video.currentTime && video.currentTime <= ranges.end(i)) {
|
|
11126
|
+
end = ranges.end(i);
|
|
11127
|
+
break;
|
|
11128
|
+
}
|
|
11129
|
+
}
|
|
11130
|
+
this.bufferedEnd.set(end);
|
|
11131
|
+
}
|
|
11132
|
+
onPlay() {
|
|
11133
|
+
this.hasEnded.set(false);
|
|
11134
|
+
this.hasStarted.set(true);
|
|
11135
|
+
this.playing.set(true);
|
|
11136
|
+
this.scheduleHide();
|
|
11137
|
+
}
|
|
11138
|
+
onPause() {
|
|
11139
|
+
this.playing.set(false);
|
|
11140
|
+
this.isBuffering.set(false);
|
|
11141
|
+
this.revealControls(false);
|
|
11142
|
+
}
|
|
11143
|
+
onEnded() {
|
|
11144
|
+
this.hasEnded.set(true);
|
|
11145
|
+
this.playing.set(false);
|
|
11146
|
+
this.revealControls(false);
|
|
11147
|
+
this.playbackEnded.emit();
|
|
11148
|
+
}
|
|
11149
|
+
onWaiting() {
|
|
11150
|
+
this.isBuffering.set(true);
|
|
11151
|
+
}
|
|
11152
|
+
onPlaying() {
|
|
11153
|
+
this.isBuffering.set(false);
|
|
11154
|
+
}
|
|
11155
|
+
onSeeked(video) {
|
|
11156
|
+
this.isBuffering.set(false);
|
|
11157
|
+
if (this.hasEnded() && video.currentTime < video.duration) {
|
|
11158
|
+
this.hasEnded.set(false);
|
|
11159
|
+
}
|
|
11160
|
+
this.seekEnd.emit({ currentTime: video.currentTime, duration: video.duration });
|
|
11161
|
+
}
|
|
11162
|
+
onVolumeChange(video) {
|
|
11163
|
+
this.volume.set(video.volume);
|
|
11164
|
+
this.muted.set(video.muted);
|
|
11165
|
+
}
|
|
11166
|
+
onRateChange(video) {
|
|
11167
|
+
this.playbackRate.set(video.playbackRate);
|
|
11168
|
+
}
|
|
11169
|
+
onMediaError(video) {
|
|
11170
|
+
// Ignore the spurious error fired when the element is reset with no source.
|
|
11171
|
+
if (!video.currentSrc && this.sourceList().length === 0)
|
|
11172
|
+
return;
|
|
11173
|
+
this.hasError.set(true);
|
|
11174
|
+
this.isBuffering.set(false);
|
|
11175
|
+
this.playing.set(false);
|
|
11176
|
+
this.playerError.emit({
|
|
11177
|
+
code: 'media',
|
|
11178
|
+
message: video.error?.message || 'Media error',
|
|
11179
|
+
originalError: video.error ?? undefined,
|
|
11180
|
+
});
|
|
11181
|
+
}
|
|
11182
|
+
// ── Stage interaction ───────────────────────────────────────────────────────
|
|
11183
|
+
onStagePointerDown(event) {
|
|
11184
|
+
this.lastPointerType = event.pointerType || 'mouse';
|
|
11185
|
+
}
|
|
11186
|
+
/** Click on the video surface: toggles play on mouse; on touch, first reveals the controls. */
|
|
11187
|
+
onStageClick() {
|
|
11188
|
+
if (this.settingsOpen()) {
|
|
11189
|
+
this.closeSettings();
|
|
11190
|
+
return;
|
|
11191
|
+
}
|
|
11192
|
+
if (this.lastPointerType === 'touch' && this.controlsHidden()) {
|
|
11193
|
+
this.revealControls();
|
|
11194
|
+
return;
|
|
11195
|
+
}
|
|
11196
|
+
this.togglePlay();
|
|
11197
|
+
}
|
|
11198
|
+
onStageDoubleClick() {
|
|
11199
|
+
if (this.lastPointerType !== 'touch' && this.showFullscreen()) {
|
|
11200
|
+
void this.toggleFullscreen();
|
|
11201
|
+
}
|
|
11202
|
+
}
|
|
11203
|
+
/** Keyboard shortcuts on the player region. */
|
|
11204
|
+
onRootKeydown(event) {
|
|
11205
|
+
const target = event.target;
|
|
11206
|
+
const root = this.rootRef()?.nativeElement;
|
|
11207
|
+
if (event.key === 'Escape' && this.settingsOpen()) {
|
|
11208
|
+
event.preventDefault();
|
|
11209
|
+
this.closeSettings(true);
|
|
11210
|
+
return;
|
|
11211
|
+
}
|
|
11212
|
+
// Let native controls (buttons, sliders, menu) handle their own keys.
|
|
11213
|
+
const onInteractive = !!target &&
|
|
11214
|
+
target !== root &&
|
|
11215
|
+
(target.matches('button, input, [role="slider"], [role="menuitemradio"]') ||
|
|
11216
|
+
!!target.closest('[role="menu"]'));
|
|
11217
|
+
const nativeKey = event.key === ' ' ||
|
|
11218
|
+
event.key === 'Enter' ||
|
|
11219
|
+
event.key.startsWith('Arrow') ||
|
|
11220
|
+
event.key === 'Home' ||
|
|
11221
|
+
event.key === 'End';
|
|
11222
|
+
if (onInteractive && nativeKey) {
|
|
11223
|
+
this.revealControls();
|
|
11224
|
+
return;
|
|
11225
|
+
}
|
|
11226
|
+
let handled = true;
|
|
11227
|
+
switch (event.key) {
|
|
11228
|
+
case ' ':
|
|
11229
|
+
case 'k':
|
|
11230
|
+
case 'K':
|
|
11231
|
+
this.togglePlay();
|
|
11232
|
+
break;
|
|
11233
|
+
case 'ArrowLeft':
|
|
11234
|
+
case 'j':
|
|
11235
|
+
case 'J':
|
|
11236
|
+
this.seekBy(-this.seekStep());
|
|
11237
|
+
break;
|
|
11238
|
+
case 'ArrowRight':
|
|
11239
|
+
case 'l':
|
|
11240
|
+
case 'L':
|
|
11241
|
+
this.seekBy(this.seekStep());
|
|
11242
|
+
break;
|
|
11243
|
+
case 'ArrowUp':
|
|
11244
|
+
if (this.showVolume())
|
|
11245
|
+
this.setVolume(this.volume() + 0.1);
|
|
11246
|
+
break;
|
|
11247
|
+
case 'ArrowDown':
|
|
11248
|
+
if (this.showVolume())
|
|
11249
|
+
this.setVolume(this.volume() - 0.1);
|
|
11250
|
+
break;
|
|
11251
|
+
case 'Home':
|
|
11252
|
+
this.seek(0);
|
|
11253
|
+
break;
|
|
11254
|
+
case 'End':
|
|
11255
|
+
this.seek(this.duration());
|
|
11256
|
+
break;
|
|
11257
|
+
case 'm':
|
|
11258
|
+
case 'M':
|
|
11259
|
+
if (this.showVolume())
|
|
11260
|
+
this.toggleMute();
|
|
11261
|
+
break;
|
|
11262
|
+
case 'f':
|
|
11263
|
+
case 'F':
|
|
11264
|
+
if (this.showFullscreen())
|
|
11265
|
+
void this.toggleFullscreen();
|
|
11266
|
+
break;
|
|
11267
|
+
case 'c':
|
|
11268
|
+
case 'C':
|
|
11269
|
+
if (this.captionsButtonVisible())
|
|
11270
|
+
this.toggleCaptions();
|
|
11271
|
+
break;
|
|
11272
|
+
default:
|
|
11273
|
+
handled = false;
|
|
11274
|
+
}
|
|
11275
|
+
if (handled) {
|
|
11276
|
+
event.preventDefault();
|
|
11277
|
+
this.revealControls();
|
|
11278
|
+
}
|
|
11279
|
+
}
|
|
11280
|
+
// ── Progress / scrubbing ────────────────────────────────────────────────────
|
|
11281
|
+
onProgressPointerDown(event) {
|
|
11282
|
+
if (event.pointerType === 'mouse' && event.button !== 0)
|
|
11283
|
+
return;
|
|
11284
|
+
const bar = this.progressRef()?.nativeElement;
|
|
11285
|
+
if (!bar || this.duration() <= 0)
|
|
11286
|
+
return;
|
|
11287
|
+
event.preventDefault();
|
|
11288
|
+
bar.setPointerCapture(event.pointerId);
|
|
11289
|
+
this.isSeeking.set(true);
|
|
11290
|
+
this.seekToPointer(event, bar);
|
|
11291
|
+
}
|
|
11292
|
+
onProgressPointerMove(event) {
|
|
11293
|
+
if (!this.isSeeking())
|
|
11294
|
+
return;
|
|
11295
|
+
this.seekToPointer(event, this.progressRef()?.nativeElement);
|
|
11296
|
+
}
|
|
11297
|
+
onProgressPointerUp(event) {
|
|
11298
|
+
if (!this.isSeeking())
|
|
11299
|
+
return;
|
|
11300
|
+
const bar = this.progressRef()?.nativeElement;
|
|
11301
|
+
if (bar?.hasPointerCapture(event.pointerId)) {
|
|
11302
|
+
bar.releasePointerCapture(event.pointerId);
|
|
11303
|
+
}
|
|
11304
|
+
this.seekToPointer(event, bar);
|
|
11305
|
+
this.isSeeking.set(false);
|
|
11306
|
+
this.scheduleHide();
|
|
11307
|
+
}
|
|
11308
|
+
onProgressKeydown(event) {
|
|
11309
|
+
const d = this.duration();
|
|
11310
|
+
if (d <= 0)
|
|
11311
|
+
return;
|
|
11312
|
+
let handled = true;
|
|
11313
|
+
switch (event.key) {
|
|
11314
|
+
case 'ArrowLeft':
|
|
11315
|
+
case 'ArrowDown':
|
|
11316
|
+
this.seekBy(-this.seekStep());
|
|
11317
|
+
break;
|
|
11318
|
+
case 'ArrowRight':
|
|
11319
|
+
case 'ArrowUp':
|
|
11320
|
+
this.seekBy(this.seekStep());
|
|
11321
|
+
break;
|
|
11322
|
+
case 'PageDown':
|
|
11323
|
+
this.seekBy(-d * 0.1);
|
|
11324
|
+
break;
|
|
11325
|
+
case 'PageUp':
|
|
11326
|
+
this.seekBy(d * 0.1);
|
|
11327
|
+
break;
|
|
11328
|
+
case 'Home':
|
|
11329
|
+
this.seek(0);
|
|
11330
|
+
break;
|
|
11331
|
+
case 'End':
|
|
11332
|
+
this.seek(d);
|
|
11333
|
+
break;
|
|
11334
|
+
default:
|
|
11335
|
+
handled = false;
|
|
11336
|
+
}
|
|
11337
|
+
if (handled) {
|
|
11338
|
+
event.preventDefault();
|
|
11339
|
+
event.stopPropagation();
|
|
11340
|
+
this.revealControls();
|
|
11341
|
+
}
|
|
11342
|
+
}
|
|
11343
|
+
seekToPointer(event, bar) {
|
|
11344
|
+
if (!bar)
|
|
11345
|
+
return;
|
|
11346
|
+
const rect = bar.getBoundingClientRect();
|
|
11347
|
+
if (rect.width <= 0)
|
|
11348
|
+
return;
|
|
11349
|
+
const fraction = Math.min(1, Math.max(0, (event.clientX - rect.left) / rect.width));
|
|
11350
|
+
this.seek(fraction * this.duration());
|
|
11351
|
+
}
|
|
11352
|
+
// ── Volume slider ───────────────────────────────────────────────────────────
|
|
11353
|
+
onVolumeInput(event) {
|
|
11354
|
+
const value = Number(event.target.value);
|
|
11355
|
+
this.setVolume(value / 100);
|
|
11356
|
+
}
|
|
11357
|
+
// ── Settings menu ───────────────────────────────────────────────────────────
|
|
11358
|
+
onSettingsMenuKeydown(event) {
|
|
11359
|
+
const menu = this.settingsMenuRef()?.nativeElement;
|
|
11360
|
+
if (!menu)
|
|
11361
|
+
return;
|
|
11362
|
+
const items = Array.from(menu.querySelectorAll('[role="menuitemradio"]'));
|
|
11363
|
+
if (items.length === 0)
|
|
11364
|
+
return;
|
|
11365
|
+
const index = items.indexOf(document.activeElement);
|
|
11366
|
+
let next;
|
|
11367
|
+
switch (event.key) {
|
|
11368
|
+
case 'ArrowDown':
|
|
11369
|
+
next = (index + 1) % items.length;
|
|
11370
|
+
break;
|
|
11371
|
+
case 'ArrowUp':
|
|
11372
|
+
next = (index - 1 + items.length) % items.length;
|
|
11373
|
+
break;
|
|
11374
|
+
case 'Home':
|
|
11375
|
+
next = 0;
|
|
11376
|
+
break;
|
|
11377
|
+
case 'End':
|
|
11378
|
+
next = items.length - 1;
|
|
11379
|
+
break;
|
|
11380
|
+
case 'Tab':
|
|
11381
|
+
this.closeSettings();
|
|
11382
|
+
return;
|
|
11383
|
+
default:
|
|
11384
|
+
return;
|
|
11385
|
+
}
|
|
11386
|
+
event.preventDefault();
|
|
11387
|
+
event.stopPropagation();
|
|
11388
|
+
items[next]?.focus();
|
|
11389
|
+
}
|
|
11390
|
+
onRootFocusOut(event) {
|
|
11391
|
+
const root = this.rootRef()?.nativeElement;
|
|
11392
|
+
const next = event.relatedTarget;
|
|
11393
|
+
if (this.settingsOpen() && root && (!next || !root.contains(next))) {
|
|
11394
|
+
this.closeSettings();
|
|
11395
|
+
}
|
|
11396
|
+
}
|
|
11397
|
+
onDocumentFullscreenChange() {
|
|
11398
|
+
if (!this.isBrowser)
|
|
11399
|
+
return;
|
|
11400
|
+
const root = this.rootRef()?.nativeElement;
|
|
11401
|
+
const active = !!root && document.fullscreenElement === root;
|
|
11402
|
+
if (active !== this.isFullscreen()) {
|
|
11403
|
+
this.isFullscreen.set(active);
|
|
11404
|
+
this.fullscreenChange.emit(active);
|
|
11405
|
+
}
|
|
11406
|
+
}
|
|
11407
|
+
onDocumentPointerDown(event) {
|
|
11408
|
+
if (!this.settingsOpen())
|
|
11409
|
+
return;
|
|
11410
|
+
const root = this.rootRef()?.nativeElement;
|
|
11411
|
+
const menu = this.settingsMenuRef()?.nativeElement;
|
|
11412
|
+
const target = event.target;
|
|
11413
|
+
const settingsBtn = root?.querySelector(`#${this.uid}-settings-btn`);
|
|
11414
|
+
if (!menu?.contains(target) && !settingsBtn?.contains(target)) {
|
|
11415
|
+
this.closeSettings();
|
|
11416
|
+
}
|
|
11417
|
+
}
|
|
11418
|
+
// ── Controls visibility ─────────────────────────────────────────────────────
|
|
11419
|
+
/** Show the controls and (if playing) restart the idle timer. */
|
|
11420
|
+
revealControls(reschedule = true) {
|
|
11421
|
+
if (!this.controlsVisible()) {
|
|
11422
|
+
this.controlsVisible.set(true);
|
|
11423
|
+
}
|
|
11424
|
+
if (reschedule) {
|
|
11425
|
+
this.scheduleHide();
|
|
11426
|
+
}
|
|
11427
|
+
else {
|
|
11428
|
+
this.clearHideTimer();
|
|
11429
|
+
}
|
|
11430
|
+
}
|
|
11431
|
+
scheduleHide(delay = IDLE_HIDE_DELAY_MS) {
|
|
11432
|
+
this.clearHideTimer();
|
|
11433
|
+
if (!this.isBrowser || !this.playing())
|
|
11434
|
+
return;
|
|
11435
|
+
this.zone.runOutsideAngular(() => {
|
|
11436
|
+
this.hideTimer = setTimeout(() => {
|
|
11437
|
+
this.hideTimer = null;
|
|
11438
|
+
if (!this.playing() || this.settingsOpen() || this.isSeeking())
|
|
11439
|
+
return;
|
|
11440
|
+
// Keep controls visible while a control (not the root itself) has focus.
|
|
11441
|
+
const root = this.rootRef()?.nativeElement;
|
|
11442
|
+
const active = document.activeElement;
|
|
11443
|
+
if (root && active && active !== root && root.contains(active))
|
|
11444
|
+
return;
|
|
11445
|
+
this.zone.run(() => this.controlsVisible.set(false));
|
|
11446
|
+
}, delay);
|
|
11447
|
+
});
|
|
11448
|
+
}
|
|
11449
|
+
clearHideTimer() {
|
|
11450
|
+
if (this.hideTimer !== null) {
|
|
11451
|
+
clearTimeout(this.hideTimer);
|
|
11452
|
+
this.hideTimer = null;
|
|
11453
|
+
}
|
|
11454
|
+
}
|
|
11455
|
+
/**
|
|
11456
|
+
* High-frequency pointer listeners are registered outside the Angular zone so
|
|
11457
|
+
* mouse movement over the stage doesn't trigger change detection; we only
|
|
11458
|
+
* re-enter the zone when the visibility state actually flips.
|
|
11459
|
+
*/
|
|
11460
|
+
attachIdleListeners() {
|
|
11461
|
+
const root = this.rootRef()?.nativeElement;
|
|
11462
|
+
if (!root)
|
|
11463
|
+
return;
|
|
11464
|
+
this.zone.runOutsideAngular(() => {
|
|
11465
|
+
const onMove = () => {
|
|
11466
|
+
if (this.controlsVisible()) {
|
|
11467
|
+
this.scheduleHide();
|
|
11468
|
+
}
|
|
11469
|
+
else {
|
|
11470
|
+
this.zone.run(() => this.revealControls());
|
|
11471
|
+
}
|
|
11472
|
+
};
|
|
11473
|
+
const onLeave = () => {
|
|
11474
|
+
if (this.playing())
|
|
11475
|
+
this.scheduleHide(LEAVE_HIDE_DELAY_MS);
|
|
11476
|
+
};
|
|
11477
|
+
root.addEventListener('pointermove', onMove, { passive: true });
|
|
11478
|
+
root.addEventListener('pointerleave', onLeave, { passive: true });
|
|
11479
|
+
this.destroyRef.onDestroy(() => {
|
|
11480
|
+
root.removeEventListener('pointermove', onMove);
|
|
11481
|
+
root.removeEventListener('pointerleave', onLeave);
|
|
11482
|
+
});
|
|
11483
|
+
});
|
|
11484
|
+
}
|
|
11485
|
+
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
11486
|
+
safePlay(video) {
|
|
11487
|
+
if (this.pendingPlay)
|
|
11488
|
+
return;
|
|
11489
|
+
const attempt = video.play();
|
|
11490
|
+
if (!attempt || typeof attempt.then !== 'function')
|
|
11491
|
+
return;
|
|
11492
|
+
this.pendingPlay = attempt
|
|
11493
|
+
.catch((err) => {
|
|
11494
|
+
this.playing.set(false);
|
|
11495
|
+
this.playerError.emit({
|
|
11496
|
+
code: 'play-rejected',
|
|
11497
|
+
message: err instanceof Error ? err.message : 'Playback was rejected',
|
|
11498
|
+
originalError: err,
|
|
11499
|
+
});
|
|
11500
|
+
})
|
|
11501
|
+
.finally(() => {
|
|
11502
|
+
this.pendingPlay = null;
|
|
11503
|
+
});
|
|
11504
|
+
}
|
|
11505
|
+
applyCaptionMode(video, enabled) {
|
|
11506
|
+
const list = video.textTracks;
|
|
11507
|
+
if (!list || list.length === 0)
|
|
11508
|
+
return;
|
|
11509
|
+
const configured = this.tracks();
|
|
11510
|
+
const defaultIndex = Math.max(0, configured.findIndex((t) => t.default));
|
|
11511
|
+
for (let i = 0; i < list.length; i++) {
|
|
11512
|
+
const track = list[i];
|
|
11513
|
+
if (track.kind !== 'captions' && track.kind !== 'subtitles')
|
|
11514
|
+
continue;
|
|
11515
|
+
track.mode = enabled && i === defaultIndex ? 'showing' : 'hidden';
|
|
11516
|
+
}
|
|
11517
|
+
}
|
|
11518
|
+
resetForNewSource(video) {
|
|
11519
|
+
this.hasError.set(false);
|
|
11520
|
+
this.hasEnded.set(false);
|
|
11521
|
+
this.hasStarted.set(false);
|
|
11522
|
+
this.isBuffering.set(false);
|
|
11523
|
+
this.bufferedEnd.set(0);
|
|
11524
|
+
this.duration.set(0);
|
|
11525
|
+
this.startTimeApplied = false;
|
|
11526
|
+
// Adopt the new source's start position straight away (it is re-applied for
|
|
11527
|
+
// real once metadata lands) so consumers driving `src` + `startTime`
|
|
11528
|
+
// together — a course player switching lessons — never flash 0:00.
|
|
11529
|
+
this.currentTime.set(Math.max(0, this.startTime()));
|
|
11530
|
+
video.load();
|
|
11531
|
+
if (this.playing()) {
|
|
11532
|
+
this.safePlay(video);
|
|
11533
|
+
}
|
|
11534
|
+
}
|
|
11535
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: VideoPlayerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
11536
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: VideoPlayerComponent, isStandalone: true, selector: "pecb-video-player", inputs: { src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: false, transformFunction: null }, poster: { classPropertyName: "poster", publicName: "poster", isSignal: true, isRequired: false, transformFunction: null }, tracks: { classPropertyName: "tracks", publicName: "tracks", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, crossOrigin: { classPropertyName: "crossOrigin", publicName: "crossOrigin", isSignal: true, isRequired: false, transformFunction: null }, preload: { classPropertyName: "preload", publicName: "preload", isSignal: true, isRequired: false, transformFunction: null }, autoplay: { classPropertyName: "autoplay", publicName: "autoplay", isSignal: true, isRequired: false, transformFunction: null }, loop: { classPropertyName: "loop", publicName: "loop", isSignal: true, isRequired: false, transformFunction: null }, playsInline: { classPropertyName: "playsInline", publicName: "playsInline", isSignal: true, isRequired: false, transformFunction: null }, startTime: { classPropertyName: "startTime", publicName: "startTime", isSignal: true, isRequired: false, transformFunction: null }, watermark: { classPropertyName: "watermark", publicName: "watermark", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, aspectRatio: { classPropertyName: "aspectRatio", publicName: "aspectRatio", isSignal: true, isRequired: false, transformFunction: null }, showCenterButton: { classPropertyName: "showCenterButton", publicName: "showCenterButton", isSignal: true, isRequired: false, transformFunction: null }, showSkipBack: { classPropertyName: "showSkipBack", publicName: "showSkipBack", isSignal: true, isRequired: false, transformFunction: null }, showVolume: { classPropertyName: "showVolume", publicName: "showVolume", isSignal: true, isRequired: false, transformFunction: null }, showTime: { classPropertyName: "showTime", publicName: "showTime", isSignal: true, isRequired: false, transformFunction: null }, showCaptions: { classPropertyName: "showCaptions", publicName: "showCaptions", isSignal: true, isRequired: false, transformFunction: null }, showSettings: { classPropertyName: "showSettings", publicName: "showSettings", isSignal: true, isRequired: false, transformFunction: null }, showFullscreen: { classPropertyName: "showFullscreen", publicName: "showFullscreen", isSignal: true, isRequired: false, transformFunction: null }, seekStep: { classPropertyName: "seekStep", publicName: "seekStep", isSignal: true, isRequired: false, transformFunction: null }, playbackRates: { classPropertyName: "playbackRates", publicName: "playbackRates", isSignal: true, isRequired: false, transformFunction: null }, labels: { classPropertyName: "labels", publicName: "labels", isSignal: true, isRequired: false, transformFunction: null }, playing: { classPropertyName: "playing", publicName: "playing", isSignal: true, isRequired: false, transformFunction: null }, currentTime: { classPropertyName: "currentTime", publicName: "currentTime", isSignal: true, isRequired: false, transformFunction: null }, volume: { classPropertyName: "volume", publicName: "volume", isSignal: true, isRequired: false, transformFunction: null }, muted: { classPropertyName: "muted", publicName: "muted", isSignal: true, isRequired: false, transformFunction: null }, playbackRate: { classPropertyName: "playbackRate", publicName: "playbackRate", isSignal: true, isRequired: false, transformFunction: null }, captionsEnabled: { classPropertyName: "captionsEnabled", publicName: "captionsEnabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { playing: "playingChange", currentTime: "currentTimeChange", volume: "volumeChange", muted: "mutedChange", playbackRate: "playbackRateChange", captionsEnabled: "captionsEnabledChange", timeUpdate: "timeUpdate", loadedMetadata: "loadedMetadata", seekEnd: "seekEnd", playbackEnded: "playbackEnded", fullscreenChange: "fullscreenChange", playerError: "playerError" }, host: { listeners: { "document:fullscreenchange": "onDocumentFullscreenChange()", "document:pointerdown": "onDocumentPointerDown($event)" }, properties: { "attr.title": "null", "style.--pecb-video-aspect-ratio": "aspectRatio()" }, classAttribute: "pecb-video-player-host" }, viewQueries: [{ propertyName: "rootRef", first: true, predicate: ["root"], descendants: true, isSignal: true }, { propertyName: "videoRef", first: true, predicate: ["video"], descendants: true, isSignal: true }, { propertyName: "progressRef", first: true, predicate: ["progress"], descendants: true, isSignal: true }, { propertyName: "settingsMenuRef", first: true, predicate: ["settingsMenu"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n #root\n [class]=\"hostClasses()\"\n role=\"region\"\n [attr.aria-label]=\"regionLabel()\"\n aria-roledescription=\"video player\"\n tabindex=\"0\"\n (keydown)=\"onRootKeydown($event)\"\n (focusout)=\"onRootFocusOut($event)\"\n>\n <!-- \u2500\u2500 Media \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <video\n #video\n class=\"pecb-video-player__media\"\n [attr.src]=\"singleSrc() ?? null\"\n [attr.poster]=\"poster() ?? null\"\n [attr.preload]=\"preload()\"\n [attr.crossorigin]=\"crossOrigin() ?? null\"\n [attr.playsinline]=\"playsInline() ? '' : null\"\n [attr.webkit-playsinline]=\"playsInline() ? '' : null\"\n [attr.autoplay]=\"autoplay() ? '' : null\"\n [attr.loop]=\"loop() ? '' : null\"\n [attr.aria-label]=\"title() ?? null\"\n (loadedmetadata)=\"onLoadedMetadata(video)\"\n (durationchange)=\"onDurationChange(video)\"\n (timeupdate)=\"onTimeUpdate(video)\"\n (progress)=\"onProgress(video)\"\n (play)=\"onPlay()\"\n (pause)=\"onPause()\"\n (ended)=\"onEnded()\"\n (waiting)=\"onWaiting()\"\n (playing)=\"onPlaying()\"\n (seeked)=\"onSeeked(video)\"\n (volumechange)=\"onVolumeChange(video)\"\n (ratechange)=\"onRateChange(video)\"\n (error)=\"onMediaError(video)\"\n (pointerdown)=\"onStagePointerDown($event)\"\n (click)=\"onStageClick()\"\n (dblclick)=\"onStageDoubleClick()\"\n >\n @if (!singleSrc()) {\n @for (s of sourceList(); track s.src) {\n <source [attr.src]=\"s.src\" [attr.type]=\"s.type ?? null\" [attr.media]=\"s.media ?? null\" />\n }\n }\n @for (t of tracks(); track t.src) {\n <track\n [attr.src]=\"t.src\"\n [attr.kind]=\"t.kind ?? 'captions'\"\n [attr.srclang]=\"t.srclang\"\n [attr.label]=\"t.label ?? null\"\n [attr.default]=\"t.default ? '' : null\"\n />\n }\n </video>\n\n <!-- \u2500\u2500 Gradient scrim + branding \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"pecb-video-player__scrim\" aria-hidden=\"true\"></div>\n\n @if (watermark()) {\n <div class=\"pecb-video-player__watermark\" aria-hidden=\"true\">{{ watermark() }}</div>\n }\n @if (label()) {\n <div class=\"pecb-video-player__label\" aria-hidden=\"true\">{{ label() }}</div>\n }\n\n <!-- Custom overlays (quizzes, end screens\u2026) projected by the consumer -->\n <ng-content select=\"[pecbVideoOverlay]\"></ng-content>\n\n <!-- \u2500\u2500 Centre state: error / buffering / play-pause \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (hasError()) {\n <div class=\"pecb-video-player__error\" role=\"alert\">\n <svg class=\"pecb-video-player__error-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <path d=\"M12 8v4.5\" />\n <circle cx=\"12\" cy=\"16\" r=\".6\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n <span>{{ text().error }}</span>\n </div>\n } @else if (isBuffering() && playing()) {\n <div class=\"pecb-video-player__spinner\" aria-hidden=\"true\"></div>\n } @else if (showCenterButton()) {\n <button\n type=\"button\"\n class=\"pecb-video-player__center\"\n [attr.aria-label]=\"hasEnded() ? text().replay : playing() ? text().pause : text().play\"\n (click)=\"togglePlay()\"\n >\n @if (hasEnded()) {\n <svg width=\"26\" height=\"26\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M3 12a9 9 0 1 0 3-6.7\" />\n <path d=\"M3 4v5h5\" />\n </svg>\n } @else if (playing()) {\n <svg width=\"26\" height=\"26\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <rect x=\"6\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n <rect x=\"14\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n </svg>\n } @else {\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <polygon points=\"6 4 20 12 6 20 6 4\" />\n </svg>\n }\n </button>\n }\n\n <!-- \u2500\u2500 Control bar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"pecb-video-player__controls\" [attr.aria-hidden]=\"controlsHidden() ? 'true' : null\">\n <!-- Progress / seek bar -->\n <div\n #progress\n class=\"pecb-video-player__progress\"\n role=\"slider\"\n [attr.tabindex]=\"controlsHidden() ? -1 : 0\"\n [attr.aria-label]=\"text().seek\"\n aria-valuemin=\"0\"\n [attr.aria-valuemax]=\"duration()\"\n [attr.aria-valuenow]=\"currentTime()\"\n [attr.aria-valuetext]=\"currentTimeLabel() + ' / ' + durationLabel()\"\n [attr.aria-disabled]=\"duration() > 0 ? null : 'true'\"\n (pointerdown)=\"onProgressPointerDown($event)\"\n (pointermove)=\"onProgressPointerMove($event)\"\n (pointerup)=\"onProgressPointerUp($event)\"\n (pointercancel)=\"onProgressPointerUp($event)\"\n (keydown)=\"onProgressKeydown($event)\"\n >\n <div class=\"pecb-video-player__track\">\n <span class=\"pecb-video-player__buffered\" [style.width.%]=\"bufferedPct()\"></span>\n <span class=\"pecb-video-player__fill\" [style.width.%]=\"progressPct()\"></span>\n <span class=\"pecb-video-player__knob\" [style.left.%]=\"progressPct()\"></span>\n </div>\n </div>\n\n <!-- Buttons row -->\n <div class=\"pecb-video-player__bar\">\n <button\n type=\"button\"\n class=\"pecb-video-player__btn\"\n [attr.aria-label]=\"hasEnded() ? text().replay : playing() ? text().pause : text().play\"\n [attr.tabindex]=\"controlsHidden() ? -1 : 0\"\n (click)=\"togglePlay()\"\n >\n @if (playing()) {\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <rect x=\"6\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n <rect x=\"14\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n </svg>\n } @else {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <polygon points=\"6 4 20 12 6 20 6 4\" />\n </svg>\n }\n </button>\n\n @if (showSkipBack()) {\n <button\n type=\"button\"\n class=\"pecb-video-player__btn\"\n [attr.aria-label]=\"skipBackLabel()\"\n [attr.tabindex]=\"controlsHidden() ? -1 : 0\"\n (click)=\"skipBack()\"\n >\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <polygon points=\"19 5 9 12 19 19 19 5\" fill=\"currentColor\" stroke=\"none\" />\n <line x1=\"6\" y1=\"5\" x2=\"6\" y2=\"19\" />\n </svg>\n </button>\n }\n\n @if (showVolume()) {\n <div class=\"pecb-video-player__volume\">\n <button\n type=\"button\"\n class=\"pecb-video-player__btn\"\n [attr.aria-label]=\"muted() ? text().unmute : text().mute\"\n [attr.aria-pressed]=\"muted()\"\n [attr.tabindex]=\"controlsHidden() ? -1 : 0\"\n (click)=\"toggleMute()\"\n >\n @switch (volumeLevel()) {\n @case ('muted') {\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <polygon points=\"4 9 8 9 12 5 12 19 8 15 4 15 4 9\" fill=\"currentColor\" stroke=\"none\" />\n <path d=\"m16 9.5 5 5M21 9.5l-5 5\" />\n </svg>\n }\n @case ('low') {\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <polygon points=\"4 9 8 9 12 5 12 19 8 15 4 15 4 9\" fill=\"currentColor\" stroke=\"none\" />\n <path d=\"M16 9a4 4 0 0 1 0 6\" />\n </svg>\n }\n @default {\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <polygon points=\"4 9 8 9 12 5 12 19 8 15 4 15 4 9\" fill=\"currentColor\" stroke=\"none\" />\n <path d=\"M16 9a4 4 0 0 1 0 6\" />\n <path d=\"M18.5 6.5a8 8 0 0 1 0 11\" />\n </svg>\n }\n }\n </button>\n <input\n class=\"pecb-video-player__volume-slider\"\n type=\"range\"\n min=\"0\"\n max=\"100\"\n step=\"1\"\n [value]=\"volumePct()\"\n [attr.aria-label]=\"text().volume\"\n [attr.aria-valuetext]=\"volumePct() + '%'\"\n [attr.tabindex]=\"controlsHidden() ? -1 : 0\"\n [style.--pecb-video-volume-pct]=\"volumePct() + '%'\"\n (input)=\"onVolumeInput($event)\"\n />\n </div>\n }\n\n @if (showTime()) {\n <span class=\"pecb-video-player__time\" aria-live=\"off\">\n <span class=\"pecb-video-player__time-current\">{{ currentTimeLabel() }}</span>\n <span class=\"pecb-video-player__time-sep\" aria-hidden=\"true\"> / </span>\n <span class=\"pecb-video-player__time-total\">{{ durationLabel() }}</span>\n </span>\n }\n\n <div class=\"pecb-video-player__spacer\"></div>\n\n @if (captionsButtonVisible()) {\n <button\n type=\"button\"\n class=\"pecb-video-player__btn\"\n [class.pecb-video-player__btn--active]=\"captionsEnabled()\"\n [attr.aria-label]=\"captionsEnabled() ? text().captionsOff : text().captionsOn\"\n [attr.aria-pressed]=\"captionsEnabled()\"\n [attr.tabindex]=\"controlsHidden() ? -1 : 0\"\n (click)=\"toggleCaptions()\"\n >\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"2\" />\n <path d=\"M10 10a2 2 0 1 0 0 4M17 10a2 2 0 1 0 0 4\" />\n </svg>\n </button>\n }\n\n @if (showSettings()) {\n <div class=\"pecb-video-player__settings\">\n <button\n type=\"button\"\n class=\"pecb-video-player__btn\"\n [id]=\"uid + '-settings-btn'\"\n [class.pecb-video-player__btn--active]=\"settingsOpen()\"\n [attr.aria-label]=\"text().settings\"\n aria-haspopup=\"menu\"\n [attr.aria-expanded]=\"settingsOpen()\"\n [attr.aria-controls]=\"uid + '-settings-menu'\"\n [attr.tabindex]=\"controlsHidden() ? -1 : 0\"\n (click)=\"toggleSettings()\"\n >\n <svg width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"3\" />\n <path d=\"M19.4 13.5a1.6 1.6 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.6 1.6 0 0 0-2.7 1.1V21a2 2 0 0 1-4 0v-.2A1.6 1.6 0 0 0 6.6 19l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1A1.6 1.6 0 0 0 3 13.5H3a2 2 0 0 1 0-4h.1a1.6 1.6 0 0 0 1.5-1.1 1.6 1.6 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.6 1.6 0 0 0 1.8.3H9.6A1.6 1.6 0 0 0 10.6 3V3a2 2 0 0 1 4 0v.1a1.6 1.6 0 0 0 2.7 1.1l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.6 1.6 0 0 0-.3 1.8v.1a1.6 1.6 0 0 0 1.5 1H21a2 2 0 0 1 0 4h-.1a1.6 1.6 0 0 0-1.5 1Z\" />\n </svg>\n </button>\n\n @if (settingsOpen()) {\n <div\n #settingsMenu\n class=\"pecb-video-player__menu\"\n role=\"menu\"\n [id]=\"uid + '-settings-menu'\"\n [attr.aria-label]=\"text().playbackSpeed\"\n (keydown)=\"onSettingsMenuKeydown($event)\"\n >\n <div class=\"pecb-video-player__menu-title\">{{ text().playbackSpeed }}</div>\n @for (rate of playbackRates(); track rate) {\n <button\n type=\"button\"\n class=\"pecb-video-player__menu-item\"\n role=\"menuitemradio\"\n [attr.aria-checked]=\"rate === playbackRate()\"\n tabindex=\"-1\"\n (click)=\"setPlaybackRate(rate)\"\n >\n <span class=\"pecb-video-player__menu-check\" aria-hidden=\"true\">\n @if (rate === playbackRate()) {\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n }\n </span>\n <span>{{ rate === 1 ? text().normalSpeed : rate + '\u00D7' }}</span>\n </button>\n }\n </div>\n }\n </div>\n }\n\n @if (showFullscreen()) {\n <button\n type=\"button\"\n class=\"pecb-video-player__btn\"\n [attr.aria-label]=\"isFullscreen() ? text().exitFullscreen : text().enterFullscreen\"\n [attr.aria-pressed]=\"isFullscreen()\"\n [attr.tabindex]=\"controlsHidden() ? -1 : 0\"\n (click)=\"toggleFullscreen()\"\n >\n @if (isFullscreen()) {\n <svg width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3M3 16h3a2 2 0 0 1 2 2v3m13-5h-3a2 2 0 0 0-2 2v3\" />\n </svg>\n } @else {\n <svg width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3M3 16v3a2 2 0 0 0 2 2h3m13-5v3a2 2 0 0 1-2 2h-3\" />\n </svg>\n }\n </button>\n }\n </div>\n </div>\n</div>\n", styles: [":host{display:block;width:100%;max-width:100%;container-type:inline-size;container-name:pecb-video-player;--pecb-video-radius: 12px;--pecb-video-bg: #0b0b0d;--pecb-video-accent: #ffffff;--pecb-video-controls-color: rgba(255, 255, 255, .92);--pecb-video-font-display: Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif;--pecb-video-font-body: \"Inter\", Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif;--pecb-video-focus-ring: #ffffff}.pecb-video-player{position:relative;width:100%;aspect-ratio:var(--pecb-video-aspect-ratio, 16/9);max-height:100%;background:var(--pecb-video-bg);border-radius:var(--pecb-video-radius);overflow:hidden;color:var(--pecb-video-controls-color);font-family:var(--pecb-video-font-display);outline:none;-webkit-tap-highlight-color:transparent;-webkit-user-select:none;user-select:none}.pecb-video-player:focus-visible{box-shadow:0 0 0 2px var(--pecb-video-bg),0 0 0 4px var(--pecb-video-focus-ring)}.pecb-video-player__media{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;background:var(--pecb-video-bg);display:block;cursor:pointer}.pecb-video-player__media::cue{font-family:var(--pecb-video-font-body);font-size:.95em;background:#0009}.pecb-video-player__scrim{position:absolute;inset:0;pointer-events:none;background:linear-gradient(180deg,#0000001f,#0000 30% 60%,#0000008c)}.pecb-video-player__watermark,.pecb-video-player__label{position:absolute;bottom:64px;pointer-events:none;font-family:var(--pecb-video-font-display);font-weight:800;color:#ffffffeb;white-space:nowrap;max-width:45%;overflow:hidden;text-overflow:ellipsis;text-shadow:0 1px 2px rgba(0,0,0,.25);transition:opacity .22s ease}.pecb-video-player__watermark{left:24px;font-size:22px;letter-spacing:.04em}.pecb-video-player__label{right:24px;font-size:26px;letter-spacing:.05em;text-transform:uppercase}.pecb-video-player__center{padding:0;border:none;background:none;font:inherit;color:inherit;position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:64px;height:64px;border-radius:50%;background:#1414168c;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);color:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:background .15s ease-in-out,transform .15s ease-in-out,opacity .22s ease}.pecb-video-player__center:focus{outline:none}.pecb-video-player__center:hover{background:#141416b8}.pecb-video-player__center:active{transform:translate(-50%,-50%) scale(.96)}.pecb-video-player__center:focus-visible{outline:2px solid var(--pecb-video-focus-ring);outline-offset:2px}.pecb-video-player__center svg polygon{transform:translate(1px)}.pecb-video-player__spinner{position:absolute;left:50%;top:50%;width:48px;height:48px;margin:-24px 0 0 -24px;border-radius:50%;border:3px solid rgba(255,255,255,.25);border-top-color:#fff;animation:pecb-video-spin .9s linear infinite;pointer-events:none}.pecb-video-player__error{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:16px;text-align:center;font-family:var(--pecb-video-font-body);font-size:14px;color:#ffffffd9;background:#00000073}.pecb-video-player__error-icon{width:36px;height:36px;opacity:.9}.pecb-video-player__controls{position:absolute;left:0;right:0;bottom:0;display:flex;flex-direction:column;gap:8px;padding:10px 16px 12px;background:linear-gradient(0deg,#0009,#0000);transition:opacity .22s ease,transform .22s ease}.pecb-video-player__progress{position:relative;padding:6px 0;margin:-6px 0;cursor:pointer;touch-action:none;border-radius:6px;outline:none}.pecb-video-player__progress[aria-disabled=true]{cursor:default}.pecb-video-player__progress:focus-visible .pecb-video-player__track{box-shadow:0 0 0 2px #0009,0 0 0 4px var(--pecb-video-focus-ring)}.pecb-video-player__progress:hover .pecb-video-player__knob,.pecb-video-player__progress:focus-visible .pecb-video-player__knob{transform:translate(-50%,-50%) scale(1.15)}.pecb-video-player__track{position:relative;height:4px;border-radius:4px;background:#ffffff47;overflow:visible}.pecb-video-player__buffered,.pecb-video-player__fill{position:absolute;left:0;top:0;bottom:0;border-radius:4px;pointer-events:none}.pecb-video-player__buffered{background:#ffffff38}.pecb-video-player__fill{background:var(--pecb-video-accent)}.pecb-video-player__knob{position:absolute;top:50%;width:11px;height:11px;border-radius:50%;background:var(--pecb-video-accent);transform:translate(-50%,-50%);pointer-events:none;box-shadow:0 1px 3px #00000059;transition:transform .15s ease-in-out}.pecb-video-player__bar{display:flex;align-items:center;gap:6px;color:var(--pecb-video-controls-color);min-height:28px}.pecb-video-player__btn{padding:0;border:none;background:none;font:inherit;flex:0 0 auto;display:flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:6px;color:inherit;opacity:.92;cursor:pointer;transition:opacity .15s ease-in-out,background .15s ease-in-out}.pecb-video-player__btn:focus{outline:none}.pecb-video-player__btn:hover{opacity:1;background:#ffffff1a}.pecb-video-player__btn:focus-visible{opacity:1;outline:2px solid var(--pecb-video-focus-ring);outline-offset:1px}.pecb-video-player__btn--active{opacity:1;position:relative}.pecb-video-player__btn--active:after{content:\"\";position:absolute;left:8px;right:8px;bottom:2px;height:2px;border-radius:2px;background:var(--pecb-video-accent)}.pecb-video-player__btn svg{display:block;flex:0 0 auto}.pecb-video-player__spacer{flex:1 1 auto;min-width:0}.pecb-video-player__time{font-family:var(--pecb-video-font-body);font-size:12.5px;font-weight:500;font-variant-numeric:tabular-nums;white-space:nowrap;margin-left:4px;opacity:.95}.pecb-video-player__volume{display:flex;align-items:center;flex:0 0 auto}.pecb-video-player__volume-slider{width:0;opacity:0;margin:0;height:28px;cursor:pointer;appearance:none;-webkit-appearance:none;background:transparent;transition:width .15s ease-in-out,opacity .15s ease-in-out,margin .15s ease-in-out}.pecb-video-player__volume-slider:focus-visible{outline:2px solid var(--pecb-video-focus-ring);outline-offset:1px;border-radius:6px}.pecb-video-player__volume-slider::-webkit-slider-runnable-track{height:4px;border-radius:4px;background:linear-gradient(to right,var(--pecb-video-accent) 0 var(--pecb-video-volume-pct, 100%),rgba(255,255,255,.28) var(--pecb-video-volume-pct, 100%) 100%)}.pecb-video-player__volume-slider::-moz-range-track{height:4px;border-radius:4px;background:#ffffff47}.pecb-video-player__volume-slider::-moz-range-progress{height:4px;border-radius:4px;background:var(--pecb-video-accent)}.pecb-video-player__volume-slider::-webkit-slider-thumb{-webkit-appearance:none;width:11px;height:11px;margin-top:-3.5px;border-radius:50%;border:none;background:var(--pecb-video-accent)}.pecb-video-player__volume-slider::-moz-range-thumb{width:11px;height:11px;border-radius:50%;border:none;background:var(--pecb-video-accent)}.pecb-video-player__volume:hover .pecb-video-player__volume-slider,.pecb-video-player__volume:focus-within .pecb-video-player__volume-slider{width:72px;opacity:1;margin-left:4px;margin-right:6px}.pecb-video-player__settings{position:relative;flex:0 0 auto}.pecb-video-player__menu{position:absolute;right:0;bottom:calc(100% + 10px);min-width:168px;padding:6px;border-radius:8px;background:#18181bf5;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -2px #0000000d;color:#fff;z-index:2;font-family:var(--pecb-video-font-body);animation:pecb-video-menu-in .14s ease-out}.pecb-video-player__menu-title{padding:6px 10px 8px;font-size:12px;font-weight:500;color:#fff9;letter-spacing:.02em}.pecb-video-player__menu-item{border:none;background:none;font:inherit;display:flex;align-items:center;gap:8px;width:100%;padding:7px 10px;border-radius:6px;font-size:13px;font-weight:500;color:inherit;text-align:left;cursor:pointer}.pecb-video-player__menu-item:focus{outline:none}.pecb-video-player__menu-item:hover,.pecb-video-player__menu-item:focus-visible{background:#ffffff1a;outline:none}.pecb-video-player__menu-item[aria-checked=true]{font-weight:700}.pecb-video-player__menu-check{width:14px;height:14px;display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto}.pecb-video-player--idle,.pecb-video-player--idle .pecb-video-player__media{cursor:none}.pecb-video-player--idle .pecb-video-player__controls,.pecb-video-player--idle .pecb-video-player__center{opacity:0;pointer-events:none}.pecb-video-player--idle .pecb-video-player__controls{transform:translateY(6px)}.pecb-video-player--fullscreen{width:100%;height:100%;aspect-ratio:auto;border-radius:0;max-height:none}.pecb-video-player--fullscreen .pecb-video-player__media{object-fit:contain}.pecb-video-player--error .pecb-video-player__media{cursor:default}@container pecb-video-player (max-width: 640px){.pecb-video-player__watermark,.pecb-video-player__label{bottom:56px}.pecb-video-player__watermark{left:16px;font-size:17px}.pecb-video-player__label{right:16px;font-size:20px}.pecb-video-player__center{width:54px;height:54px}.pecb-video-player__center svg{width:22px;height:22px}.pecb-video-player__controls{padding:8px 12px 10px;gap:6px}.pecb-video-player__bar{gap:4px}.pecb-video-player__time{font-size:11.5px}.pecb-video-player__volume:hover .pecb-video-player__volume-slider,.pecb-video-player__volume:focus-within .pecb-video-player__volume-slider{width:56px}}@container pecb-video-player (max-width: 400px){.pecb-video-player__watermark,.pecb-video-player__label{bottom:50px}.pecb-video-player__watermark{font-size:14px}.pecb-video-player__label{font-size:16px}.pecb-video-player__center{width:46px;height:46px}.pecb-video-player__center svg{width:19px;height:19px}.pecb-video-player__bar{gap:2px}.pecb-video-player__time{font-size:11px;margin-left:2px}.pecb-video-player__time-sep,.pecb-video-player__time-total{display:none}.pecb-video-player__menu{min-width:148px}}@media (hover: none) and (pointer: coarse){.pecb-video-player__volume-slider{display:none}}@media (prefers-reduced-motion: reduce){.pecb-video-player,.pecb-video-player *,.pecb-video-player *:before,.pecb-video-player *:after{transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}.pecb-video-player__spinner{animation:none;border-top-color:#ffffff40}}@keyframes pecb-video-spin{to{transform:rotate(360deg)}}@keyframes pecb-video-menu-in{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
11537
|
+
}
|
|
11538
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: VideoPlayerComponent, decorators: [{
|
|
11539
|
+
type: Component,
|
|
11540
|
+
args: [{ selector: 'pecb-video-player', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
11541
|
+
class: 'pecb-video-player-host',
|
|
11542
|
+
// `title` is a component input — keep it off the host so the browser doesn't
|
|
11543
|
+
// show a native tooltip over the whole stage.
|
|
11544
|
+
'[attr.title]': 'null',
|
|
11545
|
+
'[style.--pecb-video-aspect-ratio]': 'aspectRatio()',
|
|
11546
|
+
'(document:fullscreenchange)': 'onDocumentFullscreenChange()',
|
|
11547
|
+
'(document:pointerdown)': 'onDocumentPointerDown($event)',
|
|
11548
|
+
}, template: "<div\n #root\n [class]=\"hostClasses()\"\n role=\"region\"\n [attr.aria-label]=\"regionLabel()\"\n aria-roledescription=\"video player\"\n tabindex=\"0\"\n (keydown)=\"onRootKeydown($event)\"\n (focusout)=\"onRootFocusOut($event)\"\n>\n <!-- \u2500\u2500 Media \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <video\n #video\n class=\"pecb-video-player__media\"\n [attr.src]=\"singleSrc() ?? null\"\n [attr.poster]=\"poster() ?? null\"\n [attr.preload]=\"preload()\"\n [attr.crossorigin]=\"crossOrigin() ?? null\"\n [attr.playsinline]=\"playsInline() ? '' : null\"\n [attr.webkit-playsinline]=\"playsInline() ? '' : null\"\n [attr.autoplay]=\"autoplay() ? '' : null\"\n [attr.loop]=\"loop() ? '' : null\"\n [attr.aria-label]=\"title() ?? null\"\n (loadedmetadata)=\"onLoadedMetadata(video)\"\n (durationchange)=\"onDurationChange(video)\"\n (timeupdate)=\"onTimeUpdate(video)\"\n (progress)=\"onProgress(video)\"\n (play)=\"onPlay()\"\n (pause)=\"onPause()\"\n (ended)=\"onEnded()\"\n (waiting)=\"onWaiting()\"\n (playing)=\"onPlaying()\"\n (seeked)=\"onSeeked(video)\"\n (volumechange)=\"onVolumeChange(video)\"\n (ratechange)=\"onRateChange(video)\"\n (error)=\"onMediaError(video)\"\n (pointerdown)=\"onStagePointerDown($event)\"\n (click)=\"onStageClick()\"\n (dblclick)=\"onStageDoubleClick()\"\n >\n @if (!singleSrc()) {\n @for (s of sourceList(); track s.src) {\n <source [attr.src]=\"s.src\" [attr.type]=\"s.type ?? null\" [attr.media]=\"s.media ?? null\" />\n }\n }\n @for (t of tracks(); track t.src) {\n <track\n [attr.src]=\"t.src\"\n [attr.kind]=\"t.kind ?? 'captions'\"\n [attr.srclang]=\"t.srclang\"\n [attr.label]=\"t.label ?? null\"\n [attr.default]=\"t.default ? '' : null\"\n />\n }\n </video>\n\n <!-- \u2500\u2500 Gradient scrim + branding \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"pecb-video-player__scrim\" aria-hidden=\"true\"></div>\n\n @if (watermark()) {\n <div class=\"pecb-video-player__watermark\" aria-hidden=\"true\">{{ watermark() }}</div>\n }\n @if (label()) {\n <div class=\"pecb-video-player__label\" aria-hidden=\"true\">{{ label() }}</div>\n }\n\n <!-- Custom overlays (quizzes, end screens\u2026) projected by the consumer -->\n <ng-content select=\"[pecbVideoOverlay]\"></ng-content>\n\n <!-- \u2500\u2500 Centre state: error / buffering / play-pause \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (hasError()) {\n <div class=\"pecb-video-player__error\" role=\"alert\">\n <svg class=\"pecb-video-player__error-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <path d=\"M12 8v4.5\" />\n <circle cx=\"12\" cy=\"16\" r=\".6\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n <span>{{ text().error }}</span>\n </div>\n } @else if (isBuffering() && playing()) {\n <div class=\"pecb-video-player__spinner\" aria-hidden=\"true\"></div>\n } @else if (showCenterButton()) {\n <button\n type=\"button\"\n class=\"pecb-video-player__center\"\n [attr.aria-label]=\"hasEnded() ? text().replay : playing() ? text().pause : text().play\"\n (click)=\"togglePlay()\"\n >\n @if (hasEnded()) {\n <svg width=\"26\" height=\"26\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M3 12a9 9 0 1 0 3-6.7\" />\n <path d=\"M3 4v5h5\" />\n </svg>\n } @else if (playing()) {\n <svg width=\"26\" height=\"26\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <rect x=\"6\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n <rect x=\"14\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n </svg>\n } @else {\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <polygon points=\"6 4 20 12 6 20 6 4\" />\n </svg>\n }\n </button>\n }\n\n <!-- \u2500\u2500 Control bar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"pecb-video-player__controls\" [attr.aria-hidden]=\"controlsHidden() ? 'true' : null\">\n <!-- Progress / seek bar -->\n <div\n #progress\n class=\"pecb-video-player__progress\"\n role=\"slider\"\n [attr.tabindex]=\"controlsHidden() ? -1 : 0\"\n [attr.aria-label]=\"text().seek\"\n aria-valuemin=\"0\"\n [attr.aria-valuemax]=\"duration()\"\n [attr.aria-valuenow]=\"currentTime()\"\n [attr.aria-valuetext]=\"currentTimeLabel() + ' / ' + durationLabel()\"\n [attr.aria-disabled]=\"duration() > 0 ? null : 'true'\"\n (pointerdown)=\"onProgressPointerDown($event)\"\n (pointermove)=\"onProgressPointerMove($event)\"\n (pointerup)=\"onProgressPointerUp($event)\"\n (pointercancel)=\"onProgressPointerUp($event)\"\n (keydown)=\"onProgressKeydown($event)\"\n >\n <div class=\"pecb-video-player__track\">\n <span class=\"pecb-video-player__buffered\" [style.width.%]=\"bufferedPct()\"></span>\n <span class=\"pecb-video-player__fill\" [style.width.%]=\"progressPct()\"></span>\n <span class=\"pecb-video-player__knob\" [style.left.%]=\"progressPct()\"></span>\n </div>\n </div>\n\n <!-- Buttons row -->\n <div class=\"pecb-video-player__bar\">\n <button\n type=\"button\"\n class=\"pecb-video-player__btn\"\n [attr.aria-label]=\"hasEnded() ? text().replay : playing() ? text().pause : text().play\"\n [attr.tabindex]=\"controlsHidden() ? -1 : 0\"\n (click)=\"togglePlay()\"\n >\n @if (playing()) {\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <rect x=\"6\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n <rect x=\"14\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n </svg>\n } @else {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <polygon points=\"6 4 20 12 6 20 6 4\" />\n </svg>\n }\n </button>\n\n @if (showSkipBack()) {\n <button\n type=\"button\"\n class=\"pecb-video-player__btn\"\n [attr.aria-label]=\"skipBackLabel()\"\n [attr.tabindex]=\"controlsHidden() ? -1 : 0\"\n (click)=\"skipBack()\"\n >\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <polygon points=\"19 5 9 12 19 19 19 5\" fill=\"currentColor\" stroke=\"none\" />\n <line x1=\"6\" y1=\"5\" x2=\"6\" y2=\"19\" />\n </svg>\n </button>\n }\n\n @if (showVolume()) {\n <div class=\"pecb-video-player__volume\">\n <button\n type=\"button\"\n class=\"pecb-video-player__btn\"\n [attr.aria-label]=\"muted() ? text().unmute : text().mute\"\n [attr.aria-pressed]=\"muted()\"\n [attr.tabindex]=\"controlsHidden() ? -1 : 0\"\n (click)=\"toggleMute()\"\n >\n @switch (volumeLevel()) {\n @case ('muted') {\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <polygon points=\"4 9 8 9 12 5 12 19 8 15 4 15 4 9\" fill=\"currentColor\" stroke=\"none\" />\n <path d=\"m16 9.5 5 5M21 9.5l-5 5\" />\n </svg>\n }\n @case ('low') {\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <polygon points=\"4 9 8 9 12 5 12 19 8 15 4 15 4 9\" fill=\"currentColor\" stroke=\"none\" />\n <path d=\"M16 9a4 4 0 0 1 0 6\" />\n </svg>\n }\n @default {\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <polygon points=\"4 9 8 9 12 5 12 19 8 15 4 15 4 9\" fill=\"currentColor\" stroke=\"none\" />\n <path d=\"M16 9a4 4 0 0 1 0 6\" />\n <path d=\"M18.5 6.5a8 8 0 0 1 0 11\" />\n </svg>\n }\n }\n </button>\n <input\n class=\"pecb-video-player__volume-slider\"\n type=\"range\"\n min=\"0\"\n max=\"100\"\n step=\"1\"\n [value]=\"volumePct()\"\n [attr.aria-label]=\"text().volume\"\n [attr.aria-valuetext]=\"volumePct() + '%'\"\n [attr.tabindex]=\"controlsHidden() ? -1 : 0\"\n [style.--pecb-video-volume-pct]=\"volumePct() + '%'\"\n (input)=\"onVolumeInput($event)\"\n />\n </div>\n }\n\n @if (showTime()) {\n <span class=\"pecb-video-player__time\" aria-live=\"off\">\n <span class=\"pecb-video-player__time-current\">{{ currentTimeLabel() }}</span>\n <span class=\"pecb-video-player__time-sep\" aria-hidden=\"true\"> / </span>\n <span class=\"pecb-video-player__time-total\">{{ durationLabel() }}</span>\n </span>\n }\n\n <div class=\"pecb-video-player__spacer\"></div>\n\n @if (captionsButtonVisible()) {\n <button\n type=\"button\"\n class=\"pecb-video-player__btn\"\n [class.pecb-video-player__btn--active]=\"captionsEnabled()\"\n [attr.aria-label]=\"captionsEnabled() ? text().captionsOff : text().captionsOn\"\n [attr.aria-pressed]=\"captionsEnabled()\"\n [attr.tabindex]=\"controlsHidden() ? -1 : 0\"\n (click)=\"toggleCaptions()\"\n >\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"2\" />\n <path d=\"M10 10a2 2 0 1 0 0 4M17 10a2 2 0 1 0 0 4\" />\n </svg>\n </button>\n }\n\n @if (showSettings()) {\n <div class=\"pecb-video-player__settings\">\n <button\n type=\"button\"\n class=\"pecb-video-player__btn\"\n [id]=\"uid + '-settings-btn'\"\n [class.pecb-video-player__btn--active]=\"settingsOpen()\"\n [attr.aria-label]=\"text().settings\"\n aria-haspopup=\"menu\"\n [attr.aria-expanded]=\"settingsOpen()\"\n [attr.aria-controls]=\"uid + '-settings-menu'\"\n [attr.tabindex]=\"controlsHidden() ? -1 : 0\"\n (click)=\"toggleSettings()\"\n >\n <svg width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"3\" />\n <path d=\"M19.4 13.5a1.6 1.6 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.6 1.6 0 0 0-2.7 1.1V21a2 2 0 0 1-4 0v-.2A1.6 1.6 0 0 0 6.6 19l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1A1.6 1.6 0 0 0 3 13.5H3a2 2 0 0 1 0-4h.1a1.6 1.6 0 0 0 1.5-1.1 1.6 1.6 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.6 1.6 0 0 0 1.8.3H9.6A1.6 1.6 0 0 0 10.6 3V3a2 2 0 0 1 4 0v.1a1.6 1.6 0 0 0 2.7 1.1l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.6 1.6 0 0 0-.3 1.8v.1a1.6 1.6 0 0 0 1.5 1H21a2 2 0 0 1 0 4h-.1a1.6 1.6 0 0 0-1.5 1Z\" />\n </svg>\n </button>\n\n @if (settingsOpen()) {\n <div\n #settingsMenu\n class=\"pecb-video-player__menu\"\n role=\"menu\"\n [id]=\"uid + '-settings-menu'\"\n [attr.aria-label]=\"text().playbackSpeed\"\n (keydown)=\"onSettingsMenuKeydown($event)\"\n >\n <div class=\"pecb-video-player__menu-title\">{{ text().playbackSpeed }}</div>\n @for (rate of playbackRates(); track rate) {\n <button\n type=\"button\"\n class=\"pecb-video-player__menu-item\"\n role=\"menuitemradio\"\n [attr.aria-checked]=\"rate === playbackRate()\"\n tabindex=\"-1\"\n (click)=\"setPlaybackRate(rate)\"\n >\n <span class=\"pecb-video-player__menu-check\" aria-hidden=\"true\">\n @if (rate === playbackRate()) {\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n }\n </span>\n <span>{{ rate === 1 ? text().normalSpeed : rate + '\u00D7' }}</span>\n </button>\n }\n </div>\n }\n </div>\n }\n\n @if (showFullscreen()) {\n <button\n type=\"button\"\n class=\"pecb-video-player__btn\"\n [attr.aria-label]=\"isFullscreen() ? text().exitFullscreen : text().enterFullscreen\"\n [attr.aria-pressed]=\"isFullscreen()\"\n [attr.tabindex]=\"controlsHidden() ? -1 : 0\"\n (click)=\"toggleFullscreen()\"\n >\n @if (isFullscreen()) {\n <svg width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3M3 16h3a2 2 0 0 1 2 2v3m13-5h-3a2 2 0 0 0-2 2v3\" />\n </svg>\n } @else {\n <svg width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3M3 16v3a2 2 0 0 0 2 2h3m13-5v3a2 2 0 0 1-2 2h-3\" />\n </svg>\n }\n </button>\n }\n </div>\n </div>\n</div>\n", styles: [":host{display:block;width:100%;max-width:100%;container-type:inline-size;container-name:pecb-video-player;--pecb-video-radius: 12px;--pecb-video-bg: #0b0b0d;--pecb-video-accent: #ffffff;--pecb-video-controls-color: rgba(255, 255, 255, .92);--pecb-video-font-display: Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif;--pecb-video-font-body: \"Inter\", Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif;--pecb-video-focus-ring: #ffffff}.pecb-video-player{position:relative;width:100%;aspect-ratio:var(--pecb-video-aspect-ratio, 16/9);max-height:100%;background:var(--pecb-video-bg);border-radius:var(--pecb-video-radius);overflow:hidden;color:var(--pecb-video-controls-color);font-family:var(--pecb-video-font-display);outline:none;-webkit-tap-highlight-color:transparent;-webkit-user-select:none;user-select:none}.pecb-video-player:focus-visible{box-shadow:0 0 0 2px var(--pecb-video-bg),0 0 0 4px var(--pecb-video-focus-ring)}.pecb-video-player__media{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;background:var(--pecb-video-bg);display:block;cursor:pointer}.pecb-video-player__media::cue{font-family:var(--pecb-video-font-body);font-size:.95em;background:#0009}.pecb-video-player__scrim{position:absolute;inset:0;pointer-events:none;background:linear-gradient(180deg,#0000001f,#0000 30% 60%,#0000008c)}.pecb-video-player__watermark,.pecb-video-player__label{position:absolute;bottom:64px;pointer-events:none;font-family:var(--pecb-video-font-display);font-weight:800;color:#ffffffeb;white-space:nowrap;max-width:45%;overflow:hidden;text-overflow:ellipsis;text-shadow:0 1px 2px rgba(0,0,0,.25);transition:opacity .22s ease}.pecb-video-player__watermark{left:24px;font-size:22px;letter-spacing:.04em}.pecb-video-player__label{right:24px;font-size:26px;letter-spacing:.05em;text-transform:uppercase}.pecb-video-player__center{padding:0;border:none;background:none;font:inherit;color:inherit;position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:64px;height:64px;border-radius:50%;background:#1414168c;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);color:#fff;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:background .15s ease-in-out,transform .15s ease-in-out,opacity .22s ease}.pecb-video-player__center:focus{outline:none}.pecb-video-player__center:hover{background:#141416b8}.pecb-video-player__center:active{transform:translate(-50%,-50%) scale(.96)}.pecb-video-player__center:focus-visible{outline:2px solid var(--pecb-video-focus-ring);outline-offset:2px}.pecb-video-player__center svg polygon{transform:translate(1px)}.pecb-video-player__spinner{position:absolute;left:50%;top:50%;width:48px;height:48px;margin:-24px 0 0 -24px;border-radius:50%;border:3px solid rgba(255,255,255,.25);border-top-color:#fff;animation:pecb-video-spin .9s linear infinite;pointer-events:none}.pecb-video-player__error{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:16px;text-align:center;font-family:var(--pecb-video-font-body);font-size:14px;color:#ffffffd9;background:#00000073}.pecb-video-player__error-icon{width:36px;height:36px;opacity:.9}.pecb-video-player__controls{position:absolute;left:0;right:0;bottom:0;display:flex;flex-direction:column;gap:8px;padding:10px 16px 12px;background:linear-gradient(0deg,#0009,#0000);transition:opacity .22s ease,transform .22s ease}.pecb-video-player__progress{position:relative;padding:6px 0;margin:-6px 0;cursor:pointer;touch-action:none;border-radius:6px;outline:none}.pecb-video-player__progress[aria-disabled=true]{cursor:default}.pecb-video-player__progress:focus-visible .pecb-video-player__track{box-shadow:0 0 0 2px #0009,0 0 0 4px var(--pecb-video-focus-ring)}.pecb-video-player__progress:hover .pecb-video-player__knob,.pecb-video-player__progress:focus-visible .pecb-video-player__knob{transform:translate(-50%,-50%) scale(1.15)}.pecb-video-player__track{position:relative;height:4px;border-radius:4px;background:#ffffff47;overflow:visible}.pecb-video-player__buffered,.pecb-video-player__fill{position:absolute;left:0;top:0;bottom:0;border-radius:4px;pointer-events:none}.pecb-video-player__buffered{background:#ffffff38}.pecb-video-player__fill{background:var(--pecb-video-accent)}.pecb-video-player__knob{position:absolute;top:50%;width:11px;height:11px;border-radius:50%;background:var(--pecb-video-accent);transform:translate(-50%,-50%);pointer-events:none;box-shadow:0 1px 3px #00000059;transition:transform .15s ease-in-out}.pecb-video-player__bar{display:flex;align-items:center;gap:6px;color:var(--pecb-video-controls-color);min-height:28px}.pecb-video-player__btn{padding:0;border:none;background:none;font:inherit;flex:0 0 auto;display:flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:6px;color:inherit;opacity:.92;cursor:pointer;transition:opacity .15s ease-in-out,background .15s ease-in-out}.pecb-video-player__btn:focus{outline:none}.pecb-video-player__btn:hover{opacity:1;background:#ffffff1a}.pecb-video-player__btn:focus-visible{opacity:1;outline:2px solid var(--pecb-video-focus-ring);outline-offset:1px}.pecb-video-player__btn--active{opacity:1;position:relative}.pecb-video-player__btn--active:after{content:\"\";position:absolute;left:8px;right:8px;bottom:2px;height:2px;border-radius:2px;background:var(--pecb-video-accent)}.pecb-video-player__btn svg{display:block;flex:0 0 auto}.pecb-video-player__spacer{flex:1 1 auto;min-width:0}.pecb-video-player__time{font-family:var(--pecb-video-font-body);font-size:12.5px;font-weight:500;font-variant-numeric:tabular-nums;white-space:nowrap;margin-left:4px;opacity:.95}.pecb-video-player__volume{display:flex;align-items:center;flex:0 0 auto}.pecb-video-player__volume-slider{width:0;opacity:0;margin:0;height:28px;cursor:pointer;appearance:none;-webkit-appearance:none;background:transparent;transition:width .15s ease-in-out,opacity .15s ease-in-out,margin .15s ease-in-out}.pecb-video-player__volume-slider:focus-visible{outline:2px solid var(--pecb-video-focus-ring);outline-offset:1px;border-radius:6px}.pecb-video-player__volume-slider::-webkit-slider-runnable-track{height:4px;border-radius:4px;background:linear-gradient(to right,var(--pecb-video-accent) 0 var(--pecb-video-volume-pct, 100%),rgba(255,255,255,.28) var(--pecb-video-volume-pct, 100%) 100%)}.pecb-video-player__volume-slider::-moz-range-track{height:4px;border-radius:4px;background:#ffffff47}.pecb-video-player__volume-slider::-moz-range-progress{height:4px;border-radius:4px;background:var(--pecb-video-accent)}.pecb-video-player__volume-slider::-webkit-slider-thumb{-webkit-appearance:none;width:11px;height:11px;margin-top:-3.5px;border-radius:50%;border:none;background:var(--pecb-video-accent)}.pecb-video-player__volume-slider::-moz-range-thumb{width:11px;height:11px;border-radius:50%;border:none;background:var(--pecb-video-accent)}.pecb-video-player__volume:hover .pecb-video-player__volume-slider,.pecb-video-player__volume:focus-within .pecb-video-player__volume-slider{width:72px;opacity:1;margin-left:4px;margin-right:6px}.pecb-video-player__settings{position:relative;flex:0 0 auto}.pecb-video-player__menu{position:absolute;right:0;bottom:calc(100% + 10px);min-width:168px;padding:6px;border-radius:8px;background:#18181bf5;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -2px #0000000d;color:#fff;z-index:2;font-family:var(--pecb-video-font-body);animation:pecb-video-menu-in .14s ease-out}.pecb-video-player__menu-title{padding:6px 10px 8px;font-size:12px;font-weight:500;color:#fff9;letter-spacing:.02em}.pecb-video-player__menu-item{border:none;background:none;font:inherit;display:flex;align-items:center;gap:8px;width:100%;padding:7px 10px;border-radius:6px;font-size:13px;font-weight:500;color:inherit;text-align:left;cursor:pointer}.pecb-video-player__menu-item:focus{outline:none}.pecb-video-player__menu-item:hover,.pecb-video-player__menu-item:focus-visible{background:#ffffff1a;outline:none}.pecb-video-player__menu-item[aria-checked=true]{font-weight:700}.pecb-video-player__menu-check{width:14px;height:14px;display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto}.pecb-video-player--idle,.pecb-video-player--idle .pecb-video-player__media{cursor:none}.pecb-video-player--idle .pecb-video-player__controls,.pecb-video-player--idle .pecb-video-player__center{opacity:0;pointer-events:none}.pecb-video-player--idle .pecb-video-player__controls{transform:translateY(6px)}.pecb-video-player--fullscreen{width:100%;height:100%;aspect-ratio:auto;border-radius:0;max-height:none}.pecb-video-player--fullscreen .pecb-video-player__media{object-fit:contain}.pecb-video-player--error .pecb-video-player__media{cursor:default}@container pecb-video-player (max-width: 640px){.pecb-video-player__watermark,.pecb-video-player__label{bottom:56px}.pecb-video-player__watermark{left:16px;font-size:17px}.pecb-video-player__label{right:16px;font-size:20px}.pecb-video-player__center{width:54px;height:54px}.pecb-video-player__center svg{width:22px;height:22px}.pecb-video-player__controls{padding:8px 12px 10px;gap:6px}.pecb-video-player__bar{gap:4px}.pecb-video-player__time{font-size:11.5px}.pecb-video-player__volume:hover .pecb-video-player__volume-slider,.pecb-video-player__volume:focus-within .pecb-video-player__volume-slider{width:56px}}@container pecb-video-player (max-width: 400px){.pecb-video-player__watermark,.pecb-video-player__label{bottom:50px}.pecb-video-player__watermark{font-size:14px}.pecb-video-player__label{font-size:16px}.pecb-video-player__center{width:46px;height:46px}.pecb-video-player__center svg{width:19px;height:19px}.pecb-video-player__bar{gap:2px}.pecb-video-player__time{font-size:11px;margin-left:2px}.pecb-video-player__time-sep,.pecb-video-player__time-total{display:none}.pecb-video-player__menu{min-width:148px}}@media (hover: none) and (pointer: coarse){.pecb-video-player__volume-slider{display:none}}@media (prefers-reduced-motion: reduce){.pecb-video-player,.pecb-video-player *,.pecb-video-player *:before,.pecb-video-player *:after{transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}.pecb-video-player__spinner{animation:none;border-top-color:#ffffff40}}@keyframes pecb-video-spin{to{transform:rotate(360deg)}}@keyframes pecb-video-menu-in{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}\n"] }]
|
|
11549
|
+
}], ctorParameters: () => [], propDecorators: { rootRef: [{ type: i0.ViewChild, args: ['root', { isSignal: true }] }], videoRef: [{ type: i0.ViewChild, args: ['video', { isSignal: true }] }], progressRef: [{ type: i0.ViewChild, args: ['progress', { isSignal: true }] }], settingsMenuRef: [{ type: i0.ViewChild, args: ['settingsMenu', { isSignal: true }] }], src: [{ type: i0.Input, args: [{ isSignal: true, alias: "src", required: false }] }], poster: [{ type: i0.Input, args: [{ isSignal: true, alias: "poster", required: false }] }], tracks: [{ type: i0.Input, args: [{ isSignal: true, alias: "tracks", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], crossOrigin: [{ type: i0.Input, args: [{ isSignal: true, alias: "crossOrigin", required: false }] }], preload: [{ type: i0.Input, args: [{ isSignal: true, alias: "preload", required: false }] }], autoplay: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoplay", required: false }] }], loop: [{ type: i0.Input, args: [{ isSignal: true, alias: "loop", required: false }] }], playsInline: [{ type: i0.Input, args: [{ isSignal: true, alias: "playsInline", required: false }] }], startTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "startTime", required: false }] }], watermark: [{ type: i0.Input, args: [{ isSignal: true, alias: "watermark", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], aspectRatio: [{ type: i0.Input, args: [{ isSignal: true, alias: "aspectRatio", required: false }] }], showCenterButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCenterButton", required: false }] }], showSkipBack: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSkipBack", required: false }] }], showVolume: [{ type: i0.Input, args: [{ isSignal: true, alias: "showVolume", required: false }] }], showTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTime", required: false }] }], showCaptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCaptions", required: false }] }], showSettings: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSettings", required: false }] }], showFullscreen: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFullscreen", required: false }] }], seekStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "seekStep", required: false }] }], playbackRates: [{ type: i0.Input, args: [{ isSignal: true, alias: "playbackRates", required: false }] }], labels: [{ type: i0.Input, args: [{ isSignal: true, alias: "labels", required: false }] }], playing: [{ type: i0.Input, args: [{ isSignal: true, alias: "playing", required: false }] }, { type: i0.Output, args: ["playingChange"] }], currentTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentTime", required: false }] }, { type: i0.Output, args: ["currentTimeChange"] }], volume: [{ type: i0.Input, args: [{ isSignal: true, alias: "volume", required: false }] }, { type: i0.Output, args: ["volumeChange"] }], muted: [{ type: i0.Input, args: [{ isSignal: true, alias: "muted", required: false }] }, { type: i0.Output, args: ["mutedChange"] }], playbackRate: [{ type: i0.Input, args: [{ isSignal: true, alias: "playbackRate", required: false }] }, { type: i0.Output, args: ["playbackRateChange"] }], captionsEnabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "captionsEnabled", required: false }] }, { type: i0.Output, args: ["captionsEnabledChange"] }], timeUpdate: [{ type: i0.Output, args: ["timeUpdate"] }], loadedMetadata: [{ type: i0.Output, args: ["loadedMetadata"] }], seekEnd: [{ type: i0.Output, args: ["seekEnd"] }], playbackEnded: [{ type: i0.Output, args: ["playbackEnded"] }], fullscreenChange: [{ type: i0.Output, args: ["fullscreenChange"] }], playerError: [{ type: i0.Output, args: ["playerError"] }] } });
|
|
11550
|
+
|
|
11551
|
+
/*
|
|
11552
|
+
* Shared data model for the PECB course components
|
|
11553
|
+
* (`pecb-course-content-panel` + `pecb-course-player`).
|
|
11554
|
+
*
|
|
11555
|
+
* Source of truth: "Course Player — Content Redesign" (data.jsx / panel.jsx).
|
|
11556
|
+
*/
|
|
11557
|
+
// ─── Formatting helpers ───────────────────────────────────────────────────────
|
|
11558
|
+
/**
|
|
11559
|
+
* Human-readable lesson length: `"9 min"`, `"12 min 4 sec"`, `"1 hr 3 min"`.
|
|
11560
|
+
* Non-finite / negative input renders as `"0 min"`.
|
|
11561
|
+
*/
|
|
11562
|
+
function formatCourseDuration(seconds) {
|
|
11563
|
+
if (!Number.isFinite(seconds) || seconds < 0)
|
|
11564
|
+
return '0 min';
|
|
11565
|
+
const total = Math.floor(seconds);
|
|
11566
|
+
const minutes = Math.floor(total / 60);
|
|
11567
|
+
const rest = total % 60;
|
|
11568
|
+
if (minutes >= 60) {
|
|
11569
|
+
const hours = Math.floor(minutes / 60);
|
|
11570
|
+
return `${hours} hr ${minutes % 60} min`;
|
|
11571
|
+
}
|
|
11572
|
+
return `${minutes} min${rest ? ` ${rest} sec` : ''}`;
|
|
11573
|
+
}
|
|
11574
|
+
/**
|
|
11575
|
+
* Clock reading: `m:ss`, or `h:mm:ss` from one hour up.
|
|
11576
|
+
* Non-finite / negative input renders as `0:00`.
|
|
11577
|
+
*/
|
|
11578
|
+
function formatCourseTime(seconds) {
|
|
11579
|
+
if (!Number.isFinite(seconds) || seconds < 0)
|
|
11580
|
+
return '0:00';
|
|
11581
|
+
const total = Math.floor(seconds);
|
|
11582
|
+
const h = Math.floor(total / 3600);
|
|
11583
|
+
const m = Math.floor((total % 3600) / 60);
|
|
11584
|
+
const s = total % 60;
|
|
11585
|
+
const mm = h > 0 ? String(m).padStart(2, '0') : String(m);
|
|
11586
|
+
return h > 0 ? `${h}:${mm}:${String(s).padStart(2, '0')}` : `${mm}:${String(s).padStart(2, '0')}`;
|
|
11587
|
+
}
|
|
11588
|
+
// ─── Derivation helpers ───────────────────────────────────────────────────────
|
|
11589
|
+
const PART_MARKER = ' — Part ';
|
|
11590
|
+
/**
|
|
11591
|
+
* Splits `"Base — Part 3"` into `{ base: 'Base', part: 'Part 3' }`.
|
|
11592
|
+
* A title without the marker yields `{ base: title, part: null }`.
|
|
11593
|
+
*/
|
|
11594
|
+
function parseLessonPartTitle(title) {
|
|
11595
|
+
const index = title.indexOf(PART_MARKER);
|
|
11596
|
+
if (index === -1)
|
|
11597
|
+
return { base: title, part: null };
|
|
11598
|
+
return { base: title.slice(0, index), part: `Part ${title.slice(index + PART_MARKER.length).trim()}` };
|
|
11599
|
+
}
|
|
11600
|
+
/**
|
|
11601
|
+
* Resolves the status actually rendered for a lesson.
|
|
11602
|
+
*
|
|
11603
|
+
* The active video lesson follows live playback (`playing` while the player is
|
|
11604
|
+
* running, otherwise `started` — unless it is already `completed`), and a
|
|
11605
|
+
* `playing` status on any *other* lesson degrades to `started`, so exactly one
|
|
11606
|
+
* row can ever show the "now playing" treatment. `locked` always wins.
|
|
11607
|
+
*/
|
|
11608
|
+
function resolveLessonStatus(lesson, active, livePlaying) {
|
|
11609
|
+
const raw = lesson.status ?? 'available';
|
|
11610
|
+
if (raw === 'locked')
|
|
11611
|
+
return 'locked';
|
|
11612
|
+
if (active && (lesson.type ?? 'video') === 'video') {
|
|
11613
|
+
return livePlaying ? 'playing' : raw === 'completed' ? 'completed' : 'started';
|
|
11614
|
+
}
|
|
11615
|
+
if (!active && raw === 'playing')
|
|
11616
|
+
return 'started';
|
|
11617
|
+
return raw;
|
|
11618
|
+
}
|
|
11619
|
+
/** Counters for one module, computed from already-resolved lesson statuses. */
|
|
11620
|
+
function courseModuleStats(module, resolve = (l) => l.status ?? 'available') {
|
|
11621
|
+
const total = module.lessons.length;
|
|
11622
|
+
let done = 0;
|
|
11623
|
+
let playing = false;
|
|
11624
|
+
let started = false;
|
|
11625
|
+
let durationSeconds = 0;
|
|
11626
|
+
for (const lesson of module.lessons) {
|
|
11627
|
+
const status = resolve(lesson);
|
|
11628
|
+
if (status === 'completed')
|
|
11629
|
+
done++;
|
|
11630
|
+
if (status === 'playing')
|
|
11631
|
+
playing = true;
|
|
11632
|
+
if (status === 'started')
|
|
11633
|
+
started = true;
|
|
11634
|
+
durationSeconds += lesson.durationSeconds ?? 0;
|
|
11635
|
+
}
|
|
11636
|
+
return {
|
|
11637
|
+
total,
|
|
11638
|
+
done,
|
|
11639
|
+
playing,
|
|
11640
|
+
started,
|
|
11641
|
+
durationSeconds,
|
|
11642
|
+
percent: total > 0 ? Math.round((done / total) * 100) : 0,
|
|
11643
|
+
};
|
|
11644
|
+
}
|
|
11645
|
+
/** Counters for a whole course. */
|
|
11646
|
+
function courseProgress(modules, resolve = (l) => l.status ?? 'available') {
|
|
11647
|
+
const lessons = modules.flatMap((m) => m.lessons);
|
|
11648
|
+
const total = lessons.length;
|
|
11649
|
+
let done = 0;
|
|
11650
|
+
let remainingSeconds = 0;
|
|
11651
|
+
for (const lesson of lessons) {
|
|
11652
|
+
const status = resolve(lesson);
|
|
11653
|
+
if (status === 'completed') {
|
|
11654
|
+
done++;
|
|
11655
|
+
}
|
|
11656
|
+
else if ((lesson.type ?? 'video') === 'video') {
|
|
11657
|
+
remainingSeconds += lesson.durationSeconds ?? 0;
|
|
11658
|
+
}
|
|
11659
|
+
}
|
|
11660
|
+
return {
|
|
11661
|
+
total,
|
|
11662
|
+
done,
|
|
11663
|
+
percent: total > 0 ? Math.round((done / total) * 100) : 0,
|
|
11664
|
+
remainingSeconds,
|
|
11665
|
+
};
|
|
11666
|
+
}
|
|
11667
|
+
/** Finds the module a lesson belongs to. */
|
|
11668
|
+
function findCourseModule(modules, lessonId) {
|
|
11669
|
+
if (!lessonId)
|
|
11670
|
+
return undefined;
|
|
11671
|
+
return modules.find((m) => m.lessons.some((l) => l.id === lessonId));
|
|
11672
|
+
}
|
|
11673
|
+
/** Finds a lesson by id across all modules. */
|
|
11674
|
+
function findCourseLesson(modules, lessonId) {
|
|
11675
|
+
if (!lessonId)
|
|
11676
|
+
return undefined;
|
|
11677
|
+
for (const module of modules) {
|
|
11678
|
+
const lesson = module.lessons.find((l) => l.id === lessonId);
|
|
11679
|
+
if (lesson)
|
|
11680
|
+
return lesson;
|
|
11681
|
+
}
|
|
11682
|
+
return undefined;
|
|
11683
|
+
}
|
|
11684
|
+
|
|
11685
|
+
const DEFAULT_COURSE_CONTENT_PANEL_LABELS = {
|
|
11686
|
+
contentTab: 'Content',
|
|
11687
|
+
transcriptTab: 'Transcript',
|
|
11688
|
+
searchLessons: 'Search lessons and quizzes',
|
|
11689
|
+
searchTranscript: 'Search transcript',
|
|
11690
|
+
clearSearch: 'Clear search',
|
|
11691
|
+
quiz: 'Quiz',
|
|
11692
|
+
questions: '{n} questions',
|
|
11693
|
+
passed: 'Passed · {done}/{total}',
|
|
11694
|
+
answered: '{done} of {total} answered',
|
|
11695
|
+
resume: 'Resume',
|
|
11696
|
+
moduleDone: '{done}/{total} done',
|
|
11697
|
+
section: 'Section {n}',
|
|
11698
|
+
noLessons: 'No lessons match “{query}”.',
|
|
11699
|
+
noTranscript: 'No transcript lines match “{query}”.',
|
|
11700
|
+
autoscroll: 'Autoscroll',
|
|
11701
|
+
courseProgress: 'Course progress',
|
|
11702
|
+
courseProgressDetail: '{done} of {total} lessons · {duration} left',
|
|
11703
|
+
certificate: 'Certificate',
|
|
11704
|
+
locked: 'Locked',
|
|
11705
|
+
lessonList: 'Course content',
|
|
11706
|
+
};
|
|
11707
|
+
// Disc geometry per density (design: 30 / 26 px).
|
|
11708
|
+
const DISC_SIZE = { comfortable: 30, compact: 26 };
|
|
11709
|
+
const RING_STROKE = 3;
|
|
11710
|
+
// ─── Component ────────────────────────────────────────────────────────────────
|
|
11711
|
+
/**
|
|
11712
|
+
* PECB course **Content** panel — the sidebar that sits next to the course
|
|
11713
|
+
* video player.
|
|
11714
|
+
*
|
|
11715
|
+
* Reproduces the "Course Player — Content Redesign" panel exactly: the
|
|
11716
|
+
* Content / Transcript tabs, lesson search, the collapsible **Section**
|
|
11717
|
+
* accordion (one section open at a time, auto-opening the section that owns the
|
|
11718
|
+
* active lesson), status discs (completed / now playing / in-progress ring /
|
|
11719
|
+
* not started / locked), quiz rows with badge + score, per-lesson progress bars,
|
|
11720
|
+
* the "Part 1 / Part 2 …" timeline rail for multi-part lessons, an alternative
|
|
11721
|
+
* flat timeline layout, and the searchable transcript with click-to-seek,
|
|
11722
|
+
* live cue highlighting and an autoscroll toggle.
|
|
11723
|
+
*
|
|
11724
|
+
* It is standalone and data-driven — no player dependency — so it can be used
|
|
11725
|
+
* next to `pecb-video-player`, inside `pecb-course-player`, or on its own.
|
|
11726
|
+
*
|
|
11727
|
+
* @example
|
|
11728
|
+
* ```html
|
|
11729
|
+
* <pecb-course-content-panel
|
|
11730
|
+
* [modules]="modules"
|
|
11731
|
+
* [transcript]="cues"
|
|
11732
|
+
* [(activeLessonId)]="lessonId"
|
|
11733
|
+
* [playing]="playing()"
|
|
11734
|
+
* [currentTime]="currentTime()"
|
|
11735
|
+
* (lessonSelect)="openLesson($event)"
|
|
11736
|
+
* (transcriptSeek)="seekTo($event)">
|
|
11737
|
+
* </pecb-course-content-panel>
|
|
11738
|
+
* ```
|
|
11739
|
+
*
|
|
11740
|
+
* Theming — override the CSS custom properties on the host:
|
|
11741
|
+
* ```css
|
|
11742
|
+
* pecb-course-content-panel { --pecb-course-panel-accent: #a11e29; }
|
|
11743
|
+
* ```
|
|
11744
|
+
*/
|
|
11745
|
+
class CourseContentPanelComponent {
|
|
11746
|
+
constructor() {
|
|
11747
|
+
this.platformId = inject(PLATFORM_ID);
|
|
11748
|
+
this.injector = inject(Injector);
|
|
11749
|
+
this.isBrowser = isPlatformBrowser(this.platformId);
|
|
11750
|
+
/** Unique id prefix used to wire ARIA relationships. */
|
|
11751
|
+
this.uid = generateUniqueId('pecb-course-panel');
|
|
11752
|
+
this.transcriptScrollRef = viewChild('transcriptScroll', ...(ngDevMode ? [{ debugName: "transcriptScrollRef" }] : []));
|
|
11753
|
+
// ── Data inputs ─────────────────────────────────────────────────────────────
|
|
11754
|
+
/** Course structure rendered by the Content tab. */
|
|
11755
|
+
this.modules = input([], ...(ngDevMode ? [{ debugName: "modules" }] : []));
|
|
11756
|
+
/**
|
|
11757
|
+
* Transcript cues for the active lesson. A `transcript` on the active
|
|
11758
|
+
* `CourseLesson` takes precedence over this input.
|
|
11759
|
+
*/
|
|
11760
|
+
this.transcript = input([], ...(ngDevMode ? [{ debugName: "transcript" }] : []));
|
|
11761
|
+
// ── State (two-way) ─────────────────────────────────────────────────────────
|
|
11762
|
+
/** Currently selected lesson. */
|
|
11763
|
+
this.activeLessonId = model(null, ...(ngDevMode ? [{ debugName: "activeLessonId" }] : []));
|
|
11764
|
+
/** Visible tab. */
|
|
11765
|
+
this.tab = model('content', ...(ngDevMode ? [{ debugName: "tab" }] : []));
|
|
11766
|
+
/**
|
|
11767
|
+
* Id of the expanded section. `null` collapses all of them.
|
|
11768
|
+
* Left `undefined` the panel picks one itself (the active lesson's section,
|
|
11769
|
+
* otherwise the first one).
|
|
11770
|
+
*/
|
|
11771
|
+
this.openModuleId = model(undefined, ...(ngDevMode ? [{ debugName: "openModuleId" }] : []));
|
|
11772
|
+
/** Whether the transcript follows playback. */
|
|
11773
|
+
this.autoscroll = model(true, ...(ngDevMode ? [{ debugName: "autoscroll" }] : []));
|
|
11774
|
+
/** Lesson search text (Content tab). */
|
|
11775
|
+
this.query = model('', ...(ngDevMode ? [{ debugName: "query" }] : []));
|
|
11776
|
+
/** Transcript search text. */
|
|
11777
|
+
this.transcriptQuery = model('', ...(ngDevMode ? [{ debugName: "transcriptQuery" }] : []));
|
|
11778
|
+
// ── Live playback state ─────────────────────────────────────────────────────
|
|
11779
|
+
/** Whether the player is currently playing — drives the "now playing" row. */
|
|
11780
|
+
this.playing = input(false, ...(ngDevMode ? [{ debugName: "playing", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
11781
|
+
/**
|
|
11782
|
+
* Live playhead position in seconds. Used to highlight the transcript cue and
|
|
11783
|
+
* to draw live progress on the active lesson row; falls back to each lesson's
|
|
11784
|
+
* `resumeSeconds` when omitted.
|
|
11785
|
+
*/
|
|
11786
|
+
this.currentTime = input(undefined, ...(ngDevMode ? [{ debugName: "currentTime" }] : []));
|
|
11787
|
+
// ── Appearance ──────────────────────────────────────────────────────────────
|
|
11788
|
+
/** @default 'grouped' */
|
|
11789
|
+
this.layout = input('grouped', ...(ngDevMode ? [{ debugName: "layout" }] : []));
|
|
11790
|
+
/** @default 'comfortable' */
|
|
11791
|
+
this.density = input('comfortable', ...(ngDevMode ? [{ debugName: "density" }] : []));
|
|
11792
|
+
/** Colour of the "now playing" treatment. @default 'charcoal' */
|
|
11793
|
+
this.accent = input('charcoal', ...(ngDevMode ? [{ debugName: "accent" }] : []));
|
|
11794
|
+
/** Show the Content / Transcript tab strip. @default true */
|
|
11795
|
+
this.showTabs = input(true, ...(ngDevMode ? [{ debugName: "showTabs", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
11796
|
+
/** Show the lesson search box. @default true */
|
|
11797
|
+
this.showSearch = input(true, ...(ngDevMode ? [{ debugName: "showSearch", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
11798
|
+
/** Show the transcript search box. @default true */
|
|
11799
|
+
this.showTranscriptSearch = input(true, ...(ngDevMode ? [{ debugName: "showTranscriptSearch", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
11800
|
+
/** Show the autoscroll footer on the Transcript tab. @default true */
|
|
11801
|
+
this.showAutoscroll = input(true, ...(ngDevMode ? [{ debugName: "showAutoscroll", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
11802
|
+
/** Show the circular course-progress header above the list. @default false */
|
|
11803
|
+
this.showProgress = input(false, ...(ngDevMode ? [{ debugName: "showProgress", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
11804
|
+
/** Show the "Certificate" pill inside the progress header. @default true */
|
|
11805
|
+
this.showCertificate = input(true, ...(ngDevMode ? [{ debugName: "showCertificate", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
11806
|
+
/**
|
|
11807
|
+
* Render a module that holds exactly one lesson as a single direct-play row
|
|
11808
|
+
* instead of a collapsible section. @default false
|
|
11809
|
+
*/
|
|
11810
|
+
this.soloSingleLessonModules = input(false, ...(ngDevMode ? [{ debugName: "soloSingleLessonModules", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
11811
|
+
/** Partial override of the user-visible strings (i18n). */
|
|
11812
|
+
this.labels = input({}, ...(ngDevMode ? [{ debugName: "labels" }] : []));
|
|
11813
|
+
// ── Outputs ─────────────────────────────────────────────────────────────────
|
|
11814
|
+
/** A lesson row was activated. */
|
|
11815
|
+
this.lessonSelect = output();
|
|
11816
|
+
/** A transcript cue was clicked — seek the player to this position (seconds). */
|
|
11817
|
+
this.transcriptSeek = output();
|
|
11818
|
+
/** A section was expanded or collapsed. */
|
|
11819
|
+
this.moduleToggle = output();
|
|
11820
|
+
/** The "Certificate" pill was clicked. */
|
|
11821
|
+
this.certificateClick = output();
|
|
11822
|
+
// ── Labels ──────────────────────────────────────────────────────────────────
|
|
11823
|
+
this.text = computed(() => ({
|
|
11824
|
+
...DEFAULT_COURSE_CONTENT_PANEL_LABELS,
|
|
11825
|
+
...this.labels(),
|
|
11826
|
+
}), ...(ngDevMode ? [{ debugName: "text" }] : []));
|
|
11827
|
+
// ── Theming ─────────────────────────────────────────────────────────────────
|
|
11828
|
+
this.hostClasses = computed(() => [
|
|
11829
|
+
'pecb-course-panel',
|
|
11830
|
+
`pecb-course-panel--${this.density()}`,
|
|
11831
|
+
`pecb-course-panel--${this.layout()}`,
|
|
11832
|
+
`pecb-course-panel--accent-${this.accent()}`,
|
|
11833
|
+
].join(' '), ...(ngDevMode ? [{ debugName: "hostClasses" }] : []));
|
|
11834
|
+
// ── Derived data ────────────────────────────────────────────────────────────
|
|
11835
|
+
this.compact = computed(() => this.density() === 'compact', ...(ngDevMode ? [{ debugName: "compact" }] : []));
|
|
11836
|
+
/** Resolved status of every lesson, keyed by lesson id. */
|
|
11837
|
+
this.statusById = computed(() => {
|
|
11838
|
+
const activeId = this.activeLessonId();
|
|
11839
|
+
const livePlaying = this.playing();
|
|
11840
|
+
const map = new Map();
|
|
11841
|
+
for (const module of this.modules()) {
|
|
11842
|
+
for (const lesson of module.lessons) {
|
|
11843
|
+
map.set(lesson.id, resolveLessonStatus(lesson, lesson.id === activeId, livePlaying));
|
|
11844
|
+
}
|
|
11845
|
+
}
|
|
11846
|
+
return map;
|
|
11847
|
+
}, ...(ngDevMode ? [{ debugName: "statusById" }] : []));
|
|
11848
|
+
this.resolveStatus = computed(() => {
|
|
11849
|
+
const map = this.statusById();
|
|
11850
|
+
return (lesson) => map.get(lesson.id) ?? (lesson.status ?? 'available');
|
|
11851
|
+
}, ...(ngDevMode ? [{ debugName: "resolveStatus" }] : []));
|
|
11852
|
+
/** Modules narrowed by the search box (design: match lesson titles, else module title). */
|
|
11853
|
+
this.filteredModules = computed(() => {
|
|
11854
|
+
const term = this.query().trim().toLowerCase();
|
|
11855
|
+
const modules = this.modules();
|
|
11856
|
+
if (!term)
|
|
11857
|
+
return modules;
|
|
11858
|
+
return modules
|
|
11859
|
+
.map((module) => ({
|
|
11860
|
+
...module,
|
|
11861
|
+
lessons: module.lessons.filter((lesson) => lesson.title.toLowerCase().includes(term)),
|
|
11862
|
+
}))
|
|
11863
|
+
.filter((module) => module.lessons.length > 0 || module.title.toLowerCase().includes(term));
|
|
11864
|
+
}, ...(ngDevMode ? [{ debugName: "filteredModules" }] : []));
|
|
11865
|
+
this.moduleViews = computed(() => {
|
|
11866
|
+
const all = this.modules();
|
|
11867
|
+
const activeId = this.activeLessonId();
|
|
11868
|
+
const solo = this.soloSingleLessonModules();
|
|
11869
|
+
const text = this.text();
|
|
11870
|
+
const livePlaying = this.playing();
|
|
11871
|
+
return this.filteredModules().map((module) => {
|
|
11872
|
+
const index = all.findIndex((m) => m.id === module.id);
|
|
11873
|
+
const active = module.lessons.some((lesson) => lesson.id === activeId);
|
|
11874
|
+
const isSolo = solo && module.lessons.length === 1;
|
|
11875
|
+
// Section counters come from the stored statuses (design behaviour), with
|
|
11876
|
+
// live playback folded in so the badge reacts while the video runs.
|
|
11877
|
+
const raw = courseModuleStats(module);
|
|
11878
|
+
const stats = {
|
|
11879
|
+
...raw,
|
|
11880
|
+
playing: raw.playing || (active && livePlaying),
|
|
11881
|
+
};
|
|
11882
|
+
return {
|
|
11883
|
+
module,
|
|
11884
|
+
id: module.id,
|
|
11885
|
+
number: (index === -1 ? all.length : index) + 1,
|
|
11886
|
+
title: module.title,
|
|
11887
|
+
stats,
|
|
11888
|
+
statsLabel: format(text.moduleDone, { done: stats.done, total: stats.total }),
|
|
11889
|
+
durationLabel: formatCourseDuration(stats.durationSeconds),
|
|
11890
|
+
allDone: stats.total > 0 && stats.done === stats.total,
|
|
11891
|
+
active,
|
|
11892
|
+
runs: isSolo ? [] : this.buildRuns(module.lessons),
|
|
11893
|
+
solo: isSolo ? this.buildLessonView(module.lessons[0], { titleOverride: module.title }) : null,
|
|
11894
|
+
};
|
|
11895
|
+
});
|
|
11896
|
+
}, ...(ngDevMode ? [{ debugName: "moduleViews" }] : []));
|
|
11897
|
+
this.timelineRows = computed(() => {
|
|
11898
|
+
const all = this.modules();
|
|
11899
|
+
const activeId = this.activeLessonId();
|
|
11900
|
+
const livePlaying = this.playing();
|
|
11901
|
+
const rows = [];
|
|
11902
|
+
for (const module of this.filteredModules()) {
|
|
11903
|
+
const index = all.findIndex((m) => m.id === module.id);
|
|
11904
|
+
const rawStats = courseModuleStats(module);
|
|
11905
|
+
rows.push({
|
|
11906
|
+
key: `h-${module.id}`,
|
|
11907
|
+
header: {
|
|
11908
|
+
number: (index === -1 ? all.length : index) + 1,
|
|
11909
|
+
title: module.title,
|
|
11910
|
+
stats: {
|
|
11911
|
+
...rawStats,
|
|
11912
|
+
playing: rawStats.playing ||
|
|
11913
|
+
(livePlaying && module.lessons.some((l) => l.id === activeId)),
|
|
11914
|
+
},
|
|
11915
|
+
},
|
|
11916
|
+
lesson: null,
|
|
11917
|
+
});
|
|
11918
|
+
module.lessons.forEach((lesson, i) => {
|
|
11919
|
+
rows.push({
|
|
11920
|
+
key: lesson.id,
|
|
11921
|
+
header: null,
|
|
11922
|
+
lesson: this.buildLessonView(lesson, { connector: i < module.lessons.length - 1 }),
|
|
11923
|
+
});
|
|
11924
|
+
});
|
|
11925
|
+
}
|
|
11926
|
+
return rows;
|
|
11927
|
+
}, ...(ngDevMode ? [{ debugName: "timelineRows" }] : []));
|
|
11928
|
+
this.isEmpty = computed(() => this.filteredModules().length === 0, ...(ngDevMode ? [{ debugName: "isEmpty" }] : []));
|
|
11929
|
+
// ── Progress header ─────────────────────────────────────────────────────────
|
|
11930
|
+
this.progress = computed(() => courseProgress(this.modules()), ...(ngDevMode ? [{ debugName: "progress" }] : []));
|
|
11931
|
+
/** Geometry of the 56 px progress ring (design: r = 22, stroke = 5). */
|
|
11932
|
+
this.progressRing = computed(() => {
|
|
11933
|
+
const radius = 22;
|
|
11934
|
+
const circumference = 2 * Math.PI * radius;
|
|
11935
|
+
return {
|
|
11936
|
+
radius,
|
|
11937
|
+
circumference,
|
|
11938
|
+
offset: circumference * (1 - this.progress().percent / 100),
|
|
11939
|
+
};
|
|
11940
|
+
}, ...(ngDevMode ? [{ debugName: "progressRing" }] : []));
|
|
11941
|
+
this.progressDetail = computed(() => {
|
|
11942
|
+
const stats = this.progress();
|
|
11943
|
+
return format(this.text().courseProgressDetail, {
|
|
11944
|
+
done: stats.done,
|
|
11945
|
+
total: stats.total,
|
|
11946
|
+
duration: formatCourseDuration(stats.remainingSeconds),
|
|
11947
|
+
});
|
|
11948
|
+
}, ...(ngDevMode ? [{ debugName: "progressDetail" }] : []));
|
|
11949
|
+
// ── Transcript ──────────────────────────────────────────────────────────────
|
|
11950
|
+
/** Cues of the active lesson, falling back to the panel-level `transcript`. */
|
|
11951
|
+
this.activeCues = computed(() => {
|
|
11952
|
+
const activeId = this.activeLessonId();
|
|
11953
|
+
for (const module of this.modules()) {
|
|
11954
|
+
const lesson = module.lessons.find((l) => l.id === activeId);
|
|
11955
|
+
if (lesson?.transcript?.length)
|
|
11956
|
+
return lesson.transcript;
|
|
11957
|
+
}
|
|
11958
|
+
return this.transcript();
|
|
11959
|
+
}, ...(ngDevMode ? [{ debugName: "activeCues" }] : []));
|
|
11960
|
+
/** Index of the cue covering the playhead, or `-1`. */
|
|
11961
|
+
this.activeCueIndex = computed(() => {
|
|
11962
|
+
const cues = this.activeCues();
|
|
11963
|
+
const at = this.currentTime();
|
|
11964
|
+
if (at === undefined || cues.length === 0)
|
|
11965
|
+
return -1;
|
|
11966
|
+
let found = -1;
|
|
11967
|
+
for (let i = 0; i < cues.length; i++) {
|
|
11968
|
+
const next = cues[i + 1];
|
|
11969
|
+
if (at >= cues[i].time && (!next || at < next.time)) {
|
|
11970
|
+
found = i;
|
|
11971
|
+
break;
|
|
11972
|
+
}
|
|
11973
|
+
}
|
|
11974
|
+
return found;
|
|
11975
|
+
}, ...(ngDevMode ? [{ debugName: "activeCueIndex" }] : []));
|
|
11976
|
+
this.transcriptRows = computed(() => {
|
|
11977
|
+
const cues = this.activeCues();
|
|
11978
|
+
const term = this.transcriptQuery().trim();
|
|
11979
|
+
const lower = term.toLowerCase();
|
|
11980
|
+
const activeIndex = this.activeCueIndex();
|
|
11981
|
+
const rows = [];
|
|
11982
|
+
cues.forEach((cue, index) => {
|
|
11983
|
+
const matchIndex = lower ? cue.text.toLowerCase().indexOf(lower) : -1;
|
|
11984
|
+
if (lower && matchIndex === -1)
|
|
11985
|
+
return;
|
|
11986
|
+
rows.push({
|
|
11987
|
+
key: `${index}-${cue.time}`,
|
|
11988
|
+
cue,
|
|
11989
|
+
timeLabel: formatCourseTime(cue.time),
|
|
11990
|
+
// While searching, the design drops the "current cue" highlight.
|
|
11991
|
+
current: !lower && index === activeIndex,
|
|
11992
|
+
before: matchIndex === -1 ? cue.text : cue.text.slice(0, matchIndex),
|
|
11993
|
+
match: matchIndex === -1 ? '' : cue.text.slice(matchIndex, matchIndex + term.length),
|
|
11994
|
+
after: matchIndex === -1 ? '' : cue.text.slice(matchIndex + term.length),
|
|
11995
|
+
});
|
|
11996
|
+
});
|
|
11997
|
+
return rows;
|
|
11998
|
+
}, ...(ngDevMode ? [{ debugName: "transcriptRows" }] : []));
|
|
11999
|
+
this.transcriptEmpty = computed(() => this.transcriptRows().length === 0, ...(ngDevMode ? [{ debugName: "transcriptEmpty" }] : []));
|
|
12000
|
+
// ── Messages ────────────────────────────────────────────────────────────────
|
|
12001
|
+
this.noLessonsLabel = computed(() => format(this.text().noLessons, { query: this.query().trim() }), ...(ngDevMode ? [{ debugName: "noLessonsLabel" }] : []));
|
|
12002
|
+
this.noTranscriptLabel = computed(() => format(this.text().noTranscript, { query: this.transcriptQuery().trim() }), ...(ngDevMode ? [{ debugName: "noTranscriptLabel" }] : []));
|
|
12003
|
+
// Keep the section holding the active lesson expanded. Tracks `activeLessonId`
|
|
12004
|
+
// only — so collapsing the current section by hand stays collapsed until the
|
|
12005
|
+
// selection actually changes (design behaviour).
|
|
12006
|
+
effect(() => {
|
|
12007
|
+
const activeId = this.activeLessonId();
|
|
12008
|
+
untracked(() => {
|
|
12009
|
+
if (!activeId)
|
|
12010
|
+
return;
|
|
12011
|
+
const module = findCourseModule(this.modules(), activeId);
|
|
12012
|
+
if (module)
|
|
12013
|
+
this.openModuleId.set(module.id);
|
|
12014
|
+
});
|
|
12015
|
+
});
|
|
12016
|
+
// First time modules are available and nothing has been decided yet, expand
|
|
12017
|
+
// the active lesson's section, else the first one.
|
|
12018
|
+
let autoOpened = false;
|
|
12019
|
+
effect(() => {
|
|
12020
|
+
const modules = this.modules();
|
|
12021
|
+
untracked(() => {
|
|
12022
|
+
if (autoOpened || modules.length === 0)
|
|
12023
|
+
return;
|
|
12024
|
+
autoOpened = true;
|
|
12025
|
+
if (this.openModuleId() !== undefined)
|
|
12026
|
+
return;
|
|
12027
|
+
const module = findCourseModule(modules, this.activeLessonId());
|
|
12028
|
+
this.openModuleId.set(module?.id ?? modules[0].id);
|
|
12029
|
+
});
|
|
12030
|
+
});
|
|
12031
|
+
// Follow the playhead in the transcript.
|
|
12032
|
+
effect(() => {
|
|
12033
|
+
const index = this.activeCueIndex();
|
|
12034
|
+
const auto = this.autoscroll();
|
|
12035
|
+
const tab = this.tab();
|
|
12036
|
+
const searching = this.transcriptQuery().trim().length > 0;
|
|
12037
|
+
untracked(() => {
|
|
12038
|
+
if (!this.isBrowser || !auto || searching || tab !== 'transcript' || index < 0)
|
|
12039
|
+
return;
|
|
12040
|
+
afterNextRender(() => this.scrollActiveCueIntoView(), { injector: this.injector });
|
|
12041
|
+
});
|
|
12042
|
+
});
|
|
12043
|
+
}
|
|
12044
|
+
// ── Template helpers ────────────────────────────────────────────────────────
|
|
12045
|
+
/** `"Section 3"` — the `{n}` placeholder resolved against the label set. */
|
|
12046
|
+
sectionLabel(index) {
|
|
12047
|
+
return format(this.text().section, { n: index });
|
|
12048
|
+
}
|
|
12049
|
+
/** Whether a section is expanded. */
|
|
12050
|
+
isModuleOpen(moduleId) {
|
|
12051
|
+
return this.openModuleId() === moduleId;
|
|
12052
|
+
}
|
|
12053
|
+
/** Expand a section, collapsing the one that was open (single-open accordion). */
|
|
12054
|
+
toggleModule(view) {
|
|
12055
|
+
const open = !this.isModuleOpen(view.id);
|
|
12056
|
+
this.openModuleId.set(open ? view.id : null);
|
|
12057
|
+
this.moduleToggle.emit({ module: view.module, open });
|
|
12058
|
+
}
|
|
12059
|
+
/** Roving keyboard support between section headers (WAI-ARIA accordion). */
|
|
12060
|
+
onModuleHeaderKeydown(event) {
|
|
12061
|
+
const key = event.key;
|
|
12062
|
+
if (key !== 'ArrowDown' && key !== 'ArrowUp' && key !== 'Home' && key !== 'End')
|
|
12063
|
+
return;
|
|
12064
|
+
const target = event.target;
|
|
12065
|
+
const root = target.closest('.pecb-course-panel__list');
|
|
12066
|
+
if (!root)
|
|
12067
|
+
return;
|
|
12068
|
+
const headers = Array.from(root.querySelectorAll('.pecb-course-panel__section-head'));
|
|
12069
|
+
if (headers.length === 0)
|
|
12070
|
+
return;
|
|
12071
|
+
const index = headers.indexOf(target);
|
|
12072
|
+
if (index === -1)
|
|
12073
|
+
return;
|
|
12074
|
+
let next;
|
|
12075
|
+
switch (key) {
|
|
12076
|
+
case 'ArrowDown':
|
|
12077
|
+
next = (index + 1) % headers.length;
|
|
12078
|
+
break;
|
|
12079
|
+
case 'ArrowUp':
|
|
12080
|
+
next = (index - 1 + headers.length) % headers.length;
|
|
12081
|
+
break;
|
|
12082
|
+
case 'Home':
|
|
12083
|
+
next = 0;
|
|
12084
|
+
break;
|
|
12085
|
+
default:
|
|
12086
|
+
next = headers.length - 1;
|
|
12087
|
+
}
|
|
12088
|
+
event.preventDefault();
|
|
12089
|
+
headers[next].focus();
|
|
12090
|
+
}
|
|
12091
|
+
/** Activate a lesson (ignored when locked). */
|
|
12092
|
+
selectLesson(lesson) {
|
|
12093
|
+
if ((lesson.status ?? 'available') === 'locked')
|
|
12094
|
+
return;
|
|
12095
|
+
this.activeLessonId.set(lesson.id);
|
|
12096
|
+
this.lessonSelect.emit(lesson);
|
|
12097
|
+
}
|
|
12098
|
+
/** Seek the player to a transcript cue. */
|
|
12099
|
+
seekToCue(cue) {
|
|
12100
|
+
this.transcriptSeek.emit(cue.time);
|
|
12101
|
+
}
|
|
12102
|
+
setTab(tab) {
|
|
12103
|
+
this.tab.set(tab);
|
|
12104
|
+
}
|
|
12105
|
+
onTabKeydown(event) {
|
|
12106
|
+
if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight')
|
|
12107
|
+
return;
|
|
12108
|
+
event.preventDefault();
|
|
12109
|
+
this.setTab(this.tab() === 'content' ? 'transcript' : 'content');
|
|
12110
|
+
const next = event.target
|
|
12111
|
+
.closest('[role="tablist"]')
|
|
12112
|
+
?.querySelector('[aria-selected="false"]');
|
|
12113
|
+
next?.focus();
|
|
12114
|
+
}
|
|
12115
|
+
onQueryInput(event) {
|
|
12116
|
+
this.query.set(event.target.value);
|
|
12117
|
+
}
|
|
12118
|
+
onTranscriptQueryInput(event) {
|
|
12119
|
+
this.transcriptQuery.set(event.target.value);
|
|
12120
|
+
}
|
|
12121
|
+
clearTranscriptQuery() {
|
|
12122
|
+
this.transcriptQuery.set('');
|
|
12123
|
+
}
|
|
12124
|
+
toggleAutoscroll() {
|
|
12125
|
+
this.autoscroll.update((value) => !value);
|
|
12126
|
+
}
|
|
12127
|
+
onAutoscrollChange(event) {
|
|
12128
|
+
this.autoscroll.set(event.target.checked);
|
|
12129
|
+
}
|
|
12130
|
+
// ── Internals ───────────────────────────────────────────────────────────────
|
|
12131
|
+
/**
|
|
12132
|
+
* Splits a module's lessons into runs of consecutive lessons sharing a base
|
|
12133
|
+
* title. A run of two or more where at least one carries a `— Part n` suffix
|
|
12134
|
+
* renders as a sub-heading plus a connected rail (design behaviour).
|
|
12135
|
+
*/
|
|
12136
|
+
buildRuns(lessons) {
|
|
12137
|
+
const runs = [];
|
|
12138
|
+
let i = 0;
|
|
12139
|
+
while (i < lessons.length) {
|
|
12140
|
+
const base = parseLessonPartTitle(lessons[i].title).base;
|
|
12141
|
+
const run = [];
|
|
12142
|
+
let j = i;
|
|
12143
|
+
while (j < lessons.length && parseLessonPartTitle(lessons[j].title).base === base) {
|
|
12144
|
+
run.push(lessons[j]);
|
|
12145
|
+
j++;
|
|
12146
|
+
}
|
|
12147
|
+
const hasParts = run.some((lesson) => parseLessonPartTitle(lesson.title).part !== null);
|
|
12148
|
+
if (hasParts && run.length > 1) {
|
|
12149
|
+
runs.push({
|
|
12150
|
+
key: `parts-${run[0].id}`,
|
|
12151
|
+
parts: true,
|
|
12152
|
+
base,
|
|
12153
|
+
lessons: run.map((lesson) => {
|
|
12154
|
+
const { part } = parseLessonPartTitle(lesson.title);
|
|
12155
|
+
// Inside a parts rail the quiz shows only its badge (design).
|
|
12156
|
+
const titleOverride = (lesson.type ?? 'video') === 'quiz' ? null : part ?? lesson.title;
|
|
12157
|
+
return this.buildLessonView(lesson, { titleOverride });
|
|
12158
|
+
}),
|
|
12159
|
+
});
|
|
12160
|
+
}
|
|
12161
|
+
else {
|
|
12162
|
+
runs.push({
|
|
12163
|
+
key: `plain-${run[0].id}`,
|
|
12164
|
+
parts: false,
|
|
12165
|
+
base,
|
|
12166
|
+
lessons: run.map((lesson) => this.buildLessonView(lesson)),
|
|
12167
|
+
});
|
|
12168
|
+
}
|
|
12169
|
+
i = j;
|
|
12170
|
+
}
|
|
12171
|
+
return runs;
|
|
12172
|
+
}
|
|
12173
|
+
buildLessonView(lesson, options = {}) {
|
|
12174
|
+
const text = this.text();
|
|
12175
|
+
const compact = this.compact();
|
|
12176
|
+
const active = lesson.id === this.activeLessonId();
|
|
12177
|
+
const status = this.resolveStatus()(lesson);
|
|
12178
|
+
const isQuiz = (lesson.type ?? 'video') === 'quiz';
|
|
12179
|
+
const isPlaying = status === 'playing';
|
|
12180
|
+
const isStarted = status === 'started';
|
|
12181
|
+
const isCompleted = status === 'completed';
|
|
12182
|
+
const isLocked = status === 'locked';
|
|
12183
|
+
const duration = lesson.durationSeconds ?? 0;
|
|
12184
|
+
const questions = lesson.questionCount ?? 0;
|
|
12185
|
+
const answered = lesson.answeredCount ?? 0;
|
|
12186
|
+
// The active lesson follows the live playhead; everything else uses its
|
|
12187
|
+
// stored resume position.
|
|
12188
|
+
const live = this.currentTime();
|
|
12189
|
+
const at = active && live !== undefined ? live : lesson.resumeSeconds ?? 0;
|
|
12190
|
+
const fraction = isQuiz
|
|
12191
|
+
? questions
|
|
12192
|
+
? Math.min(1, answered / questions)
|
|
12193
|
+
: 0.4
|
|
12194
|
+
: duration
|
|
12195
|
+
? Math.min(1, at / duration)
|
|
12196
|
+
: 0.4;
|
|
12197
|
+
const discSize = compact ? DISC_SIZE.compact : DISC_SIZE.comfortable;
|
|
12198
|
+
const ringRadius = discSize / 2 - RING_STROKE / 2;
|
|
12199
|
+
const ringCircumference = 2 * Math.PI * ringRadius;
|
|
12200
|
+
const showBar = (isPlaying || isStarted) && (isQuiz ? questions > 0 : duration > 0);
|
|
12201
|
+
const barPercent = isQuiz
|
|
12202
|
+
? questions
|
|
12203
|
+
? (answered / questions) * 100
|
|
12204
|
+
: 0
|
|
12205
|
+
: duration
|
|
12206
|
+
? Math.min(100, (at / duration) * 100)
|
|
12207
|
+
: 0;
|
|
12208
|
+
let quizResultLabel = null;
|
|
12209
|
+
if (isQuiz && isCompleted) {
|
|
12210
|
+
quizResultLabel = format(text.passed, { done: lesson.correctCount ?? 0, total: questions });
|
|
12211
|
+
}
|
|
12212
|
+
else if (isQuiz && isStarted) {
|
|
12213
|
+
quizResultLabel = format(text.answered, { done: answered, total: questions });
|
|
12214
|
+
}
|
|
12215
|
+
const timeLabel = !isQuiz && (isPlaying || isStarted) && duration
|
|
12216
|
+
? `${isStarted ? `${text.resume} ` : ''}${formatCourseTime(at)} / ${formatCourseTime(duration)}`
|
|
12217
|
+
: null;
|
|
12218
|
+
const displayTitle = options.titleOverride !== undefined ? options.titleOverride : lesson.title;
|
|
12219
|
+
return {
|
|
12220
|
+
lesson,
|
|
12221
|
+
id: lesson.id,
|
|
12222
|
+
active,
|
|
12223
|
+
status,
|
|
12224
|
+
isQuiz,
|
|
12225
|
+
isPlaying,
|
|
12226
|
+
isStarted,
|
|
12227
|
+
isCompleted,
|
|
12228
|
+
isLocked,
|
|
12229
|
+
displayTitle,
|
|
12230
|
+
ariaLabel: isLocked ? `${lesson.title} — ${text.locked}` : lesson.title,
|
|
12231
|
+
durationLabel: formatCourseDuration(duration),
|
|
12232
|
+
questionsLabel: format(text.questions, { n: questions }),
|
|
12233
|
+
quizResultLabel,
|
|
12234
|
+
timeLabel,
|
|
12235
|
+
showBar,
|
|
12236
|
+
barPercent,
|
|
12237
|
+
discSize,
|
|
12238
|
+
discCenter: discSize / 2,
|
|
12239
|
+
ringRadius,
|
|
12240
|
+
ringCircumference,
|
|
12241
|
+
ringOffset: ringCircumference * (1 - fraction),
|
|
12242
|
+
ringViewBox: `0 0 ${discSize} ${discSize}`,
|
|
12243
|
+
checkSize: compact ? 14 : 16,
|
|
12244
|
+
pauseSize: compact ? 11 : 12,
|
|
12245
|
+
// The in-progress ring leaves a touch less room than the plain outline.
|
|
12246
|
+
playIconSize: isStarted ? (compact ? 9 : 10) : compact ? 10 : 11,
|
|
12247
|
+
quizIconSize: isStarted ? (compact ? 13 : 14) : compact ? 14 : 15,
|
|
12248
|
+
connector: options.connector ?? false,
|
|
12249
|
+
connectorDone: isCompleted,
|
|
12250
|
+
};
|
|
12251
|
+
}
|
|
12252
|
+
scrollActiveCueIntoView() {
|
|
12253
|
+
const container = this.transcriptScrollRef()?.nativeElement;
|
|
12254
|
+
if (!container)
|
|
12255
|
+
return;
|
|
12256
|
+
const row = container.querySelector('[data-current="true"]');
|
|
12257
|
+
if (!row)
|
|
12258
|
+
return;
|
|
12259
|
+
const rowRect = row.getBoundingClientRect();
|
|
12260
|
+
const boxRect = container.getBoundingClientRect();
|
|
12261
|
+
const top = container.scrollTop + (rowRect.top - boxRect.top) - (boxRect.height / 2 - rowRect.height / 2);
|
|
12262
|
+
const reduceMotion = typeof window !== 'undefined' &&
|
|
12263
|
+
typeof window.matchMedia === 'function' &&
|
|
12264
|
+
window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
12265
|
+
container.scrollTo({ top, behavior: reduceMotion ? 'auto' : 'smooth' });
|
|
12266
|
+
}
|
|
12267
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: CourseContentPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
12268
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: CourseContentPanelComponent, isStandalone: true, selector: "pecb-course-content-panel", inputs: { modules: { classPropertyName: "modules", publicName: "modules", isSignal: true, isRequired: false, transformFunction: null }, transcript: { classPropertyName: "transcript", publicName: "transcript", isSignal: true, isRequired: false, transformFunction: null }, activeLessonId: { classPropertyName: "activeLessonId", publicName: "activeLessonId", isSignal: true, isRequired: false, transformFunction: null }, tab: { classPropertyName: "tab", publicName: "tab", isSignal: true, isRequired: false, transformFunction: null }, openModuleId: { classPropertyName: "openModuleId", publicName: "openModuleId", isSignal: true, isRequired: false, transformFunction: null }, autoscroll: { classPropertyName: "autoscroll", publicName: "autoscroll", isSignal: true, isRequired: false, transformFunction: null }, query: { classPropertyName: "query", publicName: "query", isSignal: true, isRequired: false, transformFunction: null }, transcriptQuery: { classPropertyName: "transcriptQuery", publicName: "transcriptQuery", isSignal: true, isRequired: false, transformFunction: null }, playing: { classPropertyName: "playing", publicName: "playing", isSignal: true, isRequired: false, transformFunction: null }, currentTime: { classPropertyName: "currentTime", publicName: "currentTime", isSignal: true, isRequired: false, transformFunction: null }, layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null }, density: { classPropertyName: "density", publicName: "density", isSignal: true, isRequired: false, transformFunction: null }, accent: { classPropertyName: "accent", publicName: "accent", isSignal: true, isRequired: false, transformFunction: null }, showTabs: { classPropertyName: "showTabs", publicName: "showTabs", isSignal: true, isRequired: false, transformFunction: null }, showSearch: { classPropertyName: "showSearch", publicName: "showSearch", isSignal: true, isRequired: false, transformFunction: null }, showTranscriptSearch: { classPropertyName: "showTranscriptSearch", publicName: "showTranscriptSearch", isSignal: true, isRequired: false, transformFunction: null }, showAutoscroll: { classPropertyName: "showAutoscroll", publicName: "showAutoscroll", isSignal: true, isRequired: false, transformFunction: null }, showProgress: { classPropertyName: "showProgress", publicName: "showProgress", isSignal: true, isRequired: false, transformFunction: null }, showCertificate: { classPropertyName: "showCertificate", publicName: "showCertificate", isSignal: true, isRequired: false, transformFunction: null }, soloSingleLessonModules: { classPropertyName: "soloSingleLessonModules", publicName: "soloSingleLessonModules", isSignal: true, isRequired: false, transformFunction: null }, labels: { classPropertyName: "labels", publicName: "labels", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activeLessonId: "activeLessonIdChange", tab: "tabChange", openModuleId: "openModuleIdChange", autoscroll: "autoscrollChange", query: "queryChange", transcriptQuery: "transcriptQueryChange", lessonSelect: "lessonSelect", transcriptSeek: "transcriptSeek", moduleToggle: "moduleToggle", certificateClick: "certificateClick" }, host: { classAttribute: "pecb-course-panel-host" }, viewQueries: [{ propertyName: "transcriptScrollRef", first: true, predicate: ["transcriptScroll"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n Lesson row \u2014 one <button> per lesson, shared by the grouped, parts-rail,\n timeline and solo renderings.\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n<ng-template #lessonRowTpl let-vm let-solo=\"solo\">\n <button\n type=\"button\"\n class=\"pecb-course-panel__row\"\n [class.pecb-course-panel__row--active]=\"vm.active\"\n [class.pecb-course-panel__row--quiz]=\"vm.isQuiz\"\n [class.pecb-course-panel__row--playing]=\"vm.isPlaying\"\n [class.pecb-course-panel__row--started]=\"vm.isStarted\"\n [class.pecb-course-panel__row--completed]=\"vm.isCompleted\"\n [class.pecb-course-panel__row--locked]=\"vm.isLocked\"\n [class.pecb-course-panel__row--solo]=\"!!solo\"\n [disabled]=\"vm.isLocked\"\n [attr.aria-current]=\"vm.active ? 'true' : null\"\n [attr.aria-label]=\"vm.ariaLabel\"\n (click)=\"selectLesson(vm.lesson)\"\n >\n @if (vm.active && vm.isPlaying) {\n <span class=\"pecb-course-panel__row-marker\" aria-hidden=\"true\"></span>\n }\n\n <!-- Status disc (+ optional timeline connector) -->\n <span class=\"pecb-course-panel__disc-col\">\n <span\n class=\"pecb-course-panel__disc\"\n [class.pecb-course-panel__disc--completed]=\"vm.isCompleted\"\n [class.pecb-course-panel__disc--playing]=\"vm.isPlaying\"\n [class.pecb-course-panel__disc--started]=\"vm.isStarted\"\n [class.pecb-course-panel__disc--locked]=\"vm.isLocked\"\n [class.pecb-course-panel__disc--plain]=\"!vm.isCompleted && !vm.isPlaying && !vm.isStarted\"\n [style.width.px]=\"vm.discSize\"\n [style.height.px]=\"vm.discSize\"\n aria-hidden=\"true\"\n >\n @if (vm.isCompleted) {\n <svg [attr.width]=\"vm.checkSize\" [attr.height]=\"vm.checkSize\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n } @else if (vm.isPlaying) {\n <svg [attr.width]=\"vm.pauseSize\" [attr.height]=\"vm.pauseSize\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <rect x=\"6\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n <rect x=\"14\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n </svg>\n } @else if (vm.isStarted) {\n <svg class=\"pecb-course-panel__ring\" [attr.width]=\"vm.discSize\" [attr.height]=\"vm.discSize\" [attr.viewBox]=\"vm.ringViewBox\">\n <circle\n class=\"pecb-course-panel__ring-track\"\n [attr.cx]=\"vm.discCenter\" [attr.cy]=\"vm.discCenter\" [attr.r]=\"vm.ringRadius\"\n fill=\"none\" stroke-width=\"3\" />\n <circle\n class=\"pecb-course-panel__ring-value\"\n [attr.cx]=\"vm.discCenter\" [attr.cy]=\"vm.discCenter\" [attr.r]=\"vm.ringRadius\"\n fill=\"none\" stroke-width=\"3\" stroke-linecap=\"round\"\n [attr.stroke-dasharray]=\"vm.ringCircumference\"\n [attr.stroke-dashoffset]=\"vm.ringOffset\" />\n </svg>\n <span class=\"pecb-course-panel__disc-glyph\">\n @if (vm.isQuiz) {\n <svg [attr.width]=\"vm.quizIconSize\" [attr.height]=\"vm.quizIconSize\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <path d=\"M9.5 9.2a2.5 2.5 0 1 1 3.4 2.3c-.6.3-.9.7-.9 1.4v.4\" />\n <circle cx=\"12\" cy=\"16.3\" r=\".6\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n } @else {\n <svg [attr.width]=\"vm.playIconSize\" [attr.height]=\"vm.playIconSize\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <polygon points=\"6 4 20 12 6 20 6 4\" />\n </svg>\n }\n </span>\n } @else if (vm.isLocked) {\n <svg [attr.width]=\"vm.checkSize\" [attr.height]=\"vm.checkSize\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"5\" y=\"11\" width=\"14\" height=\"9\" rx=\"2\" />\n <path d=\"M8 11V8a4 4 0 0 1 8 0v3\" />\n </svg>\n } @else if (vm.isQuiz) {\n <svg [attr.width]=\"vm.quizIconSize\" [attr.height]=\"vm.quizIconSize\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <path d=\"M9.5 9.2a2.5 2.5 0 1 1 3.4 2.3c-.6.3-.9.7-.9 1.4v.4\" />\n <circle cx=\"12\" cy=\"16.3\" r=\".6\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n } @else {\n <svg [attr.width]=\"vm.playIconSize\" [attr.height]=\"vm.playIconSize\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <polygon points=\"6 4 20 12 6 20 6 4\" />\n </svg>\n }\n </span>\n\n @if (vm.connector) {\n <span\n class=\"pecb-course-panel__connector\"\n [class.pecb-course-panel__connector--done]=\"vm.connectorDone\"\n aria-hidden=\"true\"\n ></span>\n }\n </span>\n\n <!-- Body -->\n <span class=\"pecb-course-panel__row-body\">\n @if (vm.isQuiz) {\n <span class=\"pecb-course-panel__quiz-badge-line\">\n <span class=\"pecb-course-panel__quiz-badge\">{{ text().quiz }}</span>\n </span>\n }\n\n @if (vm.displayTitle) {\n <span class=\"pecb-course-panel__row-title\">{{ vm.displayTitle }}</span>\n }\n\n <span class=\"pecb-course-panel__row-meta\">\n <span class=\"pecb-course-panel__meta-primary\">\n @if (vm.isQuiz) {\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <path d=\"M9.5 9.2a2.5 2.5 0 1 1 3.4 2.3c-.6.3-.9.7-.9 1.4v.4\" />\n <circle cx=\"12\" cy=\"16.3\" r=\".6\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n {{ vm.questionsLabel }}\n } @else {\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <polyline points=\"12 7 12 12 15.5 14\" />\n </svg>\n {{ vm.durationLabel }}\n }\n </span>\n\n @if (vm.quizResultLabel) {\n <span\n class=\"pecb-course-panel__meta-result\"\n [class.pecb-course-panel__meta-result--passed]=\"vm.isCompleted\"\n >\n @if (vm.isCompleted) {\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n }\n {{ vm.quizResultLabel }}\n </span>\n }\n\n @if (vm.timeLabel) {\n <span class=\"pecb-course-panel__meta-time\">\n @if (vm.isStarted) {\n <svg width=\"10\" height=\"10\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <polygon points=\"6 4 20 12 6 20 6 4\" />\n </svg>\n }\n {{ vm.timeLabel }}\n </span>\n }\n </span>\n\n @if (vm.showBar) {\n <span class=\"pecb-course-panel__bar\" aria-hidden=\"true\">\n <span class=\"pecb-course-panel__bar-fill\" [style.width.%]=\"vm.barPercent\"></span>\n </span>\n }\n </span>\n\n @if (solo) {\n <span class=\"pecb-course-panel__solo-play\" aria-hidden=\"true\">\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <polygon points=\"6 4 20 12 6 20 6 4\" />\n </svg>\n </span>\n }\n </button>\n</ng-template>\n\n<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n Panel\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n<div [class]=\"hostClasses()\">\n @if (showTabs()) {\n <div class=\"pecb-course-panel__tabs\" role=\"tablist\" [attr.aria-label]=\"text().lessonList\">\n <button\n type=\"button\"\n role=\"tab\"\n class=\"pecb-course-panel__tab\"\n [class.pecb-course-panel__tab--active]=\"tab() === 'content'\"\n [id]=\"uid + '-tab-content'\"\n [attr.aria-selected]=\"tab() === 'content'\"\n [attr.aria-controls]=\"uid + '-panel-content'\"\n [attr.tabindex]=\"tab() === 'content' ? 0 : -1\"\n (click)=\"setTab('content')\"\n (keydown)=\"onTabKeydown($event)\"\n >\n {{ text().contentTab }}\n @if (tab() === 'content') {\n <span class=\"pecb-course-panel__tab-indicator\" aria-hidden=\"true\"></span>\n }\n </button>\n <button\n type=\"button\"\n role=\"tab\"\n class=\"pecb-course-panel__tab\"\n [class.pecb-course-panel__tab--active]=\"tab() === 'transcript'\"\n [id]=\"uid + '-tab-transcript'\"\n [attr.aria-selected]=\"tab() === 'transcript'\"\n [attr.aria-controls]=\"uid + '-panel-transcript'\"\n [attr.tabindex]=\"tab() === 'transcript' ? 0 : -1\"\n (click)=\"setTab('transcript')\"\n (keydown)=\"onTabKeydown($event)\"\n >\n {{ text().transcriptTab }}\n @if (tab() === 'transcript') {\n <span class=\"pecb-course-panel__tab-indicator\" aria-hidden=\"true\"></span>\n }\n </button>\n </div>\n }\n\n <!-- \u2500\u2500 Content tab \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (tab() === 'content') {\n <div\n class=\"pecb-course-panel__body\"\n role=\"tabpanel\"\n [id]=\"uid + '-panel-content'\"\n [attr.aria-labelledby]=\"showTabs() ? uid + '-tab-content' : null\"\n >\n @if (showProgress()) {\n <div class=\"pecb-course-panel__progress\">\n <span class=\"pecb-course-panel__progress-ring\">\n <svg width=\"56\" height=\"56\" viewBox=\"0 0 56 56\" aria-hidden=\"true\">\n <circle class=\"pecb-course-panel__progress-track\" cx=\"28\" cy=\"28\" [attr.r]=\"progressRing().radius\" fill=\"none\" stroke-width=\"5\" />\n <circle\n class=\"pecb-course-panel__progress-value\"\n cx=\"28\" cy=\"28\" [attr.r]=\"progressRing().radius\" fill=\"none\" stroke-width=\"5\" stroke-linecap=\"round\"\n [attr.stroke-dasharray]=\"progressRing().circumference\"\n [attr.stroke-dashoffset]=\"progressRing().offset\" />\n </svg>\n <span class=\"pecb-course-panel__progress-percent\">{{ progress().percent }}%</span>\n </span>\n <span class=\"pecb-course-panel__progress-text\">\n <span class=\"pecb-course-panel__progress-title\">{{ text().courseProgress }}</span>\n <span class=\"pecb-course-panel__progress-detail\">{{ progressDetail() }}</span>\n </span>\n @if (showCertificate()) {\n <button type=\"button\" class=\"pecb-course-panel__certificate\" (click)=\"certificateClick.emit()\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"8\" r=\"5\" />\n <path d=\"M8.5 12.5 7 21l5-2.5L17 21l-1.5-8.5\" />\n </svg>\n {{ text().certificate }}\n </button>\n }\n </div>\n }\n\n @if (showSearch()) {\n <div class=\"pecb-course-panel__search\">\n <div class=\"pecb-course-panel__search-box\">\n <span class=\"pecb-course-panel__search-icon\" aria-hidden=\"true\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"7\" />\n <path d=\"m20 20-3.2-3.2\" />\n </svg>\n </span>\n <input\n type=\"search\"\n class=\"pecb-course-panel__search-input\"\n [value]=\"query()\"\n [attr.placeholder]=\"text().searchLessons\"\n [attr.aria-label]=\"text().searchLessons\"\n (input)=\"onQueryInput($event)\"\n />\n </div>\n </div>\n }\n\n <div class=\"pecb-course-panel__scroll pecb-course-panel__list\">\n @if (isEmpty()) {\n <p class=\"pecb-course-panel__empty\">{{ noLessonsLabel() }}</p>\n } @else if (layout() === 'timeline') {\n <div class=\"pecb-course-panel__timeline\">\n @for (row of timelineRows(); track row.key) {\n @if (row.header; as header) {\n <div class=\"pecb-course-panel__timeline-head\">\n <span class=\"pecb-course-panel__timeline-label\">{{ sectionLabel(header.number) }}</span>\n <span class=\"pecb-course-panel__timeline-rule\" aria-hidden=\"true\"></span>\n <span class=\"pecb-course-panel__timeline-count\">{{ header.stats.done }}/{{ header.stats.total }}</span>\n </div>\n } @else {\n <ng-container *ngTemplateOutlet=\"lessonRowTpl; context: { $implicit: row.lesson }\"></ng-container>\n }\n }\n </div>\n } @else {\n @for (view of moduleViews(); track view.id) {\n @if (view.solo; as soloView) {\n <div class=\"pecb-course-panel__section pecb-course-panel__section--solo\">\n <ng-container\n *ngTemplateOutlet=\"lessonRowTpl; context: { $implicit: soloView, solo: true }\"\n ></ng-container>\n </div>\n } @else {\n <div\n class=\"pecb-course-panel__section\"\n [class.pecb-course-panel__section--open]=\"isModuleOpen(view.id)\"\n >\n <button\n type=\"button\"\n class=\"pecb-course-panel__section-head\"\n [class.pecb-course-panel__section-head--open]=\"isModuleOpen(view.id)\"\n [id]=\"uid + '-head-' + view.id\"\n [attr.aria-expanded]=\"isModuleOpen(view.id)\"\n [attr.aria-controls]=\"uid + '-region-' + view.id\"\n (click)=\"toggleModule(view)\"\n (keydown)=\"onModuleHeaderKeydown($event)\"\n >\n <span\n class=\"pecb-course-panel__section-badge\"\n [class.pecb-course-panel__section-badge--done]=\"view.allDone\"\n [class.pecb-course-panel__section-badge--active]=\"!view.allDone && (view.stats.playing || view.stats.started)\"\n aria-hidden=\"true\"\n >\n @if (view.allDone) {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n } @else if (view.stats.playing) {\n <svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <rect x=\"6\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n <rect x=\"14\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n </svg>\n } @else {\n <svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <polygon points=\"6 4 20 12 6 20 6 4\" />\n </svg>\n }\n </span>\n\n <span class=\"pecb-course-panel__section-text\">\n <span class=\"pecb-course-panel__section-title\">{{ sectionLabel(view.number) }}</span>\n <span class=\"pecb-course-panel__section-meta\">\n <span>{{ view.statsLabel }}</span>\n @if (view.stats.durationSeconds > 0) {\n <span class=\"pecb-course-panel__section-duration\">\n <svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <polyline points=\"12 7 12 12 15.5 14\" />\n </svg>\n {{ view.durationLabel }}\n </span>\n }\n </span>\n </span>\n\n <span class=\"pecb-course-panel__section-chevron\" aria-hidden=\"true\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"6 9 12 15 18 9\" />\n </svg>\n </span>\n </button>\n\n <div class=\"pecb-course-panel__section-wrap\">\n <div\n class=\"pecb-course-panel__section-clip\"\n [id]=\"uid + '-region-' + view.id\"\n role=\"region\"\n [attr.aria-labelledby]=\"uid + '-head-' + view.id\"\n [attr.inert]=\"isModuleOpen(view.id) ? null : ''\"\n >\n <div class=\"pecb-course-panel__section-body\">\n @for (run of view.runs; track run.key) {\n @if (run.parts) {\n <p class=\"pecb-course-panel__run-title\">{{ run.base }}</p>\n <div class=\"pecb-course-panel__rail\">\n <span class=\"pecb-course-panel__rail-line\" aria-hidden=\"true\"></span>\n @for (lessonView of run.lessons; track lessonView.id) {\n <ng-container *ngTemplateOutlet=\"lessonRowTpl; context: { $implicit: lessonView }\"></ng-container>\n }\n </div>\n } @else {\n @for (lessonView of run.lessons; track lessonView.id) {\n <ng-container *ngTemplateOutlet=\"lessonRowTpl; context: { $implicit: lessonView }\"></ng-container>\n }\n }\n }\n </div>\n </div>\n </div>\n </div>\n }\n }\n }\n </div>\n </div>\n } @else {\n <!-- \u2500\u2500 Transcript tab \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div\n class=\"pecb-course-panel__body\"\n role=\"tabpanel\"\n [id]=\"uid + '-panel-transcript'\"\n [attr.aria-labelledby]=\"showTabs() ? uid + '-tab-transcript' : null\"\n >\n @if (showTranscriptSearch()) {\n <div class=\"pecb-course-panel__search\">\n <div class=\"pecb-course-panel__search-box\">\n <span class=\"pecb-course-panel__search-icon\" aria-hidden=\"true\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"7\" />\n <path d=\"m20 20-3.2-3.2\" />\n </svg>\n </span>\n <input\n type=\"search\"\n class=\"pecb-course-panel__search-input\"\n [value]=\"transcriptQuery()\"\n [attr.placeholder]=\"text().searchTranscript\"\n [attr.aria-label]=\"text().searchTranscript\"\n (input)=\"onTranscriptQueryInput($event)\"\n />\n @if (transcriptQuery()) {\n <button\n type=\"button\"\n class=\"pecb-course-panel__search-clear\"\n [attr.aria-label]=\"text().clearSearch\"\n (click)=\"clearTranscriptQuery()\"\n >\u00D7</button>\n }\n </div>\n </div>\n }\n\n <div class=\"pecb-course-panel__scroll pecb-course-panel__transcript\" #transcriptScroll>\n @if (transcriptEmpty()) {\n <p class=\"pecb-course-panel__empty\">{{ noTranscriptLabel() }}</p>\n } @else {\n <div class=\"pecb-course-panel__cues\">\n @for (row of transcriptRows(); track row.key) {\n <button\n type=\"button\"\n class=\"pecb-course-panel__cue\"\n [class.pecb-course-panel__cue--current]=\"row.current\"\n [attr.data-current]=\"row.current\"\n [attr.aria-current]=\"row.current ? 'true' : null\"\n (click)=\"seekToCue(row.cue)\"\n >\n <span class=\"pecb-course-panel__cue-time\">{{ row.timeLabel }}</span>\n <span class=\"pecb-course-panel__cue-text\">{{ row.before }}@if (row.match) {<mark class=\"pecb-course-panel__mark\">{{ row.match }}</mark>}{{ row.after }}</span>\n </button>\n }\n </div>\n }\n </div>\n\n @if (showAutoscroll()) {\n <div class=\"pecb-course-panel__footer\">\n <label class=\"pecb-course-panel__autoscroll\">\n <input\n type=\"checkbox\"\n class=\"pecb-course-panel__autoscroll-input\"\n [checked]=\"autoscroll()\"\n (change)=\"onAutoscrollChange($event)\"\n />\n <span class=\"pecb-course-panel__autoscroll-box\" aria-hidden=\"true\">\n @if (autoscroll()) {\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n }\n </span>\n <span class=\"pecb-course-panel__autoscroll-label\">{{ text().autoscroll }}</span>\n </label>\n </div>\n }\n </div>\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%;min-height:0;container-type:inline-size;container-name:pecb-course-panel}.pecb-course-panel{--_row-pad-y: 11px;--_row-pad-left: 14px;--_row-pad-right: 16px;--_row-gap: 13px;--_disc: 30px;--_section-pad-y: 15px;--_title-size: 13.5px;display:flex;flex-direction:column;height:100%;min-height:0;box-sizing:border-box;background:var(--pecb-course-panel-bg, #ffffff);font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);line-height:normal;color:#212427;overflow:hidden}.pecb-course-panel *,.pecb-course-panel *:before,.pecb-course-panel *:after{box-sizing:border-box}.pecb-course-panel--compact{--_row-pad-y: 8px;--_row-pad-left: 12px;--_row-pad-right: 14px;--_row-gap: 11px;--_disc: 26px;--_section-pad-y: 12px;--_title-size: 12.5px}.pecb-course-panel--accent-red{--pecb-course-panel-accent: #a11e29;--pecb-course-panel-accent-soft: rgba(161, 30, 41, .27)}.pecb-course-panel__tabs{display:flex;flex:0 0 auto;padding:0 20px;border-bottom:1px solid var(--pecb-course-panel-border, #ececec)}.pecb-course-panel__tab{border:none;background:none;font:inherit;color:inherit;position:relative;flex:1;padding:16px 0 14px;font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:14px;font-weight:500;color:#616161;cursor:pointer;transition:color .15s ease-in-out}.pecb-course-panel__tab:focus{outline:none}.pecb-course-panel__tab:hover{color:#212427}.pecb-course-panel__tab:focus-visible{outline:2px solid #a11e29;outline-offset:-3px;border-radius:6px}.pecb-course-panel__tab--active{font-weight:700;color:#a11e29}.pecb-course-panel__tab-indicator{position:absolute;left:0;right:0;bottom:-1px;height:2.5px;border-radius:3px;background:#a11e29}.pecb-course-panel__body{display:flex;flex:1 1 auto;flex-direction:column;min-height:0}.pecb-course-panel__scroll{flex:1 1 auto;min-height:0;overflow-y:auto;overflow-x:hidden;overscroll-behavior:contain;scrollbar-width:thin;scrollbar-color:#ececec transparent}.pecb-course-panel__scroll::-webkit-scrollbar{width:8px}.pecb-course-panel__scroll::-webkit-scrollbar-thumb{background:#ececec;border-radius:8px}.pecb-course-panel__scroll::-webkit-scrollbar-track{background:transparent}.pecb-course-panel__empty{margin:0;padding:40px;text-align:center;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:13px;color:#919191}.pecb-course-panel__progress{display:flex;flex:0 0 auto;align-items:center;gap:16px;padding:16px 20px;border-bottom:1px solid var(--pecb-course-panel-border, #ececec)}.pecb-course-panel__progress-ring{position:relative;flex:0 0 auto;width:56px;height:56px}.pecb-course-panel__progress-ring svg{display:block;transform:rotate(-90deg)}.pecb-course-panel__progress-track{stroke:#ececec}.pecb-course-panel__progress-value{stroke:var(--pecb-course-panel-accent, #212427);transition:stroke-dashoffset .5s ease}.pecb-course-panel__progress-percent{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:14px;font-weight:700;color:#212427}.pecb-course-panel__progress-text{display:flex;flex:1 1 auto;flex-direction:column;min-width:0}.pecb-course-panel__progress-title{font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:14px;font-weight:700;color:#212427}.pecb-course-panel__progress-detail{margin-top:3px;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:12px;color:#616161}.pecb-course-panel__certificate{border:none;background:none;font:inherit;color:inherit;display:inline-flex;flex:0 0 auto;align-items:center;gap:6px;padding:6px 12px;border-radius:9999px;background:#212427;color:#fff;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:12px;font-weight:600;cursor:pointer;transition:background .15s ease-in-out}.pecb-course-panel__certificate:focus{outline:none}.pecb-course-panel__certificate:hover{background:#313131}.pecb-course-panel__certificate:focus-visible{outline:2px solid #a11e29;outline-offset:2px}.pecb-course-panel__search{flex:0 0 auto;padding:12px 20px 10px}.pecb-course-panel__search-box{display:flex;align-items:center;gap:9px;min-height:40px;padding:9px 12px;border:1px solid #c2c2c2;border-radius:6px;background:#fff;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}.pecb-course-panel__search-box:focus-within{border-color:#212427;box-shadow:0 0 0 3px #21242714}.pecb-course-panel__search-icon{display:flex;flex:0 0 auto;color:#919191}.pecb-course-panel__search-input{flex:1 1 auto;min-width:0;border:none;outline:none;background:transparent;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:13px;color:#212427;appearance:none}.pecb-course-panel__search-input::placeholder{color:#919191}.pecb-course-panel__search-input::-webkit-search-cancel-button,.pecb-course-panel__search-input::-webkit-search-decoration{appearance:none;display:none}.pecb-course-panel__search-clear{padding:0;border:none;background:none;font:inherit;color:inherit;display:flex;flex:0 0 auto;align-items:center;justify-content:center;width:18px;height:18px;border-radius:50%;color:#919191;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:17px;line-height:1;cursor:pointer}.pecb-course-panel__search-clear:focus{outline:none}.pecb-course-panel__search-clear:hover{background:#f4f4f4;color:#212427}.pecb-course-panel__search-clear:focus-visible{outline:2px solid #a11e29;outline-offset:1px}.pecb-course-panel__section{border-bottom:1px solid var(--pecb-course-panel-border, #ececec);background:transparent}.pecb-course-panel__section--open{background:#fff}.pecb-course-panel__section--solo{border-bottom:1px solid var(--pecb-course-panel-border, #ececec)}.pecb-course-panel__section-head{padding:0;border:none;background:none;font:inherit;color:inherit;display:flex;align-items:center;gap:12px;width:100%;padding:var(--_section-pad-y) 16px;text-align:left;background:#f4f4f4;cursor:pointer;transition:background .15s ease-in-out}.pecb-course-panel__section-head:focus{outline:none}.pecb-course-panel__section-head:hover{background:#fff}.pecb-course-panel__section-head:focus-visible{outline:2px solid #a11e29;outline-offset:-2px}.pecb-course-panel__section-head--open{background:#ececec}.pecb-course-panel__section-head--open:hover{background:#f4f4f4}.pecb-course-panel__section-badge{display:flex;flex:0 0 auto;align-items:center;justify-content:center;width:30px;height:30px;border-radius:8px;background:#c2c2c2;color:#fff;font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:13px;font-weight:700}.pecb-course-panel__section-badge--active{background:var(--pecb-course-panel-accent, #212427)}.pecb-course-panel__section-badge--done{background:#e6f4ea;color:green}.pecb-course-panel__section-text{display:flex;flex:1 1 auto;flex-direction:column;min-width:0}.pecb-course-panel__section-title{font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:13.5px;font-weight:600;line-height:1.3;color:#212427}.pecb-course-panel__section-meta{display:flex;align-items:center;gap:10px;margin-top:4px;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:11.5px;color:#919191}.pecb-course-panel__section-duration{display:inline-flex;align-items:center;gap:4px}.pecb-course-panel__section-chevron{display:flex;flex:0 0 auto;color:#919191;transform:rotate(-90deg);transition:transform .18s ease}.pecb-course-panel__section-head--open .pecb-course-panel__section-chevron{transform:rotate(0)}.pecb-course-panel__section-wrap{display:grid;grid-template-rows:0fr;transition:grid-template-rows .22s ease}.pecb-course-panel__section--open .pecb-course-panel__section-wrap{grid-template-rows:1fr}.pecb-course-panel__section-clip{overflow:hidden;min-height:0}.pecb-course-panel__section-body{display:flex;flex-direction:column;gap:2px;padding:6px 12px 10px var(--_row-pad-left)}.pecb-course-panel--compact .pecb-course-panel__section-body{padding:4px 10px 8px var(--_row-pad-left)}.pecb-course-panel__run-title{margin:0;padding:9px 8px 5px 12px;font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:13px;font-weight:600;line-height:1.35;color:#212427;text-wrap:pretty}.pecb-course-panel__rail{position:relative}.pecb-course-panel__rail-line{position:absolute;left:calc(var(--_row-pad-left) + var(--_disc) / 2 - 1px);top:calc(var(--_row-pad-y) + var(--_disc) / 2);bottom:calc(var(--_row-pad-y) + var(--_disc) / 2);z-index:1;width:2px;border-radius:2px;background:#ececec}.pecb-course-panel__row{padding:0;border:none;background:none;font:inherit;color:inherit;position:relative;display:flex;align-items:center;gap:var(--_row-gap);width:100%;padding:var(--_row-pad-y) var(--_row-pad-right) var(--_row-pad-y) var(--_row-pad-left);border-radius:8px;text-align:left;background:transparent;cursor:pointer;transition:background .14s ease,box-shadow .14s ease}.pecb-course-panel__row:focus{outline:none}.pecb-course-panel__row:hover:not(:disabled):not(.pecb-course-panel__row--active){background:#fafafa}.pecb-course-panel__row:focus-visible{outline:2px solid #a11e29;outline-offset:-2px}.pecb-course-panel__row:disabled{cursor:not-allowed}.pecb-course-panel__row--locked{opacity:.72}.pecb-course-panel__row--active{background:#fff;box-shadow:inset 0 0 0 1px var(--pecb-course-panel-border, #ececec)}.pecb-course-panel__row--active.pecb-course-panel__row--completed{background:#e6f4ea}.pecb-course-panel__row--active.pecb-course-panel__row--started{background:#fff4e5}.pecb-course-panel__row--active.pecb-course-panel__row--playing{background:#fbeaec;box-shadow:inset 0 0 0 1px var(--pecb-course-panel-accent-soft, rgba(33, 36, 39, .27))}.pecb-course-panel__row--active.pecb-course-panel__row--quiz{background:#eee8ff}.pecb-course-panel__row--solo{gap:12px;padding:var(--_section-pad-y) 16px;border-radius:0}.pecb-course-panel__row-marker{position:absolute;left:0;top:8px;bottom:8px;width:3px;border-radius:3px;background:var(--pecb-course-panel-accent, #212427)}.pecb-course-panel__row--solo .pecb-course-panel__row-marker{top:10px;bottom:10px}.pecb-course-panel__row--solo .pecb-course-panel__row-title{font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:13.5px;font-weight:600;line-height:1.3}.pecb-course-panel__solo-play{display:flex;flex:0 0 auto;align-items:center;justify-content:center;width:30px;height:30px;border-radius:50%;background:#f4f4f4;color:#616161}.pecb-course-panel__disc-col{position:relative;display:flex;flex:0 0 auto;flex-direction:column;align-items:center;align-self:stretch}.pecb-course-panel__disc{position:relative;z-index:2;display:flex;flex:0 0 auto;align-items:center;justify-content:center;border-radius:50%;background:#fff;border:none;color:#919191}.pecb-course-panel__disc svg{display:block}.pecb-course-panel__disc--plain{border:1.5px solid #ececec}.pecb-course-panel__disc--completed{background:#57bc57;color:#fff}.pecb-course-panel__disc--playing{background:var(--pecb-course-panel-accent, #212427);color:#fff;box-shadow:0 0 0 4px var(--pecb-course-panel-accent-soft, rgba(33, 36, 39, .27))}.pecb-course-panel__disc--started{background:#fff;color:#ff9c4a}.pecb-course-panel__disc--locked{color:#c2c2c2}.pecb-course-panel__ring{position:absolute;inset:0;transform:rotate(-90deg)}.pecb-course-panel__ring-track{stroke:#ececec}.pecb-course-panel__ring-value{stroke:#ff9c4a;transition:stroke-dashoffset .3s ease}.pecb-course-panel__disc-glyph{position:relative;display:flex;color:#ff9c4a}.pecb-course-panel__connector{flex:1 1 auto;width:2px;margin-top:2px;border-radius:2px;background:#ececec}.pecb-course-panel__connector--done{background:#57bc57}.pecb-course-panel__row-body{display:block;flex:1 1 auto;min-width:0}.pecb-course-panel__quiz-badge-line{display:flex;align-items:center;min-height:18px;margin-bottom:5px}.pecb-course-panel__quiz-badge{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:9999px;background:#8e6df0;color:#fff;font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:9.5px;font-weight:700;letter-spacing:.05em;text-transform:uppercase}.pecb-course-panel__row-title{display:block;margin-bottom:4px;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:var(--_title-size);font-weight:500;line-height:1.35;color:#212427;text-wrap:pretty;overflow-wrap:anywhere}.pecb-course-panel--compact .pecb-course-panel__row-title{margin-bottom:2px}.pecb-course-panel__row--active .pecb-course-panel__row-title{font-weight:600}.pecb-course-panel__row-meta{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.pecb-course-panel__meta-primary{display:inline-flex;align-items:center;gap:5px;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:11.5px;font-weight:500;color:#919191}.pecb-course-panel__row--quiz .pecb-course-panel__meta-primary{color:#8e6df0}.pecb-course-panel__meta-result{display:inline-flex;align-items:center;gap:4px;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:11px;font-weight:600;color:#ff9c4a}.pecb-course-panel__meta-result--passed{color:#57bc57}.pecb-course-panel__meta-time{display:inline-flex;align-items:center;gap:5px;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:11px;font-weight:600;color:#ff9c4a}.pecb-course-panel__row--playing .pecb-course-panel__meta-time{color:var(--pecb-course-panel-accent, #212427)}.pecb-course-panel__bar{display:block;margin-top:7px;height:4px;border-radius:4px;background:#ececec;overflow:hidden}.pecb-course-panel__bar-fill{display:block;height:100%;border-radius:4px;background:#ff9c4a;transition:width .2s linear}.pecb-course-panel__row--playing .pecb-course-panel__bar-fill{background:var(--pecb-course-panel-accent, #212427)}.pecb-course-panel__timeline{padding:6px 14px 16px}.pecb-course-panel--compact .pecb-course-panel__timeline{padding:4px 12px 12px}.pecb-course-panel__timeline-head{display:flex;align-items:center;gap:10px;padding:18px 2px 8px}.pecb-course-panel--compact .pecb-course-panel__timeline-head{padding:14px 2px 6px}.pecb-course-panel__timeline-label{font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:10.5px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;color:#919191}.pecb-course-panel__timeline-rule{flex:1 1 auto;height:1px;background:var(--pecb-course-panel-border, #ececec)}.pecb-course-panel__timeline-count{font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:11px;color:#919191}.pecb-course-panel__transcript{padding:0 14px}.pecb-course-panel__cues{padding:8px 6px 20px}.pecb-course-panel__cue{border:none;background:none;font:inherit;color:inherit;display:flex;gap:14px;width:100%;padding:10px 12px;border-radius:8px;text-align:left;background:transparent;cursor:pointer;transition:background .14s ease}.pecb-course-panel__cue:focus{outline:none}.pecb-course-panel__cue:hover:not(.pecb-course-panel__cue--current){background:#fafafa}.pecb-course-panel__cue:focus-visible{outline:2px solid #a11e29;outline-offset:-2px}.pecb-course-panel__cue--current{background:#fbeaec}.pecb-course-panel__cue-time{flex:0 0 auto;width:38px;padding-top:1px;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:12px;font-weight:600;font-variant-numeric:tabular-nums;color:#919191}.pecb-course-panel__cue--current .pecb-course-panel__cue-time{color:#a11e29}.pecb-course-panel__cue-text{font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:13.5px;line-height:1.55;color:#313131;text-wrap:pretty;overflow-wrap:anywhere}.pecb-course-panel__cue--current .pecb-course-panel__cue-text{color:#212427}.pecb-course-panel__mark{padding:0 1px;border-radius:3px;background:#fce3b4;color:inherit}.pecb-course-panel__footer{flex:0 0 auto;padding:12px 20px;border-top:1px solid var(--pecb-course-panel-border, #ececec);background:var(--pecb-course-panel-bg, #ffffff)}.pecb-course-panel__autoscroll{display:flex;align-items:center;gap:10px;cursor:pointer;-webkit-user-select:none;user-select:none}.pecb-course-panel__autoscroll-input{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none}.pecb-course-panel__autoscroll-box{display:flex;flex:0 0 auto;align-items:center;justify-content:center;width:18px;height:18px;border:1.5px solid #c2c2c2;border-radius:5px;background:#fff;color:#fff;transition:all .14s ease}.pecb-course-panel__autoscroll-input:checked+.pecb-course-panel__autoscroll-box{border:1px solid #a11e29;background:#a11e29}.pecb-course-panel__autoscroll-input:focus-visible+.pecb-course-panel__autoscroll-box{outline:2px solid #a11e29;outline-offset:2px}.pecb-course-panel__autoscroll-label{font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:13px;font-weight:500;color:#616161}@container pecb-course-panel (max-width: 380px){.pecb-course-panel{--_row-pad-right: 12px}.pecb-course-panel__tabs{padding:0 12px}.pecb-course-panel__tab{padding:12px 0 10px;font-size:13px}.pecb-course-panel__search{padding:10px 12px 8px}.pecb-course-panel__progress{flex-wrap:wrap;gap:12px;padding:12px 16px}.pecb-course-panel__section-head{gap:10px;padding:12px}.pecb-course-panel__section-body{padding-right:8px}.pecb-course-panel__transcript{padding:0 8px}.pecb-course-panel__cue{gap:10px;padding:8px 10px}.pecb-course-panel__cue-text{font-size:13px}.pecb-course-panel__footer{padding:10px 16px}.pecb-course-panel__timeline{padding:4px 10px 12px}}@media (hover: none) and (pointer: coarse){.pecb-course-panel__row,.pecb-course-panel__cue{min-height:44px}}@media (prefers-reduced-motion: reduce){.pecb-course-panel *,.pecb-course-panel *:before,.pecb-course-panel *:after{transition-duration:.01ms!important;animation-duration:.01ms!important}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
12269
|
+
}
|
|
12270
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: CourseContentPanelComponent, decorators: [{
|
|
12271
|
+
type: Component,
|
|
12272
|
+
args: [{ selector: 'pecb-course-content-panel', standalone: true, imports: [NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'pecb-course-panel-host' }, template: "<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n Lesson row \u2014 one <button> per lesson, shared by the grouped, parts-rail,\n timeline and solo renderings.\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n<ng-template #lessonRowTpl let-vm let-solo=\"solo\">\n <button\n type=\"button\"\n class=\"pecb-course-panel__row\"\n [class.pecb-course-panel__row--active]=\"vm.active\"\n [class.pecb-course-panel__row--quiz]=\"vm.isQuiz\"\n [class.pecb-course-panel__row--playing]=\"vm.isPlaying\"\n [class.pecb-course-panel__row--started]=\"vm.isStarted\"\n [class.pecb-course-panel__row--completed]=\"vm.isCompleted\"\n [class.pecb-course-panel__row--locked]=\"vm.isLocked\"\n [class.pecb-course-panel__row--solo]=\"!!solo\"\n [disabled]=\"vm.isLocked\"\n [attr.aria-current]=\"vm.active ? 'true' : null\"\n [attr.aria-label]=\"vm.ariaLabel\"\n (click)=\"selectLesson(vm.lesson)\"\n >\n @if (vm.active && vm.isPlaying) {\n <span class=\"pecb-course-panel__row-marker\" aria-hidden=\"true\"></span>\n }\n\n <!-- Status disc (+ optional timeline connector) -->\n <span class=\"pecb-course-panel__disc-col\">\n <span\n class=\"pecb-course-panel__disc\"\n [class.pecb-course-panel__disc--completed]=\"vm.isCompleted\"\n [class.pecb-course-panel__disc--playing]=\"vm.isPlaying\"\n [class.pecb-course-panel__disc--started]=\"vm.isStarted\"\n [class.pecb-course-panel__disc--locked]=\"vm.isLocked\"\n [class.pecb-course-panel__disc--plain]=\"!vm.isCompleted && !vm.isPlaying && !vm.isStarted\"\n [style.width.px]=\"vm.discSize\"\n [style.height.px]=\"vm.discSize\"\n aria-hidden=\"true\"\n >\n @if (vm.isCompleted) {\n <svg [attr.width]=\"vm.checkSize\" [attr.height]=\"vm.checkSize\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n } @else if (vm.isPlaying) {\n <svg [attr.width]=\"vm.pauseSize\" [attr.height]=\"vm.pauseSize\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <rect x=\"6\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n <rect x=\"14\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n </svg>\n } @else if (vm.isStarted) {\n <svg class=\"pecb-course-panel__ring\" [attr.width]=\"vm.discSize\" [attr.height]=\"vm.discSize\" [attr.viewBox]=\"vm.ringViewBox\">\n <circle\n class=\"pecb-course-panel__ring-track\"\n [attr.cx]=\"vm.discCenter\" [attr.cy]=\"vm.discCenter\" [attr.r]=\"vm.ringRadius\"\n fill=\"none\" stroke-width=\"3\" />\n <circle\n class=\"pecb-course-panel__ring-value\"\n [attr.cx]=\"vm.discCenter\" [attr.cy]=\"vm.discCenter\" [attr.r]=\"vm.ringRadius\"\n fill=\"none\" stroke-width=\"3\" stroke-linecap=\"round\"\n [attr.stroke-dasharray]=\"vm.ringCircumference\"\n [attr.stroke-dashoffset]=\"vm.ringOffset\" />\n </svg>\n <span class=\"pecb-course-panel__disc-glyph\">\n @if (vm.isQuiz) {\n <svg [attr.width]=\"vm.quizIconSize\" [attr.height]=\"vm.quizIconSize\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <path d=\"M9.5 9.2a2.5 2.5 0 1 1 3.4 2.3c-.6.3-.9.7-.9 1.4v.4\" />\n <circle cx=\"12\" cy=\"16.3\" r=\".6\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n } @else {\n <svg [attr.width]=\"vm.playIconSize\" [attr.height]=\"vm.playIconSize\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <polygon points=\"6 4 20 12 6 20 6 4\" />\n </svg>\n }\n </span>\n } @else if (vm.isLocked) {\n <svg [attr.width]=\"vm.checkSize\" [attr.height]=\"vm.checkSize\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"5\" y=\"11\" width=\"14\" height=\"9\" rx=\"2\" />\n <path d=\"M8 11V8a4 4 0 0 1 8 0v3\" />\n </svg>\n } @else if (vm.isQuiz) {\n <svg [attr.width]=\"vm.quizIconSize\" [attr.height]=\"vm.quizIconSize\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <path d=\"M9.5 9.2a2.5 2.5 0 1 1 3.4 2.3c-.6.3-.9.7-.9 1.4v.4\" />\n <circle cx=\"12\" cy=\"16.3\" r=\".6\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n } @else {\n <svg [attr.width]=\"vm.playIconSize\" [attr.height]=\"vm.playIconSize\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <polygon points=\"6 4 20 12 6 20 6 4\" />\n </svg>\n }\n </span>\n\n @if (vm.connector) {\n <span\n class=\"pecb-course-panel__connector\"\n [class.pecb-course-panel__connector--done]=\"vm.connectorDone\"\n aria-hidden=\"true\"\n ></span>\n }\n </span>\n\n <!-- Body -->\n <span class=\"pecb-course-panel__row-body\">\n @if (vm.isQuiz) {\n <span class=\"pecb-course-panel__quiz-badge-line\">\n <span class=\"pecb-course-panel__quiz-badge\">{{ text().quiz }}</span>\n </span>\n }\n\n @if (vm.displayTitle) {\n <span class=\"pecb-course-panel__row-title\">{{ vm.displayTitle }}</span>\n }\n\n <span class=\"pecb-course-panel__row-meta\">\n <span class=\"pecb-course-panel__meta-primary\">\n @if (vm.isQuiz) {\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <path d=\"M9.5 9.2a2.5 2.5 0 1 1 3.4 2.3c-.6.3-.9.7-.9 1.4v.4\" />\n <circle cx=\"12\" cy=\"16.3\" r=\".6\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n {{ vm.questionsLabel }}\n } @else {\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <polyline points=\"12 7 12 12 15.5 14\" />\n </svg>\n {{ vm.durationLabel }}\n }\n </span>\n\n @if (vm.quizResultLabel) {\n <span\n class=\"pecb-course-panel__meta-result\"\n [class.pecb-course-panel__meta-result--passed]=\"vm.isCompleted\"\n >\n @if (vm.isCompleted) {\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n }\n {{ vm.quizResultLabel }}\n </span>\n }\n\n @if (vm.timeLabel) {\n <span class=\"pecb-course-panel__meta-time\">\n @if (vm.isStarted) {\n <svg width=\"10\" height=\"10\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <polygon points=\"6 4 20 12 6 20 6 4\" />\n </svg>\n }\n {{ vm.timeLabel }}\n </span>\n }\n </span>\n\n @if (vm.showBar) {\n <span class=\"pecb-course-panel__bar\" aria-hidden=\"true\">\n <span class=\"pecb-course-panel__bar-fill\" [style.width.%]=\"vm.barPercent\"></span>\n </span>\n }\n </span>\n\n @if (solo) {\n <span class=\"pecb-course-panel__solo-play\" aria-hidden=\"true\">\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <polygon points=\"6 4 20 12 6 20 6 4\" />\n </svg>\n </span>\n }\n </button>\n</ng-template>\n\n<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n Panel\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n<div [class]=\"hostClasses()\">\n @if (showTabs()) {\n <div class=\"pecb-course-panel__tabs\" role=\"tablist\" [attr.aria-label]=\"text().lessonList\">\n <button\n type=\"button\"\n role=\"tab\"\n class=\"pecb-course-panel__tab\"\n [class.pecb-course-panel__tab--active]=\"tab() === 'content'\"\n [id]=\"uid + '-tab-content'\"\n [attr.aria-selected]=\"tab() === 'content'\"\n [attr.aria-controls]=\"uid + '-panel-content'\"\n [attr.tabindex]=\"tab() === 'content' ? 0 : -1\"\n (click)=\"setTab('content')\"\n (keydown)=\"onTabKeydown($event)\"\n >\n {{ text().contentTab }}\n @if (tab() === 'content') {\n <span class=\"pecb-course-panel__tab-indicator\" aria-hidden=\"true\"></span>\n }\n </button>\n <button\n type=\"button\"\n role=\"tab\"\n class=\"pecb-course-panel__tab\"\n [class.pecb-course-panel__tab--active]=\"tab() === 'transcript'\"\n [id]=\"uid + '-tab-transcript'\"\n [attr.aria-selected]=\"tab() === 'transcript'\"\n [attr.aria-controls]=\"uid + '-panel-transcript'\"\n [attr.tabindex]=\"tab() === 'transcript' ? 0 : -1\"\n (click)=\"setTab('transcript')\"\n (keydown)=\"onTabKeydown($event)\"\n >\n {{ text().transcriptTab }}\n @if (tab() === 'transcript') {\n <span class=\"pecb-course-panel__tab-indicator\" aria-hidden=\"true\"></span>\n }\n </button>\n </div>\n }\n\n <!-- \u2500\u2500 Content tab \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (tab() === 'content') {\n <div\n class=\"pecb-course-panel__body\"\n role=\"tabpanel\"\n [id]=\"uid + '-panel-content'\"\n [attr.aria-labelledby]=\"showTabs() ? uid + '-tab-content' : null\"\n >\n @if (showProgress()) {\n <div class=\"pecb-course-panel__progress\">\n <span class=\"pecb-course-panel__progress-ring\">\n <svg width=\"56\" height=\"56\" viewBox=\"0 0 56 56\" aria-hidden=\"true\">\n <circle class=\"pecb-course-panel__progress-track\" cx=\"28\" cy=\"28\" [attr.r]=\"progressRing().radius\" fill=\"none\" stroke-width=\"5\" />\n <circle\n class=\"pecb-course-panel__progress-value\"\n cx=\"28\" cy=\"28\" [attr.r]=\"progressRing().radius\" fill=\"none\" stroke-width=\"5\" stroke-linecap=\"round\"\n [attr.stroke-dasharray]=\"progressRing().circumference\"\n [attr.stroke-dashoffset]=\"progressRing().offset\" />\n </svg>\n <span class=\"pecb-course-panel__progress-percent\">{{ progress().percent }}%</span>\n </span>\n <span class=\"pecb-course-panel__progress-text\">\n <span class=\"pecb-course-panel__progress-title\">{{ text().courseProgress }}</span>\n <span class=\"pecb-course-panel__progress-detail\">{{ progressDetail() }}</span>\n </span>\n @if (showCertificate()) {\n <button type=\"button\" class=\"pecb-course-panel__certificate\" (click)=\"certificateClick.emit()\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"8\" r=\"5\" />\n <path d=\"M8.5 12.5 7 21l5-2.5L17 21l-1.5-8.5\" />\n </svg>\n {{ text().certificate }}\n </button>\n }\n </div>\n }\n\n @if (showSearch()) {\n <div class=\"pecb-course-panel__search\">\n <div class=\"pecb-course-panel__search-box\">\n <span class=\"pecb-course-panel__search-icon\" aria-hidden=\"true\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"7\" />\n <path d=\"m20 20-3.2-3.2\" />\n </svg>\n </span>\n <input\n type=\"search\"\n class=\"pecb-course-panel__search-input\"\n [value]=\"query()\"\n [attr.placeholder]=\"text().searchLessons\"\n [attr.aria-label]=\"text().searchLessons\"\n (input)=\"onQueryInput($event)\"\n />\n </div>\n </div>\n }\n\n <div class=\"pecb-course-panel__scroll pecb-course-panel__list\">\n @if (isEmpty()) {\n <p class=\"pecb-course-panel__empty\">{{ noLessonsLabel() }}</p>\n } @else if (layout() === 'timeline') {\n <div class=\"pecb-course-panel__timeline\">\n @for (row of timelineRows(); track row.key) {\n @if (row.header; as header) {\n <div class=\"pecb-course-panel__timeline-head\">\n <span class=\"pecb-course-panel__timeline-label\">{{ sectionLabel(header.number) }}</span>\n <span class=\"pecb-course-panel__timeline-rule\" aria-hidden=\"true\"></span>\n <span class=\"pecb-course-panel__timeline-count\">{{ header.stats.done }}/{{ header.stats.total }}</span>\n </div>\n } @else {\n <ng-container *ngTemplateOutlet=\"lessonRowTpl; context: { $implicit: row.lesson }\"></ng-container>\n }\n }\n </div>\n } @else {\n @for (view of moduleViews(); track view.id) {\n @if (view.solo; as soloView) {\n <div class=\"pecb-course-panel__section pecb-course-panel__section--solo\">\n <ng-container\n *ngTemplateOutlet=\"lessonRowTpl; context: { $implicit: soloView, solo: true }\"\n ></ng-container>\n </div>\n } @else {\n <div\n class=\"pecb-course-panel__section\"\n [class.pecb-course-panel__section--open]=\"isModuleOpen(view.id)\"\n >\n <button\n type=\"button\"\n class=\"pecb-course-panel__section-head\"\n [class.pecb-course-panel__section-head--open]=\"isModuleOpen(view.id)\"\n [id]=\"uid + '-head-' + view.id\"\n [attr.aria-expanded]=\"isModuleOpen(view.id)\"\n [attr.aria-controls]=\"uid + '-region-' + view.id\"\n (click)=\"toggleModule(view)\"\n (keydown)=\"onModuleHeaderKeydown($event)\"\n >\n <span\n class=\"pecb-course-panel__section-badge\"\n [class.pecb-course-panel__section-badge--done]=\"view.allDone\"\n [class.pecb-course-panel__section-badge--active]=\"!view.allDone && (view.stats.playing || view.stats.started)\"\n aria-hidden=\"true\"\n >\n @if (view.allDone) {\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n } @else if (view.stats.playing) {\n <svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <rect x=\"6\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n <rect x=\"14\" y=\"5\" width=\"4\" height=\"14\" rx=\"1\" />\n </svg>\n } @else {\n <svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <polygon points=\"6 4 20 12 6 20 6 4\" />\n </svg>\n }\n </span>\n\n <span class=\"pecb-course-panel__section-text\">\n <span class=\"pecb-course-panel__section-title\">{{ sectionLabel(view.number) }}</span>\n <span class=\"pecb-course-panel__section-meta\">\n <span>{{ view.statsLabel }}</span>\n @if (view.stats.durationSeconds > 0) {\n <span class=\"pecb-course-panel__section-duration\">\n <svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"9\" />\n <polyline points=\"12 7 12 12 15.5 14\" />\n </svg>\n {{ view.durationLabel }}\n </span>\n }\n </span>\n </span>\n\n <span class=\"pecb-course-panel__section-chevron\" aria-hidden=\"true\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"6 9 12 15 18 9\" />\n </svg>\n </span>\n </button>\n\n <div class=\"pecb-course-panel__section-wrap\">\n <div\n class=\"pecb-course-panel__section-clip\"\n [id]=\"uid + '-region-' + view.id\"\n role=\"region\"\n [attr.aria-labelledby]=\"uid + '-head-' + view.id\"\n [attr.inert]=\"isModuleOpen(view.id) ? null : ''\"\n >\n <div class=\"pecb-course-panel__section-body\">\n @for (run of view.runs; track run.key) {\n @if (run.parts) {\n <p class=\"pecb-course-panel__run-title\">{{ run.base }}</p>\n <div class=\"pecb-course-panel__rail\">\n <span class=\"pecb-course-panel__rail-line\" aria-hidden=\"true\"></span>\n @for (lessonView of run.lessons; track lessonView.id) {\n <ng-container *ngTemplateOutlet=\"lessonRowTpl; context: { $implicit: lessonView }\"></ng-container>\n }\n </div>\n } @else {\n @for (lessonView of run.lessons; track lessonView.id) {\n <ng-container *ngTemplateOutlet=\"lessonRowTpl; context: { $implicit: lessonView }\"></ng-container>\n }\n }\n }\n </div>\n </div>\n </div>\n </div>\n }\n }\n }\n </div>\n </div>\n } @else {\n <!-- \u2500\u2500 Transcript tab \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div\n class=\"pecb-course-panel__body\"\n role=\"tabpanel\"\n [id]=\"uid + '-panel-transcript'\"\n [attr.aria-labelledby]=\"showTabs() ? uid + '-tab-transcript' : null\"\n >\n @if (showTranscriptSearch()) {\n <div class=\"pecb-course-panel__search\">\n <div class=\"pecb-course-panel__search-box\">\n <span class=\"pecb-course-panel__search-icon\" aria-hidden=\"true\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"7\" />\n <path d=\"m20 20-3.2-3.2\" />\n </svg>\n </span>\n <input\n type=\"search\"\n class=\"pecb-course-panel__search-input\"\n [value]=\"transcriptQuery()\"\n [attr.placeholder]=\"text().searchTranscript\"\n [attr.aria-label]=\"text().searchTranscript\"\n (input)=\"onTranscriptQueryInput($event)\"\n />\n @if (transcriptQuery()) {\n <button\n type=\"button\"\n class=\"pecb-course-panel__search-clear\"\n [attr.aria-label]=\"text().clearSearch\"\n (click)=\"clearTranscriptQuery()\"\n >\u00D7</button>\n }\n </div>\n </div>\n }\n\n <div class=\"pecb-course-panel__scroll pecb-course-panel__transcript\" #transcriptScroll>\n @if (transcriptEmpty()) {\n <p class=\"pecb-course-panel__empty\">{{ noTranscriptLabel() }}</p>\n } @else {\n <div class=\"pecb-course-panel__cues\">\n @for (row of transcriptRows(); track row.key) {\n <button\n type=\"button\"\n class=\"pecb-course-panel__cue\"\n [class.pecb-course-panel__cue--current]=\"row.current\"\n [attr.data-current]=\"row.current\"\n [attr.aria-current]=\"row.current ? 'true' : null\"\n (click)=\"seekToCue(row.cue)\"\n >\n <span class=\"pecb-course-panel__cue-time\">{{ row.timeLabel }}</span>\n <span class=\"pecb-course-panel__cue-text\">{{ row.before }}@if (row.match) {<mark class=\"pecb-course-panel__mark\">{{ row.match }}</mark>}{{ row.after }}</span>\n </button>\n }\n </div>\n }\n </div>\n\n @if (showAutoscroll()) {\n <div class=\"pecb-course-panel__footer\">\n <label class=\"pecb-course-panel__autoscroll\">\n <input\n type=\"checkbox\"\n class=\"pecb-course-panel__autoscroll-input\"\n [checked]=\"autoscroll()\"\n (change)=\"onAutoscrollChange($event)\"\n />\n <span class=\"pecb-course-panel__autoscroll-box\" aria-hidden=\"true\">\n @if (autoscroll()) {\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n }\n </span>\n <span class=\"pecb-course-panel__autoscroll-label\">{{ text().autoscroll }}</span>\n </label>\n </div>\n }\n </div>\n }\n</div>\n", styles: [":host{display:block;width:100%;height:100%;min-height:0;container-type:inline-size;container-name:pecb-course-panel}.pecb-course-panel{--_row-pad-y: 11px;--_row-pad-left: 14px;--_row-pad-right: 16px;--_row-gap: 13px;--_disc: 30px;--_section-pad-y: 15px;--_title-size: 13.5px;display:flex;flex-direction:column;height:100%;min-height:0;box-sizing:border-box;background:var(--pecb-course-panel-bg, #ffffff);font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);line-height:normal;color:#212427;overflow:hidden}.pecb-course-panel *,.pecb-course-panel *:before,.pecb-course-panel *:after{box-sizing:border-box}.pecb-course-panel--compact{--_row-pad-y: 8px;--_row-pad-left: 12px;--_row-pad-right: 14px;--_row-gap: 11px;--_disc: 26px;--_section-pad-y: 12px;--_title-size: 12.5px}.pecb-course-panel--accent-red{--pecb-course-panel-accent: #a11e29;--pecb-course-panel-accent-soft: rgba(161, 30, 41, .27)}.pecb-course-panel__tabs{display:flex;flex:0 0 auto;padding:0 20px;border-bottom:1px solid var(--pecb-course-panel-border, #ececec)}.pecb-course-panel__tab{border:none;background:none;font:inherit;color:inherit;position:relative;flex:1;padding:16px 0 14px;font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:14px;font-weight:500;color:#616161;cursor:pointer;transition:color .15s ease-in-out}.pecb-course-panel__tab:focus{outline:none}.pecb-course-panel__tab:hover{color:#212427}.pecb-course-panel__tab:focus-visible{outline:2px solid #a11e29;outline-offset:-3px;border-radius:6px}.pecb-course-panel__tab--active{font-weight:700;color:#a11e29}.pecb-course-panel__tab-indicator{position:absolute;left:0;right:0;bottom:-1px;height:2.5px;border-radius:3px;background:#a11e29}.pecb-course-panel__body{display:flex;flex:1 1 auto;flex-direction:column;min-height:0}.pecb-course-panel__scroll{flex:1 1 auto;min-height:0;overflow-y:auto;overflow-x:hidden;overscroll-behavior:contain;scrollbar-width:thin;scrollbar-color:#ececec transparent}.pecb-course-panel__scroll::-webkit-scrollbar{width:8px}.pecb-course-panel__scroll::-webkit-scrollbar-thumb{background:#ececec;border-radius:8px}.pecb-course-panel__scroll::-webkit-scrollbar-track{background:transparent}.pecb-course-panel__empty{margin:0;padding:40px;text-align:center;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:13px;color:#919191}.pecb-course-panel__progress{display:flex;flex:0 0 auto;align-items:center;gap:16px;padding:16px 20px;border-bottom:1px solid var(--pecb-course-panel-border, #ececec)}.pecb-course-panel__progress-ring{position:relative;flex:0 0 auto;width:56px;height:56px}.pecb-course-panel__progress-ring svg{display:block;transform:rotate(-90deg)}.pecb-course-panel__progress-track{stroke:#ececec}.pecb-course-panel__progress-value{stroke:var(--pecb-course-panel-accent, #212427);transition:stroke-dashoffset .5s ease}.pecb-course-panel__progress-percent{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:14px;font-weight:700;color:#212427}.pecb-course-panel__progress-text{display:flex;flex:1 1 auto;flex-direction:column;min-width:0}.pecb-course-panel__progress-title{font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:14px;font-weight:700;color:#212427}.pecb-course-panel__progress-detail{margin-top:3px;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:12px;color:#616161}.pecb-course-panel__certificate{border:none;background:none;font:inherit;color:inherit;display:inline-flex;flex:0 0 auto;align-items:center;gap:6px;padding:6px 12px;border-radius:9999px;background:#212427;color:#fff;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:12px;font-weight:600;cursor:pointer;transition:background .15s ease-in-out}.pecb-course-panel__certificate:focus{outline:none}.pecb-course-panel__certificate:hover{background:#313131}.pecb-course-panel__certificate:focus-visible{outline:2px solid #a11e29;outline-offset:2px}.pecb-course-panel__search{flex:0 0 auto;padding:12px 20px 10px}.pecb-course-panel__search-box{display:flex;align-items:center;gap:9px;min-height:40px;padding:9px 12px;border:1px solid #c2c2c2;border-radius:6px;background:#fff;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}.pecb-course-panel__search-box:focus-within{border-color:#212427;box-shadow:0 0 0 3px #21242714}.pecb-course-panel__search-icon{display:flex;flex:0 0 auto;color:#919191}.pecb-course-panel__search-input{flex:1 1 auto;min-width:0;border:none;outline:none;background:transparent;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:13px;color:#212427;appearance:none}.pecb-course-panel__search-input::placeholder{color:#919191}.pecb-course-panel__search-input::-webkit-search-cancel-button,.pecb-course-panel__search-input::-webkit-search-decoration{appearance:none;display:none}.pecb-course-panel__search-clear{padding:0;border:none;background:none;font:inherit;color:inherit;display:flex;flex:0 0 auto;align-items:center;justify-content:center;width:18px;height:18px;border-radius:50%;color:#919191;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:17px;line-height:1;cursor:pointer}.pecb-course-panel__search-clear:focus{outline:none}.pecb-course-panel__search-clear:hover{background:#f4f4f4;color:#212427}.pecb-course-panel__search-clear:focus-visible{outline:2px solid #a11e29;outline-offset:1px}.pecb-course-panel__section{border-bottom:1px solid var(--pecb-course-panel-border, #ececec);background:transparent}.pecb-course-panel__section--open{background:#fff}.pecb-course-panel__section--solo{border-bottom:1px solid var(--pecb-course-panel-border, #ececec)}.pecb-course-panel__section-head{padding:0;border:none;background:none;font:inherit;color:inherit;display:flex;align-items:center;gap:12px;width:100%;padding:var(--_section-pad-y) 16px;text-align:left;background:#f4f4f4;cursor:pointer;transition:background .15s ease-in-out}.pecb-course-panel__section-head:focus{outline:none}.pecb-course-panel__section-head:hover{background:#fff}.pecb-course-panel__section-head:focus-visible{outline:2px solid #a11e29;outline-offset:-2px}.pecb-course-panel__section-head--open{background:#ececec}.pecb-course-panel__section-head--open:hover{background:#f4f4f4}.pecb-course-panel__section-badge{display:flex;flex:0 0 auto;align-items:center;justify-content:center;width:30px;height:30px;border-radius:8px;background:#c2c2c2;color:#fff;font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:13px;font-weight:700}.pecb-course-panel__section-badge--active{background:var(--pecb-course-panel-accent, #212427)}.pecb-course-panel__section-badge--done{background:#e6f4ea;color:green}.pecb-course-panel__section-text{display:flex;flex:1 1 auto;flex-direction:column;min-width:0}.pecb-course-panel__section-title{font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:13.5px;font-weight:600;line-height:1.3;color:#212427}.pecb-course-panel__section-meta{display:flex;align-items:center;gap:10px;margin-top:4px;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:11.5px;color:#919191}.pecb-course-panel__section-duration{display:inline-flex;align-items:center;gap:4px}.pecb-course-panel__section-chevron{display:flex;flex:0 0 auto;color:#919191;transform:rotate(-90deg);transition:transform .18s ease}.pecb-course-panel__section-head--open .pecb-course-panel__section-chevron{transform:rotate(0)}.pecb-course-panel__section-wrap{display:grid;grid-template-rows:0fr;transition:grid-template-rows .22s ease}.pecb-course-panel__section--open .pecb-course-panel__section-wrap{grid-template-rows:1fr}.pecb-course-panel__section-clip{overflow:hidden;min-height:0}.pecb-course-panel__section-body{display:flex;flex-direction:column;gap:2px;padding:6px 12px 10px var(--_row-pad-left)}.pecb-course-panel--compact .pecb-course-panel__section-body{padding:4px 10px 8px var(--_row-pad-left)}.pecb-course-panel__run-title{margin:0;padding:9px 8px 5px 12px;font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:13px;font-weight:600;line-height:1.35;color:#212427;text-wrap:pretty}.pecb-course-panel__rail{position:relative}.pecb-course-panel__rail-line{position:absolute;left:calc(var(--_row-pad-left) + var(--_disc) / 2 - 1px);top:calc(var(--_row-pad-y) + var(--_disc) / 2);bottom:calc(var(--_row-pad-y) + var(--_disc) / 2);z-index:1;width:2px;border-radius:2px;background:#ececec}.pecb-course-panel__row{padding:0;border:none;background:none;font:inherit;color:inherit;position:relative;display:flex;align-items:center;gap:var(--_row-gap);width:100%;padding:var(--_row-pad-y) var(--_row-pad-right) var(--_row-pad-y) var(--_row-pad-left);border-radius:8px;text-align:left;background:transparent;cursor:pointer;transition:background .14s ease,box-shadow .14s ease}.pecb-course-panel__row:focus{outline:none}.pecb-course-panel__row:hover:not(:disabled):not(.pecb-course-panel__row--active){background:#fafafa}.pecb-course-panel__row:focus-visible{outline:2px solid #a11e29;outline-offset:-2px}.pecb-course-panel__row:disabled{cursor:not-allowed}.pecb-course-panel__row--locked{opacity:.72}.pecb-course-panel__row--active{background:#fff;box-shadow:inset 0 0 0 1px var(--pecb-course-panel-border, #ececec)}.pecb-course-panel__row--active.pecb-course-panel__row--completed{background:#e6f4ea}.pecb-course-panel__row--active.pecb-course-panel__row--started{background:#fff4e5}.pecb-course-panel__row--active.pecb-course-panel__row--playing{background:#fbeaec;box-shadow:inset 0 0 0 1px var(--pecb-course-panel-accent-soft, rgba(33, 36, 39, .27))}.pecb-course-panel__row--active.pecb-course-panel__row--quiz{background:#eee8ff}.pecb-course-panel__row--solo{gap:12px;padding:var(--_section-pad-y) 16px;border-radius:0}.pecb-course-panel__row-marker{position:absolute;left:0;top:8px;bottom:8px;width:3px;border-radius:3px;background:var(--pecb-course-panel-accent, #212427)}.pecb-course-panel__row--solo .pecb-course-panel__row-marker{top:10px;bottom:10px}.pecb-course-panel__row--solo .pecb-course-panel__row-title{font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:13.5px;font-weight:600;line-height:1.3}.pecb-course-panel__solo-play{display:flex;flex:0 0 auto;align-items:center;justify-content:center;width:30px;height:30px;border-radius:50%;background:#f4f4f4;color:#616161}.pecb-course-panel__disc-col{position:relative;display:flex;flex:0 0 auto;flex-direction:column;align-items:center;align-self:stretch}.pecb-course-panel__disc{position:relative;z-index:2;display:flex;flex:0 0 auto;align-items:center;justify-content:center;border-radius:50%;background:#fff;border:none;color:#919191}.pecb-course-panel__disc svg{display:block}.pecb-course-panel__disc--plain{border:1.5px solid #ececec}.pecb-course-panel__disc--completed{background:#57bc57;color:#fff}.pecb-course-panel__disc--playing{background:var(--pecb-course-panel-accent, #212427);color:#fff;box-shadow:0 0 0 4px var(--pecb-course-panel-accent-soft, rgba(33, 36, 39, .27))}.pecb-course-panel__disc--started{background:#fff;color:#ff9c4a}.pecb-course-panel__disc--locked{color:#c2c2c2}.pecb-course-panel__ring{position:absolute;inset:0;transform:rotate(-90deg)}.pecb-course-panel__ring-track{stroke:#ececec}.pecb-course-panel__ring-value{stroke:#ff9c4a;transition:stroke-dashoffset .3s ease}.pecb-course-panel__disc-glyph{position:relative;display:flex;color:#ff9c4a}.pecb-course-panel__connector{flex:1 1 auto;width:2px;margin-top:2px;border-radius:2px;background:#ececec}.pecb-course-panel__connector--done{background:#57bc57}.pecb-course-panel__row-body{display:block;flex:1 1 auto;min-width:0}.pecb-course-panel__quiz-badge-line{display:flex;align-items:center;min-height:18px;margin-bottom:5px}.pecb-course-panel__quiz-badge{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:9999px;background:#8e6df0;color:#fff;font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:9.5px;font-weight:700;letter-spacing:.05em;text-transform:uppercase}.pecb-course-panel__row-title{display:block;margin-bottom:4px;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:var(--_title-size);font-weight:500;line-height:1.35;color:#212427;text-wrap:pretty;overflow-wrap:anywhere}.pecb-course-panel--compact .pecb-course-panel__row-title{margin-bottom:2px}.pecb-course-panel__row--active .pecb-course-panel__row-title{font-weight:600}.pecb-course-panel__row-meta{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.pecb-course-panel__meta-primary{display:inline-flex;align-items:center;gap:5px;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:11.5px;font-weight:500;color:#919191}.pecb-course-panel__row--quiz .pecb-course-panel__meta-primary{color:#8e6df0}.pecb-course-panel__meta-result{display:inline-flex;align-items:center;gap:4px;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:11px;font-weight:600;color:#ff9c4a}.pecb-course-panel__meta-result--passed{color:#57bc57}.pecb-course-panel__meta-time{display:inline-flex;align-items:center;gap:5px;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:11px;font-weight:600;color:#ff9c4a}.pecb-course-panel__row--playing .pecb-course-panel__meta-time{color:var(--pecb-course-panel-accent, #212427)}.pecb-course-panel__bar{display:block;margin-top:7px;height:4px;border-radius:4px;background:#ececec;overflow:hidden}.pecb-course-panel__bar-fill{display:block;height:100%;border-radius:4px;background:#ff9c4a;transition:width .2s linear}.pecb-course-panel__row--playing .pecb-course-panel__bar-fill{background:var(--pecb-course-panel-accent, #212427)}.pecb-course-panel__timeline{padding:6px 14px 16px}.pecb-course-panel--compact .pecb-course-panel__timeline{padding:4px 12px 12px}.pecb-course-panel__timeline-head{display:flex;align-items:center;gap:10px;padding:18px 2px 8px}.pecb-course-panel--compact .pecb-course-panel__timeline-head{padding:14px 2px 6px}.pecb-course-panel__timeline-label{font-family:var(--pecb-course-panel-font-display, Plus Jakarta Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:10.5px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;color:#919191}.pecb-course-panel__timeline-rule{flex:1 1 auto;height:1px;background:var(--pecb-course-panel-border, #ececec)}.pecb-course-panel__timeline-count{font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:11px;color:#919191}.pecb-course-panel__transcript{padding:0 14px}.pecb-course-panel__cues{padding:8px 6px 20px}.pecb-course-panel__cue{border:none;background:none;font:inherit;color:inherit;display:flex;gap:14px;width:100%;padding:10px 12px;border-radius:8px;text-align:left;background:transparent;cursor:pointer;transition:background .14s ease}.pecb-course-panel__cue:focus{outline:none}.pecb-course-panel__cue:hover:not(.pecb-course-panel__cue--current){background:#fafafa}.pecb-course-panel__cue:focus-visible{outline:2px solid #a11e29;outline-offset:-2px}.pecb-course-panel__cue--current{background:#fbeaec}.pecb-course-panel__cue-time{flex:0 0 auto;width:38px;padding-top:1px;font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:12px;font-weight:600;font-variant-numeric:tabular-nums;color:#919191}.pecb-course-panel__cue--current .pecb-course-panel__cue-time{color:#a11e29}.pecb-course-panel__cue-text{font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:13.5px;line-height:1.55;color:#313131;text-wrap:pretty;overflow-wrap:anywhere}.pecb-course-panel__cue--current .pecb-course-panel__cue-text{color:#212427}.pecb-course-panel__mark{padding:0 1px;border-radius:3px;background:#fce3b4;color:inherit}.pecb-course-panel__footer{flex:0 0 auto;padding:12px 20px;border-top:1px solid var(--pecb-course-panel-border, #ececec);background:var(--pecb-course-panel-bg, #ffffff)}.pecb-course-panel__autoscroll{display:flex;align-items:center;gap:10px;cursor:pointer;-webkit-user-select:none;user-select:none}.pecb-course-panel__autoscroll-input{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none}.pecb-course-panel__autoscroll-box{display:flex;flex:0 0 auto;align-items:center;justify-content:center;width:18px;height:18px;border:1.5px solid #c2c2c2;border-radius:5px;background:#fff;color:#fff;transition:all .14s ease}.pecb-course-panel__autoscroll-input:checked+.pecb-course-panel__autoscroll-box{border:1px solid #a11e29;background:#a11e29}.pecb-course-panel__autoscroll-input:focus-visible+.pecb-course-panel__autoscroll-box{outline:2px solid #a11e29;outline-offset:2px}.pecb-course-panel__autoscroll-label{font-family:var(--pecb-course-panel-font-body, Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif);font-size:13px;font-weight:500;color:#616161}@container pecb-course-panel (max-width: 380px){.pecb-course-panel{--_row-pad-right: 12px}.pecb-course-panel__tabs{padding:0 12px}.pecb-course-panel__tab{padding:12px 0 10px;font-size:13px}.pecb-course-panel__search{padding:10px 12px 8px}.pecb-course-panel__progress{flex-wrap:wrap;gap:12px;padding:12px 16px}.pecb-course-panel__section-head{gap:10px;padding:12px}.pecb-course-panel__section-body{padding-right:8px}.pecb-course-panel__transcript{padding:0 8px}.pecb-course-panel__cue{gap:10px;padding:8px 10px}.pecb-course-panel__cue-text{font-size:13px}.pecb-course-panel__footer{padding:10px 16px}.pecb-course-panel__timeline{padding:4px 10px 12px}}@media (hover: none) and (pointer: coarse){.pecb-course-panel__row,.pecb-course-panel__cue{min-height:44px}}@media (prefers-reduced-motion: reduce){.pecb-course-panel *,.pecb-course-panel *:before,.pecb-course-panel *:after{transition-duration:.01ms!important;animation-duration:.01ms!important}}\n"] }]
|
|
12273
|
+
}], ctorParameters: () => [], propDecorators: { transcriptScrollRef: [{ type: i0.ViewChild, args: ['transcriptScroll', { isSignal: true }] }], modules: [{ type: i0.Input, args: [{ isSignal: true, alias: "modules", required: false }] }], transcript: [{ type: i0.Input, args: [{ isSignal: true, alias: "transcript", required: false }] }], activeLessonId: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeLessonId", required: false }] }, { type: i0.Output, args: ["activeLessonIdChange"] }], tab: [{ type: i0.Input, args: [{ isSignal: true, alias: "tab", required: false }] }, { type: i0.Output, args: ["tabChange"] }], openModuleId: [{ type: i0.Input, args: [{ isSignal: true, alias: "openModuleId", required: false }] }, { type: i0.Output, args: ["openModuleIdChange"] }], autoscroll: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoscroll", required: false }] }, { type: i0.Output, args: ["autoscrollChange"] }], query: [{ type: i0.Input, args: [{ isSignal: true, alias: "query", required: false }] }, { type: i0.Output, args: ["queryChange"] }], transcriptQuery: [{ type: i0.Input, args: [{ isSignal: true, alias: "transcriptQuery", required: false }] }, { type: i0.Output, args: ["transcriptQueryChange"] }], playing: [{ type: i0.Input, args: [{ isSignal: true, alias: "playing", required: false }] }], currentTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentTime", required: false }] }], layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }], density: [{ type: i0.Input, args: [{ isSignal: true, alias: "density", required: false }] }], accent: [{ type: i0.Input, args: [{ isSignal: true, alias: "accent", required: false }] }], showTabs: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTabs", required: false }] }], showSearch: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSearch", required: false }] }], showTranscriptSearch: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTranscriptSearch", required: false }] }], showAutoscroll: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAutoscroll", required: false }] }], showProgress: [{ type: i0.Input, args: [{ isSignal: true, alias: "showProgress", required: false }] }], showCertificate: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCertificate", required: false }] }], soloSingleLessonModules: [{ type: i0.Input, args: [{ isSignal: true, alias: "soloSingleLessonModules", required: false }] }], labels: [{ type: i0.Input, args: [{ isSignal: true, alias: "labels", required: false }] }], lessonSelect: [{ type: i0.Output, args: ["lessonSelect"] }], transcriptSeek: [{ type: i0.Output, args: ["transcriptSeek"] }], moduleToggle: [{ type: i0.Output, args: ["moduleToggle"] }], certificateClick: [{ type: i0.Output, args: ["certificateClick"] }] } });
|
|
12274
|
+
/** Replaces `{token}` placeholders in a label. */
|
|
12275
|
+
function format(template, values) {
|
|
12276
|
+
return template.replace(/\{(\w+)\}/g, (match, key) => key in values ? String(values[key]) : match);
|
|
12277
|
+
}
|
|
12278
|
+
|
|
12279
|
+
/**
|
|
12280
|
+
* PECB course player — the video stage with the **Content** sidebar attached,
|
|
12281
|
+
* exactly as laid out in "Course Player — Content Redesign".
|
|
12282
|
+
*
|
|
12283
|
+
* Composes {@link VideoPlayerComponent} and {@link CourseContentPanelComponent}
|
|
12284
|
+
* and wires them together: picking a lesson loads its media and resumes at its
|
|
12285
|
+
* saved position, clicking a transcript cue seeks the video, and the panel
|
|
12286
|
+
* follows playback live (now-playing row, progress bars, transcript highlight).
|
|
12287
|
+
*
|
|
12288
|
+
* The two-column grid collapses to a single stacked column when the space it is
|
|
12289
|
+
* given gets narrow, so the same markup works in a page, a drawer or a phone.
|
|
12290
|
+
*
|
|
12291
|
+
* @example
|
|
12292
|
+
* ```html
|
|
12293
|
+
* <pecb-course-player
|
|
12294
|
+
* [modules]="modules"
|
|
12295
|
+
* [(activeLessonId)]="lessonId"
|
|
12296
|
+
* (lessonSelect)="track($event)"
|
|
12297
|
+
* (lessonEnded)="markComplete($event)">
|
|
12298
|
+
* </pecb-course-player>
|
|
12299
|
+
* ```
|
|
12300
|
+
*
|
|
12301
|
+
* Layout hooks:
|
|
12302
|
+
* ```css
|
|
12303
|
+
* pecb-course-player {
|
|
12304
|
+
* --pecb-course-player-panel-width: 408px;
|
|
12305
|
+
* --pecb-course-player-gap: 20px;
|
|
12306
|
+
* --pecb-course-player-panel-stacked-height: 460px;
|
|
12307
|
+
* }
|
|
12308
|
+
* ```
|
|
12309
|
+
*/
|
|
12310
|
+
class CoursePlayerComponent {
|
|
12311
|
+
constructor() {
|
|
12312
|
+
// ── Data ────────────────────────────────────────────────────────────────────
|
|
12313
|
+
/** Course structure. */
|
|
12314
|
+
this.modules = input([], ...(ngDevMode ? [{ debugName: "modules" }] : []));
|
|
12315
|
+
/** Fallback transcript, used when the active lesson has no `transcript`. */
|
|
12316
|
+
this.transcript = input([], ...(ngDevMode ? [{ debugName: "transcript" }] : []));
|
|
12317
|
+
// ── State (two-way) ─────────────────────────────────────────────────────────
|
|
12318
|
+
/** Selected lesson. */
|
|
12319
|
+
this.activeLessonId = model(null, ...(ngDevMode ? [{ debugName: "activeLessonId" }] : []));
|
|
12320
|
+
/** Playback state of the video. */
|
|
12321
|
+
this.playing = model(false, ...(ngDevMode ? [{ debugName: "playing" }] : []));
|
|
12322
|
+
/** Playhead position, in seconds. */
|
|
12323
|
+
this.currentTime = model(0, ...(ngDevMode ? [{ debugName: "currentTime" }] : []));
|
|
12324
|
+
/** Which panel tab is showing. */
|
|
12325
|
+
this.panelTab = model('content', ...(ngDevMode ? [{ debugName: "panelTab" }] : []));
|
|
12326
|
+
// ── Video options ───────────────────────────────────────────────────────────
|
|
12327
|
+
/** Poster used when the active lesson has none. */
|
|
12328
|
+
this.poster = input(undefined, ...(ngDevMode ? [{ debugName: "poster" }] : []));
|
|
12329
|
+
/** Media used when the active lesson has no `src` (e.g. a single-video course). */
|
|
12330
|
+
this.src = input(undefined, ...(ngDevMode ? [{ debugName: "src" }] : []));
|
|
12331
|
+
/** Caption tracks used when the active lesson has none. */
|
|
12332
|
+
this.tracks = input([], ...(ngDevMode ? [{ debugName: "tracks" }] : []));
|
|
12333
|
+
/** Stage label used when the active lesson has no `stageLabel`. */
|
|
12334
|
+
this.stageLabel = input(undefined, ...(ngDevMode ? [{ debugName: "stageLabel" }] : []));
|
|
12335
|
+
/** Brand watermark on the video stage. @default 'PECB' */
|
|
12336
|
+
this.watermark = input('PECB', ...(ngDevMode ? [{ debugName: "watermark" }] : []));
|
|
12337
|
+
/** @default '16 / 9' */
|
|
12338
|
+
this.aspectRatio = input('16 / 9', ...(ngDevMode ? [{ debugName: "aspectRatio" }] : []));
|
|
12339
|
+
/** @default 'metadata' */
|
|
12340
|
+
this.preload = input('metadata', ...(ngDevMode ? [{ debugName: "preload" }] : []));
|
|
12341
|
+
/** `crossorigin` for the media element. */
|
|
12342
|
+
this.crossOrigin = input(undefined, ...(ngDevMode ? [{ debugName: "crossOrigin" }] : []));
|
|
12343
|
+
/** Seconds the rewind button / arrow keys jump. @default 10 */
|
|
12344
|
+
this.seekStep = input(10, ...(ngDevMode ? [{ debugName: "seekStep", transform: numberAttribute }] : [{ transform: numberAttribute }]));
|
|
12345
|
+
/** i18n overrides for the video player. */
|
|
12346
|
+
this.videoLabels = input({}, ...(ngDevMode ? [{ debugName: "videoLabels" }] : []));
|
|
12347
|
+
/** Start playing as soon as a video lesson is picked. @default true */
|
|
12348
|
+
this.autoPlayOnSelect = input(true, ...(ngDevMode ? [{ debugName: "autoPlayOnSelect", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
12349
|
+
/** Show the active lesson's title under the video. @default true */
|
|
12350
|
+
this.showLessonTitle = input(true, ...(ngDevMode ? [{ debugName: "showLessonTitle", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
12351
|
+
// ── Panel options ───────────────────────────────────────────────────────────
|
|
12352
|
+
/** Which side the panel sits on. @default 'end' */
|
|
12353
|
+
this.panelPosition = input('end', ...(ngDevMode ? [{ debugName: "panelPosition" }] : []));
|
|
12354
|
+
/** @default 'grouped' */
|
|
12355
|
+
this.layout = input('grouped', ...(ngDevMode ? [{ debugName: "layout" }] : []));
|
|
12356
|
+
/** @default 'comfortable' */
|
|
12357
|
+
this.density = input('comfortable', ...(ngDevMode ? [{ debugName: "density" }] : []));
|
|
12358
|
+
/** @default 'charcoal' */
|
|
12359
|
+
this.accent = input('charcoal', ...(ngDevMode ? [{ debugName: "accent" }] : []));
|
|
12360
|
+
/** Show the panel's tab strip. @default true */
|
|
12361
|
+
this.showTabs = input(true, ...(ngDevMode ? [{ debugName: "showTabs", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
12362
|
+
/** Show the lesson search box. @default true */
|
|
12363
|
+
this.showSearch = input(true, ...(ngDevMode ? [{ debugName: "showSearch", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
12364
|
+
/** Show the circular course-progress header. @default false */
|
|
12365
|
+
this.showProgress = input(false, ...(ngDevMode ? [{ debugName: "showProgress", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
12366
|
+
/** Render single-lesson sections as direct-play rows. @default false */
|
|
12367
|
+
this.soloSingleLessonModules = input(false, ...(ngDevMode ? [{ debugName: "soloSingleLessonModules", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
12368
|
+
/** i18n overrides for the panel. */
|
|
12369
|
+
this.panelLabels = input({}, ...(ngDevMode ? [{ debugName: "panelLabels" }] : []));
|
|
12370
|
+
// ── Outputs ─────────────────────────────────────────────────────────────────
|
|
12371
|
+
/** A lesson row was picked (fires for quizzes too). */
|
|
12372
|
+
this.lessonSelect = output();
|
|
12373
|
+
/** The video of a lesson played to the end. */
|
|
12374
|
+
this.lessonEnded = output();
|
|
12375
|
+
/** Playhead moved — use it to persist progress. */
|
|
12376
|
+
this.timeUpdate = output();
|
|
12377
|
+
/** The panel's "Certificate" pill was clicked. */
|
|
12378
|
+
this.certificateClick = output();
|
|
12379
|
+
/** The media failed to load, or playback was rejected. */
|
|
12380
|
+
this.playerError = output();
|
|
12381
|
+
// ── Derived ─────────────────────────────────────────────────────────────────
|
|
12382
|
+
/** The lesson currently selected in the panel (may be a quiz). */
|
|
12383
|
+
this.activeLesson = computed(() => findCourseLesson(this.modules(), this.activeLessonId()), ...(ngDevMode ? [{ debugName: "activeLesson" }] : []));
|
|
12384
|
+
/**
|
|
12385
|
+
* Id of the last *video* lesson that was selected. Picking a quiz keeps the
|
|
12386
|
+
* video on the stage (design behaviour) instead of blanking it.
|
|
12387
|
+
*/
|
|
12388
|
+
this.videoLessonId = signal(null, ...(ngDevMode ? [{ debugName: "videoLessonId" }] : []));
|
|
12389
|
+
this.videoLesson = computed(() => {
|
|
12390
|
+
const explicit = findCourseLesson(this.modules(), this.videoLessonId());
|
|
12391
|
+
if (explicit)
|
|
12392
|
+
return explicit;
|
|
12393
|
+
// Fall back to the first video lesson so the stage is never empty.
|
|
12394
|
+
for (const module of this.modules()) {
|
|
12395
|
+
const lesson = module.lessons.find((l) => (l.type ?? 'video') === 'video');
|
|
12396
|
+
if (lesson)
|
|
12397
|
+
return lesson;
|
|
12398
|
+
}
|
|
12399
|
+
return undefined;
|
|
12400
|
+
}, ...(ngDevMode ? [{ debugName: "videoLesson" }] : []));
|
|
12401
|
+
this.videoSrc = computed(() => this.videoLesson()?.src ?? this.src(), ...(ngDevMode ? [{ debugName: "videoSrc" }] : []));
|
|
12402
|
+
this.videoPoster = computed(() => this.videoLesson()?.poster ?? this.poster(), ...(ngDevMode ? [{ debugName: "videoPoster" }] : []));
|
|
12403
|
+
this.videoTracks = computed(() => this.videoLesson()?.tracks ?? this.tracks(), ...(ngDevMode ? [{ debugName: "videoTracks" }] : []));
|
|
12404
|
+
this.videoStageLabel = computed(() => this.videoLesson()?.stageLabel ?? this.stageLabel(), ...(ngDevMode ? [{ debugName: "videoStageLabel" }] : []));
|
|
12405
|
+
this.videoTitle = computed(() => this.videoLesson()?.title, ...(ngDevMode ? [{ debugName: "videoTitle" }] : []));
|
|
12406
|
+
this.lessonTitle = computed(() => this.activeLesson()?.title ?? '', ...(ngDevMode ? [{ debugName: "lessonTitle" }] : []));
|
|
12407
|
+
/** Resume position handed to the player whenever the source changes. */
|
|
12408
|
+
this.startTime = computed(() => this.videoLesson()?.resumeSeconds ?? 0, ...(ngDevMode ? [{ debugName: "startTime" }] : []));
|
|
12409
|
+
this.hostClasses = computed(() => [
|
|
12410
|
+
'pecb-course-player',
|
|
12411
|
+
`pecb-course-player--panel-${this.panelPosition()}`,
|
|
12412
|
+
].join(' '), ...(ngDevMode ? [{ debugName: "hostClasses" }] : []));
|
|
12413
|
+
/** Id of the lesson whose resume position has already been pushed into `currentTime`. */
|
|
12414
|
+
this.resumeAppliedFor = null;
|
|
12415
|
+
// Track the newest video lesson, including the initial selection.
|
|
12416
|
+
effect(() => {
|
|
12417
|
+
const lesson = this.activeLesson();
|
|
12418
|
+
untracked(() => {
|
|
12419
|
+
if (lesson && (lesson.type ?? 'video') === 'video') {
|
|
12420
|
+
this.videoLessonId.set(lesson.id);
|
|
12421
|
+
}
|
|
12422
|
+
});
|
|
12423
|
+
});
|
|
12424
|
+
// Seed the playhead from the lesson's stored position whenever the loaded
|
|
12425
|
+
// video changes — including the very first one, which arrives as an input
|
|
12426
|
+
// rather than through a click. Without this the panel would show 0:00 for a
|
|
12427
|
+
// half-watched lesson until the media reported its own time.
|
|
12428
|
+
effect(() => {
|
|
12429
|
+
const lesson = this.videoLesson();
|
|
12430
|
+
untracked(() => {
|
|
12431
|
+
if (!lesson || lesson.id === this.resumeAppliedFor)
|
|
12432
|
+
return;
|
|
12433
|
+
this.resumeAppliedFor = lesson.id;
|
|
12434
|
+
this.currentTime.set(lesson.resumeSeconds ?? 0);
|
|
12435
|
+
});
|
|
12436
|
+
});
|
|
12437
|
+
}
|
|
12438
|
+
// ── Wiring ──────────────────────────────────────────────────────────────────
|
|
12439
|
+
/** Panel → player: load a lesson, resuming where it left off. */
|
|
12440
|
+
onLessonSelect(lesson) {
|
|
12441
|
+
const isVideo = (lesson.type ?? 'video') === 'video';
|
|
12442
|
+
if (isVideo) {
|
|
12443
|
+
this.videoLessonId.set(lesson.id);
|
|
12444
|
+
this.currentTime.set(lesson.resumeSeconds ?? 0);
|
|
12445
|
+
this.playing.set(this.autoPlayOnSelect());
|
|
12446
|
+
}
|
|
12447
|
+
else {
|
|
12448
|
+
// A quiz has nothing to play — stop the video and leave it on the stage.
|
|
12449
|
+
this.playing.set(false);
|
|
12450
|
+
}
|
|
12451
|
+
this.lessonSelect.emit(lesson);
|
|
12452
|
+
}
|
|
12453
|
+
/** Panel → player: jump to a transcript cue and keep playing. */
|
|
12454
|
+
onTranscriptSeek(seconds) {
|
|
12455
|
+
this.currentTime.set(seconds);
|
|
12456
|
+
this.playing.set(true);
|
|
12457
|
+
}
|
|
12458
|
+
onPlaybackEnded() {
|
|
12459
|
+
const lesson = this.videoLesson();
|
|
12460
|
+
if (lesson)
|
|
12461
|
+
this.lessonEnded.emit(lesson);
|
|
12462
|
+
}
|
|
12463
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: CoursePlayerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
12464
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: CoursePlayerComponent, isStandalone: true, selector: "pecb-course-player", inputs: { modules: { classPropertyName: "modules", publicName: "modules", isSignal: true, isRequired: false, transformFunction: null }, transcript: { classPropertyName: "transcript", publicName: "transcript", isSignal: true, isRequired: false, transformFunction: null }, activeLessonId: { classPropertyName: "activeLessonId", publicName: "activeLessonId", isSignal: true, isRequired: false, transformFunction: null }, playing: { classPropertyName: "playing", publicName: "playing", isSignal: true, isRequired: false, transformFunction: null }, currentTime: { classPropertyName: "currentTime", publicName: "currentTime", isSignal: true, isRequired: false, transformFunction: null }, panelTab: { classPropertyName: "panelTab", publicName: "panelTab", isSignal: true, isRequired: false, transformFunction: null }, poster: { classPropertyName: "poster", publicName: "poster", isSignal: true, isRequired: false, transformFunction: null }, src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: false, transformFunction: null }, tracks: { classPropertyName: "tracks", publicName: "tracks", isSignal: true, isRequired: false, transformFunction: null }, stageLabel: { classPropertyName: "stageLabel", publicName: "stageLabel", isSignal: true, isRequired: false, transformFunction: null }, watermark: { classPropertyName: "watermark", publicName: "watermark", isSignal: true, isRequired: false, transformFunction: null }, aspectRatio: { classPropertyName: "aspectRatio", publicName: "aspectRatio", isSignal: true, isRequired: false, transformFunction: null }, preload: { classPropertyName: "preload", publicName: "preload", isSignal: true, isRequired: false, transformFunction: null }, crossOrigin: { classPropertyName: "crossOrigin", publicName: "crossOrigin", isSignal: true, isRequired: false, transformFunction: null }, seekStep: { classPropertyName: "seekStep", publicName: "seekStep", isSignal: true, isRequired: false, transformFunction: null }, videoLabels: { classPropertyName: "videoLabels", publicName: "videoLabels", isSignal: true, isRequired: false, transformFunction: null }, autoPlayOnSelect: { classPropertyName: "autoPlayOnSelect", publicName: "autoPlayOnSelect", isSignal: true, isRequired: false, transformFunction: null }, showLessonTitle: { classPropertyName: "showLessonTitle", publicName: "showLessonTitle", isSignal: true, isRequired: false, transformFunction: null }, panelPosition: { classPropertyName: "panelPosition", publicName: "panelPosition", isSignal: true, isRequired: false, transformFunction: null }, layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null }, density: { classPropertyName: "density", publicName: "density", isSignal: true, isRequired: false, transformFunction: null }, accent: { classPropertyName: "accent", publicName: "accent", isSignal: true, isRequired: false, transformFunction: null }, showTabs: { classPropertyName: "showTabs", publicName: "showTabs", isSignal: true, isRequired: false, transformFunction: null }, showSearch: { classPropertyName: "showSearch", publicName: "showSearch", isSignal: true, isRequired: false, transformFunction: null }, showProgress: { classPropertyName: "showProgress", publicName: "showProgress", isSignal: true, isRequired: false, transformFunction: null }, soloSingleLessonModules: { classPropertyName: "soloSingleLessonModules", publicName: "soloSingleLessonModules", isSignal: true, isRequired: false, transformFunction: null }, panelLabels: { classPropertyName: "panelLabels", publicName: "panelLabels", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { activeLessonId: "activeLessonIdChange", playing: "playingChange", currentTime: "currentTimeChange", panelTab: "panelTabChange", lessonSelect: "lessonSelect", lessonEnded: "lessonEnded", timeUpdate: "timeUpdate", certificateClick: "certificateClick", playerError: "playerError" }, host: { classAttribute: "pecb-course-player-host" }, ngImport: i0, template: "<div [class]=\"hostClasses()\">\n <!-- \u2500\u2500 Video stage \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"pecb-course-player__stage\">\n <pecb-video-player\n class=\"pecb-course-player__video\"\n [src]=\"videoSrc()\"\n [poster]=\"videoPoster()\"\n [tracks]=\"videoTracks()\"\n [title]=\"videoTitle()\"\n [label]=\"videoStageLabel()\"\n [watermark]=\"watermark()\"\n [aspectRatio]=\"aspectRatio()\"\n [preload]=\"preload()\"\n [crossOrigin]=\"crossOrigin()\"\n [seekStep]=\"seekStep()\"\n [startTime]=\"startTime()\"\n [labels]=\"videoLabels()\"\n [(playing)]=\"playing\"\n [(currentTime)]=\"currentTime\"\n (timeUpdate)=\"timeUpdate.emit($event)\"\n (playbackEnded)=\"onPlaybackEnded()\"\n (playerError)=\"playerError.emit($event)\"\n ></pecb-video-player>\n\n @if (showLessonTitle() && lessonTitle()) {\n <h2 class=\"pecb-course-player__title\">{{ lessonTitle() }}</h2>\n }\n </div>\n\n <!-- \u2500\u2500 Content sidebar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"pecb-course-player__panel\">\n <div class=\"pecb-course-player__panel-inner\">\n <pecb-course-content-panel\n [modules]=\"modules()\"\n [transcript]=\"transcript()\"\n [(activeLessonId)]=\"activeLessonId\"\n [(tab)]=\"panelTab\"\n [playing]=\"playing()\"\n [currentTime]=\"currentTime()\"\n [layout]=\"layout()\"\n [density]=\"density()\"\n [accent]=\"accent()\"\n [showTabs]=\"showTabs()\"\n [showSearch]=\"showSearch()\"\n [showProgress]=\"showProgress()\"\n [soloSingleLessonModules]=\"soloSingleLessonModules()\"\n [labels]=\"panelLabels()\"\n (lessonSelect)=\"onLessonSelect($event)\"\n (transcriptSeek)=\"onTranscriptSeek($event)\"\n (certificateClick)=\"certificateClick.emit()\"\n ></pecb-course-content-panel>\n </div>\n </div>\n</div>\n", styles: [":host{display:block;width:100%;height:100%;min-height:0;container-type:inline-size;container-name:pecb-course-player}.pecb-course-player{display:grid;grid-template-columns:minmax(0,1fr) var(--pecb-course-player-panel-width, 408px);gap:var(--pecb-course-player-gap, 20px);align-items:stretch;height:100%;min-height:0}.pecb-course-player--panel-start{grid-template-columns:var(--pecb-course-player-panel-width, 408px) minmax(0,1fr)}.pecb-course-player--panel-start .pecb-course-player__stage{order:2}.pecb-course-player--panel-start .pecb-course-player__panel{order:1}.pecb-course-player__stage{display:flex;flex-direction:column;gap:14px;min-width:0;min-height:0}.pecb-course-player__video{flex:0 0 auto}.pecb-course-player__title{flex:0 0 auto;margin:0;padding:0 2px;font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:16px;font-weight:700;line-height:1.35;color:#212427;text-wrap:pretty}.pecb-course-player__panel{position:relative;min-width:0;min-height:240px}.pecb-course-player__panel-inner{position:absolute;inset:0;display:flex;overflow:hidden;border:1px solid #ececec;border-radius:var(--pecb-course-player-radius, 12px);background:#fff}.pecb-course-player pecb-course-content-panel{flex:1 1 auto;min-width:0}@container pecb-course-player (max-width: 900px){.pecb-course-player,.pecb-course-player--panel-start{grid-template-columns:minmax(0,1fr);height:auto}.pecb-course-player__stage,.pecb-course-player--panel-start .pecb-course-player__stage{order:1}.pecb-course-player__panel,.pecb-course-player--panel-start .pecb-course-player__panel{order:2;position:static;min-height:0;height:var(--pecb-course-player-panel-stacked-height, 460px)}.pecb-course-player__panel-inner{position:static;height:100%}}@container pecb-course-player (max-width: 560px){.pecb-course-player{gap:16px}.pecb-course-player__panel{height:var(--pecb-course-player-panel-stacked-height, 400px)}.pecb-course-player__title{font-size:14px}}\n"], dependencies: [{ kind: "component", type: VideoPlayerComponent, selector: "pecb-video-player", inputs: ["src", "poster", "tracks", "title", "crossOrigin", "preload", "autoplay", "loop", "playsInline", "startTime", "watermark", "label", "aspectRatio", "showCenterButton", "showSkipBack", "showVolume", "showTime", "showCaptions", "showSettings", "showFullscreen", "seekStep", "playbackRates", "labels", "playing", "currentTime", "volume", "muted", "playbackRate", "captionsEnabled"], outputs: ["playingChange", "currentTimeChange", "volumeChange", "mutedChange", "playbackRateChange", "captionsEnabledChange", "timeUpdate", "loadedMetadata", "seekEnd", "playbackEnded", "fullscreenChange", "playerError"] }, { kind: "component", type: CourseContentPanelComponent, selector: "pecb-course-content-panel", inputs: ["modules", "transcript", "activeLessonId", "tab", "openModuleId", "autoscroll", "query", "transcriptQuery", "playing", "currentTime", "layout", "density", "accent", "showTabs", "showSearch", "showTranscriptSearch", "showAutoscroll", "showProgress", "showCertificate", "soloSingleLessonModules", "labels"], outputs: ["activeLessonIdChange", "tabChange", "openModuleIdChange", "autoscrollChange", "queryChange", "transcriptQueryChange", "lessonSelect", "transcriptSeek", "moduleToggle", "certificateClick"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
12465
|
+
}
|
|
12466
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: CoursePlayerComponent, decorators: [{
|
|
12467
|
+
type: Component,
|
|
12468
|
+
args: [{ selector: 'pecb-course-player', standalone: true, imports: [VideoPlayerComponent, CourseContentPanelComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'pecb-course-player-host' }, template: "<div [class]=\"hostClasses()\">\n <!-- \u2500\u2500 Video stage \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"pecb-course-player__stage\">\n <pecb-video-player\n class=\"pecb-course-player__video\"\n [src]=\"videoSrc()\"\n [poster]=\"videoPoster()\"\n [tracks]=\"videoTracks()\"\n [title]=\"videoTitle()\"\n [label]=\"videoStageLabel()\"\n [watermark]=\"watermark()\"\n [aspectRatio]=\"aspectRatio()\"\n [preload]=\"preload()\"\n [crossOrigin]=\"crossOrigin()\"\n [seekStep]=\"seekStep()\"\n [startTime]=\"startTime()\"\n [labels]=\"videoLabels()\"\n [(playing)]=\"playing\"\n [(currentTime)]=\"currentTime\"\n (timeUpdate)=\"timeUpdate.emit($event)\"\n (playbackEnded)=\"onPlaybackEnded()\"\n (playerError)=\"playerError.emit($event)\"\n ></pecb-video-player>\n\n @if (showLessonTitle() && lessonTitle()) {\n <h2 class=\"pecb-course-player__title\">{{ lessonTitle() }}</h2>\n }\n </div>\n\n <!-- \u2500\u2500 Content sidebar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n <div class=\"pecb-course-player__panel\">\n <div class=\"pecb-course-player__panel-inner\">\n <pecb-course-content-panel\n [modules]=\"modules()\"\n [transcript]=\"transcript()\"\n [(activeLessonId)]=\"activeLessonId\"\n [(tab)]=\"panelTab\"\n [playing]=\"playing()\"\n [currentTime]=\"currentTime()\"\n [layout]=\"layout()\"\n [density]=\"density()\"\n [accent]=\"accent()\"\n [showTabs]=\"showTabs()\"\n [showSearch]=\"showSearch()\"\n [showProgress]=\"showProgress()\"\n [soloSingleLessonModules]=\"soloSingleLessonModules()\"\n [labels]=\"panelLabels()\"\n (lessonSelect)=\"onLessonSelect($event)\"\n (transcriptSeek)=\"onTranscriptSeek($event)\"\n (certificateClick)=\"certificateClick.emit()\"\n ></pecb-course-content-panel>\n </div>\n </div>\n</div>\n", styles: [":host{display:block;width:100%;height:100%;min-height:0;container-type:inline-size;container-name:pecb-course-player}.pecb-course-player{display:grid;grid-template-columns:minmax(0,1fr) var(--pecb-course-player-panel-width, 408px);gap:var(--pecb-course-player-gap, 20px);align-items:stretch;height:100%;min-height:0}.pecb-course-player--panel-start{grid-template-columns:var(--pecb-course-player-panel-width, 408px) minmax(0,1fr)}.pecb-course-player--panel-start .pecb-course-player__stage{order:2}.pecb-course-player--panel-start .pecb-course-player__panel{order:1}.pecb-course-player__stage{display:flex;flex-direction:column;gap:14px;min-width:0;min-height:0}.pecb-course-player__video{flex:0 0 auto}.pecb-course-player__title{flex:0 0 auto;margin:0;padding:0 2px;font-family:Plus Jakarta Sans,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:16px;font-weight:700;line-height:1.35;color:#212427;text-wrap:pretty}.pecb-course-player__panel{position:relative;min-width:0;min-height:240px}.pecb-course-player__panel-inner{position:absolute;inset:0;display:flex;overflow:hidden;border:1px solid #ececec;border-radius:var(--pecb-course-player-radius, 12px);background:#fff}.pecb-course-player pecb-course-content-panel{flex:1 1 auto;min-width:0}@container pecb-course-player (max-width: 900px){.pecb-course-player,.pecb-course-player--panel-start{grid-template-columns:minmax(0,1fr);height:auto}.pecb-course-player__stage,.pecb-course-player--panel-start .pecb-course-player__stage{order:1}.pecb-course-player__panel,.pecb-course-player--panel-start .pecb-course-player__panel{order:2;position:static;min-height:0;height:var(--pecb-course-player-panel-stacked-height, 460px)}.pecb-course-player__panel-inner{position:static;height:100%}}@container pecb-course-player (max-width: 560px){.pecb-course-player{gap:16px}.pecb-course-player__panel{height:var(--pecb-course-player-panel-stacked-height, 400px)}.pecb-course-player__title{font-size:14px}}\n"] }]
|
|
12469
|
+
}], ctorParameters: () => [], propDecorators: { modules: [{ type: i0.Input, args: [{ isSignal: true, alias: "modules", required: false }] }], transcript: [{ type: i0.Input, args: [{ isSignal: true, alias: "transcript", required: false }] }], activeLessonId: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeLessonId", required: false }] }, { type: i0.Output, args: ["activeLessonIdChange"] }], playing: [{ type: i0.Input, args: [{ isSignal: true, alias: "playing", required: false }] }, { type: i0.Output, args: ["playingChange"] }], currentTime: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentTime", required: false }] }, { type: i0.Output, args: ["currentTimeChange"] }], panelTab: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelTab", required: false }] }, { type: i0.Output, args: ["panelTabChange"] }], poster: [{ type: i0.Input, args: [{ isSignal: true, alias: "poster", required: false }] }], src: [{ type: i0.Input, args: [{ isSignal: true, alias: "src", required: false }] }], tracks: [{ type: i0.Input, args: [{ isSignal: true, alias: "tracks", required: false }] }], stageLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "stageLabel", required: false }] }], watermark: [{ type: i0.Input, args: [{ isSignal: true, alias: "watermark", required: false }] }], aspectRatio: [{ type: i0.Input, args: [{ isSignal: true, alias: "aspectRatio", required: false }] }], preload: [{ type: i0.Input, args: [{ isSignal: true, alias: "preload", required: false }] }], crossOrigin: [{ type: i0.Input, args: [{ isSignal: true, alias: "crossOrigin", required: false }] }], seekStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "seekStep", required: false }] }], videoLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "videoLabels", required: false }] }], autoPlayOnSelect: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoPlayOnSelect", required: false }] }], showLessonTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "showLessonTitle", required: false }] }], panelPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelPosition", required: false }] }], layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }], density: [{ type: i0.Input, args: [{ isSignal: true, alias: "density", required: false }] }], accent: [{ type: i0.Input, args: [{ isSignal: true, alias: "accent", required: false }] }], showTabs: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTabs", required: false }] }], showSearch: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSearch", required: false }] }], showProgress: [{ type: i0.Input, args: [{ isSignal: true, alias: "showProgress", required: false }] }], soloSingleLessonModules: [{ type: i0.Input, args: [{ isSignal: true, alias: "soloSingleLessonModules", required: false }] }], panelLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "panelLabels", required: false }] }], lessonSelect: [{ type: i0.Output, args: ["lessonSelect"] }], lessonEnded: [{ type: i0.Output, args: ["lessonEnded"] }], timeUpdate: [{ type: i0.Output, args: ["timeUpdate"] }], certificateClick: [{ type: i0.Output, args: ["certificateClick"] }], playerError: [{ type: i0.Output, args: ["playerError"] }] } });
|
|
12470
|
+
|
|
12471
|
+
/**
|
|
12472
|
+
* An injectable, Toastr-style notification service.
|
|
12473
|
+
*
|
|
12474
|
+
* Calling any of the helpers renders a `pecb-alert` toast in a CDK overlay
|
|
12475
|
+
* stack on `<body>` — no template wiring, host component, or module setup is
|
|
12476
|
+
* required. Just inject and call:
|
|
12477
|
+
*
|
|
12478
|
+
* @example
|
|
12479
|
+
* ```typescript
|
|
12480
|
+
* import { NotificationService } from '@pecb-ui/components';
|
|
12481
|
+
*
|
|
12482
|
+
* @Component({...})
|
|
12483
|
+
* export class MyComponent {
|
|
12484
|
+
* private notify = inject(NotificationService);
|
|
12485
|
+
*
|
|
12486
|
+
* save(): void {
|
|
12487
|
+
* this.notify.success('Saved!');
|
|
12488
|
+
* this.notify.error('Something went wrong', 'Error');
|
|
12489
|
+
* }
|
|
12490
|
+
* }
|
|
12491
|
+
* ```
|
|
12492
|
+
*
|
|
12493
|
+
* @publicApi
|
|
12494
|
+
*/
|
|
12495
|
+
class NotificationService {
|
|
12496
|
+
constructor() {
|
|
12497
|
+
this.overlay = inject(Overlay);
|
|
12498
|
+
this.appRef = inject(ApplicationRef);
|
|
12499
|
+
this.environmentInjector = inject(EnvironmentInjector);
|
|
12500
|
+
// Legacy reactive streams — kept for backward compatibility. Rendering is now
|
|
12501
|
+
// handled directly by this service, so subscribing to these is no longer
|
|
12502
|
+
// required to display notifications.
|
|
12503
|
+
this.notificationSubject = new Subject();
|
|
12504
|
+
this.dismissSubject = new Subject();
|
|
12505
|
+
this.clearSubject = new Subject();
|
|
12506
|
+
/** Default duration for notifications in milliseconds */
|
|
12507
|
+
this.defaultDuration = 5000;
|
|
12508
|
+
/** Default position for notifications */
|
|
12509
|
+
this.defaultPosition = 'top-right';
|
|
12510
|
+
/** Time to keep a toast mounted after dismiss so its leave animation can play. */
|
|
12511
|
+
this.leaveAnimationMs = 300;
|
|
12512
|
+
/** One shared overlay (a vertical stack) per on-screen position. */
|
|
12513
|
+
this.stacks = new Map();
|
|
12514
|
+
/** All live toasts, keyed by their id. */
|
|
12515
|
+
this.toasts = new Map();
|
|
12516
|
+
}
|
|
12517
|
+
/** @deprecated Notifications now render automatically; no need to subscribe. */
|
|
12518
|
+
get notifications$() {
|
|
12519
|
+
return this.notificationSubject.asObservable();
|
|
12520
|
+
}
|
|
12521
|
+
/** @deprecated Notifications now render automatically; no need to subscribe. */
|
|
12522
|
+
get dismiss$() {
|
|
12523
|
+
return this.dismissSubject.asObservable();
|
|
12524
|
+
}
|
|
12525
|
+
/** @deprecated Notifications now render automatically; no need to subscribe. */
|
|
12526
|
+
get clear$() {
|
|
12527
|
+
return this.clearSubject.asObservable();
|
|
12528
|
+
}
|
|
12529
|
+
/**
|
|
12530
|
+
* Show a success notification
|
|
12531
|
+
* @param message - The notification message
|
|
12532
|
+
* @param title - Optional title
|
|
12533
|
+
* @param config - Optional additional configuration
|
|
12534
|
+
*/
|
|
12535
|
+
success(message, title, config) {
|
|
12536
|
+
return this.show({ ...config, message, title, type: 'success' });
|
|
12537
|
+
}
|
|
12538
|
+
/**
|
|
12539
|
+
* Show an error notification
|
|
12540
|
+
* @param message - The notification message
|
|
12541
|
+
* @param title - Optional title
|
|
12542
|
+
* @param config - Optional additional configuration
|
|
12543
|
+
*/
|
|
12544
|
+
error(message, title, config) {
|
|
12545
|
+
return this.show({ ...config, message, title, type: 'error' });
|
|
12546
|
+
}
|
|
12547
|
+
/**
|
|
12548
|
+
* Show a warning notification
|
|
12549
|
+
* @param message - The notification message
|
|
12550
|
+
* @param title - Optional title
|
|
12551
|
+
* @param config - Optional additional configuration
|
|
12552
|
+
*/
|
|
12553
|
+
warning(message, title, config) {
|
|
12554
|
+
return this.show({ ...config, message, title, type: 'warning' });
|
|
12555
|
+
}
|
|
12556
|
+
/**
|
|
12557
|
+
* Show an info notification
|
|
12558
|
+
* @param message - The notification message
|
|
12559
|
+
* @param title - Optional title
|
|
12560
|
+
* @param config - Optional additional configuration
|
|
12561
|
+
*/
|
|
12562
|
+
info(message, title, config) {
|
|
12563
|
+
return this.show({ ...config, message, title, type: 'info' });
|
|
12564
|
+
}
|
|
12565
|
+
/**
|
|
12566
|
+
* Show a notification with custom configuration
|
|
12567
|
+
* @param config - Full notification configuration
|
|
12568
|
+
* @returns The notification ID
|
|
12569
|
+
*/
|
|
12570
|
+
show(config) {
|
|
12571
|
+
const id = config.id || this.generateId();
|
|
12572
|
+
const position = config.position ?? this.defaultPosition;
|
|
12573
|
+
const duration = config.duration ?? this.defaultDuration;
|
|
12574
|
+
const dismissible = config.dismissible ?? true;
|
|
12575
|
+
// Legacy stream for any pre-existing subscribers.
|
|
12576
|
+
this.notificationSubject.next({
|
|
12577
|
+
id,
|
|
12578
|
+
message: config.message,
|
|
12579
|
+
title: config.title,
|
|
12580
|
+
type: config.type,
|
|
12581
|
+
duration,
|
|
12582
|
+
dismissible,
|
|
12583
|
+
position,
|
|
12584
|
+
customClass: config.customClass,
|
|
12585
|
+
timestamp: new Date()
|
|
12586
|
+
});
|
|
12587
|
+
const overlayRef = this.getStack(position);
|
|
12588
|
+
// Build the alert imperatively and drop it into the stack's pane.
|
|
12589
|
+
const componentRef = createComponent(AlertComponent, {
|
|
12590
|
+
environmentInjector: this.environmentInjector
|
|
12591
|
+
});
|
|
12592
|
+
componentRef.setInput('type', config.type);
|
|
12593
|
+
componentRef.setInput('variant', config.variant ?? 'banner');
|
|
12594
|
+
componentRef.setInput('title', config.title ?? '');
|
|
12595
|
+
componentRef.setInput('message', config.message);
|
|
12596
|
+
componentRef.setInput('dismissible', dismissible);
|
|
12597
|
+
componentRef.setInput('primaryAction', config.primaryAction);
|
|
12598
|
+
componentRef.setInput('secondaryAction', config.secondaryAction);
|
|
12599
|
+
const host = componentRef.location.nativeElement;
|
|
12600
|
+
host.style.pointerEvents = 'auto';
|
|
12601
|
+
if (config.customClass) {
|
|
12602
|
+
host.classList.add(config.customClass);
|
|
12603
|
+
}
|
|
12604
|
+
this.appRef.attachView(componentRef.hostView);
|
|
12605
|
+
overlayRef.overlayElement.appendChild(host);
|
|
12606
|
+
// User clicked the × — start the removal (leave animation + unmount).
|
|
12607
|
+
componentRef.instance.dismissed.subscribe(() => this.remove(id));
|
|
12608
|
+
// Action buttons: run the callback, then dismiss unless it returns false.
|
|
12609
|
+
componentRef.instance.primaryActionClick.subscribe(() => {
|
|
12610
|
+
if (config.onPrimaryAction?.() !== false) {
|
|
12611
|
+
this.remove(id);
|
|
12612
|
+
}
|
|
12613
|
+
});
|
|
12614
|
+
componentRef.instance.secondaryActionClick.subscribe(() => {
|
|
12615
|
+
if (config.onSecondaryAction?.() !== false) {
|
|
12616
|
+
this.remove(id);
|
|
12617
|
+
}
|
|
12618
|
+
});
|
|
12619
|
+
const toast = { componentRef, position };
|
|
12620
|
+
this.toasts.set(id, toast);
|
|
12621
|
+
if (duration > 0) {
|
|
12622
|
+
toast.removeTimer = setTimeout(() => this.remove(id), duration);
|
|
12623
|
+
}
|
|
12624
|
+
return id;
|
|
12625
|
+
}
|
|
12626
|
+
/**
|
|
12627
|
+
* Dismiss a specific notification by ID
|
|
12628
|
+
* @param id - The notification ID to dismiss
|
|
12629
|
+
*/
|
|
10743
12630
|
dismiss(id) {
|
|
10744
12631
|
this.dismissSubject.next(id);
|
|
12632
|
+
this.remove(id);
|
|
10745
12633
|
}
|
|
10746
12634
|
/**
|
|
10747
12635
|
* Clear all notifications
|
|
10748
12636
|
*/
|
|
10749
12637
|
clearAll() {
|
|
10750
12638
|
this.clearSubject.next();
|
|
12639
|
+
for (const id of [...this.toasts.keys()]) {
|
|
12640
|
+
this.remove(id);
|
|
12641
|
+
}
|
|
12642
|
+
}
|
|
12643
|
+
/** Get (or lazily create) the overlay stack for a screen position. */
|
|
12644
|
+
getStack(position) {
|
|
12645
|
+
let overlayRef = this.stacks.get(position);
|
|
12646
|
+
if (overlayRef) {
|
|
12647
|
+
return overlayRef;
|
|
12648
|
+
}
|
|
12649
|
+
const [vertical, horizontal] = position.split('-');
|
|
12650
|
+
overlayRef = this.overlay.create({
|
|
12651
|
+
// Toasts must never block the page underneath.
|
|
12652
|
+
hasBackdrop: false,
|
|
12653
|
+
scrollStrategy: this.overlay.scrollStrategies.noop()
|
|
12654
|
+
});
|
|
12655
|
+
// We position the stack manually rather than via a CDK position strategy:
|
|
12656
|
+
// the strategy's apply() only runs once a portal is attached, but we append
|
|
12657
|
+
// toast elements straight into the pane (so several can share one stack).
|
|
12658
|
+
// The `cdk-global-overlay-wrapper` class (normally added by the global
|
|
12659
|
+
// strategy) makes the host a full-viewport flexbox; aligning it then places
|
|
12660
|
+
// the pane in the chosen corner.
|
|
12661
|
+
const wrapper = overlayRef.hostElement;
|
|
12662
|
+
wrapper.classList.add('cdk-global-overlay-wrapper');
|
|
12663
|
+
wrapper.style.justifyContent =
|
|
12664
|
+
horizontal === 'center' ? 'center' : horizontal === 'right' ? 'flex-end' : 'flex-start';
|
|
12665
|
+
wrapper.style.alignItems = vertical === 'top' ? 'flex-start' : 'flex-end';
|
|
12666
|
+
// Lay the toasts out as a vertical, non-blocking column. Bottom positions
|
|
12667
|
+
// grow upward so the newest toast sits nearest the edge.
|
|
12668
|
+
const pane = overlayRef.overlayElement;
|
|
12669
|
+
pane.style.position = 'static'; // let the flex wrapper place it
|
|
12670
|
+
pane.style.margin = '16px';
|
|
12671
|
+
pane.style.display = 'flex';
|
|
12672
|
+
pane.style.flexDirection = vertical === 'top' ? 'column' : 'column-reverse';
|
|
12673
|
+
pane.style.gap = '12px';
|
|
12674
|
+
pane.style.width = '380px';
|
|
12675
|
+
pane.style.maxWidth = 'calc(100vw - 32px)';
|
|
12676
|
+
pane.style.pointerEvents = 'none';
|
|
12677
|
+
this.stacks.set(position, overlayRef);
|
|
12678
|
+
return overlayRef;
|
|
12679
|
+
}
|
|
12680
|
+
/** Play the leave animation, then unmount the toast and tidy up its stack. */
|
|
12681
|
+
remove(id) {
|
|
12682
|
+
const toast = this.toasts.get(id);
|
|
12683
|
+
if (!toast) {
|
|
12684
|
+
return;
|
|
12685
|
+
}
|
|
12686
|
+
this.toasts.delete(id);
|
|
12687
|
+
clearTimeout(toast.removeTimer);
|
|
12688
|
+
// Triggers the alert's leave animation (see AlertComponent.visible/leaving).
|
|
12689
|
+
toast.componentRef.instance.visible.set(false);
|
|
12690
|
+
setTimeout(() => {
|
|
12691
|
+
this.appRef.detachView(toast.componentRef.hostView);
|
|
12692
|
+
toast.componentRef.destroy();
|
|
12693
|
+
this.disposeStackIfEmpty(toast.position);
|
|
12694
|
+
}, this.leaveAnimationMs);
|
|
12695
|
+
}
|
|
12696
|
+
/** Dispose a position's overlay once it holds no more toasts. */
|
|
12697
|
+
disposeStackIfEmpty(position) {
|
|
12698
|
+
const stillUsed = [...this.toasts.values()].some((t) => t.position === position);
|
|
12699
|
+
if (stillUsed) {
|
|
12700
|
+
return;
|
|
12701
|
+
}
|
|
12702
|
+
this.stacks.get(position)?.dispose();
|
|
12703
|
+
this.stacks.delete(position);
|
|
10751
12704
|
}
|
|
10752
12705
|
/**
|
|
10753
12706
|
* Generate a unique notification ID
|
|
@@ -17157,5 +19110,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
17157
19110
|
* Generated bundle index. Do not edit.
|
|
17158
19111
|
*/
|
|
17159
19112
|
|
|
17160
|
-
export { AccordionItemComponent, AccordionSmallComponent, AddButtonComponent, AdminHeaderComponent, AdminHeaderFieldTemplateDirective, AffixComponent, AlertComponent, AnchorComponent, ApplicationStatusBarComponent, AuditorStatusComponent, AuthorDateTimeComponent, BackToTopComponent, BadgeComponent, BlurDirective, BottomSheetComponent, BreadcrumbsComponent, ButtonComponent, ButtonGroupComponent, ButtonGroupItemComponent, CancelUpdateButtonsComponent, CardBodyComponent, CardComponent, CardFooterComponent, CardHeaderComponent, CertificateUploadBarComponent, CheckDeleteIconComponent, CheckboxComponent, CheckboxDisplayComponent, CodeInputComponent, CodeSnippetComponent, ColorPaletteComponent, ConfirmationComponent, ContentTypeTagComponent, DEFAULT_LANGUAGES, DashboardGridComponent, DatepickerComponent, DividerComponent, DropdownComponent, EMPTY_STATE_MAX_BUTTONS, EditButtonComponent, EditCoverPhotoComponent, EmptyStateComponent, ExpandableRowTableComponent, FileUploadComponent, FilterColumnsComponent, FloatButtonComponent, FloatButtonItemComponent, FullscreenModalComponent, FullscreenModalContentDirective, FullscreenModalFooterDirective, FullscreenModalHeaderDirective, GeneralComponent, GridComponent, GridItemComponent, HeaderActionsComponent, HeaderComponent, HeaderDividerComponent, HeaderLanguageComponent, HeaderSearchComponent, HeaderUserComponent, HierarchicalTableComponent, HorizontalStepsComponent, IconComponent, IconRegistry, IconTagComponent, InformationBoxComponent, InputComponent, LanguageDropdownComponent, LinkButtonComponent, LoadingService, MediaComponent, MessageBubbleComponent, MessageItemComponent, MetricsCardComponent, MiniIconButtonComponent, NoResultsComponent, NoteSidebarItemComponent, NotesPanelComponent, NotificationService, NotificationStatusLinkComponent, PECB_COLOR_PALETTE, PECB_CUSTOM_ICONS, PECB_FONT_STYLES, PECB_ICONS, PECB_TYPE_SCALE, PaginationComponent, PriceMethodComponent, ProfileComponent, ProfileElementsCardComponent, ProfileGroupComponent, ProgressBarComponent, ProgressCircleComponent, ProjectLayoutComponent, QuantitySelectorComponent, QuestionTypeTagComponent, RadioComponent, RadioDisplayComponent, RatingNumberComponent, ReasonForReturnComponent, RequestSentByComponent, RequestStatusBarComponent, ResultPageComponent, RightModalComponent, RightModalContentDirective, RightModalFooterDirective, RightModalHeaderDirective, ShadowDirective, SidebarComponent, SkeletonComponent, SlidePointsComponent, SpacerComponent, SpinnerComponent, StandardsPdfCardComponent, StatisticsCardComponent, StatusComponent, StepperComponent, TabComponent, TableComponent, TagComponent, TestimonialComponent, TextFormFieldComponent, ThemeService, ToggleComponent, ToolbarBarComponent, TooltipComponent, TooltipDirective, TourComponent, TranscriptLineComponent, TypographyComponent, UserWithEmailComponent, VerifyChecklistComponent, VideoUploadBarComponent, VirtualTableComponent, addClass, announceToScreenReader, capitalize, closestElement, copyToClipboard, createAuthError, createAuthorizationError, createConfigError, createError, createNetworkError, createValidationError, disableBodyScroll, escapeHtml, formatCount, formatErrorForLog, formatErrorMessage, generateLinkedIds, generateUniqueId, getAriaCurrent, getButtonAriaAttributes, getComputedStyleValue, getDialogAriaAttributes, getFocusableElements, getInitials, getInputAriaAttributes, getOptionAriaAttributes, getProgressAriaAttributes, getScrollParent, getTabAriaAttributes, getVisuallyHiddenStyles, handleError, hasClass, isBlank, isBrowser, isElementVisible, isNotBlank, isPecbError, isRecoverableError, matchesSelector, pluralize, prefersHighContrast, prefersReducedMotion, registerErrorHandler, removeClass, scrollIntoView, slugify, stripHtml, toCamelCase, toKebabCase, toPascalCase, toSnakeCase, toggleClass, trapFocus, truncate, tryAsync, trySync, wrapError };
|
|
19113
|
+
export { AccordionItemComponent, AccordionSmallComponent, AddButtonComponent, AdminHeaderComponent, AdminHeaderFieldTemplateDirective, AffixComponent, AlertComponent, AnchorComponent, ApplicationStatusBarComponent, AuditorStatusComponent, AuthorDateTimeComponent, BackToTopComponent, BadgeComponent, BlurDirective, BottomSheetComponent, BreadcrumbsComponent, ButtonComponent, ButtonGroupComponent, ButtonGroupItemComponent, CancelUpdateButtonsComponent, CardBodyComponent, CardComponent, CardFooterComponent, CardHeaderComponent, CertificateUploadBarComponent, CheckDeleteIconComponent, CheckboxComponent, CheckboxDisplayComponent, CodeInputComponent, CodeSnippetComponent, ColorPaletteComponent, ConfirmationComponent, ContentTypeTagComponent, CourseContentPanelComponent, CoursePlayerComponent, DEFAULT_COURSE_CONTENT_PANEL_LABELS, DEFAULT_LANGUAGES, DEFAULT_VIDEO_PLAYER_LABELS, DEFAULT_VIDEO_PLAYER_RATES, DashboardGridComponent, DatepickerComponent, DividerComponent, DropdownComponent, EMPTY_STATE_MAX_BUTTONS, EditButtonComponent, EditCoverPhotoComponent, EmptyStateComponent, ExpandableRowTableComponent, FileUploadComponent, FilterColumnsComponent, FloatButtonComponent, FloatButtonItemComponent, FullscreenModalComponent, FullscreenModalContentDirective, FullscreenModalFooterDirective, FullscreenModalHeaderDirective, GeneralComponent, GridComponent, GridItemComponent, HeaderActionsComponent, HeaderComponent, HeaderDividerComponent, HeaderLanguageComponent, HeaderSearchComponent, HeaderUserComponent, HierarchicalTableComponent, HorizontalStepsComponent, IconComponent, IconRegistry, IconTagComponent, InformationBoxComponent, InputComponent, LanguageDropdownComponent, LinkButtonComponent, LoadingService, MediaComponent, MessageBubbleComponent, MessageItemComponent, MetricsCardComponent, MiniIconButtonComponent, NoResultsComponent, NoteSidebarItemComponent, NotesPanelComponent, NotificationService, NotificationStatusLinkComponent, PECB_COLOR_PALETTE, PECB_CUSTOM_ICONS, PECB_FONT_STYLES, PECB_ICONS, PECB_TYPE_SCALE, PaginationComponent, PriceMethodComponent, ProfileComponent, ProfileElementsCardComponent, ProfileGroupComponent, ProgressBarComponent, ProgressCircleComponent, ProjectLayoutComponent, QuantitySelectorComponent, QuestionTypeTagComponent, RadioComponent, RadioDisplayComponent, RatingNumberComponent, ReasonForReturnComponent, RequestSentByComponent, RequestStatusBarComponent, ResultPageComponent, RightModalComponent, RightModalContentDirective, RightModalFooterDirective, RightModalHeaderDirective, ShadowDirective, SidebarComponent, SkeletonComponent, SlidePointsComponent, SpacerComponent, SpinnerComponent, StandardsPdfCardComponent, StatisticsCardComponent, StatusComponent, StepperComponent, TabComponent, TableComponent, TagComponent, TestimonialComponent, TextFormFieldComponent, ThemeService, ToggleComponent, ToolbarBarComponent, TooltipComponent, TooltipDirective, TourComponent, TranscriptLineComponent, TypographyComponent, UserWithEmailComponent, VerifyChecklistComponent, VideoPlayerComponent, VideoUploadBarComponent, VirtualTableComponent, addClass, announceToScreenReader, capitalize, closestElement, copyToClipboard, courseModuleStats, courseProgress, createAuthError, createAuthorizationError, createConfigError, createError, createNetworkError, createValidationError, disableBodyScroll, escapeHtml, findCourseLesson, findCourseModule, formatCount, formatCourseDuration, formatCourseTime, formatErrorForLog, formatErrorMessage, formatVideoTime, generateLinkedIds, generateUniqueId, getAriaCurrent, getButtonAriaAttributes, getComputedStyleValue, getDialogAriaAttributes, getFocusableElements, getInitials, getInputAriaAttributes, getOptionAriaAttributes, getProgressAriaAttributes, getScrollParent, getTabAriaAttributes, getVisuallyHiddenStyles, handleError, hasClass, isBlank, isBrowser, isElementVisible, isNotBlank, isPecbError, isRecoverableError, matchesSelector, parseLessonPartTitle, pluralize, prefersHighContrast, prefersReducedMotion, registerErrorHandler, removeClass, resolveLessonStatus, scrollIntoView, slugify, stripHtml, toCamelCase, toKebabCase, toPascalCase, toSnakeCase, toggleClass, trapFocus, truncate, tryAsync, trySync, wrapError };
|
|
17161
19114
|
//# sourceMappingURL=pecb-ui-components.mjs.map
|