mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-10 03:32:52 +00:00
169 lines
4.3 KiB
TypeScript
169 lines
4.3 KiB
TypeScript
/*
|
|
* Copyright (C) 2022 StApps
|
|
* This program is free software: you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the Free
|
|
* Software Foundation, version 3.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
import {
|
|
ComponentRef,
|
|
Directive,
|
|
ElementRef,
|
|
OnDestroy,
|
|
OnInit,
|
|
ViewContainerRef,
|
|
} from '@angular/core';
|
|
import {IonIcon} from '@ionic/angular';
|
|
import {IonIconDirective} from './ion-icon.directive';
|
|
|
|
export type IconData = Omit<
|
|
Partial<IonIconDirective>,
|
|
| 'ngOnChanges'
|
|
| 'ngOnInit'
|
|
| 'viewContainerRef'
|
|
| 'ngOnDestroy'
|
|
| 'element'
|
|
| 'ionIcon'
|
|
| 'disableProperty'
|
|
>;
|
|
|
|
/**
|
|
* A utility class to replace ion-icons in other ionic components.
|
|
*/
|
|
@Directive()
|
|
export abstract class IconReplacer implements OnInit, OnDestroy {
|
|
private mutationObserver: MutationObserver;
|
|
|
|
protected slotName = 'sc-icon';
|
|
|
|
protected maxAttempts = 10;
|
|
|
|
protected retryAfterMs = 10;
|
|
|
|
/**
|
|
* The host element
|
|
*
|
|
* This will be either element.nativeElement.shadowRoot or element.nativeElement
|
|
* depending on the iconDomLocation
|
|
*/
|
|
protected get host() {
|
|
return this.iconDomLocation === 'shadow'
|
|
? this.element.nativeElement.shadowRoot
|
|
: this.element.nativeElement;
|
|
}
|
|
|
|
/**
|
|
* @param element The host element
|
|
* @param viewContainerRef The view container ref
|
|
* @param iconDomLocation If the icon is placed inside the shadow dom or not
|
|
* @protected
|
|
*/
|
|
protected constructor(
|
|
private readonly element: ElementRef,
|
|
private readonly viewContainerRef: ViewContainerRef,
|
|
private readonly iconDomLocation: 'shadow' | 'light',
|
|
) {}
|
|
|
|
/**
|
|
* Replace the icons here
|
|
*/
|
|
abstract replace(): void;
|
|
|
|
/**
|
|
* If any additional work needs to be done, this
|
|
* is called during ngOnInit
|
|
*/
|
|
init() {
|
|
// noop
|
|
}
|
|
|
|
/**
|
|
* If you need to do cleanup, this method is called during ngOnDestroy
|
|
*/
|
|
destroy() {
|
|
// noop
|
|
}
|
|
|
|
ngOnInit() {
|
|
this.init();
|
|
|
|
if (!this.host) {
|
|
let tries = 0;
|
|
console.warn('IconReplacer: host not found, trying again');
|
|
const interval = setInterval(() => {
|
|
if (tries > this.maxAttempts) {
|
|
clearInterval(interval);
|
|
throw new Error('IconReplacer: host not found');
|
|
}
|
|
if (this.host) {
|
|
clearInterval(interval);
|
|
this.replace();
|
|
}
|
|
tries++;
|
|
}, this.retryAfterMs);
|
|
} else {
|
|
this.attachObserver();
|
|
}
|
|
}
|
|
|
|
private attachObserver() {
|
|
this.mutationObserver = new MutationObserver(() => this.replace());
|
|
this.mutationObserver.observe(this.host, {
|
|
childList: true,
|
|
});
|
|
}
|
|
|
|
replaceIcon(parent: HTMLElement | null, iconData: IconData, slotName = '') {
|
|
if (!parent) return;
|
|
|
|
const icon = parent.querySelector('ion-icon');
|
|
if (!icon) return;
|
|
|
|
const scIcon = this.createIcon(iconData);
|
|
// @ts-expect-error can be spread
|
|
scIcon.location.nativeElement.classList.add(...icon.classList);
|
|
|
|
if (this.iconDomLocation === 'shadow') {
|
|
// shadow dom needs to utilize slotting, to put it outside
|
|
// the shadow dom, otherwise it won't receive any css data
|
|
const slot = document.createElement('slot');
|
|
slot.name = this.slotName + slotName;
|
|
icon.replaceWith(slot);
|
|
|
|
scIcon.location.nativeElement.slot = this.slotName + slotName;
|
|
this.element.nativeElement.append(scIcon.location.nativeElement);
|
|
} else {
|
|
icon.replaceWith(scIcon.location.nativeElement);
|
|
}
|
|
}
|
|
|
|
private createIcon(iconData: IconData): ComponentRef<IonIcon> {
|
|
const ionIcon = this.viewContainerRef.createComponent(IonIcon, {});
|
|
const iconDirective = new IonIconDirective(
|
|
ionIcon.location,
|
|
this.viewContainerRef,
|
|
ionIcon.instance,
|
|
);
|
|
for (const key in iconData) {
|
|
// @ts-expect-error type mismatch
|
|
iconDirective[key] = iconData[key];
|
|
}
|
|
iconDirective.ngOnInit();
|
|
|
|
return ionIcon;
|
|
}
|
|
|
|
ngOnDestroy() {
|
|
this.mutationObserver?.disconnect();
|
|
this.destroy();
|
|
}
|
|
}
|