@cycle/dom
Advanced tools
Comparing version 8.0.0 to 8.1.0
@@ -103,3 +103,3 @@ "use strict"; | ||
function makeIsStrictlyInRootScope(rootList, namespace) { | ||
function makeIsStrictlyInRootScope(namespace) { | ||
var classIsForeign = function classIsForeign(c) { | ||
@@ -114,7 +114,3 @@ var matched = c.match(/cycle-scope-(\S+)/); | ||
return function isStrictlyInRootScope(leaf) { | ||
for (var el = leaf; el !== null; el = el.parentElement) { | ||
if (rootList.indexOf(el) >= 0) { | ||
return true; | ||
} | ||
for (var el = leaf; el; el = el.parentElement) { | ||
var split = String.prototype.split; | ||
@@ -133,3 +129,3 @@ var classList = el.classList || split.call(el.className, " "); | ||
function makeEventsSelector(element$) { | ||
function makeEventsSelector(rootEl$, namespace) { | ||
return function events(eventName) { | ||
@@ -141,8 +137,23 @@ var useCapture = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1]; | ||
} | ||
var isStrictlyInRootScope = makeIsStrictlyInRootScope(namespace); | ||
return element$.flatMapLatest(function (elements) { | ||
if (elements.length === 0) { | ||
return rootEl$.first().flatMapLatest(function (rootEl) { | ||
if (rootEl.length === 0) { | ||
return Rx.Observable.empty(); | ||
} | ||
return fromEvent(elements, eventName, useCapture); | ||
if (!namespace || namespace.length === 0) { | ||
return fromEvent(rootEl, eventName, useCapture); | ||
} | ||
var descendantSelector = namespace.join(" "); | ||
if (matchesSelector(rootEl, descendantSelector)) { | ||
// is root Element | ||
return fromEvent(rootEl, eventName, useCapture); | ||
} | ||
var topSelector = namespace.join(""); | ||
return fromEvent(rootEl, eventName, useCapture).filter(function (ev) { | ||
if (matchesSelector(ev.target, descendantSelector) || matchesSelector(ev.target, topSelector)) { | ||
return isStrictlyInRootScope(ev.target); | ||
} | ||
return false; | ||
}); | ||
}).share(); | ||
@@ -159,22 +170,20 @@ }; | ||
var namespace = this.namespace; | ||
var element$ = selector.trim() === ":root" ? rootEl$ : rootEl$.map(function (x) { | ||
var array = Array.isArray(x) ? x : [x]; | ||
return array.map(function (element) { | ||
var boundarySelector = "" + namespace.join(" ") + selector; | ||
if (matchesSelector(element, boundarySelector)) { | ||
return [element]; | ||
} else { | ||
var scopedSelector = (namespace.join(" ") + " " + selector).trim(); | ||
var nodeList = element.querySelectorAll(scopedSelector); | ||
return Array.prototype.slice.call(nodeList); | ||
} | ||
}).reduce(function (prev, curr) { | ||
return prev.concat(curr); | ||
}, []).filter(makeIsStrictlyInRootScope(array, namespace.concat(selector))); | ||
var trimmedSelector = selector.trim(); | ||
var childNamespace = trimmedSelector === ":root" ? namespace : namespace.concat(trimmedSelector); | ||
var element$ = rootEl$.map(function (rootEl) { | ||
if (childNamespace.join("") === "") { | ||
return rootEl; | ||
} | ||
var nodeList = rootEl.querySelectorAll(childNamespace.join(" ")); | ||
if (nodeList.length === 0) { | ||
nodeList = rootEl.querySelectorAll(childNamespace.join("")); | ||
} | ||
var array = Array.prototype.slice.call(nodeList); | ||
return array.filter(makeIsStrictlyInRootScope(childNamespace)); | ||
}); | ||
return { | ||
observable: element$, | ||
namespace: namespace.concat(selector), | ||
select: makeElementSelector(element$), | ||
events: makeEventsSelector(element$), | ||
namespace: childNamespace, | ||
select: makeElementSelector(rootEl$), | ||
events: makeEventsSelector(rootEl$, childNamespace), | ||
isolateSource: isolateSource, | ||
@@ -192,2 +201,10 @@ isolateSink: isolateSink | ||
function defaultOnErrorFn(msg) { | ||
if (console && console.error) { | ||
console.error(msg); | ||
} else { | ||
console.log(msg); | ||
} | ||
} | ||
function makeDOMDriver(container, options) { | ||
@@ -206,3 +223,3 @@ // Find and prepare the container | ||
var _ref3$onError = _ref3.onError; | ||
var onError = _ref3$onError === undefined ? console.error.bind(console) : _ref3$onError; | ||
var onError = _ref3$onError === undefined ? defaultOnErrorFn : _ref3$onError; | ||
@@ -218,5 +235,9 @@ if (typeof onError !== "function") { | ||
return { | ||
observable: rootElem$, | ||
namespace: [], | ||
select: makeElementSelector(rootElem$), | ||
dispose: disposable.dispose.bind(disposable), | ||
events: makeEventsSelector(rootElem$, []), | ||
dispose: function dispose() { | ||
return disposable.dispose(); | ||
}, | ||
isolateSource: isolateSource, | ||
@@ -223,0 +244,0 @@ isolateSink: isolateSink |
{ | ||
"name": "@cycle/dom", | ||
"version": "8.0.0", | ||
"version": "8.1.0", | ||
"author": "Andre Staltz", | ||
@@ -5,0 +5,0 @@ "description": "The standard DOM Driver for Cycle.js, based on virtual-dom, and other helpers", |
@@ -85,3 +85,3 @@ let Rx = require(`rx`) | ||
function makeIsStrictlyInRootScope(rootList, namespace) { | ||
function makeIsStrictlyInRootScope(namespace) { | ||
const classIsForeign = c => { | ||
@@ -96,7 +96,3 @@ const matched = c.match(/cycle-scope-(\S+)/) | ||
return function isStrictlyInRootScope(leaf) { | ||
for (let el = leaf; el !== null; el = el.parentElement) { | ||
if (rootList.indexOf(el) >= 0) { | ||
return true | ||
} | ||
for (let el = leaf; el; el = el.parentElement) { | ||
const split = String.prototype.split | ||
@@ -115,3 +111,3 @@ const classList = el.classList || split.call(el.className, ` `) | ||
function makeEventsSelector(element$) { | ||
function makeEventsSelector(rootEl$, namespace) { | ||
return function events(eventName, useCapture = false) { | ||
@@ -122,9 +118,28 @@ if (typeof eventName !== `string`) { | ||
} | ||
const isStrictlyInRootScope = makeIsStrictlyInRootScope(namespace) | ||
return element$.flatMapLatest(elements => { | ||
if (elements.length === 0) { | ||
return Rx.Observable.empty() | ||
} | ||
return fromEvent(elements, eventName, useCapture) | ||
}).share() | ||
return rootEl$ | ||
.first() | ||
.flatMapLatest(rootEl => { | ||
if (rootEl.length === 0) { | ||
return Rx.Observable.empty() | ||
} | ||
if (!namespace || namespace.length === 0) { | ||
return fromEvent(rootEl, eventName, useCapture) | ||
} | ||
const descendantSelector = namespace.join(` `) | ||
if (matchesSelector(rootEl, descendantSelector)) { // is root Element | ||
return fromEvent(rootEl, eventName, useCapture) | ||
} | ||
const topSelector = namespace.join(``) | ||
return fromEvent(rootEl, eventName, useCapture).filter(ev => { | ||
if (matchesSelector(ev.target, descendantSelector) || | ||
matchesSelector(ev.target, topSelector)) | ||
{ | ||
return isStrictlyInRootScope(ev.target) | ||
} | ||
return false | ||
}) | ||
}) | ||
.share() | ||
} | ||
@@ -141,22 +156,22 @@ } | ||
const namespace = this.namespace | ||
const element$ = selector.trim() === `:root` ? rootEl$ : rootEl$.map(x => { | ||
const array = Array.isArray(x) ? x : [x] | ||
return array.map(element => { | ||
const boundarySelector = `${namespace.join(` `)}${selector}` | ||
if (matchesSelector(element, boundarySelector)) { | ||
return [element] | ||
} else { | ||
const scopedSelector = `${namespace.join(` `)} ${selector}`.trim() | ||
const nodeList = element.querySelectorAll(scopedSelector) | ||
return Array.prototype.slice.call(nodeList) | ||
} | ||
}) | ||
.reduce((prev, curr) => prev.concat(curr), []) | ||
.filter(makeIsStrictlyInRootScope(array, namespace.concat(selector))) | ||
const trimmedSelector = selector.trim() | ||
const childNamespace = trimmedSelector === `:root` ? | ||
namespace : | ||
namespace.concat(trimmedSelector) | ||
const element$ = rootEl$.map(rootEl => { | ||
if (childNamespace.join(``) === ``) { | ||
return rootEl | ||
} | ||
let nodeList = rootEl.querySelectorAll(childNamespace.join(` `)) | ||
if (nodeList.length === 0) { | ||
nodeList = rootEl.querySelectorAll(childNamespace.join(``)) | ||
} | ||
const array = Array.prototype.slice.call(nodeList) | ||
return array.filter(makeIsStrictlyInRootScope(childNamespace)) | ||
}) | ||
return { | ||
observable: element$, | ||
namespace: namespace.concat(selector), | ||
select: makeElementSelector(element$), | ||
events: makeEventsSelector(element$), | ||
namespace: childNamespace, | ||
select: makeElementSelector(rootEl$), | ||
events: makeEventsSelector(rootEl$, childNamespace), | ||
isolateSource, | ||
@@ -175,2 +190,10 @@ isolateSink, | ||
function defaultOnErrorFn(msg) { | ||
if (console && console.error) { | ||
console.error(msg) | ||
} else { | ||
console.log(msg) | ||
} | ||
} | ||
function makeDOMDriver(container, options) { | ||
@@ -188,3 +211,3 @@ // Find and prepare the container | ||
} | ||
const {onError = console.error.bind(console)} = options || {} | ||
const {onError = defaultOnErrorFn} = options || {} | ||
if (typeof onError !== `function`) { | ||
@@ -203,5 +226,7 @@ throw new Error(`You provided an \`onError\` to makeDOMDriver but it was ` + | ||
return { | ||
observable: rootElem$, | ||
namespace: [], | ||
select: makeElementSelector(rootElem$), | ||
dispose: disposable.dispose.bind(disposable), | ||
events: makeEventsSelector(rootElem$, []), | ||
dispose: () => disposable.dispose(), | ||
isolateSource, | ||
@@ -208,0 +233,0 @@ isolateSink, |
@@ -37,25 +37,14 @@ 'use strict'; | ||
let isolatedDOMSource = sources.DOM.isolateSource(sources.DOM, 'foo'); | ||
// Make assertions | ||
isolatedDOMSource.select('.bar').events('click').subscribe(ev => { | ||
assert.strictEqual(ev.type, 'click'); | ||
assert.strictEqual(ev.target.textContent, 'Correct'); | ||
isolatedDOMSource.select('.bar').observable.skip(1).take(1).subscribe(elements => { | ||
assert.strictEqual(elements.length, 1); | ||
const correctElement = elements[0]; | ||
assert.notStrictEqual(correctElement, null); | ||
assert.notStrictEqual(typeof correctElement, 'undefined'); | ||
assert.strictEqual(correctElement.tagName, 'H4'); | ||
assert.strictEqual(correctElement.textContent, 'Correct'); | ||
sources.dispose(); | ||
done(); | ||
}); | ||
sources.DOM.select(':root').observable.skip(1).take(1) | ||
.subscribe(function (root) { | ||
let wrongElement = root.querySelector('.bar'); | ||
let correctElement = root.querySelector('.cycle-scope-foo .bar'); | ||
assert.notStrictEqual(wrongElement, null); | ||
assert.notStrictEqual(correctElement, null); | ||
assert.notStrictEqual(typeof wrongElement, 'undefined'); | ||
assert.notStrictEqual(typeof correctElement, 'undefined'); | ||
assert.strictEqual(wrongElement.tagName, 'H2'); | ||
assert.strictEqual(correctElement.tagName, 'H4'); | ||
assert.doesNotThrow(function () { | ||
wrongElement.click(); | ||
setTimeout(() => correctElement.click(), 5); | ||
}); | ||
done(); | ||
}); | ||
}); | ||
@@ -62,0 +51,0 @@ |
@@ -9,3 +9,3 @@ /** @jsx hJSX */ | ||
let Rx = require('rx'); | ||
let {h, svg, div, p, span, h2, h3, h4, hJSX, select, option, makeDOMDriver} = CycleDOM; | ||
let {h, svg, div, input, p, span, h2, h3, h4, hJSX, select, option, makeDOMDriver} = CycleDOM; | ||
@@ -273,2 +273,33 @@ function createRenderTarget(id = null) { | ||
it('should catch interaction events without prior select()', function (done) { | ||
function app() { | ||
return { | ||
DOM: Rx.Observable.just(div('.parent', [ | ||
h3('.myelementclass', 'Foobar') | ||
])) | ||
}; | ||
} | ||
let {sinks, sources} = Cycle.run(app, { | ||
DOM: makeDOMDriver(createRenderTarget()) | ||
}); | ||
// Make assertions | ||
sources.DOM.events('click').subscribe(ev => { | ||
assert.strictEqual(ev.type, 'click'); | ||
assert.strictEqual(ev.target.textContent, 'Foobar'); | ||
sources.dispose(); | ||
done(); | ||
}); | ||
sources.DOM.select(':root').observable.skip(1).take(1).subscribe(function (root) { | ||
let myElement = root.querySelector('.myelementclass'); | ||
assert.notStrictEqual(myElement, null); | ||
assert.notStrictEqual(typeof myElement, 'undefined'); | ||
assert.strictEqual(myElement.tagName, 'H3'); | ||
assert.doesNotThrow(function () { | ||
myElement.click(); | ||
}); | ||
}); | ||
}); | ||
it('should catch interaction events coming from wrapped View', function (done) { | ||
@@ -466,2 +497,115 @@ // Make a View reactively imitating another View | ||
it('should catch interaction events from future elements', function (done) { | ||
function app() { | ||
return { | ||
DOM: Rx.Observable.concat( | ||
Rx.Observable.just(h2('.blesh', 'Blesh')), | ||
Rx.Observable.just(h3('.blish', 'Blish')).delay(100), | ||
Rx.Observable.just(h4('.blosh', 'Blosh')).delay(100), | ||
) | ||
}; | ||
} | ||
let {sinks, sources} = Cycle.run(app, { | ||
DOM: makeDOMDriver(createRenderTarget('parent-002')) | ||
}); | ||
// Make assertions | ||
sources.DOM.select('.blosh').events('click').subscribe(ev => { | ||
assert.strictEqual(ev.type, 'click'); | ||
assert.strictEqual(ev.target.textContent, 'Blosh'); | ||
sources.dispose(); | ||
done(); | ||
}); | ||
sources.DOM.select(':root').observable.skip(3).take(1) | ||
.subscribe(function (root) { | ||
let myElement = root.querySelector('.blosh'); | ||
assert.notStrictEqual(myElement, null); | ||
assert.notStrictEqual(typeof myElement, 'undefined'); | ||
assert.strictEqual(myElement.tagName, 'H4'); | ||
assert.strictEqual(myElement.textContent, 'Blosh'); | ||
assert.doesNotThrow(function () { | ||
myElement.click(); | ||
}); | ||
}); | ||
}); | ||
it('should catch a non-bubbling click event with useCapture', function (done) { | ||
function app() { | ||
return { | ||
DOM: Rx.Observable.just(div('.parent', [ | ||
div('.clickable', 'Hello') | ||
])) | ||
} | ||
} | ||
function click(el) { | ||
let ev = document.createEvent(`MouseEvent`) | ||
ev.initMouseEvent( | ||
`click`, | ||
false /* bubble */, true /* cancelable */, | ||
window, null, | ||
0, 0, 0, 0, /* coordinates */ | ||
false, false, false, false, /* modifier keys */ | ||
0 /*left*/, null | ||
) | ||
el.dispatchEvent(ev) | ||
} | ||
let {sinks, sources} = Cycle.run(app, { | ||
DOM: makeDOMDriver(createRenderTarget()) | ||
}); | ||
sources.DOM.select('.clickable').events('click', true).subscribe(ev => { | ||
assert.strictEqual(ev.type, 'click'); | ||
assert.strictEqual(ev.target.tagName, 'DIV'); | ||
assert.strictEqual(ev.target.className, 'clickable'); | ||
assert.strictEqual(ev.target.textContent, 'Hello'); | ||
done(); | ||
}); | ||
sources.DOM.select('.clickable').events('click', false).subscribe(assert.fail); | ||
sources.DOM.select(':root').observable.skip(1).take(1).subscribe(root => { | ||
const clickable = root.querySelector('.clickable'); | ||
click(clickable); | ||
}); | ||
}); | ||
// This test does not work if and only if the browser is unfocused in the | ||
// operating system. In some browsers in SauceLabs, this test would always | ||
// fail for that reason. Until we find how to force the browser to be | ||
// focused, we can't run this test. | ||
it.skip('should catch a blur event with useCapture', function (done) { | ||
function app() { | ||
return { | ||
DOM: Rx.Observable.just(div('.parent', [ | ||
input('.correct', {type: 'text'}, []), | ||
input('.wrong', {type: 'text'}, []), | ||
input('.dummy', {type: 'text'}) | ||
])) | ||
} | ||
} | ||
let {sinks, sources} = Cycle.run(app, { | ||
DOM: makeDOMDriver(createRenderTarget()) | ||
}); | ||
sources.DOM.select('.correct').events('blur', true).subscribe(ev => { | ||
assert.strictEqual(ev.type, 'blur'); | ||
assert.strictEqual(ev.target.className, 'correct'); | ||
done(); | ||
}) | ||
sources.DOM.select('.wrong').events('blur', false).subscribe(assert.fail) | ||
sources.DOM.select(':root').observable.skip(1).take(1).subscribe(root => { | ||
const correct = root.querySelector('.correct'); | ||
const wrong = root.querySelector('.wrong'); | ||
const dummy = root.querySelector('.dummy'); | ||
setTimeout(() => wrong.focus(), 50); | ||
setTimeout(() => dummy.focus(), 100); | ||
setTimeout(() => correct.focus(), 150); | ||
setTimeout(() => dummy.focus(), 200); | ||
}); | ||
}); | ||
describe('DOM.select()', function () { | ||
@@ -468,0 +612,0 @@ it('should be an object with observable and events()', function (done) { |
@@ -68,9 +68,4 @@ 'use strict'; | ||
let subscribeExecuted = false; | ||
userEvents.select('.impossible').events('scroll').subscribe(ev => { | ||
subscribeExecuted = true; | ||
}); | ||
setTimeout(() => { | ||
assert.strictEqual(subscribeExecuted, false); | ||
done(); | ||
}, 1000); | ||
userEvents.select('.impossible').events('scroll') | ||
.subscribe(assert.fail, assert.fail, done); | ||
}); | ||
@@ -85,10 +80,5 @@ | ||
let subscribeExecuted = false; | ||
userEvents.select('.foo').observable.subscribe(ev => { | ||
subscribeExecuted = true; | ||
}); | ||
setTimeout(() => { | ||
assert.strictEqual(subscribeExecuted, false); | ||
done(); | ||
}, 1000); | ||
userEvents.select('.foo').observable | ||
.subscribe(assert.fail, assert.fail, done); | ||
}); | ||
}); |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
346488
6634