UNPKG

@surface/custom-element

Version:

Provides support of directives and data binding on custom elements.

86 lines (85 loc) 4.15 kB
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; } } }