UNPKG

ng-hub-ui-accordion

Version:

A flexible, accessible, and customizable accordion component for Angular 19, part of the ng-hub-ui family.

282 lines (275 loc) 15.2 kB
import { NgTemplateOutlet } from '@angular/common'; import * as i0 from '@angular/core'; import { Directive, input, model, contentChild, TemplateRef, output, Component, contentChildren, effect, forwardRef, HostBinding } from '@angular/core'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; class AccordionPanelHeaderDirective { constructor(templateRef) { this.templateRef = templateRef; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AccordionPanelHeaderDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.8", type: AccordionPanelHeaderDirective, isStandalone: true, selector: "[hubAccordionPanelHeader]", ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AccordionPanelHeaderDirective, decorators: [{ type: Directive, args: [{ selector: '[hubAccordionPanelHeader]' }] }], ctorParameters: () => [{ type: i0.TemplateRef }] }); /** * A component that represents a single panel within an accordion. * Each panel can be expanded or collapsed independently and contains a header and content section. * * @example * ```html * <hub-accordion-panel [title]="'Panel Title'" [value]="panelValue"> * <ng-template hubAccordionPanelHeader>Custom Header</ng-template> * Panel Content * </hub-accordion-panel> * ``` * * @implements {OnInit} * * @property {number} index - The position of this panel within its parent accordion * @property {any} value - The value associated with this panel * @property {WritableSignal<boolean>} collapsed - Signal controlling the panel's collapsed state * @property {string} title - The title text displayed in the panel's header * @property {TemplateRef<any>} headerTpt - Template reference for custom header content * * @emits {collapseEvent} collapsedChange - Fired when the panel's collapsed state changes */ class AccordionPanelComponent { constructor() { this.value = input(); /** * Signal that controls the collapsed state of the accordion panel. * When true, the panel is collapsed. When false, the panel is expanded. * @default true */ this.collapsed = model(true); /** * The title of the accordion panel. * * @type {string} */ this.title = input(); /** * Reference to the header template of the accordion panel. * Uses contentChild query to find a template marked with AccordionPanelHeaderDirective. * The template reference is read using TemplateRef. */ this.headerTpt = contentChild(AccordionPanelHeaderDirective, { read: TemplateRef }); /** * Event emitter that fires when the panel's collapsed state changes. * Emits a collapseEvent object containing the collapse state and animation details. * @event */ this.collapsedChange = output(); } /** * Toggles the collapse state of the accordion panel and emits the change event. * The event includes the panel's index and its new collapsed state. * * @emits collapsedChange - Emits an object containing the panel index and collapsed state */ toggleCollapse() { this.collapsed.update((value) => !value); this.collapsedChange.emit({ index: this.index, collapsed: this.collapsed(), uncollapsed: !this.collapsed(), value: this.value() }); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AccordionPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.8", type: AccordionPanelComponent, isStandalone: true, selector: "hub-accordion-panel", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { collapsed: "collapsedChange", collapsedChange: "collapsedChange" }, host: { classAttribute: "hub-accordion-panel" }, queries: [{ propertyName: "headerTpt", first: true, predicate: AccordionPanelHeaderDirective, descendants: true, read: TemplateRef, isSignal: true }], ngImport: i0, template: "<h2 class=\"hub-accordion-header\">\n\t<button\n\t\tclass=\"hub-accordion-button\"\n\t\ttype=\"button\"\n\t\t[class.collapsed]=\"collapsed()\"\n\t\t[attr.data-bs-target]=\"'#collapse' + index\"\n\t\t[attr.aria-expanded]=\"!collapsed()\"\n\t\t[attr.aria-controls]=\"'collapse' + index\"\n\t\t(click)=\"toggleCollapse()\"\n\t>\n\t\t@if (headerTpt()) {\n\t\t\t<ng-container [ngTemplateOutlet]=\"$any(headerTpt())\"></ng-container>\n\t\t} @else if (title()) {\n\t\t\t{{ title() }}\n\t\t}\n\t</button>\n</h2>\n<div\n\t[attr.id]=\"'collapse' + index\"\n\tclass=\"hub-accordion-collapse collapse\"\n\t[class.show]=\"!collapsed()\"\n>\n\t<div class=\"hub-accordion-body\">\n\t\t<ng-content></ng-content>\n\t</div>\n</div>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AccordionPanelComponent, decorators: [{ type: Component, args: [{ selector: 'hub-accordion-panel', imports: [NgTemplateOutlet], host: { class: 'hub-accordion-panel' }, template: "<h2 class=\"hub-accordion-header\">\n\t<button\n\t\tclass=\"hub-accordion-button\"\n\t\ttype=\"button\"\n\t\t[class.collapsed]=\"collapsed()\"\n\t\t[attr.data-bs-target]=\"'#collapse' + index\"\n\t\t[attr.aria-expanded]=\"!collapsed()\"\n\t\t[attr.aria-controls]=\"'collapse' + index\"\n\t\t(click)=\"toggleCollapse()\"\n\t>\n\t\t@if (headerTpt()) {\n\t\t\t<ng-container [ngTemplateOutlet]=\"$any(headerTpt())\"></ng-container>\n\t\t} @else if (title()) {\n\t\t\t{{ title() }}\n\t\t}\n\t</button>\n</h2>\n<div\n\t[attr.id]=\"'collapse' + index\"\n\tclass=\"hub-accordion-collapse collapse\"\n\t[class.show]=\"!collapsed()\"\n>\n\t<div class=\"hub-accordion-body\">\n\t\t<ng-content></ng-content>\n\t</div>\n</div>\n" }] }] }); /** * A component that implements an accordion UI pattern, allowing users to show and hide * sections of related content. Supports single or multiple expanded panels and flush styling. * Implements ControlValueAccessor for form integration. * * @example * ```html * <hub-accordion [multiple]="true" [options]="{flush: true}"> * <hub-accordion-panel>...</hub-accordion-panel> * <hub-accordion-panel>...</hub-accordion-panel> * </hub-accordion> * ``` * * @implements {ControlValueAccessor} * @extends {SelectMultipleControlValueAccessor} */ class AccordionComponent { constructor() { /** * The current value of the accordion component. * An array that may contain any type of data. */ this.value = []; /** * Input binding that configures the property or attribute to bind to. * Defines the property that will be bound to the value of the accordion. * @default undefined */ this.bindValue = input(); /** * Configuration options for the accordion component. * * @property {Object} options - The options object for configuring the accordion. * @property {boolean} options.flush - When true, removes some borders and rounded corners to render * accordions edge-to-edge with their parent container. Defaults to false. */ this.options = input({ flush: false }); /** * Defines a comparison function to determine if two accordion items are equal. * Used to track identity of objects in accordion selections. * * @param o1 First object to compare * @param o2 Second object to compare * @returns {boolean} True if objects are considered equal, false otherwise * @defaultValue Default implementation uses strict equality (===) */ this.compareWith = input((o1, o2) => o1 === o2); /** * When true, expanding an accordion item will close all other expanded items. * Defaults to false. */ this.multiple = input(false); /** * Collection of child AccordionPanelComponent instances within this accordion. * Uses Angular's contentChildren query to obtain all panel components. * @type {QueryList<AccordionPanelComponent>} */ this.panels = contentChildren(AccordionPanelComponent); /** * Effect that manages panel changes and their subscriptions. * Initializes panel indices and subscribes to collapse events for each panel. * This effect runs whenever the panels signal changes. * * For each panel: * - Sets its index based on array position * - Subscribes to panel collapse events to handle state changes */ this.panelsChangeEffect = effect(() => { this.panels().forEach((panel, index) => { panel.index = index; panel.collapsedChange.subscribe((event) => { this.handlePanelCollapse(event); }); }); }); this.onChange = () => { }; this.onTouch = () => { }; } /** * Gets whether the accordion has the flush styling option enabled * @returns {boolean} True if the flush option is enabled, false otherwise */ get haveFlushClass() { return this.options().flush; } /** * Implements the ControlValueAccessor interface to write a new value to the form control. * If multiple selection is not enabled, wraps the value in an array. * @param obj The value to be written to the form control */ writeValue(obj) { if (!obj) { this.value = []; return; } this.value = !this.multiple() ? [obj] : obj; this.handleValue(); } /** * Registers a callback function that is invoked when the control's value changes in the UI. * This is part of the ControlValueAccessor interface implementation. * @param fn - The callback function to register. This function will be called with the new value when the control's value changes. */ registerOnChange(fn) { this.onChange = fn; } /** * Registers a callback function that is called when the control receives a touch event. * Part of the ControlValueAccessor interface implementation. * @param fn - The callback function to register. Gets called when the control is touched. */ registerOnTouched(fn) { this.onTouch = fn; } /** * Updates the collapse state of all panels based on the current value. * Each panel is collapsed if its value is not found in the accordion's value array using the compareWith function. * @internal */ handleValue() { for (const panel of this.panels()) { panel.collapsed.set(!this.value.find((v) => this.compareWith()(v, panel.value()))); } } /** * Handles the collapse/expand event of an accordion panel. * If the panel is being expanded and multiple is enabled, * all other panels will be collapsed. * * @param {CollapseEvent} param0 - Object containing panel index and collapse state * @param {number} param0.index - The index of the panel being toggled * @param {boolean} param0.collapsed - The new collapse state of the panel * @param {boolean} param0.value - The panel value */ handlePanelCollapse({ index, collapsed, value }) { if (!Array.isArray(this.value)) { this.value = []; } if (collapsed) { this.value = this.value.filter((item) => !this.compareWith()(item, value)); } else { this.value = !this.multiple() ? [value] : [...this.value, value]; } if (!collapsed && !this.multiple()) { for (const panel of this.panels()) { if (panel.index === index) { continue; } panel.collapsed.set(true); } } this.onChange(!this.multiple() ? (this.value.at(0) ?? null) : this.value); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AccordionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "19.2.8", type: AccordionComponent, isStandalone: true, selector: "hub-accordion", inputs: { bindValue: { classPropertyName: "bindValue", publicName: "bindValue", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "compareWith", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.hub-accordion-flush": "this.haveFlushClass" }, classAttribute: "hub-accordion" }, providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => AccordionComponent), multi: true } ], queries: [{ propertyName: "panels", predicate: AccordionPanelComponent, isSignal: true }], ngImport: i0, template: "<ng-content></ng-content>\n" }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AccordionComponent, decorators: [{ type: Component, args: [{ selector: 'hub-accordion', host: { class: 'hub-accordion' }, providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => AccordionComponent), multi: true } ], template: "<ng-content></ng-content>\n" }] }], propDecorators: { haveFlushClass: [{ type: HostBinding, args: ['class.hub-accordion-flush'] }] } }); /* * Public API Surface of accordion */ /** * Generated bundle index. Do not edit. */ export { AccordionComponent, AccordionPanelComponent, AccordionPanelHeaderDirective }; //# sourceMappingURL=ng-hub-ui-accordion.mjs.map