diff --git a/ui/src/components/menu/ClickOutside.js b/ui/src/components/menu/ClickOutside.js index bb90b4831ab1..3d76d0555af9 100644 --- a/ui/src/components/menu/ClickOutside.js +++ b/ui/src/components/menu/ClickOutside.js @@ -51,7 +51,7 @@ function globalHandler (evt) { export default { name: 'click-outside', - bind (el, { value, arg }, vnode) { + bind (el, { arg, value }, vnode) { const vmEl = vnode.componentInstance || vnode.context const ctx = { @@ -106,14 +106,16 @@ export default { }, 500) }, - update (el, { value, oldValue, arg }) { + update (el, { arg, value, oldValue }) { const ctx = el.__qclickoutside - if (value !== oldValue) { - ctx.trigger = value - } - if (arg !== ctx.arg) { - ctx.toggleEl = arg + if (ctx !== void 0) { + if (ctx.toggleEl !== arg) { + ctx.toggleEl = arg + } + if (oldValue !== value) { + ctx.trigger = value + } } }, diff --git a/ui/src/directives/ClosePopup.js b/ui/src/directives/ClosePopup.js index aa20d1feca77..c6d8533749ed 100644 --- a/ui/src/directives/ClosePopup.js +++ b/ui/src/directives/ClosePopup.js @@ -60,7 +60,7 @@ export default { }, update (el, { value, oldValue }) { - if (el.__qclosepopup !== void 0 && value !== oldValue) { + if (el.__qclosepopup !== void 0 && oldValue !== value) { el.__qclosepopup.depth = getDepth(value) } }, diff --git a/ui/src/directives/GoBack.js b/ui/src/directives/GoBack.js index 20e0b7464b38..e6fb52ab6046 100644 --- a/ui/src/directives/GoBack.js +++ b/ui/src/directives/GoBack.js @@ -13,7 +13,7 @@ function destroy (el) { export default { name: 'go-back', - bind (el, { value, modifiers }, vnode) { + bind (el, { modifiers, value }, vnode) { if (el.__qgoback !== void 0) { destroy(el) el.__qgoback_destroyed = true @@ -51,11 +51,16 @@ export default { el.addEventListener('keyup', ctx.goBackKey) }, - update (el, { value, oldValue }) { + update (el, { modifiers, value, oldValue }) { const ctx = el.__qgoback - if (ctx !== void 0 && value !== oldValue) { - ctx.value = value + if (ctx !== void 0) { + if (ctx.single !== modifiers.single) { + ctx.single = modifiers.single + } + if (oldValue !== value) { + ctx.value = value + } } }, diff --git a/ui/src/directives/GoBack.json b/ui/src/directives/GoBack.json index a775b3d270b5..3627679cb627 100644 --- a/ui/src/directives/GoBack.json +++ b/ui/src/directives/GoBack.json @@ -16,6 +16,7 @@ "single": { "type": "Boolean", "desc": "Go back to previous route instead of the whole way to before directive was initialized", + "reactive": true, "examples": [ "v-go-back.single" ] } } diff --git a/ui/src/directives/Intersection.json b/ui/src/directives/Intersection.json index f8769fc0274f..7b77440cac52 100644 --- a/ui/src/directives/Intersection.json +++ b/ui/src/directives/Intersection.json @@ -225,9 +225,7 @@ "once": { "type": "Boolean", "desc": "Call handler only once, when the conditions are first met", - "examples": [ - "v-intersection.once" - ] + "examples": [ "v-intersection.once" ] } } } diff --git a/ui/src/directives/Morph.js b/ui/src/directives/Morph.js index 0776ff263c46..e6cf5a7abcfb 100644 --- a/ui/src/directives/Morph.js +++ b/ui/src/directives/Morph.js @@ -1,16 +1,15 @@ import morph from '../utils/morph.js' const morphGroups = {} +const mods = [ + 'resize', 'useCSS', 'hideFromClone', 'keepToClone', 'tween' +] const props = [ 'duration', 'delay', 'easing', 'fill', - 'classes', 'style', 'duration', 'resize', - 'useCSS', 'hideFromClone', 'keepToClone', 'tween', + 'classes', 'style', 'duration', 'tweenFromOpacity', 'tweenToOpacity', 'waitFor', 'onEnd' ] -const mods = [ - 'resize', 'useCSS', 'hideFromClone', 'keepToClone', 'tween' -] function changeClass (ctx, action) { if (ctx.clsAction !== action) { @@ -65,47 +64,7 @@ function trigger (group) { } } -function updateModifiers (mod, ctx) { - const opts = ctx.opts - - mods.forEach(name => { - opts[name] = mod[name] === true - }) -} - -function insertArgs (arg, ctx) { - const opts = typeof arg === 'string' && arg.length > 0 - ? arg.split(':') : [] - - ctx.name = opts[0] - ctx.group = opts[1] - - Object.assign(ctx.opts, { - duration: isNaN(opts[2]) === true - ? 300 - : parseFloat(opts[2]), - waitFor: opts[3] - }) -} - -function updateArgs (arg, ctx) { - if (arg.group !== void 0) { - ctx.group = arg.group - } - if (arg.name !== void 0) { - ctx.name = arg.name - } - - const opts = ctx.opts - - props.forEach(name => { - if (arg[name] !== void 0) { - opts[name] = arg[name] - } - }) -} - -function updateModel (name, ctx) { +function changeModel (ctx, name) { if (ctx.name === name) { const group = morphGroups[ctx.group] @@ -138,13 +97,53 @@ function updateModel (name, ctx) { } } +function setOptsFromValue (ctx, value) { + if (value.group !== void 0) { + ctx.group = value.group + } + if (value.name !== void 0) { + ctx.name = value.name + } + + const opts = ctx.opts + + props.forEach(name => { + if (value[name] !== void 0) { + opts[name] = value[name] + } + }) +} + +function updateArg (ctx, arg) { + const opts = typeof arg === 'string' && arg.length > 0 + ? arg.split(':') : [] + + ctx.name = opts[0] + ctx.group = opts[1] + + Object.assign(ctx.opts, { + duration: isNaN(opts[2]) === true + ? 300 + : parseFloat(opts[2]), + waitFor: opts[3] + }) +} + +function updateModifiers (ctx, modifiers) { + const opts = ctx.opts + + mods.forEach(name => { + opts[name] = modifiers[name] === true + }) +} + function updateValue (ctx, value) { let model if (Object(value) === value) { model = '' + value.model - updateArgs(value, ctx) - updateModifiers(value, ctx) + setOptsFromValue(ctx, value) + updateModifiers(ctx, value) } else { model = '' + value @@ -152,7 +151,7 @@ function updateValue (ctx, value) { if (model !== ctx.model) { ctx.model = model - updateModel(model, ctx) + changeModel(ctx, model) } else if (ctx.animating === false && ctx.clsAction !== void 0) { // ensure HMR @@ -190,7 +189,7 @@ function destroy (el) { export default { name: 'morph', - inserted (el, binding) { + inserted (el, { modifiers, arg, value }) { if (el.__qmorph !== void 0) { destroy(el) el.__qmorph_destroyed = true @@ -198,20 +197,29 @@ export default { const ctx = { el, + + arg, + animating: false, opts: {} } - updateModifiers(binding.modifiers, ctx) - insertArgs(binding.arg, ctx) - updateValue(ctx, binding.value) + updateModifiers(ctx, modifiers) + updateArg(ctx, arg) + updateValue(ctx, value) el.__qmorph = ctx }, - update (el, binding) { + update (el, { modifiers, arg, value }) { const ctx = el.__qmorph - ctx !== void 0 && updateValue(ctx, binding.value) + if (ctx !== void 0) { + updateModifiers(ctx, modifiers) + if (ctx.arg !== arg) { + updateArg(ctx, arg) + } + updateValue(ctx, value) + } }, unbind (el) { diff --git a/ui/src/directives/Morph.json b/ui/src/directives/Morph.json index 6026ca8f4758..5c70178caff2 100644 --- a/ui/src/directives/Morph.json +++ b/ui/src/directives/Morph.json @@ -141,6 +141,7 @@ "arg": { "type": "String", "desc": "x:x2:y:z, where x is the morph element name, x2 is the morph group, y is the animation duration (in milliseconds) and z is the amount of time to wait (in milliseconds) or the 'transitionend' string", + "reactive": true, "examples": [ "v-morph:name=\"options\"", "v-morph:name:groupName=\"options\"", @@ -153,27 +154,32 @@ "modifiers": { "resize": { "type": "Boolean", - "desc": "Use resize instead of scale transform for morph (forceResize option of the morph function)" + "desc": "Use resize instead of scale transform for morph (forceResize option of the morph function)", + "reactive": true }, "useCSS": { "type": "Boolean", - "desc": "Use CSS animations for morph (forceCssAnimation option of the morph function)" + "desc": "Use CSS animations for morph (forceCssAnimation option of the morph function)", + "reactive": true }, "hideFromClone": { "type": "Boolean", - "desc": "Hide the spacer for the initial element (hideFromClone option of the morph function)" + "desc": "Hide the spacer for the initial element (hideFromClone option of the morph function)", + "reactive": true }, "keepToClone": { "type": "Boolean", - "desc": "Keep the final element visible while morphing (keepToClone option of the morph function)" + "desc": "Keep the final element visible while morphing (keepToClone option of the morph function)", + "reactive": true }, "tween": { "type": "Boolean", - "desc": "Use opacity tween morphing between initial and final elements (tween option of the morph function)" + "desc": "Use opacity tween morphing between initial and final elements (tween option of the morph function)", + "reactive": true } } } diff --git a/ui/src/directives/Mutation.js b/ui/src/directives/Mutation.js index 5c6584b3ab28..ebbb4c8b17b6 100644 --- a/ui/src/directives/Mutation.js +++ b/ui/src/directives/Mutation.js @@ -1,3 +1,5 @@ +import { isDeepEqual } from '../utils/is.js' + const defaultCfg = { childList: true, subtree: true, @@ -35,28 +37,37 @@ function destroy (el) { export default { name: 'mutation', - inserted (el, { modifiers: { once, ...mod }, value }) { + inserted (el, { modifiers: { once, ...opts }, value }) { if (el.__qmutation !== void 0) { destroy(el) el.__qmutation_destroyed = true } const ctx = { - once, - opts: Object.keys(mod).length === 0 - ? defaultCfg - : mod + once } + ctx.opts = Object.keys(opts).length === 0 + ? defaultCfg + : opts update(el, ctx, value) el.__qmutation = ctx }, - update (el, { oldValue, value }) { + update (el, { modifiers: { once, ...opts }, value, oldValue }) { const ctx = el.__qmutation - if (ctx !== void 0 && oldValue !== value) { - update(el, ctx, value) + if (ctx !== void 0) { + const newOpts = Object.keys(opts).length === 0 + ? defaultCfg + : opts + if ( + oldValue !== value || + isDeepEqual(ctx.opts, newOpts) !== true + ) { + ctx.opts = newOpts + update(el, ctx, value) + } } }, diff --git a/ui/src/directives/Mutation.json b/ui/src/directives/Mutation.json index d69b0af7455e..65f0ee552c33 100644 --- a/ui/src/directives/Mutation.json +++ b/ui/src/directives/Mutation.json @@ -90,36 +90,42 @@ "childList": { "type": "Boolean", "desc": "Monitor the target node (and, if 'subtree' is also set, its descendants) for the addition of new child nodes or removal of existing child nodes", + "reactive": true, "examples": [ "v-mutation.childList" ] }, "subtree": { "type": "Boolean", "desc": "Extend monitoring to the entire subtree of nodes rooted at target", + "reactive": true, "examples": [ "v-mutation.subtree" ] }, "attributes": { "type": "Boolean", "desc": "Watch for changes to the value of attributes on the node or nodes being monitored", + "reactive": true, "examples": [ "v-mutation.attributes" ] }, "characterData": { "type": "Boolean", "desc": "Monitor the specified target node or subtree for changes to the character data contained within the node or nodes", + "reactive": true, "examples": [ "v-mutation.characterData" ] }, "attributeOldValue": { "type": "Boolean", "desc": "Record the previous value of any attribute that changes when monitoring the node or nodes for attribute changes", + "reactive": true, "examples": [ "v-mutation.attributeOldValue" ] }, "characterDataOldValue": { "type": "Boolean", "desc": "Record the previous value of a node's text whenever the text changes on nodes being monitored", + "reactive": true, "examples": [ "v-mutation.characterDataOldValue" ] } } diff --git a/ui/src/directives/Ripple.js b/ui/src/directives/Ripple.js index e1f3b30237d0..840d24f649b8 100644 --- a/ui/src/directives/Ripple.js +++ b/ui/src/directives/Ripple.js @@ -61,7 +61,7 @@ function showRipple (evt, el, ctx, forceCenter) { }, 50) } -function updateModifiers (ctx, { modifiers, value, arg }) { +function updateModifiers (ctx, { modifiers, arg, value }) { const cfg = Object.assign({}, $q.config.ripple, modifiers, value) ctx.modifiers = { early: cfg.early === true, @@ -133,10 +133,10 @@ export default { update (el, binding) { const ctx = el.__qripple - if (ctx !== void 0 && binding.oldValue !== binding.value) { + if (ctx !== void 0) { ctx.enabled = binding.value !== false - if (ctx.enabled === true && Object(binding.value) === binding.value) { + if (ctx.enabled === true) { updateModifiers(ctx, binding) } } diff --git a/ui/src/directives/Ripple.json b/ui/src/directives/Ripple.json index 2c100dd4af88..619a5a73320e 100644 --- a/ui/src/directives/Ripple.json +++ b/ui/src/directives/Ripple.json @@ -77,32 +77,30 @@ "arg": { "type": "String", "desc": "Color name from Quasar Color Palette; Overrides default dynamic color", - "examples": [ - "v-ripple:orange-5" - ] + "reactive": true, + "examples": [ "v-ripple:orange-5" ] }, "modifiers": { "early": { "type": "Boolean", "desc": "Trigger early/immediately on user interaction", + "reactive": true, "addedIn": "v1.9.8" }, "stop": { "type": "Boolean", "desc": "Stop click/touch event propagation", - "examples": [ - "v-ripple.stop" - ] + "reactive": true, + "examples": [ "v-ripple.stop" ] }, "center": { "type": "Boolean", "desc": "Ripple starts from the absolute center", - "examples": [ - "v-ripple.center" - ] + "reactive": true, + "examples": [ "v-ripple.center" ] } } } diff --git a/ui/src/directives/TouchHold.js b/ui/src/directives/TouchHold.js index 9305202bd246..1f1048c92643 100644 --- a/ui/src/directives/TouchHold.js +++ b/ui/src/directives/TouchHold.js @@ -1,7 +1,26 @@ import { client } from '../plugins/Platform.js' +import { isDeepEqual } from '../utils/is.js' import { addEvt, cleanEvt, position, leftClick, stopAndPrevent, noop } from '../utils/event.js' import { clearSelection } from '../utils/private/selection.js' +function parseArg (arg) { + // duration in ms, touch in pixels, mouse in pixels + const data = [600, 5, 7] + + if (typeof arg === 'string' && arg.length > 0) { + arg.split(':').forEach((val, index) => { + const v = parseInt(val, 10) + v && (data[index] = v) + }) + } + + return { + duration: data[0], + touchSensitivity: data[1], + mouseSensitivity: data[2] + } +} + function destroy (el) { const ctx = el.__qtouchhold if (ctx !== void 0) { @@ -15,31 +34,50 @@ function destroy (el) { } } +function configureEvents (el, ctx, modifiers) { + if (ctx.modifiers.mouse !== modifiers.mouse || ctx.modifiers.mouseCapture !== modifiers.mouseCapture || ctx.modifiers.mousecapture !== modifiers.mousecapture) { + ctx.modifiers.mouse === true && cleanEvt(ctx, 'main_mouse') + + modifiers.mouse === true && addEvt(ctx, 'main_mouse', [ + [ el, 'mousedown', 'mouseStart', `passive${modifiers.mouseCapture === true || modifiers.mousecapture === true ? 'Capture' : ''}` ] + ]) + } + + if (client.has.touch === true && ctx.modifiers.capture !== modifiers.capture) { + cleanEvt(ctx, 'main_touch') + + addEvt(ctx, 'main_touch', [ + [ el, 'touchstart', 'touchStart', `passive${modifiers.capture === true ? 'Capture' : ''}` ], + [ el, 'touchmove', 'noop', 'passiveCapture' ] + ]) + } + + ctx.modifiers = modifiers +} + export default { name: 'touch-hold', - bind (el, binding) { + bind (el, { modifiers, arg, value }) { if (el.__qtouchhold !== void 0) { destroy(el) el.__qtouchhold_destroyed = true } - const { modifiers } = binding + const ctx = { + handler: value, + arg, + modifiers: { capture: null }, // make sure touch listeners are initiated - // early return, we don't need to do anything - if (modifiers.mouse !== true && client.has.touch !== true) { - return - } + ...parseArg(arg), - const ctx = { - handler: binding.value, noop, mouseStart (evt) { if (typeof ctx.handler === 'function' && leftClick(evt) === true) { addEvt(ctx, 'temp', [ - [ document, 'mousemove', 'move', 'passiveCapture' ], - [ document, 'click', 'end', 'notPassiveCapture' ] + [document, 'mousemove', 'move', 'passiveCapture'], + [document, 'click', 'end', 'notPassiveCapture'] ]) ctx.start(evt, true) } @@ -49,9 +87,9 @@ export default { if (evt.target !== void 0 && typeof ctx.handler === 'function') { const target = evt.target addEvt(ctx, 'temp', [ - [ target, 'touchmove', 'move', 'passiveCapture' ], - [ target, 'touchcancel', 'end', 'notPassiveCapture' ], - [ target, 'touchend', 'end', 'notPassiveCapture' ] + [target, 'touchmove', 'move', 'passiveCapture'], + [target, 'touchcancel', 'end', 'notPassiveCapture'], + [target, 'touchend', 'end', 'notPassiveCapture'] ]) ctx.start(evt) } @@ -125,35 +163,26 @@ export default { } } - // duration in ms, touch in pixels, mouse in pixels - const data = [600, 5, 7] - - if (typeof binding.arg === 'string' && binding.arg.length > 0) { - binding.arg.split(':').forEach((val, index) => { - const v = parseInt(val, 10) - v && (data[index] = v) - }) - } - - [ ctx.duration, ctx.touchSensitivity, ctx.mouseSensitivity ] = data - el.__qtouchhold = ctx - modifiers.mouse === true && addEvt(ctx, 'main', [ - [ el, 'mousedown', 'mouseStart', `passive${modifiers.mouseCapture === true || modifiers.mousecapture === true ? 'Capture' : ''}` ] - ]) - - client.has.touch === true && addEvt(ctx, 'main', [ - [ el, 'touchstart', 'touchStart', `passive${modifiers.capture === true ? 'Capture' : ''}` ], - [ el, 'touchend', 'noop', 'passiveCapture' ] - ]) + configureEvents(el, ctx, modifiers) }, - update (el, binding) { + update (el, { modifiers, arg, value, oldValue }) { const ctx = el.__qtouchhold - if (ctx !== void 0 && binding.oldValue !== binding.value) { - typeof binding.value !== 'function' && ctx.end() - ctx.handler = binding.value + if (ctx !== void 0) { + if (oldValue !== value) { + typeof value !== 'function' && ctx.end() + ctx.handler = value + } + + if (ctx.arg !== arg) { + Object.assign(ctx, parseArg(arg)) + } + + if (isDeepEqual(ctx.modifiers, modifiers) !== true) { + configureEvents(el, ctx, modifiers) + } } }, diff --git a/ui/src/directives/TouchHold.json b/ui/src/directives/TouchHold.json index 179dfb9666b0..85f882dd80ae 100644 --- a/ui/src/directives/TouchHold.json +++ b/ui/src/directives/TouchHold.json @@ -55,6 +55,7 @@ "arg": { "type": "String", "desc": "x:y:z, where x is the amount of time to wait (in milliseconds), y is the touch event sensitivity (in pixels) and z is the mouse event sensitivity (in pixels)", + "reactive": true, "default": "600:5:7", "examples": [ "v-touch-hold:400=\"fnToCall\"", @@ -66,17 +67,20 @@ "modifiers": { "capture": { "type": "Boolean", - "desc": "Use capture for touchstart event" + "desc": "Use capture for touchstart event", + "reactive": true }, "mouse": { "type": "Boolean", - "desc": "Listen for mouse events too" + "desc": "Listen for mouse events too", + "reactive": true }, "mouseCapture": { "type": "Boolean", - "desc": "Use capture for mousedown event" + "desc": "Use capture for mousedown event", + "reactive": true } } } diff --git a/ui/src/directives/TouchPan.js b/ui/src/directives/TouchPan.js index a63bce951d5c..8440c321d8b6 100644 --- a/ui/src/directives/TouchPan.js +++ b/ui/src/directives/TouchPan.js @@ -1,4 +1,5 @@ import { client } from '../plugins/Platform.js' +import { isDeepEqual } from '../utils/is.js' import { getModifierDirections, shouldStart } from '../utils/private/touch.js' import { addEvt, cleanEvt, position, leftClick, prevent, stop, stopAndPrevent, preventDraggable, noop } from '../utils/event.js' import { clearSelection } from '../utils/private/selection.js' @@ -122,7 +123,8 @@ function destroy (el) { // the condition is also checked in the start of function but we avoid the call ctx.event !== void 0 && ctx.end() - cleanEvt(ctx, 'main') + cleanEvt(ctx, 'main_mouse') + cleanEvt(ctx, 'main_touch') cleanEvt(ctx, 'temp') client.is.firefox === true && preventDraggable(el, false) @@ -132,36 +134,52 @@ function destroy (el) { } } +function configureEvents (el, ctx, modifiers) { + if (ctx.modifiers.mouse !== modifiers.mouse || ctx.modifiers.mouseCapture !== modifiers.mouseCapture || ctx.modifiers.mousecapture !== modifiers.mousecapture) { + ctx.modifiers.mouse === true && cleanEvt(ctx, 'main_mouse') + + modifiers.mouse === true && addEvt(ctx, 'main_mouse', [ + [ el, 'mousedown', 'mouseStart', `passive${modifiers.mouseCapture === true || modifiers.mousecapture === true ? 'Capture' : ''}` ] + ]) + } + + if (client.has.touch === true && ctx.modifiers.capture !== modifiers.capture) { + cleanEvt(ctx, 'main_touch') + + addEvt(ctx, 'main_touch', [ + [ el, 'touchstart', 'touchStart', `passive${modifiers.capture === true ? 'Capture' : ''}` ], + [ el, 'touchmove', 'noop', 'notPassiveCapture' ] // cannot be passive (ex: iOS scroll) + ]) + } + + ctx.modifiers = modifiers +} + +function handleEvent (ctx, evt, mouseEvent) { + if (ctx.modifiers.mouse === true && mouseEvent === true) { + stopAndPrevent(evt) + } + else { + ctx.modifiers.stop === true && stop(evt) + ctx.modifiers.prevent === true && prevent(evt) + } +} + let uid = 0 export default { name: 'touch-pan', - bind (el, { value, modifiers }) { + bind (el, { modifiers, value }) { if (el.__qtouchpan !== void 0) { destroy(el) el.__qtouchpan_destroyed = true } - // early return, we don't need to do anything - if (modifiers.mouse !== true && client.has.touch !== true) { - return - } - - function handleEvent (evt, mouseEvent) { - if (modifiers.mouse === true && mouseEvent === true) { - stopAndPrevent(evt) - } - else { - modifiers.stop === true && stop(evt) - modifiers.prevent === true && prevent(evt) - } - } - const ctx = { uid: 'qvtp_' + (uid++), handler: value, - modifiers, + modifiers: { capture: null }, // make sure touch listeners are initiated direction: getModifierDirections(modifiers), noop, @@ -201,7 +219,7 @@ export default { * Stop propagation so possible upper v-touch-pan don't catch this as well; * If we're not the target (based on modifiers), we'll re-emit the event later */ - if (mouseEvent === true || modifiers.stop === true) { + if (mouseEvent === true || ctx.modifiers.stop === true) { /* * are we directly switching to detected state? * clone event only otherwise @@ -253,7 +271,7 @@ export default { const isMouseEvt = ctx.event.mouse === true const start = () => { - handleEvent(evt, isMouseEvt) + handleEvent(ctx, evt, isMouseEvt) let cursor if (ctx.modifiers.preserveCursor !== true && ctx.modifiers.preservecursor !== true) { @@ -292,7 +310,7 @@ export default { } if (ctx.event.detected === true) { - ctx.event.isFirst !== true && handleEvent(evt, ctx.event.mouse) + ctx.event.isFirst !== true && handleEvent(ctx, evt, ctx.event.mouse) const { payload, synthetic } = getChanges(evt, ctx, false) @@ -387,21 +405,22 @@ export default { el.__qtouchpan = ctx - modifiers.mouse === true && addEvt(ctx, 'main', [ - [ el, 'mousedown', 'mouseStart', `passive${modifiers.mouseCapture === true || modifiers.mousecapture === true ? 'Capture' : ''}` ] - ]) - - client.has.touch === true && addEvt(ctx, 'main', [ - [ el, 'touchstart', 'touchStart', `passive${modifiers.capture === true ? 'Capture' : ''}` ], - [ el, 'touchmove', 'noop', 'notPassiveCapture' ] // cannot be passive (ex: iOS scroll) - ]) + configureEvents(el, ctx, modifiers) }, - update (el, { oldValue, value }) { + update (el, { modifiers, value, oldValue }) { const ctx = el.__qtouchpan - if (ctx !== void 0 && oldValue !== value) { - typeof value !== 'function' && ctx.end() - ctx.handler = value + if (ctx !== void 0) { + if (oldValue !== value) { + typeof value !== 'function' && ctx.end() + ctx.handler = value + } + + if (isDeepEqual(ctx.modifiers, modifiers) !== true) { + configureEvents(el, ctx, modifiers) + + ctx.direction = getModifierDirections(modifiers) + } } }, diff --git a/ui/src/directives/TouchPan.json b/ui/src/directives/TouchPan.json index ddb08f901822..1c08f4d76ceb 100644 --- a/ui/src/directives/TouchPan.json +++ b/ui/src/directives/TouchPan.json @@ -116,68 +116,81 @@ "modifiers": { "stop": { "type": "Boolean", - "desc": "Stop event propagation for touch events" + "desc": "Stop event propagation for touch events", + "reactive": true }, "prevent": { "type": "Boolean", - "desc": "Calls event.preventDefault() for touch events" + "desc": "Calls event.preventDefault() for touch events", + "reactive": true }, "capture": { "type": "Boolean", - "desc": "Use capture for touchstart event" + "desc": "Use capture for touchstart event", + "reactive": true }, "mouse": { "type": "Boolean", - "desc": "Listen for mouse events too" + "desc": "Listen for mouse events too", + "reactive": true }, "mouseCapture": { "type": "Boolean", - "desc": "Use capture for mousedown event" + "desc": "Use capture for mousedown event", + "reactive": true }, "mouseAllDir": { "type": "Boolean", - "desc": "Ignore initial mouse move direction (do not abort if the first mouse move is in an unaccepted direction)" + "desc": "Ignore initial mouse move direction (do not abort if the first mouse move is in an unaccepted direction)", + "reactive": true }, "preserveCursor": { "type": "Boolean", "desc": "Prevent the mouse cursor from automatically displaying as grabbing when panning", + "reactive": true, "addedIn": "v1.13" }, "horizontal": { "type": "Boolean", - "desc": "Catch horizontal (left/right) movement" + "desc": "Catch horizontal (left/right) movement", + "reactive": true }, "vertical": { "type": "Boolean", - "desc": "Catch vertical (up/down) movement" + "desc": "Catch vertical (up/down) movement", + "reactive": true }, "up": { "type": "Boolean", - "desc": "Catch panning to up" + "desc": "Catch panning to up", + "reactive": true }, "right": { "type": "Boolean", - "desc": "Catch panning to right" + "desc": "Catch panning to right", + "reactive": true }, "down": { "type": "Boolean", - "desc": "Catch panning to down" + "desc": "Catch panning to down", + "reactive": true }, "left": { "type": "Boolean", - "desc": "Catch panning to left" + "desc": "Catch panning to left", + "reactive": true } } } diff --git a/ui/src/directives/TouchRepeat.js b/ui/src/directives/TouchRepeat.js index f80ac2900dab..3b045d4794ef 100644 --- a/ui/src/directives/TouchRepeat.js +++ b/ui/src/directives/TouchRepeat.js @@ -1,4 +1,5 @@ import { client } from '../plugins/Platform.js' +import { isDeepEqual } from '../utils/is.js' import { addEvt, cleanEvt, position, leftClick, stopAndPrevent, noop } from '../utils/event.js' import { clearSelection } from '../utils/private/selection.js' import { isKeyCode } from '../utils/private/key-composition.js' @@ -24,12 +25,25 @@ function shouldEnd (evt, origin) { Math.abs(top - origin.top) >= 7 } +function parseArg (arg) { + const durations = typeof arg === 'string' && arg.length > 0 + ? arg.split(':').map(val => parseInt(val, 10)) + : [0, 600, 300] + + return { + durations, + durationsLast: durations.length - 1 + } +} + function destroy (el) { const ctx = el.__qtouchrepeat if (ctx !== void 0) { clearTimeout(ctx.timer) - cleanEvt(ctx, 'main') + cleanEvt(ctx, 'main_mouse') + cleanEvt(ctx, 'main_touch') + cleanEvt(ctx, 'main_kbd') cleanEvt(ctx, 'temp') ctx.styleCleanup !== void 0 && ctx.styleCleanup() @@ -38,41 +52,60 @@ function destroy (el) { } } +function configureEvents (el, ctx, modifiers) { + if (ctx.modifiers.mouse !== modifiers.mouse || ctx.modifiers.mouseCapture !== modifiers.mouseCapture || ctx.modifiers.mousecapture !== modifiers.mousecapture) { + ctx.modifiers.mouse === true && cleanEvt(ctx, 'main_mouse') + + modifiers.mouse === true && addEvt(ctx, 'main_mouse', [ + [ el, 'mousedown', 'mouseStart', `passive${modifiers.mouseCapture === true || modifiers.mousecapture === true ? 'Capture' : ''}` ] + ]) + } + + if (client.has.touch === true && ctx.modifiers.capture !== modifiers.capture) { + cleanEvt(ctx, 'main_touch') + + addEvt(ctx, 'main_touch', [ + [ el, 'touchstart', 'touchStart', `passive${modifiers.capture === true ? 'Capture' : ''}` ], + [ el, 'touchmove', 'noop', 'passiveCapture' ] + ]) + } + + const keyboard = Object.keys(modifiers).reduce((acc, key) => { + if (keyRegex.test(key) === true) { + const keyCode = isNaN(parseInt(key, 10)) ? keyCodes[key.toLowerCase()] : parseInt(key, 10) + keyCode >= 0 && acc.push(keyCode) + } + return acc + }, []) + + if ((ctx.keyboard.length > 0) !== (keyboard.length > 0) || ctx.modifiers.keyCapture !== modifiers.keyCapture || ctx.modifiers.keycapture !== modifiers.keycapture) { + ctx.keyboard.length > 0 && cleanEvt(ctx, 'main_kbd') + + keyboard.length > 0 && addEvt(ctx, 'main_kbd', [ + [ el, 'keydown', 'keyboardStart', `notPassive${modifiers.keyCapture === true || modifiers.keycapture === true ? 'Capture' : ''}` ] + ]) + } + + ctx.modifiers = modifiers + ctx.keyboard = keyboard +} + export default { name: 'touch-repeat', - bind (el, { modifiers, value, arg }) { + bind (el, { modifiers, arg, value }) { if (el.__qtouchrepeat !== void 0) { destroy(el) el.__qtouchrepeat_destroyed = true } - const keyboard = Object.keys(modifiers).reduce((acc, key) => { - if (keyRegex.test(key) === true) { - const keyCode = isNaN(parseInt(key, 10)) ? keyCodes[key.toLowerCase()] : parseInt(key, 10) - keyCode >= 0 && acc.push(keyCode) - } - return acc - }, []) - - // early return, we don't need to do anything - if ( - modifiers.mouse !== true && - client.has.touch !== true && - keyboard.length === 0 - ) { - return - } - - const durations = typeof arg === 'string' && arg.length > 0 - ? arg.split(':').map(val => parseInt(val, 10)) - : [0, 600, 300] - - const durationsLast = durations.length - 1 - const ctx = { - keyboard, + keyboard: [], handler: value, + arg, + modifiers: { capture: null }, // make sure touch listeners are initiated + + ...parseArg(arg), noop, @@ -87,8 +120,8 @@ export default { }, keyboardStart (evt) { - if (typeof ctx.handler === 'function' && isKeyCode(evt, keyboard) === true) { - if (durations[0] === 0 || ctx.event !== void 0) { + if (typeof ctx.handler === 'function' && isKeyCode(evt, ctx.keyboard) === true) { + if (ctx.durations[0] === 0 || ctx.event !== void 0) { stopAndPrevent(evt) el.focus() if (ctx.event !== void 0) { @@ -179,18 +212,18 @@ export default { ctx.handler(ctx.event) - const index = durationsLast < ctx.event.repeatCount - ? durationsLast + const index = ctx.durationsLast < ctx.event.repeatCount + ? ctx.durationsLast : ctx.event.repeatCount - ctx.timer = setTimeout(fn, durations[index]) + ctx.timer = setTimeout(fn, ctx.durations[index]) } - if (durations[0] === 0) { + if (ctx.durations[0] === 0) { fn() } else { - ctx.timer = setTimeout(fn, durations[0]) + ctx.timer = setTimeout(fn, ctx.durations[0]) } }, @@ -217,25 +250,24 @@ export default { el.__qtouchrepeat = ctx - modifiers.mouse === true && addEvt(ctx, 'main', [ - [ el, 'mousedown', 'mouseStart', `passive${modifiers.mouseCapture === true || modifiers.mousecapture === true ? 'Capture' : ''}` ] - ]) - - client.has.touch === true && addEvt(ctx, 'main', [ - [ el, 'touchstart', 'touchStart', `passive${modifiers.capture === true ? 'Capture' : ''}` ], - [ el, 'touchend', 'noop', 'passiveCapture' ] - ]) - - keyboard.length > 0 && addEvt(ctx, 'main', [ - [ el, 'keydown', 'keyboardStart', `notPassive${modifiers.keyCapture === true || modifiers.keycapture === true ? 'Capture' : ''}` ] - ]) + configureEvents(el, ctx, modifiers) }, - update (el, { oldValue, value }) { + update (el, { modifiers, arg, value, oldValue }) { const ctx = el.__qtouchrepeat - if (ctx !== void 0 && oldValue !== value) { - typeof value !== 'function' && ctx.end() - ctx.handler = value + if (ctx !== void 0) { + if (oldValue !== value) { + typeof value !== 'function' && ctx.end() + ctx.handler = value + } + + if (ctx.arg !== arg) { + Object.assign(ctx, parseArg(arg)) + } + + if (isDeepEqual(ctx.modifiers, modifiers) !== true) { + configureEvents(el, ctx, modifiers) + } } }, diff --git a/ui/src/directives/TouchRepeat.json b/ui/src/directives/TouchRepeat.json index 6377a7967845..1b106e15c0c8 100644 --- a/ui/src/directives/TouchRepeat.json +++ b/ui/src/directives/TouchRepeat.json @@ -74,6 +74,7 @@ "arg": { "type": "String", "desc": "String of numbers (at least one number) separated by ':' which defines the amount of time to wait for 1st handler call, 2nd, 3rd and so on; All subsequent calls will use last value as time to wait until triggering", + "reactive": true, "default": "0:600:300", "examples": [ "v-touch-repeat:0:400=\"fnToCall\"" ] }, @@ -81,72 +82,86 @@ "modifiers": { "capture": { "type": "Boolean", - "desc": "Use capture for touchstart event" + "desc": "Use capture for touchstart event", + "reactive": true }, "mouse": { "type": "Boolean", - "desc": "Listen for mouse events too" + "desc": "Listen for mouse events too", + "reactive": true }, "mouseCapture": { "type": "Boolean", - "desc": "Use capture for mousedown event" + "desc": "Use capture for mousedown event", + "reactive": true }, "keyCapture": { "type": "Boolean", - "desc": "Use capture for keydown event" + "desc": "Use capture for keydown event", + "reactive": true }, "esc": { "type": "Boolean", - "desc": "Catch ESC key" + "desc": "Catch ESC key", + "reactive": true }, "tab": { "type": "Boolean", - "desc": "Catch TAB key" + "desc": "Catch TAB key", + "reactive": true }, "enter": { "type": "Boolean", - "desc": "Catch ENTER key" + "desc": "Catch ENTER key", + "reactive": true }, "space": { "type": "Boolean", - "desc": "Catch SPACE key" + "desc": "Catch SPACE key", + "reactive": true }, "up": { "type": "Boolean", - "desc": "Catch UP arrow key" + "desc": "Catch UP arrow key", + "reactive": true }, "left": { "type": "Boolean", - "desc": "Catch LEFT arrow key" + "desc": "Catch LEFT arrow key", + "reactive": true }, "right": { "type": "Boolean", - "desc": "Catch RIGHT arrow key" + "desc": "Catch RIGHT arrow key", + "reactive": true }, "down": { "type": "Boolean", - "desc": "Catch DOWN key" + "desc": "Catch DOWN key", + "reactive": true }, "delete": { "type": "Boolean", - "desc": "Catch DELETE key" + "desc": "Catch DELETE key", + "reactive": true }, "[keycode]": { "type": "Number", "desc": "Key code to catch", + "reactive": true, "examples": [ "v-touch-repeat.68=\"fnToCall\"" ] } } diff --git a/ui/src/directives/TouchSwipe.js b/ui/src/directives/TouchSwipe.js index 8fe539d4607b..993479c19eb3 100644 --- a/ui/src/directives/TouchSwipe.js +++ b/ui/src/directives/TouchSwipe.js @@ -1,4 +1,5 @@ import { client } from '../plugins/Platform.js' +import { isDeepEqual } from '../utils/is.js' import { getModifierDirections, shouldStart } from '../utils/private/touch.js' import { addEvt, cleanEvt, position, leftClick, stopAndPrevent, preventDraggable, noop } from '../utils/event.js' import { clearSelection } from '../utils/private/selection.js' @@ -23,7 +24,8 @@ function destroy (el) { const ctx = el.__qtouchswipe if (ctx !== void 0) { - cleanEvt(ctx, 'main') + cleanEvt(ctx, 'main_mouse') + cleanEvt(ctx, 'main_touch') cleanEvt(ctx, 'temp') client.is.firefox === true && preventDraggable(el, false) @@ -33,27 +35,41 @@ function destroy (el) { } } +function configureEvents (el, ctx, modifiers) { + if (ctx.modifiers.mouse !== modifiers.mouse || ctx.modifiers.mouseCapture !== modifiers.mouseCapture || ctx.modifiers.mousecapture !== modifiers.mousecapture) { + ctx.modifiers.mouse === true && cleanEvt(ctx, 'main_mouse') + + modifiers.mouse === true && addEvt(ctx, 'main_mouse', [ + [ el, 'mousedown', 'mouseStart', `passive${modifiers.mouseCapture === true || modifiers.mousecapture === true ? 'Capture' : ''}` ] + ]) + } + + if (client.has.touch === true && ctx.modifiers.capture !== modifiers.capture) { + cleanEvt(ctx, 'main_touch') + + addEvt(ctx, 'main_touch', [ + [ el, 'touchstart', 'touchStart', `passive${modifiers.capture === true ? 'Capture' : ''}` ], + [ el, 'touchmove', 'noop', 'notPassiveCapture' ] // cannot be passive (ex: iOS scroll) + ]) + } + + ctx.modifiers = modifiers +} + export default { name: 'touch-swipe', - bind (el, { value, arg, modifiers }) { + bind (el, { modifiers, arg, value }) { if (el.__qtouchswipe !== void 0) { destroy(el) el.__qtouchswipe_destroyed = true } - // early return, we don't need to do anything - if (modifiers.mouse !== true && client.has.touch !== true) { - return - } - - const mouseCapture = modifiers.mouseCapture === true || modifiers.mousecapture === true ? 'Capture' : '' - const ctx = { handler: value, sensitivity: parseArg(arg), - - modifiers: modifiers, + arg, + modifiers: { capture: null }, // make sure touch listeners are initiated direction: getModifierDirections(modifiers), noop, @@ -61,7 +77,7 @@ export default { mouseStart (evt) { if (shouldStart(evt, ctx) && leftClick(evt)) { addEvt(ctx, 'temp', [ - [ document, 'mousemove', 'move', `notPassive${mouseCapture}` ], + [ document, 'mousemove', 'move', `notPassive${ctx.modifiers.mouseCapture === true ? 'Capture' : ''}` ], [ document, 'mouseup', 'end', 'notPassiveCapture' ] ]) ctx.start(evt, true) @@ -244,21 +260,26 @@ export default { el.__qtouchswipe = ctx - modifiers.mouse === true && addEvt(ctx, 'main', [ - [ el, 'mousedown', 'mouseStart', `passive${mouseCapture}` ] - ]) - - client.has.touch === true && addEvt(ctx, 'main', [ - [ el, 'touchstart', 'touchStart', `passive${modifiers.capture === true ? 'Capture' : ''}` ], - [ el, 'touchmove', 'noop', 'notPassiveCapture' ] // cannot be passive (ex: iOS scroll) - ]) + configureEvents(el, ctx, modifiers) }, - update (el, { oldValue, value }) { + update (el, { modifiers, arg, value, oldValue }) { const ctx = el.__qtouchswipe - if (ctx !== void 0 && oldValue !== value) { - typeof value !== 'function' && ctx.end() - ctx.handler = value + if (ctx !== void 0) { + if (oldValue !== value) { + typeof value !== 'function' && ctx.end() + ctx.handler = value + } + + if (ctx.arg !== arg) { + ctx.sensitivity = parseArg(arg) + } + + if (isDeepEqual(ctx.modifiers, modifiers) !== true) { + configureEvents(el, ctx, modifiers) + + ctx.direction = getModifierDirections(modifiers) + } } }, diff --git a/ui/src/directives/TouchSwipe.json b/ui/src/directives/TouchSwipe.json index 98034026ebe6..8af10592e342 100644 --- a/ui/src/directives/TouchSwipe.json +++ b/ui/src/directives/TouchSwipe.json @@ -60,6 +60,7 @@ "arg": { "type": "String", "desc": "x:y:z, where x is minimum velocity (dist/time; please use float without a dot, example: 6e-2 which is equivalent to 6 * 10^-2 = 0.06), y is minimum distance on first move on mobile, z is minimum distance on desktop until deciding if it's a swipe indeed", + "reactive": true, "default": "6e-2:6:50", "examples": [ "v-touch-swipe:7e-2:10:100=\"fnToCall\"" ] }, @@ -67,47 +68,56 @@ "modifiers": { "capture": { "type": "Boolean", - "desc": "Use capture for touchstart event" + "desc": "Use capture for touchstart event", + "reactive": true }, "mouse": { "type": "Boolean", - "desc": "Listen for mouse events too" + "desc": "Listen for mouse events too", + "reactive": true }, "mouseCapture": { "type": "Boolean", - "desc": "Use capture for mousedown event" + "desc": "Use capture for mousedown event", + "reactive": true }, "horizontal": { "type": "Boolean", - "desc": "Catch horizontal (left/right) movement" + "desc": "Catch horizontal (left/right) movement", + "reactive": true }, "vertical": { "type": "Boolean", - "desc": "Catch vertical (up/down) movement" + "desc": "Catch vertical (up/down) movement", + "reactive": true }, "up": { "type": "Boolean", - "desc": "Catch swipe to up" + "desc": "Catch swipe to up", + "reactive": true }, "right": { "type": "Boolean", - "desc": "Catch swipe to right" + "desc": "Catch swipe to right", + "reactive": true }, "down": { "type": "Boolean", - "desc": "Catch swipe to down" + "desc": "Catch swipe to down", + "reactive": true }, "left": { "type": "Boolean", - "desc": "Catch swipe to left" + "desc": "Catch swipe to left", + "reactive": true } } }