UNPKG

@cradokski/fullscreen

Version:

An Angular service and **directives** that provide **fullscreen control**, **reactive state**, and **template-friendly utilities**.

327 lines (316 loc) 14.8 kB
import * as i0 from '@angular/core'; import { Injectable, Directive, HostListener, NgModule } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; import { CommonModule } from '@angular/common'; class FullScreenService { constructor(zone) { this.zone = zone; /** Internal flag for synchronous API compatibility */ this.isFullscreenFlag = false; /** Timestamp for double-tap detection */ this.lastTapTime = null; /** For testing: allows mocking time */ this.nowFn = () => Date.now(); /** RxJS state observable for template bindings */ this.fullscreen$ = new BehaviorSubject({ active: false, element: null }); // --------------------------------------------------------------------- // DOM fullscreen event handler // --------------------------------------------------------------------- this.handleFullscreenEvent = () => { const el = this.getFullscreenElement(); const isFs = !!el; this.isFullscreenFlag = isFs; this.zone.run(() => { this.fullscreen$.next({ active: isFs, element: el }); }); }; [ 'fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange' ].forEach(event => document.addEventListener(event, this.handleFullscreenEvent)); } // --------------------------------------------------------------------- // Internal helpers // --------------------------------------------------------------------- /** Returns the currently fullscreen element (cross-browser) */ getFullscreenElement() { return (document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement || null); } /** Request fullscreen on a specific element (default: <html>) */ async enterFullscreenElement(element = document.documentElement) { const request = element.requestFullscreen ?? element.webkitRequestFullscreen ?? element.mozRequestFullScreen ?? element.msRequestFullscreen; if (!request) { throw new Error('Fullscreen API not supported'); } await request.call(element); return 'entered'; } /** Exit fullscreen (cross-browser) */ async exitFullscreenElement() { const exit = document.exitFullscreen ?? document.webkitExitFullscreen ?? document.mozCancelFullScreen ?? document.msExitFullscreen; if (!exit) { throw new Error('Fullscreen API not supported'); } await exit.call(document); return 'exited'; } /** Sync check used by directives and consumers */ isFullscreenActive() { const active = !!this.getFullscreenElement(); this.isFullscreenFlag = active; return active; } // --------------------------------------------------------------------- // Public API (backwards compatible) // --------------------------------------------------------------------- enterFullscreen(element) { this.enterFullscreenElement(element) .then(() => (this.isFullscreenFlag = true)) .catch(() => { }); } exitFullscreen() { this.exitFullscreenElement() .then(() => (this.isFullscreenFlag = false)) .catch(() => { }); } toggleFullscreen(element) { return this.isFullscreenActive() ? this.exitFullscreenElement() : this.enterFullscreenElement(element); } ensureFullscreen(element) { return this.isFullscreenActive() ? Promise.resolve('entered') : this.enterFullscreenElement(element); } ensureExitedFullscreen() { return this.isFullscreenActive() ? this.exitFullscreenElement() : Promise.resolve('exited'); } // --------------------------------------------------------------------- // Double-tap browser toggle // --------------------------------------------------------------------- browserFsToggle(options) { this.browserFsToggleAsync(options); return this.isFullscreenFlag; } async browserFsToggleAsync(options) { const delayMs = options?.delayMs ?? 300; if (options?.requireDoubleTap) { const now = this.nowFn(); if (this.lastTapTime && now - this.lastTapTime < delayMs) { this.lastTapTime = 0; const status = await this.toggleFullscreen(); return status === 'entered'; } this.lastTapTime = now; return false; } const status = await this.toggleFullscreen(); return status === 'entered'; } // --------------------------------------------------------------------- // Watch API (unified) // --------------------------------------------------------------------- /** Subscribe to fullscreen changes; returns unwatch() */ watchFullscreen(callback) { const sub = this.fullscreen$.subscribe(({ active, element }) => callback(active, element)); return () => sub.unsubscribe(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FullScreenService, deps: [{ token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FullScreenService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FullScreenService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: i0.NgZone }] }); class FsToggleDirective { constructor(host, fs) { this.host = host; this.fs = fs; } onClick() { const el = this.host.nativeElement; this.fs.toggleFullscreen(el); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FsToggleDirective, deps: [{ token: i0.ElementRef }, { token: FullScreenService }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: FsToggleDirective, isStandalone: true, selector: "[fsToggle]", host: { listeners: { "click": "onClick()" } }, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FsToggleDirective, decorators: [{ type: Directive, args: [{ standalone: true, selector: '[fsToggle]' }] }], ctorParameters: () => [{ type: i0.ElementRef }, { type: FullScreenService }], propDecorators: { onClick: [{ type: HostListener, args: ['click'] }] } }); class FsExitDirective { constructor(fs) { this.fs = fs; } onClick() { this.fs.ensureExitedFullscreen(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FsExitDirective, deps: [{ token: FullScreenService }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: FsExitDirective, isStandalone: true, selector: "[fsExit]", host: { listeners: { "click": "onClick()" } }, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FsExitDirective, decorators: [{ type: Directive, args: [{ standalone: true, selector: '[fsExit]' }] }], ctorParameters: () => [{ type: FullScreenService }], propDecorators: { onClick: [{ type: HostListener, args: ['click'] }] } }); class FsDoubleTapDirective { constructor(fs) { this.fs = fs; } onClick() { this.fs.browserFsToggle({ requireDoubleTap: true }); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FsDoubleTapDirective, deps: [{ token: FullScreenService }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: FsDoubleTapDirective, isStandalone: true, selector: "[fsDoubleTap]", host: { listeners: { "click": "onClick()" } }, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FsDoubleTapDirective, decorators: [{ type: Directive, args: [{ standalone: true, selector: '[fsDoubleTap]' }] }], ctorParameters: () => [{ type: FullScreenService }], propDecorators: { onClick: [{ type: HostListener, args: ['click'] }] } }); class IfFullscreenDirective { constructor(tpl, vcr, fs) { this.tpl = tpl; this.vcr = vcr; this.fs = fs; this.isShown = false; } ngOnInit() { console.log('IfFullscreenDirective init'); this.sub = this.fs.fullscreen$.subscribe(status => { if (status.active && !this.isShown) { this.vcr.createEmbeddedView(this.tpl); this.isShown = true; console.log('IfFullscreenDirective active && !this.isShown'); } else if (!status.active && this.isShown) { this.vcr.clear(); this.isShown = false; console.log('IfFullscreenDirective !active && this.isShown'); } else console.log('IfFullscreenDirective no change'); }); } ngOnDestroy() { this.sub?.unsubscribe(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: IfFullscreenDirective, deps: [{ token: i0.TemplateRef }, { token: i0.ViewContainerRef }, { token: FullScreenService }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: IfFullscreenDirective, isStandalone: true, selector: "[ifFullscreen]", ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: IfFullscreenDirective, decorators: [{ type: Directive, args: [{ standalone: true, selector: '[ifFullscreen]' }] }], ctorParameters: () => [{ type: i0.TemplateRef }, { type: i0.ViewContainerRef }, { type: FullScreenService }] }); class FsActiveClassDirective { constructor(host, renderer, fs) { this.host = host; this.renderer = renderer; this.fs = fs; this.unwatch = () => { }; } ngOnInit() { console.log('FsActiveClassDirective init', this.host.nativeElement); this.unwatch = this.fs.watchFullscreen((active, el) => { const isHostFs = active; console.log('ngOnInit isHostFs', isHostFs, " el =[", el, "] host.native` = [", this.host.nativeElement, "]"); if (isHostFs) { this.renderer.addClass(this.host.nativeElement, 'fs-active'); } else { this.renderer.removeClass(this.host.nativeElement, 'fs-active'); } }); } ngOnDestroy() { this.unwatch(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FsActiveClassDirective, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }, { token: FullScreenService }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: FsActiveClassDirective, isStandalone: true, selector: "[fsActiveClass]", ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FsActiveClassDirective, decorators: [{ type: Directive, args: [{ standalone: true, selector: '[fsActiveClass]' }] }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }, { type: FullScreenService }] }); class FullscreenModule { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FullscreenModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.14", ngImport: i0, type: FullscreenModule, imports: [CommonModule, FsToggleDirective, FsExitDirective, FsDoubleTapDirective, IfFullscreenDirective, FsActiveClassDirective], exports: [FsToggleDirective, FsExitDirective, FsDoubleTapDirective, IfFullscreenDirective, FsActiveClassDirective] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FullscreenModule, imports: [CommonModule] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FullscreenModule, decorators: [{ type: NgModule, args: [{ imports: [ CommonModule, FsToggleDirective, FsExitDirective, FsDoubleTapDirective, IfFullscreenDirective, FsActiveClassDirective ], exports: [ FsToggleDirective, FsExitDirective, FsDoubleTapDirective, IfFullscreenDirective, FsActiveClassDirective ] }] }] }); /* * Public API Surface of fullscreen */ /** * Generated bundle index. Do not edit. */ export { FsActiveClassDirective, FsDoubleTapDirective, FsExitDirective, FsToggleDirective, FullScreenService, FullscreenModule, IfFullscreenDirective }; //# sourceMappingURL=cradokski-fullscreen.mjs.map