@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
JavaScript
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