Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

scroll-behavior-polyfill

Package Overview
Dependencies
Maintainers
1
Versions
14
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

scroll-behavior-polyfill - npm Package Compare versions

Comparing version 1.0.2 to 2.0.1

dist/index.d.ts

23

CHANGELOG.md

@@ -1,17 +0,22 @@

<a name="1.0.2"></a>
## 1.0.2 (2017-09-08)
# [2.0.0](https://github.com/wessberg/scroll-behavior-polyfill/compare/v1.0.2...v2.0.0) (2019-01-09)
* 1.0.2 ([dba31c2](https://github.com/wessberg/scroll-behavior-polyfill/commit/dba31c2))
* Bumped version ([372ea1e](https://github.com/wessberg/scroll-behavior-polyfill/commit/372ea1e))
* Fixed an issue ([5c19034](https://github.com/wessberg/scroll-behavior-polyfill/commit/5c19034))
### Features
* **release:** new major version and rewritten from scratch. ([5647eb3](https://github.com/wessberg/scroll-behavior-polyfill/commit/5647eb3))
<a name="1.0.1"></a>
### BREAKING CHANGES
* **release:** CSS Stylesheets will no longer be parsed. Instead, you must either set inline styles, an attribute with the same name, or set it imperatively. Of course, you can still use the imperative API.
## [1.0.2](https://github.com/wessberg/scroll-behavior-polyfill/compare/v1.0.1...v1.0.2) (2017-09-08)
## 1.0.1 (2017-09-08)
* 1.0.1 ([212446f](https://github.com/wessberg/scroll-behavior-polyfill/commit/212446f))
* First commit ([b701121](https://github.com/wessberg/scroll-behavior-polyfill/commit/b701121))
(function () {
'use strict';
'use strict';
/*!
* Polyfill.js - v0.1.0
*
* Copyright (c) 2015 Philip Walton <http://philipwalton.com>
* Released under the MIT license
*
* Date: 2015-06-21
*/
(function(window, document, undefined){
/**
* Is true if the browser natively supports the 'scroll-behavior' CSS-property.
* @type {boolean}
*/
var SUPPORTS_SCROLL_BEHAVIOR = "scrollBehavior" in document.documentElement.style;
'use strict';
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
var reNative = RegExp('^' +
String({}.valueOf)
.replace(/[.*+?\^${}()|\[\]\\]/g, '\\$&')
.replace(/valueOf|for [^\]]+/g, '.+?') + '$'
);
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
/**
* Trim any leading or trailing whitespace
*/
function trim(s) {
return s.replace(/^\s+|\s+$/g,'')
}
var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
function __read(o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
}
/**
* Detects the presence of an item in an array
*/
function inArray(target, items) {
var item
, i = 0;
if (!target || !items) return false
while(item = items[i++]) {
if (target === item) return true
}
return false
}
var ELEMENT_ORIGINAL_SCROLL = Element.prototype.scroll;
var ELEMENT_ORIGINAL_SCROLL_BY = Element.prototype.scrollBy;
/**
* Determine if a method is support natively by the browser
*/
function isNative(fn) {
return reNative.test(fn)
}
var ELEMENT_ORIGINAL_SCROLL_TO = Element.prototype.scrollTo;
/**
* Determine if a URL is local to the document origin
* Inspired form Respond.js
* https://github.com/scottjehl/Respond/blob/master/respond.src.js#L90-L91
*/
var isLocalURL = (function() {
var base = document.getElementsByTagName("base")[0]
, reProtocol = /^([a-zA-Z:]*\/\/)/;
return function(url) {
var isLocal = (!reProtocol.test(url) && !base)
|| url.replace(RegExp.$1, "").split("/")[0] === location.host;
return isLocal
}
}());
var styleDeclarationPropertyName = "scrollBehavior";
var styleAttributePropertyName = "scroll-behavior";
var styleAttributePropertyNameRegex = new RegExp(styleAttributePropertyName + ":\\s*([^;]*)");
/**
* Determines the scroll behavior to use, depending on the given ScrollOptions and the position of the Element
* within the DOM
* @param {Element|HTMLElement|Window} inputTarget
* @param {ScrollOptions} [options]
* @returns {ScrollBehavior}
*/
function getScrollBehavior(inputTarget, options) {
// If the given 'behavior' is 'smooth', apply smooth scrolling no matter what
if (options != null && options.behavior === "smooth")
return "smooth";
var target = "style" in inputTarget ? inputTarget : document.scrollingElement != null ? document.scrollingElement : document.documentElement;
var value;
if ("style" in target) {
// Check if scroll-behavior is set as a property on the CSSStyleDeclaration
var scrollBehaviorPropertyValue = target.style[styleDeclarationPropertyName];
// Return it if it is given and has a proper value
if (scrollBehaviorPropertyValue != null && scrollBehaviorPropertyValue !== "") {
value = scrollBehaviorPropertyValue;
}
}
if (value == null) {
var attributeValue = target.getAttribute("scroll-behavior");
if (attributeValue != null && attributeValue !== "") {
value = attributeValue;
}
}
if (value == null) {
// Otherwise, check if it is set as an inline style
var styleAttributeValue = target.getAttribute("style");
if (styleAttributeValue != null && styleAttributeValue.includes(styleAttributePropertyName)) {
var match = styleAttributeValue.match(styleAttributePropertyNameRegex);
if (match != null) {
var _a = __read(match, 2), behavior = _a[1];
if (behavior != null && behavior !== "") {
value = behavior;
}
}
}
}
if (value == null) {
// Take the computed style for the element and see if it contains a specific 'scroll-behavior' value
var computedStyle = getComputedStyle(target);
var computedStyleValue = computedStyle.getPropertyValue("scrollBehavior");
if (computedStyleValue != null && computedStyleValue !== "") {
value = computedStyleValue;
}
}
// In all other cases, use the value from the CSSOM
return value;
}
var supports = {
// true with either native support or a polyfil, we don't care which
matchMedia: window.matchMedia && window.matchMedia( "only all" ).matches,
// true only if the browser supports window.matchMeida natively
nativeMatchMedia: isNative(window.matchMedia)
};
var HALF = 0.5;
/**
* The easing function to use when applying the smooth scrolling
* @param {number} k
* @returns {number}
*/
function ease(k) {
return HALF * (1 - Math.cos(Math.PI * k));
}
var DownloadManager = (function() {
/**
* The duration of a smooth scroll
* @type {number}
*/
var SCROLL_TIME = 15000;
/**
* Performs a smooth repositioning of the scroll
* @param {ISmoothScrollOptions} options
*/
function smoothScroll(options) {
var startTime = options.startTime, startX = options.startX, startY = options.startY, endX = options.endX, endY = options.endY, method = options.method;
var timeLapsed = 0;
var distanceX = endX - startX;
var distanceY = endY - startY;
var speed = Math.max(Math.abs(distanceX / 1000 * SCROLL_TIME), Math.abs(distanceY / 1000 * SCROLL_TIME));
requestAnimationFrame(function animate(timestamp) {
timeLapsed += timestamp - startTime;
var percentage = Math.max(0, Math.min(1, speed === 0 ? 0 : (timeLapsed / speed)));
var positionX = Math.floor(startX + (distanceX * ease(percentage)));
var positionY = Math.floor(startY + (distanceY * ease(percentage)));
method(positionX, positionY);
if (positionX !== endX || positionY !== endY) {
requestAnimationFrame(animate);
}
});
}
var cache = {}
, queue = []
, callbacks = []
, requestCount = 0
, xhr = (function() {
var method;
try { method = new window.XMLHttpRequest(); }
catch (e) { method = new window.ActiveXObject( "Microsoft.XMLHTTP" ); }
return method
}());
/**
* Returns a High Resolution timestamp if possible, otherwise fallbacks to Date.now()
* @returns {number}
*/
function now() {
if ("performance" in window)
return performance.now();
return Date.now();
}
// return function(urls, callback) {
var WINDOW_ORIGINAL_SCROLL_TO = window.scrollTo;
function addURLsToQueue(urls) {
var url
, i = 0;
while (url = urls[i++]) {
if (!cache[url] && !inArray(url, queue)) {
queue.push(url);
}
}
}
/**
* Gets the Smooth Scroll Options to use for the step function
* @param {Element|Window} element
* @param {number} x
* @param {number} y
* @param {ScrollMethodName} kind
* @returns {ISmoothScrollOptions}
*/
function getSmoothScrollOptions(element, x, y, kind) {
var startTime = now();
if (!(element instanceof Element)) {
// Use window as the scroll container
var scrollX_1 = window.scrollX, pageXOffset_1 = window.pageXOffset, scrollY_1 = window.scrollY, pageYOffset_1 = window.pageYOffset;
var startX = scrollX_1 == null || scrollX_1 === 0 ? pageXOffset_1 : scrollX_1;
var startY = scrollY_1 == null || scrollY_1 === 0 ? pageYOffset_1 : scrollY_1;
return {
startTime: startTime,
startX: startX,
startY: startY,
endX: Math.floor(kind === "scrollBy"
? startX + x
: x),
endY: Math.floor(kind === "scrollBy"
? startY + y
: y),
method: WINDOW_ORIGINAL_SCROLL_TO.bind(window)
};
}
else {
var scrollLeft = element.scrollLeft, scrollTop = element.scrollTop;
var startX = scrollLeft;
var startY = scrollTop;
return {
startTime: startTime,
startX: startX,
startY: startY,
endX: kind === "scrollBy"
? startX + x
: x,
endY: kind === "scrollBy"
? startY + y
: y,
method: ELEMENT_ORIGINAL_SCROLL_TO.bind(element)
};
}
}
function processQueue() {
// don't process the next one if we're in the middle of a download
if (!(xhr.readyState === 0 || xhr.readyState === 4)) return
/**
* Gets the scrollLeft version of an element. If a window is provided, the 'pageXOffset' is used.
* @param {Element | Window} element
* @returns {number}
*/
function getScrollLeft(element) {
if (element instanceof Element)
return element.scrollLeft;
return element.pageXOffset;
}
var url;
if (url = queue[0]) {
downloadStylesheet(url);
}
if (!url) {
invokeCallbacks();
}
}
/**
* Ensures that the given value is numeric
* @param {number} value
* @return {number}
*/
function ensureNumeric(value) {
if (value == null)
return 0;
else if (typeof value === "number") {
return value;
}
else if (typeof value === "string") {
return parseFloat(value);
}
else {
return 0;
}
}
/**
* Make the requests
*
* TODO: Get simultaneous downloads working, it can't be that hard
*/
function downloadStylesheet(url) {
requestCount++;
xhr.open("GET", url, true);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && (xhr.status == 200 || xhr.status == 304)) {
cache[url] = xhr.responseText;
queue.shift();
processQueue();
}
};
xhr.send(null);
}
/**
* Gets the scrollTop version of an element. If a window is provided, the 'pageYOffset' is used.
* @param {Element | Window} element
* @returns {number}
*/
function getScrollTop(element) {
if (element instanceof Element)
return element.scrollTop;
return element.pageYOffset;
}
/**
* Check the cache to make sure all requests are complete
*/
function downloadsFinished(urls) {
var url
, i = 0
, len = 0;
while (url = urls[i++]) {
if (cache[url]) len++;
}
return (len === urls.length)
}
/**
* Returns true if the given value is some ScrollToOptions
* @param {number | ScrollToOptions} value
* @return {value is ScrollToOptions}
*/
function isScrollToOptions(value) {
return value != null && typeof value === "object";
}
/**
* Invoke each callback and remove it from the list
*/
function invokeCallbacks() {
var callback;
while (callback = callbacks.shift()) {
invokeCallback(callback.urls, callback.fn);
}
}
var WINDOW_ORIGINAL_SCROLL = window.scroll;
/**
* Put the stylesheets in the proper order and invoke the callback
*/
function invokeCallback(urls, callback) {
var stylesheets = []
, url
, i = 0;
while (url = urls[i++]) {
stylesheets.push(cache[url]);
}
callback.call(null, stylesheets);
}
var WINDOW_ORIGINAL_SCROLL_BY = window.scrollBy;
return {
request: function(urls, callback) {
// Add the callback to the list
callbacks.push({urls: urls, fn: callback});
/**
* Handles a scroll method
* @param {Element|Window} element
* @param {ScrollMethodName} kind
* @param {number | ScrollToOptions} optionsOrX
* @param {number} y
*/
function handleScrollMethod(element, kind, optionsOrX, y) {
// If only one argument is given, and it isn't an options object, throw a TypeError
if (y === undefined && !isScrollToOptions(optionsOrX)) {
throw new TypeError("Failed to execute 'scroll' on 'Element': parameter 1 ('options') is not an object.");
}
// Scroll based on the primitive values given as arguments
if (!isScrollToOptions(optionsOrX)) {
var _a = normalizeScrollCoordinates(optionsOrX, y, element, kind), left = _a.left, top_1 = _a.top;
onScrollPrimitive(left, top_1, element, kind);
}
// Scroll based on the received options object
else {
onScrollWithOptions(__assign({}, normalizeScrollCoordinates(optionsOrX.left, optionsOrX.top, element, kind), { behavior: optionsOrX.behavior == null ? "auto" : optionsOrX.behavior }), element, kind);
}
}
/**
* Gets the original non-patched prototype method for the given kind
* @param {ScrollMethodName} kind
* @param {Element|Window} element
* @return {Function}
*/
function getOriginalPrototypeMethodForKind(kind, element) {
switch (kind) {
case "scroll":
return element instanceof Element ? ELEMENT_ORIGINAL_SCROLL : WINDOW_ORIGINAL_SCROLL;
case "scrollBy":
return element instanceof Element ? ELEMENT_ORIGINAL_SCROLL_BY : WINDOW_ORIGINAL_SCROLL_BY;
case "scrollTo":
return element instanceof Element ? ELEMENT_ORIGINAL_SCROLL_TO : WINDOW_ORIGINAL_SCROLL_TO;
}
}
/**
* Invoked when a 'ScrollToOptions' dict is provided to 'scroll()' as the first argument
* @param {ScrollToOptions} options
* @param {Element|Window} element
* @param {ScrollMethodName} kind
*/
function onScrollWithOptions(options, element, kind) {
var behavior = getScrollBehavior(element, options);
// If the behavior is 'auto' apply instantaneous scrolling
if (behavior == null || behavior === "auto") {
getOriginalPrototypeMethodForKind(kind, element).call(element, options.left, options.top);
}
else {
smoothScroll(getSmoothScrollOptions(element, options.left, options.top, kind));
}
}
/**
* Invoked when 'scroll()' is invoked with primitive x or y values
* @param {number} x
* @param {number} y
* @param {ScrollMethodName} kind
* @param {Element|Window} element
*/
function onScrollPrimitive(x, y, element, kind) {
// noinspection SuspiciousTypeOfGuard
return onScrollWithOptions({
left: x,
top: y,
behavior: "auto"
}, element, kind);
}
/**
* Normalizes the given scroll coordinates
* @param {number?} x
* @param {number?} y
* @param {Element|Window} element
* @param {ScrollMethodName} kind
* @return {Required<Pick<ScrollToOptions, "top" | "left">>}
*/
function normalizeScrollCoordinates(x, y, element, kind) {
switch (kind) {
case "scrollBy":
return {
left: getScrollLeft(element) + ensureNumeric(x),
top: getScrollTop(element) + ensureNumeric(y)
};
default:
return {
left: ensureNumeric(x),
top: ensureNumeric(y)
};
}
}
if (downloadsFinished(urls)) {
invokeCallbacks();
} else {
addURLsToQueue(urls);
processQueue();
}
},
clearCache: function() {
cache = {};
},
_getRequestCount: function() {
return requestCount
}
}
/**
* Patches the 'scroll' method on the Element prototype
*/
function patchElementScroll() {
Element.prototype.scroll = function (optionsOrX, y) {
handleScrollMethod(this, "scroll", optionsOrX, y);
};
}
}());
/**
* Patches the 'scrollBy' method on the Element prototype
*/
function patchElementScrollBy() {
Element.prototype.scrollBy = function (optionsOrX, y) {
handleScrollMethod(this, "scrollBy", optionsOrX, y);
};
}
var StyleManager = {
/**
* Patches the 'scrollTo' method on the Element prototype
*/
function patchElementScrollTo() {
Element.prototype.scrollTo = function (optionsOrX, y) {
handleScrollMethod(this, "scrollTo", optionsOrX, y);
};
}
_cache: {},
clearCache: function() {
StyleManager._cache = {};
},
/**
* Parse a string of CSS
* optionaly pass an identifier for caching
*
* Adopted from TJ Holowaychuk's
* https://github.com/visionmedia/css-parse
*
* Minor changes include removing the "stylesheet" root and
* using String.charAt(i) instead of String[i] for IE7 compatibility
*/
parse: function(css, identifier) {
/**
* Opening brace.
*/
function open() {
return match(/^\{\s*/)
}
/**
* Closing brace.
*/
function close() {
return match(/^\}\s*/)
}
/**
* Parse ruleset.
*/
function rules() {
var node;
var rules = [];
whitespace();
comments(rules);
while (css.charAt(0) != '}' && (node = atrule() || rule())) {
rules.push(node);
comments(rules);
}
return rules
}
/**
* Match `re` and return captures.
*/
function match(re) {
var m = re.exec(css);
if (!m) return
css = css.slice(m[0].length);
return m
}
/**
* Parse whitespace.
*/
function whitespace() {
match(/^\s*/);
}
/**
* Parse comments
*/
function comments(rules) {
rules = rules || [];
var c;
while (c = comment()) rules.push(c);
return rules
}
/**
* Parse comment.
*/
function comment() {
if ('/' == css[0] && '*' == css[1]) {
var i = 2;
while ('*' != css[i] || '/' != css[i + 1]) ++i;
i += 2;
var comment = css.slice(2, i - 2);
css = css.slice(i);
whitespace();
return { comment: comment }
}
}
/**
* Parse selector.
*/
function selector() {
var m = match(/^([^{]+)/);
if (!m) return
return trim(m[0]).split(/\s*,\s*/)
}
/**
* Parse declaration.
*/
function declaration() {
// prop
var prop = match(/^(\*?[\-\w]+)\s*/);
if (!prop) return
prop = prop[0];
// :
if (!match(/^:\s*/)) return
// val
var val = match(/^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^\)]*?\)|[^};])+)\s*/);
if (!val) return
val = trim(val[0]);
//
match(/^[;\s]*/);
return { property: prop, value: val }
}
/**
* Parse keyframe.
*/
function keyframe() {
var m;
var vals = [];
while (m = match(/^(from|to|\d+%|\.\d+%|\d+\.\d+%)\s*/)) {
vals.push(m[1]);
match(/^,\s*/);
}
if (!vals.length) return
return {
values: vals,
declarations: declarations()
}
}
/**
* Parse keyframes.
*/
function keyframes() {
var m = match(/^@([\-\w]+)?keyframes */);
if (!m) return
var vendor = m[1];
// identifier
var m = match(/^([\-\w]+)\s*/);
if (!m) return
var name = m[1];
if (!open()) return
comments();
var frame;
var frames = [];
while (frame = keyframe()) {
frames.push(frame);
comments();
}
if (!close()) return
var obj = {
name: name,
keyframes: frames
};
// don't include vendor unles there's a match
if (vendor) obj.vendor = vendor;
return obj
}
/**
* Parse supports.
*/
function supports() {
var m = match(/^@supports *([^{]+)/);
if (!m) return
var supports = trim(m[1]);
if (!open()) return
comments();
var style = rules();
if (!close()) return
return { supports: supports, rules: style }
}
/**
* Parse media.
*/
function media() {
var m = match(/^@media *([^{]+)/);
if (!m) return
var media = trim(m[1]);
if (!open()) return
comments();
var style = rules();
if (!close()) return
return { media: media, rules: style }
}
/**
* Parse paged media.
*/
function atpage() {
var m = match(/^@page */);
if (!m) return
var sel = selector() || [];
var decls = [];
if (!open()) return
comments();
// declarations
var decl;
while (decl = declaration() || atmargin()) {
decls.push(decl);
comments();
}
if (!close()) return
return {
type: "page",
selectors: sel,
declarations: decls
}
}
/**
* Parse margin at-rules
*/
function atmargin() {
var m = match(/^@([a-z\-]+) */);
if (!m) return
var type = m[1];
return {
type: type,
declarations: declarations()
}
}
/**
* Parse import
*/
function atimport() {
return _atrule('import')
}
/**
* Parse charset
*/
function atcharset() {
return _atrule('charset')
}
/**
* Parse namespace
*/
function atnamespace() {
return _atrule('namespace')
}
/**
* Parse non-block at-rules
*/
function _atrule(name) {
var m = match(new RegExp('^@' + name + ' *([^;\\n]+);\\s*'));
if (!m) return
var ret = {};
ret[name] = trim(m[1]);
return ret
}
/**
* Parse declarations.
*/
function declarations() {
var decls = [];
if (!open()) return
comments();
// declarations
var decl;
while (decl = declaration()) {
decls.push(decl);
comments();
}
if (!close()) return
return decls
}
/**
* Parse at rule.
*/
function atrule() {
return keyframes()
|| media()
|| supports()
|| atimport()
|| atcharset()
|| atnamespace()
|| atpage()
}
/**
* Parse rule.
*/
function rule() {
var sel = selector();
if (!sel) return
comments();
return { selectors: sel, declarations: declarations() }
}
/**
* Check the cache first, otherwise parse the CSS
*/
if (identifier && StyleManager._cache[identifier]) {
return StyleManager._cache[identifier]
} else {
// strip comments before parsing
css = css.replace(/\/\*[\s\S]*?\*\//g, "");
return StyleManager._cache[identifier] = rules()
}
},
/**
* Filter a ruleset by the passed keywords
* Keywords may be either selector or property/value patterns
*/
filter: function(rules, keywords) {
var filteredRules = [];
/**
* Concat a2 onto a1 even if a1 is undefined
*/
function safeConcat(a1, a2) {
if (!a1 && !a2) return
if (!a1) return [a2]
return a1.concat(a2)
}
/**
* Add a rule to the filtered ruleset,
* but don't add empty media or supports values
*/
function addRule(rule) {
if (rule.media == null) delete rule.media;
if (rule.supports == null) delete rule.supports;
filteredRules.push(rule);
}
function containsKeyword(string, keywordList) {
if (!keywordList) return
var i = keywordList.length;
while (i--) {
if (string.indexOf(keywordList[i]) >= 0) return true
}
}
function matchesKeywordPattern(declaration, patternList) {
var wildcard = /\*/
, pattern
, parts
, reProp
, reValue
, i = 0;
while (pattern = patternList[i++]) {
parts = pattern.split(":");
reProp = new RegExp("^" + trim(parts[0]).replace(wildcard, ".*") + "$");
reValue = new RegExp("^" + trim(parts[1]).replace(wildcard, ".*") + "$");
if (reProp.test(declaration.property) && reValue.test(declaration.value)) {
return true
}
}
}
function matchSelectors(rule, media, supports) {
if (!keywords.selectors) return
if (containsKeyword(rule.selectors.join(","), keywords.selectors)) {
addRule({
media: media,
supports: supports,
selectors: rule.selectors,
declarations: rule.declarations
});
return true
}
}
function matchesDeclaration(rule, media, supports) {
if (!keywords.declarations) return
var declaration
, i = 0;
while (declaration = rule.declarations[i++]) {
if (matchesKeywordPattern(declaration, keywords.declarations)) {
addRule({
media: media,
supports: supports,
selectors: rule.selectors,
declarations: rule.declarations
});
return true
}
}
}
function filterRules(rules, media, supports) {
var rule
, i = 0;
while (rule = rules[i++]) {
if (rule.declarations) {
matchSelectors(rule, media, supports) || matchesDeclaration(rule, media, supports);
}
else if (rule.rules && rule.media) {
filterRules(rule.rules, safeConcat(media, rule.media), supports);
}
else if (rule.rules && rule.supports) {
filterRules(rule.rules, media, safeConcat(supports, rule.supports));
}
}
}
// start the filtering
filterRules(rules);
// return the results
return filteredRules
}
};
var MediaManager = (function() {
var reMinWidth = /\(min\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/
, reMaxWidth = /\(max\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/
// a cache of the active media query info
, mediaQueryMap = {}
// the value of an `em` as used in a media query,
// not necessarily the base font-size
, emValueInPixels
, currentWidth;
/**
* Get the pixel value of 1em for use in parsing media queries
* ems in media queries are not affected by CSS, instead they
* are the value of the browsers default font size, usually 16px
*/
function getEmValueInPixels() {
// cache this value because it probably won't change and
// it's expensive to lookup
if (emValueInPixels) return emValueInPixels
var html = document.documentElement
, body = document.body
, originalHTMLFontSize = html.style.fontSize
, originalBodyFontSize = body.style.fontSize
, div = document.createElement("div");
// 1em is the value of the default font size of the browser
// reset html and body to ensure the correct value is returned
html.style.fontSize = "1em";
body.style.fontSize = "1em";
// add a test element and measure it
body.appendChild(div);
div.style.width = "1em";
div.style.position = "absolute";
emValueInPixels = div.offsetWidth;
// remove the test element and restore the previous values
body.removeChild(div);
body.style.fontSize = originalBodyFontSize;
html.style.fontSize = originalHTMLFontSize;
return emValueInPixels
}
/**
* Use the browsers matchMedia function or existing shim
*/
function matchMediaNatively(query) {
return window.matchMedia(query)
}
/**
* Try to determine if a mediaQuery matches by
* parsing the query and figuring it out manually
* TODO: cache current width for repeated invocations
*/
function matchMediaManually(query) {
var minWidth
, maxWidth
, matches = false;
// recalculate the width if it's not set
// if (!currentWidth) currentWidth = document.documentElement.clientWidth
currentWidth = document.documentElement.clientWidth;
// parse min and max widths from query
if (reMinWidth.test(query)) {
minWidth = RegExp.$2 === "em"
? parseFloat(RegExp.$1) * getEmValueInPixels()
: parseFloat(RegExp.$1);
}
if (reMaxWidth.test(query)) {
maxWidth = RegExp.$2 === "em"
? parseFloat(RegExp.$1) * getEmValueInPixels()
: parseFloat(RegExp.$1);
}
// if both minWith and maxWidth are set
if (minWidth && maxWidth) {
matches = (minWidth <= currentWidth && maxWidth >= currentWidth);
} else {
if (minWidth && minWidth <= currentWidth) matches = true;
if (maxWidth && maxWidth >= currentWidth) matches = true;
}
// return fake MediaQueryList object
return {
matches: matches,
media: query
}
}
return {
/**
* Similar to the window.matchMedia method
* results are cached to avoid expensive relookups
* @returns MediaQueryList (or a faked one)
*/
matchMedia: function(query) {
return supports.matchMedia
? matchMediaNatively(query)
: matchMediaManually(query)
// return mediaQueryMap[query] || (
// mediaQueryMap[query] = supports.matchMedia
// ? matchMediaNatively(query)
// : matchMediaManually(query)
// )
},
clearCache: function() {
// we don't use cache when the browser supports matchMedia listeners
if (!supports.nativeMatchMedia) {
currentWidth = null;
}
}
}
}());
var EventManager = (function() {
var MediaListener = (function() {
var listeners = [];
return {
add: function(polyfill, mql, fn) {
var listener
, i = 0;
// if the listener is already in the array, return false
while (listener = listeners[i++]) {
if (
listener.polyfill == polyfill
&& listener.mql === mql
&& listener.fn === fn
) {
return false
}
}
// otherwise add it
mql.addListener(fn);
listeners.push({
polyfill: polyfill,
mql: mql,
fn: fn
});
},
remove: function(polyfill) {
var listener
, i = 0;
while (listener = listeners[i++]) {
if (listener.polyfill === polyfill) {
listener.mql.removeListener(listener.fn);
listeners.splice(--i, 1);
}
}
}
}
}());
var ResizeListener = (function(listeners) {
function onresize() {
var listener
, i = 0;
while (listener = listeners[i++]) {
listener.fn();
}
}
return {
add: function(polyfill, fn) {
if (!listeners.length) {
if (window.addEventListener) {
window.addEventListener("resize", onresize, false);
} else {
window.attachEvent("onresize", onresize);
}
}
listeners.push({
polyfill: polyfill,
fn: fn
});
},
remove: function(polyfill) {
var listener
, i = 0;
while (listener = listeners[i++]) {
if (listener.polyfill === polyfill) {
listeners.splice(--i, 1);
}
}
if (!listeners.length) {
if (window.removeEventListener) {
window.removeEventListener("resize", onresize, false);
} else if (window.detachEvent) {
window.detachEvent("onresize", onresize);
}
}
}
}
}([]));
/**
* Simple debounce function
*/
function debounce(fn, wait) {
var timeout;
return function() {
clearTimeout(timeout);
timeout = setTimeout(fn, wait);
}
}
return {
removeListeners: function(polyfill) {
supports.nativeMatchMedia
? MediaListener.remove(polyfill)
: ResizeListener.remove(polyfill);
},
addListeners: function(polyfill, callback) {
var queries = polyfill._mediaQueryMap
, state = {};(function() {
for (var query in queries) {
if (!queries.hasOwnProperty(query)) continue
state[query] = MediaManager.matchMedia(query).matches;
}
}());
/**
* Register the listeners to detect media query changes
* if the browser doesn't support this natively, use resize events instead
*/
function addListeners() {
if (supports.nativeMatchMedia) {
for (var query in queries) {
if (queries.hasOwnProperty(query)) {
// a closure is needed here to keep the variable reference
(function(mql, query) {
MediaListener.add(polyfill, mql, function() {
callback.call(polyfill, query, mql.matches);
});
}(queries[query], query));
}
}
} else {
var fn = debounce((function(polyfill, queries) {
return function() {
updateMatchedMedia(polyfill, queries);
}
}(polyfill, queries)), polyfill._options.debounceTimeout || 100);
ResizeListener.add(polyfill, fn);
}
}
/**
* Check each media query to see if it still matches
* Note: this is only invoked when the browser doesn't
* natively support window.matchMedia addListeners
*/
function updateMatchedMedia(polyfill, queries) {
var query
, current = {};
// clear the cache since a resize just happened
MediaManager.clearCache();
// look for media matches that have changed since the last inspection
for (query in queries) {
if (!queries.hasOwnProperty(query)) continue
current[query] = MediaManager.matchMedia(query).matches;
if (current[query] != state[query]) {
callback.call(polyfill, query, MediaManager.matchMedia(query).matches);
}
}
state = current;
}
addListeners();
}
}
}());
function Ruleset(rules) {
var i = 0
, rule;
this._rules = [];
while (rule = rules[i++]) {
this._rules.push(new Rule(rule));
}
}
Ruleset.prototype.each = function(iterator, context) {
var rule
, i = 0;
context || (context = this);
while (rule = this._rules[i++]) {
iterator.call(context, rule);
}
};
Ruleset.prototype.size = function() {
return this._rules.length
};
Ruleset.prototype.at = function(index) {
return this._rules[index]
};
function Rule(rule) {
this._rule = rule;
}
Rule.prototype.getDeclaration = function() {
var styles = {}
, i = 0
, declaration
, declarations = this._rule.declarations;
while (declaration = declarations[i++]) {
styles[declaration.property] = declaration.value;
}
return styles
};
Rule.prototype.getSelectors = function() {
return this._rule.selectors.join(", ")
};
Rule.prototype.getMedia = function() {
return this._rule.media.join(" and ")
};
function Polyfill(options) {
if (!(this instanceof Polyfill)) return new Polyfill(options)
// set the options
this._options = options;
// allow the keywords option to be the only object passed
if (!options.keywords) this._options = { keywords: options };
this._promise = [];
// then do the stuff
this._getStylesheets();
this._downloadStylesheets();
this._parseStylesheets();
this._filterCSSByKeywords();
this._buildMediaQueryMap();
this._reportInitialMatches();
this._addMediaListeners();
}
/**
* Fired when the media change and new rules match
*/
Polyfill.prototype.doMatched = function(fn) {
this._doMatched = fn;
this._resolve();
return this
};
/**
* Fired when the media changes and previously matching rules no longer match
*/
Polyfill.prototype.undoUnmatched = function(fn) {
this._undoUnmatched = fn;
this._resolve();
return this
};
/**
* Get all the rules the match the current media
*/
Polyfill.prototype.getCurrentMatches = function() {
var i = 0
, rule
, media
, matches = [];
while (rule = this._filteredRules[i++]) {
// rules are considered matches if they they have
// no media query or the media query curently matches
media = rule.media && rule.media.join(" and ");
if (!media || MediaManager.matchMedia(media).matches) {
matches.push(rule);
}
}
return new Ruleset(matches)
};
/**
* Destroy the instance
* Remove any bound events and send all current
* matches to the callback as unmatches
*/
Polyfill.prototype.destroy = function() {
if (this._undoUnmatched) {
this._undoUnmatched(this.getCurrentMatches());
EventManager.removeListeners(this);
}
return
};
/**
* Defer a task until after a condition is met
*/
Polyfill.prototype._defer = function(condition, callback) {
condition.call(this)
? callback.call(this)
: this._promise.push({condition: condition, callback: callback});
};
/**
* Invoke any functions that have been deferred
*/
Polyfill.prototype._resolve = function() {
var promise
, i = 0;
while (promise = this._promise[i]) {
if (promise.condition.call(this)) {
this._promise.splice(i, 1);
promise.callback.call(this);
} else {
i++;
}
}
};
/**
* Get a list of <link> tags in the head
* optionally filter by the include/exclude options
*/
Polyfill.prototype._getStylesheets = function() {
var i = 0
, id
, ids
, link
, links
, inline
, inlines
, stylesheet
, stylesheets = [];
if (this._options.include) {
// get only the included stylesheets link tags
ids = this._options.include;
while (id = ids[i++]) {
if (link = document.getElementById(id)) {
// if this tag is an inline style
if (link.nodeName === "STYLE") {
stylesheet = { text: link.textContent };
stylesheets.push(stylesheet);
continue
}
// ignore print stylesheets
if (link.media && link.media == "print") continue
// ignore non-local stylesheets
if (!isLocalURL(link.href)) continue
stylesheet = { href: link.href };
link.media && (stylesheet.media = link.media);
stylesheets.push(stylesheet);
}
}
}
else {
// otherwise get all the stylesheets stylesheets tags
// except the explicitely exluded ones
ids = this._options.exclude;
links = document.getElementsByTagName( "link" );
while (link = links[i++]) {
if (
link.rel
&& (link.rel == "stylesheet")
&& (link.media != "print") // ignore print stylesheets
&& (isLocalURL(link.href)) // only request local stylesheets
&& (!inArray(link.id, ids))
) {
stylesheet = { href: link.href };
link.media && (stylesheet.media = link.media);
stylesheets.push(stylesheet);
}
}
inlines = document.getElementsByTagName('style');
i = 0;
while (inline = inlines[i++]){
stylesheet = { text: inline.textContent };
stylesheets.push(stylesheet);
}
}
return this._stylesheets = stylesheets
};
/**
* Download each stylesheet in the _stylesheetURLs array
*/
Polyfill.prototype._downloadStylesheets = function() {
var self = this
, stylesheet
, urls = []
, i = 0;
while (stylesheet = this._stylesheets[i++]) {
urls.push(stylesheet.href);
}
DownloadManager.request(urls, function(stylesheets) {
var stylesheet
, i = 0;
while (stylesheet = stylesheets[i]) {
self._stylesheets[i++].text = stylesheet;
}
self._resolve();
});
};
Polyfill.prototype._parseStylesheets = function() {
this._defer(
function() {
return this._stylesheets
&& this._stylesheets.length
&& this._stylesheets[0].text },
function() {
var i = 0
, stylesheet;
while (stylesheet = this._stylesheets[i++]) {
stylesheet.rules = StyleManager.parse(stylesheet.text, stylesheet.url);
}
}
);
};
Polyfill.prototype._filterCSSByKeywords = function() {
this._defer(
function() {
return this._stylesheets
&& this._stylesheets.length
&& this._stylesheets[0].rules
},
function() {
var stylesheet
, media
, rules = []
, i = 0;
while (stylesheet = this._stylesheets[i++]) {
media = stylesheet.media;
// Treat stylesheets with a media attribute as being contained inside
// a single @media block, but ignore `all` and `screen` media values
// since they're basically meaningless in this context
if (media && media != "all" && media != "screen") {
rules.push({rules: stylesheet.rules, media: stylesheet.media});
} else {
rules = rules.concat(stylesheet.rules);
}
}
this._filteredRules = StyleManager.filter(rules, this._options.keywords);
}
);
};
Polyfill.prototype._buildMediaQueryMap = function() {
this._defer(
function() { return this._filteredRules },
function() {
var i = 0
, media
, rule;
this._mediaQueryMap = {};
while (rule = this._filteredRules[i++]) {
if (rule.media) {
media = rule.media.join(" and ");
this._mediaQueryMap[media] = MediaManager.matchMedia(media);
}
}
}
);
};
Polyfill.prototype._reportInitialMatches = function() {
this._defer(
function() {
return this._filteredRules && this._doMatched
},
function() {
this._doMatched(this.getCurrentMatches());
}
);
};
Polyfill.prototype._addMediaListeners = function() {
this._defer(
function() {
return this._filteredRules
&& this._doMatched
&& this._undoUnmatched
},
function() {
EventManager.addListeners(
this,
function(query, isMatch) {
var i = 0
, rule
, matches = []
, unmatches = [];
while (rule = this._filteredRules[i++]) {
if (rule.media && rule.media.join(" and ") == query) {
(isMatch ? matches : unmatches).push(rule);
}
}
matches.length && this._doMatched(new Ruleset(matches));
unmatches.length && this._undoUnmatched(new Ruleset(unmatches));
}
);
}
);
};
Polyfill.modules = {
DownloadManager: DownloadManager,
StyleManager: StyleManager,
MediaManager: MediaManager,
EventManager: EventManager
};
Polyfill.constructors = {
Ruleset: Ruleset,
Rule: Rule
};
window.Polyfill = Polyfill;
}(window, document));
/**
* Is true if the browser natively supports the 'scroll-behavior' CSS-property.
* @type {boolean}
*/
var SUPPORTS_SCROLL_BEHAVIOR = "scrollBehavior" in document.documentElement.style;
/**
* A Map between elements and their IElementWrapper
* @type {Map<any, any>}
*/
var ELEMENT_WRAPPER = new Map();
/**
* Adds an IElementWrapper
* @param {HTMLElement} element
* @param {IElementWrapper} wrapper
*/
function addWrapper(element, wrapper) {
ELEMENT_WRAPPER.set(element, wrapper);
}
/**
* Gets an IElementWrapper, if any exists for the provided element
* @param {HTMLElement} element
* @returns {IElementWrapper}
*/
function getWrapper(element) {
return ELEMENT_WRAPPER.get(element);
}
/**
* Unwraps the scroll method
* @param {HTMLElement} element
*/
function unwrapScroll(element) {
var wrapper = getWrapper(element);
// If there is no wrapper, do nothing
if (wrapper == null)
return;
// Otherwise, re-associate the original prototype methods with the element
element.scroll = wrapper.scroll.original;
element.scrollTo = wrapper.scrollTo.original;
}
/**
* Gets all HTMLElements matched by the provided selectors
* @param {string[]} selectors
* @returns {HTMLElement[]}
*/
function getElementsForSelectors(selectors) {
// Match all of the selectors
var elements = [];
selectors.forEach(function (selector) {
var element = getElementForSelector(selector);
if (element != null)
elements.push(element);
});
return elements;
}
/**
* Gets an element for the provided selector
* @param {string} selector
* @returns {HTMLElement}
*/
function getElementForSelector(selector) {
return document.querySelector(selector);
}
/**
* Disposes all elements that has once had a 'scroll-behavior' CSS property value of 'smooth' but hasn't anymore
* @param {string[]} selectors
*/
function disposeElements(selectors) {
getElementsForSelectors(selectors).forEach(function (element) { return disposeElement(element); });
}
/**
* Disposes an element that has once had a 'scroll-behavior' CSS property value of 'smooth' but hasn't anymore
* @param {HTMLElement} element
*/
function disposeElement(element) {
unwrapScroll(element);
}
var ScrollBehaviorKind;
(function (ScrollBehaviorKind) {
ScrollBehaviorKind["AUTO"] = "auto";
ScrollBehaviorKind["SMOOTH"] = "smooth";
})(ScrollBehaviorKind || (ScrollBehaviorKind = {}));
var HALF = 0.5;
/**
* The easing function to use when applying the smooth scrolling
* @param {number} k
* @returns {number}
*/
function ease(k) {
return HALF * (1 - Math.cos(Math.PI * k));
}
/**
* Returns a High Resolution timestamp if possible, otherwise fallbacks to Date.now()
* @returns {number}
*/
function now() {
if ("performance" in window)
return performance.now();
return Date.now();
}
/**
* The duration of a smooth scroll
* @type {number}
*/
var SCROLL_TIME = 200;
/**
* Performs a smooth repositioning of the scroll
* @param {ISmoothScrollOptions} options
*/
function smoothScroll(options) {
var startTime = options.startTime, startX = options.startX, startY = options.startY, x = options.x, y = options.y, method = options.method, element = options.element;
var currentTime = now();
var value;
var currentX;
var currentY;
var elapsed = (currentTime - startTime) / SCROLL_TIME;
// avoid elapsed times higher than one
elapsed = elapsed > 1 ? 1 : elapsed;
// apply easing to elapsed time
value = ease(elapsed);
currentX = startX + (x - startX) * value;
currentY = startY + (y - startY) * value;
method.call(element, currentX, currentY);
// scroll more if we have not reached our destination
if (currentX !== x || currentY !== y) {
requestAnimationFrame(function () { return smoothScroll(options); });
/**
* Patches the 'scroll' method on the Window prototype
*/
function patchWindowScroll() {
window.scroll = function (optionsOrX, y) {
handleScrollMethod(this, "scroll", optionsOrX, y);
};
}
}
/**
* Updates the scroll position of an element
* @param {HTMLElement} element
* @param {number} x
* @param {number} y
*/
function updateScrollPosition(element, x, y) {
element.scrollLeft = x;
element.scrollTop = y;
}
/**
* Gets the Smooth Scroll Options to use for the step function
* @param {HTMLElement|Window} element
* @param {number} x
* @param {number} y
* @param {Function} originalFunction
* @returns {ISmoothScrollOptions}
*/
function getSmoothScrollOptions(element, x, y, originalFunction) {
var startTime = now();
if (!(element instanceof Element)) {
// Use window as the scroll container
var scrollX_1 = window.scrollX, pageXOffset_1 = window.pageXOffset, scrollY_1 = window.scrollY, pageYOffset_1 = window.pageYOffset;
return {
element: window,
startX: scrollX_1 == null || scrollX_1 === 0 ? pageXOffset_1 : scrollX_1,
startY: scrollY_1 == null || scrollY_1 === 0 ? pageYOffset_1 : scrollY_1,
method: originalFunction,
startTime: startTime,
x: x,
y: y
/**
* Patches the 'scrollBy' method on the Window prototype
*/
function patchWindowScrollBy() {
window.scrollBy = function (optionsOrX, y) {
handleScrollMethod(this, "scrollBy", optionsOrX, y);
};
}
else {
var scrollLeft = element.scrollLeft, scrollTop = element.scrollTop;
return {
element: element,
startX: scrollLeft,
startY: scrollTop,
x: x,
y: y,
startTime: startTime,
method: updateScrollPosition.bind(element, element)
/**
* Patches the 'scrollTo' method on the Window prototype
*/
function patchWindowScrollTo() {
window.scrollTo = function (optionsOrX, y) {
handleScrollMethod(this, "scrollTo", optionsOrX, y);
};
}
}
/**
* Gets the scrollLeft version of an element. If a window is provided, the 'pageXOffset' is used.
* @param {HTMLElement | Window} element
* @returns {number}
*/
function getScrollLeft(element) {
if (element instanceof Element)
return element.scrollLeft;
return element.pageXOffset;
}
// tslint:disable:no-any
/**
* Gets the parent of an element, taking into account DocumentFragments, ShadowRoots, as well as the root context (window)
* @param {EventTarget} currentElement
* @returns {EventTarget | null}
*/
function getParent(currentElement) {
if ("nodeType" in currentElement && currentElement.nodeType === 1) {
return currentElement.parentNode;
}
if ("ShadowRoot" in window && (currentElement instanceof window.ShadowRoot)) {
return currentElement.host;
}
else if (currentElement === document) {
return window;
}
else if (currentElement instanceof Node)
return currentElement.parentNode;
return null;
}
/**
* Gets the scrollTop version of an element. If a window is provided, the 'pageYOffset' is used.
* @param {HTMLElement | Window} element
* @returns {number}
*/
function getScrollTop(element) {
if (element instanceof Element)
return element.scrollTop;
return element.pageYOffset;
}
/**
* Wraps the scroll method
* @param {HTMLElement} element
*/
function wrapScroll(element) {
var target = element instanceof HTMLHtmlElement || element instanceof HTMLBodyElement ? window : element;
// Check if the target has already been wrapped, and if so, apply the original scroll function from it
var wrapped = getWrapper(target);
var originalScroll = wrapped == null ? target.scroll : wrapped.scroll.original;
var originalScrollTo = wrapped == null ? target.scrollTo : wrapped.scrollTo.original;
var originalScrollBy = wrapped == null ? target.scrollBy : wrapped.scrollBy.original;
target.scroll = onScroll.bind(target, target, originalScroll, "scroll");
target.scrollTo = onScroll.bind(target, target, originalScrollTo, "scroll");
target.scrollBy = onScroll.bind(target, target, originalScrollBy, "scrollBy");
// Store it so we can retrieve the original handlers later on
addWrapper(target, {
scroll: {
original: originalScroll,
wrapped: target.scroll
},
scrollTo: {
original: originalScrollTo,
wrapped: target.scrollTo
},
scrollBy: {
original: originalScrollBy,
wrapped: target.scrollBy
/**
* Finds the nearest ancestor of an element that can scroll
* @param {Element} target
* @returns {Element|Window?}
*/
function findNearestAncestorsWithScrollBehavior(target) {
var currentElement = target;
while (currentElement != null) {
var behavior = getScrollBehavior(currentElement);
if (behavior != null)
return [currentElement, behavior];
var parent_1 = getParent(currentElement);
// If the last Node is equal to the latest parentNode, break immediately
if (parent_1 === currentElement)
break;
currentElement = parent_1;
}
});
}
/**
* Called when 'scroll()' is invoked on the element
* @param {HTMLElement} element
* @param {Function} original
* @param {string} kind
* @param {number | ScrollToOptions} x
* @param {number} y
*/
function onScroll(element, original, kind, x, y) {
if (typeof x === "number") {
onScrollPrimitive(x, y, element, original, kind);
return undefined;
}
else {
onScrollWithOptions(x, element, original, kind);
}
}
/**
* Invoked when a 'ScrollToOptions' dict is provided to 'scroll()' as the first argument
* @param {ScrollToOptions} options
* @param {HTMLElement} element
* @param {Function} original
* @param {string} kind
*/
function onScrollWithOptions(options, element, original, kind) {
// If scrolling is explicitly requested non-smooth, invoke the original scroll function
if (options.behavior != null && options.behavior !== ScrollBehaviorKind.SMOOTH) {
original(options);
}
else {
// Otherwise, invoke the primitive scroll function
var normalizedLeft = options.left == null ? 0 : options.left;
var normalizedTop = options.top == null ? 0 : options.top;
onScrollPrimitive(normalizedLeft, normalizedTop, element, original, kind);
}
}
/**
* Invoked when 'scroll()' is invoked with primitive x or y values
* @param {number} x
* @param {number} y
* @param {HTMLElement} element
* @param {Function} original
* @param {string} kind
*/
function onScrollPrimitive(x, y, element, original, kind) {
var normalizedX = kind === "scroll" ? x : getScrollLeft(element) + x;
var normalizedY = kind === "scroll" ? y : getScrollTop(element) + y;
smoothScroll(getSmoothScrollOptions(element, normalizedX, normalizedY, original));
}
/**
* Registers all elements with a scroll-behavior CSS-property value of 'smooth'
* @param {string[]} selectors
*/
function registerElements(selectors) {
getElementsForSelectors(selectors).forEach(function (element) { return registerElement(element); });
}
/**
* Registers an element with a scroll-behavior CSS-property value of 'smooth'
* @param {HTMLElement} element
*/
function registerElement(element) {
wrapScroll(element);
}
/**
* Get the current scroll behavior of an HTMLElement
* @param {HTMLElement} element
* @returns {ScrollBehaviorKind}
*/
function getScrollBehavior(element) {
var val = null;
// First, check as an attribute
var attribute = element.getAttribute("style");
if (attribute != null) {
// Find the position within the string where 'scroll-behavior' is declare (if it is).
var indexOfScrollBehavior = attribute.indexOf("scroll-behavior");
if (indexOfScrollBehavior >= 0) {
// Check where it ends. If it never sees a ';', it is the last (or only) style property of the string
var endIndexOfScrollBehavior = attribute.indexOf(";", indexOfScrollBehavior);
// Slice the attribute value from after the ':' sign and up until the next ';' (or to the end if it is the last or only style property)
val = attribute.slice(indexOfScrollBehavior + "scroll-behavior:".length, endIndexOfScrollBehavior < 0 ? undefined : endIndexOfScrollBehavior).trim();
// tslint:disable:no-any
/**
* Finds the nearest root from an element
* @param {Element} target
* @returns {Document|ShadowRoot}
*/
function findNearestRoot(target) {
var currentElement = target;
while (currentElement != null) {
if ("ShadowRoot" in window && (currentElement instanceof window.ShadowRoot)) {
// Assume this is a ShadowRoot
return currentElement;
}
var parent_1 = getParent(currentElement);
if (parent_1 === currentElement) {
return document;
}
currentElement = parent_1;
}
return document;
}
// If 'val' is still null, no match was found as an inline-style
if (val == null) {
/*tslint:disable*/
val = element.style.scrollBehavior;
/*tslint:enable*/
/**
* A Regular expression that matches id's of the form "#[digit]"
* @type {RegExp}
*/
var ID_WITH_LEADING_DIGIT_REGEXP = /^#\d/;
/**
* Catches anchor navigation to IDs within the same root and ensures that they can be smooth-scrolled
* if the scroll behavior is smooth in the first rooter within that context
*/
function catchNavigation() {
// Listen for 'click' events globally
window.addEventListener("click", function (e) {
// Only work with trusted events on HTMLAnchorElements
if (!e.isTrusted || !(e.target instanceof HTMLAnchorElement))
return;
var hrefAttributeValue = e.target.getAttribute("href");
// Only work with HTMLAnchorElements that navigates to a specific ID
if (hrefAttributeValue == null || !hrefAttributeValue.startsWith("#"))
return;
// Find the nearest ancestor that can be scrolled
var ancestorWithScrollBehaviorResult = findNearestAncestorsWithScrollBehavior(e.target);
// If there is none, don't proceed
if (ancestorWithScrollBehaviorResult == null)
return;
// Take the scroll behavior for that ancestor
var _a = __read(ancestorWithScrollBehaviorResult, 2), ancestorWithScrollBehavior = _a[0], behavior = _a[1];
// If the behavior isn't smooth, don't proceed
if (behavior !== "smooth")
return;
// Find the nearest root, whether it be a ShadowRoot or the document itself
var root = findNearestRoot(e.target);
// Attempt to match the selector from that root. querySelector' doesn't support IDs that start with a digit, so work around that limitation
var elementMatch = hrefAttributeValue.match(ID_WITH_LEADING_DIGIT_REGEXP) != null
? root.getElementById(hrefAttributeValue.slice(1))
: root.querySelector(hrefAttributeValue);
// If no selector could be found, don't proceed
if (elementMatch == null)
return;
// Otherwise, first prevent the default action.
e.preventDefault();
// Now, scroll to the element with that ID
ancestorWithScrollBehavior.scrollTo({
behavior: behavior,
top: elementMatch.offsetTop,
left: elementMatch.offsetLeft
});
});
}
/*tslint:enable:no-any*/
return val === ScrollBehaviorKind.SMOOTH ? ScrollBehaviorKind.SMOOTH : ScrollBehaviorKind.AUTO;
}
/**
* How often to check for elements with a 'scroll-behavior' CSS property
* @type {number}
*/
var PROPERTY_CHECK_INTERVAL = 3000;
/**
* How long to wait before tracking for the first time
* @type {number}
*/
var INIT_DELAY = 300;
/**
* The elements that are currently being tracked where 'scroll-behavior' is set as a style property
* @type {Set<HTMLElement>}
*/
var TRACKED_PROPERTY_ELEMENTS = new Set();
/**
* Starts tracking elements with a 'scroll-behavior' CSS property
*/
function startTracking() {
track();
}
/**
* Tracks all elements with a 'scroll-behavior' CSS property.
* Waits for the browser to become idle
*/
function track() {
setTimeout(trackElements, INIT_DELAY);
}
/**
* Tracks all elements. Some of this is by selectors, some of this is by watching CSS property values set imperatively
*/
function trackElements() {
trackSelectors();
startTrackingInlineProperties();
trackInlineProperties();
}
/**
* Registers an interval to track inline properties
*/
function startTrackingInlineProperties() {
setInterval(trackInlineProperties, PROPERTY_CHECK_INTERVAL);
}
/**
* Tracks inline properties
*/
function trackInlineProperties() {
var all = Array.from(document.querySelectorAll("*"));
var filtered = new Set(all.filter(function (node) { return node instanceof HTMLElement && getScrollBehavior(node) === ScrollBehaviorKind.SMOOTH; }));
// Check if a tracked element should be disposed (e.g. lost the property value in the meantime)
TRACKED_PROPERTY_ELEMENTS.forEach(function (trackedElement) {
if (!filtered.has(trackedElement)) {
TRACKED_PROPERTY_ELEMENTS.delete(trackedElement);
disposeElement(trackedElement);
var ELEMENT_ORIGINAL_SCROLL_INTO_VIEW = Element.prototype.scrollIntoView;
/**
* The majority of this file is based on https://github.com/stipsan/compute-scroll-into-view (MIT license),
* but has been rewritten to accept a scroller as an argument.
*/
/**
* Find out which edge to align against when logical scroll position is "nearest"
* Interesting fact: "nearest" works similarly to "if-needed", if the element is fully visible it will not scroll it
*
* Legends:
* ┌────────┐ ┏ ━ ━ ━ ┓
* │ target │ frame
* └────────┘ ┗ ━ ━ ━ ┛
*/
function alignNearest(scrollingEdgeStart, scrollingEdgeEnd, scrollingSize, scrollingBorderStart, scrollingBorderEnd, elementEdgeStart, elementEdgeEnd, elementSize) {
/**
* If element edge A and element edge B are both outside scrolling box edge A and scrolling box edge B
*
* ┌──┐
* ┏━│━━│━┓
* │ │
* ┃ │ │ ┃ do nothing
* │ │
* ┗━│━━│━┛
* └──┘
*
* If element edge C and element edge D are both outside scrolling box edge C and scrolling box edge D
*
* ┏ ━ ━ ━ ━ ┓
* ┌───────────┐
* │┃ ┃│ do nothing
* └───────────┘
* ┗ ━ ━ ━ ━ ┛
*/
if ((elementEdgeStart < scrollingEdgeStart &&
elementEdgeEnd > scrollingEdgeEnd) ||
(elementEdgeStart > scrollingEdgeStart && elementEdgeEnd < scrollingEdgeEnd)) {
return 0;
}
});
// Add new tracked elements from the filtered ones
filtered.forEach(function (filteredElement) {
if (!TRACKED_PROPERTY_ELEMENTS.has(filteredElement)) {
TRACKED_PROPERTY_ELEMENTS.add(filteredElement);
registerElement(filteredElement);
/**
* If element edge A is outside scrolling box edge A and element height is less than scrolling box height
*
* ┌──┐
* ┏━│━━│━┓ ┏━┌━━┐━┓
* └──┘ │ │
* from ┃ ┃ to ┃ └──┘ ┃
*
* ┗━ ━━ ━┛ ┗━ ━━ ━┛
*
* If element edge B is outside scrolling box edge B and element height is greater than scrolling box height
*
* ┏━ ━━ ━┓ ┏━┌━━┐━┓
* │ │
* from ┃ ┌──┐ ┃ to ┃ │ │ ┃
* │ │ │ │
* ┗━│━━│━┛ ┗━│━━│━┛
* │ │ └──┘
* │ │
* └──┘
*
* If element edge C is outside scrolling box edge C and element width is less than scrolling box width
*
* from to
* ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓
* ┌───┐ ┌───┐
* │ ┃ │ ┃ ┃ │ ┃
* └───┘ └───┘
* ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛
*
* If element edge D is outside scrolling box edge D and element width is greater than scrolling box width
*
* from to
* ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓
* ┌───────────┐ ┌───────────┐
* ┃ │ ┃ │ ┃ ┃ │
* └───────────┘ └───────────┘
* ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛
*/
if ((elementEdgeStart <= scrollingEdgeStart && elementSize <= scrollingSize) ||
(elementEdgeEnd >= scrollingEdgeEnd && elementSize >= scrollingSize)) {
return elementEdgeStart - scrollingEdgeStart - scrollingBorderStart;
}
});
}
/**
* Finds all elements with a 'scroll-behavior' CSS-property. Wraps all of them and unwraps all elements that
* has been previously wrapped but since lost their 'scroll-behavior' CSS-property
*/
function trackSelectors() {
window.Polyfill({
keywords: {
declarations: ["scroll-behavior: *"]
/**
* If element edge B is outside scrolling box edge B and element height is less than scrolling box height
*
* ┏━ ━━ ━┓ ┏━ ━━ ━┓
*
* from ┃ ┃ to ┃ ┌──┐ ┃
* ┌──┐ │ │
* ┗━│━━│━┛ ┗━└━━┘━┛
* └──┘
*
* If element edge A is outside scrolling box edge A and element height is greater than scrolling box height
*
* ┌──┐
* │ │
* │ │ ┌──┐
* ┏━│━━│━┓ ┏━│━━│━┓
* │ │ │ │
* from ┃ └──┘ ┃ to ┃ │ │ ┃
* │ │
* ┗━ ━━ ━┛ ┗━└━━┘━┛
*
* If element edge C is outside scrolling box edge C and element width is greater than scrolling box width
*
* from to
* ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓
* ┌───────────┐ ┌───────────┐
* │ ┃ │ ┃ │ ┃ ┃
* └───────────┘ └───────────┘
* ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛
*
* If element edge D is outside scrolling box edge D and element width is less than scrolling box width
*
* from to
* ┏ ━ ━ ━ ━ ┓ ┏ ━ ━ ━ ━ ┓
* ┌───┐ ┌───┐
* ┃ │ ┃ │ ┃ │ ┃
* └───┘ └───┘
* ┗ ━ ━ ━ ━ ┛ ┗ ━ ━ ━ ━ ┛
*
*/
if ((elementEdgeEnd > scrollingEdgeEnd && elementSize < scrollingSize) ||
(elementEdgeStart < scrollingEdgeStart && elementSize > scrollingSize)) {
return elementEdgeEnd - scrollingEdgeEnd + scrollingBorderEnd;
}
})
.doMatched(function (rules) { return registerElements(getSelectorsForCSSMatch(rules)); })
.undoUnmatched(function (rules) { return disposeElements(getSelectorsForCSSMatch(rules)); });
}
/**
* Gets all selectors for a CSS match
* @param {PolyfillRuleSet} rules
* @returns {string[]}
*/
function getSelectorsForCSSMatch(rules) {
var candidates = [];
rules.each(function (rule) {
var declaration = rule.getDeclaration();
if (declaration["scroll-behavior"] === ScrollBehaviorKind.SMOOTH) {
candidates.push(rule.getSelectors());
return 0;
}
function computeScrollIntoView(target, scroller, options) {
var block = options.block, inline = options.inline;
// Used to handle the top most element that can be scrolled
var scrollingElement = document.scrollingElement || document.documentElement;
// Support pinch-zooming properly, making sure elements scroll into the visual viewport
// Browsers that don't support visualViewport will report the layout viewport dimensions on document.documentElement.clientWidth/Height
// and viewport dimensions on window.innerWidth/Height
// https://www.quirksmode.org/mobile/viewports2.html
// https://bokand.github.io/viewport/index.html
var viewportWidth = window.visualViewport != null
? visualViewport.width
: innerWidth;
var viewportHeight = window.visualViewport != null
? visualViewport.height
: innerHeight;
var viewportX = window.scrollX != null ? window.scrollX : window.pageXOffset;
var viewportY = window.scrollY != null ? window.scrollY : window.pageYOffset;
var _a = target.getBoundingClientRect(), targetHeight = _a.height, targetWidth = _a.width, targetTop = _a.top, targetRight = _a.right, targetBottom = _a.bottom, targetLeft = _a.left;
// These values mutate as we loop through and generate scroll coordinates
var targetBlock = block === "start" || block === "nearest"
? targetTop
: block === "end"
? targetBottom
: targetTop + targetHeight / 2; // block === 'center
var targetInline = inline === "center"
? targetLeft + targetWidth / 2
: inline === "end"
? targetRight
: targetLeft; // inline === 'start || inline === 'nearest
var _b = scroller.getBoundingClientRect(), height = _b.height, width = _b.width, top = _b.top, right = _b.right, bottom = _b.bottom, left = _b.left;
var frameStyle = getComputedStyle(scroller);
var borderLeft = parseInt(frameStyle.borderLeftWidth, 10);
var borderTop = parseInt(frameStyle.borderTopWidth, 10);
var borderRight = parseInt(frameStyle.borderRightWidth, 10);
var borderBottom = parseInt(frameStyle.borderBottomWidth, 10);
var blockScroll = 0;
var inlineScroll = 0;
// The property existance checks for offset[Width|Height] is because only HTMLElement objects have them, but any Element might pass by here
// @TODO find out if the "as HTMLElement" overrides can be dropped
var scrollbarWidth = "offsetWidth" in scroller
? scroller.offsetWidth -
scroller.clientWidth -
borderLeft -
borderRight
: 0;
var scrollbarHeight = "offsetHeight" in scroller
? scroller.offsetHeight -
scroller.clientHeight -
borderTop -
borderBottom
: 0;
if (scrollingElement === scroller) {
// Handle viewport logic (document.documentElement or document.body)
if (block === "start") {
blockScroll = targetBlock;
}
else if (block === "end") {
blockScroll = targetBlock - viewportHeight;
}
else if (block === "nearest") {
blockScroll = alignNearest(viewportY, viewportY + viewportHeight, viewportHeight, borderTop, borderBottom, viewportY + targetBlock, viewportY + targetBlock + targetHeight, targetHeight);
}
else {
// block === 'center' is the default
blockScroll = targetBlock - viewportHeight / 2;
}
if (inline === "start") {
inlineScroll = targetInline;
}
else if (inline === "center") {
inlineScroll = targetInline - viewportWidth / 2;
}
else if (inline === "end") {
inlineScroll = targetInline - viewportWidth;
}
else {
// inline === 'nearest' is the default
inlineScroll = alignNearest(viewportX, viewportX + viewportWidth, viewportWidth, borderLeft, borderRight, viewportX + targetInline, viewportX + targetInline + targetWidth, targetWidth);
}
// Apply scroll position offsets and ensure they are within bounds
// @TODO add more test cases to cover this 100%
blockScroll = Math.max(0, blockScroll + viewportY);
inlineScroll = Math.max(0, inlineScroll + viewportX);
}
});
return candidates;
}
else {
// Handle each scrolling frame that might exist between the target and the viewport
if (block === "start") {
blockScroll = targetBlock - top - borderTop;
}
else if (block === "end") {
blockScroll = targetBlock - bottom + borderBottom + scrollbarHeight;
}
else if (block === "nearest") {
blockScroll = alignNearest(top, bottom, height, borderTop, borderBottom + scrollbarHeight, targetBlock, targetBlock + targetHeight, targetHeight);
}
else {
// block === 'center' is the default
blockScroll = targetBlock - (top + height / 2) + scrollbarHeight / 2;
}
if (inline === "start") {
inlineScroll = targetInline - left - borderLeft;
}
else if (inline === "center") {
inlineScroll = targetInline - (left + width / 2) + scrollbarWidth / 2;
}
else if (inline === "end") {
inlineScroll = targetInline - right + borderRight + scrollbarWidth;
}
else {
// inline === 'nearest' is the default
inlineScroll = alignNearest(left, right, width, borderLeft, borderRight + scrollbarWidth, targetInline, targetInline + targetWidth, targetWidth);
}
var scrollLeft = scroller.scrollLeft, scrollTop = scroller.scrollTop;
// Ensure scroll coordinates are not out of bounds while applying scroll offsets
blockScroll = Math.max(0, Math.min(scrollTop + blockScroll, scroller.scrollHeight - height + scrollbarHeight));
inlineScroll = Math.max(0, Math.min(scrollLeft + inlineScroll, scroller.scrollWidth - width + scrollbarWidth));
}
return {
top: blockScroll,
left: inlineScroll
};
}
/**
* Applies the polyfill
*/
function apply() {
startTracking();
}
/**
* Patches the 'scrollIntoView' method on the Element prototype
*/
function patchElementScrollIntoView() {
Element.prototype.scrollIntoView = function (arg) {
var normalizedOptions = arg == null || arg === true
? {
block: "start",
inline: "nearest"
}
: arg === false
? {
block: "end",
inline: "nearest"
}
: arg;
// Find the nearest ancestor that can be scrolled
var ancestorWithScrollBehaviorResult = findNearestAncestorsWithScrollBehavior(this);
// If there is none, opt-out by calling the original implementation
if (ancestorWithScrollBehaviorResult == null) {
ELEMENT_ORIGINAL_SCROLL_INTO_VIEW.call(this, normalizedOptions);
return;
}
var _a = __read(ancestorWithScrollBehaviorResult, 2), ancestorWithScroll = _a[0], ancestorWithScrollBehavior = _a[1];
var behavior = normalizedOptions.behavior != null ? normalizedOptions.behavior : ancestorWithScrollBehavior;
// If the behavior isn't smooth, simply invoke the original implementation and do no more
if (behavior !== "smooth") {
ELEMENT_ORIGINAL_SCROLL_INTO_VIEW.call(this, normalizedOptions);
return;
}
ancestorWithScroll.scrollTo(__assign({ behavior: behavior }, computeScrollIntoView(this, ancestorWithScroll, normalizedOptions)));
};
}
/**
* This polyfill makes any browser understand the CSS-property 'scroll-behavior'.
* For any element with a 'scroll-behavior' CSS property value of 'smooth', any change
* in its scroll position will render smoothly.
*
* DEPENDENCIES
* - requestAnimationFrame
*
* CAVEATS
* - You cannot set 'scrollLeft' or 'scrollTop'. There is no way to overwrite the property descriptors for those operations. Instead, use 'scroll()', 'scrollTo' or 'scrollBy' which does the exact same thing
* - Element.scrollIntoView() is not polyfilled at the moment.
* - Elements inside ShadowRoots won't be detected at the moment.
*/
if (!SUPPORTS_SCROLL_BEHAVIOR) {
apply();
}
/**
* Applies the polyfill
*/
function patch() {
patchElementScroll();
patchElementScrollBy();
patchElementScrollTo();
patchElementScrollIntoView();
patchWindowScroll();
patchWindowScrollBy();
patchWindowScrollTo();
catchNavigation();
}
if (!SUPPORTS_SCROLL_BEHAVIOR) {
patch();
}
}());
//# sourceMappingURL=index.js.map
{
"name": "scroll-behavior-polyfill",
"version": "1.0.2",
"description": "A polyfill for the 'scroll-behavior' CSS-property",
"scripts": {
"changelog:generate": "conventional-changelog --outfile CHANGELOG.md --release-count 0",
"readme:badges": "node node_modules/@wessberg/ts-config/readme/badge/helper/add-badges.js",
"readme:refresh": "npm run changelog:generate && npm run readme:badges",
"commit:readme": "npm run readme:refresh && git commit -am \"Bumped version\" --no-verify || true",
"clean:dist": "rm -r -f dist",
"clean": "npm run clean:dist",
"rollup": "rollup -c rollup.config.js",
"rollup:watch": "rollup -c rollup.config.js --watch",
"prebuild": "npm run clean",
"build": "npm run rollup",
"prewatch": "npm run clean",
"watch": "npm run rollup:watch",
"tslint": "tslint -c tslint.json -p tsconfig.json",
"validate": "npm run tslint && npm run test",
"test": "NODE_ENV=TEST echo \"skipping tests...\"",
"prepublishOnly": "NODE_ENV=production npm run validate && npm run build",
"precommit": "npm run tslint && exit 0",
"prepush": "npm run validate && exit 0",
"publish:major": "npm version major && npm run commit:readme && git push && npm publish",
"publish:minor": "npm version minor && npm run commit:readme && git push && npm publish",
"publish:patch": "npm version patch && npm run commit:readme && git push && npm publish"
},
"keywords": [],
"devDependencies": {
"@wessberg/environment": "^1.0.1",
"@wessberg/ts-config": "0.0.23",
"conventional-changelog-cli": "^1.3.3",
"husky": "latest",
"rollup": "^0.49.2",
"rollup-plugin-typescript2": "^0.5.2",
"rollup-plugin-uglify": "^2.0.1",
"rollup-watch": "^4.3.1",
"tslint": "^5.7.0",
"typescript": "2.5.2"
},
"dependencies": {},
"main": "./dist/index.js",
"module": "./dist/index.js",
"browser": "./dist/index.js",
"types": "./dist/index.d.ts",
"typings": "./dist/index.d.ts",
"es2015": "./dist/index.js",
"repository": {
"type": "git",
"url": "https://github.com/wessberg/scroll-behavior-polyfill.git"
},
"bugs": {
"url": "https://github.com/wessberg/scroll-behavior-polyfill/issues"
},
"author": {
"name": "Frederik Wessberg",
"email": "frederikwessberg@hotmail.com",
"url": "https://github.com/wessberg"
},
"engines": {
"node": ">=7.4.0"
},
"license": "MIT"
"name": "scroll-behavior-polyfill",
"version": "2.0.1",
"description": "A polyfill for the 'scroll-behavior' CSS-property",
"repository": {
"type": "git",
"url": "https://github.com/wessberg/scroll-behavior-polyfill.git"
},
"bugs": {
"url": "https://github.com/wessberg/scroll-behavior-polyfill/issues"
},
"scripts": {
"generate:readme": "scaffold readme",
"generate:license": "scaffold license",
"generate:contributing": "scaffold contributing",
"generate:coc": "scaffold coc",
"generate:changelog": "standard-changelog --first-release",
"generate:all": "npm run generate:license & npm run generate:contributing & npm run generate:coc & npm run generate:readme & npm run generate:changelog",
"update": "ncu -ua && npm update && npm install",
"lint": "tsc --noEmit && tslint -c tslint.json --project tsconfig.json",
"prerollup": "rm -r -f dist",
"rollup": "rollup -c rollup.config.js",
"prepare": "npm run rollup",
"publish:before": "NODE_ENV=production npm run lint && NODE_ENV=production npm run prepare && npm run generate:all && git add . && git commit -am \"Bumped version\" || true",
"publish:after": "git push && npm publish",
"publish:patch": "npm run publish:before && npm version patch && npm run publish:after",
"publish:minor": "npm run publish:before && npm version minor && npm run publish:after",
"publish:major": "npm run publish:before && npm version major && npm run publish:after"
},
"files": [
"dist/**/*.*"
],
"keywords": [
"scroll-behavior",
"polyfill",
"css",
"smooth",
"scroll behavior"
],
"author": {
"name": "Frederik Wessberg",
"email": "frederikwessberg@hotmail.com",
"url": "https://github.com/wessberg"
},
"license": "MIT",
"devDependencies": {
"@wessberg/rollup-plugin-ts": "1.1.17",
"@wessberg/scaffold": "1.0.5",
"@wessberg/ts-config": "^0.0.34",
"npm-check-updates": "^2.15.0",
"rollup": "^1.0.2",
"rollup-plugin-node-resolve": "^4.0.0",
"tslib": "^1.9.3",
"tslint": "^5.12.0",
"typescript": "^3.2.2",
"standard-changelog": "^2.0.6"
},
"dependencies": {},
"main": "./dist/index.js",
"module": "./dist/index.js",
"browser": "./dist/index.js",
"types": "./dist/index.d.ts",
"typings": "./dist/index.d.ts",
"es2015": "./dist/index.js",
"engines": {
"node": ">=9.0.0"
},
"scaffold": {
"patreonUserId": "11315442",
"contributorMeta": {
"Frederik Wessberg": {
"imageUrl": "https://avatars2.githubusercontent.com/u/20454213?s=460&v=4",
"role": "Maintainer",
"twitterHandle": "FredWessberg",
"isCocEnforcer": true
}
},
"backers": []
}
}

@@ -1,26 +0,32 @@

# `scroll-behavior` polyfill
[![NPM version][npm-version-image]][npm-version-url]
[![License-mit][license-mit-image]][license-mit-url]
<a href="https://npmcharts.com/compare/scroll-behavior-polyfill?minimal=true"><img alt="Downloads per month" src="https://img.shields.io/npm/dm/scroll-behavior-polyfill.svg" height="20"></img></a>
<a href="https://david-dm.org/scroll-behavior-polyfill"><img alt="Dependencies" src="https://img.shields.io/david/scroll-behavior-polyfill.svg" height="20"></img></a>
<a href="https://www.npmjs.com/package/scroll-behavior-polyfill"><img alt="NPM Version" src="https://badge.fury.io/js/scroll-behavior-polyfill.svg" height="20"></img></a>
<a href="https://github.com/wessberg/scroll-behavior-polyfill/graphs/contributors"><img alt="Contributors" src="https://img.shields.io/github/contributors/wessberg%2Fscroll-behavior-polyfill.svg" height="20"></img></a>
<a href="https://opensource.org/licenses/MIT"><img alt="MIT License" src="https://img.shields.io/badge/License-MIT-yellow.svg" height="20"></img></a>
<a href="https://www.patreon.com/bePatron?u=11315442"><img alt="Support on Patreon" src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" height="20"></img></a>
[license-mit-url]: https://opensource.org/licenses/MIT
# `scroll-behavior-polyfill`
[license-mit-image]: https://img.shields.io/badge/License-MIT-yellow.svg
> A polyfill for the [`scroll-behavior`](https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-behavior) CSS-property
[npm-version-url]: https://www.npmjs.com/package/scroll-behavior-polyfill
## Description
[npm-version-image]: https://badge.fury.io/js/scroll-behavior-polyfill.svg
The scroll-behavior CSS property sets the behavior for a scrolling box when scrolling is triggered by the navigation or CSSOM scrolling APIs.
> A polyfill for the new CSS property: `scroll-behavior`.
## Install
## Installation
### NPM
You can `npm install` it like this:
```
npm install scroll-behavior-polyfill
$ npm install scroll-behavior-polyfill
```
## Adding the polyfill
### Yarn
### Importing it
```
$ yarn add scroll-behavior-polyfill
```
## Applying the polyfill
The polyfill will be feature detected and applied if and only if the browser doesn't support the property already.

@@ -30,50 +36,97 @@ To include it, add this somewhere:

```typescript
import "scroll-behavior-polyfill"
import "scroll-behavior-polyfill";
```
### Conditionally importing it
However, it is strongly suggested that you only include the polyfill for browsers that doesn't already support `scroll-behavior`.
One way to do so is with an async import:
Preferably, you should feature detect before including the code since there is no need to include a polyfill that won't ever be applied.
One way to do so is with async imports:
```typescript
if (!"scrollBehavior" in document.documentElement.style) {
await import("scroll-behavior-polyfill");
await import("scroll-behavior-polyfill");
}
```
Alternatively, you can use [Polyfill.app](https://github.com/wessberg/Polyfiller) which uses this polyfill and takes care of only loading the polyfill if needed as well as adding the language features that the polyfill depends on (See [dependencies](#dependencies--browser-support)).
## Usage
You can use scroll-behavior exactly how you expect to:
### Declarative API
### As a CSS-property
You can define the `scroll-behavior` of Elements via one of the following approaches:
```css
#something {
scroll-behavior: smooth
}
```
- A style attribute including a `scroll-behavior` property.
- An element with a `scroll-behavior` attribute.
- Or, an element with a `CSSStyleDeclaration` with a `scrollBehavior` property.
### As an inline-style
This means that either of the following approaches will work:
```html
<!-- Works just fine when given in the 'style' attribute -->
<div style="scroll-behavior: smooth"></div>
<!-- Works just fine when given as an attribute of the name 'scroll-behavior' -->
<div scroll-behavior="smooth"></div>
```
### As an imperative style property
```typescript
// Works jut fine when given as a style property
element.style.scrollBehavior = "smooth";
```
## Dependencies
See [this section](#are-there-any-known-quirks) for information about why `scroll-behavior` values provided in stylesheets won't be discovered by the polyfill.
This polyfill expects `requestAnimationFrame` to be defined.
Please polyfill it!
### Imperative API
## Caveats
You can of course also use the imperative `scroll()`, `scrollTo`, `scrollBy`, and `scrollIntoView` APIs and provide `scroll-behavior` options.
For example:
```typescript
// Works for the window object
window.scroll({
behavior: "smooth",
top: 100,
left: 0
});
// Works for any element (and supports all options)
myElement.scrollIntoView();
myElement.scrollBy({
behavior: "smooth",
top: 50,
left: 0
});
```
## Dependencies & Browser support
This polyfill is distributed in ES3-compatible syntax, but is using some modern APIs and language features which must be available:
- `requestAnimationFrame`
- `Element.prototype.scrollIntoView`
For by far the most browsers, these features will already be natively available.
Generally, I would highly recommend using something like [Polyfill.app](https://github.com/wessberg/Polyfiller) which takes care of this stuff automatically.
## Contributing
Do you want to contribute? Awesome! Please follow [these recommendations](./CONTRIBUTING.md).
## Maintainers
- <a href="https://github.com/wessberg"><img alt="Frederik Wessberg" src="https://avatars2.githubusercontent.com/u/20454213?s=460&v=4" height="11"></img></a> [Frederik Wessberg](https://github.com/wessberg): _Maintainer_
## FAQ
### Are there any known quirks?
- You cannot set `scrollLeft` or `scrollTop`. There is no way to overwrite the property descriptors for those operations. Instead, use `scroll()`, `scrollTo` or `scrollBy` which does the exact same thing.
- `Element.scrollIntoView()` is not polyfilled at the moment.
- Elements inside ShadowRoots won't be detected at the moment. It probably will be soon.
- `scroll-behavior` properties declared only in stylesheets won't be discovered. This is because [polyfilling CSS is hard and really bad for performance](https://philipwalton.com/articles/the-dark-side-of-polyfilling-css/).
## Backers 🏅
[Become a backer](https://www.patreon.com/bePatron?u=11315442) and get your name, logo, and link to your site listed here.
## License 📄
MIT © [Frederik Wessberg](https://github.com/wessberg)

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc