From a6c1a12e905193ce0628fa26391b6685a918807c Mon Sep 17 00:00:00 2001 From: Admin Date: Thu, 9 Aug 2018 00:31:30 -0500 Subject: [PATCH] Adds event modifiers using | character --- src/compile/nodes/Element.ts | 56 ++++++++++++++++++++++++++++++---- src/shared/dom.js | 8 ++--- src/utils/getEventModifiers.ts | 36 ++++++++++++++++++++++ 3 files changed, 90 insertions(+), 10 deletions(-) create mode 100644 src/utils/getEventModifiers.ts diff --git a/src/compile/nodes/Element.ts b/src/compile/nodes/Element.ts index 403b9cce8b..7c42ab71e2 100644 --- a/src/compile/nodes/Element.ts +++ b/src/compile/nodes/Element.ts @@ -6,6 +6,7 @@ import validCalleeObjects from '../../utils/validCalleeObjects'; import reservedNames from '../../utils/reservedNames'; import fixAttributeCasing from '../../utils/fixAttributeCasing'; import { quoteNameIfNecessary, quotePropIfNecessary } from '../../utils/quoteIfNecessary'; +import getEventModifiers from '../../utils/getEventModifiers'; import Compiler from '../Compiler'; import Node from './shared/Node'; import Block from '../dom/Block'; @@ -21,7 +22,45 @@ import mapChildren from './shared/mapChildren'; import { dimensions } from '../../utils/patterns'; // source: https://gist.github.com/ArjanSchouten/0b8574a6ad7f5065a5e7 -const booleanAttributes = new Set('async autocomplete autofocus autoplay border challenge checked compact contenteditable controls default defer disabled formnovalidate frameborder hidden indeterminate ismap loop multiple muted nohref noresize noshade novalidate nowrap open readonly required reversed scoped scrolling seamless selected sortable spellcheck translate'.split(' ')); +const booleanAttributes = new Set([ + 'async', + 'autocomplete', + 'autofocus', + 'autoplay', + 'border', + 'challenge', + 'checked', + 'compact', + 'contenteditable', + 'controls', + 'default', + 'defer', + 'disabled', + 'formnovalidate', + 'frameborder', + 'hidden', + 'indeterminate', + 'ismap', + 'loop', + 'multiple', + 'muted', + 'nohref', + 'noresize', + 'noshade', + 'novalidate', + 'nowrap', + 'open', + 'readonly', + 'required', + 'reversed', + 'scoped', + 'scrolling', + 'seamless', + 'selected', + 'sortable', + 'spellcheck', + 'translate' +]); export default class Element extends Node { type: 'Element'; @@ -612,14 +651,19 @@ export default class Element extends Node { const target = handler.shouldHoist ? 'this' : this.var; + // split by | to remove stop, prevent, pass, etc. + const eventName = handler.name.split('|')[0]; + // get a name for the event handler that is globally unique - // if hoisted, locally unique otherwise + // if hoisted, locally unique otherwise. const handlerName = (handler.shouldHoist ? compiler : block).getUniqueName( - `${handler.name.replace(/[^a-zA-Z0-9_$]/g, '_')}_handler` + `${eventName.replace(/[^a-zA-Z0-9_$]/g, '_')}_handler` ); const component = block.alias('component'); // can't use #component, might be hoisted + const { bodyModifiers, optionModifiers } = getEventModifiers(handler.name); + // create the handler body const handlerBody = deindent` ${handler.shouldHoist && ( @@ -627,7 +671,7 @@ export default class Element extends Node { ? `const { ${[handler.usesComponent && 'component', handler.usesContext && 'ctx'].filter(Boolean).join(', ')} } = ${target}._svelte;` : null )} - + ${bodyModifiers} ${handler.snippet ? handler.snippet : `${component}.fire("${handler.name}", event);`} @@ -659,11 +703,11 @@ export default class Element extends Node { } block.builders.hydrate.addLine( - `@addListener(${this.var}, "${handler.name}", ${handlerName});` + `@addListener(${this.var}, "${eventName}", ${handlerName}, ${JSON.stringify(optionModifiers)});` ); block.builders.destroy.addLine( - `@removeListener(${this.var}, "${handler.name}", ${handlerName});` + `@removeListener(${this.var}, "${eventName}", ${handlerName}, ${JSON.stringify(optionModifiers)});` ); } }); diff --git a/src/shared/dom.js b/src/shared/dom.js index 03aaf8aaeb..0fb8f78f95 100644 --- a/src/shared/dom.js +++ b/src/shared/dom.js @@ -73,12 +73,12 @@ export function createComment() { return document.createComment(''); } -export function addListener(node, event, handler) { - node.addEventListener(event, handler, false); +export function addListener(node, event, handler, options) { + node.addEventListener(event, handler, options); } -export function removeListener(node, event, handler) { - node.removeEventListener(event, handler, false); +export function removeListener(node, event, handler, options) { + node.removeEventListener(event, handler, options); } export function setAttribute(node, attribute, value) { diff --git a/src/utils/getEventModifiers.ts b/src/utils/getEventModifiers.ts new file mode 100644 index 0000000000..f194e987e2 --- /dev/null +++ b/src/utils/getEventModifiers.ts @@ -0,0 +1,36 @@ +import EventHandler from '../compile/nodes/EventHandler'; +import deindent from '../utils/deindent'; + +export default function getEventModifiers(handlerName: String) { + // Ignore first element because it's the event name, i.e. click + let modifiers = handlerName.split('|').slice(1); + + let eventModifiers = modifiers.reduce((acc, m) => { + if (m === 'stop') + acc.bodyModifiers += 'event.stopPropagation();\n'; + else if (m === 'prevent') + acc.bodyModifiers += 'event.preventDefault();\n'; + else if (m === 'capture') + acc.optionModifiers[m] = true; + else if (m === 'once') + acc.optionModifiers[m] = true; + else if (m === 'passive') + acc.optionModifiers[m] = true; + + return acc; + }, { + bodyModifiers: '', + optionModifiers: { + capture: false, + once: false, + passive: false, + } + }); + + if (eventModifiers.bodyModifiers !== '') + eventModifiers.bodyModifiers = deindent` + ${eventModifiers.bodyModifiers} + `; + + return eventModifiers; +} \ No newline at end of file