From 2dbbec3c0b704f188ee6d6847610ad150582c8e5 Mon Sep 17 00:00:00 2001 From: Ryan Gossiaux Date: Mon, 20 Dec 2021 15:43:54 -0800 Subject: [PATCH] Add forwardEventsBuilder from svelte-material-ui --- src/lib/internal/forwardEventsBuilder.ts | 134 +++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 src/lib/internal/forwardEventsBuilder.ts diff --git a/src/lib/internal/forwardEventsBuilder.ts b/src/lib/internal/forwardEventsBuilder.ts new file mode 100644 index 0000000..03bc90a --- /dev/null +++ b/src/lib/internal/forwardEventsBuilder.ts @@ -0,0 +1,134 @@ +// This is a modified version of code from hperrin/svelte-material-ui +import type { SvelteComponent } from 'svelte'; +import { + bubble, + listen, + prevent_default, + stop_propagation, +} from 'svelte/internal'; + +const MODIFIER_DIVIDER = "!"; +const modifierRegex = new RegExp(`^[^${MODIFIER_DIVIDER}]+(?:${MODIFIER_DIVIDER}(?:preventDefault|stopPropagation|passive|nonpassive|capture|once|self))+$`); + +export function forwardEventsBuilder(component: SvelteComponent) { + // This is our pseudo $on function. It is defined on component mount. + let $on: (eventType: string, callback: (event: any) => void) => () => void; + // This is a list of events bound before mount. + let events: [string, (event: any) => void][] = []; + + // And we override the $on function to forward all bound events. + component.$on = (fullEventType: string, callback: (event: any) => void) => { + let eventType = fullEventType; + let destructor = () => { }; + if ($on) { + // The event was bound programmatically. + destructor = $on(eventType, callback); + } else { + // The event was bound before mount by Svelte. + events.push([eventType, callback]); + } + return () => { + destructor(); + }; + }; + + function forward(e: Event) { + // Internally bubble the event up from Svelte components. + bubble(component, e); + } + + return (node: HTMLElement | SVGElement) => { + const destructors: (() => void)[] = []; + const forwardDestructors: { [k: string]: () => void } = {}; + + // This function is responsible for listening and forwarding + // all bound events. + $on = (fullEventType, callback) => { + let eventType = fullEventType; + let handler = callback; + // DOM addEventListener options argument. + let options: boolean | AddEventListenerOptions = false; + const modifierMatch = eventType.match(modifierRegex); + if (modifierMatch) { + // Parse the event modifiers. + // Supported modifiers: + // - preventDefault + // - stopPropagation + // - passive + // - nonpassive + // - capture + // - once + const parts = eventType.split(MODIFIER_DIVIDER); + eventType = parts[0]; + const eventOptions: { + passive?: true; + nonpassive?: true; + capture?: true; + once?: true; + preventDefault?: true; + stopPropagation?: true; + } = Object.fromEntries(parts.slice(1).map((mod) => [mod, true])); + if (eventOptions.passive) { + options = options || ({} as AddEventListenerOptions); + options.passive = true; + } + if (eventOptions.nonpassive) { + options = options || ({} as AddEventListenerOptions); + options.passive = false; + } + if (eventOptions.capture) { + options = options || ({} as AddEventListenerOptions); + options.capture = true; + } + if (eventOptions.once) { + options = options || ({} as AddEventListenerOptions); + options.once = true; + } + if (eventOptions.preventDefault) { + handler = prevent_default(handler); + } + if (eventOptions.stopPropagation) { + handler = stop_propagation(handler); + } + } + + // Listen for the event directly, with the given options. + const off = listen(node, eventType, handler, options); + const destructor = () => { + off(); + const idx = destructors.indexOf(destructor); + if (idx > -1) { + destructors.splice(idx, 1); + } + }; + + destructors.push(destructor); + + // Forward the event from Svelte. + if (!(eventType in forwardDestructors)) { + forwardDestructors[eventType] = listen(node, eventType, forward); + } + + return destructor; + }; + + for (let i = 0; i < events.length; i++) { + // Listen to all the events added before mount. + $on(events[i][0], events[i][1]); + } + + return { + destroy: () => { + // Remove all event listeners. + for (let i = 0; i < destructors.length; i++) { + destructors[i](); + } + + // Remove all event forwarders. + for (let entry of Object.entries(forwardDestructors)) { + entry[1](); + } + }, + }; + }; +}