├── .editorconfig ├── .eslintrc ├── .gitattributes ├── .gitignore ├── .jscsrc ├── .jshintrc ├── .travis.yml ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE.txt ├── README.md ├── bower.json ├── dist ├── browser │ └── mq4-hover-shim.js └── cjs │ └── mq4-hover-shim.js ├── package.json ├── src ├── browser │ └── mq4-hover-shim.js └── nodejs │ ├── index.js │ └── postprocessor.js └── test └── postprocessor_test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "ecmaFeatures": { 4 | "blockBindings": true 5 | }, 6 | "rules": { 7 | "array-bracket-spacing": [2, "never"], 8 | "arrow-parens": 2, 9 | "arrow-spacing": [2, {"before": true, "after": true}], 10 | "block-scoped-var": 2, 11 | "block-spacing": [2, "always"], 12 | "brace-style": [2, "stroustrup"], 13 | "camelcase": 2, 14 | "constructor-super": 2, 15 | "comma-spacing": [2, {"before": false, "after": true}], 16 | "comma-style": [2, "last"], 17 | "computed-property-spacing": [2, "never"], 18 | "consistent-this": [2, "self"], 19 | "curly": 2, 20 | "dot-location": [2, "property"], 21 | "eol-last": 2, 22 | "eqeqeq": 2, 23 | "key-spacing": [2, {"beforeColon": false, "afterColon": true}], 24 | "linebreak-style": [2, "unix"], 25 | "new-cap": 2, 26 | "new-parens": 2, 27 | "no-array-constructor": 2, 28 | "no-bitwise": 2, 29 | "no-const-assign": 2, 30 | "no-constant-condition": 0, 31 | "no-dupe-args": 2, 32 | "no-dupe-class-members": 2, 33 | "no-duplicate-case": 2, 34 | "no-empty-character-class": 2, 35 | "no-eval": 2, 36 | "no-floating-decimal": 2, 37 | "no-implied-eval": 2, 38 | "no-inline-comments": 0, 39 | "no-irregular-whitespace": 2, 40 | "no-mixed-spaces-and-tabs": 2, 41 | "no-multi-spaces": 2, 42 | "no-multiple-empty-lines": 0, 43 | "no-new-object": 2, 44 | "no-param-reassign": 0, 45 | "no-process-env": 2, 46 | "no-restricted-syntax": [2, "WithStatement"], 47 | "no-self-compare": 2, 48 | "no-spaced-func": 2, 49 | "no-this-before-super": 2, 50 | "no-throw-literal": 2, 51 | "no-trailing-spaces": 2, 52 | "no-underscore-dangle": 0, 53 | "no-unexpected-multiline": 2, 54 | "no-unneeded-ternary": 2, 55 | "no-useless-call": 2, 56 | "no-void": 2, 57 | "one-var": [2, "never"], 58 | "object-curly-spacing": [2, "never"], 59 | "object-shorthand": [2, "always"], 60 | "operator-assignment": [2, "always"], 61 | "operator-linebreak": [2, "after"], 62 | "padded-blocks": 0, 63 | "prefer-const": 2, 64 | "prefer-spread": 2, 65 | "quote-props": [2, "as-needed", {"keywords": true}], 66 | "quotes": 0, 67 | "radix": 2, 68 | "require-jsdoc": 2, 69 | "semi": [2, "always"], 70 | "semi-spacing": [2, {"before": false, "after": true}], 71 | "space-after-keywords": [2, "always"], 72 | "space-before-blocks": [2, "always"], 73 | "space-before-function-paren": [2, {"anonymous": "always", "named": "never"}], 74 | "space-before-keywords": [2, "always"], 75 | "space-in-parens": [2, "never"], 76 | "space-return-throw-case": 2, 77 | "space-unary-ops": [2, {"words": true, "nonwords": false}], 78 | "spaced-comment": [2, "always", {"markers": ["!"]}], 79 | "strict": [2, "global"], 80 | "vars-on-top": 0, 81 | "wrap-iife": [2, "inside"], 82 | "yoda": [2, "never"] 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Enforce Unix newlines 2 | *.css text eol=lf 3 | *.html text eol=lf 4 | *.js text eol=lf 5 | *.json text eol=lf 6 | *.md text eol=lf 7 | *.yml text eol=lf 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | *-cov 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | # Deployed apps should consider commenting this line out: 28 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 29 | node_modules 30 | 31 | 32 | # Vim generated files 33 | *.swp 34 | *.swo 35 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "disallowEmptyBlocks": true, 3 | "disallowKeywords": ["with"], 4 | "disallowMixedSpacesAndTabs": true, 5 | "disallowMultipleLineStrings": true, 6 | "disallowMultipleVarDecl": true, 7 | "disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"], 8 | "disallowSpaceBeforePostfixUnaryOperators": ["++", "--"], 9 | "disallowSpacesInCallExpression": true, 10 | "disallowSpacesInsideBrackets": true, 11 | "disallowTrailingWhitespace": true, 12 | "esnext": true, 13 | "requireCapitalizedConstructors": true, 14 | "requireCommaBeforeLineBreak": true, 15 | "requireCurlyBraces": ["if", "else", "for", "while", "do", "try", "catch", "case", "default"], 16 | "requireLineFeedAtFileEnd": true, 17 | "requireOperatorBeforeLineBreak": ["+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", "<", ">=", "<="], 18 | "requireParenthesesAroundIIFE": true, 19 | "requireSpaceAfterBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", "<", ">=", "<="], 20 | "requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch"], 21 | "requireSpaceBeforeBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", "<", ">=", "<="], 22 | "requireSpacesInConditionalExpression": true, 23 | "requireSpacesInFunctionExpression": { "beforeOpeningCurlyBrace": true }, 24 | "requireSpacesInForStatement": true, 25 | "validateIndentation": 4, 26 | "validateLineBreaks": "LF", 27 | "validateParameterSeparator": ", ", 28 | "plugins": [ 29 | "jscs-jsdoc" 30 | ], 31 | "jsDoc": { 32 | "checkAnnotations": { 33 | "preset": "jsdoc3", 34 | "extra": {"class": false} 35 | }, 36 | "checkParamNames": true, 37 | "checkRedundantParams": true, 38 | "requireParamTypes": true 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": true, 3 | "curly": true, 4 | "eqeqeq": true, 5 | "forin": true, 6 | "immed": true, 7 | "latedef": true, 8 | "newcap": true, 9 | "noarg": true, 10 | "node": true, 11 | "noempty": true, 12 | "plusplus": false, 13 | "predef": ["exports"], 14 | "sub": true, 15 | "undef": true 16 | } 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | { 2 | "language": "node_js", 3 | "node_js": ["5"], 4 | "install": [ 5 | "npm install -g grunt-cli", 6 | "npm install" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Important notes 4 | Please don't edit files in the `dist` subdirectory as they are generated via Grunt. You'll find source code in the `src` subdirectory! 5 | 6 | ### Code style 7 | The project's coding style is laid out in the JSHint, ESLint, and JSCS configurations. 8 | 9 | ### Nodeunit 10 | Grunt can run the included [Nodeunit-based](https://github.com/caolan/nodeunit) unit tests for the CSS postprocessor via `grunt test`. 11 | 12 | ## Modifying the CSS postprocessor code 13 | First, ensure that you have the latest [Node.js](http://nodejs.org/) and [npm](http://npmjs.org/) installed. 14 | 15 | 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). 16 | 17 | 1. Fork and clone the repo. 18 | 2. Run `npm install` to install all build dependencies (including Grunt). 19 | 3. Run `grunt` to grunt this project. 20 | 21 | 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. 22 | 23 | ## Submitting pull requests 24 | 25 | 1. Create a new branch, please don't work in your `master` branch directly. 26 | 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. 27 | 3. Fix stuff. 28 | 4. Run `grunt` to see if the tests pass. Repeat steps 2-4 until done. 29 | 5. Update the documentation to reflect any changes. 30 | 6. Push to your fork and submit a pull request. 31 | 32 | ## Licensing 33 | By contributing your code, you agree to license your contribution under [the MIT License](https://github.com/twbs/mq4-hover-shim/blob/master/LICENSE.txt). 34 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 'use strict'; 3 | 4 | module.exports = function (grunt) { 5 | grunt.util.linefeed = '\n'; 6 | 7 | require('load-grunt-tasks')(grunt); 8 | require('time-grunt')(grunt); 9 | 10 | grunt.initConfig({ 11 | pkg: grunt.file.readJSON('package.json'), 12 | banner: ( 13 | "/*!\n * mq4-hover-shim v<%= pkg.version %>\n" + 14 | " * <%= pkg.homepage %>\n" + 15 | " * Copyright (c) 2014-2015 Christopher Rebert\n" + 16 | " * Licensed under the MIT License (https://github.com/twbs/mq4-hover-shim/blob/master/LICENSE).\n" + 17 | " */\n" 18 | ), 19 | 20 | babel: { 21 | options: { 22 | loose: ['es6.modules'], 23 | modules: "common" // output a CommonJS module 24 | }, 25 | dist: { 26 | files: { 27 | 'dist/cjs/<%= pkg.name %>.js': 'src/browser/<%= pkg.name %>.js' 28 | } 29 | } 30 | }, 31 | browserify: { 32 | options: { 33 | banner: '<%= banner %>', 34 | browserifyOptions: { 35 | standalone: 'mq4HoverShim', 36 | bundleExternal: false 37 | } 38 | }, 39 | dist: { 40 | src: 'dist/cjs/<%= pkg.name %>.js', 41 | dest: 'dist/browser/<%= pkg.name %>.js' 42 | } 43 | }, 44 | jshint: { 45 | options: { 46 | jshintrc: '.jshintrc' 47 | }, 48 | gruntfile: { 49 | src: 'Gruntfile.js' 50 | }, 51 | lib: { 52 | src: ['src/**/*.js'] 53 | }, 54 | test: { 55 | src: ['test/**/*.js', '!test/lib/**/*.js'] 56 | } 57 | }, 58 | jscs: { 59 | gruntfile: { 60 | src: '<%= jshint.gruntfile.src %>' 61 | }, 62 | lib: { 63 | src: ['src/**/*.js'] 64 | }, 65 | test: { 66 | src: '<%= jshint.test.src %>' 67 | } 68 | }, 69 | eslint: { 70 | options: { 71 | config: '.eslintrc' 72 | }, 73 | gruntfile: { 74 | src: '<%= jshint.gruntfile.src %>' 75 | }, 76 | lib: { 77 | src: ['src/**/*.js', '!src/browser/**/*.js'] 78 | }, 79 | test: { 80 | src: '<%= jshint.test.src %>' 81 | } 82 | }, 83 | nodeunit: { 84 | files: ['test/**/*_test.js'] 85 | } 86 | }); 87 | 88 | // Tasks 89 | grunt.registerTask('lint', ['jshint', 'eslint', 'jscs']); 90 | grunt.registerTask('dist', ['babel', 'browserify']); 91 | grunt.registerTask('test', ['lint', 'dist', 'nodeunit']); 92 | grunt.registerTask('default', ['test']); 93 | }; 94 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2015 Christopher Rebert 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mq4-hover-shim 2 | [![NPM version](https://badge.fury.io/js/mq4-hover-shim.svg)](http://badge.fury.io/js/mq4-hover-shim) 3 | [![Build Status](https://img.shields.io/travis/twbs/mq4-hover-shim/master.svg)](https://travis-ci.org/twbs/mq4-hover-shim) 4 | ![Development Status :: 4 - Beta](https://img.shields.io/badge/maturity-beta-yellow.svg "Development Status :: 4 - Beta") 5 | [![MIT License](https://img.shields.io/npm/l/mq4-hover-shim.svg)](https://github.com/twbs/mq4-hover-shim/blob/master/LICENSE.txt) 6 | [![Dependency Status](https://david-dm.org/twbs/mq4-hover-shim.svg)](https://david-dm.org/twbs/mq4-hover-shim) 7 | [![devDependency Status](https://david-dm.org/twbs/mq4-hover-shim/dev-status.svg)](https://david-dm.org/twbs/mq4-hover-shim#info=devDependencies) 8 | 9 | A shim for the [Media Queries Level 4 `hover` @media feature](http://drafts.csswg.org/mediaqueries/#hover). 10 | 11 | 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. 12 | 13 | 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. 14 | 15 | 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). 16 | 17 | The shim consists of two parts: 18 | * A [PostCSS](https://github.com/postcss/postcss)-based server-side CSS postprocessor that rewrites 19 | 20 | ```css 21 | @media (hover: hover) { 22 | some-selector { 23 | property: value; 24 | } 25 | } 26 | ``` 27 | into 28 | ```css 29 | some-prefix some-selector { 30 | property: value; 31 | } 32 | ``` 33 | (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 `` element.) 34 | * A client-side JavaScript library that detects whether the user-agent truly supports hovering. When 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: 35 | 36 | ```js 37 | $(document).on('mq4hsChange', function (e) { 38 | $(document.documentElement).toggleClass('some-special-class', e.trueHover); 39 | }); 40 | ``` 41 | Obviously, this requires JavaScript to be enabled in the browser, and would default to disabling `:hover` styles when JavaScript is disabled. 42 | 43 | [hover-pseudo]: https://developer.mozilla.org/en-US/docs/Web/CSS/:hover 44 | 45 | ## Installation 46 | 47 | * Via npm: `npm install mq4-hover-shim` 48 | * Via jspm: `jspm install mq4-hover-shim` 49 | 50 | ## Client-side dependencies 51 | 52 | The browser-side portion of the shim depends on jQuery for firing events. 53 | 54 | **Pull requests to add support for other browser event libraries would be welcomed.** 55 | 56 | ## Browser compatibility 57 | 58 | The following is a summary of the results of testing the library in various browsers. [Try out the Live Testcase](http://jsbin.com/xekahi/1). 59 | 60 | Legend: 61 | * True positive - Browser supports real hovering, and mq4-hover-shim reports that it supports real hovering 62 | * True negative - Browser does NOT support real hovering, and mq4-hover-shim reports that it does NOT support real hovering 63 | * False negative - Browser supports real hovering, and mq4-hover-shim reports that it does NOT support real hovering 64 | * False positive - Browser does NOT supports real hovering, and mq4-hover-shim reports that it supports real hovering 65 | * ??? - This case has yet to be tested. 66 | * Desktop - has a pointing device that supports true hovering (e.g. mouse, trackball, trackpad, joystick, http://xkcd.com/243/); lacks a touch-based pointing input device 67 | * [Laplet](http://en.wikipedia.org/wiki/Laplet) - has both a pointing device that supports true hovering and a touch-based pointing input device 68 | * Mobile - has a touch-based pointing input device (e.g. touchscreen); lacks a pointing device that supports true hovering 69 | 70 | Officially supported: 71 | * Blink (Chrome & recent Opera) 72 | * Desktop - True positive in Chrome >=41; False negative in Chrome <41 due to [Chromium bug #441613](http://crbug.com/441613) 73 | * Laplet - ??? (Arguable true negative presumed) 74 | * Mobile (Android) - True negative 75 | * Firefox 76 | * Desktop - True positive 77 | * Laplet - ??? (Arguable true negative presumed) 78 | * Mobile (Android) - True negative 79 | * Android browser 80 | * Mobile (Android 4.0/5.0) - True negative 81 | * Laplet (Android 4.0/5.0) - ??? (Arguable true negative presumed) 82 | * Internet Explorer 83 | * Desktop 84 | * 11 - True positive 85 | * 10 - True positive 86 | * 9 - True positive 87 | * 8 - True positive 88 | * Laplet 89 | * 11 - Arguable true negative 90 | * Mobile (Windows Phone 8.1) 91 | * 11 92 | * in mobile mode - True negative 93 | * in desktop mode - True negative 94 | * Safari (WebKit) 95 | * Desktop (Safari 8 on OS X) - True positive 96 | * Mobile (iOS 8.1) - True negative 97 | 98 | Unofficially supported: 99 | * Presto 100 | * Desktop (old Opera 12.1) - True positive 101 | * Mobile (Opera Mini) - ??? (Theoretically: True negative) 102 | * Mobile (Opera Mobile) - ??? (Theoretically: True negative) 103 | * Internet Explorer Mobile <=10 - ??? (Theoretically: True negative) 104 | 105 | ## API 106 | ### Node.js module; CSS postprocessor 107 | The npm module has the following properties: 108 | * `postprocessorFor` 109 | * Arguments: an options object with one property: 110 | * `hoverSelectorPrefix` - This string will be prepended to all selectors within `@media (hover: hover) {...}` blocks within the source CSS. 111 | * Type: `string` 112 | * Side-effects: none 113 | * Return type: A [PostCSS](https://github.com/postcss/postcss) processor object (that was returned from a call to the `postcss()` function). 114 | * Returns a CSS postprocessor that transforms the source CSS as described above. 115 | * `featureDetector` - Each of this object's properties is a string filepath to a JavaScript file containing the browser-side feature detector in a particular JavaScript module format. 116 | * `es6` - [ECMAScript 6 module](http://www.2ality.com/2014/09/es6-modules-final.html) format (this is the original from which the other versions are generated) 117 | * `cjs` - [CommonJS](http://wiki.commonjs.org/wiki/Modules/1.1) module format 118 | * `umdGlobal` - "enhanced" [UMD](https://github.com/umdjs/umd) module format; exports a `window.mq4HoverShim` global if the JS environment has no module system (e.g. if included directly via `