UNPKG

wed

Version:

Wed is a schema-aware editor for XML documents.

441 lines (440 loc) 17.3 kB
import { TreeUpdater } from "./tree-updater"; export declare type Handler = () => void; export declare type SelectorHandlerPair<H> = [string, H]; /** * Called when a **tree fragment** is added which contains the element matched * by the selector that was passed to [[DOMListener.addHandler]]. * * @param root The root of the tree being listened on. * * @param tree The node which is at the root of the tree *fragment* that was * added to trigger the event. * * @param parent The parent of the tree. * * @param previousSibling The sibling that precedes ``tree``. * * @param nextSibling The sibling that follows ``tree``. * * @param element The element that was matched. */ export declare type IncludedElementHandler = (root: Node, tree: Node, parent: Node, previousSibling: Node | null, nextSibling: Node | null, element: Element) => void; /** * Called when a **tree fragment** is removed which contains the element matched * by the selector that was passed to [[DOMListener.addHandler]]. * * @param root The root of the tree being listened on. * * @param tree The node which is at the root of the tree *fragment* that was * removed to trigger the event. * * @param parent ``null`` because the tree no longer has a parent. * * @param previousSibling ``null`` because the tree no longer has siblings. * * @param nextSibling ``null`` because the tree no longer has siblings. * * @param element The element that was matched. */ export declare type ExcludedElementHandler = (root: Node, tree: Node, parent: null, previousSibling: null, nextSibling: null, element: Element) => void; /** * Called when a **tree fragment** is about to be removed and contains the * element matched by the selector that was passed to * [[DOMListener.addHandler]]. * * @param root The root of the tree being listened on. * * @param tree The node which is at the root of the tree *fragment* that is * being removed. * * @param parent The parent of the tree. * * @param previousSibling The sibling that precedes ``tree``. * * @param nextSibling The sibling that follows ``tree``. * * @param element The element that was matched. */ export declare type ExcludingElementHandler = (root: Node, tree: Node, parent: null, previousSibling: null, nextSibling: null, element: Element) => void; /** * Called when an element has been directly added to the tree. There is no * reason to provide ``parent``, ``previousSibling``, ``nextSibling`` for an * ``added-element`` event but having the same signature for additions and * removals allows use of the same function for both cases. * * @param root The root of the tree being listened on. * * @param parent The parent of the element that was added. * * @param previousSibling The sibling that precedes the element. * * @param nextSibling The sibling that follows the element. * * @param element The element that was matched. */ export declare type AddedElementHandler = (root: Node, parent: Node, previousSibling: Node, nextSibling: Node, element: Element) => void; /** * Called when an element is about to be directly removed from the tree. * * @param root The root of the tree being listened on. * * @param parent The parent of the element that was added. * * @param previousSibling The sibling that precedes the element. * * @param nextSibling The sibling that follows the element. * * @param element The element that was matched. */ export declare type RemovingElementHandler = (root: Node, parent: Node, previousSibling: Node, nextSibling: Node, element: Element) => void; /** * Called when an element is has been directly removed from the tree. * * @param root The root of the tree being listened on. * * @param parent ``null`` because the element is no longer in the tree. * * @param previousSibling ``null`` because the element is no longer in the tree. * * @param nextSibling ``null`` because the element is no longer in the tree. * * @param element The element that was matched. */ export declare type RemovedElementHandler = (root: Node, parent: null, previousSibling: null, nextSibling: null, element: Element) => void; /** * Called when children are about to be *removed* from an element. Note the * asymmetry: **these handlers are not called when nodes are added!!** * * @param root The root of the tree being listened on. * * @param added The nodes that are about to be added. This will always be an * empty list. * * @param removed The nodes that are about to be removed. * * @param previousSibling: The node before the list of nodes to be removed. * * @param nextSibling: The node after the list of nodes to be removed. * * @param element: The element whose children are being removed. */ export declare type ChildrenChangingHandler = (root: Node, added: Node[], removed: Node[], previousSibling: Node | null, nextSibling: Node | null, element: Element) => void; /** * Called when children of an element have been added to or removed from the * element. Note that the listener will call handlers with at most one of * ``added`` or ``removed`` non-empty. * * @param root The root of the tree being listened on. * * @param added The nodes that were added. * * @param removed The nodes that were removed. * * @param previousSibling: The node before the list of nodes added or * removed. When the handler is called after a removal of children, this is * necessarily ``null``. * * @param nextSibling: The node after the list of nodes added or removed. When * the handler is called after a removal of children, this is necessarily * ``null``. * * @param element: The element whose children were modified. */ export declare type ChildrenChangedHandler = (root: Node, added: Node[], removed: Node[], previousSibling: Node | null, nextSibling: Node | null, element: Element) => void; /** * Called when a text node has its value changed. A ``text-changed`` event is * not generated when Node objects of type ``TEXT_NODE`` are added or * removed. They trigger ``children-changed`` events. * * @param root The root of the tree being listened on. * * @param node The text node that was changed. * * @param oldValue The value the node had before this change. */ export declare type TextChangedHandler = (root: Node, node: Text, oldValue: string) => void; /** * Called when an attribute value has been changed. * * @param root The root of the tree being listened on. * * @param element The element whose attribute changed. * * @param ns The URI of the namespace of the attribute. * * @param name The name of the attribute. * * @param oldValue The value of the attribute before this change. */ export declare type AttributeChangedHandler = (root: Node, element: Element, ns: string, name: string, oldValue: string) => void; /** * A ``trigger`` event with name ``[name]`` is fired when ``trigger([name])`` is * called. Trigger events are meant to be triggered by event handlers called by * the listener, not by other code. */ export declare type TriggerHandler = (root: Node) => void; export interface EventHandlers { "included-element": IncludedElementHandler; "excluded-element": ExcludedElementHandler; "excluding-element": ExcludingElementHandler; "added-element": AddedElementHandler; "removing-element": RemovingElementHandler; "removed-element": RemovedElementHandler; "children-changing": ChildrenChangingHandler; "children-changed": ChildrenChangedHandler; "text-changed": TextChangedHandler; "attribute-changed": AttributeChangedHandler; } export interface Handlers extends EventHandlers { "trigger": TriggerHandler; } export declare type Events = keyof EventHandlers; export declare type EventsOrTrigger = keyof Handlers; export declare type EventHandlerMap = { [name in Events]: SelectorHandlerPair<EventHandlers[name]>[]; }; /** * This class models a listener designed to listen to changes to a DOM tree and * fire events on the basis of the changes that it detects. * * An ``included-element`` event is fired when an element appears in the * observed tree whether it is directly added or added because its parent was * added. The opposite events are ``excluding-element`` and * ``excluded-element``. The event ``excluding-element`` is generated *before * the tree fragment is removed, and ``excluded-element`` *after*. * * An ``added-element`` event is fired when an element is directly added to the * observed tree. The opposite events are ``excluding-element`` and * ``removed-element``. * * A ``children-changing`` and ``children-changed`` event are fired when an * element's children are being changed. * * A ``text-changed`` event is fired when a text node has changed. * * An ``attribute-changed`` is fired when an attribute has changed. * * A ``trigger`` event with name ``[name]`` is fired when ``trigger([name])`` is * called. Trigger events are meant to be triggered by event handlers called by * the listener, not by other code. * * <h2>Example</h2> * * Consider the following HTML fragment: * * <ul> * <li>foo</li> * </ul> * * If the fragment is added to a ``<div>`` element, an ``included-element`` * event will be generated for ``<ul>`` and ``<li>`` but an ``added-element`` * event will be generated only for ``<ul>``. A ``changed-children`` event will * be generated for the parent of ``<ul>``. * * If the fragment is removed, an ``excluding-element`` and ``excluded-element`` * event will be generated for ``<ul>`` and ``<li>`` but a ``removing-element`` * and ``remove-element`` event will be generated only for ``<ul>``. A * ``children-changing`` and ``children-changed`` event will be generated for * the parent of ``<ul>``. * * The order in which handlers are added matters. The listener provides the * following guarantee: for any given type of event, the handlers will be called * in the order that they were added to the listener. * * <h2>Warnings:</h2> * * - Keep in mind that the ``children-changed``, ``excluded-element`` and * ``removed-element`` events are generated **after** the DOM operation that * triggers them. This has some consequences. In particular, a selector that * will work perfectly with ``removing-element`` or ``excluding-element`` may * not work with ``removed-element`` and ``excluded-element``. This would * happen if the selector tests for ancestors of the element removed or * excluded. By the time the ``-ed`` events are generated, the element is gone * from the DOM tree and such selectors will fail. * * The ``-ed`` version of these events are still useful. For instance, a wed * mode in use for editing scholarly articles listens for ``excluded-element`` * with a selector that is a tag name so that it can remove references to * these elements when they are removed. Since it does not need anything more * complex then ``excluded-element`` works perfectly. * * - A listener does not verify whether the parameters passed to handlers are * part of the DOM tree. For instance, handler A could operate on element X so * that it is removed from the DOM tree. If there is already another mutation * on X in the pipeline by the time A is called and handler B is called to * deal with it, then by the time B is run X will no longer be part of the * tree. * * To put it differently, even if when an event is generated element X was * part of the DOM tree, it is possible that by the time the handlers that * must be run for that mutation are run, X is no longer part of the DOM tree. * * Handlers that care about whether they are operating on elements that are in * the DOM tree should perform a test themselves to check whether what is * passed to them is still in the tree. * * The handlers fired on removed-elements events work on nodes that have been * removed from the DOM tree. To know what was before and after these nodes * **before** they were removed use events that have ``previous_sibling`` and * ``next_sibling`` parameters, because it is likely that the nodes themselves * will have both their ``previousSibling`` and ``nextSibling`` set to * ``null``. * * - Handlers that are fired on children-changed events, **and** which modify * the DOM tree can easily result in infinite loops. Care should be taken * early in any such handler to verify that the kind of elements added or * removed **should** result in a change to the DOM tree, and ignore those * changes that are not relevant. */ export declare class DOMListener { private readonly root; private readonly updater; private readonly eventHandlers; private readonly triggerHandlers; private triggersToFire; private stopped; private scheduledProcessTriggers; /** * @param root The root of the DOM tree about which the listener should listen * to changes. */ constructor(root: Node, updater: TreeUpdater); /** * Start listening to changes on the root passed when the object was * constructed. */ startListening(): void; /** * Stops listening to DOM changes. */ stopListening(): void; /** * Process all changes immediately. */ processImmediately(): void; /** * Clear anything that is pending. Some implementations may have triggers * delivered asynchronously. */ clearPending(): void; /** * Adds an event handler or a trigger handler. Note that if you want to add a * trigger handler, the first argument must be a single string, due to how the * 2nd argument is interpreted. * * @param eventTypes Either a string naming the event this handler will * process or an array of strings if multiple types of events are to be * handled. * * @param selector When adding an event handler, this argument is a CSS * selector. When adding a trigger handler, this argument is a trigger name. * * Note that the meaning of the ``selector`` parameter for ``text-changed`` * events is different than the usual. Whereas for all other handlers, the * ``selector`` matches the ``element`` parameter passed to the handlers, in * the case of a ``text-changed`` event the ``selector`` matches the * **parent** of the ``node`` parameter. * * @param handler The handler to be called by this listener when the events * specified in ``eventTypes`` occur. * * @throws {Error} If an event is unrecognized. */ addHandler(eventType: "trigger", selector: string, handler: TriggerHandler): void; addHandler<T extends Events>(eventType: T, selector: string, handler: EventHandlers[T]): void; /** * Tells the listener to fire the named trigger as soon as possible. * * @param {string} name The name of the trigger to fire. */ trigger(name: string): void; /** * Processes pending triggers. */ protected _processTriggers(): void; /** * Utility function for calling event handlers. * * @param handler The handler. * * @param rest The arguments to pass to the handler. */ protected _callHandler(handler: Function, ...rest: any[]): void; /** * Handles node additions. * * @param ev The event. */ private _insertNodeAtHandler(ev); /** * Handles node deletions. * * @param ev The event. */ private _beforeDeleteNodeHandler(ev); /** * Handles node deletion events. * * @param ev The event. */ private _deleteNodeHandler(ev); /** * Produces the calls for ``children-...`` events. * * @param call The type of call to produce. * * @param parent The parent of the children that have changed. * * @param added The children that were added. * * @param removed The children that were removed. * * @param prev Node preceding the children. * * @param next Node following the children. * * @returns A list of call specs. */ private _childrenCalls<T>(call, parent, added, removed, prev, next); /** * Handles text node changes events. * * @param ev The event. */ private _setTextNodeValueHandler(ev); /** * Handles attribute change events. * * @param ev The event. */ private _setAttributeNSHandler(ev); /** * Sets a timeout to run the triggers that must be run. */ private _scheduleProcessTriggers(); /** * Produces the calls for the added/removed family of events. * * @param name The event name. * * @param node The node added or removed. * * @param target The parent of this node. * * @returns A list of call specs. */ private _addRemCalls<T>(name, node, target); /** * Produces the calls for included/excluded family of events. * * @param name The event name. * * @param node The node which was included or excluded and for which we must * issue the events. * * @param target The parent of this node. * * @returns A list of call specs. */ private _incExcCalls<T>(name, node, target); }