diff options
author | Chris Rebert <code@rebertia.com> | 2015-01-01 06:28:13 +0300 |
---|---|---|
committer | Chris Rebert <code@rebertia.com> | 2015-01-01 06:43:22 +0300 |
commit | 0e97ce1c2f135cf49d5c2f1ddce4ad2576a89b83 (patch) | |
tree | b34e41f6e8a07dd6ac907983a7ff0f496cae6d8f /src |
initial checkin
Diffstat (limited to 'src')
-rw-r--r-- | src/browser/mq4-hover-hover-shim.js | 70 | ||||
-rw-r--r-- | src/nodejs/postprocessor.js | 82 |
2 files changed, 152 insertions, 0 deletions
diff --git a/src/browser/mq4-hover-hover-shim.js b/src/browser/mq4-hover-hover-shim.js new file mode 100644 index 0000000..d097bf6 --- /dev/null +++ b/src/browser/mq4-hover-hover-shim.js @@ -0,0 +1,70 @@ +/*eslint-env browser */ +/* jshint browser: true, esnext: true */ + +/** +* Does this UA's primary pointer support true hovering +* OR does the UA at least not try to quirkily emulate hovering, +* such that :hover CSS styles are appropriate? +* Essentially tries to shim the `@media (hover: hover)` CSS media query feature. +* @type {boolean} +*/ +export function supportsTrueHover() { + if (!window.matchMedia) { + // Ancient non-IE, or IE<=9, per http://caniuse.com/#feat=matchmedia + var ua = navigator.userAgent; + var isIE9mobileInMobileMode = ua.indexOf('MSIE 9.0') > -1 && (ua.indexOf('XBLWP7') > -1 || ua.indexOf('ZuneWP7') > -1); + if (isIE9mobileInMobileMode) { + // FIXME: IE9 Mobile in Mobile mode; force hoverEnabled to false??? + return false; + } + // UA is ancient enough to probably be a desktop computer or at least not attempt emulation of hover. + return true; + } + + // CSSWG Media Queries Level 4 draft + // http://drafts.csswg.org/mediaqueries/#hover + // FIXME: WTF Chrome...: https://code.google.com/p/chromium/issues/detail?id=441613 + if (window.matchMedia( + '(hover: none),(-moz-hover: none),(-ms-hover: none),(-webkit-hover: none),' + + '(hover: on-demand),(-moz-hover: on-demand),(-ms-hover: on-demand),(-webkit-hover: on-demand)' + ).matches) { + // true hovering explicitly not supported by primary pointer + return false; + } + if (window.matchMedia('(hover: hover),(-moz-hover: hover),(-ms-hover: hover),(-webkit-hover: hover)').matches) { + // true hovering explicitly supported by primary pointer + return true; + } + // `hover` media feature not implemented by this browser; keep probing + + // Touch generally implies that hovering is merely emulated, + // which doesn't count as true hovering support for our purposes + // due to the quirkiness of the emulation (e.g. :hover being sticky). + + // W3C Pointer Events LC WD, 13 November 2014 + // http://www.w3.org/TR/2014/WD-pointerevents-20141113/ + // Prefixed in IE10, per http://caniuse.com/#feat=pointer + var supportsPointerEvents = window.PointerEvent || window.MSPointerEvent; + if (supportsPointerEvents) { + var pointerEventsIsTouch = (window.navigator.maxTouchPoints || window.navigator.msMaxTouchPoints) > 0; + return !pointerEventsIsTouch; + } + + // Mozilla's -moz-touch-enabled + // https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Media_queries#-moz-touch-enabled + if (window.matchMedia('(touch-enabled),(-moz-touch-enabled),(-ms-touch-enabled),(-webkit-touch-enabled)').matches) { + return false; + } + + // W3C Touch Events + // http://www.w3.org/TR/2013/REC-touch-events-20131010/ + if ('ontouchstart' in window) { + return false; + } + + // OPEN ISSUE: Should we look for IE's "Touch" userAgent token? + // OPEN ISSUE: IE10 Mobile? + + // UA's pointer is non-touch and thus likely either supports true hovering or at least does not try to emulate it. + return true; +} diff --git a/src/nodejs/postprocessor.js b/src/nodejs/postprocessor.js new file mode 100644 index 0000000..2173bec --- /dev/null +++ b/src/nodejs/postprocessor.js @@ -0,0 +1,82 @@ +/*eslint-env node */ +/*! + * Postprocessor for shimming @media (hover: hover) from Media Queries Level 4 + * https://github.com/cvrebert/mq4-hover-hover-shim + * Copyright 2014 Christopher Rebert + * Licensed under MIT (https://github.com/cvrebert/mq4-hover-hover-shim/blob/master/LICENSE.txt) + */ + +'use strict'; + +var postcss = require('postcss'); +var mediaQuery = require('css-mediaquery'); + + +// Checks whether the at-rule is: @media (hover: hover) {...} +function isSimpleMediaHoverHover(atRule) { + var mediaOrs = mediaQuery.parse(atRule.params); + if (mediaOrs.length > 1) { + return false; + } + var mediaAnds = mediaOrs[0]; + if (mediaAnds.inverse) { + return false; + } + if (mediaAnds.expressions.length > 1) { + return false; + } + var mediaExpr = mediaAnds.expressions[0]; + return mediaExpr.feature === 'hover' && mediaExpr.value === 'hover'; +} + +function replaceWithItsChildren(atRule) { + atRule.each(function (child) { + child.moveBefore(atRule); + }); + atRule.removeSelf(); +} + +// Prefixes each selector in the given rule with the given prefix string +function prefixSelectorsWith(rule, selectorPrefix) { + // Yes, this parsing is horribly naive. + + // We don't use rule.selectors because it's "some kind of hack" per https://github.com/postcss/postcss/issues/37 + // and it doesn't preserve inter-selector whitespace. + var selectorsWithWhitespace = rule.selector.split(','); + + var revisedSelectors = selectorsWithWhitespace.map(function (selectorWithWhitespace) { + var quadruple = /^(\s*)(\S.*\S)(\s*)$/.exec(selectorWithWhitespace); + if (quadruple === null) { + // Skip weirdness + return selectorWithWhitespace; + } + + var prefixWhitespace = quadruple[1]; + var selector = quadruple[2]; + var suffixWhitespace = quadruple[3]; + + var revisedSelector = prefixWhitespace + selectorPrefix + selector + suffixWhitespace; + return revisedSelector; + }); + rule.selector = revisedSelectors.join(','); +} + + +module.exports = postcss(function process(css, opts) { + var hoverSelectorPrefix = opts.hoverSelectorPrefix; + if ((typeof hoverSelectorPrefix) !== 'string') { + throw new Error('hoverSelectorPrefix option must be a string'); + } + + css.eachAtRule('media', function (atRule) { + if (!isSimpleMediaHoverHover(atRule)) { + return; + } + + atRule.eachRule(function (rule) { + prefixSelectorsWith(rule, hoverSelectorPrefix); + }); + + replaceWithItsChildren(atRule); + }); +}); |