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

postprocessor.js « nodejs « src - github.com/twbs/mq4-hover-shim.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: addb2d54ab8c6e187f625f9eae9030447a661c57 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
/* eslint-env node */
/*!
 * Postprocessor for shimming @media (hover: hover) from Media Queries Level 4
 * https://github.com/twbs/mq4-hover-shim
 * Copyright 2014-2015 Christopher Rebert
 * Licensed under MIT (https://github.com/twbs/mq4-hover-shim/blob/master/LICENSE.txt)
 */

'use strict';

var postcss = require('postcss');
var parseMediaQuery = require('css-mq-parser');


// Returns media type iff the at-rule is: @media optional-media-type (hover: hover) {...}
function mediaTypeIfSimpleHoverHover(atRule) {
    var mediaOrs = parseMediaQuery(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];
    if (mediaExpr.feature === 'hover' && mediaExpr.value === 'hover') {
        return mediaAnds.type;
    }
    else {
        return undefined;
    }
}

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 = function (opts) {
    return postcss(function process(css) {
        var hoverSelectorPrefix = opts.hoverSelectorPrefix;
        if ((typeof hoverSelectorPrefix) !== 'string') {
            throw new Error('hoverSelectorPrefix option must be a string');
        }

        css.walkAtRules('media', function (atRule) {
            var mediaType = mediaTypeIfSimpleHoverHover(atRule);
            switch (mediaType) {
                case 'all':
                    /* falls through */
                case 'screen': {
                    atRule.eachRule(function (rule) {
                        prefixSelectorsWith(rule, hoverSelectorPrefix);
                    });
                    if (mediaType === 'screen') {
                        atRule.params = 'screen';
                    }
                    else {
                        // Remove tautological @media all {...} wrapper
                        replaceWithItsChildren(atRule);
                    }
                    return;
                }

                case 'print':
                    /* falls through */
                case 'speech': {
                    // These media types never support hovering
                    // Delete always-false media query
                    atRule.removeSelf();
                    return;
                }

                case undefined: {
                    return; // Media query irrelevant or too complicated
                }
                default: {
                    return; // Deprecated media type; take no action.
                }
            }
        });
    });
};