/*
* This file is part of AUX.
*
* AUX 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; either
* version 3 of the License, or (at your option) any later version.
*
* AUX 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, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
import { warn } from './utils/log.js';
// Map<Node, Map<string, OptionsComponent>>
const optionsChildren = new Map();
function findOptions(parent, name) {
for (; parent; parent = parent.parentNode) {
const tmp = optionsChildren.get(parent);
if (tmp && tmp.has(name)) return tmp.get(name);
}
return null;
}
class OptionsSubscriber {
constructor(parent, name, callback) {
this.parent = parent;
this.name = name;
this.callback = callback;
this.options = void 0;
this.update();
}
update() {
const options = findOptions(this.parent, this.name);
if (options !== this.options) {
this.options = options;
const cb = this.callback;
try {
cb(options);
} catch (err) {
warn('Subscriber of AUX-OPTIONS generated an error %o', err);
}
}
}
}
// Map<String, Set<OptionsSubscriber>>
const optionsSubscribers = new Map();
function triggerUpdate(name) {
const subscribers = optionsSubscribers.get(name);
if (!subscribers) return;
subscribers.forEach((subscriber) => subscriber.update());
}
function normalizeParent(parent) {
if (parent.tagName === 'HEAD' || parent.tagName === 'BODY') {
parent = parent.parentNode;
}
return parent;
}
export function registerOptions(parent, name, options) {
parent = normalizeParent(parent);
let tmp = optionsChildren.get(parent);
if (!tmp) {
optionsChildren.set(parent, (tmp = new Map()));
}
if (tmp.has(name)) {
throw new Error('AUX-OPTIONS with name ' + name + ' defined twice.');
}
tmp.set(name, options);
triggerUpdate(name);
}
export function unregisterOptions(parent, name, options) {
parent = normalizeParent(parent);
const tmp = optionsChildren.get(parent);
if (!tmp) {
throw new Error('Unknown AUX-OPTIONS');
}
if (tmp.get(name) !== options) {
throw new Error('Found wrong AUX-OPTIONS in unregisterOptions');
}
tmp.delete(name);
triggerUpdate(name);
}
/**
* Subscribe to the set of options defined in the nearest AUX-OPTIONS component
* of a given name and parent.
*
* @param {Node} parent - Parent node. This is usually the parent node of the
* component which references a given set of options.
* @param {String} name - Options name.
* @param {Function} callback - Callback to call when a set of options become
* available. Will be called with null as long as no options set can be
* found.
* @returns {Function} - Returns a function which must be called in order to
* unsubscribe from the options.
*/
export function subscribeOptions(parent, name, callback) {
parent = normalizeParent(parent);
let subscribers = optionsSubscribers.get(name);
if (!subscribers) {
optionsSubscribers.set(name, (subscribers = new Set()));
}
const subscriber = new OptionsSubscriber(parent, name, callback);
subscribers.add(subscriber);
return () => {
subscribers.delete(subscriber);
};
}
/**
* Subscribe to the set of attributes defined by the inheritance chain of
* AUX-OPTIONS of a given name and parent.
*
* @param {Node} parent - Parent node. This is usually the parent node of the
* component which references a given set of options.
* @param {String} name - Options name.
* @param {Function} callback - Callback to call when a set of options become
* available. Will be called with a Map of attributes.
* @returns {Function} - Returns a function which must be called in order to
* unsubscribe from the options.
*/
export function subscribeOptionsAttributes(parent, name, callback) {
let current_options = null;
const attributesChangedCallback = () => {
const attr = current_options ? current_options.auxAttributes() : null;
try {
callback(attr);
} catch (err) {
warn('OptionsAttributes subscriber generated an exception %o', err);
}
};
const subs = subscribeOptions(parent, name, (options) => {
if (current_options) {
current_options.removeEventListener(
'auxAttributesChanged',
attributesChangedCallback
);
}
current_options = options;
if (options) {
options.addEventListener(
'auxAttributesChanged',
attributesChangedCallback
);
}
attributesChangedCallback();
});
return () => {
subs();
if (current_options)
current_options.removeEventListener(
'auxAttributesChanged',
attributesChangedCallback
);
};
}