@surface/custom-element
Version:
Provides support of directives and data binding on custom elements.
86 lines (85 loc) • 4.15 kB
JavaScript
import { CancellationTokenSource, DisposableMetadata, assert } from "@surface/core";
import { scheduler } from "../../singletons.js";
import TemplateMetadata from "../metadata/template-metadata.js";
import observe from "../observe.js";
export default class PlaceholdeStatement {
constructor(context) {
this.context = context;
this.cancellationTokenSource = new CancellationTokenSource();
this.currentDisposable = null;
this.disposed = false;
this.key = "";
this.lazyInjectionCancellationTokenSource = new CancellationTokenSource();
this.subscription = null;
this.applyInjection = () => {
const injection = this.metadata.injections.get(this.key);
this.inject(injection);
};
this.applyLazyInjection = () => {
this.lazyInjectionCancellationTokenSource = new CancellationTokenSource();
void scheduler.enqueue(this.applyInjection, "low", this.lazyInjectionCancellationTokenSource.token);
};
this.inject = (injection) => {
this.lazyInjectionCancellationTokenSource.cancel();
this.currentDisposable?.dispose();
this.currentDisposable = null;
this.subscription?.unsubscribe();
this.subscription = null;
const task = (this.injectionContext = injection)
? this.task
: this.defaultTask;
const listener = () => void scheduler.enqueue(task, "normal", this.cancellationTokenSource.token);
this.subscription = observe(this.context.scope, this.context.observables[1], listener, true);
listener();
};
this.onKeyChange = () => {
if (this.key) {
this.metadata.defaults.delete(this.key);
this.metadata.placeholders.delete(this.key);
}
this.key = this.context.key(this.context.scope);
this.metadata.defaults.set(this.key, this.applyLazyInjection);
this.metadata.placeholders.set(this.key, this.inject);
if (this.context.host.isConnected) {
this.applyInjection();
}
else {
this.applyLazyInjection();
}
};
this.task = () => {
assert(this.injectionContext);
this.currentDisposable?.dispose();
this.context.block.clear();
const value = this.context.value(this.context.scope);
const directiveScope = this.injectionContext.pattern(this.context.scope, value);
const scope = { ...this.injectionContext.scope, ...directiveScope };
const [content, activator] = this.injectionContext.factory();
this.context.block.setContent(content);
const disposables = [activator(this.context.parent, this.context.host, scope, new Map()), DisposableMetadata.from(scope)];
this.currentDisposable = { dispose: () => disposables.splice(0).forEach(x => x.dispose()) };
};
this.defaultTask = () => {
this.currentDisposable?.dispose();
this.context.block.clear();
const [content, activator] = this.context.factory();
this.context.block.setContent(content);
this.currentDisposable = activator(this.context.parent, this.context.host, this.context.scope, this.context.directives);
};
this.metadata = TemplateMetadata.from(context.host);
this.keySubscription = observe(context.scope, context.observables[0], this.onKeyChange, true);
this.onKeyChange();
}
dispose() {
if (!this.disposed) {
this.cancellationTokenSource.cancel();
this.currentDisposable?.dispose();
this.keySubscription.unsubscribe();
this.subscription.unsubscribe();
this.metadata.defaults.delete(this.key);
this.metadata.placeholders.delete(this.key);
this.context.block.dispose();
this.disposed = true;
}
}
}