Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds media queries support to SC.View #1440

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
178 changes: 178 additions & 0 deletions frameworks/core_foundation/system/media_query.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
// ==========================================================================
// Project: Sproutcore
// Copyright: ©2020 GestiXi
// Author: Nicolas BADIA and contributors
// License: Licensed under MIT license (see license.js)
// ==========================================================================

/**
@class

Parses and determines if a given CSS Media Query matches a set of values.
Inspired by Eric Ferraiuolo & Tilo (https://github.com/ericf/css-mediaquery)

@author Nicolas BADIA
*/
SC.MediaQuery = SC.Object.create({

matchQuery: function(mediaQuery, values) {
var that = this;

return this.parseQuery(mediaQuery).some(function (query) {
var inverse = query.inverse;

// Either the parsed or specified `type` is "all", or the types must be
// equal for a match.
var typeMatch = query.type === 'all' || values.type === query.type;

// Quit early when `type` doesn't match, but take "not" into account.
if ((typeMatch && inverse) || !(typeMatch || inverse)) {
return false;
}

var expressionsMatch = query.expressions.every(function (expression) {
var feature = expression.feature,
modifier = expression.modifier,
expValue = expression.value,
value = values[feature];

if (feature === 'window-width') value = window.innerWidth;
if (feature === 'window-height') value = window.innerHeight;

// Missing or falsy values don't match.
if (!value) { return false; }

switch (feature) {
case 'orientation':
return value.toLowerCase() === expValue.toLowerCase();

case 'width':
case 'height':
case 'window-width':
case 'window-height':
expValue = that.toPx(expValue);
value = that.toPx(value);
break;

case 'resolution':
expValue = that.toDpi(expValue);
value = that.toDpi(value);
break;

case 'aspect-ratio':
case 'device-aspect-ratio':
expValue = that.toDecimal(expValue);
value = that.toDecimal(value);
break;
}

switch (modifier) {
case 'min': return value >= expValue;
case 'max': return value <= expValue;
default: return value === expValue;
}
});

return (expressionsMatch && !inverse) || (!expressionsMatch && inverse);
});
},

parseQuery: function(mediaQuery) {
return mediaQuery.split(',').map(function (query) {
query = query.trim();

var captures = query.match(/^(?:(only|not)?\s*([_a-z][_a-z0-9-]*)|(\([^\)]+\)))(?:\s*and\s*(.*))?$/i);

// Media Query must be valid.
if (!captures) {
throw new SyntaxError('Invalid CSS media query: "' + query + '"');
}

var modifier = captures[1],
type = captures[2],
expressions = ((captures[3] || '') + (captures[4] || '')).trim(),
parsed = {};

parsed.inverse = !!modifier && modifier.toLowerCase() === 'not';
parsed.type = type ? type.toLowerCase() : 'all';

// Check for media query expressions.
if (!expressions) {
parsed.expressions = [];
return parsed;
}

// Split expressions into a list.
expressions = expressions.match(/\([^\)]+\)/g);

// Media Query must be valid.
if (!expressions) {
throw new SyntaxError('Invalid CSS media query: "' + query + '"');
}

parsed.expressions = expressions.map(function (expression) {
var captures = expression.match(/^\(\s*([_a-z-][_a-z0-9-]*)\s*(?:\:\s*([^\)]+))?\s*\)$/);

// Media Query must be valid.
if (!captures) {
throw new SyntaxError('Invalid CSS media query: "' + query + '"');
}

var feature = captures[1].toLowerCase().match(/^(?:(min|max)-)?(.+)/);

return {
modifier: feature[1],
feature: feature[2],
value: captures[2]
};
});

return parsed;
});
},


// ------------------------------------------------------------------------
// Utilities
//

toDecimal: function(ratio) {
var decimal = Number(ratio),
numbers;

if (!decimal) {
numbers = ratio.match(/^(\d+)\s*\/\s*(\d+)$/);
decimal = numbers[1] / numbers[2];
}

return decimal;
},

toDpi: function(resolution) {
var value = parseFloat(resolution),
units = String(resolution).match(/(dpi|dpcm|dppx)?\s*$/)[1];

switch (units) {
case 'dpcm': return value / 2.54;
case 'dppx': return value * 96;
default: return value;
}
},

toPx: function(length) {
var value = parseFloat(length),
units = String(length).match(/(em|rem|px|cm|mm|in|pt|pc)?\s*$/)[1];

switch (units) {
case 'em': return value * 16;
case 'rem': return value * 16;
case 'cm': return value * 96 / 2.54;
case 'mm': return value * 96 / 2.54 / 10;
case 'in': return value * 96;
case 'pt': return value * 72;
case 'pc': return value * 72 / 12;
default: return value;
}
}

});
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,17 @@ test("When you set designModes on the root responder, it preps internal arrays."
equals(responder._designModeNames, undefined, "If no designModes value is set, there should not be any _designModeNames internal array.");
equals(responder._designModeThresholds, undefined, "If no designModes value is set, there should not be any _designModeNames internal array.");

designModes = { small: ((windowSize.width - 10) * (windowSize.height - 10)) / window.devicePixelRatio, large: Infinity };
designModes = { small: ((windowSize.width - 10) * (windowSize.height - 10)), large: Infinity };

responder.set('designModes', designModes);
same(responder._designModeNames, ['small', 'large'], "If designModes value is set, there should be an ordered _designModeNames internal array.");
same(responder._designModeThresholds, [((windowSize.width - 10) * (windowSize.height - 10)) / window.devicePixelRatio, Infinity], "If designModes value is set, there should be an ordered_designModeNames internal array.");
same(responder._designModeThresholds, [((windowSize.width - 10) * (windowSize.height - 10)), Infinity], "If designModes value is set, there should be an ordered_designModeNames internal array.");

designModes = { small: ((windowSize.width - 10) * (windowSize.height - 10)) / window.devicePixelRatio, medium: ((windowSize.width + 10) * (windowSize.height + 10)) / window.devicePixelRatio, large: Infinity };
designModes = { small: ((windowSize.width - 10) * (windowSize.height - 10)), medium: ((windowSize.width + 10) * (windowSize.height + 10)), large: Infinity };

responder.set('designModes', designModes);
same(responder._designModeNames, ['small', 'medium', 'large'], "If designModes value is set, there should be an ordered _designModeNames internal array.");
same(responder._designModeThresholds, [((windowSize.width - 10) * (windowSize.height - 10)) / window.devicePixelRatio, ((windowSize.width + 10) * (windowSize.height + 10)) / window.devicePixelRatio, Infinity], "If designModes value is set, there should be an ordered_designModeNames internal array.");
same(responder._designModeThresholds, [((windowSize.width - 10) * (windowSize.height - 10)), ((windowSize.width + 10) * (windowSize.height + 10)), Infinity], "If designModes value is set, there should be an ordered_designModeNames internal array.");

responder.set('designModes', null);
equals(responder._designModeNames, undefined, "If no designModes value is set, there should not be any _designModeNames internal array.");
Expand All @@ -65,13 +65,13 @@ test("When you set designModes on the root responder, it calls updateDesignMode
pane1.updateDesignMode.expect(1);
pane2.updateDesignMode.expect(1);

designModes = { small: ((windowSize.width - 10) * (windowSize.height - 10)) / window.devicePixelRatio, large: Infinity };
designModes = { small: ((windowSize.width - 10) * (windowSize.height - 10)), large: Infinity };

responder.set('designModes', designModes);
pane1.updateDesignMode.expect(2);
pane2.updateDesignMode.expect(2);

designModes = { small: ((windowSize.width - 10) * (windowSize.height - 10)) / window.devicePixelRatio, medium: ((windowSize.width + 10) * (windowSize.height + 10)) / window.devicePixelRatio, large: Infinity };
designModes = { small: ((windowSize.width - 10) * (windowSize.height - 10)), medium: ((windowSize.width + 10) * (windowSize.height + 10)), large: Infinity };

responder.set('designModes', designModes);
pane1.updateDesignMode.expect(3);
Expand Down
20 changes: 10 additions & 10 deletions frameworks/core_foundation/tests/views/pane/design_mode_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ test("When RootResponder has designModes, it sets designMode on its panes and th
orientation = SC.device.orientation;

windowSize = responder.get('currentWindowSize');
responder.set('designModes', { s: ((windowSize.width - 10) * (windowSize.height - 10)) / window.devicePixelRatio, l: Infinity });
responder.set('designModes', { s: ((windowSize.width - 10) * (windowSize.height - 10)), l: Infinity });

pane = pane.create();

Expand Down Expand Up @@ -191,7 +191,7 @@ test("When updateDesignMode() is called on a pane, it sets designMode properly o
orientation = SC.device.orientation;

windowSize = responder.get('currentWindowSize');
responder.set('designModes', { s: ((windowSize.width - 10) * (windowSize.height - 10)) / window.devicePixelRatio, l: Infinity });
responder.set('designModes', { s: ((windowSize.width - 10) * (windowSize.height - 10)), l: Infinity });

SC.run(function () {
pane = pane.create().append();
Expand Down Expand Up @@ -253,7 +253,7 @@ test("When RootResponder has designModes, and you add a view to a pane, it sets
orientation = SC.device.orientation;

windowSize = responder.get('currentWindowSize');
responder.set('designModes', { s: ((windowSize.width - 10) * (windowSize.height - 10)) / window.devicePixelRatio, l: Infinity });
responder.set('designModes', { s: ((windowSize.width - 10) * (windowSize.height - 10)), l: Infinity });

SC.run(function () {
pane = pane.create().append();
Expand Down Expand Up @@ -284,7 +284,7 @@ test("When you set designModes on RootResponder, it sets designMode on its panes
view4.set.expect(0);

windowSize = responder.get('currentWindowSize');
responder.set('designModes', { s: ((windowSize.width - 10) * (windowSize.height - 10)) / window.devicePixelRatio, l: Infinity });
responder.set('designModes', { s: ((windowSize.width - 10) * (windowSize.height - 10)), l: Infinity });

// designMode should be set (for initialization)
view1.set.expect(1);
Expand Down Expand Up @@ -315,7 +315,7 @@ test("When you change designModes on RootResponder, it sets designMode on the pa
orientation = SC.device.orientation;

windowSize = responder.get('currentWindowSize');
responder.set('designModes', { s: ((windowSize.width - 10) * (windowSize.height - 10)) / window.devicePixelRatio, l: Infinity });
responder.set('designModes', { s: ((windowSize.width - 10) * (windowSize.height - 10)), l: Infinity });

SC.run(function () {
pane = pane.create().append();
Expand Down Expand Up @@ -344,7 +344,7 @@ test("When you change designModes on RootResponder, it sets designMode on the pa
same(view4.get('classNames'), ['sc-view', 'sc-large'], "classNames for view4 should be");

// Change the small threshold
responder.set('designModes', { s: ((windowSize.width + 10) * (windowSize.height + 10)) / window.devicePixelRatio, l: Infinity });
responder.set('designModes', { s: ((windowSize.width + 10) * (windowSize.height + 10)), l: Infinity });

// designMode should be set
view1.set.expect(2);
Expand All @@ -370,8 +370,8 @@ test("When you change designModes on RootResponder, it sets designMode on the pa

// Add a medium threshold
responder.set('designModes', {
s: ((windowSize.width - 10) * (windowSize.height - 10)) / window.devicePixelRatio,
m: ((windowSize.width + 10) * (windowSize.height + 10)) / window.devicePixelRatio,
s: ((windowSize.width - 10) * (windowSize.height - 10)),
m: ((windowSize.width + 10) * (windowSize.height + 10)),
l: Infinity
});

Expand Down Expand Up @@ -406,7 +406,7 @@ test("When the design mode changes, overrides are reverted.");
// orientation = SC.device.orientation;

// windowSize = responder.get('currentWindowSize');
// responder.set('designModes', { s: ((windowSize.width - 10) * (windowSize.height - 10)) / window.devicePixelRatio, l: Infinity });
// responder.set('designModes', { s: ((windowSize.width - 10) * (windowSize.height - 10)), l: Infinity });

// SC.run(function () {
// pane = pane.create().append();
Expand Down Expand Up @@ -440,7 +440,7 @@ test("When you unset designModes on RootResponder, it clears designMode on its p
orientation = SC.device.orientation;

windowSize = responder.get('currentWindowSize');
responder.set('designModes', { s: ((windowSize.width - 10) * (windowSize.height - 10)) / window.devicePixelRatio, l: Infinity });
responder.set('designModes', { s: ((windowSize.width - 10) * (windowSize.height - 10)), l: Infinity });

SC.run(function () {
pane = pane.create().append();
Expand Down
3 changes: 0 additions & 3 deletions frameworks/core_foundation/views/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -2493,7 +2493,6 @@ SC.View = SC.CoreView.extend(/** @scope SC.View.prototype */{

// Theming
this._lastTheme = this.get('theme');

},

/** @private */
Expand Down Expand Up @@ -2526,5 +2525,3 @@ SC.View = SC.CoreView.extend(/** @scope SC.View.prototype */{

//unload views for IE, trying to collect memory.
if (SC.browser.isIE) SC.Event.add(window, 'unload', SC.View, SC.View.unload);