UNPKG

@sixbell-telco/sdk

Version:

A collection of reusable components designed for use in Sixbell Telco Angular projects

409 lines (403 loc) 19.8 kB
import * as i0 from '@angular/core'; import { ViewChild, Component, input, signal, output, effect, computed, HostListener, ContentChild } from '@angular/core'; import { CdkOverlayOrigin, CdkConnectedOverlay } from '@angular/cdk/overlay'; import { NgTemplateOutlet } from '@angular/common'; /** * OverlayContent - Content container for overlay * * @remarks * This component contains the overlay content and provides it as a template * for the overlay component to display. * * @example * ```html * <st-overlay-content> * <div class="p-4 bg-base-100 rounded-box shadow-lg"> * Your custom content here * </div> * </st-overlay-content> * ``` */ class OverlayContentComponent { template; static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: OverlayContentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.0", type: OverlayContentComponent, isStandalone: true, selector: "st-overlay-content", viewQueries: [{ propertyName: "template", first: true, predicate: ["contentTemplate"], descendants: true, static: true }], ngImport: i0, template: "<ng-template #contentTemplate>\n\t<ng-content></ng-content>\n</ng-template>\n" }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: OverlayContentComponent, decorators: [{ type: Component, args: [{ selector: 'st-overlay-content', standalone: true, template: "<ng-template #contentTemplate>\n\t<ng-content></ng-content>\n</ng-template>\n" }] }], propDecorators: { template: [{ type: ViewChild, args: ['contentTemplate', { static: true }] }] } }); /** * OverlayTrigger - Trigger component for overlay positioning * * @remarks * This component acts as the trigger that opens/closes the overlay content. * It can contain any content (buttons, inputs, avatars, text, icons, etc.). * Uses CdkOverlayOrigin for positioning reference. * * @example * ```html * <st-overlay> * <st-overlay-trigger> * <button class="btn btn-primary">Open Overlay</button> * </st-overlay-trigger> * <st-overlay-content> * <div>Your content</div> * </st-overlay-content> * </st-overlay> * ``` */ class OverlayTriggerComponent { overlayOrigin; overlayComponent; elementRef; constructor(elementRef) { this.elementRef = elementRef; } /** * Set the parent overlay component reference */ setOverlayComponent(overlay) { this.overlayComponent = overlay; } /** * Handle click events to toggle the overlay */ onClick(event) { event.stopPropagation(); if (this.overlayComponent) { this.overlayComponent.toggle(); } } /** * Get the overlay origin element */ get triggerElement() { return this.overlayOrigin; } /** * Get the native DOM element for width calculations */ get nativeElement() { return this.elementRef.nativeElement; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: OverlayTriggerComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.0", type: OverlayTriggerComponent, isStandalone: true, selector: "st-overlay-trigger", viewQueries: [{ propertyName: "overlayOrigin", first: true, predicate: CdkOverlayOrigin, descendants: true, static: true }], ngImport: i0, template: "<div\n\tcdkOverlayOrigin\n\t#triggerElement=\"cdkOverlayOrigin\"\n\trole=\"button\"\n\ttabindex=\"0\"\n\t(click)=\"onClick($event)\"\n\t(keydown.enter)=\"onClick($event)\"\n\t(keydown.space)=\"onClick($event)\"\n>\n\t<ng-content></ng-content>\n</div>\n", dependencies: [{ kind: "directive", type: CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: OverlayTriggerComponent, decorators: [{ type: Component, args: [{ selector: 'st-overlay-trigger', standalone: true, imports: [CdkOverlayOrigin], template: "<div\n\tcdkOverlayOrigin\n\t#triggerElement=\"cdkOverlayOrigin\"\n\trole=\"button\"\n\ttabindex=\"0\"\n\t(click)=\"onClick($event)\"\n\t(keydown.enter)=\"onClick($event)\"\n\t(keydown.space)=\"onClick($event)\"\n>\n\t<ng-content></ng-content>\n</div>\n" }] }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { overlayOrigin: [{ type: ViewChild, args: [CdkOverlayOrigin, { static: true }] }] } }); /** * Overlay - Shell component for creating custom overlays with positioning * * @remarks * This component provides the infrastructure for creating custom overlays with proper positioning, * accessibility, and animation support. It's designed to be used as a shell where the consumer * has full control over the trigger and content styling. * * The overlay is controlled through methods: Use `open()`, `close()`, and `toggle()` methods programmatically * via template reference variables. * * @example * ```html * <!-- Basic usage --> * <st-overlay> * <st-overlay-trigger> * <button>Trigger</button> * </st-overlay-trigger> * <st-overlay-content> * <div>Content</div> * </st-overlay-content> * </st-overlay> * * <!-- Using methods for programmatic control --> * <st-overlay #overlay="stOverlay"> * <st-overlay-trigger> * <button>Trigger</button> * </st-overlay-trigger> * <st-overlay-content> * <div>Content</div> * <button (click)="overlay.close()">Close</button> * </st-overlay-content> * </st-overlay-content> * </st-overlay> * <button (click)="overlay.open()">Open</button> * <button (click)="overlay.close()">Close</button> * * <!-- Using width sync for dropdowns/selects --> * <st-overlay [syncWidth]="true"> * <st-overlay-trigger> * <button class="w-64">Trigger with specific width</button> * </st-overlay-trigger> * <st-overlay-content> * <div>Content will match trigger width</div> * </st-overlay-content> * </st-overlay> * ``` */ class OverlayComponent { trigger; content; /** Offset X in pixels */ offsetX = input(0); /** Offset Y in pixels */ offsetY = input(0); /** Origin X position */ originX = input('start'); /** Origin Y position */ originY = input('bottom'); /** Overlay X position */ overlayX = input('start'); /** Overlay Y position */ overlayY = input('top'); /** Fallback positions configuration */ fallbacks = input({ enabled: true }); /** Whether to sync the overlay content width with the trigger width */ syncWidth = input(false); /** Internal signal to track overlay state */ _isOpen = signal(false); /** Internal signal to track the synced width */ _syncedWidth = signal(null); /** Current active position configuration */ currentPosition = signal(null); /** Event emitted when overlay is opened */ opened = output(); /** Event emitted when overlay is closed */ closed = output(); constructor() { // Sync width when syncWidth flag changes or overlay opens effect(() => { if (this.syncWidth() && this._isOpen()) { this.updateSyncedWidth(); } else if (!this.syncWidth()) { this._syncedWidth.set(null); } }); } ngAfterContentInit() { // Connect trigger and content after they're available if (this.trigger && this.content) { this.trigger.setOverlayComponent(this); } } /** * Computed positions array based on signal inputs */ positions = computed(() => { // Get current values and ensure they are strings const originX = String(this.originX()); const originY = String(this.originY()); const overlayX = String(this.overlayX()); const overlayY = String(this.overlayY()); const offsetX = Number(this.offsetX()); const offsetY = Number(this.offsetY()); // Primary position using direct signal values const primary = { originX, originY, overlayX, overlayY, offsetX, offsetY, }; // If fallbacks are disabled, return only primary position const fallbackConfig = this.fallbacks(); if (!fallbackConfig.enabled) { return [primary]; } // Use custom fallback positions if provided if (fallbackConfig.positions && fallbackConfig.positions.length > 0) { return [primary, ...fallbackConfig.positions]; } // Generate default fallback positions const fallbacks = this.generateDefaultFallbacks(primary); return [primary, ...fallbacks]; }); /** * Generate default fallback positions based on the primary position */ generateDefaultFallbacks(primary) { const fallbacks = []; const primaryOriginX = primary.originX; const primaryOriginY = primary.originY; const primaryOverlayX = primary.overlayX; const primaryOverlayY = primary.overlayY; // Add basic axis flips this.addAxisFlips(fallbacks, primary, primaryOriginX, primaryOriginY, primaryOverlayX, primaryOverlayY); // Add standard overlay positions this.addStandardPositions(fallbacks, primary); return fallbacks; } /** * Helper to flip Y position */ getOppositeY(position) { if (position === 'top') return 'bottom'; if (position === 'bottom') return 'top'; return 'center'; } /** * Helper to flip X position */ getOppositeX(position) { if (position === 'start') return 'end'; if (position === 'end') return 'start'; return 'center'; } /** * Helper to create position variant */ createPosition(primary, originX, originY, overlayX, overlayY) { return { ...primary, originX, originY, overlayX, overlayY, }; } /** * Add axis flip fallbacks */ addAxisFlips(fallbacks, primary, primaryOriginX, primaryOriginY, primaryOverlayX, primaryOverlayY) { // Flip Y axis const oppositeY = this.getOppositeY(primaryOriginY); const oppositeOverlayY = this.getOppositeY(primaryOverlayY); fallbacks.push(this.createPosition(primary, primaryOriginX, oppositeY, primaryOverlayX, oppositeOverlayY)); // Flip X axis const oppositeX = this.getOppositeX(primaryOriginX); const oppositeOverlayX = this.getOppositeX(primaryOverlayX); fallbacks.push(this.createPosition(primary, oppositeX, primaryOriginY, oppositeOverlayX, primaryOverlayY)); // Flip both axes fallbacks.push(this.createPosition(primary, oppositeX, oppositeY, oppositeOverlayX, oppositeOverlayY)); } /** * Add standard overlay positions */ addStandardPositions(fallbacks, primary) { const standardPositions = [ this.createPosition(primary, 'start', 'bottom', 'start', 'top'), // Classic dropdown this.createPosition(primary, 'start', 'top', 'start', 'bottom'), // Dropup this.createPosition(primary, 'end', 'top', 'start', 'top'), // Left side this.createPosition(primary, 'start', 'top', 'end', 'top'), // Right side this.createPosition(primary, 'center', 'bottom', 'center', 'top'), // Center dropdown this.createPosition(primary, 'center', 'top', 'center', 'bottom'), // Center dropup ]; for (const pos of standardPositions) { if (!this.isPositionDuplicate(pos, [primary, ...fallbacks])) { fallbacks.push(pos); } } } /** * Check if position is duplicate */ isPositionDuplicate(position, existing) { return existing.some((pos) => pos.originX === position.originX && pos.originY === position.originY && pos.overlayX === position.overlayX && pos.overlayY === position.overlayY); } /** * Open the overlay */ open() { if (this._isOpen()) return; this._isOpen.set(true); this.opened.emit(); // Update synced width when opening if syncWidth is enabled if (this.syncWidth()) { // Use setTimeout to ensure DOM is updated after overlay opens setTimeout(() => this.updateSyncedWidth(), 0); } } /** * Close the overlay */ close() { if (!this._isOpen()) return; this._isOpen.set(false); this.closed.emit(); } /** * Toggle the overlay open/close state */ toggle() { if (this._isOpen()) { this.close(); } else { this.open(); } } /** * Update the synced width based on trigger element width */ updateSyncedWidth() { if (!this.trigger?.nativeElement || !this.syncWidth()) { this._syncedWidth.set(null); return; } const triggerElement = this.trigger.nativeElement; const triggerWidth = triggerElement.getBoundingClientRect().width; // Set the width to match the trigger exactly this._syncedWidth.set(`${triggerWidth}px`); } /** * Handle position changes from CDK overlay */ onPositionChange(position) { if (position?.connectionPair) { this.currentPosition.set(position.connectionPair); } } /** * Handle click outside to close overlay */ onClickOutside() { this.close(); } /** * Handle window resize to update synced width */ onWindowResize() { if (this.syncWidth() && this._isOpen()) { // Debounce resize events setTimeout(() => this.updateSyncedWidth(), 50); } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: OverlayComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.0", type: OverlayComponent, isStandalone: true, selector: "st-overlay", inputs: { offsetX: { classPropertyName: "offsetX", publicName: "offsetX", isSignal: true, isRequired: false, transformFunction: null }, offsetY: { classPropertyName: "offsetY", publicName: "offsetY", isSignal: true, isRequired: false, transformFunction: null }, originX: { classPropertyName: "originX", publicName: "originX", isSignal: true, isRequired: false, transformFunction: null }, originY: { classPropertyName: "originY", publicName: "originY", isSignal: true, isRequired: false, transformFunction: null }, overlayX: { classPropertyName: "overlayX", publicName: "overlayX", isSignal: true, isRequired: false, transformFunction: null }, overlayY: { classPropertyName: "overlayY", publicName: "overlayY", isSignal: true, isRequired: false, transformFunction: null }, fallbacks: { classPropertyName: "fallbacks", publicName: "fallbacks", isSignal: true, isRequired: false, transformFunction: null }, syncWidth: { classPropertyName: "syncWidth", publicName: "syncWidth", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { opened: "opened", closed: "closed" }, host: { listeners: { "window:resize": "onWindowResize()" } }, queries: [{ propertyName: "trigger", first: true, predicate: OverlayTriggerComponent, descendants: true }, { propertyName: "content", first: true, predicate: OverlayContentComponent, descendants: true }], exportAs: ["stOverlay"], ngImport: i0, template: "<!-- Trigger Content -->\n@if (trigger) {\n\t<ng-content select=\"st-overlay-trigger\"></ng-content>\n}\n\n<!-- Content Overlay -->\n@if (_isOpen() && trigger && content) {\n\t<ng-template\n\t\tcdkConnectedOverlay\n\t\t[cdkConnectedOverlayOrigin]=\"trigger.triggerElement\"\n\t\t[cdkConnectedOverlayOpen]=\"true\"\n\t\t[cdkConnectedOverlayPositions]=\"positions()\"\n\t\t(detach)=\"close()\"\n\t\t(overlayOutsideClick)=\"onClickOutside()\"\n\t\t(positionChange)=\"onPositionChange($event)\"\n\t>\n\t\t<!-- Content Wrapper with optional width sync -->\n\t\t<div [style.width]=\"_syncedWidth()\" [style.min-width]=\"_syncedWidth() ? _syncedWidth() : null\">\n\t\t\t<!-- Content Template -->\n\t\t\t<ng-container *ngTemplateOutlet=\"content.template\"></ng-container>\n\t\t</div>\n\t</ng-template>\n}\n", dependencies: [{ kind: "directive", type: CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: OverlayComponent, decorators: [{ type: Component, args: [{ selector: 'st-overlay', standalone: true, imports: [CdkConnectedOverlay, NgTemplateOutlet], exportAs: 'stOverlay', template: "<!-- Trigger Content -->\n@if (trigger) {\n\t<ng-content select=\"st-overlay-trigger\"></ng-content>\n}\n\n<!-- Content Overlay -->\n@if (_isOpen() && trigger && content) {\n\t<ng-template\n\t\tcdkConnectedOverlay\n\t\t[cdkConnectedOverlayOrigin]=\"trigger.triggerElement\"\n\t\t[cdkConnectedOverlayOpen]=\"true\"\n\t\t[cdkConnectedOverlayPositions]=\"positions()\"\n\t\t(detach)=\"close()\"\n\t\t(overlayOutsideClick)=\"onClickOutside()\"\n\t\t(positionChange)=\"onPositionChange($event)\"\n\t>\n\t\t<!-- Content Wrapper with optional width sync -->\n\t\t<div [style.width]=\"_syncedWidth()\" [style.min-width]=\"_syncedWidth() ? _syncedWidth() : null\">\n\t\t\t<!-- Content Template -->\n\t\t\t<ng-container *ngTemplateOutlet=\"content.template\"></ng-container>\n\t\t</div>\n\t</ng-template>\n}\n" }] }], ctorParameters: () => [], propDecorators: { trigger: [{ type: ContentChild, args: [OverlayTriggerComponent] }], content: [{ type: ContentChild, args: [OverlayContentComponent] }], onWindowResize: [{ type: HostListener, args: ['window:resize'] }] } }); /** * Generated bundle index. Do not edit. */ export { OverlayComponent, OverlayContentComponent, OverlayTriggerComponent }; //# sourceMappingURL=sixbell-telco-sdk-components-overlay.mjs.map