Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/twbs/mq4-hover-shim.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris Rebert <code@rebertia.com>2015-01-01 06:28:13 +0300
committerChris Rebert <code@rebertia.com>2015-01-01 06:43:22 +0300
commit0e97ce1c2f135cf49d5c2f1ddce4ad2576a89b83 (patch)
treeb34e41f6e8a07dd6ac907983a7ff0f496cae6d8f
initial checkin
-rw-r--r--.editorconfig10
-rw-r--r--.eslintrc51
-rw-r--r--.gitattributes7
-rw-r--r--.gitignore34
-rw-r--r--.jscsrc39
-rw-r--r--.jshintrc16
-rw-r--r--.travis.yml8
-rw-r--r--CONTRIBUTING.md33
-rw-r--r--Gruntfile.js95
-rw-r--r--LICENSE.txt21
-rw-r--r--README.md91
-rw-r--r--dist/browser/mq4-hover-hover-shim.js80
-rw-r--r--dist/cjs/mq4-hover-hover-shim.js70
-rw-r--r--package.json56
-rw-r--r--src/browser/mq4-hover-hover-shim.js70
-rw-r--r--src/nodejs/postprocessor.js82
-rw-r--r--test/postprocessor_test.js118
17 files changed, 881 insertions, 0 deletions
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..0ea0cc4
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,10 @@
+# http://editorconfig.org
+root = true
+
+[*]
+indent_style = space
+indent_size = 4
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 0000000..69f2438
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,51 @@
+
+{
+ "rules": {
+ "block-scoped-var": 2,
+ "brace-style": [2, "stroustrup"],
+ "camelcase": 2,
+ "comma-spacing": [2, {"before": false, "after": true}],
+ "comma-style": [2, "last"],
+ "consistent-this": [2, "self"],
+ "curly": 2,
+ "eol-last": 2,
+ "eqeqeq": 2,
+ "key-spacing": [2, {"beforeColon": false, "afterColon": true}],
+ "new-cap": 2,
+ "new-parens": 2,
+ "no-array-constructor": 2,
+ "no-bitwise": 2,
+ "no-constant-condition": 0,
+ "no-floating-decimal": 2,
+ "no-inline-comments": 0,
+ "no-irregular-whitespace": 2,
+ "no-mixed-spaces-and-tabs": 2,
+ "no-multi-spaces": 2,
+ "no-multiple-empty-lines": 0,
+ "no-new-object": 2,
+ "no-process-env": 2,
+ "no-reserved-keys": 0,
+ "no-self-compare": 2,
+ "no-space-before-semi": 2,
+ "no-spaced-func": 2,
+ "no-trailing-spaces": 2,
+ "no-underscore-dangle": 0,
+ "no-void": 2,
+ "operator-assignment": [2, "always"],
+ "padded-blocks": 0,
+ "quotes": 0,
+ "radix": 2,
+ "semi": [2, "always"],
+ "space-after-function-name": [2, "never"],
+ "space-after-keywords": [2, "always", {"checkFunctionKeyword": true}],
+ "space-before-blocks": [2, "always"],
+ "space-in-brackets": [2, "never"],
+ "space-in-parens": [2, "never"],
+ "space-return-throw-case": 2,
+ "space-unary-ops": [2, {"words": true, "nonwords": false}],
+ "spaced-line-comment": [2, "always"],
+ "vars-on-top": 0,
+ "wrap-iife": [2, "inside"],
+ "yoda": [2, "never"]
+ }
+}
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..9619ad4
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,7 @@
+# Enforce Unix newlines
+*.css text eol=lf
+*.html text eol=lf
+*.js text eol=lf
+*.json text eol=lf
+*.md text eol=lf
+*.yml text eol=lf
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e679198
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,34 @@
+# Logs
+logs
+*.log
+
+# Runtime data
+pids
+*.pid
+*.seed
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*-cov
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directory
+# Deployed apps should consider commenting this line out:
+# see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
+node_modules
+
+
+# Vim generated files
+*.swp
+*.swo
diff --git a/.jscsrc b/.jscsrc
new file mode 100644
index 0000000..db99a77
--- /dev/null
+++ b/.jscsrc
@@ -0,0 +1,39 @@
+
+{
+ "disallowEmptyBlocks": true,
+ "disallowKeywords": ["with"],
+ "disallowMixedSpacesAndTabs": true,
+ "disallowMultipleLineStrings": true,
+ "disallowMultipleVarDecl": true,
+ "disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"],
+ "disallowSpaceBeforePostfixUnaryOperators": ["++", "--"],
+ "disallowSpacesInCallExpression": true,
+ "disallowTrailingWhitespace": true,
+ "esnext": true,
+ "requireCapitalizedConstructors": true,
+ "requireCommaBeforeLineBreak": true,
+ "requireCurlyBraces": ["if", "else", "for", "while", "do", "try", "catch", "case", "default"],
+ "requireLineFeedAtFileEnd": true,
+ "requireOperatorBeforeLineBreak": ["+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", "<", ">=", "<="],
+ "requireParenthesesAroundIIFE": true,
+ "requireSpaceAfterBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", "<", ">=", "<="],
+ "requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch"],
+ "requireSpaceBeforeBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", "<", ">=", "<="],
+ "requireSpacesInConditionalExpression": true,
+ "requireSpacesInFunctionExpression": { "beforeOpeningCurlyBrace": true },
+ "validateIndentation": 4,
+ "validateLineBreaks": "LF",
+ "validateParameterSeparator": ", ",
+ "plugins": [
+ "jscs-jsdoc"
+ ],
+ "jsDoc": {
+ "checkAnnotations": {
+ "preset": "jsdoc3",
+ "extra": {"class": false}
+ },
+ "checkParamNames": true,
+ "checkRedundantParams": true,
+ "requireParamTypes": true
+ }
+}
diff --git a/.jshintrc b/.jshintrc
new file mode 100644
index 0000000..0c9e5ce
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1,16 @@
+{
+ "bitwise": true,
+ "curly": true,
+ "eqeqeq": true,
+ "forin": true,
+ "immed": true,
+ "latedef": true,
+ "newcap": true,
+ "noarg": true,
+ "node": true,
+ "noempty": true,
+ "plusplus": false,
+ "predef": ["exports"],
+ "sub": true,
+ "undef": true
+}
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..becd574
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,8 @@
+{
+ "language": "node_js",
+ "node_js": ["0.10"],
+ "install": [
+ "npm install -g grunt-cli",
+ "npm install"
+ ]
+}
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..9e3d67c
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,33 @@
+# Contributing
+
+## Important notes
+Please don't edit files in the `dist` subdirectory as they are generated via Grunt. You'll find source code in the `src` subdirectory!
+
+### Code style
+The project's coding style is laid out in the JSHint, ESLint, and JSCS configurations.
+
+### Nodeunit
+Grunt can run the included [Nodeunit-based](https://github.com/caolan/nodeunit) unit tests for the CSS postprocessor via `grunt test`.
+
+## Modifying the CSS postprocessor code
+First, ensure that you have the latest [Node.js](http://nodejs.org/) and [npm](http://npmjs.org/) installed.
+
+Test that Grunt's CLI is installed by running `grunt --version`. If the command isn't found, run `npm install -g grunt-cli`. For more information about installing Grunt, see the [getting started with Grunt guide](http://gruntjs.com/getting-started).
+
+1. Fork and clone the repo.
+2. Run `npm install` to install all build dependencies (including Grunt).
+3. Run `grunt` to grunt this project.
+
+Assuming that you don't see any red, you're ready to go. Just be sure to run `grunt` after making any changes, to ensure that nothing is broken.
+
+## Submitting pull requests
+
+1. Create a new branch, please don't work in your `master` branch directly.
+2. If you're modifying the CSS postprocessor, add failing tests for the change you want to make. Run `grunt` to see the tests fail.
+3. Fix stuff.
+4. Run `grunt` to see if the tests pass. Repeat steps 2-4 until done.
+5. Update the documentation to reflect any changes.
+6. Push to your fork and submit a pull request.
+
+## Licensing
+By contributing your code, you agree to license your contribution under [the MIT License](https://github.com/cvrebert/mq4-hover-hover-shim/blob/master/LICENSE.txt).
diff --git a/Gruntfile.js b/Gruntfile.js
new file mode 100644
index 0000000..6561342
--- /dev/null
+++ b/Gruntfile.js
@@ -0,0 +1,95 @@
+/*eslint-env node */
+
+module.exports = function (grunt) {
+ 'use strict';
+
+ grunt.util.linefeed = '\n';
+
+ require('load-grunt-tasks')(grunt);
+ require('time-grunt')(grunt);
+
+ grunt.initConfig({
+ pkg: grunt.file.readJSON('package.json'),
+ banner: (
+ "/*!\n * mq4-hover-hover-shim v<%= pkg.version %>\n" +
+ " * <%= pkg.homepage %>\n" +
+ " * Copyright (c) 2014 Christopher Rebert\n" +
+ " * Licensed under the MIT License (https://github.com/cvrebert/mq4-hover-hover-shim/blob/master/LICENSE).\n" +
+ " */\n"
+ ),
+
+ '6to5': {
+ options: {
+ modules: "common" // output a CommonJS module
+ },
+ dist: {
+ files: {
+ 'dist/cjs/<%= pkg.name %>.js': 'src/browser/<%= pkg.name %>.js'
+ }
+ }
+ },
+ browserify: {
+ options: {
+ banner: '<%= banner %>',
+ browserifyOptions: {
+ standalone: 'mq4HoverShim'
+ }
+ },
+ dist: {
+ src: 'dist/cjs/<%= pkg.name %>.js',
+ dest: 'dist/browser/<%= pkg.name %>.js'
+ }
+ },
+ jshint: {
+ options: {
+ jshintrc: '.jshintrc'
+ },
+ gruntfile: {
+ src: 'Gruntfile.js'
+ },
+ lib: {
+ src: ['src/**/*.js']
+ },
+ test: {
+ src: ['test/**/*.js', '!test/lib/**/*.js']
+ }
+ },
+ jscs: {
+ gruntfile: {
+ src: '<%= jshint.gruntfile.src %>'
+ },
+ lib: {
+ src: '<%= jshint.lib.src %>'
+ },
+ test: {
+ src: '<%= jshint.test.src %>'
+ }
+ },
+ eslint: {
+ options: {
+ config: '.eslintrc'
+ },
+ gruntfile: {
+ src: '<%= jshint.gruntfile.src %>'
+ },
+ /*
+ // grunt-eslint doesn't support ES6
+ // (ES6 support is currently experimental in ESLint itself)
+ lib: {
+ src: '<%= jshint.lib.src %>'
+ },*/
+ test: {
+ src: '<%= jshint.test.src %>'
+ }
+ },
+ nodeunit: {
+ files: ['test/**/*_test.js']
+ }
+ });
+
+ // Tasks
+ grunt.registerTask('lint', ['jshint', 'eslint', 'jscs']);
+ grunt.registerTask('dist', ['6to5', 'browserify']);
+ grunt.registerTask('test', ['lint', 'dist', 'nodeunit']);
+ grunt.registerTask('default', ['test']);
+};
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..b76df48
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Christopher Rebert
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..80c7d92
--- /dev/null
+++ b/README.md
@@ -0,0 +1,91 @@
+# mq4-hover-hover-shim
+[![NPM version](https://badge.fury.io/js/mq4-hover-hover-shim.svg)](http://badge.fury.io/js/mq4-hover-hover-shim)
+[![Build Status](https://img.shields.io/travis/cvrebert/mq4-hover-hover-shim/master.svg)](https://travis-ci.org/cvrebert/mq4-hover-hover-shim)
+[![Dependency Status](https://david-dm.org/cvrebert/mq4-hover-hover-shim.svg)](https://david-dm.org/cvrebert/mq4-hover-hover-shim)
+[![devDependency Status](https://david-dm.org/cvrebert/mq4-hover-hover-shim/dev-status.svg)](https://david-dm.org/cvrebert/mq4-hover-hover-shim#info=devDependencies)
+
+A shim for the [Media Queries Level 4 `hover` @media feature](http://drafts.csswg.org/mediaqueries/#hover).
+
+The CSSWG's [Media Queries Level 4 Working Draft](http://drafts.csswg.org/mediaqueries/) defines a [`hover` media feature](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/hover) that can be used in media queries. This can be used to determine whether the user-agent's primary pointing device truly supports hovering (like mice do) (the `hover` value), or emulates hovering (e.g. via a long tap, like most modern touch-based mobile devices) (the `on-demand` value), or does not support hovering at all (like some old mobile devices) (the `none` value). This matters because emulated hovering typically has some ugly quirks, such as [`:hover`](hover-pseudo) being "sticky" (i.e. a hovered element stays in the [`:hover` state](hover-pseudo) even after the user stops interacting with it and until the user hovers over a different element). It is often better to avoid `:hover` styles in browsers where hovering supports is emulated.
+
+However, since it's from a relatively recent Working Draft, the `hover` media feature is not supported in all current modern browsers or in any legacy browsers. So, this library was created to shim support for the feature into browsers that lack native support for it.
+
+NOTE: This shim only adds support for the `hover` value of the `hover` media feature. So you can only tell the difference between "truly supports hovering" (the `hover` value)" and "does not truly support hovering" (the `none` or `on-demand` values).
+
+The shim consists of two parts:
+* A server-side CSS postprocessor that rewrites
+```css
+@media (hover: hover) {
+ some-selector {
+ property: value;
+ }
+}
+```
+into
+```css
+some-prefix some-selector {
+ property: value;
+}
+```
+(In normal use-cases, `some-selector` will contain the `:hover` pseudo-class and `some-prefix` will be a specially-named CSS class that will typically be added to the `<html>` element.)
+* A client-side JavaScript library that detects whether the user-agent truly supports hovering. If the check returns true, then your code can add the special CSS class to the appropriate element to enable [`:hover`](hover-pseudo) styles; for example:
+```js
+if (mq4HoverShim.supportsTrueHover()) {
+ document.documentElement.className += ' some-special-class';
+}
+```
+
+[hover-pseudo]: https://developer.mozilla.org/en-US/docs/Web/CSS/:hover
+
+## Browser compatibility
+
+The following is a summary of the results of testing the library in various browsers.
+
+Legend:
+* True positive - Browser supports real hovering, and mq4-hover-hover-shim reports that it supports real hovering
+* True negative - Browser does NOT support real hovering, and mq4-hover-hover-shim reports that it does NOT support real hovering
+* False negative - Browser supports real hovering, and mq4-hover-hover-shim reports that it does NOT support real hovering
+* False positive - Browser does NOT supports real hovering, and mq4-hover-hover-shim reports that it supports real hovering
+* ??? - This case has yet to be tested.
+
+Officially supported:
+* Blink (Chrome & recent Opera) - **False negative due to [Chromium bug #441613](http://crbug.com/441613)**
+* Firefox (latest stable version)
+ * Desktop - True positive
+ * Android - ???
+* Android browser
+ * Android 5.0 - True negative
+ * Android 4.0 - True negative
+* Internet Explorer
+ * Desktop
+ * 11 - True positive
+ * 10 - True positive
+ * 9 - True positive
+ * 8 - True positive
+ * Mobile 11
+ * Mobile mode - ???
+ * Desktop mode - ???
+* Safari (WebKit)
+ * iOS 8.1 - True negative
+ * 8 on OS X - True positive
+
+Unofficially supported:
+* Presto (old Opera 12) desktop - True positive
+* Internet Explorer Mobile 10 - ???
+* Internet Explorer Mobile 9 - ???
+
+## API
+(To-Be-Documented)
+
+## Contributing
+The project's coding style is laid out in the JSHint, ESLint, and JSCS configurations. Add unit tests when changing the CSS postprocessor. Lint and test your code using [Grunt](http://gruntjs.com/). Manually test any changes to the browser-side portion of the shim.
+
+_Also, please don't edit files in the `dist` subdirectory as they are generated via Grunt. You'll find source code in the `src` subdirectory!_
+
+## Release History
+See the [GitHub Releases page](https://github.com/cvrebert/mq4-hover-hover-shim/releases) for detailed changelogs.
+* (next release) - `master`
+* DATE - vVERSION: DESCRIPTION
+
+## License
+Copyright (c) 2014-2015 Christopher Rebert. Licensed under the MIT License.
diff --git a/dist/browser/mq4-hover-hover-shim.js b/dist/browser/mq4-hover-hover-shim.js
new file mode 100644
index 0000000..17e5821
--- /dev/null
+++ b/dist/browser/mq4-hover-hover-shim.js
@@ -0,0 +1,80 @@
+/*!
+ * mq4-hover-hover-shim v0.0.1
+ * https://github.com/cvrebert/mq4-hover-hover-shim
+ * Copyright (c) 2014 Christopher Rebert
+ * Licensed under the MIT License (https://github.com/cvrebert/mq4-hover-hover-shim/blob/master/LICENSE).
+ */
+
+!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.mq4HoverShim=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+"use strict";
+
+exports.supportsTrueHover = supportsTrueHover;
+/*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}
+*/
+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;
+}
+},{}]},{},[1])(1)
+}); \ No newline at end of file
diff --git a/dist/cjs/mq4-hover-hover-shim.js b/dist/cjs/mq4-hover-hover-shim.js
new file mode 100644
index 0000000..5db3edc
--- /dev/null
+++ b/dist/cjs/mq4-hover-hover-shim.js
@@ -0,0 +1,70 @@
+"use strict";
+
+exports.supportsTrueHover = supportsTrueHover;
+/*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}
+*/
+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;
+} \ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..123c22e
--- /dev/null
+++ b/package.json
@@ -0,0 +1,56 @@
+{
+ "name": "mq4-hover-hover-shim",
+ "version": "0.0.1",
+ "description": "A shim for the Media Queries Level 4 `hover` @media feature",
+ "scripts": {
+ "test": "grunt test"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/cvrebert/mq4-hover-hover-shim.git"
+ },
+ "keywords": [
+ "media",
+ "query",
+ "queries",
+ "feature",
+ "features",
+ "MQ4",
+ "css",
+ "shim",
+ "polyfill",
+ "hover"
+ ],
+ "author": {
+ "name": "Chris Rebert",
+ "email": "code@rebertia.com",
+ "url": "http://chrisrebert.com"
+ },
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/cvrebert/mq4-hover-hover-shim/issues"
+ },
+ "homepage": "https://github.com/cvrebert/mq4-hover-hover-shim",
+ "dependencies": {
+ "css-mediaquery": "^0.1.2",
+ "postcss": "^4.0.0"
+ },
+ "devDependencies": {
+ "grunt": "^0.4.5",
+ "grunt-6to5": "^2.0.0",
+ "grunt-browserify": "^3.2.1",
+ "grunt-contrib-jshint": "^0.10.0",
+ "grunt-contrib-nodeunit": "^0.4.1",
+ "grunt-eslint": "^3.0.0",
+ "grunt-jscs": "^1.0.0",
+ "jscs-jsdoc": "^0.4.0",
+ "load-grunt-tasks": "^2.0.0",
+ "time-grunt": "^1.0.0"
+ },
+ "main": "src/nodejs/postprocessor.js",
+ "files": [
+ "dist",
+ "src",
+ "LICENSE.txt"
+ ]
+}
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);
+ });
+});
diff --git a/test/postprocessor_test.js b/test/postprocessor_test.js
new file mode 100644
index 0000000..715706c
--- /dev/null
+++ b/test/postprocessor_test.js
@@ -0,0 +1,118 @@
+/*eslint-env node */
+'use strict';
+
+var postprocessor = require('../src/nodejs/postprocessor.js');
+
+/*
+ ======== A Handy Little Nodeunit Reference ========
+ https://github.com/caolan/nodeunit
+ Test methods:
+ test.expect(numAssertions)
+ test.done()
+ Test assertions:
+ test.ok(value, [message])
+ test.deepEqual(actual, expected, [message])
+ test.notDeepEqual(actual, expected, [message])
+ test.strictEqual(actual, expected, [message])
+ test.notStrictEqual(actual, expected, [message])
+ test.throws(block, [error], [message])
+ test.doesNotThrow(block, [error], [message])
+ test.ifError(value)
+*/
+
+exports.mq4HoverShim = {
+ setUp: function (done) {
+ // setup here
+ done();
+ },
+ 'has no effect when there are no media queries': function (test) {
+ test.expect(1);
+ test.deepEqual(
+ postprocessor.process(".foobar { display: none; }", {hoverSelectorPrefix: 'PREFIX>'}).css,
+ ".foobar { display: none; }"
+ );
+ test.done();
+ },
+ 'skips non-media at-rules': function (test) {
+ test.expect(1);
+ test.deepEqual(
+ postprocessor.process("@quux (hover: hover) { .foobar { display: none; } }", {hoverSelectorPrefix: 'PREFIX>'}).css,
+ "@quux (hover: hover) { .foobar { display: none; } }"
+ );
+ test.done();
+ },
+ 'skips media queries with ORs': function (test) {
+ test.expect(1);
+ test.deepEqual(
+ postprocessor.process("@media (hover: hover), (orientation: landscape) { .foobar { display: none; } }", {hoverSelectorPrefix: 'PREFIX>'}).css,
+ "@media (hover: hover), (orientation: landscape) { .foobar { display: none; } }"
+ );
+ test.done();
+ },
+ 'skips media queries with ANDs': function (test) {
+ test.expect(1);
+ test.deepEqual(
+ postprocessor.process("@media (hover: hover) and (orientation: landscape) { .foobar { display: none; } }", {hoverSelectorPrefix: 'PREFIX>'}).css,
+ "@media (hover: hover) and (orientation: landscape) { .foobar { display: none; } }"
+ );
+ test.done();
+ },
+ 'skips media queries that are not about the hover media feature': function (test) {
+ test.expect(1);
+ test.deepEqual(
+ postprocessor.process("@media (orientation: landscape) { .foobar { display: none; } }", {hoverSelectorPrefix: 'PREFIX>'}).css,
+ "@media (orientation: landscape) { .foobar { display: none; } }"
+ );
+ test.done();
+ },
+ 'skips media queries about the hover media feature with a non-hover value': function (test) {
+ test.expect(2);
+ test.deepEqual(
+ postprocessor.process("@media (hover: none) { .foobar { display: none; } }", {hoverSelectorPrefix: 'PREFIX>'}).css,
+ "@media (hover: none) { .foobar { display: none; } }"
+ );
+ test.deepEqual(
+ postprocessor.process("@media (hover: on-demand) { .foobar { display: none; } }", {hoverSelectorPrefix: 'PREFIX>'}).css,
+ "@media (hover: on-demand) { .foobar { display: none; } }"
+ );
+ test.done();
+ },
+ 'works correctly on a representative example': function (test) {
+ test.expect(1);
+ test.deepEqual(
+ postprocessor.process("@media (hover: hover) { .foobar { color: white; background: red; } div .quux > input { color: blue; background: white; } }", {hoverSelectorPrefix: 'PREFIX>'}).css,
+ "PREFIX>.foobar {\n color: white;\n background: red;\n}\nPREFIX>div .quux > input {\n color: blue;\n background: white;\n}"
+ );
+ test.done();
+ },
+ 'handles nested at-rules': function (test) {
+ test.expect(2);
+ test.deepEqual(
+ postprocessor.process("@media (orientation: landscape) { @media (hover: hover) { .foobar { display: none; } } }", {hoverSelectorPrefix: 'PREFIX>'}).css,
+ "@media (orientation: landscape) { PREFIX>.foobar { display: none; } }"
+ );
+ test.deepEqual(
+ postprocessor.process("@media (hover: hover) { @media (orientation: landscape) { .foobar { display: none; } } }", {hoverSelectorPrefix: 'PREFIX>'}).css,
+ "@media (orientation: landscape) {\n PREFIX>.foobar {\n display: none;\n }\n}"
+ );
+ test.done();
+ },
+ 'errors when hoverSelectorPrefix is not provided': function (test) {
+ test.expect(1);
+ test.throws(function () {
+ /*eslint-disable no-unused-expressions */
+ postprocessor.process("@media (hover: hover) { .foobar { display: none; } }", {}).css;// jshint ignore:line
+ /*eslint-enable no-unused-expressions */
+ }, Error, 'hoverSelectorPrefix option must be a string');
+ test.done();
+ },
+ 'errors when hoverSelectorPrefix is not a string': function (test) {
+ test.expect(1);
+ test.throws(function () {
+ /*eslint-disable no-unused-expressions */
+ postprocessor.process("@media (hover: hover) { .foobar { display: none; } }", {hoverSelectorPrefix: 42}).css;// jshint ignore:line
+ /*eslint-enable no-unused-expressions */
+ }, Error, 'hoverSelectorPrefix option must be a string');
+ test.done();
+ }
+};