UNPKG

aspen-decorations

Version:

Complex styling for react-aspen w/ inheritance and negations

353 lines 16.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const notificar_1 = require("notificar"); const types_1 = require("./types"); /** * The "consumer level" part of Decoration * * `ClasslistComposite` contains composited classnames from all the decorations applicable to a target * * The composite includes all the applicable inheritances and negations. * * Note that due to performance considerations, `ClasslistComposite` is one of the few cases where * we *do not* use `Disposables` for event management and instead use old school `addChangeListener`/`removeChangeListener` methods. * * Remember to pass same **named** function reference to `addChangeListener` and `removeChangeLiseneer` while subscribing and unsusbscribing * in `componentDidMount` and `componentWillUnmount` respectively. * * Note: You should also implement a `componentDidUpdate` hook where you can unsubscribe from previous decoration object and subscribe to the new one. */ class ClasslistComposite { /** @internal */ constructor( /** * Registers a function to be called when composited classlist changes * * *⚠ Remember not to use anonymous function!! (pass a named function reference instead)* */ addChangeListener, /** * Unregisters a previsously registered classlist change listener * * *⚠ Remember not to use anonymous function!! (pass a named function reference instead)* */ removeChangeListener) { this.addChangeListener = addChangeListener; this.removeChangeListener = removeChangeListener; } } exports.ClasslistComposite = ClasslistComposite; var DecorationCompositeType; (function (DecorationCompositeType) { DecorationCompositeType[DecorationCompositeType["Applicable"] = 1] = "Applicable"; DecorationCompositeType[DecorationCompositeType["Inheritable"] = 2] = "Inheritable"; })(DecorationCompositeType = exports.DecorationCompositeType || (exports.DecorationCompositeType = {})); var ChangeReason; (function (ChangeReason) { ChangeReason[ChangeReason["UnTargetDecoration"] = 1] = "UnTargetDecoration"; ChangeReason[ChangeReason["TargetDecoration"] = 2] = "TargetDecoration"; })(ChangeReason = exports.ChangeReason || (exports.ChangeReason = {})); /** * Compositer for decorations * * When multiple `Decoration`s are applied to a target, they get grouped into a `DecorationComposite` * * @internal */ class DecorationComposite { constructor(target, type, parent) { this.handleDecorationDidAddClassname = (decoration, classname) => { if (!this.selfOwned) { throw new Error(`[INTERNAL] A non-self owned composite must not be incharge of Decoration events`); } this.compositeCssClasslist.classlist.push(classname); this.notifyClasslistChange(); }; this.handleDecorationDidRemoveClassname = (decoration, classname) => { if (!this.selfOwned) { throw new Error(`[INTERNAL] A non-self owned composite must not be incharge of Decoration events`); } const idx = this.compositeCssClasslist.classlist.indexOf(classname); if (idx > -1) { this.compositeCssClasslist.classlist.splice(idx, 1); this.notifyClasslistChange(); } }; this.mergeDecorationClasslist = (decoration) => { if (!this.selfOwned) { throw new Error(`[INTERNAL] A non-self owned composite must not be incharge of Decoration events`); } this.compositeCssClasslist.classlist.push(...decoration.cssClasslist); this.notifyClasslistChange(); }; this.target = target; this.type = type; this.linkedComposites = new Set(); this.classlistChangeCallbacks = new Set(); this.compositeCssClasslist = new ClasslistComposite(this.classlistChangeCallbacks.add.bind(this.classlistChangeCallbacks), this.classlistChangeCallbacks.delete.bind(this.classlistChangeCallbacks)); if (parent) { this.selfOwned = false; this.parent = parent; this.renderedDecorations = parent.renderedDecorations; this.compositeCssClasslist.classlist = parent.compositeCssClasslist.classlist; parent.linkedComposites.add(this); } else { this.renderedDecorations = new Map(); this.targetedDecorations = new Set(); this.negatedDecorations = new Set(); this.compositeCssClasslist.classlist = []; this.selfOwned = true; } } changeParent(newParent) { if (!this.selfOwned) { return this.parentOwn(newParent); } // first purge all the decorations (unless applicable) for (const [decoration] of this.renderedDecorations) { this.recursiveRefresh(this, false, ChangeReason.UnTargetDecoration, decoration, false); } if (this.parent !== newParent) { this.parent.linkedComposites.delete(this); this.parent = newParent; newParent.linkedComposites.add(this); } // then add all the inherited decorations (unless not applicable) for (const [decoration] of newParent.renderedDecorations) { this.recursiveRefresh(this, false, ChangeReason.TargetDecoration, decoration, false); } } add(decoration) { const applicationMode = decoration.appliedTargets.get(this.target); const applicableToSelf = applicationMode && (applicationMode === types_1.TargetMatchMode.Self || applicationMode === types_1.TargetMatchMode.SelfAndChildren); const applicableToChildren = applicationMode && (applicationMode === types_1.TargetMatchMode.Children || applicationMode === types_1.TargetMatchMode.SelfAndChildren); if (this.type === DecorationCompositeType.Applicable && !applicableToSelf) { return; } if (this.type === DecorationCompositeType.Inheritable && !applicableToChildren) { return; } if (!this.selfOwned) { this.selfOwn(ChangeReason.TargetDecoration, decoration); this.targetedDecorations.add(decoration); return; } if (this.targetedDecorations.has(decoration)) { return; } this.targetedDecorations.add(decoration); this.recursiveRefresh(this, false, ChangeReason.TargetDecoration, decoration); } remove(decoration) { // a non-self owned composite wouldn't have had a decoration to begin with if (!this.selfOwned) { return; } if (this.targetedDecorations.delete(decoration)) { if (this.negatedDecorations.size === 0 && this.targetedDecorations.size === 0 && this.parent) { return this.parentOwn(null, ChangeReason.UnTargetDecoration, decoration); } this.recursiveRefresh(this, false, ChangeReason.UnTargetDecoration, decoration); } } negate(decoration) { const negationMode = decoration.negatedTargets.get(this.target); const negatedOnSelf = negationMode && (negationMode === types_1.TargetMatchMode.Self || negationMode === types_1.TargetMatchMode.SelfAndChildren); const negatedOnChildren = negationMode && (negationMode === types_1.TargetMatchMode.Children || negationMode === types_1.TargetMatchMode.SelfAndChildren); if (this.type === DecorationCompositeType.Applicable && !negatedOnSelf) { return; } if (this.type === DecorationCompositeType.Inheritable && !negatedOnChildren) { return; } if (!this.selfOwned) { this.selfOwn(ChangeReason.UnTargetDecoration, decoration); this.negatedDecorations.add(decoration); return; } if (this.negatedDecorations.has(decoration)) { return; } this.negatedDecorations.add(decoration); if (this.renderedDecorations.has(decoration)) { this.removeDecorationClasslist(decoration); } } /** * unNegate doesn't mean "explicit apply" */ unNegate(decoration) { // a non-self owned composite wouldn't have been negated to begin with if (!this.selfOwned) { return; } if (this.negatedDecorations.delete(decoration)) { if (this.negatedDecorations.size === 0 && this.targetedDecorations.size === 0 && this.parent) { return this.parentOwn(); } // currently not present if (!this.renderedDecorations.has(decoration) && // **and** either parent or itself has it applied (this.parent.renderedDecorations.has(decoration) || decoration.appliedTargets.has(this.target))) { this.recursiveRefresh(this, false, ChangeReason.TargetDecoration, decoration); } } } selfOwn(reason, decoration) { if (this.selfOwned) { throw new Error(`DecorationComposite is already self owned`); } const parent = this.parent; this.selfOwned = true; this.compositeCssClasslist.classlist = []; this.renderedDecorations = new Map(); this.targetedDecorations = new Set(); this.negatedDecorations = new Set(); // first process all the inherited decorations for (const [inheritedDecoration] of parent.renderedDecorations) { // fate of the decoration (second arg) will be decided in `#recursiveRefresh` if (inheritedDecoration !== decoration) { this.processCompositeAlteration(ChangeReason.TargetDecoration, inheritedDecoration); } } // perhaps negation is why this composite branched off if (reason === ChangeReason.UnTargetDecoration && // parent had it this.parent.renderedDecorations.has(decoration) && // this one won't !this.renderedDecorations.has(decoration)) { // announce the change this.notifyClasslistChange(false); } // then move on to main business this.recursiveRefresh(this, true, reason, decoration); } parentOwn(newParent, reason, decoration) { this.selfOwned = false; this.targetedDecorations = void 0; this.negatedDecorations = void 0; if (newParent && this.parent !== newParent) { if (this.parent) { this.parent.linkedComposites.delete(this); } newParent.linkedComposites.add(this); this.parent = newParent; } this.recursiveRefresh(this, true, reason, decoration); } processCompositeAlteration(reason, decoration) { if (!this.selfOwned) { throw new Error(`DecorationComposite is not self owned`); } if (reason === ChangeReason.UnTargetDecoration) { const disposable = this.renderedDecorations.get(decoration); if (disposable) { const applicationMode = decoration.appliedTargets.get(this.target); const applicableToSelf = applicationMode && (applicationMode === types_1.TargetMatchMode.Self || applicationMode === types_1.TargetMatchMode.SelfAndChildren); const applicableToChildren = applicationMode && (applicationMode === types_1.TargetMatchMode.Children || applicationMode === types_1.TargetMatchMode.SelfAndChildren); if (applicableToSelf && this.type === DecorationCompositeType.Applicable) { return false; } if (applicableToChildren && this.type === DecorationCompositeType.Inheritable) { return false; } this.removeDecorationClasslist(decoration, false); if (disposable) { disposable.dispose(); } return this.renderedDecorations.delete(decoration); } return false; } if (reason === ChangeReason.TargetDecoration) { const negationMode = decoration.negatedTargets.get(this.target); const negatedOnSelf = negationMode && (negationMode === types_1.TargetMatchMode.Self || negationMode === types_1.TargetMatchMode.SelfAndChildren); const negatedOnChildren = negationMode && (negationMode === types_1.TargetMatchMode.Children || negationMode === types_1.TargetMatchMode.SelfAndChildren); if (negatedOnSelf && this.type === DecorationCompositeType.Applicable) { return; } if (negatedOnChildren && this.type === DecorationCompositeType.Inheritable) { return; } if (!this.renderedDecorations.has(decoration)) { const disposables = new notificar_1.DisposablesComposite(); disposables.add(decoration.onDidAddCSSClassname(this.handleDecorationDidAddClassname)); disposables.add(decoration.onDidRemoveCSSClassname(this.handleDecorationDidRemoveClassname)); disposables.add(decoration.onDidDisableDecoration(this.removeDecorationClasslist)); disposables.add(decoration.onDidEnableDecoration(this.mergeDecorationClasslist)); this.renderedDecorations.set(decoration, disposables); if (!decoration.disabled) { this.compositeCssClasslist.classlist.push(...decoration.cssClasslist); return true; } return false; } } } recursiveRefresh(origin, updateReferences, reason, decoration, notifyListeners = true) { // references changed if (!this.selfOwned && updateReferences) { this.renderedDecorations = this.parent.renderedDecorations; this.compositeCssClasslist.classlist = this.parent.compositeCssClasslist.classlist; } if (this.selfOwned && updateReferences && origin !== this) { // purge all the decorations (unless applicable) for (const [renderedDecoration] of this.renderedDecorations) { this.processCompositeAlteration(ChangeReason.UnTargetDecoration, renderedDecoration); } // then add all the inherited decorations (unless not applicable) for (const [inheritedDecoration] of this.parent.renderedDecorations) { this.processCompositeAlteration(ChangeReason.TargetDecoration, inheritedDecoration); } if (notifyListeners) { this.notifyClasslistChange(false); } } else if (this.selfOwned && reason === ChangeReason.UnTargetDecoration && this.renderedDecorations.has(decoration)) { this.processCompositeAlteration(reason, decoration); if (notifyListeners) { this.notifyClasslistChange(false); } } else if (this.selfOwned && reason === ChangeReason.TargetDecoration && this.processCompositeAlteration(reason, decoration) && notifyListeners) { this.notifyClasslistChange(false); } else if (!this.selfOwned && notifyListeners) { this.notifyClasslistChange(false); } for (const linkedComposite of this.linkedComposites) { linkedComposite.recursiveRefresh(origin, updateReferences, reason, decoration, notifyListeners); } } removeDecorationClasslist(decoration, notifyAll = true) { if (!this.selfOwned) { throw new Error(`[INTERNAL] A non-self owned composite must not be incharge of Decoration events`); } for (const classname of decoration.cssClasslist) { const idx = this.compositeCssClasslist.classlist.indexOf(classname); if (idx > -1) { this.compositeCssClasslist.classlist.splice(idx, 1); } } if (notifyAll) { this.notifyClasslistChange(); } } notifyClasslistChange(recursive = true) { // here it's important that we don't iterate directly over this.classlistChangeCallbacks, instead create a copy of them first // if one of the callbacks alters the Set by adding/removing callback (inside another callback) it makes this loop infinite for (const cb of [...this.classlistChangeCallbacks]) { cb(); } if (recursive) { for (const linkedComposite of this.linkedComposites) { if (!linkedComposite.selfOwned) { linkedComposite.notifyClasslistChange(); } } } } } exports.DecorationComposite = DecorationComposite; //# sourceMappingURL=DecorationComposite.js.map