@sixbell-telco/sdk
Version:
A collection of reusable components designed for use in Sixbell Telco Angular projects
409 lines (403 loc) • 19.8 kB
JavaScript
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