ngx-fixed-footer
Version:
Angular directive that adds fixed footer without overlap
129 lines (122 loc) • 6.04 kB
JavaScript
import * as i0 from '@angular/core';
import { InjectionToken, inject, DOCUMENT, ElementRef, Renderer2, signal, input, computed, effect, Directive } from '@angular/core';
const DEFAULT_CSS_ATTRIBUTE = 'padding';
const DEFAULT_CONTAINER_SELECTOR = '[role="main"]';
const DEFAULT_FIXED_FOOTER_OPTIONS = {
containerSelector: DEFAULT_CONTAINER_SELECTOR,
cssAttribute: DEFAULT_CSS_ATTRIBUTE
};
const APP_FIXED_FOOTER_OPTIONS_TOKEN = new InjectionToken('[ngxFixedFooter] Options');
const provideFixedFooter = (options) => {
return {
provide: APP_FIXED_FOOTER_OPTIONS_TOKEN,
useValue: {
cssAttribute: options.cssAttribute || DEFAULT_CSS_ATTRIBUTE,
containerSelector: options.containerSelector || DEFAULT_CONTAINER_SELECTOR
}
};
};
class NgxFixedFooterDirective {
document = inject(DOCUMENT);
el = inject(ElementRef);
render = inject(Renderer2);
options = inject(APP_FIXED_FOOTER_OPTIONS_TOKEN, { optional: true }) || DEFAULT_FIXED_FOOTER_OPTIONS;
hasResizeObserver = typeof ResizeObserver !== 'undefined';
resizeObserver;
offsetHeight = signal(undefined, ...(ngDevMode ? [{ debugName: "offsetHeight" }] : []));
prevContainerSelector = signal(undefined, ...(ngDevMode ? [{ debugName: "prevContainerSelector" }] : []));
containerSelector = input(this.options.containerSelector, ...(ngDevMode ? [{ debugName: "containerSelector" }] : []));
cssAttribute = input(this.options.cssAttribute, ...(ngDevMode ? [{ debugName: "cssAttribute" }] : []));
container = computed(() => {
const selector = this.containerSelector() || this.options.containerSelector;
return this.document.body.querySelector(selector);
}, ...(ngDevMode ? [{ debugName: "container" }] : []));
constructor() {
// swap selector
effect(() => {
if (!this.hasResizeObserver || !this.document)
return;
const containerSelector = this.containerSelector();
const offsetHeight = this.offsetHeight();
if (!containerSelector || typeof offsetHeight !== 'number')
return;
const cssAttribute = this.cssAttribute();
const prevContainerSelector = this.prevContainerSelector();
if (prevContainerSelector && prevContainerSelector !== containerSelector) {
const prevContainer = this.document.body.querySelector(prevContainerSelector);
if (prevContainer) {
this.removeStyle(prevContainer, cssAttribute);
}
}
const container = this.document.body.querySelector(containerSelector);
if (container) {
this.setStyle(container, cssAttribute, offsetHeight);
this.prevContainerSelector.set(containerSelector);
}
});
// swap css attribute
effect(() => {
if (!this.hasResizeObserver || !this.document)
return;
const container = this.container();
const offsetHeight = this.offsetHeight();
if (!container || typeof offsetHeight !== 'number')
return;
const cssAttribute = this.cssAttribute();
this.removeStyle(container, cssAttribute === 'padding' ? 'margin' : 'padding');
this.setStyle(container, cssAttribute, offsetHeight);
});
}
ngOnInit() {
if (this.hasResizeObserver && this.document) {
this.resizeObserver = new ResizeObserver(() => this.checkHeight());
this.resizeObserver.observe(this.html);
}
}
ngOnDestroy() {
const container = this.container();
if (this.resizeObserver && this.document && container) {
this.removeStyle(container, this.cssAttribute());
this.resizeObserver.unobserve(this.html);
}
}
checkHeight() {
const height = this.html.offsetHeight;
const container = this.container();
if (this.offsetHeight() !== height && container) {
this.setStyle(container, this.cssAttribute(), height);
this.offsetHeight.set(height);
}
}
removeStyle(container, cssAttribute) {
if (!container) {
throw new Error(`Cannot removeStyle to undefined container`);
}
this.render.setStyle(container, `${cssAttribute}-bottom`, '');
}
setStyle(container, cssAttribute, height) {
if (!container) {
throw new Error(`Cannot setStyle to undefined container`);
}
this.render.setStyle(container, `${cssAttribute}-bottom`, height === 0 ? '' : `${height}px`);
}
get html() {
return this.el.nativeElement;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: NgxFixedFooterDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.1.7", type: NgxFixedFooterDirective, isStandalone: true, selector: "[ngxFixedFooter]", inputs: { containerSelector: { classPropertyName: "containerSelector", publicName: "containerSelector", isSignal: true, isRequired: false, transformFunction: null }, cssAttribute: { classPropertyName: "cssAttribute", publicName: "cssAttribute", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: NgxFixedFooterDirective, decorators: [{
type: Directive,
args: [{
selector: '[ngxFixedFooter]'
}]
}], ctorParameters: () => [] });
/*
* Public API Surface of ngx-fixed-footer
*/
/**
* Generated bundle index. Do not edit.
*/
export { APP_FIXED_FOOTER_OPTIONS_TOKEN, DEFAULT_CONTAINER_SELECTOR, DEFAULT_CSS_ATTRIBUTE, DEFAULT_FIXED_FOOTER_OPTIONS, NgxFixedFooterDirective, provideFixedFooter };
//# sourceMappingURL=ngx-fixed-footer.mjs.map