diff --git a/src/core/events.js b/src/core/events.js index 182f339..b2d2b04 100644 --- a/src/core/events.js +++ b/src/core/events.js @@ -38,6 +38,9 @@ Monocle.Events.deafen = function (elem, evtType, fn, useCapture) { } +// Register a series of functions to listen for the start, move, end +// events of a mouse or touch interaction. +// // 'fns' argument is an object like: // // { @@ -49,6 +52,12 @@ Monocle.Events.deafen = function (elem, evtType, fn, useCapture) { // // All of the functions in this object are optional. // +// Each function is passed the event, with additional generic info about the +// cursor/touch position: +// +// event.m.offsetX (& offsetY) -- relative to top-left of document +// event.m.registrantX (& registrantY) -- relative to top-left of elem +// // 'options' argument: // // { @@ -93,11 +102,11 @@ Monocle.Events.listenForContact = function (elem, fns, options) { var r; if (elem.getBoundingClientRect) { var er = elem.getBoundingClientRect(); - var dr = document.body.getBoundingClientRect(); + var dr = document.documentElement.getBoundingClientRect(); r = { left: er.left - dr.left, top: er.top - dr.top }; } else { r = { left: elem.offsetLeft, top: elem.offsetTop } - while (elem = elem.parentNode) { + while (elem = elem.offsetParent) { if (elem.offsetLeft || elem.offsetTop) { r.left += elem.offsetLeft; r.top += elem.offsetTop; diff --git a/test/experimental/font-sizing/content.html b/test/experimental/font-sizing/content.html new file mode 100644 index 0000000..6759696 --- /dev/null +++ b/test/experimental/font-sizing/content.html @@ -0,0 +1,44 @@ + + + + Content + + + + +

I

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. + Duis nec sapien libero. +

+

+ Change body font size using className. +

+

+ Donec quis felis venenatis diam elementum pellentesque. Pellentesque ullamcorper, urna ac bibendum varius, odio est dictum purus, non euismod diam eros ac turpis. Sed vehicula semper nisi, id elementum orci semper accumsan. Morbi at neque id sapien eleifend hendrerit. Mauris interdum lectus vitae elit dapibus at viverra lorem semper. Nullam lacinia, lacus ut vulputate rutrum, erat purus vehicula libero, non varius felis felis sit amet sapien. Cras ornare nisi non nunc convallis rhoncus. In semper est eget diam imperdiet vitae dignissim justo lacinia. Morbi lorem arcu, feugiat non suscipit vitae, adipiscing ut tellus. Proin eget sem mi. +

+

+ Aenean vitae elit augue. In blandit accumsan sem non aliquet. Quisque risus elit, pellentesque quis pharetra id, facilisis sit amet nibh. Aenean convallis tincidunt arcu quis posuere. Vivamus dictum facilisis felis eget vestibulum. Pellentesque lacinia lectus ac dolor lacinia eget commodo tellus mollis. In felis ante, ornare vitae fermentum vel, gravida sit amet sapien. Integer quis neque ut mi mollis rhoncus eget eget nisl. Nunc est elit, venenatis non lobortis in, interdum id orci. Phasellus malesuada, nibh non rutrum ornare, ligula risus viverra arcu, nec sagittis lectus est vel lorem. Aliquam et lectus id eros cursus scelerisque eu sed metus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer pharetra dapibus euismod. Aliquam laoreet pharetra diam in aliquet. Vivamus leo ligula, bibendum sit amet sodales ac, ultrices volutpat orci. Mauris nec velit vel lectus lacinia tempus eget a massa. +

+

+ Nullam faucibus, ante at ultrices tempor, nulla enim fringilla nunc, et vehicula risus tellus sit amet ipsum. Nullam ultrices imperdiet ligula, eget fermentum ante bibendum eu. Donec sollicitudin massa congue velit suscipit convallis. Quisque nec congue lacus. Etiam risus arcu, scelerisque et congue vitae, ullamcorper vitae felis. Etiam orci magna, viverra sit amet sodales at, sollicitudin eget dolor. Morbi congue diam sit amet magna fermentum vitae faucibus diam consequat. Phasellus et scelerisque lacus. Cras aliquam risus sed sem venenatis viverra. Nullam elit magna, vestibulum sed semper ut, tempus quis nisl. Pellentesque velit lectus, consequat vitae porttitor ac, adipiscing vel mi. Sed lobortis tincidunt dolor. Nulla placerat bibendum dui, pellentesque volutpat nisi tincidunt eu. Proin quis nulla nulla. Nullam interdum venenatis tempor. Nullam porta venenatis urna quis pharetra. Ut at purus id velit fermentum aliquam eget id justo. Sed vel urna ante. Suspendisse ligula dui, interdum quis venenatis vitae, egestas at turpis. Nulla eros elit, sollicitudin eu euismod vel, tincidunt non leo. +

+

+ Nullam quis augue in erat dictum iaculis sed a tellus. Suspendisse tristique varius nisi, eget eleifend dolor commodo vestibulum. In auctor consequat venenatis. In bibendum tincidunt ipsum, ac faucibus tortor dapibus mollis. Vivamus a lorem metus, et aliquam turpis. Duis gravida pulvinar interdum. Morbi ut quam eros. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Suspendisse nisl massa, interdum et elementum in, tincidunt id nisl. Nunc sodales risus et neque scelerisque porttitor. Etiam eget dui ipsum. Quisque vestibulum, quam sed adipiscing eleifend, arcu diam ornare leo, quis tincidunt nunc erat ultricies dui. Etiam libero tellus, adipiscing a volutpat sed, sagittis ac lorem. Integer sed erat nec nulla condimentum interdum. Integer mi metus, congue eget congue sed, accumsan at lectus. Nulla consequat venenatis odio sed tristique. +

+ + + diff --git a/test/experimental/font-sizing/index.html b/test/experimental/font-sizing/index.html new file mode 100644 index 0000000..e0fa53a --- /dev/null +++ b/test/experimental/font-sizing/index.html @@ -0,0 +1,92 @@ + + + + Proportional Font-sizing test + + + + + + + + ← Back to Guide +

Proportional Font-sizing test

+ +

+ Select a font-scale: + +

+ + + + +
+ +

+ Proportional font-sizing is tricky for a few reasons. + Firstly, we need to scale against the original font-size (in pixels) + for each element, so we have to traverse the DOM tree twice — + once to find font-size information and once to apply it. +

+

+ Then, once applied, the font-size is "fixed" — it will no longer + respond to changes in the parent. So class changes triggered by + scripts won't work as expected for font-sizing. +

+ +

+ The approach taken here mitigates the amount of behavioural modification + as a result of font-size changes. Primarily, whenever the scale is + unset (by choosing —), all font-size changes are reversed so that + full inheritance can again cascade. +

+ +

+ To see this: +

+ +
    +
  1. + Set scale to 100% → no change +
  2. +
  3. + Click 'Change body font size...' link → no change + because font-sizes do not cascade +
  4. +
  5. + Set scale to — → substantial increase to size of first para +
  6. +
  7. + Set scale to 100% → no change because calculating off new baseline +
  8. +
+ + + diff --git a/test/experimental/font-sizing/test.js b/test/experimental/font-sizing/test.js new file mode 100644 index 0000000..52ca7b2 --- /dev/null +++ b/test/experimental/font-sizing/test.js @@ -0,0 +1,82 @@ +(function () { + + var sweptElements = false; + + function init() { + var frame = document.getElementById('reader'); + var sel = document.getElementById('sizeSelect'); + sel.addEventListener('change', function (evt) { + adjustFontSize(frame.contentDocument, sel.value); + }); + } + + + function adjustFontSize(doc, scale) { + var elems = doc.getElementsByTagName('*'); + if (scale) { + scale = parseFloat(scale); + if (!sweptElements) { + sweepElements(doc, elems); + } + + // Iterate over each element, applying scale to the original + // font-size. If a proportional font sizing is already applied to + // the element, update existing cssText, otherwise append new cssText. + // + for (var j = 0, jj = elems.length; j < jj; ++j) { + var newFs = fsProperty(elems[j].pfsOriginal, scale); + if (elems[j].pfsApplied) { + replaceFontSizeInStyle(elems[j], newFs); + } else { + elems[j].style.cssText += newFs; + } + elems[j].pfsApplied = scale; + } + } else if (sweptElements) { + // Iterate over each element, removing proportional font-sizing flag + // and property from cssText. + for (var j = 0, jj = elems.length; j < jj; ++j) { + if (elems[j].pfsApplied) { + var oprop = elems[j].pfsOriginalProp; + var opropDec = oprop ? 'font-size: '+oprop+' ! important;' : ''; + replaceFontSizeInStyle(elems[j], opropDec); + elems[j].pfsApplied = null; + } + } + + // Establish new baselines in case classes have changed. + sweepElements(doc, elems); + } + } + + + function sweepElements(doc, elems) { + // Iterate over each element, looking at its font size and storing + // the original value against the element. + for (var i = 0, ii = elems.length; i < ii; ++i) { + var currStyle = doc.defaultView.getComputedStyle(elems[i], null); + var fs = parseFloat(currStyle.getPropertyValue('font-size')); + elems[i].pfsOriginal = fs; + elems[i].pfsOriginalProp = elems[i].style.fontSize; + } + sweptElements = true; + } + + + function fsProperty(orig, scale) { + return 'font-size: '+(orig*scale)+'px ! important;'; + } + + + function replaceFontSizeInStyle(elem, newProp) { + var lastFs = /font-size:[^;]/ + elem.style.cssText = elem.style.cssText.replace(lastFs, newProp); + } + + + window.onload = init; + window.onpageshow = function () { + document.getElementById('sizeSelect').value = ''; + } + +})(); diff --git a/test/experimental/moz-element-flipper/index.html b/test/experimental/moz-element-flipper/index.html new file mode 100644 index 0000000..ce0df11 --- /dev/null +++ b/test/experimental/moz-element-flipper/index.html @@ -0,0 +1,113 @@ + + + + + -moz-element Flipper test + + + + + + + ← Back to Guide +

-moz-element Flipper

+ +

+ Designed for recent versions of Firefox (11+?). + Click left half of page to go backward, right to go forward. +

+ +
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
+ +
+
+

+ Lorem ipsum dolor. +

+

+ The Mole had been working very hard all the morning, spring-cleaning his + little home. First with brooms, then with dusters; then on ladders and + steps and chairs, with a brush and a pail of whitewash; till he had dust + in his throat and eyes, and splashes of whitewash all over his black + fur, and an aching back and weary arms. Spring was moving in the air + above and in the earth below and around him, penetrating even his dark + and lowly little house with its spirit of divine discontent and longing. + It was small wonder, then, that he suddenly flung down his brush on the + floor, said 'Bother!' and 'O blow!' and also 'Hang spring-cleaning!' + and bolted out of the house without even waiting to put on his coat. + Something up above was calling him imperiously, and he made for + the steep little tunnel which answered in his case to the gaveled + carriage-drive owned by animals whose residences are nearer to the sun + and air. So he scraped and scratched and scrabbled and scrooged and + then he scrooged again and scrabbled and scratched and scraped, working + busily with his little paws and muttering to himself, 'Up we go! Up we + go!' till at last, pop! his snout came out into the sunlight, and he + found himself rolling in the warm grass of a great meadow. +

+

+ 'This is fine!' he said to himself. 'This is better than whitewashing!' + The sunshine struck hot on his fur, soft breezes caressed his heated + brow, and after the seclusion of the cellarage he had lived in so long + the carol of happy birds fell on his dulled hearing almost like a shout. + Jumping off all his four legs at once, in the joy of living and the + delight of spring without its cleaning, he pursued his way across the + meadow till he reached the hedge on the further side. +

+

+ 'Hold up!' said an elderly rabbit at the gap. 'Sixpence for the + privilege of passing by the private road!' He was bowled over in an + instant by the impatient and contemptuous Mole, who trotted along the + side of the hedge chaffing the other rabbits as they peeped hurriedly + from their holes to see what the row was about. 'Onion-sauce! + Onion-sauce!' he remarked jeeringly, and was gone before they could + think of a thoroughly satisfactory reply. Then they all started + grumbling at each other. 'How STUPID you are! Why didn't you tell + him——' 'Well, why didn't YOU say——' 'You might have reminded + him——' and so on, in the usual way; but, of course, it was then much + too late, as is always the case. +

+

+ It all seemed too good to be true. Hither and thither through the + meadows he rambled busily, along the hedgerows, across the + copses, finding everywhere birds building, flowers budding, leaves + thrusting—everything happy, and progressive, and occupied. And instead + of having an uneasy conscience pricking him and whispering 'whitewash!' + he somehow could only feel how jolly it was to be the only idle dog + among all these busy citizens. After all, the best part of a holiday + is perhaps not so much to be resting yourself, as to see all the other + fellows busy working. +

+

+ He thought his happiness was complete when, as he meandered aimlessly + along, suddenly he stood by the edge of a full-fed river. Never in + his life had he seen a river before—this sleek, sinuous, full-bodied + animal, chasing and chuckling, gripping things with a gurgle and + leaving them with a laugh, to fling itself on fresh playmates that shook + themselves free, and were caught and held again. All was a-shake and + a-shiver—glints and gleams and sparkles, rustle and swirl, chatter and + bubble. The Mole was bewitched, entranced, fascinated. By the side of + the river he trotted as one trots, when very small, by the side of a man + who holds one spell-bound by exciting stories; and when tired at + last, he sat on the bank, while the river still chattered on to him, + a babbling procession of the best stories in the world, sent from the + heart of the earth to be told at last to the insatiable sea. +

+
+
+ + diff --git a/test/experimental/moz-element-flipper/script.js b/test/experimental/moz-element-flipper/script.js new file mode 100644 index 0000000..c4fbd07 --- /dev/null +++ b/test/experimental/moz-element-flipper/script.js @@ -0,0 +1,71 @@ +(function () { + + var pos = 0; + var parts = {}; + + + function init() { + parts.mode = document.getElementById('mode'); + parts.frame = document.getElementById('frame'); + parts.imp1 = document.getElementById('imp1'); + parts.imp2 = document.getElementById('imp2'); + parts.imp3 = document.getElementById('imp3'); + parts.imp4 = document.getElementById('imp4'); + + parts.imp1.addEventListener('click', reverseJump, false); + parts.imp2.addEventListener('click', jump, false); + impBPs(0,1,2,3); + } + + + function jump() { + parts.mode.className = 'forwardTurn'; + parts.frame.className = 'pageTurn'; + setTimeout(nextPage, 620); + } + + + function reverseJump() { + parts.mode.className = 'reverseTurn'; + pos -= 340; + impBPs(0,2,1,3); + setTimeout(function () { + parts.frame.className = 'pageTurn'; + setTimeout(prevPage, 620); + }, 50); + } + + + function nextPage() { + parts.frame.className = ''; + pos += 340; + impBPs(0,1,2,3); + } + + + function prevPage() { + parts.mode.className = 'forwardTurn'; + parts.frame.className = ''; + impBPs(0,1,2,3); + } + + + function impBPs(a,b,c,d) { + setBP(parts.imp1, a) + setBP(parts.imp2, b) + setBP(parts.imp3, c) + setBP(parts.imp4, d) + } + + + function setBP(elem, n) { + var x = n*160+pos; + if (n >= 2) { x += 20; } + x = 0 - x; + elem.style.backgroundPosition = x+"px 0px"; + } + + + window.addEventListener('load', init, false); + +})(); diff --git a/test/experimental/moz-element-flipper/styles.css b/test/experimental/moz-element-flipper/styles.css new file mode 100644 index 0000000..a4690b1 --- /dev/null +++ b/test/experimental/moz-element-flipper/styles.css @@ -0,0 +1,118 @@ +hr.softBreak { + border: none; + border-top: 1px dotted #999; +} +section { + position: relative; + background: -moz-linear-gradient(0deg, #FFF, #555, #FFF, #555, #FFF); + background: -webkit-linear-gradient(0deg, #FFF, #555, #FFF, #555, #FFF); + width: 320px; + height: 380px; +} +.columns { + width: 2700px; + -moz-column-width: 300px; + -webkit-column-width: 300px; + -moz-column-gap: 20px; + -webkit-column-gap: 20px; + height: 380px; +} +.colCntr { + width: 0; + height: 0; + overflow: hidden; +} +.imposter { + background-color: white; + background-image: -moz-element(#origin); + background-image: -webkit-element(#origin); + background-repeat: no-repeat; + position: absolute; + width: 160px; + height: 380px; + top: 0; + left: 0; + -moz-backface-visibility: hidden; + -webkit-backface-visibility: hidden; + background-color: #FFF; +} +#imp1 { +} +#imp2 { +} +#imp3 { + -moz-transform: rotateY(180deg) translateZ(1px); + -webkit-transform: rotateY(180deg) translateZ(1px); +} +#imp4 { + left: 160px; +} +#gate { + position: absolute; + -moz-transform-origin: 0% 50%; + -webkit-transform-origin: 0% 50%; + -moz-transform-style: preserve-3d; + -webkit-transform-style: preserve-3d; + z-index: 1; + top: 0px; + left: 160px; + width: 160px; + height: 382px; + background: #FFF; +} + +.pageTurn #gate{ + -moz-transition: -moz-transform linear 600ms; + -webkit-transition: -webkit-transform linear 600ms; + -moz-transform: perspective(500px) rotateY(-180deg); + -webkit-transform: perspective(500px) rotateY(-180deg); +} + + +.forwardTurn #imp4 { + opacity: 0.25; +} + +.forwardTurn .pageTurn #imp1 { + -moz-transition: opacity linear 300ms 300ms; + -webkit-transition: opacity linear 300ms 300ms; + opacity: 0.25; +} + +.forwardTurn .pageTurn #imp4 { + -moz-transition: opacity linear 300ms; + -webkit-transition: opacity linear 300ms; + opacity: 1; +} + + +.reverseTurn #gate { + -moz-transform-origin: 100% 50%; + -webkit-transform-origin: 100% 50%; + left: 0; +} + +.reverseTurn .pageTurn #gate { + -moz-transform: perspective(500px) rotateY(180deg); + -webkit-transform: perspective(500px) rotateY(180deg); +} + +.reverseTurn #imp1 { + opacity: 0.25; +} + +.reverseTurn #imp4 { + opacity: 1; +} + +.reverseTurn .pageTurn #imp1 { + -moz-transition: opacity linear 300ms; + -webkit-transition: opacity linear 300ms; + opacity: 1; +} + +.reverseTurn .pageTurn #imp4 { + -moz-transition: opacity linear 300ms 300ms; + -webkit-transition: opacity linear 300ms 300ms; + opacity: 0.25; +} diff --git a/test/experimental/selection/content.html b/test/experimental/selection/content.html new file mode 100644 index 0000000..65498b2 --- /dev/null +++ b/test/experimental/selection/content.html @@ -0,0 +1,26 @@ + + + + Content + + + +

I

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis nec sapien libero. Nam sed nunc felis. Curabitur commodo tellus vitae ipsum interdum imperdiet. Ut sapien justo, lacinia ac sollicitudin vitae, condimentum at ante. Aliquam placerat, lorem et ultricies cursus, nunc turpis lacinia nulla, vitae bibendum sem velit sit amet ligula. Ut sit amet erat erat. Nam fermentum interdum imperdiet. Curabitur eu euismod leo. Pellentesque nec tellus enim, sit amet ultricies sem. Ut imperdiet libero id metus iaculis malesuada. Sed faucibus malesuada dui at rutrum. Suspendisse gravida ultricies lorem non porta. Aenean purus nisl, pellentesque eu consectetur vitae, molestie vel neque. Sed lacinia arcu ac metus dignissim dignissim. Aenean tristique consectetur orci. Suspendisse potenti. Nunc sit amet erat arcu. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. +

+

+ Donec quis felis venenatis diam elementum pellentesque. Pellentesque ullamcorper, urna ac bibendum varius, odio est dictum purus, non euismod diam eros ac turpis. Sed vehicula semper nisi, id elementum orci semper accumsan. Morbi at neque id sapien eleifend hendrerit. Mauris interdum lectus vitae elit dapibus at viverra lorem semper. Nullam lacinia, lacus ut vulputate rutrum, erat purus vehicula libero, non varius felis felis sit amet sapien. Cras ornare nisi non nunc convallis rhoncus. In semper est eget diam imperdiet vitae dignissim justo lacinia. Morbi lorem arcu, feugiat non suscipit vitae, adipiscing ut tellus. Proin eget sem mi. +

+

+ Aenean vitae elit augue. In blandit accumsan sem non aliquet. Quisque risus elit, pellentesque quis pharetra id, facilisis sit amet nibh. Aenean convallis tincidunt arcu quis posuere. Vivamus dictum facilisis felis eget vestibulum. Pellentesque lacinia lectus ac dolor lacinia eget commodo tellus mollis. In felis ante, ornare vitae fermentum vel, gravida sit amet sapien. Integer quis neque ut mi mollis rhoncus eget eget nisl. Nunc est elit, venenatis non lobortis in, interdum id orci. Phasellus malesuada, nibh non rutrum ornare, ligula risus viverra arcu, nec sagittis lectus est vel lorem. Aliquam et lectus id eros cursus scelerisque eu sed metus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer pharetra dapibus euismod. Aliquam laoreet pharetra diam in aliquet. Vivamus leo ligula, bibendum sit amet sodales ac, ultrices volutpat orci. Mauris nec velit vel lectus lacinia tempus eget a massa. +

+

+ Nullam faucibus, ante at ultrices tempor, nulla enim fringilla nunc, et vehicula risus tellus sit amet ipsum. Nullam ultrices imperdiet ligula, eget fermentum ante bibendum eu. Donec sollicitudin massa congue velit suscipit convallis. Quisque nec congue lacus. Etiam risus arcu, scelerisque et congue vitae, ullamcorper vitae felis. Etiam orci magna, viverra sit amet sodales at, sollicitudin eget dolor. Morbi congue diam sit amet magna fermentum vitae faucibus diam consequat. Phasellus et scelerisque lacus. Cras aliquam risus sed sem venenatis viverra. Nullam elit magna, vestibulum sed semper ut, tempus quis nisl. Pellentesque velit lectus, consequat vitae porttitor ac, adipiscing vel mi. Sed lobortis tincidunt dolor. Nulla placerat bibendum dui, pellentesque volutpat nisi tincidunt eu. Proin quis nulla nulla. Nullam interdum venenatis tempor. Nullam porta venenatis urna quis pharetra. Ut at purus id velit fermentum aliquam eget id justo. Sed vel urna ante. Suspendisse ligula dui, interdum quis venenatis vitae, egestas at turpis. Nulla eros elit, sollicitudin eu euismod vel, tincidunt non leo. +

+

+ Nullam quis augue in erat dictum iaculis sed a tellus. Suspendisse tristique varius nisi, eget eleifend dolor commodo vestibulum. In auctor consequat venenatis. In bibendum tincidunt ipsum, ac faucibus tortor dapibus mollis. Vivamus a lorem metus, et aliquam turpis. Duis gravida pulvinar interdum. Morbi ut quam eros. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Suspendisse nisl massa, interdum et elementum in, tincidunt id nisl. Nunc sodales risus et neque scelerisque porttitor. Etiam eget dui ipsum. Quisque vestibulum, quam sed adipiscing eleifend, arcu diam ornare leo, quis tincidunt nunc erat ultricies dui. Etiam libero tellus, adipiscing a volutpat sed, sagittis ac lorem. Integer sed erat nec nulla condimentum interdum. Integer mi metus, congue eget congue sed, accumsan at lectus. Nulla consequat venenatis odio sed tristique. +

+ + + diff --git a/test/experimental/selection/index.html b/test/experimental/selection/index.html new file mode 100644 index 0000000..8224cbb --- /dev/null +++ b/test/experimental/selection/index.html @@ -0,0 +1,79 @@ + + + + Selected Text test + + + + + + + + ← Back to Guide +

Selected Text test

+ + + +
Remove selection
+ +
+ +
+ +

+ Monocle needs custom events for "text has been selected" and + "text has been deselected". (These events could form the basis of + annotations or selection context menus, perhaps.) +

+ +

+ Custom events are working here if the yellow box appears and displays + selected text from the iframe, then disappears when text in the iframe is + deselected. +

+ +

+ Furthermore, as soon as a page begins turning, any selected + text should be deselected. This is trivial in most browsers — + window.getSelection().removeAllRanges() — but + bizarrely difficult in MobileSafari. +

+ +

+ The Remove Selection button is wired to + take effect on a tap or a swipe, on touch-based and cursor-based + browsers. The best test is to drag from inside to outside the button + — text in the frame should be deselected. +

+ + + diff --git a/test/experimental/selection/test.js b/test/experimental/selection/test.js new file mode 100644 index 0000000..07ff786 --- /dev/null +++ b/test/experimental/selection/test.js @@ -0,0 +1,178 @@ +(function () { + + var SELECTION_POLLING_INTERVAL = 250; + var lastSelection = {}; + + + function init() { + if (SELECTION_POLLING_INTERVAL) { + setInterval(pollSelection, SELECTION_POLLING_INTERVAL); + } + var btn = document.getElementById('deselectBtn'); + btn.addEventListener('mousedown', deselectAction, false); + btn.addEventListener('touchstart', deselectAction, false); + var frame = document.getElementById('reader'); + frame.addEventListener('experimental:selection', textSelected, false); + frame.addEventListener('experimental:deselection', textDeselected, false); + } + + + function pollSelection() { + var frame = document.getElementById('reader'); + var sel = frame.contentWindow.getSelection(); + var lm = lastSelection; + var nm = lastSelection = { + range: sel.rangeCount ? sel.getRangeAt(0) : null, + string: sel.toString() + }; + if (nm.range && nm.string) { + // Gecko keeps returning the same range object as the selection changes, + // so we have to store and compare start and end points directly. + nm.rangeStartContainer = nm.range.startContainer; + nm.rangeEndContainer = nm.range.endContainer; + nm.rangeStartOffset = nm.range.startOffset; + nm.rangeEndOffset = nm.range.endOffset; + if (!sameRange(nm, lm)) { + dispatchEvent(frame, 'experimental:selection', nm); + } + } else if (lm.string) { + dispatchEvent(frame, 'experimental:deselection', lm); + } + } + + + function sameRange(m1, m2) { + return ( + m1.rangeStartContainer == m2.rangeStartContainer && + m1.rangeEndContainer == m2.rangeEndContainer && + m1.rangeStartOffset == m2.rangeStartOffset && + m1.rangeEndOffset == m2.rangeEndOffset + ); + } + + + function dispatchEvent(elem, evtType, data) { + var evt = document.createEvent("Events"); + evt.initEvent(evtType, false, false); + evt.m = data; + return elem.dispatchEvent(evt); + } + + + function textSelected(evt) { + console.log("Selection: "+evt.m.string); + var st = document.getElementById('statusSelect'); + var sel = evt.m.string; + st.innerHTML = sel; + st.style.visibility = 'visible'; + } + + + function textDeselected(evt) { + console.log("Deselection."); + var st = document.getElementById('statusSelect'); + st.style.visibility = 'hidden'; + } + + + function deselectAction(evt) { + evt.preventDefault(); + deselect(); + } + + + // We've tried a few things under iOS. removeAllRanges() actually works, + // inasmuch as getSelection().toString() will return nothing, but + // visually the text still appears highlighted with the iOS selection menu. + // + // Creating the dummy input and calling setSelectedRange() on it almost + // works -- the text is deselected, but the selection menu is still visible, + // and there's a weird phantom selection elsewhere on the page. + // + // Experiments with focusing a dummy contenteditable div proved fruitless. + // + // The method below works, but is nasty, because it will scroll and zoom + // the viewport to the input's location. Hence, we temporarily prevent the + // viewport from zooming, and immediately return to our scroll position + // after focusing the input. + // + function deselect() { + var frame = document.getElementById('reader'); + var win = frame.contentWindow; + + if (!win.getSelection().toString()) { return; } + + if (navigator.userAgent.match(/AppleWebKit.*Mobile/)) { + console.log('Removing selection for iOS'); + preservingScale(function () { + preservingScrollPosition(function () { + var inp = document.createElement('input'); + inp.style.cssText = [ + 'position: absolute', + 'top: 0', + 'left: 0', + 'width: 0', + 'height: 0' + ].join(';'); + document.body.appendChild(inp); + inp.focus(); + document.body.removeChild(inp); + }) + }); + } else { + console.log('Removing selection for normal browsers'); + } + + var sel = win.getSelection(); + sel.removeAllRanges(); + } + + + function preservingScrollPosition(fn) { + var sx = window.scrollX, sy = window.scrollY; + fn(); + window.scrollTo(sx, sy); + } + + + function preservingScale(fn) { + var head = document.querySelector('head'); + var ovp = head.querySelector('meta[name=viewport]'); + var createViewportMeta = function (content) { + var elem = document.createElement('meta'); + elem.setAttribute('name', 'viewport'); + elem.setAttribute('content', content); + head.appendChild(elem); + return elem; + } + + if (ovp) { + var ovpcontent = ovp.getAttribute('content'); + var re = /user-scalable\s*=\s*([^,$\s])*/; + var result = ovpcontent.match(re); + if (result && ['no', '0'].indexOf(result[1]) >= 0) { + console.log("Existing viewport is already non-scalable."); + fn(); + } else { + console.log("Replacing existing viewport with non-scalable one."); + var nvpcontent = ovpcontent.replace(re, ''); + nvpcontent += nvpcontent ? ', ' : ''; + nvpcontent += 'user-scalable=no'; + head.removeChild(ovp); + var nvp = createViewportMeta(nvpcontent); + fn(); + head.removeChild(nvp); + head.appendChild(ovp); + } + } else { + console.log("Creating a non-scalable viewport."); + var nvp = createViewportMeta('user-scalable=no'); + fn(); + nvp.setAttribute('content', 'user-scalable=yes'); + } + } + + + window.onload = init; + +})(); diff --git a/test/index.html b/test/index.html index 855f4a9..915c72f 100644 --- a/test/index.html +++ b/test/index.html @@ -128,6 +128,11 @@

Bug tests

Client-specific bug index — A repository of other bugs that afflict only specific clients. +
  • + Experiments + — Ideas and proofs-of-concept that might eventually show up + in core. +
  • diff --git a/test/tests.css b/test/tests.css index c062b20..172597e 100644 --- a/test/tests.css +++ b/test/tests.css @@ -15,7 +15,7 @@ h1 { body.narrow { max-width: 25em; - /*margin: 0 auto;*/ + margin: 0 auto; } h1, h2, h3, p, li {