From a6c1a12e905193ce0628fa26391b6685a918807c Mon Sep 17 00:00:00 2001 From: Admin Date: Thu, 9 Aug 2018 00:31:30 -0500 Subject: [PATCH 1/4] 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 From 26360d4ced306bf103f8df4ff71b9afdca0e36a7 Mon Sep 17 00:00:00 2001 From: Admin Date: Thu, 9 Aug 2018 01:06:09 -0500 Subject: [PATCH 2/4] Fixes tests that use events --- test/js/samples/input-files/expected-bundle.js | 8 ++++---- test/js/samples/input-range/expected-bundle.js | 8 ++++---- .../input-without-blowback-guard/expected-bundle.js | 8 ++++---- test/js/samples/media-bindings/expected-bundle.js | 8 ++++---- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/test/js/samples/input-files/expected-bundle.js b/test/js/samples/input-files/expected-bundle.js index 097dc9e1b5..fa515f700c 100644 --- a/test/js/samples/input-files/expected-bundle.js +++ b/test/js/samples/input-files/expected-bundle.js @@ -17,12 +17,12 @@ function createElement(name) { return document.createElement(name); } -function addListener(node, event, handler) { - node.addEventListener(event, handler, false); +function addListener(node, event, handler, options) { + node.addEventListener(event, handler, options); } -function removeListener(node, event, handler) { - node.removeEventListener(event, handler, false); +function removeListener(node, event, handler, options) { + node.removeEventListener(event, handler, options); } function setAttribute(node, attribute, value) { diff --git a/test/js/samples/input-range/expected-bundle.js b/test/js/samples/input-range/expected-bundle.js index 50ba725fa9..b159277bf6 100644 --- a/test/js/samples/input-range/expected-bundle.js +++ b/test/js/samples/input-range/expected-bundle.js @@ -17,12 +17,12 @@ function createElement(name) { return document.createElement(name); } -function addListener(node, event, handler) { - node.addEventListener(event, handler, false); +function addListener(node, event, handler, options) { + node.addEventListener(event, handler, options); } -function removeListener(node, event, handler) { - node.removeEventListener(event, handler, false); +function removeListener(node, event, handler, options) { + node.removeEventListener(event, handler, options); } function setAttribute(node, attribute, value) { diff --git a/test/js/samples/input-without-blowback-guard/expected-bundle.js b/test/js/samples/input-without-blowback-guard/expected-bundle.js index 5bf43ec519..b50393ef21 100644 --- a/test/js/samples/input-without-blowback-guard/expected-bundle.js +++ b/test/js/samples/input-without-blowback-guard/expected-bundle.js @@ -17,12 +17,12 @@ function createElement(name) { return document.createElement(name); } -function addListener(node, event, handler) { - node.addEventListener(event, handler, false); +function addListener(node, event, handler, options) { + node.addEventListener(event, handler, options); } -function removeListener(node, event, handler) { - node.removeEventListener(event, handler, false); +function removeListener(node, event, handler, options) { + node.removeEventListener(event, handler, options); } function setAttribute(node, attribute, value) { diff --git a/test/js/samples/media-bindings/expected-bundle.js b/test/js/samples/media-bindings/expected-bundle.js index ec9116f544..17e820a067 100644 --- a/test/js/samples/media-bindings/expected-bundle.js +++ b/test/js/samples/media-bindings/expected-bundle.js @@ -17,12 +17,12 @@ function createElement(name) { return document.createElement(name); } -function addListener(node, event, handler) { - node.addEventListener(event, handler, false); +function addListener(node, event, handler, options) { + node.addEventListener(event, handler, options); } -function removeListener(node, event, handler) { - node.removeEventListener(event, handler, false); +function removeListener(node, event, handler, options) { + node.removeEventListener(event, handler, options); } function timeRangesToArray(ranges) { From adfc0e3e458f05293c42f1098b5b10a96640f73f Mon Sep 17 00:00:00 2001 From: Admin Date: Thu, 9 Aug 2018 22:05:31 -0500 Subject: [PATCH 3/4] Adds invalid test for event-modifiers. --- src/validate/html/validateEventHandler.ts | 13 +++++++++++++ .../samples/event-modifiers-invalid/errors.json | 15 +++++++++++++++ .../samples/event-modifiers-invalid/input.html | 1 + 3 files changed, 29 insertions(+) create mode 100644 test/validator/samples/event-modifiers-invalid/errors.json create mode 100644 test/validator/samples/event-modifiers-invalid/input.html diff --git a/src/validate/html/validateEventHandler.ts b/src/validate/html/validateEventHandler.ts index df499a9fd5..b114070993 100644 --- a/src/validate/html/validateEventHandler.ts +++ b/src/validate/html/validateEventHandler.ts @@ -6,6 +6,8 @@ import { Node } from '../../interfaces'; const validBuiltins = new Set(['set', 'fire', 'destroy']); +const validModifiers = new Set(['stop', 'prevent', 'capture', 'once', 'passive']); + export default function validateEventHandlerCallee( validator: Validator, attribute: Node, @@ -22,6 +24,17 @@ export default function validateEventHandlerCallee( }); } + const modifiers = attribute.name.split('|').slice(1); + if ( + modifiers.length > 0 && + modifiers.filter(m => !validModifiers.has(m)).length > 0 + ) { + validator.error(attribute, { + code: 'invalid-event-modifiers', + message: `Valid event modifiers are ${[...validModifiers].join(', ')}.` + }); + } + const { name } = flattenReference(callee); if (validCalleeObjects.has(name) || name === 'options') return; diff --git a/test/validator/samples/event-modifiers-invalid/errors.json b/test/validator/samples/event-modifiers-invalid/errors.json new file mode 100644 index 0000000000..99737f50d2 --- /dev/null +++ b/test/validator/samples/event-modifiers-invalid/errors.json @@ -0,0 +1,15 @@ +[{ + "message": "Valid event modifiers are stop, prevent, capture, once, passive.", + "code": "invalid-event-modifiers", + "start": { + "line": 1, + "column": 8, + "character": 8 + }, + "end": { + "line": 1, + "column": 36, + "character": 36 + }, + "pos": 8 +}] \ No newline at end of file diff --git a/test/validator/samples/event-modifiers-invalid/input.html b/test/validator/samples/event-modifiers-invalid/input.html new file mode 100644 index 0000000000..27dacdbc2d --- /dev/null +++ b/test/validator/samples/event-modifiers-invalid/input.html @@ -0,0 +1 @@ + \ No newline at end of file From 7c4b9a5a41dbbcc8e7dcba07c2b9ad78867d89f4 Mon Sep 17 00:00:00 2001 From: Admin Date: Sat, 11 Aug 2018 21:16:42 -0500 Subject: [PATCH 4/4] Changes stop and prevent to stopPropagation and preventDefault --- src/utils/getEventModifiers.ts | 4 ++-- src/validate/html/validateEventHandler.ts | 2 +- test/validator/samples/event-modifiers-invalid/errors.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/utils/getEventModifiers.ts b/src/utils/getEventModifiers.ts index f194e987e2..554a2c79af 100644 --- a/src/utils/getEventModifiers.ts +++ b/src/utils/getEventModifiers.ts @@ -6,9 +6,9 @@ export default function getEventModifiers(handlerName: String) { let modifiers = handlerName.split('|').slice(1); let eventModifiers = modifiers.reduce((acc, m) => { - if (m === 'stop') + if (m === 'stopPropagation') acc.bodyModifiers += 'event.stopPropagation();\n'; - else if (m === 'prevent') + else if (m === 'preventDefault') acc.bodyModifiers += 'event.preventDefault();\n'; else if (m === 'capture') acc.optionModifiers[m] = true; diff --git a/src/validate/html/validateEventHandler.ts b/src/validate/html/validateEventHandler.ts index b114070993..f1df2f741c 100644 --- a/src/validate/html/validateEventHandler.ts +++ b/src/validate/html/validateEventHandler.ts @@ -6,7 +6,7 @@ import { Node } from '../../interfaces'; const validBuiltins = new Set(['set', 'fire', 'destroy']); -const validModifiers = new Set(['stop', 'prevent', 'capture', 'once', 'passive']); +const validModifiers = new Set(['stopPropagation', 'preventDefault', 'capture', 'once', 'passive']); export default function validateEventHandlerCallee( validator: Validator, diff --git a/test/validator/samples/event-modifiers-invalid/errors.json b/test/validator/samples/event-modifiers-invalid/errors.json index 99737f50d2..af1cca83e4 100644 --- a/test/validator/samples/event-modifiers-invalid/errors.json +++ b/test/validator/samples/event-modifiers-invalid/errors.json @@ -1,5 +1,5 @@ [{ - "message": "Valid event modifiers are stop, prevent, capture, once, passive.", + "message": "Valid event modifiers are stopPropagation, preventDefault, capture, once, passive.", "code": "invalid-event-modifiers", "start": { "line": 1,