diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 517ed3ceb6..8fc5d8a302 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -66,6 +66,7 @@ const invisible_elements = new Set(['meta', 'html', 'script', 'style']); const valid_modifiers = new Set([ 'preventDefault', 'stopPropagation', + 'stopImmediatePropagation' 'capture', 'once', 'passive', diff --git a/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts b/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts index 157e186ea6..373bf3f3ad 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/EventHandler.ts @@ -1,8 +1,8 @@ -import EventHandler from '../../../nodes/EventHandler'; -import Wrapper from '../shared/Wrapper'; -import Block from '../../Block'; -import { b, x, p } from 'code-red'; -import { Expression } from 'estree'; +import EventHandler from "../../../nodes/EventHandler"; +import Wrapper from "../shared/Wrapper"; +import Block from "../../Block"; +import { b, x, p } from "code-red"; +import { Expression } from "estree"; const TRUE = x`true`; const FALSE = x`false`; @@ -27,7 +27,9 @@ export default class EventHandlerWrapper { } get_snippet(block) { - const snippet = this.node.expression ? this.node.expression.manipulate(block) : block.renderer.reference(this.node.handler_name); + const snippet = this.node.expression + ? this.node.expression.manipulate(block) + : block.renderer.reference(this.node.handler_name); if (this.node.reassigned) { block.maintain_context = true; @@ -39,24 +41,35 @@ export default class EventHandlerWrapper { render(block: Block, target: string | Expression) { let snippet = this.get_snippet(block); - if (this.node.modifiers.has('preventDefault')) snippet = x`@prevent_default(${snippet})`; - if (this.node.modifiers.has('stopPropagation')) snippet = x`@stop_propagation(${snippet})`; - if (this.node.modifiers.has('self')) snippet = x`@self(${snippet})`; + if (this.node.modifiers.has("preventDefault")) + snippet = x`@prevent_default(${snippet})`; + if (this.node.modifiers.has("stopPropagation")) + snippet = x`@stop_propagation(${snippet})`; + if (this.node.modifiers.has("stopImmediatePropagation")) + snippet = x`@stop_immediate_propagation(${snippet})`; + if (this.node.modifiers.has("self")) snippet = x`@self(${snippet})`; const args = []; - const opts = ['passive', 'once', 'capture'].filter(mod => this.node.modifiers.has(mod)); + const opts = ["passive", "once", "capture"].filter((mod) => + this.node.modifiers.has(mod) + ); if (opts.length) { - args.push((opts.length === 1 && opts[0] === 'capture') - ? TRUE - : x`{ ${opts.map(opt => p`${opt}: true`)} }`); + args.push( + opts.length === 1 && opts[0] === "capture" + ? TRUE + : x`{ ${opts.map((opt) => p`${opt}: true`)} }` + ); } else if (block.renderer.options.dev) { args.push(FALSE); } if (block.renderer.options.dev) { - args.push(this.node.modifiers.has('preventDefault') ? TRUE : FALSE); - args.push(this.node.modifiers.has('stopPropagation') ? TRUE : FALSE); + args.push(this.node.modifiers.has("preventDefault") ? TRUE : FALSE); + args.push(this.node.modifiers.has("stopPropagation") ? TRUE : FALSE); + args.push( + this.node.modifiers.has("stopImmediatePropagation") ? TRUE : FALSE + ); } block.event_listeners.push( diff --git a/src/runtime/internal/dev.ts b/src/runtime/internal/dev.ts index 751f1f802b..32c2e44f6b 100644 --- a/src/runtime/internal/dev.ts +++ b/src/runtime/internal/dev.ts @@ -1,8 +1,10 @@ -import { custom_event, append, insert, detach, listen, attr } from './dom'; -import { SvelteComponent } from './Component'; +import { custom_event, append, insert, detach, listen, attr } from "./dom"; +import { SvelteComponent } from "./Component"; -export function dispatch_dev(type: string, detail?: T) { - document.dispatchEvent(custom_event(type, { version: '__VERSION__', ...detail })); +export function dispatch_dev(type: string, detail?: T) { + document.dispatchEvent( + custom_event(type, { version: "__VERSION__", ...detail }) + ); } export function append_dev(target: Node, node: Node) { @@ -38,16 +40,41 @@ export function detach_after_dev(before: Node) { } } -export function listen_dev(node: Node, event: string, handler: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions | EventListenerOptions, has_prevent_default?: boolean, has_stop_propagation?: boolean) { - const modifiers = options === true ? [ "capture" ] : options ? Array.from(Object.keys(options)) : []; - if (has_prevent_default) modifiers.push('preventDefault'); - if (has_stop_propagation) modifiers.push('stopPropagation'); - - dispatch_dev("SvelteDOMAddEventListener", { node, event, handler, modifiers }); +export function listen_dev( + node: Node, + event: string, + handler: EventListenerOrEventListenerObject, + options?: boolean | AddEventListenerOptions | EventListenerOptions, + has_prevent_default?: boolean, + has_stop_propagation?: boolean, + has_stop_immediate_propagation?: boolean +) { + const modifiers = + options === true + ? ["capture"] + : options + ? Array.from(Object.keys(options)) + : []; + if (has_prevent_default) modifiers.push("preventDefault"); + if (has_stop_propagation) modifiers.push("stopPropagation"); + if (has_stop_immediate_propagation) + modifiers.push("stopImmediatePropagation"); + + dispatch_dev("SvelteDOMAddEventListener", { + node, + event, + handler, + modifiers, + }); const dispose = listen(node, event, handler, options); return () => { - dispatch_dev("SvelteDOMRemoveEventListener", { node, event, handler, modifiers }); + dispatch_dev("SvelteDOMRemoveEventListener", { + node, + event, + handler, + modifiers, + }); dispose(); }; } @@ -55,7 +82,8 @@ export function listen_dev(node: Node, event: string, handler: EventListenerOrEv export function attr_dev(node: Element, attribute: string, value?: string) { attr(node, attribute, value); - if (value == null) dispatch_dev("SvelteDOMRemoveAttribute", { node, attribute }); + if (value == null) + dispatch_dev("SvelteDOMRemoveAttribute", { node, attribute }); else dispatch_dev("SvelteDOMSetAttribute", { node, attribute, value }); } @@ -72,7 +100,7 @@ export function dataset_dev(node: HTMLElement, property: string, value?: any) { } export function set_data_dev(text, data) { - data = '' + data; + data = "" + data; if (text.data === data) return; dispatch_dev("SvelteDOMSetData", { node: text, data }); @@ -80,10 +108,13 @@ export function set_data_dev(text, data) { } export function validate_each_argument(arg) { - if (typeof arg !== 'string' && !(arg && typeof arg === 'object' && 'length' in arg)) { - let msg = '{#each} only iterates over array-like objects.'; - if (typeof Symbol === 'function' && arg && Symbol.iterator in arg) { - msg += ' You can use a spread to convert this iterable into an array.'; + if ( + typeof arg !== "string" && + !(arg && typeof arg === "object" && "length" in arg) + ) { + let msg = "{#each} only iterates over array-like objects."; + if (typeof Symbol === "function" && arg && Symbol.iterator in arg) { + msg += " You can use a spread to convert this iterable into an array."; } throw new Error(msg); } @@ -100,7 +131,10 @@ export function validate_slots(name, slot, keys) { type Props = Record; export interface SvelteComponentDev { $set(props?: Props): void; - $on(event: string, callback: (event: CustomEvent) => void): () => void; + $on( + event: string, + callback: (event: CustomEvent) => void + ): () => void; $destroy(): void; [accessor: string]: any; } @@ -113,7 +147,7 @@ export class SvelteComponentDev extends SvelteComponent { hydrate?: boolean; intro?: boolean; $$inline?: boolean; - }) { + }) { if (!options || (!options.target && !options.$$inline)) { throw new Error(`'target' is a required option`); } diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts index 0cd670fcb9..c6238c2a4d 100644 --- a/src/runtime/internal/dom.ts +++ b/src/runtime/internal/dom.ts @@ -22,17 +22,23 @@ export function element(name: K) { return document.createElement(name); } -export function element_is(name: K, is: string) { +export function element_is( + name: K, + is: string +) { return document.createElement(name, { is }); } -export function object_without_properties(obj: T, exclude: K[]) { +export function object_without_properties( + obj: T, + exclude: K[] +) { const target = {} as Pick>; for (const k in obj) { if ( - has_prop(obj, k) + has_prop(obj, k) && // @ts-ignore - && exclude.indexOf(k) === -1 + exclude.indexOf(k) === -1 ) { // @ts-ignore target[k] = obj[k]; @@ -41,8 +47,10 @@ export function object_without_properties(obj: T, exclude: return target; } -export function svg_element(name: K): SVGElement { - return document.createElementNS('http://www.w3.org/2000/svg', name); +export function svg_element( + name: K +): SVGElement { + return document.createElementNS("http://www.w3.org/2000/svg", name); } export function text(data: string) { @@ -50,20 +58,25 @@ export function text(data: string) { } export function space() { - return text(' '); + return text(" "); } export function empty() { - return text(''); + return text(""); } -export function listen(node: EventTarget, event: string, handler: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions | EventListenerOptions) { +export function listen( + node: EventTarget, + event: string, + handler: EventListenerOrEventListenerObject, + options?: boolean | AddEventListenerOptions | EventListenerOptions +) { node.addEventListener(event, handler, options); return () => node.removeEventListener(event, handler, options); } export function prevent_default(fn) { - return function(event) { + return function (event) { event.preventDefault(); // @ts-ignore return fn.call(this, event); @@ -71,15 +84,23 @@ export function prevent_default(fn) { } export function stop_propagation(fn) { - return function(event) { + return function (event) { event.stopPropagation(); // @ts-ignore return fn.call(this, event); }; } +export function stop_immediate_propagation(fn) { + return function (event) { + event.stopImmediatePropagation(); + // @ts-ignore + return fn.call(this, event); + }; +} + export function self(fn) { - return function(event) { + return function (event) { // @ts-ignore if (event.target === this) fn.call(this, event); }; @@ -87,18 +108,22 @@ export function self(fn) { export function attr(node: Element, attribute: string, value?: string) { if (value == null) node.removeAttribute(attribute); - else if (node.getAttribute(attribute) !== value) node.setAttribute(attribute, value); + else if (node.getAttribute(attribute) !== value) + node.setAttribute(attribute, value); } -export function set_attributes(node: Element & ElementCSSInlineStyle, attributes: { [x: string]: string }) { +export function set_attributes( + node: Element & ElementCSSInlineStyle, + attributes: { [x: string]: string } +) { // @ts-ignore const descriptors = Object.getOwnPropertyDescriptors(node.__proto__); for (const key in attributes) { if (attributes[key] == null) { node.removeAttribute(key); - } else if (key === 'style') { + } else if (key === "style") { node.style.cssText = attributes[key]; - } else if (key === '__value') { + } else if (key === "__value") { (node as any).value = node[key] = attributes[key]; } else if (descriptors[key] && descriptors[key].set) { node[key] = attributes[key]; @@ -108,7 +133,10 @@ export function set_attributes(node: Element & ElementCSSInlineStyle, attributes } } -export function set_svg_attributes(node: Element & ElementCSSInlineStyle, attributes: { [x: string]: string }) { +export function set_svg_attributes( + node: Element & ElementCSSInlineStyle, + attributes: { [x: string]: string } +) { for (const key in attributes) { attr(node, key, attributes[key]); } @@ -123,7 +151,7 @@ export function set_custom_element_data(node, prop, value) { } export function xlink_attr(node, attribute, value) { - node.setAttributeNS('http://www.w3.org/1999/xlink', attribute, value); + node.setAttributeNS("http://www.w3.org/1999/xlink", attribute, value); } export function get_binding_group_value(group, __value, checked) { @@ -138,7 +166,7 @@ export function get_binding_group_value(group, __value, checked) { } export function to_number(value) { - return value === '' ? undefined : +value; + return value === "" ? undefined : +value; } export function time_ranges_to_array(ranges) { @@ -179,7 +207,7 @@ export function claim_text(nodes, data) { for (let i = 0; i < nodes.length; i += 1) { const node = nodes[i]; if (node.nodeType === 3) { - node.data = '' + data; + node.data = "" + data; return nodes.splice(i, 1)[0]; } } @@ -188,16 +216,16 @@ export function claim_text(nodes, data) { } export function claim_space(nodes) { - return claim_text(nodes, ' '); + return claim_text(nodes, " "); } export function set_data(text, data) { - data = '' + data; + data = "" + data; if (text.data !== data) text.data = data; } export function set_input_value(input, value) { - input.value = value == null ? '' : value; + input.value = value == null ? "" : value; } export function set_input_type(input, type) { @@ -209,7 +237,7 @@ export function set_input_type(input, type) { } export function set_style(node, key, value, important) { - node.style.setProperty(key, value, important ? 'important' : ''); + node.style.setProperty(key, value, important ? "important" : ""); } export function select_option(select, value) { @@ -231,12 +259,15 @@ export function select_options(select, value) { } export function select_value(select) { - const selected_option = select.querySelector(':checked') || select.options[0]; + const selected_option = select.querySelector(":checked") || select.options[0]; return selected_option && selected_option.__value; } export function select_multiple_value(select) { - return [].map.call(select.querySelectorAll(':checked'), option => option.__value); + return [].map.call( + select.querySelectorAll(":checked"), + (option) => option.__value + ); } // unfortunately this can't be a constant as that wouldn't be tree-shakeable @@ -248,7 +279,7 @@ export function is_crossorigin() { crossorigin = false; try { - if (typeof window !== 'undefined' && window.parent) { + if (typeof window !== "undefined" && window.parent) { void window.parent.document; } } catch (error) { @@ -263,16 +294,17 @@ export function add_resize_listener(node: HTMLElement, fn: () => void) { const computed_style = getComputedStyle(node); const z_index = (parseInt(computed_style.zIndex) || 0) - 1; - if (computed_style.position === 'static') { - node.style.position = 'relative'; + if (computed_style.position === "static") { + node.style.position = "relative"; } - const iframe = element('iframe'); - iframe.setAttribute('style', + const iframe = element("iframe"); + iframe.setAttribute( + "style", `display: block; position: absolute; top: 0; left: 0; width: 100%; height: 100%; ` + - `overflow: hidden; border: 0; opacity: 0; pointer-events: none; z-index: ${z_index};` + `overflow: hidden; border: 0; opacity: 0; pointer-events: none; z-index: ${z_index};` ); - iframe.setAttribute('aria-hidden', 'true'); + iframe.setAttribute("aria-hidden", "true"); iframe.tabIndex = -1; const crossorigin = is_crossorigin(); @@ -281,13 +313,13 @@ export function add_resize_listener(node: HTMLElement, fn: () => void) { if (crossorigin) { iframe.src = `data:text/html,`; - unsubscribe = listen(window, 'message', (event: MessageEvent) => { + unsubscribe = listen(window, "message", (event: MessageEvent) => { if (event.source === iframe.contentWindow) fn(); }); } else { - iframe.src = 'about:blank'; + iframe.src = "about:blank"; iframe.onload = () => { - unsubscribe = listen(iframe.contentWindow, 'resize', fn); + unsubscribe = listen(iframe.contentWindow, "resize", fn); }; } @@ -305,16 +337,19 @@ export function add_resize_listener(node: HTMLElement, fn: () => void) { } export function toggle_class(element, name, toggle) { - element.classList[toggle ? 'add' : 'remove'](name); + element.classList[toggle ? "add" : "remove"](name); } -export function custom_event(type: string, detail?: T) { - const e: CustomEvent = document.createEvent('CustomEvent'); +export function custom_event(type: string, detail?: T) { + const e: CustomEvent = document.createEvent("CustomEvent"); e.initCustomEvent(type, false, false, detail); return e; } -export function query_selector_all(selector: string, parent: HTMLElement = document.body) { +export function query_selector_all( + selector: string, + parent: HTMLElement = document.body +) { return Array.from(parent.querySelectorAll(selector)); }