forked from Gitlink/forgeplus
40474 lines
1.1 MiB
40474 lines
1.1 MiB
/*
|
|
Unobtrusive JavaScript
|
|
https://github.com/rails/rails/blob/master/actionview/app/assets/javascripts
|
|
Released under the MIT license
|
|
*/
|
|
;
|
|
|
|
(function() {
|
|
var context = this;
|
|
|
|
(function() {
|
|
(function() {
|
|
this.Rails = {
|
|
linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote]:not([disabled]), a[data-disable-with], a[data-disable]',
|
|
buttonClickSelector: {
|
|
selector: 'button[data-remote]:not([form]), button[data-confirm]:not([form])',
|
|
exclude: 'form button'
|
|
},
|
|
inputChangeSelector: 'select[data-remote], input[data-remote], textarea[data-remote]',
|
|
formSubmitSelector: 'form',
|
|
formInputClickSelector: 'form input[type=submit], form input[type=image], form button[type=submit], form button:not([type]), input[type=submit][form], input[type=image][form], button[type=submit][form], button[form]:not([type])',
|
|
formDisableSelector: 'input[data-disable-with]:enabled, button[data-disable-with]:enabled, textarea[data-disable-with]:enabled, input[data-disable]:enabled, button[data-disable]:enabled, textarea[data-disable]:enabled',
|
|
formEnableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled, input[data-disable]:disabled, button[data-disable]:disabled, textarea[data-disable]:disabled',
|
|
fileInputSelector: 'input[name][type=file]:not([disabled])',
|
|
linkDisableSelector: 'a[data-disable-with], a[data-disable]',
|
|
buttonDisableSelector: 'button[data-remote][data-disable-with], button[data-remote][data-disable]'
|
|
};
|
|
|
|
}).call(this);
|
|
}).call(context);
|
|
|
|
var Rails = context.Rails;
|
|
|
|
(function() {
|
|
(function() {
|
|
var nonce;
|
|
|
|
nonce = null;
|
|
|
|
Rails.loadCSPNonce = function() {
|
|
var ref;
|
|
return nonce = (ref = document.querySelector("meta[name=csp-nonce]")) != null ? ref.content : void 0;
|
|
};
|
|
|
|
Rails.cspNonce = function() {
|
|
return nonce != null ? nonce : Rails.loadCSPNonce();
|
|
};
|
|
|
|
}).call(this);
|
|
(function() {
|
|
var expando, m;
|
|
|
|
m = Element.prototype.matches || Element.prototype.matchesSelector || Element.prototype.mozMatchesSelector || Element.prototype.msMatchesSelector || Element.prototype.oMatchesSelector || Element.prototype.webkitMatchesSelector;
|
|
|
|
Rails.matches = function(element, selector) {
|
|
if (selector.exclude != null) {
|
|
return m.call(element, selector.selector) && !m.call(element, selector.exclude);
|
|
} else {
|
|
return m.call(element, selector);
|
|
}
|
|
};
|
|
|
|
expando = '_ujsData';
|
|
|
|
Rails.getData = function(element, key) {
|
|
var ref;
|
|
return (ref = element[expando]) != null ? ref[key] : void 0;
|
|
};
|
|
|
|
Rails.setData = function(element, key, value) {
|
|
if (element[expando] == null) {
|
|
element[expando] = {};
|
|
}
|
|
return element[expando][key] = value;
|
|
};
|
|
|
|
Rails.$ = function(selector) {
|
|
return Array.prototype.slice.call(document.querySelectorAll(selector));
|
|
};
|
|
|
|
}).call(this);
|
|
(function() {
|
|
var $, csrfParam, csrfToken;
|
|
|
|
$ = Rails.$;
|
|
|
|
csrfToken = Rails.csrfToken = function() {
|
|
var meta;
|
|
meta = document.querySelector('meta[name=csrf-token]');
|
|
return meta && meta.content;
|
|
};
|
|
|
|
csrfParam = Rails.csrfParam = function() {
|
|
var meta;
|
|
meta = document.querySelector('meta[name=csrf-param]');
|
|
return meta && meta.content;
|
|
};
|
|
|
|
Rails.CSRFProtection = function(xhr) {
|
|
var token;
|
|
token = csrfToken();
|
|
if (token != null) {
|
|
return xhr.setRequestHeader('X-CSRF-Token', token);
|
|
}
|
|
};
|
|
|
|
Rails.refreshCSRFTokens = function() {
|
|
var param, token;
|
|
token = csrfToken();
|
|
param = csrfParam();
|
|
if ((token != null) && (param != null)) {
|
|
return $('form input[name="' + param + '"]').forEach(function(input) {
|
|
return input.value = token;
|
|
});
|
|
}
|
|
};
|
|
|
|
}).call(this);
|
|
(function() {
|
|
var CustomEvent, fire, matches, preventDefault;
|
|
|
|
matches = Rails.matches;
|
|
|
|
CustomEvent = window.CustomEvent;
|
|
|
|
if (typeof CustomEvent !== 'function') {
|
|
CustomEvent = function(event, params) {
|
|
var evt;
|
|
evt = document.createEvent('CustomEvent');
|
|
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
|
|
return evt;
|
|
};
|
|
CustomEvent.prototype = window.Event.prototype;
|
|
preventDefault = CustomEvent.prototype.preventDefault;
|
|
CustomEvent.prototype.preventDefault = function() {
|
|
var result;
|
|
result = preventDefault.call(this);
|
|
if (this.cancelable && !this.defaultPrevented) {
|
|
Object.defineProperty(this, 'defaultPrevented', {
|
|
get: function() {
|
|
return true;
|
|
}
|
|
});
|
|
}
|
|
return result;
|
|
};
|
|
}
|
|
|
|
fire = Rails.fire = function(obj, name, data) {
|
|
var event;
|
|
event = new CustomEvent(name, {
|
|
bubbles: true,
|
|
cancelable: true,
|
|
detail: data
|
|
});
|
|
obj.dispatchEvent(event);
|
|
return !event.defaultPrevented;
|
|
};
|
|
|
|
Rails.stopEverything = function(e) {
|
|
fire(e.target, 'ujs:everythingStopped');
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
return e.stopImmediatePropagation();
|
|
};
|
|
|
|
Rails.delegate = function(element, selector, eventType, handler) {
|
|
return element.addEventListener(eventType, function(e) {
|
|
var target;
|
|
target = e.target;
|
|
while (!(!(target instanceof Element) || matches(target, selector))) {
|
|
target = target.parentNode;
|
|
}
|
|
if (target instanceof Element && handler.call(target, e) === false) {
|
|
e.preventDefault();
|
|
return e.stopPropagation();
|
|
}
|
|
});
|
|
};
|
|
|
|
}).call(this);
|
|
(function() {
|
|
var AcceptHeaders, CSRFProtection, createXHR, cspNonce, fire, prepareOptions, processResponse;
|
|
|
|
cspNonce = Rails.cspNonce, CSRFProtection = Rails.CSRFProtection, fire = Rails.fire;
|
|
|
|
AcceptHeaders = {
|
|
'*': '*/*',
|
|
text: 'text/plain',
|
|
html: 'text/html',
|
|
xml: 'application/xml, text/xml',
|
|
json: 'application/json, text/javascript',
|
|
script: 'text/javascript, application/javascript, application/ecmascript, application/x-ecmascript'
|
|
};
|
|
|
|
Rails.ajax = function(options) {
|
|
var xhr;
|
|
options = prepareOptions(options);
|
|
xhr = createXHR(options, function() {
|
|
var ref, response;
|
|
response = processResponse((ref = xhr.response) != null ? ref : xhr.responseText, xhr.getResponseHeader('Content-Type'));
|
|
if (Math.floor(xhr.status / 100) === 2) {
|
|
if (typeof options.success === "function") {
|
|
options.success(response, xhr.statusText, xhr);
|
|
}
|
|
} else {
|
|
if (typeof options.error === "function") {
|
|
options.error(response, xhr.statusText, xhr);
|
|
}
|
|
}
|
|
return typeof options.complete === "function" ? options.complete(xhr, xhr.statusText) : void 0;
|
|
});
|
|
if ((options.beforeSend != null) && !options.beforeSend(xhr, options)) {
|
|
return false;
|
|
}
|
|
if (xhr.readyState === XMLHttpRequest.OPENED) {
|
|
return xhr.send(options.data);
|
|
}
|
|
};
|
|
|
|
prepareOptions = function(options) {
|
|
options.url = options.url || location.href;
|
|
options.type = options.type.toUpperCase();
|
|
if (options.type === 'GET' && options.data) {
|
|
if (options.url.indexOf('?') < 0) {
|
|
options.url += '?' + options.data;
|
|
} else {
|
|
options.url += '&' + options.data;
|
|
}
|
|
}
|
|
if (AcceptHeaders[options.dataType] == null) {
|
|
options.dataType = '*';
|
|
}
|
|
options.accept = AcceptHeaders[options.dataType];
|
|
if (options.dataType !== '*') {
|
|
options.accept += ', */*; q=0.01';
|
|
}
|
|
return options;
|
|
};
|
|
|
|
createXHR = function(options, done) {
|
|
var xhr;
|
|
xhr = new XMLHttpRequest();
|
|
xhr.open(options.type, options.url, true);
|
|
xhr.setRequestHeader('Accept', options.accept);
|
|
if (typeof options.data === 'string') {
|
|
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
|
|
}
|
|
if (!options.crossDomain) {
|
|
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
|
|
}
|
|
CSRFProtection(xhr);
|
|
xhr.withCredentials = !!options.withCredentials;
|
|
xhr.onreadystatechange = function() {
|
|
if (xhr.readyState === XMLHttpRequest.DONE) {
|
|
return done(xhr);
|
|
}
|
|
};
|
|
return xhr;
|
|
};
|
|
|
|
processResponse = function(response, type) {
|
|
var parser, script;
|
|
if (typeof response === 'string' && typeof type === 'string') {
|
|
if (type.match(/\bjson\b/)) {
|
|
try {
|
|
response = JSON.parse(response);
|
|
} catch (error) {}
|
|
} else if (type.match(/\b(?:java|ecma)script\b/)) {
|
|
script = document.createElement('script');
|
|
script.setAttribute('nonce', cspNonce());
|
|
script.text = response;
|
|
document.head.appendChild(script).parentNode.removeChild(script);
|
|
} else if (type.match(/\b(xml|html|svg)\b/)) {
|
|
parser = new DOMParser();
|
|
type = type.replace(/;.+/, '');
|
|
try {
|
|
response = parser.parseFromString(response, type);
|
|
} catch (error) {}
|
|
}
|
|
}
|
|
return response;
|
|
};
|
|
|
|
Rails.href = function(element) {
|
|
return element.href;
|
|
};
|
|
|
|
Rails.isCrossDomain = function(url) {
|
|
var e, originAnchor, urlAnchor;
|
|
originAnchor = document.createElement('a');
|
|
originAnchor.href = location.href;
|
|
urlAnchor = document.createElement('a');
|
|
try {
|
|
urlAnchor.href = url;
|
|
return !(((!urlAnchor.protocol || urlAnchor.protocol === ':') && !urlAnchor.host) || (originAnchor.protocol + '//' + originAnchor.host === urlAnchor.protocol + '//' + urlAnchor.host));
|
|
} catch (error) {
|
|
e = error;
|
|
return true;
|
|
}
|
|
};
|
|
|
|
}).call(this);
|
|
(function() {
|
|
var matches, toArray;
|
|
|
|
matches = Rails.matches;
|
|
|
|
toArray = function(e) {
|
|
return Array.prototype.slice.call(e);
|
|
};
|
|
|
|
Rails.serializeElement = function(element, additionalParam) {
|
|
var inputs, params;
|
|
inputs = [element];
|
|
if (matches(element, 'form')) {
|
|
inputs = toArray(element.elements);
|
|
}
|
|
params = [];
|
|
inputs.forEach(function(input) {
|
|
if (!input.name || input.disabled) {
|
|
return;
|
|
}
|
|
if (matches(input, 'select')) {
|
|
return toArray(input.options).forEach(function(option) {
|
|
if (option.selected) {
|
|
return params.push({
|
|
name: input.name,
|
|
value: option.value
|
|
});
|
|
}
|
|
});
|
|
} else if (input.checked || ['radio', 'checkbox', 'submit'].indexOf(input.type) === -1) {
|
|
return params.push({
|
|
name: input.name,
|
|
value: input.value
|
|
});
|
|
}
|
|
});
|
|
if (additionalParam) {
|
|
params.push(additionalParam);
|
|
}
|
|
return params.map(function(param) {
|
|
if (param.name != null) {
|
|
return (encodeURIComponent(param.name)) + "=" + (encodeURIComponent(param.value));
|
|
} else {
|
|
return param;
|
|
}
|
|
}).join('&');
|
|
};
|
|
|
|
Rails.formElements = function(form, selector) {
|
|
if (matches(form, 'form')) {
|
|
return toArray(form.elements).filter(function(el) {
|
|
return matches(el, selector);
|
|
});
|
|
} else {
|
|
return toArray(form.querySelectorAll(selector));
|
|
}
|
|
};
|
|
|
|
}).call(this);
|
|
(function() {
|
|
var allowAction, fire, stopEverything;
|
|
|
|
fire = Rails.fire, stopEverything = Rails.stopEverything;
|
|
|
|
Rails.handleConfirm = function(e) {
|
|
if (!allowAction(this)) {
|
|
return stopEverything(e);
|
|
}
|
|
};
|
|
|
|
allowAction = function(element) {
|
|
var answer, callback, message;
|
|
message = element.getAttribute('data-confirm');
|
|
if (!message) {
|
|
return true;
|
|
}
|
|
answer = false;
|
|
if (fire(element, 'confirm')) {
|
|
try {
|
|
answer = confirm(message);
|
|
} catch (error) {}
|
|
callback = fire(element, 'confirm:complete', [answer]);
|
|
}
|
|
return answer && callback;
|
|
};
|
|
|
|
}).call(this);
|
|
(function() {
|
|
var disableFormElement, disableFormElements, disableLinkElement, enableFormElement, enableFormElements, enableLinkElement, formElements, getData, matches, setData, stopEverything;
|
|
|
|
matches = Rails.matches, getData = Rails.getData, setData = Rails.setData, stopEverything = Rails.stopEverything, formElements = Rails.formElements;
|
|
|
|
Rails.handleDisabledElement = function(e) {
|
|
var element;
|
|
element = this;
|
|
if (element.disabled) {
|
|
return stopEverything(e);
|
|
}
|
|
};
|
|
|
|
Rails.enableElement = function(e) {
|
|
var element;
|
|
element = e instanceof Event ? e.target : e;
|
|
if (matches(element, Rails.linkDisableSelector)) {
|
|
return enableLinkElement(element);
|
|
} else if (matches(element, Rails.buttonDisableSelector) || matches(element, Rails.formEnableSelector)) {
|
|
return enableFormElement(element);
|
|
} else if (matches(element, Rails.formSubmitSelector)) {
|
|
return enableFormElements(element);
|
|
}
|
|
};
|
|
|
|
Rails.disableElement = function(e) {
|
|
var element;
|
|
element = e instanceof Event ? e.target : e;
|
|
if (matches(element, Rails.linkDisableSelector)) {
|
|
return disableLinkElement(element);
|
|
} else if (matches(element, Rails.buttonDisableSelector) || matches(element, Rails.formDisableSelector)) {
|
|
return disableFormElement(element);
|
|
} else if (matches(element, Rails.formSubmitSelector)) {
|
|
return disableFormElements(element);
|
|
}
|
|
};
|
|
|
|
disableLinkElement = function(element) {
|
|
var replacement;
|
|
replacement = element.getAttribute('data-disable-with');
|
|
if (replacement != null) {
|
|
setData(element, 'ujs:enable-with', element.innerHTML);
|
|
element.innerHTML = replacement;
|
|
}
|
|
element.addEventListener('click', stopEverything);
|
|
return setData(element, 'ujs:disabled', true);
|
|
};
|
|
|
|
enableLinkElement = function(element) {
|
|
var originalText;
|
|
originalText = getData(element, 'ujs:enable-with');
|
|
if (originalText != null) {
|
|
element.innerHTML = originalText;
|
|
setData(element, 'ujs:enable-with', null);
|
|
}
|
|
element.removeEventListener('click', stopEverything);
|
|
return setData(element, 'ujs:disabled', null);
|
|
};
|
|
|
|
disableFormElements = function(form) {
|
|
return formElements(form, Rails.formDisableSelector).forEach(disableFormElement);
|
|
};
|
|
|
|
disableFormElement = function(element) {
|
|
var replacement;
|
|
replacement = element.getAttribute('data-disable-with');
|
|
if (replacement != null) {
|
|
if (matches(element, 'button')) {
|
|
setData(element, 'ujs:enable-with', element.innerHTML);
|
|
element.innerHTML = replacement;
|
|
} else {
|
|
setData(element, 'ujs:enable-with', element.value);
|
|
element.value = replacement;
|
|
}
|
|
}
|
|
element.disabled = true;
|
|
return setData(element, 'ujs:disabled', true);
|
|
};
|
|
|
|
enableFormElements = function(form) {
|
|
return formElements(form, Rails.formEnableSelector).forEach(enableFormElement);
|
|
};
|
|
|
|
enableFormElement = function(element) {
|
|
var originalText;
|
|
originalText = getData(element, 'ujs:enable-with');
|
|
if (originalText != null) {
|
|
if (matches(element, 'button')) {
|
|
element.innerHTML = originalText;
|
|
} else {
|
|
element.value = originalText;
|
|
}
|
|
setData(element, 'ujs:enable-with', null);
|
|
}
|
|
element.disabled = false;
|
|
return setData(element, 'ujs:disabled', null);
|
|
};
|
|
|
|
}).call(this);
|
|
(function() {
|
|
var stopEverything;
|
|
|
|
stopEverything = Rails.stopEverything;
|
|
|
|
Rails.handleMethod = function(e) {
|
|
var csrfParam, csrfToken, form, formContent, href, link, method;
|
|
link = this;
|
|
method = link.getAttribute('data-method');
|
|
if (!method) {
|
|
return;
|
|
}
|
|
href = Rails.href(link);
|
|
csrfToken = Rails.csrfToken();
|
|
csrfParam = Rails.csrfParam();
|
|
form = document.createElement('form');
|
|
formContent = "<input name='_method' value='" + method + "' type='hidden' />";
|
|
if ((csrfParam != null) && (csrfToken != null) && !Rails.isCrossDomain(href)) {
|
|
formContent += "<input name='" + csrfParam + "' value='" + csrfToken + "' type='hidden' />";
|
|
}
|
|
formContent += '<input type="submit" />';
|
|
form.method = 'post';
|
|
form.action = href;
|
|
form.target = link.target;
|
|
form.innerHTML = formContent;
|
|
form.style.display = 'none';
|
|
document.body.appendChild(form);
|
|
form.querySelector('[type="submit"]').click();
|
|
return stopEverything(e);
|
|
};
|
|
|
|
}).call(this);
|
|
(function() {
|
|
var ajax, fire, getData, isCrossDomain, isRemote, matches, serializeElement, setData, stopEverything,
|
|
slice = [].slice;
|
|
|
|
matches = Rails.matches, getData = Rails.getData, setData = Rails.setData, fire = Rails.fire, stopEverything = Rails.stopEverything, ajax = Rails.ajax, isCrossDomain = Rails.isCrossDomain, serializeElement = Rails.serializeElement;
|
|
|
|
isRemote = function(element) {
|
|
var value;
|
|
value = element.getAttribute('data-remote');
|
|
return (value != null) && value !== 'false';
|
|
};
|
|
|
|
Rails.handleRemote = function(e) {
|
|
var button, data, dataType, element, method, url, withCredentials;
|
|
element = this;
|
|
if (!isRemote(element)) {
|
|
return true;
|
|
}
|
|
if (!fire(element, 'ajax:before')) {
|
|
fire(element, 'ajax:stopped');
|
|
return false;
|
|
}
|
|
withCredentials = element.getAttribute('data-with-credentials');
|
|
dataType = element.getAttribute('data-type') || 'script';
|
|
if (matches(element, Rails.formSubmitSelector)) {
|
|
button = getData(element, 'ujs:submit-button');
|
|
method = getData(element, 'ujs:submit-button-formmethod') || element.method;
|
|
url = getData(element, 'ujs:submit-button-formaction') || element.getAttribute('action') || location.href;
|
|
if (method.toUpperCase() === 'GET') {
|
|
url = url.replace(/\?.*$/, '');
|
|
}
|
|
if (element.enctype === 'multipart/form-data') {
|
|
data = new FormData(element);
|
|
if (button != null) {
|
|
data.append(button.name, button.value);
|
|
}
|
|
} else {
|
|
data = serializeElement(element, button);
|
|
}
|
|
setData(element, 'ujs:submit-button', null);
|
|
setData(element, 'ujs:submit-button-formmethod', null);
|
|
setData(element, 'ujs:submit-button-formaction', null);
|
|
} else if (matches(element, Rails.buttonClickSelector) || matches(element, Rails.inputChangeSelector)) {
|
|
method = element.getAttribute('data-method');
|
|
url = element.getAttribute('data-url');
|
|
data = serializeElement(element, element.getAttribute('data-params'));
|
|
} else {
|
|
method = element.getAttribute('data-method');
|
|
url = Rails.href(element);
|
|
data = element.getAttribute('data-params');
|
|
}
|
|
ajax({
|
|
type: method || 'GET',
|
|
url: url,
|
|
data: data,
|
|
dataType: dataType,
|
|
beforeSend: function(xhr, options) {
|
|
if (fire(element, 'ajax:beforeSend', [xhr, options])) {
|
|
return fire(element, 'ajax:send', [xhr]);
|
|
} else {
|
|
fire(element, 'ajax:stopped');
|
|
return false;
|
|
}
|
|
},
|
|
success: function() {
|
|
var args;
|
|
args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
|
|
return fire(element, 'ajax:success', args);
|
|
},
|
|
error: function() {
|
|
var args;
|
|
args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
|
|
return fire(element, 'ajax:error', args);
|
|
},
|
|
complete: function() {
|
|
var args;
|
|
args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
|
|
return fire(element, 'ajax:complete', args);
|
|
},
|
|
crossDomain: isCrossDomain(url),
|
|
withCredentials: (withCredentials != null) && withCredentials !== 'false'
|
|
});
|
|
return stopEverything(e);
|
|
};
|
|
|
|
Rails.formSubmitButtonClick = function(e) {
|
|
var button, form;
|
|
button = this;
|
|
form = button.form;
|
|
if (!form) {
|
|
return;
|
|
}
|
|
if (button.name) {
|
|
setData(form, 'ujs:submit-button', {
|
|
name: button.name,
|
|
value: button.value
|
|
});
|
|
}
|
|
setData(form, 'ujs:formnovalidate-button', button.formNoValidate);
|
|
setData(form, 'ujs:submit-button-formaction', button.getAttribute('formaction'));
|
|
return setData(form, 'ujs:submit-button-formmethod', button.getAttribute('formmethod'));
|
|
};
|
|
|
|
Rails.preventInsignificantClick = function(e) {
|
|
var data, insignificantMetaClick, link, metaClick, method, nonPrimaryMouseClick;
|
|
link = this;
|
|
method = (link.getAttribute('data-method') || 'GET').toUpperCase();
|
|
data = link.getAttribute('data-params');
|
|
metaClick = e.metaKey || e.ctrlKey;
|
|
insignificantMetaClick = metaClick && method === 'GET' && !data;
|
|
nonPrimaryMouseClick = (e.button != null) && e.button !== 0;
|
|
if (nonPrimaryMouseClick || insignificantMetaClick) {
|
|
return e.stopImmediatePropagation();
|
|
}
|
|
};
|
|
|
|
}).call(this);
|
|
(function() {
|
|
var $, CSRFProtection, delegate, disableElement, enableElement, fire, formSubmitButtonClick, getData, handleConfirm, handleDisabledElement, handleMethod, handleRemote, loadCSPNonce, preventInsignificantClick, refreshCSRFTokens;
|
|
|
|
fire = Rails.fire, delegate = Rails.delegate, getData = Rails.getData, $ = Rails.$, refreshCSRFTokens = Rails.refreshCSRFTokens, CSRFProtection = Rails.CSRFProtection, loadCSPNonce = Rails.loadCSPNonce, enableElement = Rails.enableElement, disableElement = Rails.disableElement, handleDisabledElement = Rails.handleDisabledElement, handleConfirm = Rails.handleConfirm, preventInsignificantClick = Rails.preventInsignificantClick, handleRemote = Rails.handleRemote, formSubmitButtonClick = Rails.formSubmitButtonClick, handleMethod = Rails.handleMethod;
|
|
|
|
if ((typeof jQuery !== "undefined" && jQuery !== null) && (jQuery.ajax != null)) {
|
|
if (jQuery.rails) {
|
|
throw new Error('If you load both jquery_ujs and rails-ujs, use rails-ujs only.');
|
|
}
|
|
jQuery.rails = Rails;
|
|
jQuery.ajaxPrefilter(function(options, originalOptions, xhr) {
|
|
if (!options.crossDomain) {
|
|
return CSRFProtection(xhr);
|
|
}
|
|
});
|
|
}
|
|
|
|
Rails.start = function() {
|
|
if (window._rails_loaded) {
|
|
throw new Error('rails-ujs has already been loaded!');
|
|
}
|
|
window.addEventListener('pageshow', function() {
|
|
$(Rails.formEnableSelector).forEach(function(el) {
|
|
if (getData(el, 'ujs:disabled')) {
|
|
return enableElement(el);
|
|
}
|
|
});
|
|
return $(Rails.linkDisableSelector).forEach(function(el) {
|
|
if (getData(el, 'ujs:disabled')) {
|
|
return enableElement(el);
|
|
}
|
|
});
|
|
});
|
|
delegate(document, Rails.linkDisableSelector, 'ajax:complete', enableElement);
|
|
delegate(document, Rails.linkDisableSelector, 'ajax:stopped', enableElement);
|
|
delegate(document, Rails.buttonDisableSelector, 'ajax:complete', enableElement);
|
|
delegate(document, Rails.buttonDisableSelector, 'ajax:stopped', enableElement);
|
|
delegate(document, Rails.linkClickSelector, 'click', preventInsignificantClick);
|
|
delegate(document, Rails.linkClickSelector, 'click', handleDisabledElement);
|
|
delegate(document, Rails.linkClickSelector, 'click', handleConfirm);
|
|
delegate(document, Rails.linkClickSelector, 'click', disableElement);
|
|
delegate(document, Rails.linkClickSelector, 'click', handleRemote);
|
|
delegate(document, Rails.linkClickSelector, 'click', handleMethod);
|
|
delegate(document, Rails.buttonClickSelector, 'click', preventInsignificantClick);
|
|
delegate(document, Rails.buttonClickSelector, 'click', handleDisabledElement);
|
|
delegate(document, Rails.buttonClickSelector, 'click', handleConfirm);
|
|
delegate(document, Rails.buttonClickSelector, 'click', disableElement);
|
|
delegate(document, Rails.buttonClickSelector, 'click', handleRemote);
|
|
delegate(document, Rails.inputChangeSelector, 'change', handleDisabledElement);
|
|
delegate(document, Rails.inputChangeSelector, 'change', handleConfirm);
|
|
delegate(document, Rails.inputChangeSelector, 'change', handleRemote);
|
|
delegate(document, Rails.formSubmitSelector, 'submit', handleDisabledElement);
|
|
delegate(document, Rails.formSubmitSelector, 'submit', handleConfirm);
|
|
delegate(document, Rails.formSubmitSelector, 'submit', handleRemote);
|
|
delegate(document, Rails.formSubmitSelector, 'submit', function(e) {
|
|
return setTimeout((function() {
|
|
return disableElement(e);
|
|
}), 13);
|
|
});
|
|
delegate(document, Rails.formSubmitSelector, 'ajax:send', disableElement);
|
|
delegate(document, Rails.formSubmitSelector, 'ajax:complete', enableElement);
|
|
delegate(document, Rails.formInputClickSelector, 'click', preventInsignificantClick);
|
|
delegate(document, Rails.formInputClickSelector, 'click', handleDisabledElement);
|
|
delegate(document, Rails.formInputClickSelector, 'click', handleConfirm);
|
|
delegate(document, Rails.formInputClickSelector, 'click', formSubmitButtonClick);
|
|
document.addEventListener('DOMContentLoaded', refreshCSRFTokens);
|
|
document.addEventListener('DOMContentLoaded', loadCSPNonce);
|
|
return window._rails_loaded = true;
|
|
};
|
|
|
|
if (window.Rails === Rails && fire(document, 'rails:attachBindings')) {
|
|
Rails.start();
|
|
}
|
|
|
|
}).call(this);
|
|
}).call(this);
|
|
|
|
if (typeof module === "object" && module.exports) {
|
|
module.exports = Rails;
|
|
} else if (typeof define === "function" && define.amd) {
|
|
define(Rails);
|
|
}
|
|
}).call(this);
|
|
(function(global, factory) {
|
|
typeof exports === "object" && typeof module !== "undefined" ? factory(exports) : typeof define === "function" && define.amd ? define([ "exports" ], factory) : factory(global.ActiveStorage = {});
|
|
})(this, function(exports) {
|
|
"use strict";
|
|
function createCommonjsModule(fn, module) {
|
|
return module = {
|
|
exports: {}
|
|
}, fn(module, module.exports), module.exports;
|
|
}
|
|
var sparkMd5 = createCommonjsModule(function(module, exports) {
|
|
(function(factory) {
|
|
{
|
|
module.exports = factory();
|
|
}
|
|
})(function(undefined) {
|
|
var hex_chr = [ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" ];
|
|
function md5cycle(x, k) {
|
|
var a = x[0], b = x[1], c = x[2], d = x[3];
|
|
a += (b & c | ~b & d) + k[0] - 680876936 | 0;
|
|
a = (a << 7 | a >>> 25) + b | 0;
|
|
d += (a & b | ~a & c) + k[1] - 389564586 | 0;
|
|
d = (d << 12 | d >>> 20) + a | 0;
|
|
c += (d & a | ~d & b) + k[2] + 606105819 | 0;
|
|
c = (c << 17 | c >>> 15) + d | 0;
|
|
b += (c & d | ~c & a) + k[3] - 1044525330 | 0;
|
|
b = (b << 22 | b >>> 10) + c | 0;
|
|
a += (b & c | ~b & d) + k[4] - 176418897 | 0;
|
|
a = (a << 7 | a >>> 25) + b | 0;
|
|
d += (a & b | ~a & c) + k[5] + 1200080426 | 0;
|
|
d = (d << 12 | d >>> 20) + a | 0;
|
|
c += (d & a | ~d & b) + k[6] - 1473231341 | 0;
|
|
c = (c << 17 | c >>> 15) + d | 0;
|
|
b += (c & d | ~c & a) + k[7] - 45705983 | 0;
|
|
b = (b << 22 | b >>> 10) + c | 0;
|
|
a += (b & c | ~b & d) + k[8] + 1770035416 | 0;
|
|
a = (a << 7 | a >>> 25) + b | 0;
|
|
d += (a & b | ~a & c) + k[9] - 1958414417 | 0;
|
|
d = (d << 12 | d >>> 20) + a | 0;
|
|
c += (d & a | ~d & b) + k[10] - 42063 | 0;
|
|
c = (c << 17 | c >>> 15) + d | 0;
|
|
b += (c & d | ~c & a) + k[11] - 1990404162 | 0;
|
|
b = (b << 22 | b >>> 10) + c | 0;
|
|
a += (b & c | ~b & d) + k[12] + 1804603682 | 0;
|
|
a = (a << 7 | a >>> 25) + b | 0;
|
|
d += (a & b | ~a & c) + k[13] - 40341101 | 0;
|
|
d = (d << 12 | d >>> 20) + a | 0;
|
|
c += (d & a | ~d & b) + k[14] - 1502002290 | 0;
|
|
c = (c << 17 | c >>> 15) + d | 0;
|
|
b += (c & d | ~c & a) + k[15] + 1236535329 | 0;
|
|
b = (b << 22 | b >>> 10) + c | 0;
|
|
a += (b & d | c & ~d) + k[1] - 165796510 | 0;
|
|
a = (a << 5 | a >>> 27) + b | 0;
|
|
d += (a & c | b & ~c) + k[6] - 1069501632 | 0;
|
|
d = (d << 9 | d >>> 23) + a | 0;
|
|
c += (d & b | a & ~b) + k[11] + 643717713 | 0;
|
|
c = (c << 14 | c >>> 18) + d | 0;
|
|
b += (c & a | d & ~a) + k[0] - 373897302 | 0;
|
|
b = (b << 20 | b >>> 12) + c | 0;
|
|
a += (b & d | c & ~d) + k[5] - 701558691 | 0;
|
|
a = (a << 5 | a >>> 27) + b | 0;
|
|
d += (a & c | b & ~c) + k[10] + 38016083 | 0;
|
|
d = (d << 9 | d >>> 23) + a | 0;
|
|
c += (d & b | a & ~b) + k[15] - 660478335 | 0;
|
|
c = (c << 14 | c >>> 18) + d | 0;
|
|
b += (c & a | d & ~a) + k[4] - 405537848 | 0;
|
|
b = (b << 20 | b >>> 12) + c | 0;
|
|
a += (b & d | c & ~d) + k[9] + 568446438 | 0;
|
|
a = (a << 5 | a >>> 27) + b | 0;
|
|
d += (a & c | b & ~c) + k[14] - 1019803690 | 0;
|
|
d = (d << 9 | d >>> 23) + a | 0;
|
|
c += (d & b | a & ~b) + k[3] - 187363961 | 0;
|
|
c = (c << 14 | c >>> 18) + d | 0;
|
|
b += (c & a | d & ~a) + k[8] + 1163531501 | 0;
|
|
b = (b << 20 | b >>> 12) + c | 0;
|
|
a += (b & d | c & ~d) + k[13] - 1444681467 | 0;
|
|
a = (a << 5 | a >>> 27) + b | 0;
|
|
d += (a & c | b & ~c) + k[2] - 51403784 | 0;
|
|
d = (d << 9 | d >>> 23) + a | 0;
|
|
c += (d & b | a & ~b) + k[7] + 1735328473 | 0;
|
|
c = (c << 14 | c >>> 18) + d | 0;
|
|
b += (c & a | d & ~a) + k[12] - 1926607734 | 0;
|
|
b = (b << 20 | b >>> 12) + c | 0;
|
|
a += (b ^ c ^ d) + k[5] - 378558 | 0;
|
|
a = (a << 4 | a >>> 28) + b | 0;
|
|
d += (a ^ b ^ c) + k[8] - 2022574463 | 0;
|
|
d = (d << 11 | d >>> 21) + a | 0;
|
|
c += (d ^ a ^ b) + k[11] + 1839030562 | 0;
|
|
c = (c << 16 | c >>> 16) + d | 0;
|
|
b += (c ^ d ^ a) + k[14] - 35309556 | 0;
|
|
b = (b << 23 | b >>> 9) + c | 0;
|
|
a += (b ^ c ^ d) + k[1] - 1530992060 | 0;
|
|
a = (a << 4 | a >>> 28) + b | 0;
|
|
d += (a ^ b ^ c) + k[4] + 1272893353 | 0;
|
|
d = (d << 11 | d >>> 21) + a | 0;
|
|
c += (d ^ a ^ b) + k[7] - 155497632 | 0;
|
|
c = (c << 16 | c >>> 16) + d | 0;
|
|
b += (c ^ d ^ a) + k[10] - 1094730640 | 0;
|
|
b = (b << 23 | b >>> 9) + c | 0;
|
|
a += (b ^ c ^ d) + k[13] + 681279174 | 0;
|
|
a = (a << 4 | a >>> 28) + b | 0;
|
|
d += (a ^ b ^ c) + k[0] - 358537222 | 0;
|
|
d = (d << 11 | d >>> 21) + a | 0;
|
|
c += (d ^ a ^ b) + k[3] - 722521979 | 0;
|
|
c = (c << 16 | c >>> 16) + d | 0;
|
|
b += (c ^ d ^ a) + k[6] + 76029189 | 0;
|
|
b = (b << 23 | b >>> 9) + c | 0;
|
|
a += (b ^ c ^ d) + k[9] - 640364487 | 0;
|
|
a = (a << 4 | a >>> 28) + b | 0;
|
|
d += (a ^ b ^ c) + k[12] - 421815835 | 0;
|
|
d = (d << 11 | d >>> 21) + a | 0;
|
|
c += (d ^ a ^ b) + k[15] + 530742520 | 0;
|
|
c = (c << 16 | c >>> 16) + d | 0;
|
|
b += (c ^ d ^ a) + k[2] - 995338651 | 0;
|
|
b = (b << 23 | b >>> 9) + c | 0;
|
|
a += (c ^ (b | ~d)) + k[0] - 198630844 | 0;
|
|
a = (a << 6 | a >>> 26) + b | 0;
|
|
d += (b ^ (a | ~c)) + k[7] + 1126891415 | 0;
|
|
d = (d << 10 | d >>> 22) + a | 0;
|
|
c += (a ^ (d | ~b)) + k[14] - 1416354905 | 0;
|
|
c = (c << 15 | c >>> 17) + d | 0;
|
|
b += (d ^ (c | ~a)) + k[5] - 57434055 | 0;
|
|
b = (b << 21 | b >>> 11) + c | 0;
|
|
a += (c ^ (b | ~d)) + k[12] + 1700485571 | 0;
|
|
a = (a << 6 | a >>> 26) + b | 0;
|
|
d += (b ^ (a | ~c)) + k[3] - 1894986606 | 0;
|
|
d = (d << 10 | d >>> 22) + a | 0;
|
|
c += (a ^ (d | ~b)) + k[10] - 1051523 | 0;
|
|
c = (c << 15 | c >>> 17) + d | 0;
|
|
b += (d ^ (c | ~a)) + k[1] - 2054922799 | 0;
|
|
b = (b << 21 | b >>> 11) + c | 0;
|
|
a += (c ^ (b | ~d)) + k[8] + 1873313359 | 0;
|
|
a = (a << 6 | a >>> 26) + b | 0;
|
|
d += (b ^ (a | ~c)) + k[15] - 30611744 | 0;
|
|
d = (d << 10 | d >>> 22) + a | 0;
|
|
c += (a ^ (d | ~b)) + k[6] - 1560198380 | 0;
|
|
c = (c << 15 | c >>> 17) + d | 0;
|
|
b += (d ^ (c | ~a)) + k[13] + 1309151649 | 0;
|
|
b = (b << 21 | b >>> 11) + c | 0;
|
|
a += (c ^ (b | ~d)) + k[4] - 145523070 | 0;
|
|
a = (a << 6 | a >>> 26) + b | 0;
|
|
d += (b ^ (a | ~c)) + k[11] - 1120210379 | 0;
|
|
d = (d << 10 | d >>> 22) + a | 0;
|
|
c += (a ^ (d | ~b)) + k[2] + 718787259 | 0;
|
|
c = (c << 15 | c >>> 17) + d | 0;
|
|
b += (d ^ (c | ~a)) + k[9] - 343485551 | 0;
|
|
b = (b << 21 | b >>> 11) + c | 0;
|
|
x[0] = a + x[0] | 0;
|
|
x[1] = b + x[1] | 0;
|
|
x[2] = c + x[2] | 0;
|
|
x[3] = d + x[3] | 0;
|
|
}
|
|
function md5blk(s) {
|
|
var md5blks = [], i;
|
|
for (i = 0; i < 64; i += 4) {
|
|
md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24);
|
|
}
|
|
return md5blks;
|
|
}
|
|
function md5blk_array(a) {
|
|
var md5blks = [], i;
|
|
for (i = 0; i < 64; i += 4) {
|
|
md5blks[i >> 2] = a[i] + (a[i + 1] << 8) + (a[i + 2] << 16) + (a[i + 3] << 24);
|
|
}
|
|
return md5blks;
|
|
}
|
|
function md51(s) {
|
|
var n = s.length, state = [ 1732584193, -271733879, -1732584194, 271733878 ], i, length, tail, tmp, lo, hi;
|
|
for (i = 64; i <= n; i += 64) {
|
|
md5cycle(state, md5blk(s.substring(i - 64, i)));
|
|
}
|
|
s = s.substring(i - 64);
|
|
length = s.length;
|
|
tail = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ];
|
|
for (i = 0; i < length; i += 1) {
|
|
tail[i >> 2] |= s.charCodeAt(i) << (i % 4 << 3);
|
|
}
|
|
tail[i >> 2] |= 128 << (i % 4 << 3);
|
|
if (i > 55) {
|
|
md5cycle(state, tail);
|
|
for (i = 0; i < 16; i += 1) {
|
|
tail[i] = 0;
|
|
}
|
|
}
|
|
tmp = n * 8;
|
|
tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/);
|
|
lo = parseInt(tmp[2], 16);
|
|
hi = parseInt(tmp[1], 16) || 0;
|
|
tail[14] = lo;
|
|
tail[15] = hi;
|
|
md5cycle(state, tail);
|
|
return state;
|
|
}
|
|
function md51_array(a) {
|
|
var n = a.length, state = [ 1732584193, -271733879, -1732584194, 271733878 ], i, length, tail, tmp, lo, hi;
|
|
for (i = 64; i <= n; i += 64) {
|
|
md5cycle(state, md5blk_array(a.subarray(i - 64, i)));
|
|
}
|
|
a = i - 64 < n ? a.subarray(i - 64) : new Uint8Array(0);
|
|
length = a.length;
|
|
tail = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ];
|
|
for (i = 0; i < length; i += 1) {
|
|
tail[i >> 2] |= a[i] << (i % 4 << 3);
|
|
}
|
|
tail[i >> 2] |= 128 << (i % 4 << 3);
|
|
if (i > 55) {
|
|
md5cycle(state, tail);
|
|
for (i = 0; i < 16; i += 1) {
|
|
tail[i] = 0;
|
|
}
|
|
}
|
|
tmp = n * 8;
|
|
tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/);
|
|
lo = parseInt(tmp[2], 16);
|
|
hi = parseInt(tmp[1], 16) || 0;
|
|
tail[14] = lo;
|
|
tail[15] = hi;
|
|
md5cycle(state, tail);
|
|
return state;
|
|
}
|
|
function rhex(n) {
|
|
var s = "", j;
|
|
for (j = 0; j < 4; j += 1) {
|
|
s += hex_chr[n >> j * 8 + 4 & 15] + hex_chr[n >> j * 8 & 15];
|
|
}
|
|
return s;
|
|
}
|
|
function hex(x) {
|
|
var i;
|
|
for (i = 0; i < x.length; i += 1) {
|
|
x[i] = rhex(x[i]);
|
|
}
|
|
return x.join("");
|
|
}
|
|
if (hex(md51("hello")) !== "5d41402abc4b2a76b9719d911017c592") ;
|
|
if (typeof ArrayBuffer !== "undefined" && !ArrayBuffer.prototype.slice) {
|
|
(function() {
|
|
function clamp(val, length) {
|
|
val = val | 0 || 0;
|
|
if (val < 0) {
|
|
return Math.max(val + length, 0);
|
|
}
|
|
return Math.min(val, length);
|
|
}
|
|
ArrayBuffer.prototype.slice = function(from, to) {
|
|
var length = this.byteLength, begin = clamp(from, length), end = length, num, target, targetArray, sourceArray;
|
|
if (to !== undefined) {
|
|
end = clamp(to, length);
|
|
}
|
|
if (begin > end) {
|
|
return new ArrayBuffer(0);
|
|
}
|
|
num = end - begin;
|
|
target = new ArrayBuffer(num);
|
|
targetArray = new Uint8Array(target);
|
|
sourceArray = new Uint8Array(this, begin, num);
|
|
targetArray.set(sourceArray);
|
|
return target;
|
|
};
|
|
})();
|
|
}
|
|
function toUtf8(str) {
|
|
if (/[\u0080-\uFFFF]/.test(str)) {
|
|
str = unescape(encodeURIComponent(str));
|
|
}
|
|
return str;
|
|
}
|
|
function utf8Str2ArrayBuffer(str, returnUInt8Array) {
|
|
var length = str.length, buff = new ArrayBuffer(length), arr = new Uint8Array(buff), i;
|
|
for (i = 0; i < length; i += 1) {
|
|
arr[i] = str.charCodeAt(i);
|
|
}
|
|
return returnUInt8Array ? arr : buff;
|
|
}
|
|
function arrayBuffer2Utf8Str(buff) {
|
|
return String.fromCharCode.apply(null, new Uint8Array(buff));
|
|
}
|
|
function concatenateArrayBuffers(first, second, returnUInt8Array) {
|
|
var result = new Uint8Array(first.byteLength + second.byteLength);
|
|
result.set(new Uint8Array(first));
|
|
result.set(new Uint8Array(second), first.byteLength);
|
|
return returnUInt8Array ? result : result.buffer;
|
|
}
|
|
function hexToBinaryString(hex) {
|
|
var bytes = [], length = hex.length, x;
|
|
for (x = 0; x < length - 1; x += 2) {
|
|
bytes.push(parseInt(hex.substr(x, 2), 16));
|
|
}
|
|
return String.fromCharCode.apply(String, bytes);
|
|
}
|
|
function SparkMD5() {
|
|
this.reset();
|
|
}
|
|
SparkMD5.prototype.append = function(str) {
|
|
this.appendBinary(toUtf8(str));
|
|
return this;
|
|
};
|
|
SparkMD5.prototype.appendBinary = function(contents) {
|
|
this._buff += contents;
|
|
this._length += contents.length;
|
|
var length = this._buff.length, i;
|
|
for (i = 64; i <= length; i += 64) {
|
|
md5cycle(this._hash, md5blk(this._buff.substring(i - 64, i)));
|
|
}
|
|
this._buff = this._buff.substring(i - 64);
|
|
return this;
|
|
};
|
|
SparkMD5.prototype.end = function(raw) {
|
|
var buff = this._buff, length = buff.length, i, tail = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], ret;
|
|
for (i = 0; i < length; i += 1) {
|
|
tail[i >> 2] |= buff.charCodeAt(i) << (i % 4 << 3);
|
|
}
|
|
this._finish(tail, length);
|
|
ret = hex(this._hash);
|
|
if (raw) {
|
|
ret = hexToBinaryString(ret);
|
|
}
|
|
this.reset();
|
|
return ret;
|
|
};
|
|
SparkMD5.prototype.reset = function() {
|
|
this._buff = "";
|
|
this._length = 0;
|
|
this._hash = [ 1732584193, -271733879, -1732584194, 271733878 ];
|
|
return this;
|
|
};
|
|
SparkMD5.prototype.getState = function() {
|
|
return {
|
|
buff: this._buff,
|
|
length: this._length,
|
|
hash: this._hash
|
|
};
|
|
};
|
|
SparkMD5.prototype.setState = function(state) {
|
|
this._buff = state.buff;
|
|
this._length = state.length;
|
|
this._hash = state.hash;
|
|
return this;
|
|
};
|
|
SparkMD5.prototype.destroy = function() {
|
|
delete this._hash;
|
|
delete this._buff;
|
|
delete this._length;
|
|
};
|
|
SparkMD5.prototype._finish = function(tail, length) {
|
|
var i = length, tmp, lo, hi;
|
|
tail[i >> 2] |= 128 << (i % 4 << 3);
|
|
if (i > 55) {
|
|
md5cycle(this._hash, tail);
|
|
for (i = 0; i < 16; i += 1) {
|
|
tail[i] = 0;
|
|
}
|
|
}
|
|
tmp = this._length * 8;
|
|
tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/);
|
|
lo = parseInt(tmp[2], 16);
|
|
hi = parseInt(tmp[1], 16) || 0;
|
|
tail[14] = lo;
|
|
tail[15] = hi;
|
|
md5cycle(this._hash, tail);
|
|
};
|
|
SparkMD5.hash = function(str, raw) {
|
|
return SparkMD5.hashBinary(toUtf8(str), raw);
|
|
};
|
|
SparkMD5.hashBinary = function(content, raw) {
|
|
var hash = md51(content), ret = hex(hash);
|
|
return raw ? hexToBinaryString(ret) : ret;
|
|
};
|
|
SparkMD5.ArrayBuffer = function() {
|
|
this.reset();
|
|
};
|
|
SparkMD5.ArrayBuffer.prototype.append = function(arr) {
|
|
var buff = concatenateArrayBuffers(this._buff.buffer, arr, true), length = buff.length, i;
|
|
this._length += arr.byteLength;
|
|
for (i = 64; i <= length; i += 64) {
|
|
md5cycle(this._hash, md5blk_array(buff.subarray(i - 64, i)));
|
|
}
|
|
this._buff = i - 64 < length ? new Uint8Array(buff.buffer.slice(i - 64)) : new Uint8Array(0);
|
|
return this;
|
|
};
|
|
SparkMD5.ArrayBuffer.prototype.end = function(raw) {
|
|
var buff = this._buff, length = buff.length, tail = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], i, ret;
|
|
for (i = 0; i < length; i += 1) {
|
|
tail[i >> 2] |= buff[i] << (i % 4 << 3);
|
|
}
|
|
this._finish(tail, length);
|
|
ret = hex(this._hash);
|
|
if (raw) {
|
|
ret = hexToBinaryString(ret);
|
|
}
|
|
this.reset();
|
|
return ret;
|
|
};
|
|
SparkMD5.ArrayBuffer.prototype.reset = function() {
|
|
this._buff = new Uint8Array(0);
|
|
this._length = 0;
|
|
this._hash = [ 1732584193, -271733879, -1732584194, 271733878 ];
|
|
return this;
|
|
};
|
|
SparkMD5.ArrayBuffer.prototype.getState = function() {
|
|
var state = SparkMD5.prototype.getState.call(this);
|
|
state.buff = arrayBuffer2Utf8Str(state.buff);
|
|
return state;
|
|
};
|
|
SparkMD5.ArrayBuffer.prototype.setState = function(state) {
|
|
state.buff = utf8Str2ArrayBuffer(state.buff, true);
|
|
return SparkMD5.prototype.setState.call(this, state);
|
|
};
|
|
SparkMD5.ArrayBuffer.prototype.destroy = SparkMD5.prototype.destroy;
|
|
SparkMD5.ArrayBuffer.prototype._finish = SparkMD5.prototype._finish;
|
|
SparkMD5.ArrayBuffer.hash = function(arr, raw) {
|
|
var hash = md51_array(new Uint8Array(arr)), ret = hex(hash);
|
|
return raw ? hexToBinaryString(ret) : ret;
|
|
};
|
|
return SparkMD5;
|
|
});
|
|
});
|
|
var classCallCheck = function(instance, Constructor) {
|
|
if (!(instance instanceof Constructor)) {
|
|
throw new TypeError("Cannot call a class as a function");
|
|
}
|
|
};
|
|
var createClass = function() {
|
|
function defineProperties(target, props) {
|
|
for (var i = 0; i < props.length; i++) {
|
|
var descriptor = props[i];
|
|
descriptor.enumerable = descriptor.enumerable || false;
|
|
descriptor.configurable = true;
|
|
if ("value" in descriptor) descriptor.writable = true;
|
|
Object.defineProperty(target, descriptor.key, descriptor);
|
|
}
|
|
}
|
|
return function(Constructor, protoProps, staticProps) {
|
|
if (protoProps) defineProperties(Constructor.prototype, protoProps);
|
|
if (staticProps) defineProperties(Constructor, staticProps);
|
|
return Constructor;
|
|
};
|
|
}();
|
|
var fileSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
|
|
var FileChecksum = function() {
|
|
createClass(FileChecksum, null, [ {
|
|
key: "create",
|
|
value: function create(file, callback) {
|
|
var instance = new FileChecksum(file);
|
|
instance.create(callback);
|
|
}
|
|
} ]);
|
|
function FileChecksum(file) {
|
|
classCallCheck(this, FileChecksum);
|
|
this.file = file;
|
|
this.chunkSize = 2097152;
|
|
this.chunkCount = Math.ceil(this.file.size / this.chunkSize);
|
|
this.chunkIndex = 0;
|
|
}
|
|
createClass(FileChecksum, [ {
|
|
key: "create",
|
|
value: function create(callback) {
|
|
var _this = this;
|
|
this.callback = callback;
|
|
this.md5Buffer = new sparkMd5.ArrayBuffer();
|
|
this.fileReader = new FileReader();
|
|
this.fileReader.addEventListener("load", function(event) {
|
|
return _this.fileReaderDidLoad(event);
|
|
});
|
|
this.fileReader.addEventListener("error", function(event) {
|
|
return _this.fileReaderDidError(event);
|
|
});
|
|
this.readNextChunk();
|
|
}
|
|
}, {
|
|
key: "fileReaderDidLoad",
|
|
value: function fileReaderDidLoad(event) {
|
|
this.md5Buffer.append(event.target.result);
|
|
if (!this.readNextChunk()) {
|
|
var binaryDigest = this.md5Buffer.end(true);
|
|
var base64digest = btoa(binaryDigest);
|
|
this.callback(null, base64digest);
|
|
}
|
|
}
|
|
}, {
|
|
key: "fileReaderDidError",
|
|
value: function fileReaderDidError(event) {
|
|
this.callback("Error reading " + this.file.name);
|
|
}
|
|
}, {
|
|
key: "readNextChunk",
|
|
value: function readNextChunk() {
|
|
if (this.chunkIndex < this.chunkCount || this.chunkIndex == 0 && this.chunkCount == 0) {
|
|
var start = this.chunkIndex * this.chunkSize;
|
|
var end = Math.min(start + this.chunkSize, this.file.size);
|
|
var bytes = fileSlice.call(this.file, start, end);
|
|
this.fileReader.readAsArrayBuffer(bytes);
|
|
this.chunkIndex++;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
} ]);
|
|
return FileChecksum;
|
|
}();
|
|
function getMetaValue(name) {
|
|
var element = findElement(document.head, 'meta[name="' + name + '"]');
|
|
if (element) {
|
|
return element.getAttribute("content");
|
|
}
|
|
}
|
|
function findElements(root, selector) {
|
|
if (typeof root == "string") {
|
|
selector = root;
|
|
root = document;
|
|
}
|
|
var elements = root.querySelectorAll(selector);
|
|
return toArray$1(elements);
|
|
}
|
|
function findElement(root, selector) {
|
|
if (typeof root == "string") {
|
|
selector = root;
|
|
root = document;
|
|
}
|
|
return root.querySelector(selector);
|
|
}
|
|
function dispatchEvent(element, type) {
|
|
var eventInit = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
var disabled = element.disabled;
|
|
var bubbles = eventInit.bubbles, cancelable = eventInit.cancelable, detail = eventInit.detail;
|
|
var event = document.createEvent("Event");
|
|
event.initEvent(type, bubbles || true, cancelable || true);
|
|
event.detail = detail || {};
|
|
try {
|
|
element.disabled = false;
|
|
element.dispatchEvent(event);
|
|
} finally {
|
|
element.disabled = disabled;
|
|
}
|
|
return event;
|
|
}
|
|
function toArray$1(value) {
|
|
if (Array.isArray(value)) {
|
|
return value;
|
|
} else if (Array.from) {
|
|
return Array.from(value);
|
|
} else {
|
|
return [].slice.call(value);
|
|
}
|
|
}
|
|
var BlobRecord = function() {
|
|
function BlobRecord(file, checksum, url) {
|
|
var _this = this;
|
|
classCallCheck(this, BlobRecord);
|
|
this.file = file;
|
|
this.attributes = {
|
|
filename: file.name,
|
|
content_type: file.type,
|
|
byte_size: file.size,
|
|
checksum: checksum
|
|
};
|
|
this.xhr = new XMLHttpRequest();
|
|
this.xhr.open("POST", url, true);
|
|
this.xhr.responseType = "json";
|
|
this.xhr.setRequestHeader("Content-Type", "application/json");
|
|
this.xhr.setRequestHeader("Accept", "application/json");
|
|
this.xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
|
|
this.xhr.setRequestHeader("X-CSRF-Token", getMetaValue("csrf-token"));
|
|
this.xhr.addEventListener("load", function(event) {
|
|
return _this.requestDidLoad(event);
|
|
});
|
|
this.xhr.addEventListener("error", function(event) {
|
|
return _this.requestDidError(event);
|
|
});
|
|
}
|
|
createClass(BlobRecord, [ {
|
|
key: "create",
|
|
value: function create(callback) {
|
|
this.callback = callback;
|
|
this.xhr.send(JSON.stringify({
|
|
blob: this.attributes
|
|
}));
|
|
}
|
|
}, {
|
|
key: "requestDidLoad",
|
|
value: function requestDidLoad(event) {
|
|
if (this.status >= 200 && this.status < 300) {
|
|
var response = this.response;
|
|
var direct_upload = response.direct_upload;
|
|
delete response.direct_upload;
|
|
this.attributes = response;
|
|
this.directUploadData = direct_upload;
|
|
this.callback(null, this.toJSON());
|
|
} else {
|
|
this.requestDidError(event);
|
|
}
|
|
}
|
|
}, {
|
|
key: "requestDidError",
|
|
value: function requestDidError(event) {
|
|
this.callback('Error creating Blob for "' + this.file.name + '". Status: ' + this.status);
|
|
}
|
|
}, {
|
|
key: "toJSON",
|
|
value: function toJSON() {
|
|
var result = {};
|
|
for (var key in this.attributes) {
|
|
result[key] = this.attributes[key];
|
|
}
|
|
return result;
|
|
}
|
|
}, {
|
|
key: "status",
|
|
get: function get$$1() {
|
|
return this.xhr.status;
|
|
}
|
|
}, {
|
|
key: "response",
|
|
get: function get$$1() {
|
|
var _xhr = this.xhr, responseType = _xhr.responseType, response = _xhr.response;
|
|
if (responseType == "json") {
|
|
return response;
|
|
} else {
|
|
return JSON.parse(response);
|
|
}
|
|
}
|
|
} ]);
|
|
return BlobRecord;
|
|
}();
|
|
var BlobUpload = function() {
|
|
function BlobUpload(blob) {
|
|
var _this = this;
|
|
classCallCheck(this, BlobUpload);
|
|
this.blob = blob;
|
|
this.file = blob.file;
|
|
var _blob$directUploadDat = blob.directUploadData, url = _blob$directUploadDat.url, headers = _blob$directUploadDat.headers;
|
|
this.xhr = new XMLHttpRequest();
|
|
this.xhr.open("PUT", url, true);
|
|
this.xhr.responseType = "text";
|
|
for (var key in headers) {
|
|
this.xhr.setRequestHeader(key, headers[key]);
|
|
}
|
|
this.xhr.addEventListener("load", function(event) {
|
|
return _this.requestDidLoad(event);
|
|
});
|
|
this.xhr.addEventListener("error", function(event) {
|
|
return _this.requestDidError(event);
|
|
});
|
|
}
|
|
createClass(BlobUpload, [ {
|
|
key: "create",
|
|
value: function create(callback) {
|
|
this.callback = callback;
|
|
this.xhr.send(this.file.slice());
|
|
}
|
|
}, {
|
|
key: "requestDidLoad",
|
|
value: function requestDidLoad(event) {
|
|
var _xhr = this.xhr, status = _xhr.status, response = _xhr.response;
|
|
if (status >= 200 && status < 300) {
|
|
this.callback(null, response);
|
|
} else {
|
|
this.requestDidError(event);
|
|
}
|
|
}
|
|
}, {
|
|
key: "requestDidError",
|
|
value: function requestDidError(event) {
|
|
this.callback('Error storing "' + this.file.name + '". Status: ' + this.xhr.status);
|
|
}
|
|
} ]);
|
|
return BlobUpload;
|
|
}();
|
|
var id = 0;
|
|
var DirectUpload = function() {
|
|
function DirectUpload(file, url, delegate) {
|
|
classCallCheck(this, DirectUpload);
|
|
this.id = ++id;
|
|
this.file = file;
|
|
this.url = url;
|
|
this.delegate = delegate;
|
|
}
|
|
createClass(DirectUpload, [ {
|
|
key: "create",
|
|
value: function create(callback) {
|
|
var _this = this;
|
|
FileChecksum.create(this.file, function(error, checksum) {
|
|
if (error) {
|
|
callback(error);
|
|
return;
|
|
}
|
|
var blob = new BlobRecord(_this.file, checksum, _this.url);
|
|
notify(_this.delegate, "directUploadWillCreateBlobWithXHR", blob.xhr);
|
|
blob.create(function(error) {
|
|
if (error) {
|
|
callback(error);
|
|
} else {
|
|
var upload = new BlobUpload(blob);
|
|
notify(_this.delegate, "directUploadWillStoreFileWithXHR", upload.xhr);
|
|
upload.create(function(error) {
|
|
if (error) {
|
|
callback(error);
|
|
} else {
|
|
callback(null, blob.toJSON());
|
|
}
|
|
});
|
|
}
|
|
});
|
|
});
|
|
}
|
|
} ]);
|
|
return DirectUpload;
|
|
}();
|
|
function notify(object, methodName) {
|
|
if (object && typeof object[methodName] == "function") {
|
|
for (var _len = arguments.length, messages = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
|
|
messages[_key - 2] = arguments[_key];
|
|
}
|
|
return object[methodName].apply(object, messages);
|
|
}
|
|
}
|
|
var DirectUploadController = function() {
|
|
function DirectUploadController(input, file) {
|
|
classCallCheck(this, DirectUploadController);
|
|
this.input = input;
|
|
this.file = file;
|
|
this.directUpload = new DirectUpload(this.file, this.url, this);
|
|
this.dispatch("initialize");
|
|
}
|
|
createClass(DirectUploadController, [ {
|
|
key: "start",
|
|
value: function start(callback) {
|
|
var _this = this;
|
|
var hiddenInput = document.createElement("input");
|
|
hiddenInput.type = "hidden";
|
|
hiddenInput.name = this.input.name;
|
|
this.input.insertAdjacentElement("beforebegin", hiddenInput);
|
|
this.dispatch("start");
|
|
this.directUpload.create(function(error, attributes) {
|
|
if (error) {
|
|
hiddenInput.parentNode.removeChild(hiddenInput);
|
|
_this.dispatchError(error);
|
|
} else {
|
|
hiddenInput.value = attributes.signed_id;
|
|
}
|
|
_this.dispatch("end");
|
|
callback(error);
|
|
});
|
|
}
|
|
}, {
|
|
key: "uploadRequestDidProgress",
|
|
value: function uploadRequestDidProgress(event) {
|
|
var progress = event.loaded / event.total * 100;
|
|
if (progress) {
|
|
this.dispatch("progress", {
|
|
progress: progress
|
|
});
|
|
}
|
|
}
|
|
}, {
|
|
key: "dispatch",
|
|
value: function dispatch(name) {
|
|
var detail = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
detail.file = this.file;
|
|
detail.id = this.directUpload.id;
|
|
return dispatchEvent(this.input, "direct-upload:" + name, {
|
|
detail: detail
|
|
});
|
|
}
|
|
}, {
|
|
key: "dispatchError",
|
|
value: function dispatchError(error) {
|
|
var event = this.dispatch("error", {
|
|
error: error
|
|
});
|
|
if (!event.defaultPrevented) {
|
|
alert(error);
|
|
}
|
|
}
|
|
}, {
|
|
key: "directUploadWillCreateBlobWithXHR",
|
|
value: function directUploadWillCreateBlobWithXHR(xhr) {
|
|
this.dispatch("before-blob-request", {
|
|
xhr: xhr
|
|
});
|
|
}
|
|
}, {
|
|
key: "directUploadWillStoreFileWithXHR",
|
|
value: function directUploadWillStoreFileWithXHR(xhr) {
|
|
var _this2 = this;
|
|
this.dispatch("before-storage-request", {
|
|
xhr: xhr
|
|
});
|
|
xhr.upload.addEventListener("progress", function(event) {
|
|
return _this2.uploadRequestDidProgress(event);
|
|
});
|
|
}
|
|
}, {
|
|
key: "url",
|
|
get: function get$$1() {
|
|
return this.input.getAttribute("data-direct-upload-url");
|
|
}
|
|
} ]);
|
|
return DirectUploadController;
|
|
}();
|
|
var inputSelector = "input[type=file][data-direct-upload-url]:not([disabled])";
|
|
var DirectUploadsController = function() {
|
|
function DirectUploadsController(form) {
|
|
classCallCheck(this, DirectUploadsController);
|
|
this.form = form;
|
|
this.inputs = findElements(form, inputSelector).filter(function(input) {
|
|
return input.files.length;
|
|
});
|
|
}
|
|
createClass(DirectUploadsController, [ {
|
|
key: "start",
|
|
value: function start(callback) {
|
|
var _this = this;
|
|
var controllers = this.createDirectUploadControllers();
|
|
var startNextController = function startNextController() {
|
|
var controller = controllers.shift();
|
|
if (controller) {
|
|
controller.start(function(error) {
|
|
if (error) {
|
|
callback(error);
|
|
_this.dispatch("end");
|
|
} else {
|
|
startNextController();
|
|
}
|
|
});
|
|
} else {
|
|
callback();
|
|
_this.dispatch("end");
|
|
}
|
|
};
|
|
this.dispatch("start");
|
|
startNextController();
|
|
}
|
|
}, {
|
|
key: "createDirectUploadControllers",
|
|
value: function createDirectUploadControllers() {
|
|
var controllers = [];
|
|
this.inputs.forEach(function(input) {
|
|
toArray$1(input.files).forEach(function(file) {
|
|
var controller = new DirectUploadController(input, file);
|
|
controllers.push(controller);
|
|
});
|
|
});
|
|
return controllers;
|
|
}
|
|
}, {
|
|
key: "dispatch",
|
|
value: function dispatch(name) {
|
|
var detail = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
return dispatchEvent(this.form, "direct-uploads:" + name, {
|
|
detail: detail
|
|
});
|
|
}
|
|
} ]);
|
|
return DirectUploadsController;
|
|
}();
|
|
var processingAttribute = "data-direct-uploads-processing";
|
|
var submitButtonsByForm = new WeakMap();
|
|
var started = false;
|
|
function start() {
|
|
if (!started) {
|
|
started = true;
|
|
document.addEventListener("click", didClick, true);
|
|
document.addEventListener("submit", didSubmitForm);
|
|
document.addEventListener("ajax:before", didSubmitRemoteElement);
|
|
}
|
|
}
|
|
function didClick(event) {
|
|
var target = event.target;
|
|
if ((target.tagName == "INPUT" || target.tagName == "BUTTON") && target.type == "submit" && target.form) {
|
|
submitButtonsByForm.set(target.form, target);
|
|
}
|
|
}
|
|
function didSubmitForm(event) {
|
|
handleFormSubmissionEvent(event);
|
|
}
|
|
function didSubmitRemoteElement(event) {
|
|
if (event.target.tagName == "FORM") {
|
|
handleFormSubmissionEvent(event);
|
|
}
|
|
}
|
|
function handleFormSubmissionEvent(event) {
|
|
var form = event.target;
|
|
if (form.hasAttribute(processingAttribute)) {
|
|
event.preventDefault();
|
|
return;
|
|
}
|
|
var controller = new DirectUploadsController(form);
|
|
var inputs = controller.inputs;
|
|
if (inputs.length) {
|
|
event.preventDefault();
|
|
form.setAttribute(processingAttribute, "");
|
|
inputs.forEach(disable);
|
|
controller.start(function(error) {
|
|
form.removeAttribute(processingAttribute);
|
|
if (error) {
|
|
inputs.forEach(enable);
|
|
} else {
|
|
submitForm(form);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
function submitForm(form) {
|
|
var button = submitButtonsByForm.get(form) || findElement(form, "input[type=submit], button[type=submit]");
|
|
if (button) {
|
|
var _button = button, disabled = _button.disabled;
|
|
button.disabled = false;
|
|
button.focus();
|
|
button.click();
|
|
button.disabled = disabled;
|
|
} else {
|
|
button = document.createElement("input");
|
|
button.type = "submit";
|
|
button.style.display = "none";
|
|
form.appendChild(button);
|
|
button.click();
|
|
form.removeChild(button);
|
|
}
|
|
submitButtonsByForm.delete(form);
|
|
}
|
|
function disable(input) {
|
|
input.disabled = true;
|
|
}
|
|
function enable(input) {
|
|
input.disabled = false;
|
|
}
|
|
function autostart() {
|
|
if (window.ActiveStorage) {
|
|
start();
|
|
}
|
|
}
|
|
setTimeout(autostart, 1);
|
|
exports.start = start;
|
|
exports.DirectUpload = DirectUpload;
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
});
|
|
/*
|
|
Turbolinks 5.2.0
|
|
Copyright © 2018 Basecamp, LLC
|
|
*/
|
|
|
|
(function(){var t=this;(function(){(function(){this.Turbolinks={supported:function(){return null!=window.history.pushState&&null!=window.requestAnimationFrame&&null!=window.addEventListener}(),visit:function(t,r){return e.controller.visit(t,r)},clearCache:function(){return e.controller.clearCache()},setProgressBarDelay:function(t){return e.controller.setProgressBarDelay(t)}}}).call(this)}).call(t);var e=t.Turbolinks;(function(){(function(){var t,r,n,o=[].slice;e.copyObject=function(t){var e,r,n;r={};for(e in t)n=t[e],r[e]=n;return r},e.closest=function(e,r){return t.call(e,r)},t=function(){var t,e;return t=document.documentElement,null!=(e=t.closest)?e:function(t){var e;for(e=this;e;){if(e.nodeType===Node.ELEMENT_NODE&&r.call(e,t))return e;e=e.parentNode}}}(),e.defer=function(t){return setTimeout(t,1)},e.throttle=function(t){var e;return e=null,function(){var r;return r=1<=arguments.length?o.call(arguments,0):[],null!=e?e:e=requestAnimationFrame(function(n){return function(){return e=null,t.apply(n,r)}}(this))}},e.dispatch=function(t,e){var r,o,i,s,a,u;return a=null!=e?e:{},u=a.target,r=a.cancelable,o=a.data,i=document.createEvent("Events"),i.initEvent(t,!0,r===!0),i.data=null!=o?o:{},i.cancelable&&!n&&(s=i.preventDefault,i.preventDefault=function(){return this.defaultPrevented||Object.defineProperty(this,"defaultPrevented",{get:function(){return!0}}),s.call(this)}),(null!=u?u:document).dispatchEvent(i),i},n=function(){var t;return t=document.createEvent("Events"),t.initEvent("test",!0,!0),t.preventDefault(),t.defaultPrevented}(),e.match=function(t,e){return r.call(t,e)},r=function(){var t,e,r,n;return t=document.documentElement,null!=(e=null!=(r=null!=(n=t.matchesSelector)?n:t.webkitMatchesSelector)?r:t.msMatchesSelector)?e:t.mozMatchesSelector}(),e.uuid=function(){var t,e,r;for(r="",t=e=1;36>=e;t=++e)r+=9===t||14===t||19===t||24===t?"-":15===t?"4":20===t?(Math.floor(4*Math.random())+8).toString(16):Math.floor(15*Math.random()).toString(16);return r}}).call(this),function(){e.Location=function(){function t(t){var e,r;null==t&&(t=""),r=document.createElement("a"),r.href=t.toString(),this.absoluteURL=r.href,e=r.hash.length,2>e?this.requestURL=this.absoluteURL:(this.requestURL=this.absoluteURL.slice(0,-e),this.anchor=r.hash.slice(1))}var e,r,n,o;return t.wrap=function(t){return t instanceof this?t:new this(t)},t.prototype.getOrigin=function(){return this.absoluteURL.split("/",3).join("/")},t.prototype.getPath=function(){var t,e;return null!=(t=null!=(e=this.requestURL.match(/\/\/[^\/]*(\/[^?;]*)/))?e[1]:void 0)?t:"/"},t.prototype.getPathComponents=function(){return this.getPath().split("/").slice(1)},t.prototype.getLastPathComponent=function(){return this.getPathComponents().slice(-1)[0]},t.prototype.getExtension=function(){var t,e;return null!=(t=null!=(e=this.getLastPathComponent().match(/\.[^.]*$/))?e[0]:void 0)?t:""},t.prototype.isHTML=function(){return this.getExtension().match(/^(?:|\.(?:htm|html|xhtml))$/)},t.prototype.isPrefixedBy=function(t){var e;return e=r(t),this.isEqualTo(t)||o(this.absoluteURL,e)},t.prototype.isEqualTo=function(t){return this.absoluteURL===(null!=t?t.absoluteURL:void 0)},t.prototype.toCacheKey=function(){return this.requestURL},t.prototype.toJSON=function(){return this.absoluteURL},t.prototype.toString=function(){return this.absoluteURL},t.prototype.valueOf=function(){return this.absoluteURL},r=function(t){return e(t.getOrigin()+t.getPath())},e=function(t){return n(t,"/")?t:t+"/"},o=function(t,e){return t.slice(0,e.length)===e},n=function(t,e){return t.slice(-e.length)===e},t}()}.call(this),function(){var t=function(t,e){return function(){return t.apply(e,arguments)}};e.HttpRequest=function(){function r(r,n,o){this.delegate=r,this.requestCanceled=t(this.requestCanceled,this),this.requestTimedOut=t(this.requestTimedOut,this),this.requestFailed=t(this.requestFailed,this),this.requestLoaded=t(this.requestLoaded,this),this.requestProgressed=t(this.requestProgressed,this),this.url=e.Location.wrap(n).requestURL,this.referrer=e.Location.wrap(o).absoluteURL,this.createXHR()}return r.NETWORK_FAILURE=0,r.TIMEOUT_FAILURE=-1,r.timeout=60,r.prototype.send=function(){var t;return this.xhr&&!this.sent?(this.notifyApplicationBeforeRequestStart(),this.setProgress(0),this.xhr.send(),this.sent=!0,"function"==typeof(t=this.delegate).requestStarted?t.requestStarted():void 0):void 0},r.prototype.cancel=function(){return this.xhr&&this.sent?this.xhr.abort():void 0},r.prototype.requestProgressed=function(t){return t.lengthComputable?this.setProgress(t.loaded/t.total):void 0},r.prototype.requestLoaded=function(){return this.endRequest(function(t){return function(){var e;return 200<=(e=t.xhr.status)&&300>e?t.delegate.requestCompletedWithResponse(t.xhr.responseText,t.xhr.getResponseHeader("Turbolinks-Location")):(t.failed=!0,t.delegate.requestFailedWithStatusCode(t.xhr.status,t.xhr.responseText))}}(this))},r.prototype.requestFailed=function(){return this.endRequest(function(t){return function(){return t.failed=!0,t.delegate.requestFailedWithStatusCode(t.constructor.NETWORK_FAILURE)}}(this))},r.prototype.requestTimedOut=function(){return this.endRequest(function(t){return function(){return t.failed=!0,t.delegate.requestFailedWithStatusCode(t.constructor.TIMEOUT_FAILURE)}}(this))},r.prototype.requestCanceled=function(){return this.endRequest()},r.prototype.notifyApplicationBeforeRequestStart=function(){return e.dispatch("turbolinks:request-start",{data:{url:this.url,xhr:this.xhr}})},r.prototype.notifyApplicationAfterRequestEnd=function(){return e.dispatch("turbolinks:request-end",{data:{url:this.url,xhr:this.xhr}})},r.prototype.createXHR=function(){return this.xhr=new XMLHttpRequest,this.xhr.open("GET",this.url,!0),this.xhr.timeout=1e3*this.constructor.timeout,this.xhr.setRequestHeader("Accept","text/html, application/xhtml+xml"),this.xhr.setRequestHeader("Turbolinks-Referrer",this.referrer),this.xhr.onprogress=this.requestProgressed,this.xhr.onload=this.requestLoaded,this.xhr.onerror=this.requestFailed,this.xhr.ontimeout=this.requestTimedOut,this.xhr.onabort=this.requestCanceled},r.prototype.endRequest=function(t){return this.xhr?(this.notifyApplicationAfterRequestEnd(),null!=t&&t.call(this),this.destroy()):void 0},r.prototype.setProgress=function(t){var e;return this.progress=t,"function"==typeof(e=this.delegate).requestProgressed?e.requestProgressed(this.progress):void 0},r.prototype.destroy=function(){var t;return this.setProgress(1),"function"==typeof(t=this.delegate).requestFinished&&t.requestFinished(),this.delegate=null,this.xhr=null},r}()}.call(this),function(){var t=function(t,e){return function(){return t.apply(e,arguments)}};e.ProgressBar=function(){function e(){this.trickle=t(this.trickle,this),this.stylesheetElement=this.createStylesheetElement(),this.progressElement=this.createProgressElement()}var r;return r=300,e.defaultCSS=".turbolinks-progress-bar {\n position: fixed;\n display: block;\n top: 0;\n left: 0;\n height: 3px;\n background: #0076ff;\n z-index: 9999;\n transition: width "+r+"ms ease-out, opacity "+r/2+"ms "+r/2+"ms ease-in;\n transform: translate3d(0, 0, 0);\n}",e.prototype.show=function(){return this.visible?void 0:(this.visible=!0,this.installStylesheetElement(),this.installProgressElement(),this.startTrickling())},e.prototype.hide=function(){return this.visible&&!this.hiding?(this.hiding=!0,this.fadeProgressElement(function(t){return function(){return t.uninstallProgressElement(),t.stopTrickling(),t.visible=!1,t.hiding=!1}}(this))):void 0},e.prototype.setValue=function(t){return this.value=t,this.refresh()},e.prototype.installStylesheetElement=function(){return document.head.insertBefore(this.stylesheetElement,document.head.firstChild)},e.prototype.installProgressElement=function(){return this.progressElement.style.width=0,this.progressElement.style.opacity=1,document.documentElement.insertBefore(this.progressElement,document.body),this.refresh()},e.prototype.fadeProgressElement=function(t){return this.progressElement.style.opacity=0,setTimeout(t,1.5*r)},e.prototype.uninstallProgressElement=function(){return this.progressElement.parentNode?document.documentElement.removeChild(this.progressElement):void 0},e.prototype.startTrickling=function(){return null!=this.trickleInterval?this.trickleInterval:this.trickleInterval=setInterval(this.trickle,r)},e.prototype.stopTrickling=function(){return clearInterval(this.trickleInterval),this.trickleInterval=null},e.prototype.trickle=function(){return this.setValue(this.value+Math.random()/100)},e.prototype.refresh=function(){return requestAnimationFrame(function(t){return function(){return t.progressElement.style.width=10+90*t.value+"%"}}(this))},e.prototype.createStylesheetElement=function(){var t;return t=document.createElement("style"),t.type="text/css",t.textContent=this.constructor.defaultCSS,t},e.prototype.createProgressElement=function(){var t;return t=document.createElement("div"),t.className="turbolinks-progress-bar",t},e}()}.call(this),function(){var t=function(t,e){return function(){return t.apply(e,arguments)}};e.BrowserAdapter=function(){function r(r){this.controller=r,this.showProgressBar=t(this.showProgressBar,this),this.progressBar=new e.ProgressBar}var n,o,i;return i=e.HttpRequest,n=i.NETWORK_FAILURE,o=i.TIMEOUT_FAILURE,r.prototype.visitProposedToLocationWithAction=function(t,e){return this.controller.startVisitToLocationWithAction(t,e)},r.prototype.visitStarted=function(t){return t.issueRequest(),t.changeHistory(),t.loadCachedSnapshot()},r.prototype.visitRequestStarted=function(t){return this.progressBar.setValue(0),t.hasCachedSnapshot()||"restore"!==t.action?this.showProgressBarAfterDelay():this.showProgressBar()},r.prototype.visitRequestProgressed=function(t){return this.progressBar.setValue(t.progress)},r.prototype.visitRequestCompleted=function(t){return t.loadResponse()},r.prototype.visitRequestFailedWithStatusCode=function(t,e){switch(e){case n:case o:return this.reload();default:return t.loadResponse()}},r.prototype.visitRequestFinished=function(t){return this.hideProgressBar()},r.prototype.visitCompleted=function(t){return t.followRedirect()},r.prototype.pageInvalidated=function(){return this.reload()},r.prototype.showProgressBarAfterDelay=function(){return this.progressBarTimeout=setTimeout(this.showProgressBar,this.controller.progressBarDelay)},r.prototype.showProgressBar=function(){return this.progressBar.show()},r.prototype.hideProgressBar=function(){return this.progressBar.hide(),clearTimeout(this.progressBarTimeout)},r.prototype.reload=function(){return window.location.reload()},r}()}.call(this),function(){var t=function(t,e){return function(){return t.apply(e,arguments)}};e.History=function(){function r(e){this.delegate=e,this.onPageLoad=t(this.onPageLoad,this),this.onPopState=t(this.onPopState,this)}return r.prototype.start=function(){return this.started?void 0:(addEventListener("popstate",this.onPopState,!1),addEventListener("load",this.onPageLoad,!1),this.started=!0)},r.prototype.stop=function(){return this.started?(removeEventListener("popstate",this.onPopState,!1),removeEventListener("load",this.onPageLoad,!1),this.started=!1):void 0},r.prototype.push=function(t,r){return t=e.Location.wrap(t),this.update("push",t,r)},r.prototype.replace=function(t,r){return t=e.Location.wrap(t),this.update("replace",t,r)},r.prototype.onPopState=function(t){var r,n,o,i;return this.shouldHandlePopState()&&(i=null!=(n=t.state)?n.turbolinks:void 0)?(r=e.Location.wrap(window.location),o=i.restorationIdentifier,this.delegate.historyPoppedToLocationWithRestorationIdentifier(r,o)):void 0},r.prototype.onPageLoad=function(t){return e.defer(function(t){return function(){return t.pageLoaded=!0}}(this))},r.prototype.shouldHandlePopState=function(){return this.pageIsLoaded()},r.prototype.pageIsLoaded=function(){return this.pageLoaded||"complete"===document.readyState},r.prototype.update=function(t,e,r){var n;return n={turbolinks:{restorationIdentifier:r}},history[t+"State"](n,null,e)},r}()}.call(this),function(){e.HeadDetails=function(){function t(t){var e,r,n,s,a,u;for(this.elements={},n=0,a=t.length;a>n;n++)u=t[n],u.nodeType===Node.ELEMENT_NODE&&(s=u.outerHTML,r=null!=(e=this.elements)[s]?e[s]:e[s]={type:i(u),tracked:o(u),elements:[]},r.elements.push(u))}var e,r,n,o,i;return t.fromHeadElement=function(t){var e;return new this(null!=(e=null!=t?t.childNodes:void 0)?e:[])},t.prototype.hasElementWithKey=function(t){return t in this.elements},t.prototype.getTrackedElementSignature=function(){var t,e;return function(){var r,n;r=this.elements,n=[];for(t in r)e=r[t].tracked,e&&n.push(t);return n}.call(this).join("")},t.prototype.getScriptElementsNotInDetails=function(t){return this.getElementsMatchingTypeNotInDetails("script",t)},t.prototype.getStylesheetElementsNotInDetails=function(t){return this.getElementsMatchingTypeNotInDetails("stylesheet",t)},t.prototype.getElementsMatchingTypeNotInDetails=function(t,e){var r,n,o,i,s,a;o=this.elements,s=[];for(n in o)i=o[n],a=i.type,r=i.elements,a!==t||e.hasElementWithKey(n)||s.push(r[0]);return s},t.prototype.getProvisionalElements=function(){var t,e,r,n,o,i,s;r=[],n=this.elements;for(e in n)o=n[e],s=o.type,i=o.tracked,t=o.elements,null!=s||i?t.length>1&&r.push.apply(r,t.slice(1)):r.push.apply(r,t);return r},t.prototype.getMetaValue=function(t){var e;return null!=(e=this.findMetaElementByName(t))?e.getAttribute("content"):void 0},t.prototype.findMetaElementByName=function(t){var r,n,o,i;r=void 0,i=this.elements;for(o in i)n=i[o].elements,e(n[0],t)&&(r=n[0]);return r},i=function(t){return r(t)?"script":n(t)?"stylesheet":void 0},o=function(t){return"reload"===t.getAttribute("data-turbolinks-track")},r=function(t){var e;return e=t.tagName.toLowerCase(),"script"===e},n=function(t){var e;return e=t.tagName.toLowerCase(),"style"===e||"link"===e&&"stylesheet"===t.getAttribute("rel")},e=function(t,e){var r;return r=t.tagName.toLowerCase(),"meta"===r&&t.getAttribute("name")===e},t}()}.call(this),function(){e.Snapshot=function(){function t(t,e){this.headDetails=t,this.bodyElement=e}return t.wrap=function(t){return t instanceof this?t:"string"==typeof t?this.fromHTMLString(t):this.fromHTMLElement(t)},t.fromHTMLString=function(t){var e;return e=document.createElement("html"),e.innerHTML=t,this.fromHTMLElement(e)},t.fromHTMLElement=function(t){var r,n,o,i;return o=t.querySelector("head"),r=null!=(i=t.querySelector("body"))?i:document.createElement("body"),n=e.HeadDetails.fromHeadElement(o),new this(n,r)},t.prototype.clone=function(){return new this.constructor(this.headDetails,this.bodyElement.cloneNode(!0))},t.prototype.getRootLocation=function(){var t,r;return r=null!=(t=this.getSetting("root"))?t:"/",new e.Location(r)},t.prototype.getCacheControlValue=function(){return this.getSetting("cache-control")},t.prototype.getElementForAnchor=function(t){try{return this.bodyElement.querySelector("[id='"+t+"'], a[name='"+t+"']")}catch(e){}},t.prototype.getPermanentElements=function(){return this.bodyElement.querySelectorAll("[id][data-turbolinks-permanent]")},t.prototype.getPermanentElementById=function(t){return this.bodyElement.querySelector("#"+t+"[data-turbolinks-permanent]")},t.prototype.getPermanentElementsPresentInSnapshot=function(t){var e,r,n,o,i;for(o=this.getPermanentElements(),i=[],r=0,n=o.length;n>r;r++)e=o[r],t.getPermanentElementById(e.id)&&i.push(e);return i},t.prototype.findFirstAutofocusableElement=function(){return this.bodyElement.querySelector("[autofocus]")},t.prototype.hasAnchor=function(t){return null!=this.getElementForAnchor(t)},t.prototype.isPreviewable=function(){return"no-preview"!==this.getCacheControlValue()},t.prototype.isCacheable=function(){return"no-cache"!==this.getCacheControlValue()},t.prototype.isVisitable=function(){return"reload"!==this.getSetting("visit-control")},t.prototype.getSetting=function(t){return this.headDetails.getMetaValue("turbolinks-"+t)},t}()}.call(this),function(){var t=[].slice;e.Renderer=function(){function e(){}var r;return e.render=function(){var e,r,n,o;return n=arguments[0],r=arguments[1],e=3<=arguments.length?t.call(arguments,2):[],o=function(t,e,r){r.prototype=t.prototype;var n=new r,o=t.apply(n,e);return Object(o)===o?o:n}(this,e,function(){}),o.delegate=n,o.render(r),o},e.prototype.renderView=function(t){return this.delegate.viewWillRender(this.newBody),t(),this.delegate.viewRendered(this.newBody)},e.prototype.invalidateView=function(){return this.delegate.viewInvalidated()},e.prototype.createScriptElement=function(t){var e;return"false"===t.getAttribute("data-turbolinks-eval")?t:(e=document.createElement("script"),e.textContent=t.textContent,e.async=!1,r(e,t),e)},r=function(t,e){var r,n,o,i,s,a,u;for(i=e.attributes,a=[],r=0,n=i.length;n>r;r++)s=i[r],o=s.name,u=s.value,a.push(t.setAttribute(o,u));return a},e}()}.call(this),function(){var t,r,n=function(t,e){function r(){this.constructor=t}for(var n in e)o.call(e,n)&&(t[n]=e[n]);return r.prototype=e.prototype,t.prototype=new r,t.__super__=e.prototype,t},o={}.hasOwnProperty;e.SnapshotRenderer=function(e){function o(t,e,r){this.currentSnapshot=t,this.newSnapshot=e,this.isPreview=r,this.currentHeadDetails=this.currentSnapshot.headDetails,this.newHeadDetails=this.newSnapshot.headDetails,this.currentBody=this.currentSnapshot.bodyElement,this.newBody=this.newSnapshot.bodyElement}return n(o,e),o.prototype.render=function(t){return this.shouldRender()?(this.mergeHead(),this.renderView(function(e){return function(){return e.replaceBody(),e.isPreview||e.focusFirstAutofocusableElement(),t()}}(this))):this.invalidateView()},o.prototype.mergeHead=function(){return this.copyNewHeadStylesheetElements(),this.copyNewHeadScriptElements(),this.removeCurrentHeadProvisionalElements(),this.copyNewHeadProvisionalElements()},o.prototype.replaceBody=function(){var t;return t=this.relocateCurrentBodyPermanentElements(),this.activateNewBodyScriptElements(),this.assignNewBody(),this.replacePlaceholderElementsWithClonedPermanentElements(t)},o.prototype.shouldRender=function(){return this.newSnapshot.isVisitable()&&this.trackedElementsAreIdentical()},o.prototype.trackedElementsAreIdentical=function(){return this.currentHeadDetails.getTrackedElementSignature()===this.newHeadDetails.getTrackedElementSignature()},o.prototype.copyNewHeadStylesheetElements=function(){var t,e,r,n,o;for(n=this.getNewHeadStylesheetElements(),o=[],e=0,r=n.length;r>e;e++)t=n[e],o.push(document.head.appendChild(t));return o},o.prototype.copyNewHeadScriptElements=function(){var t,e,r,n,o;for(n=this.getNewHeadScriptElements(),o=[],e=0,r=n.length;r>e;e++)t=n[e],o.push(document.head.appendChild(this.createScriptElement(t)));return o},o.prototype.removeCurrentHeadProvisionalElements=function(){var t,e,r,n,o;for(n=this.getCurrentHeadProvisionalElements(),o=[],e=0,r=n.length;r>e;e++)t=n[e],o.push(document.head.removeChild(t));return o},o.prototype.copyNewHeadProvisionalElements=function(){var t,e,r,n,o;for(n=this.getNewHeadProvisionalElements(),o=[],e=0,r=n.length;r>e;e++)t=n[e],o.push(document.head.appendChild(t));return o},o.prototype.relocateCurrentBodyPermanentElements=function(){var e,n,o,i,s,a,u;for(a=this.getCurrentBodyPermanentElements(),u=[],e=0,n=a.length;n>e;e++)i=a[e],s=t(i),o=this.newSnapshot.getPermanentElementById(i.id),r(i,s.element),r(o,i),u.push(s);return u},o.prototype.replacePlaceholderElementsWithClonedPermanentElements=function(t){var e,n,o,i,s,a,u;for(u=[],o=0,i=t.length;i>o;o++)a=t[o],n=a.element,s=a.permanentElement,e=s.cloneNode(!0),u.push(r(n,e));return u},o.prototype.activateNewBodyScriptElements=function(){var t,e,n,o,i,s;for(i=this.getNewBodyScriptElements(),s=[],e=0,o=i.length;o>e;e++)n=i[e],t=this.createScriptElement(n),s.push(r(n,t));return s},o.prototype.assignNewBody=function(){return document.body=this.newBody},o.prototype.focusFirstAutofocusableElement=function(){var t;return null!=(t=this.newSnapshot.findFirstAutofocusableElement())?t.focus():void 0},o.prototype.getNewHeadStylesheetElements=function(){return this.newHeadDetails.getStylesheetElementsNotInDetails(this.currentHeadDetails)},o.prototype.getNewHeadScriptElements=function(){return this.newHeadDetails.getScriptElementsNotInDetails(this.currentHeadDetails)},o.prototype.getCurrentHeadProvisionalElements=function(){return this.currentHeadDetails.getProvisionalElements()},o.prototype.getNewHeadProvisionalElements=function(){return this.newHeadDetails.getProvisionalElements()},o.prototype.getCurrentBodyPermanentElements=function(){return this.currentSnapshot.getPermanentElementsPresentInSnapshot(this.newSnapshot)},o.prototype.getNewBodyScriptElements=function(){return this.newBody.querySelectorAll("script")},o}(e.Renderer),t=function(t){var e;return e=document.createElement("meta"),e.setAttribute("name","turbolinks-permanent-placeholder"),e.setAttribute("content",t.id),{element:e,permanentElement:t}},r=function(t,e){var r;return(r=t.parentNode)?r.replaceChild(e,t):void 0}}.call(this),function(){var t=function(t,e){function n(){this.constructor=t}for(var o in e)r.call(e,o)&&(t[o]=e[o]);return n.prototype=e.prototype,t.prototype=new n,t.__super__=e.prototype,t},r={}.hasOwnProperty;e.ErrorRenderer=function(e){function r(t){var e;e=document.createElement("html"),e.innerHTML=t,this.newHead=e.querySelector("head"),this.newBody=e.querySelector("body")}return t(r,e),r.prototype.render=function(t){return this.renderView(function(e){return function(){return e.replaceHeadAndBody(),e.activateBodyScriptElements(),t()}}(this))},r.prototype.replaceHeadAndBody=function(){var t,e;return e=document.head,t=document.body,e.parentNode.replaceChild(this.newHead,e),t.parentNode.replaceChild(this.newBody,t)},r.prototype.activateBodyScriptElements=function(){var t,e,r,n,o,i;for(n=this.getScriptElements(),i=[],e=0,r=n.length;r>e;e++)o=n[e],t=this.createScriptElement(o),i.push(o.parentNode.replaceChild(t,o));return i},r.prototype.getScriptElements=function(){return document.documentElement.querySelectorAll("script")},r}(e.Renderer)}.call(this),function(){e.View=function(){function t(t){this.delegate=t,this.htmlElement=document.documentElement}return t.prototype.getRootLocation=function(){return this.getSnapshot().getRootLocation()},t.prototype.getElementForAnchor=function(t){return this.getSnapshot().getElementForAnchor(t)},t.prototype.getSnapshot=function(){return e.Snapshot.fromHTMLElement(this.htmlElement)},t.prototype.render=function(t,e){var r,n,o;return o=t.snapshot,r=t.error,n=t.isPreview,this.markAsPreview(n),null!=o?this.renderSnapshot(o,n,e):this.renderError(r,e)},t.prototype.markAsPreview=function(t){return t?this.htmlElement.setAttribute("data-turbolinks-preview",""):this.htmlElement.removeAttribute("data-turbolinks-preview")},t.prototype.renderSnapshot=function(t,r,n){return e.SnapshotRenderer.render(this.delegate,n,this.getSnapshot(),e.Snapshot.wrap(t),r)},t.prototype.renderError=function(t,r){return e.ErrorRenderer.render(this.delegate,r,t)},t}()}.call(this),function(){var t=function(t,e){return function(){return t.apply(e,arguments)}};e.ScrollManager=function(){function r(r){this.delegate=r,this.onScroll=t(this.onScroll,this),this.onScroll=e.throttle(this.onScroll)}return r.prototype.start=function(){return this.started?void 0:(addEventListener("scroll",this.onScroll,!1),this.onScroll(),this.started=!0)},r.prototype.stop=function(){return this.started?(removeEventListener("scroll",this.onScroll,!1),this.started=!1):void 0},r.prototype.scrollToElement=function(t){return t.scrollIntoView()},r.prototype.scrollToPosition=function(t){var e,r;return e=t.x,r=t.y,window.scrollTo(e,r)},r.prototype.onScroll=function(t){return this.updatePosition({x:window.pageXOffset,y:window.pageYOffset})},r.prototype.updatePosition=function(t){var e;return this.position=t,null!=(e=this.delegate)?e.scrollPositionChanged(this.position):void 0},r}()}.call(this),function(){e.SnapshotCache=function(){function t(t){this.size=t,this.keys=[],this.snapshots={}}var r;return t.prototype.has=function(t){var e;return e=r(t),e in this.snapshots},t.prototype.get=function(t){var e;if(this.has(t))return e=this.read(t),this.touch(t),e},t.prototype.put=function(t,e){return this.write(t,e),this.touch(t),e},t.prototype.read=function(t){var e;return e=r(t),this.snapshots[e]},t.prototype.write=function(t,e){var n;return n=r(t),this.snapshots[n]=e},t.prototype.touch=function(t){var e,n;return n=r(t),e=this.keys.indexOf(n),e>-1&&this.keys.splice(e,1),this.keys.unshift(n),this.trim()},t.prototype.trim=function(){var t,e,r,n,o;for(n=this.keys.splice(this.size),o=[],t=0,r=n.length;r>t;t++)e=n[t],o.push(delete this.snapshots[e]);return o},r=function(t){return e.Location.wrap(t).toCacheKey()},t}()}.call(this),function(){var t=function(t,e){return function(){return t.apply(e,arguments)}};e.Visit=function(){function r(r,n,o){this.controller=r,this.action=o,this.performScroll=t(this.performScroll,this),this.identifier=e.uuid(),this.location=e.Location.wrap(n),this.adapter=this.controller.adapter,this.state="initialized",this.timingMetrics={}}var n;return r.prototype.start=function(){return"initialized"===this.state?(this.recordTimingMetric("visitStart"),this.state="started",this.adapter.visitStarted(this)):void 0},r.prototype.cancel=function(){var t;return"started"===this.state?(null!=(t=this.request)&&t.cancel(),this.cancelRender(),this.state="canceled"):void 0},r.prototype.complete=function(){var t;return"started"===this.state?(this.recordTimingMetric("visitEnd"),this.state="completed","function"==typeof(t=this.adapter).visitCompleted&&t.visitCompleted(this),this.controller.visitCompleted(this)):void 0},r.prototype.fail=function(){var t;return"started"===this.state?(this.state="failed","function"==typeof(t=this.adapter).visitFailed?t.visitFailed(this):void 0):void 0},r.prototype.changeHistory=function(){var t,e;return this.historyChanged?void 0:(t=this.location.isEqualTo(this.referrer)?"replace":this.action,e=n(t),this.controller[e](this.location,this.restorationIdentifier),this.historyChanged=!0)},r.prototype.issueRequest=function(){return this.shouldIssueRequest()&&null==this.request?(this.progress=0,this.request=new e.HttpRequest(this,this.location,this.referrer),this.request.send()):void 0},r.prototype.getCachedSnapshot=function(){var t;return!(t=this.controller.getCachedSnapshotForLocation(this.location))||null!=this.location.anchor&&!t.hasAnchor(this.location.anchor)||"restore"!==this.action&&!t.isPreviewable()?void 0:t},r.prototype.hasCachedSnapshot=function(){return null!=this.getCachedSnapshot()},r.prototype.loadCachedSnapshot=function(){var t,e;return(e=this.getCachedSnapshot())?(t=this.shouldIssueRequest(),this.render(function(){var r;return this.cacheSnapshot(),this.controller.render({snapshot:e,isPreview:t},this.performScroll),"function"==typeof(r=this.adapter).visitRendered&&r.visitRendered(this),t?void 0:this.complete()})):void 0},r.prototype.loadResponse=function(){return null!=this.response?this.render(function(){var t,e;return this.cacheSnapshot(),this.request.failed?(this.controller.render({error:this.response},this.performScroll),"function"==typeof(t=this.adapter).visitRendered&&t.visitRendered(this),this.fail()):(this.controller.render({snapshot:this.response},this.performScroll),"function"==typeof(e=this.adapter).visitRendered&&e.visitRendered(this),this.complete())}):void 0},r.prototype.followRedirect=function(){return this.redirectedToLocation&&!this.followedRedirect?(this.location=this.redirectedToLocation,this.controller.replaceHistoryWithLocationAndRestorationIdentifier(this.redirectedToLocation,this.restorationIdentifier),this.followedRedirect=!0):void 0},r.prototype.requestStarted=function(){var t;return this.recordTimingMetric("requestStart"),"function"==typeof(t=this.adapter).visitRequestStarted?t.visitRequestStarted(this):void 0},r.prototype.requestProgressed=function(t){var e;return this.progress=t,"function"==typeof(e=this.adapter).visitRequestProgressed?e.visitRequestProgressed(this):void 0},r.prototype.requestCompletedWithResponse=function(t,r){return this.response=t,null!=r&&(this.redirectedToLocation=e.Location.wrap(r)),this.adapter.visitRequestCompleted(this)},r.prototype.requestFailedWithStatusCode=function(t,e){return this.response=e,this.adapter.visitRequestFailedWithStatusCode(this,t)},r.prototype.requestFinished=function(){var t;return this.recordTimingMetric("requestEnd"),"function"==typeof(t=this.adapter).visitRequestFinished?t.visitRequestFinished(this):void 0},r.prototype.performScroll=function(){return this.scrolled?void 0:("restore"===this.action?this.scrollToRestoredPosition()||this.scrollToTop():this.scrollToAnchor()||this.scrollToTop(),this.scrolled=!0)},r.prototype.scrollToRestoredPosition=function(){var t,e;return t=null!=(e=this.restorationData)?e.scrollPosition:void 0,null!=t?(this.controller.scrollToPosition(t),!0):void 0},r.prototype.scrollToAnchor=function(){return null!=this.location.anchor?(this.controller.scrollToAnchor(this.location.anchor),!0):void 0},r.prototype.scrollToTop=function(){return this.controller.scrollToPosition({x:0,y:0})},r.prototype.recordTimingMetric=function(t){var e;return null!=(e=this.timingMetrics)[t]?e[t]:e[t]=(new Date).getTime()},r.prototype.getTimingMetrics=function(){return e.copyObject(this.timingMetrics)},n=function(t){switch(t){case"replace":return"replaceHistoryWithLocationAndRestorationIdentifier";case"advance":case"restore":return"pushHistoryWithLocationAndRestorationIdentifier"}},r.prototype.shouldIssueRequest=function(){return"restore"===this.action?!this.hasCachedSnapshot():!0},r.prototype.cacheSnapshot=function(){return this.snapshotCached?void 0:(this.controller.cacheSnapshot(),this.snapshotCached=!0)},r.prototype.render=function(t){return this.cancelRender(),this.frame=requestAnimationFrame(function(e){return function(){return e.frame=null,t.call(e)}}(this))},r.prototype.cancelRender=function(){return this.frame?cancelAnimationFrame(this.frame):void 0},r}()}.call(this),function(){var t=function(t,e){return function(){return t.apply(e,arguments)}};e.Controller=function(){function r(){this.clickBubbled=t(this.clickBubbled,this),this.clickCaptured=t(this.clickCaptured,this),this.pageLoaded=t(this.pageLoaded,this),this.history=new e.History(this),this.view=new e.View(this),this.scrollManager=new e.ScrollManager(this),this.restorationData={},this.clearCache(),this.setProgressBarDelay(500)}return r.prototype.start=function(){return e.supported&&!this.started?(addEventListener("click",this.clickCaptured,!0),addEventListener("DOMContentLoaded",this.pageLoaded,!1),this.scrollManager.start(),this.startHistory(),this.started=!0,this.enabled=!0):void 0},r.prototype.disable=function(){return this.enabled=!1},r.prototype.stop=function(){return this.started?(removeEventListener("click",this.clickCaptured,!0),removeEventListener("DOMContentLoaded",this.pageLoaded,!1),this.scrollManager.stop(),this.stopHistory(),this.started=!1):void 0},r.prototype.clearCache=function(){return this.cache=new e.SnapshotCache(10)},r.prototype.visit=function(t,r){var n,o;return null==r&&(r={}),t=e.Location.wrap(t),this.applicationAllowsVisitingLocation(t)?this.locationIsVisitable(t)?(n=null!=(o=r.action)?o:"advance",this.adapter.visitProposedToLocationWithAction(t,n)):window.location=t:void 0},r.prototype.startVisitToLocationWithAction=function(t,r,n){var o;return e.supported?(o=this.getRestorationDataForIdentifier(n),this.startVisit(t,r,{restorationData:o})):window.location=t},r.prototype.setProgressBarDelay=function(t){return this.progressBarDelay=t},r.prototype.startHistory=function(){return this.location=e.Location.wrap(window.location),this.restorationIdentifier=e.uuid(),this.history.start(),this.history.replace(this.location,this.restorationIdentifier)},r.prototype.stopHistory=function(){return this.history.stop()},r.prototype.pushHistoryWithLocationAndRestorationIdentifier=function(t,r){return this.restorationIdentifier=r,this.location=e.Location.wrap(t),this.history.push(this.location,this.restorationIdentifier)},r.prototype.replaceHistoryWithLocationAndRestorationIdentifier=function(t,r){return this.restorationIdentifier=r,this.location=e.Location.wrap(t),this.history.replace(this.location,this.restorationIdentifier)},r.prototype.historyPoppedToLocationWithRestorationIdentifier=function(t,r){var n;return this.restorationIdentifier=r,this.enabled?(n=this.getRestorationDataForIdentifier(this.restorationIdentifier),this.startVisit(t,"restore",{restorationIdentifier:this.restorationIdentifier,restorationData:n,historyChanged:!0}),this.location=e.Location.wrap(t)):this.adapter.pageInvalidated()},r.prototype.getCachedSnapshotForLocation=function(t){var e;return null!=(e=this.cache.get(t))?e.clone():void 0},r.prototype.shouldCacheSnapshot=function(){return this.view.getSnapshot().isCacheable();
|
|
},r.prototype.cacheSnapshot=function(){var t,r;return this.shouldCacheSnapshot()?(this.notifyApplicationBeforeCachingSnapshot(),r=this.view.getSnapshot(),t=this.lastRenderedLocation,e.defer(function(e){return function(){return e.cache.put(t,r.clone())}}(this))):void 0},r.prototype.scrollToAnchor=function(t){var e;return(e=this.view.getElementForAnchor(t))?this.scrollToElement(e):this.scrollToPosition({x:0,y:0})},r.prototype.scrollToElement=function(t){return this.scrollManager.scrollToElement(t)},r.prototype.scrollToPosition=function(t){return this.scrollManager.scrollToPosition(t)},r.prototype.scrollPositionChanged=function(t){var e;return e=this.getCurrentRestorationData(),e.scrollPosition=t},r.prototype.render=function(t,e){return this.view.render(t,e)},r.prototype.viewInvalidated=function(){return this.adapter.pageInvalidated()},r.prototype.viewWillRender=function(t){return this.notifyApplicationBeforeRender(t)},r.prototype.viewRendered=function(){return this.lastRenderedLocation=this.currentVisit.location,this.notifyApplicationAfterRender()},r.prototype.pageLoaded=function(){return this.lastRenderedLocation=this.location,this.notifyApplicationAfterPageLoad()},r.prototype.clickCaptured=function(){return removeEventListener("click",this.clickBubbled,!1),addEventListener("click",this.clickBubbled,!1)},r.prototype.clickBubbled=function(t){var e,r,n;return this.enabled&&this.clickEventIsSignificant(t)&&(r=this.getVisitableLinkForNode(t.target))&&(n=this.getVisitableLocationForLink(r))&&this.applicationAllowsFollowingLinkToLocation(r,n)?(t.preventDefault(),e=this.getActionForLink(r),this.visit(n,{action:e})):void 0},r.prototype.applicationAllowsFollowingLinkToLocation=function(t,e){var r;return r=this.notifyApplicationAfterClickingLinkToLocation(t,e),!r.defaultPrevented},r.prototype.applicationAllowsVisitingLocation=function(t){var e;return e=this.notifyApplicationBeforeVisitingLocation(t),!e.defaultPrevented},r.prototype.notifyApplicationAfterClickingLinkToLocation=function(t,r){return e.dispatch("turbolinks:click",{target:t,data:{url:r.absoluteURL},cancelable:!0})},r.prototype.notifyApplicationBeforeVisitingLocation=function(t){return e.dispatch("turbolinks:before-visit",{data:{url:t.absoluteURL},cancelable:!0})},r.prototype.notifyApplicationAfterVisitingLocation=function(t){return e.dispatch("turbolinks:visit",{data:{url:t.absoluteURL}})},r.prototype.notifyApplicationBeforeCachingSnapshot=function(){return e.dispatch("turbolinks:before-cache")},r.prototype.notifyApplicationBeforeRender=function(t){return e.dispatch("turbolinks:before-render",{data:{newBody:t}})},r.prototype.notifyApplicationAfterRender=function(){return e.dispatch("turbolinks:render")},r.prototype.notifyApplicationAfterPageLoad=function(t){return null==t&&(t={}),e.dispatch("turbolinks:load",{data:{url:this.location.absoluteURL,timing:t}})},r.prototype.startVisit=function(t,e,r){var n;return null!=(n=this.currentVisit)&&n.cancel(),this.currentVisit=this.createVisit(t,e,r),this.currentVisit.start(),this.notifyApplicationAfterVisitingLocation(t)},r.prototype.createVisit=function(t,r,n){var o,i,s,a,u;return i=null!=n?n:{},a=i.restorationIdentifier,s=i.restorationData,o=i.historyChanged,u=new e.Visit(this,t,r),u.restorationIdentifier=null!=a?a:e.uuid(),u.restorationData=e.copyObject(s),u.historyChanged=o,u.referrer=this.location,u},r.prototype.visitCompleted=function(t){return this.notifyApplicationAfterPageLoad(t.getTimingMetrics())},r.prototype.clickEventIsSignificant=function(t){return!(t.defaultPrevented||t.target.isContentEditable||t.which>1||t.altKey||t.ctrlKey||t.metaKey||t.shiftKey)},r.prototype.getVisitableLinkForNode=function(t){return this.nodeIsVisitable(t)?e.closest(t,"a[href]:not([target]):not([download])"):void 0},r.prototype.getVisitableLocationForLink=function(t){var r;return r=new e.Location(t.getAttribute("href")),this.locationIsVisitable(r)?r:void 0},r.prototype.getActionForLink=function(t){var e;return null!=(e=t.getAttribute("data-turbolinks-action"))?e:"advance"},r.prototype.nodeIsVisitable=function(t){var r;return(r=e.closest(t,"[data-turbolinks]"))?"false"!==r.getAttribute("data-turbolinks"):!0},r.prototype.locationIsVisitable=function(t){return t.isPrefixedBy(this.view.getRootLocation())&&t.isHTML()},r.prototype.getCurrentRestorationData=function(){return this.getRestorationDataForIdentifier(this.restorationIdentifier)},r.prototype.getRestorationDataForIdentifier=function(t){var e;return null!=(e=this.restorationData)[t]?e[t]:e[t]={}},r}()}.call(this),function(){!function(){var t,e;if((t=e=document.currentScript)&&!e.hasAttribute("data-turbolinks-suppress-warning"))for(;t=t.parentNode;)if(t===document.body)return console.warn("You are loading Turbolinks from a <script> element inside the <body> element. This is probably not what you meant to do!\n\nLoad your application\u2019s JavaScript bundle inside the <head> element instead. <script> elements in <body> are evaluated with each page change.\n\nFor more information, see: https://github.com/turbolinks/turbolinks#working-with-script-elements\n\n\u2014\u2014\nSuppress this warning by adding a `data-turbolinks-suppress-warning` attribute to: %s",e.outerHTML)}()}.call(this),function(){var t,r,n;e.start=function(){return r()?(null==e.controller&&(e.controller=t()),e.controller.start()):void 0},r=function(){return null==window.Turbolinks&&(window.Turbolinks=e),n()},t=function(){var t;return t=new e.Controller,t.adapter=new e.BrowserAdapter(t),t},n=function(){return window.Turbolinks===e},n()&&e.start()}.call(this)}).call(this),"object"==typeof module&&module.exports?module.exports=e:"function"==typeof define&&define.amd&&define(e)}).call(this);
|
|
/*!
|
|
* jQuery JavaScript Library v3.4.1
|
|
* https://jquery.com/
|
|
*
|
|
* Includes Sizzle.js
|
|
* https://sizzlejs.com/
|
|
*
|
|
* Copyright JS Foundation and other contributors
|
|
* Released under the MIT license
|
|
* https://jquery.org/license
|
|
*
|
|
* Date: 2019-05-01T21:04Z
|
|
*/
|
|
|
|
( function( global, factory ) {
|
|
|
|
"use strict";
|
|
|
|
if ( typeof module === "object" && typeof module.exports === "object" ) {
|
|
|
|
// For CommonJS and CommonJS-like environments where a proper `window`
|
|
// is present, execute the factory and get jQuery.
|
|
// For environments that do not have a `window` with a `document`
|
|
// (such as Node.js), expose a factory as module.exports.
|
|
// This accentuates the need for the creation of a real `window`.
|
|
// e.g. var jQuery = require("jquery")(window);
|
|
// See ticket #14549 for more info.
|
|
module.exports = global.document ?
|
|
factory( global, true ) :
|
|
function( w ) {
|
|
if ( !w.document ) {
|
|
throw new Error( "jQuery requires a window with a document" );
|
|
}
|
|
return factory( w );
|
|
};
|
|
} else {
|
|
factory( global );
|
|
}
|
|
|
|
// Pass this if window is not defined yet
|
|
} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
|
|
|
|
// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1
|
|
// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode
|
|
// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common
|
|
// enough that all such attempts are guarded in a try block.
|
|
"use strict";
|
|
|
|
var arr = [];
|
|
|
|
var document = window.document;
|
|
|
|
var getProto = Object.getPrototypeOf;
|
|
|
|
var slice = arr.slice;
|
|
|
|
var concat = arr.concat;
|
|
|
|
var push = arr.push;
|
|
|
|
var indexOf = arr.indexOf;
|
|
|
|
var class2type = {};
|
|
|
|
var toString = class2type.toString;
|
|
|
|
var hasOwn = class2type.hasOwnProperty;
|
|
|
|
var fnToString = hasOwn.toString;
|
|
|
|
var ObjectFunctionString = fnToString.call( Object );
|
|
|
|
var support = {};
|
|
|
|
var isFunction = function isFunction( obj ) {
|
|
|
|
// Support: Chrome <=57, Firefox <=52
|
|
// In some browsers, typeof returns "function" for HTML <object> elements
|
|
// (i.e., `typeof document.createElement( "object" ) === "function"`).
|
|
// We don't want to classify *any* DOM node as a function.
|
|
return typeof obj === "function" && typeof obj.nodeType !== "number";
|
|
};
|
|
|
|
|
|
var isWindow = function isWindow( obj ) {
|
|
return obj != null && obj === obj.window;
|
|
};
|
|
|
|
|
|
|
|
|
|
var preservedScriptAttributes = {
|
|
type: true,
|
|
src: true,
|
|
nonce: true,
|
|
noModule: true
|
|
};
|
|
|
|
function DOMEval( code, node, doc ) {
|
|
doc = doc || document;
|
|
|
|
var i, val,
|
|
script = doc.createElement( "script" );
|
|
|
|
script.text = code;
|
|
if ( node ) {
|
|
for ( i in preservedScriptAttributes ) {
|
|
|
|
// Support: Firefox 64+, Edge 18+
|
|
// Some browsers don't support the "nonce" property on scripts.
|
|
// On the other hand, just using `getAttribute` is not enough as
|
|
// the `nonce` attribute is reset to an empty string whenever it
|
|
// becomes browsing-context connected.
|
|
// See https://github.com/whatwg/html/issues/2369
|
|
// See https://html.spec.whatwg.org/#nonce-attributes
|
|
// The `node.getAttribute` check was added for the sake of
|
|
// `jQuery.globalEval` so that it can fake a nonce-containing node
|
|
// via an object.
|
|
val = node[ i ] || node.getAttribute && node.getAttribute( i );
|
|
if ( val ) {
|
|
script.setAttribute( i, val );
|
|
}
|
|
}
|
|
}
|
|
doc.head.appendChild( script ).parentNode.removeChild( script );
|
|
}
|
|
|
|
|
|
function toType( obj ) {
|
|
if ( obj == null ) {
|
|
return obj + "";
|
|
}
|
|
|
|
// Support: Android <=2.3 only (functionish RegExp)
|
|
return typeof obj === "object" || typeof obj === "function" ?
|
|
class2type[ toString.call( obj ) ] || "object" :
|
|
typeof obj;
|
|
}
|
|
/* global Symbol */
|
|
// Defining this global in .eslintrc.json would create a danger of using the global
|
|
// unguarded in another place, it seems safer to define global only for this module
|
|
|
|
|
|
|
|
var
|
|
version = "3.4.1",
|
|
|
|
// Define a local copy of jQuery
|
|
jQuery = function( selector, context ) {
|
|
|
|
// The jQuery object is actually just the init constructor 'enhanced'
|
|
// Need init if jQuery is called (just allow error to be thrown if not included)
|
|
return new jQuery.fn.init( selector, context );
|
|
},
|
|
|
|
// Support: Android <=4.0 only
|
|
// Make sure we trim BOM and NBSP
|
|
rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;
|
|
|
|
jQuery.fn = jQuery.prototype = {
|
|
|
|
// The current version of jQuery being used
|
|
jquery: version,
|
|
|
|
constructor: jQuery,
|
|
|
|
// The default length of a jQuery object is 0
|
|
length: 0,
|
|
|
|
toArray: function() {
|
|
return slice.call( this );
|
|
},
|
|
|
|
// Get the Nth element in the matched element set OR
|
|
// Get the whole matched element set as a clean array
|
|
get: function( num ) {
|
|
|
|
// Return all the elements in a clean array
|
|
if ( num == null ) {
|
|
return slice.call( this );
|
|
}
|
|
|
|
// Return just the one element from the set
|
|
return num < 0 ? this[ num + this.length ] : this[ num ];
|
|
},
|
|
|
|
// Take an array of elements and push it onto the stack
|
|
// (returning the new matched element set)
|
|
pushStack: function( elems ) {
|
|
|
|
// Build a new jQuery matched element set
|
|
var ret = jQuery.merge( this.constructor(), elems );
|
|
|
|
// Add the old object onto the stack (as a reference)
|
|
ret.prevObject = this;
|
|
|
|
// Return the newly-formed element set
|
|
return ret;
|
|
},
|
|
|
|
// Execute a callback for every element in the matched set.
|
|
each: function( callback ) {
|
|
return jQuery.each( this, callback );
|
|
},
|
|
|
|
map: function( callback ) {
|
|
return this.pushStack( jQuery.map( this, function( elem, i ) {
|
|
return callback.call( elem, i, elem );
|
|
} ) );
|
|
},
|
|
|
|
slice: function() {
|
|
return this.pushStack( slice.apply( this, arguments ) );
|
|
},
|
|
|
|
first: function() {
|
|
return this.eq( 0 );
|
|
},
|
|
|
|
last: function() {
|
|
return this.eq( -1 );
|
|
},
|
|
|
|
eq: function( i ) {
|
|
var len = this.length,
|
|
j = +i + ( i < 0 ? len : 0 );
|
|
return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] );
|
|
},
|
|
|
|
end: function() {
|
|
return this.prevObject || this.constructor();
|
|
},
|
|
|
|
// For internal use only.
|
|
// Behaves like an Array's method, not like a jQuery method.
|
|
push: push,
|
|
sort: arr.sort,
|
|
splice: arr.splice
|
|
};
|
|
|
|
jQuery.extend = jQuery.fn.extend = function() {
|
|
var options, name, src, copy, copyIsArray, clone,
|
|
target = arguments[ 0 ] || {},
|
|
i = 1,
|
|
length = arguments.length,
|
|
deep = false;
|
|
|
|
// Handle a deep copy situation
|
|
if ( typeof target === "boolean" ) {
|
|
deep = target;
|
|
|
|
// Skip the boolean and the target
|
|
target = arguments[ i ] || {};
|
|
i++;
|
|
}
|
|
|
|
// Handle case when target is a string or something (possible in deep copy)
|
|
if ( typeof target !== "object" && !isFunction( target ) ) {
|
|
target = {};
|
|
}
|
|
|
|
// Extend jQuery itself if only one argument is passed
|
|
if ( i === length ) {
|
|
target = this;
|
|
i--;
|
|
}
|
|
|
|
for ( ; i < length; i++ ) {
|
|
|
|
// Only deal with non-null/undefined values
|
|
if ( ( options = arguments[ i ] ) != null ) {
|
|
|
|
// Extend the base object
|
|
for ( name in options ) {
|
|
copy = options[ name ];
|
|
|
|
// Prevent Object.prototype pollution
|
|
// Prevent never-ending loop
|
|
if ( name === "__proto__" || target === copy ) {
|
|
continue;
|
|
}
|
|
|
|
// Recurse if we're merging plain objects or arrays
|
|
if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
|
|
( copyIsArray = Array.isArray( copy ) ) ) ) {
|
|
src = target[ name ];
|
|
|
|
// Ensure proper type for the source value
|
|
if ( copyIsArray && !Array.isArray( src ) ) {
|
|
clone = [];
|
|
} else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) {
|
|
clone = {};
|
|
} else {
|
|
clone = src;
|
|
}
|
|
copyIsArray = false;
|
|
|
|
// Never move original objects, clone them
|
|
target[ name ] = jQuery.extend( deep, clone, copy );
|
|
|
|
// Don't bring in undefined values
|
|
} else if ( copy !== undefined ) {
|
|
target[ name ] = copy;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return the modified object
|
|
return target;
|
|
};
|
|
|
|
jQuery.extend( {
|
|
|
|
// Unique for each copy of jQuery on the page
|
|
expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),
|
|
|
|
// Assume jQuery is ready without the ready module
|
|
isReady: true,
|
|
|
|
error: function( msg ) {
|
|
throw new Error( msg );
|
|
},
|
|
|
|
noop: function() {},
|
|
|
|
isPlainObject: function( obj ) {
|
|
var proto, Ctor;
|
|
|
|
// Detect obvious negatives
|
|
// Use toString instead of jQuery.type to catch host objects
|
|
if ( !obj || toString.call( obj ) !== "[object Object]" ) {
|
|
return false;
|
|
}
|
|
|
|
proto = getProto( obj );
|
|
|
|
// Objects with no prototype (e.g., `Object.create( null )`) are plain
|
|
if ( !proto ) {
|
|
return true;
|
|
}
|
|
|
|
// Objects with prototype are plain iff they were constructed by a global Object function
|
|
Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor;
|
|
return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;
|
|
},
|
|
|
|
isEmptyObject: function( obj ) {
|
|
var name;
|
|
|
|
for ( name in obj ) {
|
|
return false;
|
|
}
|
|
return true;
|
|
},
|
|
|
|
// Evaluates a script in a global context
|
|
globalEval: function( code, options ) {
|
|
DOMEval( code, { nonce: options && options.nonce } );
|
|
},
|
|
|
|
each: function( obj, callback ) {
|
|
var length, i = 0;
|
|
|
|
if ( isArrayLike( obj ) ) {
|
|
length = obj.length;
|
|
for ( ; i < length; i++ ) {
|
|
if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
for ( i in obj ) {
|
|
if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return obj;
|
|
},
|
|
|
|
// Support: Android <=4.0 only
|
|
trim: function( text ) {
|
|
return text == null ?
|
|
"" :
|
|
( text + "" ).replace( rtrim, "" );
|
|
},
|
|
|
|
// results is for internal usage only
|
|
makeArray: function( arr, results ) {
|
|
var ret = results || [];
|
|
|
|
if ( arr != null ) {
|
|
if ( isArrayLike( Object( arr ) ) ) {
|
|
jQuery.merge( ret,
|
|
typeof arr === "string" ?
|
|
[ arr ] : arr
|
|
);
|
|
} else {
|
|
push.call( ret, arr );
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
},
|
|
|
|
inArray: function( elem, arr, i ) {
|
|
return arr == null ? -1 : indexOf.call( arr, elem, i );
|
|
},
|
|
|
|
// Support: Android <=4.0 only, PhantomJS 1 only
|
|
// push.apply(_, arraylike) throws on ancient WebKit
|
|
merge: function( first, second ) {
|
|
var len = +second.length,
|
|
j = 0,
|
|
i = first.length;
|
|
|
|
for ( ; j < len; j++ ) {
|
|
first[ i++ ] = second[ j ];
|
|
}
|
|
|
|
first.length = i;
|
|
|
|
return first;
|
|
},
|
|
|
|
grep: function( elems, callback, invert ) {
|
|
var callbackInverse,
|
|
matches = [],
|
|
i = 0,
|
|
length = elems.length,
|
|
callbackExpect = !invert;
|
|
|
|
// Go through the array, only saving the items
|
|
// that pass the validator function
|
|
for ( ; i < length; i++ ) {
|
|
callbackInverse = !callback( elems[ i ], i );
|
|
if ( callbackInverse !== callbackExpect ) {
|
|
matches.push( elems[ i ] );
|
|
}
|
|
}
|
|
|
|
return matches;
|
|
},
|
|
|
|
// arg is for internal usage only
|
|
map: function( elems, callback, arg ) {
|
|
var length, value,
|
|
i = 0,
|
|
ret = [];
|
|
|
|
// Go through the array, translating each of the items to their new values
|
|
if ( isArrayLike( elems ) ) {
|
|
length = elems.length;
|
|
for ( ; i < length; i++ ) {
|
|
value = callback( elems[ i ], i, arg );
|
|
|
|
if ( value != null ) {
|
|
ret.push( value );
|
|
}
|
|
}
|
|
|
|
// Go through every key on the object,
|
|
} else {
|
|
for ( i in elems ) {
|
|
value = callback( elems[ i ], i, arg );
|
|
|
|
if ( value != null ) {
|
|
ret.push( value );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Flatten any nested arrays
|
|
return concat.apply( [], ret );
|
|
},
|
|
|
|
// A global GUID counter for objects
|
|
guid: 1,
|
|
|
|
// jQuery.support is not used in Core but other projects attach their
|
|
// properties to it so it needs to exist.
|
|
support: support
|
|
} );
|
|
|
|
if ( typeof Symbol === "function" ) {
|
|
jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ];
|
|
}
|
|
|
|
// Populate the class2type map
|
|
jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ),
|
|
function( i, name ) {
|
|
class2type[ "[object " + name + "]" ] = name.toLowerCase();
|
|
} );
|
|
|
|
function isArrayLike( obj ) {
|
|
|
|
// Support: real iOS 8.2 only (not reproducible in simulator)
|
|
// `in` check used to prevent JIT error (gh-2145)
|
|
// hasOwn isn't used here due to false negatives
|
|
// regarding Nodelist length in IE
|
|
var length = !!obj && "length" in obj && obj.length,
|
|
type = toType( obj );
|
|
|
|
if ( isFunction( obj ) || isWindow( obj ) ) {
|
|
return false;
|
|
}
|
|
|
|
return type === "array" || length === 0 ||
|
|
typeof length === "number" && length > 0 && ( length - 1 ) in obj;
|
|
}
|
|
var Sizzle =
|
|
/*!
|
|
* Sizzle CSS Selector Engine v2.3.4
|
|
* https://sizzlejs.com/
|
|
*
|
|
* Copyright JS Foundation and other contributors
|
|
* Released under the MIT license
|
|
* https://js.foundation/
|
|
*
|
|
* Date: 2019-04-08
|
|
*/
|
|
(function( window ) {
|
|
|
|
var i,
|
|
support,
|
|
Expr,
|
|
getText,
|
|
isXML,
|
|
tokenize,
|
|
compile,
|
|
select,
|
|
outermostContext,
|
|
sortInput,
|
|
hasDuplicate,
|
|
|
|
// Local document vars
|
|
setDocument,
|
|
document,
|
|
docElem,
|
|
documentIsHTML,
|
|
rbuggyQSA,
|
|
rbuggyMatches,
|
|
matches,
|
|
contains,
|
|
|
|
// Instance-specific data
|
|
expando = "sizzle" + 1 * new Date(),
|
|
preferredDoc = window.document,
|
|
dirruns = 0,
|
|
done = 0,
|
|
classCache = createCache(),
|
|
tokenCache = createCache(),
|
|
compilerCache = createCache(),
|
|
nonnativeSelectorCache = createCache(),
|
|
sortOrder = function( a, b ) {
|
|
if ( a === b ) {
|
|
hasDuplicate = true;
|
|
}
|
|
return 0;
|
|
},
|
|
|
|
// Instance methods
|
|
hasOwn = ({}).hasOwnProperty,
|
|
arr = [],
|
|
pop = arr.pop,
|
|
push_native = arr.push,
|
|
push = arr.push,
|
|
slice = arr.slice,
|
|
// Use a stripped-down indexOf as it's faster than native
|
|
// https://jsperf.com/thor-indexof-vs-for/5
|
|
indexOf = function( list, elem ) {
|
|
var i = 0,
|
|
len = list.length;
|
|
for ( ; i < len; i++ ) {
|
|
if ( list[i] === elem ) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
},
|
|
|
|
booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",
|
|
|
|
// Regular expressions
|
|
|
|
// http://www.w3.org/TR/css3-selectors/#whitespace
|
|
whitespace = "[\\x20\\t\\r\\n\\f]",
|
|
|
|
// http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
|
|
identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+",
|
|
|
|
// Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors
|
|
attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace +
|
|
// Operator (capture 2)
|
|
"*([*^$|!~]?=)" + whitespace +
|
|
// "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]"
|
|
"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace +
|
|
"*\\]",
|
|
|
|
pseudos = ":(" + identifier + ")(?:\\((" +
|
|
// To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
|
|
// 1. quoted (capture 3; capture 4 or capture 5)
|
|
"('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +
|
|
// 2. simple (capture 6)
|
|
"((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +
|
|
// 3. anything else (capture 2)
|
|
".*" +
|
|
")\\)|)",
|
|
|
|
// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
|
|
rwhitespace = new RegExp( whitespace + "+", "g" ),
|
|
rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
|
|
|
|
rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
|
|
rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ),
|
|
rdescend = new RegExp( whitespace + "|>" ),
|
|
|
|
rpseudo = new RegExp( pseudos ),
|
|
ridentifier = new RegExp( "^" + identifier + "$" ),
|
|
|
|
matchExpr = {
|
|
"ID": new RegExp( "^#(" + identifier + ")" ),
|
|
"CLASS": new RegExp( "^\\.(" + identifier + ")" ),
|
|
"TAG": new RegExp( "^(" + identifier + "|[*])" ),
|
|
"ATTR": new RegExp( "^" + attributes ),
|
|
"PSEUDO": new RegExp( "^" + pseudos ),
|
|
"CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
|
|
"*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
|
|
"*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
|
|
"bool": new RegExp( "^(?:" + booleans + ")$", "i" ),
|
|
// For use in libraries implementing .is()
|
|
// We use this for POS matching in `select`
|
|
"needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
|
|
whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
|
|
},
|
|
|
|
rhtml = /HTML$/i,
|
|
rinputs = /^(?:input|select|textarea|button)$/i,
|
|
rheader = /^h\d$/i,
|
|
|
|
rnative = /^[^{]+\{\s*\[native \w/,
|
|
|
|
// Easily-parseable/retrievable ID or TAG or CLASS selectors
|
|
rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
|
|
|
|
rsibling = /[+~]/,
|
|
|
|
// CSS escapes
|
|
// http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
|
|
runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ),
|
|
funescape = function( _, escaped, escapedWhitespace ) {
|
|
var high = "0x" + escaped - 0x10000;
|
|
// NaN means non-codepoint
|
|
// Support: Firefox<24
|
|
// Workaround erroneous numeric interpretation of +"0x"
|
|
return high !== high || escapedWhitespace ?
|
|
escaped :
|
|
high < 0 ?
|
|
// BMP codepoint
|
|
String.fromCharCode( high + 0x10000 ) :
|
|
// Supplemental Plane codepoint (surrogate pair)
|
|
String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
|
|
},
|
|
|
|
// CSS string/identifier serialization
|
|
// https://drafts.csswg.org/cssom/#common-serializing-idioms
|
|
rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,
|
|
fcssescape = function( ch, asCodePoint ) {
|
|
if ( asCodePoint ) {
|
|
|
|
// U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER
|
|
if ( ch === "\0" ) {
|
|
return "\uFFFD";
|
|
}
|
|
|
|
// Control characters and (dependent upon position) numbers get escaped as code points
|
|
return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " ";
|
|
}
|
|
|
|
// Other potentially-special ASCII characters get backslash-escaped
|
|
return "\\" + ch;
|
|
},
|
|
|
|
// Used for iframes
|
|
// See setDocument()
|
|
// Removing the function wrapper causes a "Permission Denied"
|
|
// error in IE
|
|
unloadHandler = function() {
|
|
setDocument();
|
|
},
|
|
|
|
inDisabledFieldset = addCombinator(
|
|
function( elem ) {
|
|
return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset";
|
|
},
|
|
{ dir: "parentNode", next: "legend" }
|
|
);
|
|
|
|
// Optimize for push.apply( _, NodeList )
|
|
try {
|
|
push.apply(
|
|
(arr = slice.call( preferredDoc.childNodes )),
|
|
preferredDoc.childNodes
|
|
);
|
|
// Support: Android<4.0
|
|
// Detect silently failing push.apply
|
|
arr[ preferredDoc.childNodes.length ].nodeType;
|
|
} catch ( e ) {
|
|
push = { apply: arr.length ?
|
|
|
|
// Leverage slice if possible
|
|
function( target, els ) {
|
|
push_native.apply( target, slice.call(els) );
|
|
} :
|
|
|
|
// Support: IE<9
|
|
// Otherwise append directly
|
|
function( target, els ) {
|
|
var j = target.length,
|
|
i = 0;
|
|
// Can't trust NodeList.length
|
|
while ( (target[j++] = els[i++]) ) {}
|
|
target.length = j - 1;
|
|
}
|
|
};
|
|
}
|
|
|
|
function Sizzle( selector, context, results, seed ) {
|
|
var m, i, elem, nid, match, groups, newSelector,
|
|
newContext = context && context.ownerDocument,
|
|
|
|
// nodeType defaults to 9, since context defaults to document
|
|
nodeType = context ? context.nodeType : 9;
|
|
|
|
results = results || [];
|
|
|
|
// Return early from calls with invalid selector or context
|
|
if ( typeof selector !== "string" || !selector ||
|
|
nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {
|
|
|
|
return results;
|
|
}
|
|
|
|
// Try to shortcut find operations (as opposed to filters) in HTML documents
|
|
if ( !seed ) {
|
|
|
|
if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
|
|
setDocument( context );
|
|
}
|
|
context = context || document;
|
|
|
|
if ( documentIsHTML ) {
|
|
|
|
// If the selector is sufficiently simple, try using a "get*By*" DOM method
|
|
// (excepting DocumentFragment context, where the methods don't exist)
|
|
if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) {
|
|
|
|
// ID selector
|
|
if ( (m = match[1]) ) {
|
|
|
|
// Document context
|
|
if ( nodeType === 9 ) {
|
|
if ( (elem = context.getElementById( m )) ) {
|
|
|
|
// Support: IE, Opera, Webkit
|
|
// TODO: identify versions
|
|
// getElementById can match elements by name instead of ID
|
|
if ( elem.id === m ) {
|
|
results.push( elem );
|
|
return results;
|
|
}
|
|
} else {
|
|
return results;
|
|
}
|
|
|
|
// Element context
|
|
} else {
|
|
|
|
// Support: IE, Opera, Webkit
|
|
// TODO: identify versions
|
|
// getElementById can match elements by name instead of ID
|
|
if ( newContext && (elem = newContext.getElementById( m )) &&
|
|
contains( context, elem ) &&
|
|
elem.id === m ) {
|
|
|
|
results.push( elem );
|
|
return results;
|
|
}
|
|
}
|
|
|
|
// Type selector
|
|
} else if ( match[2] ) {
|
|
push.apply( results, context.getElementsByTagName( selector ) );
|
|
return results;
|
|
|
|
// Class selector
|
|
} else if ( (m = match[3]) && support.getElementsByClassName &&
|
|
context.getElementsByClassName ) {
|
|
|
|
push.apply( results, context.getElementsByClassName( m ) );
|
|
return results;
|
|
}
|
|
}
|
|
|
|
// Take advantage of querySelectorAll
|
|
if ( support.qsa &&
|
|
!nonnativeSelectorCache[ selector + " " ] &&
|
|
(!rbuggyQSA || !rbuggyQSA.test( selector )) &&
|
|
|
|
// Support: IE 8 only
|
|
// Exclude object elements
|
|
(nodeType !== 1 || context.nodeName.toLowerCase() !== "object") ) {
|
|
|
|
newSelector = selector;
|
|
newContext = context;
|
|
|
|
// qSA considers elements outside a scoping root when evaluating child or
|
|
// descendant combinators, which is not what we want.
|
|
// In such cases, we work around the behavior by prefixing every selector in the
|
|
// list with an ID selector referencing the scope context.
|
|
// Thanks to Andrew Dupont for this technique.
|
|
if ( nodeType === 1 && rdescend.test( selector ) ) {
|
|
|
|
// Capture the context ID, setting it first if necessary
|
|
if ( (nid = context.getAttribute( "id" )) ) {
|
|
nid = nid.replace( rcssescape, fcssescape );
|
|
} else {
|
|
context.setAttribute( "id", (nid = expando) );
|
|
}
|
|
|
|
// Prefix every selector in the list
|
|
groups = tokenize( selector );
|
|
i = groups.length;
|
|
while ( i-- ) {
|
|
groups[i] = "#" + nid + " " + toSelector( groups[i] );
|
|
}
|
|
newSelector = groups.join( "," );
|
|
|
|
// Expand context for sibling selectors
|
|
newContext = rsibling.test( selector ) && testContext( context.parentNode ) ||
|
|
context;
|
|
}
|
|
|
|
try {
|
|
push.apply( results,
|
|
newContext.querySelectorAll( newSelector )
|
|
);
|
|
return results;
|
|
} catch ( qsaError ) {
|
|
nonnativeSelectorCache( selector, true );
|
|
} finally {
|
|
if ( nid === expando ) {
|
|
context.removeAttribute( "id" );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// All others
|
|
return select( selector.replace( rtrim, "$1" ), context, results, seed );
|
|
}
|
|
|
|
/**
|
|
* Create key-value caches of limited size
|
|
* @returns {function(string, object)} Returns the Object data after storing it on itself with
|
|
* property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
|
|
* deleting the oldest entry
|
|
*/
|
|
function createCache() {
|
|
var keys = [];
|
|
|
|
function cache( key, value ) {
|
|
// Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
|
|
if ( keys.push( key + " " ) > Expr.cacheLength ) {
|
|
// Only keep the most recent entries
|
|
delete cache[ keys.shift() ];
|
|
}
|
|
return (cache[ key + " " ] = value);
|
|
}
|
|
return cache;
|
|
}
|
|
|
|
/**
|
|
* Mark a function for special use by Sizzle
|
|
* @param {Function} fn The function to mark
|
|
*/
|
|
function markFunction( fn ) {
|
|
fn[ expando ] = true;
|
|
return fn;
|
|
}
|
|
|
|
/**
|
|
* Support testing using an element
|
|
* @param {Function} fn Passed the created element and returns a boolean result
|
|
*/
|
|
function assert( fn ) {
|
|
var el = document.createElement("fieldset");
|
|
|
|
try {
|
|
return !!fn( el );
|
|
} catch (e) {
|
|
return false;
|
|
} finally {
|
|
// Remove from its parent by default
|
|
if ( el.parentNode ) {
|
|
el.parentNode.removeChild( el );
|
|
}
|
|
// release memory in IE
|
|
el = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds the same handler for all of the specified attrs
|
|
* @param {String} attrs Pipe-separated list of attributes
|
|
* @param {Function} handler The method that will be applied
|
|
*/
|
|
function addHandle( attrs, handler ) {
|
|
var arr = attrs.split("|"),
|
|
i = arr.length;
|
|
|
|
while ( i-- ) {
|
|
Expr.attrHandle[ arr[i] ] = handler;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks document order of two siblings
|
|
* @param {Element} a
|
|
* @param {Element} b
|
|
* @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b
|
|
*/
|
|
function siblingCheck( a, b ) {
|
|
var cur = b && a,
|
|
diff = cur && a.nodeType === 1 && b.nodeType === 1 &&
|
|
a.sourceIndex - b.sourceIndex;
|
|
|
|
// Use IE sourceIndex if available on both nodes
|
|
if ( diff ) {
|
|
return diff;
|
|
}
|
|
|
|
// Check if b follows a
|
|
if ( cur ) {
|
|
while ( (cur = cur.nextSibling) ) {
|
|
if ( cur === b ) {
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return a ? 1 : -1;
|
|
}
|
|
|
|
/**
|
|
* Returns a function to use in pseudos for input types
|
|
* @param {String} type
|
|
*/
|
|
function createInputPseudo( type ) {
|
|
return function( elem ) {
|
|
var name = elem.nodeName.toLowerCase();
|
|
return name === "input" && elem.type === type;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Returns a function to use in pseudos for buttons
|
|
* @param {String} type
|
|
*/
|
|
function createButtonPseudo( type ) {
|
|
return function( elem ) {
|
|
var name = elem.nodeName.toLowerCase();
|
|
return (name === "input" || name === "button") && elem.type === type;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Returns a function to use in pseudos for :enabled/:disabled
|
|
* @param {Boolean} disabled true for :disabled; false for :enabled
|
|
*/
|
|
function createDisabledPseudo( disabled ) {
|
|
|
|
// Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable
|
|
return function( elem ) {
|
|
|
|
// Only certain elements can match :enabled or :disabled
|
|
// https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled
|
|
// https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled
|
|
if ( "form" in elem ) {
|
|
|
|
// Check for inherited disabledness on relevant non-disabled elements:
|
|
// * listed form-associated elements in a disabled fieldset
|
|
// https://html.spec.whatwg.org/multipage/forms.html#category-listed
|
|
// https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled
|
|
// * option elements in a disabled optgroup
|
|
// https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled
|
|
// All such elements have a "form" property.
|
|
if ( elem.parentNode && elem.disabled === false ) {
|
|
|
|
// Option elements defer to a parent optgroup if present
|
|
if ( "label" in elem ) {
|
|
if ( "label" in elem.parentNode ) {
|
|
return elem.parentNode.disabled === disabled;
|
|
} else {
|
|
return elem.disabled === disabled;
|
|
}
|
|
}
|
|
|
|
// Support: IE 6 - 11
|
|
// Use the isDisabled shortcut property to check for disabled fieldset ancestors
|
|
return elem.isDisabled === disabled ||
|
|
|
|
// Where there is no isDisabled, check manually
|
|
/* jshint -W018 */
|
|
elem.isDisabled !== !disabled &&
|
|
inDisabledFieldset( elem ) === disabled;
|
|
}
|
|
|
|
return elem.disabled === disabled;
|
|
|
|
// Try to winnow out elements that can't be disabled before trusting the disabled property.
|
|
// Some victims get caught in our net (label, legend, menu, track), but it shouldn't
|
|
// even exist on them, let alone have a boolean value.
|
|
} else if ( "label" in elem ) {
|
|
return elem.disabled === disabled;
|
|
}
|
|
|
|
// Remaining elements are neither :enabled nor :disabled
|
|
return false;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Returns a function to use in pseudos for positionals
|
|
* @param {Function} fn
|
|
*/
|
|
function createPositionalPseudo( fn ) {
|
|
return markFunction(function( argument ) {
|
|
argument = +argument;
|
|
return markFunction(function( seed, matches ) {
|
|
var j,
|
|
matchIndexes = fn( [], seed.length, argument ),
|
|
i = matchIndexes.length;
|
|
|
|
// Match elements found at the specified indexes
|
|
while ( i-- ) {
|
|
if ( seed[ (j = matchIndexes[i]) ] ) {
|
|
seed[j] = !(matches[j] = seed[j]);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Checks a node for validity as a Sizzle context
|
|
* @param {Element|Object=} context
|
|
* @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
|
|
*/
|
|
function testContext( context ) {
|
|
return context && typeof context.getElementsByTagName !== "undefined" && context;
|
|
}
|
|
|
|
// Expose support vars for convenience
|
|
support = Sizzle.support = {};
|
|
|
|
/**
|
|
* Detects XML nodes
|
|
* @param {Element|Object} elem An element or a document
|
|
* @returns {Boolean} True iff elem is a non-HTML XML node
|
|
*/
|
|
isXML = Sizzle.isXML = function( elem ) {
|
|
var namespace = elem.namespaceURI,
|
|
docElem = (elem.ownerDocument || elem).documentElement;
|
|
|
|
// Support: IE <=8
|
|
// Assume HTML when documentElement doesn't yet exist, such as inside loading iframes
|
|
// https://bugs.jquery.com/ticket/4833
|
|
return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" );
|
|
};
|
|
|
|
/**
|
|
* Sets document-related variables once based on the current document
|
|
* @param {Element|Object} [doc] An element or document object to use to set the document
|
|
* @returns {Object} Returns the current document
|
|
*/
|
|
setDocument = Sizzle.setDocument = function( node ) {
|
|
var hasCompare, subWindow,
|
|
doc = node ? node.ownerDocument || node : preferredDoc;
|
|
|
|
// Return early if doc is invalid or already selected
|
|
if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
|
|
return document;
|
|
}
|
|
|
|
// Update global variables
|
|
document = doc;
|
|
docElem = document.documentElement;
|
|
documentIsHTML = !isXML( document );
|
|
|
|
// Support: IE 9-11, Edge
|
|
// Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936)
|
|
if ( preferredDoc !== document &&
|
|
(subWindow = document.defaultView) && subWindow.top !== subWindow ) {
|
|
|
|
// Support: IE 11, Edge
|
|
if ( subWindow.addEventListener ) {
|
|
subWindow.addEventListener( "unload", unloadHandler, false );
|
|
|
|
// Support: IE 9 - 10 only
|
|
} else if ( subWindow.attachEvent ) {
|
|
subWindow.attachEvent( "onunload", unloadHandler );
|
|
}
|
|
}
|
|
|
|
/* Attributes
|
|
---------------------------------------------------------------------- */
|
|
|
|
// Support: IE<8
|
|
// Verify that getAttribute really returns attributes and not properties
|
|
// (excepting IE8 booleans)
|
|
support.attributes = assert(function( el ) {
|
|
el.className = "i";
|
|
return !el.getAttribute("className");
|
|
});
|
|
|
|
/* getElement(s)By*
|
|
---------------------------------------------------------------------- */
|
|
|
|
// Check if getElementsByTagName("*") returns only elements
|
|
support.getElementsByTagName = assert(function( el ) {
|
|
el.appendChild( document.createComment("") );
|
|
return !el.getElementsByTagName("*").length;
|
|
});
|
|
|
|
// Support: IE<9
|
|
support.getElementsByClassName = rnative.test( document.getElementsByClassName );
|
|
|
|
// Support: IE<10
|
|
// Check if getElementById returns elements by name
|
|
// The broken getElementById methods don't pick up programmatically-set names,
|
|
// so use a roundabout getElementsByName test
|
|
support.getById = assert(function( el ) {
|
|
docElem.appendChild( el ).id = expando;
|
|
return !document.getElementsByName || !document.getElementsByName( expando ).length;
|
|
});
|
|
|
|
// ID filter and find
|
|
if ( support.getById ) {
|
|
Expr.filter["ID"] = function( id ) {
|
|
var attrId = id.replace( runescape, funescape );
|
|
return function( elem ) {
|
|
return elem.getAttribute("id") === attrId;
|
|
};
|
|
};
|
|
Expr.find["ID"] = function( id, context ) {
|
|
if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
|
|
var elem = context.getElementById( id );
|
|
return elem ? [ elem ] : [];
|
|
}
|
|
};
|
|
} else {
|
|
Expr.filter["ID"] = function( id ) {
|
|
var attrId = id.replace( runescape, funescape );
|
|
return function( elem ) {
|
|
var node = typeof elem.getAttributeNode !== "undefined" &&
|
|
elem.getAttributeNode("id");
|
|
return node && node.value === attrId;
|
|
};
|
|
};
|
|
|
|
// Support: IE 6 - 7 only
|
|
// getElementById is not reliable as a find shortcut
|
|
Expr.find["ID"] = function( id, context ) {
|
|
if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
|
|
var node, i, elems,
|
|
elem = context.getElementById( id );
|
|
|
|
if ( elem ) {
|
|
|
|
// Verify the id attribute
|
|
node = elem.getAttributeNode("id");
|
|
if ( node && node.value === id ) {
|
|
return [ elem ];
|
|
}
|
|
|
|
// Fall back on getElementsByName
|
|
elems = context.getElementsByName( id );
|
|
i = 0;
|
|
while ( (elem = elems[i++]) ) {
|
|
node = elem.getAttributeNode("id");
|
|
if ( node && node.value === id ) {
|
|
return [ elem ];
|
|
}
|
|
}
|
|
}
|
|
|
|
return [];
|
|
}
|
|
};
|
|
}
|
|
|
|
// Tag
|
|
Expr.find["TAG"] = support.getElementsByTagName ?
|
|
function( tag, context ) {
|
|
if ( typeof context.getElementsByTagName !== "undefined" ) {
|
|
return context.getElementsByTagName( tag );
|
|
|
|
// DocumentFragment nodes don't have gEBTN
|
|
} else if ( support.qsa ) {
|
|
return context.querySelectorAll( tag );
|
|
}
|
|
} :
|
|
|
|
function( tag, context ) {
|
|
var elem,
|
|
tmp = [],
|
|
i = 0,
|
|
// By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too
|
|
results = context.getElementsByTagName( tag );
|
|
|
|
// Filter out possible comments
|
|
if ( tag === "*" ) {
|
|
while ( (elem = results[i++]) ) {
|
|
if ( elem.nodeType === 1 ) {
|
|
tmp.push( elem );
|
|
}
|
|
}
|
|
|
|
return tmp;
|
|
}
|
|
return results;
|
|
};
|
|
|
|
// Class
|
|
Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) {
|
|
if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) {
|
|
return context.getElementsByClassName( className );
|
|
}
|
|
};
|
|
|
|
/* QSA/matchesSelector
|
|
---------------------------------------------------------------------- */
|
|
|
|
// QSA and matchesSelector support
|
|
|
|
// matchesSelector(:active) reports false when true (IE9/Opera 11.5)
|
|
rbuggyMatches = [];
|
|
|
|
// qSa(:focus) reports false when true (Chrome 21)
|
|
// We allow this because of a bug in IE8/9 that throws an error
|
|
// whenever `document.activeElement` is accessed on an iframe
|
|
// So, we allow :focus to pass through QSA all the time to avoid the IE error
|
|
// See https://bugs.jquery.com/ticket/13378
|
|
rbuggyQSA = [];
|
|
|
|
if ( (support.qsa = rnative.test( document.querySelectorAll )) ) {
|
|
// Build QSA regex
|
|
// Regex strategy adopted from Diego Perini
|
|
assert(function( el ) {
|
|
// Select is set to empty string on purpose
|
|
// This is to test IE's treatment of not explicitly
|
|
// setting a boolean content attribute,
|
|
// since its presence should be enough
|
|
// https://bugs.jquery.com/ticket/12359
|
|
docElem.appendChild( el ).innerHTML = "<a id='" + expando + "'></a>" +
|
|
"<select id='" + expando + "-\r\\' msallowcapture=''>" +
|
|
"<option selected=''></option></select>";
|
|
|
|
// Support: IE8, Opera 11-12.16
|
|
// Nothing should be selected when empty strings follow ^= or $= or *=
|
|
// The test attribute must be unknown in Opera but "safe" for WinRT
|
|
// https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
|
|
if ( el.querySelectorAll("[msallowcapture^='']").length ) {
|
|
rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
|
|
}
|
|
|
|
// Support: IE8
|
|
// Boolean attributes and "value" are not treated correctly
|
|
if ( !el.querySelectorAll("[selected]").length ) {
|
|
rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
|
|
}
|
|
|
|
// Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+
|
|
if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) {
|
|
rbuggyQSA.push("~=");
|
|
}
|
|
|
|
// Webkit/Opera - :checked should return selected option elements
|
|
// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
|
|
// IE8 throws error here and will not see later tests
|
|
if ( !el.querySelectorAll(":checked").length ) {
|
|
rbuggyQSA.push(":checked");
|
|
}
|
|
|
|
// Support: Safari 8+, iOS 8+
|
|
// https://bugs.webkit.org/show_bug.cgi?id=136851
|
|
// In-page `selector#id sibling-combinator selector` fails
|
|
if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) {
|
|
rbuggyQSA.push(".#.+[+~]");
|
|
}
|
|
});
|
|
|
|
assert(function( el ) {
|
|
el.innerHTML = "<a href='' disabled='disabled'></a>" +
|
|
"<select disabled='disabled'><option/></select>";
|
|
|
|
// Support: Windows 8 Native Apps
|
|
// The type and name attributes are restricted during .innerHTML assignment
|
|
var input = document.createElement("input");
|
|
input.setAttribute( "type", "hidden" );
|
|
el.appendChild( input ).setAttribute( "name", "D" );
|
|
|
|
// Support: IE8
|
|
// Enforce case-sensitivity of name attribute
|
|
if ( el.querySelectorAll("[name=d]").length ) {
|
|
rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" );
|
|
}
|
|
|
|
// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
|
|
// IE8 throws error here and will not see later tests
|
|
if ( el.querySelectorAll(":enabled").length !== 2 ) {
|
|
rbuggyQSA.push( ":enabled", ":disabled" );
|
|
}
|
|
|
|
// Support: IE9-11+
|
|
// IE's :disabled selector does not pick up the children of disabled fieldsets
|
|
docElem.appendChild( el ).disabled = true;
|
|
if ( el.querySelectorAll(":disabled").length !== 2 ) {
|
|
rbuggyQSA.push( ":enabled", ":disabled" );
|
|
}
|
|
|
|
// Opera 10-11 does not throw on post-comma invalid pseudos
|
|
el.querySelectorAll("*,:x");
|
|
rbuggyQSA.push(",.*:");
|
|
});
|
|
}
|
|
|
|
if ( (support.matchesSelector = rnative.test( (matches = docElem.matches ||
|
|
docElem.webkitMatchesSelector ||
|
|
docElem.mozMatchesSelector ||
|
|
docElem.oMatchesSelector ||
|
|
docElem.msMatchesSelector) )) ) {
|
|
|
|
assert(function( el ) {
|
|
// Check to see if it's possible to do matchesSelector
|
|
// on a disconnected node (IE 9)
|
|
support.disconnectedMatch = matches.call( el, "*" );
|
|
|
|
// This should fail with an exception
|
|
// Gecko does not error, returns false instead
|
|
matches.call( el, "[s!='']:x" );
|
|
rbuggyMatches.push( "!=", pseudos );
|
|
});
|
|
}
|
|
|
|
rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );
|
|
rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") );
|
|
|
|
/* Contains
|
|
---------------------------------------------------------------------- */
|
|
hasCompare = rnative.test( docElem.compareDocumentPosition );
|
|
|
|
// Element contains another
|
|
// Purposefully self-exclusive
|
|
// As in, an element does not contain itself
|
|
contains = hasCompare || rnative.test( docElem.contains ) ?
|
|
function( a, b ) {
|
|
var adown = a.nodeType === 9 ? a.documentElement : a,
|
|
bup = b && b.parentNode;
|
|
return a === bup || !!( bup && bup.nodeType === 1 && (
|
|
adown.contains ?
|
|
adown.contains( bup ) :
|
|
a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
|
|
));
|
|
} :
|
|
function( a, b ) {
|
|
if ( b ) {
|
|
while ( (b = b.parentNode) ) {
|
|
if ( b === a ) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
/* Sorting
|
|
---------------------------------------------------------------------- */
|
|
|
|
// Document order sorting
|
|
sortOrder = hasCompare ?
|
|
function( a, b ) {
|
|
|
|
// Flag for duplicate removal
|
|
if ( a === b ) {
|
|
hasDuplicate = true;
|
|
return 0;
|
|
}
|
|
|
|
// Sort on method existence if only one input has compareDocumentPosition
|
|
var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
|
|
if ( compare ) {
|
|
return compare;
|
|
}
|
|
|
|
// Calculate position if both inputs belong to the same document
|
|
compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ?
|
|
a.compareDocumentPosition( b ) :
|
|
|
|
// Otherwise we know they are disconnected
|
|
1;
|
|
|
|
// Disconnected nodes
|
|
if ( compare & 1 ||
|
|
(!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {
|
|
|
|
// Choose the first element that is related to our preferred document
|
|
if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) {
|
|
return -1;
|
|
}
|
|
if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) {
|
|
return 1;
|
|
}
|
|
|
|
// Maintain original order
|
|
return sortInput ?
|
|
( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
|
|
0;
|
|
}
|
|
|
|
return compare & 4 ? -1 : 1;
|
|
} :
|
|
function( a, b ) {
|
|
// Exit early if the nodes are identical
|
|
if ( a === b ) {
|
|
hasDuplicate = true;
|
|
return 0;
|
|
}
|
|
|
|
var cur,
|
|
i = 0,
|
|
aup = a.parentNode,
|
|
bup = b.parentNode,
|
|
ap = [ a ],
|
|
bp = [ b ];
|
|
|
|
// Parentless nodes are either documents or disconnected
|
|
if ( !aup || !bup ) {
|
|
return a === document ? -1 :
|
|
b === document ? 1 :
|
|
aup ? -1 :
|
|
bup ? 1 :
|
|
sortInput ?
|
|
( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
|
|
0;
|
|
|
|
// If the nodes are siblings, we can do a quick check
|
|
} else if ( aup === bup ) {
|
|
return siblingCheck( a, b );
|
|
}
|
|
|
|
// Otherwise we need full lists of their ancestors for comparison
|
|
cur = a;
|
|
while ( (cur = cur.parentNode) ) {
|
|
ap.unshift( cur );
|
|
}
|
|
cur = b;
|
|
while ( (cur = cur.parentNode) ) {
|
|
bp.unshift( cur );
|
|
}
|
|
|
|
// Walk down the tree looking for a discrepancy
|
|
while ( ap[i] === bp[i] ) {
|
|
i++;
|
|
}
|
|
|
|
return i ?
|
|
// Do a sibling check if the nodes have a common ancestor
|
|
siblingCheck( ap[i], bp[i] ) :
|
|
|
|
// Otherwise nodes in our document sort first
|
|
ap[i] === preferredDoc ? -1 :
|
|
bp[i] === preferredDoc ? 1 :
|
|
0;
|
|
};
|
|
|
|
return document;
|
|
};
|
|
|
|
Sizzle.matches = function( expr, elements ) {
|
|
return Sizzle( expr, null, null, elements );
|
|
};
|
|
|
|
Sizzle.matchesSelector = function( elem, expr ) {
|
|
// Set document vars if needed
|
|
if ( ( elem.ownerDocument || elem ) !== document ) {
|
|
setDocument( elem );
|
|
}
|
|
|
|
if ( support.matchesSelector && documentIsHTML &&
|
|
!nonnativeSelectorCache[ expr + " " ] &&
|
|
( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&
|
|
( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) {
|
|
|
|
try {
|
|
var ret = matches.call( elem, expr );
|
|
|
|
// IE 9's matchesSelector returns false on disconnected nodes
|
|
if ( ret || support.disconnectedMatch ||
|
|
// As well, disconnected nodes are said to be in a document
|
|
// fragment in IE 9
|
|
elem.document && elem.document.nodeType !== 11 ) {
|
|
return ret;
|
|
}
|
|
} catch (e) {
|
|
nonnativeSelectorCache( expr, true );
|
|
}
|
|
}
|
|
|
|
return Sizzle( expr, document, null, [ elem ] ).length > 0;
|
|
};
|
|
|
|
Sizzle.contains = function( context, elem ) {
|
|
// Set document vars if needed
|
|
if ( ( context.ownerDocument || context ) !== document ) {
|
|
setDocument( context );
|
|
}
|
|
return contains( context, elem );
|
|
};
|
|
|
|
Sizzle.attr = function( elem, name ) {
|
|
// Set document vars if needed
|
|
if ( ( elem.ownerDocument || elem ) !== document ) {
|
|
setDocument( elem );
|
|
}
|
|
|
|
var fn = Expr.attrHandle[ name.toLowerCase() ],
|
|
// Don't get fooled by Object.prototype properties (jQuery #13807)
|
|
val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?
|
|
fn( elem, name, !documentIsHTML ) :
|
|
undefined;
|
|
|
|
return val !== undefined ?
|
|
val :
|
|
support.attributes || !documentIsHTML ?
|
|
elem.getAttribute( name ) :
|
|
(val = elem.getAttributeNode(name)) && val.specified ?
|
|
val.value :
|
|
null;
|
|
};
|
|
|
|
Sizzle.escape = function( sel ) {
|
|
return (sel + "").replace( rcssescape, fcssescape );
|
|
};
|
|
|
|
Sizzle.error = function( msg ) {
|
|
throw new Error( "Syntax error, unrecognized expression: " + msg );
|
|
};
|
|
|
|
/**
|
|
* Document sorting and removing duplicates
|
|
* @param {ArrayLike} results
|
|
*/
|
|
Sizzle.uniqueSort = function( results ) {
|
|
var elem,
|
|
duplicates = [],
|
|
j = 0,
|
|
i = 0;
|
|
|
|
// Unless we *know* we can detect duplicates, assume their presence
|
|
hasDuplicate = !support.detectDuplicates;
|
|
sortInput = !support.sortStable && results.slice( 0 );
|
|
results.sort( sortOrder );
|
|
|
|
if ( hasDuplicate ) {
|
|
while ( (elem = results[i++]) ) {
|
|
if ( elem === results[ i ] ) {
|
|
j = duplicates.push( i );
|
|
}
|
|
}
|
|
while ( j-- ) {
|
|
results.splice( duplicates[ j ], 1 );
|
|
}
|
|
}
|
|
|
|
// Clear input after sorting to release objects
|
|
// See https://github.com/jquery/sizzle/pull/225
|
|
sortInput = null;
|
|
|
|
return results;
|
|
};
|
|
|
|
/**
|
|
* Utility function for retrieving the text value of an array of DOM nodes
|
|
* @param {Array|Element} elem
|
|
*/
|
|
getText = Sizzle.getText = function( elem ) {
|
|
var node,
|
|
ret = "",
|
|
i = 0,
|
|
nodeType = elem.nodeType;
|
|
|
|
if ( !nodeType ) {
|
|
// If no nodeType, this is expected to be an array
|
|
while ( (node = elem[i++]) ) {
|
|
// Do not traverse comment nodes
|
|
ret += getText( node );
|
|
}
|
|
} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
|
|
// Use textContent for elements
|
|
// innerText usage removed for consistency of new lines (jQuery #11153)
|
|
if ( typeof elem.textContent === "string" ) {
|
|
return elem.textContent;
|
|
} else {
|
|
// Traverse its children
|
|
for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
|
|
ret += getText( elem );
|
|
}
|
|
}
|
|
} else if ( nodeType === 3 || nodeType === 4 ) {
|
|
return elem.nodeValue;
|
|
}
|
|
// Do not include comment or processing instruction nodes
|
|
|
|
return ret;
|
|
};
|
|
|
|
Expr = Sizzle.selectors = {
|
|
|
|
// Can be adjusted by the user
|
|
cacheLength: 50,
|
|
|
|
createPseudo: markFunction,
|
|
|
|
match: matchExpr,
|
|
|
|
attrHandle: {},
|
|
|
|
find: {},
|
|
|
|
relative: {
|
|
">": { dir: "parentNode", first: true },
|
|
" ": { dir: "parentNode" },
|
|
"+": { dir: "previousSibling", first: true },
|
|
"~": { dir: "previousSibling" }
|
|
},
|
|
|
|
preFilter: {
|
|
"ATTR": function( match ) {
|
|
match[1] = match[1].replace( runescape, funescape );
|
|
|
|
// Move the given value to match[3] whether quoted or unquoted
|
|
match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape );
|
|
|
|
if ( match[2] === "~=" ) {
|
|
match[3] = " " + match[3] + " ";
|
|
}
|
|
|
|
return match.slice( 0, 4 );
|
|
},
|
|
|
|
"CHILD": function( match ) {
|
|
/* matches from matchExpr["CHILD"]
|
|
1 type (only|nth|...)
|
|
2 what (child|of-type)
|
|
3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
|
|
4 xn-component of xn+y argument ([+-]?\d*n|)
|
|
5 sign of xn-component
|
|
6 x of xn-component
|
|
7 sign of y-component
|
|
8 y of y-component
|
|
*/
|
|
match[1] = match[1].toLowerCase();
|
|
|
|
if ( match[1].slice( 0, 3 ) === "nth" ) {
|
|
// nth-* requires argument
|
|
if ( !match[3] ) {
|
|
Sizzle.error( match[0] );
|
|
}
|
|
|
|
// numeric x and y parameters for Expr.filter.CHILD
|
|
// remember that false/true cast respectively to 0/1
|
|
match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) );
|
|
match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" );
|
|
|
|
// other types prohibit arguments
|
|
} else if ( match[3] ) {
|
|
Sizzle.error( match[0] );
|
|
}
|
|
|
|
return match;
|
|
},
|
|
|
|
"PSEUDO": function( match ) {
|
|
var excess,
|
|
unquoted = !match[6] && match[2];
|
|
|
|
if ( matchExpr["CHILD"].test( match[0] ) ) {
|
|
return null;
|
|
}
|
|
|
|
// Accept quoted arguments as-is
|
|
if ( match[3] ) {
|
|
match[2] = match[4] || match[5] || "";
|
|
|
|
// Strip excess characters from unquoted arguments
|
|
} else if ( unquoted && rpseudo.test( unquoted ) &&
|
|
// Get excess from tokenize (recursively)
|
|
(excess = tokenize( unquoted, true )) &&
|
|
// advance to the next closing parenthesis
|
|
(excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {
|
|
|
|
// excess is a negative index
|
|
match[0] = match[0].slice( 0, excess );
|
|
match[2] = unquoted.slice( 0, excess );
|
|
}
|
|
|
|
// Return only captures needed by the pseudo filter method (type and argument)
|
|
return match.slice( 0, 3 );
|
|
}
|
|
},
|
|
|
|
filter: {
|
|
|
|
"TAG": function( nodeNameSelector ) {
|
|
var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
|
|
return nodeNameSelector === "*" ?
|
|
function() { return true; } :
|
|
function( elem ) {
|
|
return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
|
|
};
|
|
},
|
|
|
|
"CLASS": function( className ) {
|
|
var pattern = classCache[ className + " " ];
|
|
|
|
return pattern ||
|
|
(pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) &&
|
|
classCache( className, function( elem ) {
|
|
return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" );
|
|
});
|
|
},
|
|
|
|
"ATTR": function( name, operator, check ) {
|
|
return function( elem ) {
|
|
var result = Sizzle.attr( elem, name );
|
|
|
|
if ( result == null ) {
|
|
return operator === "!=";
|
|
}
|
|
if ( !operator ) {
|
|
return true;
|
|
}
|
|
|
|
result += "";
|
|
|
|
return operator === "=" ? result === check :
|
|
operator === "!=" ? result !== check :
|
|
operator === "^=" ? check && result.indexOf( check ) === 0 :
|
|
operator === "*=" ? check && result.indexOf( check ) > -1 :
|
|
operator === "$=" ? check && result.slice( -check.length ) === check :
|
|
operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 :
|
|
operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
|
|
false;
|
|
};
|
|
},
|
|
|
|
"CHILD": function( type, what, argument, first, last ) {
|
|
var simple = type.slice( 0, 3 ) !== "nth",
|
|
forward = type.slice( -4 ) !== "last",
|
|
ofType = what === "of-type";
|
|
|
|
return first === 1 && last === 0 ?
|
|
|
|
// Shortcut for :nth-*(n)
|
|
function( elem ) {
|
|
return !!elem.parentNode;
|
|
} :
|
|
|
|
function( elem, context, xml ) {
|
|
var cache, uniqueCache, outerCache, node, nodeIndex, start,
|
|
dir = simple !== forward ? "nextSibling" : "previousSibling",
|
|
parent = elem.parentNode,
|
|
name = ofType && elem.nodeName.toLowerCase(),
|
|
useCache = !xml && !ofType,
|
|
diff = false;
|
|
|
|
if ( parent ) {
|
|
|
|
// :(first|last|only)-(child|of-type)
|
|
if ( simple ) {
|
|
while ( dir ) {
|
|
node = elem;
|
|
while ( (node = node[ dir ]) ) {
|
|
if ( ofType ?
|
|
node.nodeName.toLowerCase() === name :
|
|
node.nodeType === 1 ) {
|
|
|
|
return false;
|
|
}
|
|
}
|
|
// Reverse direction for :only-* (if we haven't yet done so)
|
|
start = dir = type === "only" && !start && "nextSibling";
|
|
}
|
|
return true;
|
|
}
|
|
|
|
start = [ forward ? parent.firstChild : parent.lastChild ];
|
|
|
|
// non-xml :nth-child(...) stores cache data on `parent`
|
|
if ( forward && useCache ) {
|
|
|
|
// Seek `elem` from a previously-cached index
|
|
|
|
// ...in a gzip-friendly way
|
|
node = parent;
|
|
outerCache = node[ expando ] || (node[ expando ] = {});
|
|
|
|
// Support: IE <9 only
|
|
// Defend against cloned attroperties (jQuery gh-1709)
|
|
uniqueCache = outerCache[ node.uniqueID ] ||
|
|
(outerCache[ node.uniqueID ] = {});
|
|
|
|
cache = uniqueCache[ type ] || [];
|
|
nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
|
|
diff = nodeIndex && cache[ 2 ];
|
|
node = nodeIndex && parent.childNodes[ nodeIndex ];
|
|
|
|
while ( (node = ++nodeIndex && node && node[ dir ] ||
|
|
|
|
// Fallback to seeking `elem` from the start
|
|
(diff = nodeIndex = 0) || start.pop()) ) {
|
|
|
|
// When found, cache indexes on `parent` and break
|
|
if ( node.nodeType === 1 && ++diff && node === elem ) {
|
|
uniqueCache[ type ] = [ dirruns, nodeIndex, diff ];
|
|
break;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
// Use previously-cached element index if available
|
|
if ( useCache ) {
|
|
// ...in a gzip-friendly way
|
|
node = elem;
|
|
outerCache = node[ expando ] || (node[ expando ] = {});
|
|
|
|
// Support: IE <9 only
|
|
// Defend against cloned attroperties (jQuery gh-1709)
|
|
uniqueCache = outerCache[ node.uniqueID ] ||
|
|
(outerCache[ node.uniqueID ] = {});
|
|
|
|
cache = uniqueCache[ type ] || [];
|
|
nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
|
|
diff = nodeIndex;
|
|
}
|
|
|
|
// xml :nth-child(...)
|
|
// or :nth-last-child(...) or :nth(-last)?-of-type(...)
|
|
if ( diff === false ) {
|
|
// Use the same loop as above to seek `elem` from the start
|
|
while ( (node = ++nodeIndex && node && node[ dir ] ||
|
|
(diff = nodeIndex = 0) || start.pop()) ) {
|
|
|
|
if ( ( ofType ?
|
|
node.nodeName.toLowerCase() === name :
|
|
node.nodeType === 1 ) &&
|
|
++diff ) {
|
|
|
|
// Cache the index of each encountered element
|
|
if ( useCache ) {
|
|
outerCache = node[ expando ] || (node[ expando ] = {});
|
|
|
|
// Support: IE <9 only
|
|
// Defend against cloned attroperties (jQuery gh-1709)
|
|
uniqueCache = outerCache[ node.uniqueID ] ||
|
|
(outerCache[ node.uniqueID ] = {});
|
|
|
|
uniqueCache[ type ] = [ dirruns, diff ];
|
|
}
|
|
|
|
if ( node === elem ) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Incorporate the offset, then check against cycle size
|
|
diff -= last;
|
|
return diff === first || ( diff % first === 0 && diff / first >= 0 );
|
|
}
|
|
};
|
|
},
|
|
|
|
"PSEUDO": function( pseudo, argument ) {
|
|
// pseudo-class names are case-insensitive
|
|
// http://www.w3.org/TR/selectors/#pseudo-classes
|
|
// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
|
|
// Remember that setFilters inherits from pseudos
|
|
var args,
|
|
fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
|
|
Sizzle.error( "unsupported pseudo: " + pseudo );
|
|
|
|
// The user may use createPseudo to indicate that
|
|
// arguments are needed to create the filter function
|
|
// just as Sizzle does
|
|
if ( fn[ expando ] ) {
|
|
return fn( argument );
|
|
}
|
|
|
|
// But maintain support for old signatures
|
|
if ( fn.length > 1 ) {
|
|
args = [ pseudo, pseudo, "", argument ];
|
|
return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
|
|
markFunction(function( seed, matches ) {
|
|
var idx,
|
|
matched = fn( seed, argument ),
|
|
i = matched.length;
|
|
while ( i-- ) {
|
|
idx = indexOf( seed, matched[i] );
|
|
seed[ idx ] = !( matches[ idx ] = matched[i] );
|
|
}
|
|
}) :
|
|
function( elem ) {
|
|
return fn( elem, 0, args );
|
|
};
|
|
}
|
|
|
|
return fn;
|
|
}
|
|
},
|
|
|
|
pseudos: {
|
|
// Potentially complex pseudos
|
|
"not": markFunction(function( selector ) {
|
|
// Trim the selector passed to compile
|
|
// to avoid treating leading and trailing
|
|
// spaces as combinators
|
|
var input = [],
|
|
results = [],
|
|
matcher = compile( selector.replace( rtrim, "$1" ) );
|
|
|
|
return matcher[ expando ] ?
|
|
markFunction(function( seed, matches, context, xml ) {
|
|
var elem,
|
|
unmatched = matcher( seed, null, xml, [] ),
|
|
i = seed.length;
|
|
|
|
// Match elements unmatched by `matcher`
|
|
while ( i-- ) {
|
|
if ( (elem = unmatched[i]) ) {
|
|
seed[i] = !(matches[i] = elem);
|
|
}
|
|
}
|
|
}) :
|
|
function( elem, context, xml ) {
|
|
input[0] = elem;
|
|
matcher( input, null, xml, results );
|
|
// Don't keep the element (issue #299)
|
|
input[0] = null;
|
|
return !results.pop();
|
|
};
|
|
}),
|
|
|
|
"has": markFunction(function( selector ) {
|
|
return function( elem ) {
|
|
return Sizzle( selector, elem ).length > 0;
|
|
};
|
|
}),
|
|
|
|
"contains": markFunction(function( text ) {
|
|
text = text.replace( runescape, funescape );
|
|
return function( elem ) {
|
|
return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1;
|
|
};
|
|
}),
|
|
|
|
// "Whether an element is represented by a :lang() selector
|
|
// is based solely on the element's language value
|
|
// being equal to the identifier C,
|
|
// or beginning with the identifier C immediately followed by "-".
|
|
// The matching of C against the element's language value is performed case-insensitively.
|
|
// The identifier C does not have to be a valid language name."
|
|
// http://www.w3.org/TR/selectors/#lang-pseudo
|
|
"lang": markFunction( function( lang ) {
|
|
// lang value must be a valid identifier
|
|
if ( !ridentifier.test(lang || "") ) {
|
|
Sizzle.error( "unsupported lang: " + lang );
|
|
}
|
|
lang = lang.replace( runescape, funescape ).toLowerCase();
|
|
return function( elem ) {
|
|
var elemLang;
|
|
do {
|
|
if ( (elemLang = documentIsHTML ?
|
|
elem.lang :
|
|
elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) {
|
|
|
|
elemLang = elemLang.toLowerCase();
|
|
return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
|
|
}
|
|
} while ( (elem = elem.parentNode) && elem.nodeType === 1 );
|
|
return false;
|
|
};
|
|
}),
|
|
|
|
// Miscellaneous
|
|
"target": function( elem ) {
|
|
var hash = window.location && window.location.hash;
|
|
return hash && hash.slice( 1 ) === elem.id;
|
|
},
|
|
|
|
"root": function( elem ) {
|
|
return elem === docElem;
|
|
},
|
|
|
|
"focus": function( elem ) {
|
|
return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
|
|
},
|
|
|
|
// Boolean properties
|
|
"enabled": createDisabledPseudo( false ),
|
|
"disabled": createDisabledPseudo( true ),
|
|
|
|
"checked": function( elem ) {
|
|
// In CSS3, :checked should return both checked and selected elements
|
|
// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
|
|
var nodeName = elem.nodeName.toLowerCase();
|
|
return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
|
|
},
|
|
|
|
"selected": function( elem ) {
|
|
// Accessing this property makes selected-by-default
|
|
// options in Safari work properly
|
|
if ( elem.parentNode ) {
|
|
elem.parentNode.selectedIndex;
|
|
}
|
|
|
|
return elem.selected === true;
|
|
},
|
|
|
|
// Contents
|
|
"empty": function( elem ) {
|
|
// http://www.w3.org/TR/selectors/#empty-pseudo
|
|
// :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
|
|
// but not by others (comment: 8; processing instruction: 7; etc.)
|
|
// nodeType < 6 works because attributes (2) do not appear as children
|
|
for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
|
|
if ( elem.nodeType < 6 ) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
},
|
|
|
|
"parent": function( elem ) {
|
|
return !Expr.pseudos["empty"]( elem );
|
|
},
|
|
|
|
// Element/input types
|
|
"header": function( elem ) {
|
|
return rheader.test( elem.nodeName );
|
|
},
|
|
|
|
"input": function( elem ) {
|
|
return rinputs.test( elem.nodeName );
|
|
},
|
|
|
|
"button": function( elem ) {
|
|
var name = elem.nodeName.toLowerCase();
|
|
return name === "input" && elem.type === "button" || name === "button";
|
|
},
|
|
|
|
"text": function( elem ) {
|
|
var attr;
|
|
return elem.nodeName.toLowerCase() === "input" &&
|
|
elem.type === "text" &&
|
|
|
|
// Support: IE<8
|
|
// New HTML5 attribute values (e.g., "search") appear with elem.type === "text"
|
|
( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" );
|
|
},
|
|
|
|
// Position-in-collection
|
|
"first": createPositionalPseudo(function() {
|
|
return [ 0 ];
|
|
}),
|
|
|
|
"last": createPositionalPseudo(function( matchIndexes, length ) {
|
|
return [ length - 1 ];
|
|
}),
|
|
|
|
"eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
|
|
return [ argument < 0 ? argument + length : argument ];
|
|
}),
|
|
|
|
"even": createPositionalPseudo(function( matchIndexes, length ) {
|
|
var i = 0;
|
|
for ( ; i < length; i += 2 ) {
|
|
matchIndexes.push( i );
|
|
}
|
|
return matchIndexes;
|
|
}),
|
|
|
|
"odd": createPositionalPseudo(function( matchIndexes, length ) {
|
|
var i = 1;
|
|
for ( ; i < length; i += 2 ) {
|
|
matchIndexes.push( i );
|
|
}
|
|
return matchIndexes;
|
|
}),
|
|
|
|
"lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
|
|
var i = argument < 0 ?
|
|
argument + length :
|
|
argument > length ?
|
|
length :
|
|
argument;
|
|
for ( ; --i >= 0; ) {
|
|
matchIndexes.push( i );
|
|
}
|
|
return matchIndexes;
|
|
}),
|
|
|
|
"gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
|
|
var i = argument < 0 ? argument + length : argument;
|
|
for ( ; ++i < length; ) {
|
|
matchIndexes.push( i );
|
|
}
|
|
return matchIndexes;
|
|
})
|
|
}
|
|
};
|
|
|
|
Expr.pseudos["nth"] = Expr.pseudos["eq"];
|
|
|
|
// Add button/input type pseudos
|
|
for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
|
|
Expr.pseudos[ i ] = createInputPseudo( i );
|
|
}
|
|
for ( i in { submit: true, reset: true } ) {
|
|
Expr.pseudos[ i ] = createButtonPseudo( i );
|
|
}
|
|
|
|
// Easy API for creating new setFilters
|
|
function setFilters() {}
|
|
setFilters.prototype = Expr.filters = Expr.pseudos;
|
|
Expr.setFilters = new setFilters();
|
|
|
|
tokenize = Sizzle.tokenize = function( selector, parseOnly ) {
|
|
var matched, match, tokens, type,
|
|
soFar, groups, preFilters,
|
|
cached = tokenCache[ selector + " " ];
|
|
|
|
if ( cached ) {
|
|
return parseOnly ? 0 : cached.slice( 0 );
|
|
}
|
|
|
|
soFar = selector;
|
|
groups = [];
|
|
preFilters = Expr.preFilter;
|
|
|
|
while ( soFar ) {
|
|
|
|
// Comma and first run
|
|
if ( !matched || (match = rcomma.exec( soFar )) ) {
|
|
if ( match ) {
|
|
// Don't consume trailing commas as valid
|
|
soFar = soFar.slice( match[0].length ) || soFar;
|
|
}
|
|
groups.push( (tokens = []) );
|
|
}
|
|
|
|
matched = false;
|
|
|
|
// Combinators
|
|
if ( (match = rcombinators.exec( soFar )) ) {
|
|
matched = match.shift();
|
|
tokens.push({
|
|
value: matched,
|
|
// Cast descendant combinators to space
|
|
type: match[0].replace( rtrim, " " )
|
|
});
|
|
soFar = soFar.slice( matched.length );
|
|
}
|
|
|
|
// Filters
|
|
for ( type in Expr.filter ) {
|
|
if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
|
|
(match = preFilters[ type ]( match ))) ) {
|
|
matched = match.shift();
|
|
tokens.push({
|
|
value: matched,
|
|
type: type,
|
|
matches: match
|
|
});
|
|
soFar = soFar.slice( matched.length );
|
|
}
|
|
}
|
|
|
|
if ( !matched ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Return the length of the invalid excess
|
|
// if we're just parsing
|
|
// Otherwise, throw an error or return tokens
|
|
return parseOnly ?
|
|
soFar.length :
|
|
soFar ?
|
|
Sizzle.error( selector ) :
|
|
// Cache the tokens
|
|
tokenCache( selector, groups ).slice( 0 );
|
|
};
|
|
|
|
function toSelector( tokens ) {
|
|
var i = 0,
|
|
len = tokens.length,
|
|
selector = "";
|
|
for ( ; i < len; i++ ) {
|
|
selector += tokens[i].value;
|
|
}
|
|
return selector;
|
|
}
|
|
|
|
function addCombinator( matcher, combinator, base ) {
|
|
var dir = combinator.dir,
|
|
skip = combinator.next,
|
|
key = skip || dir,
|
|
checkNonElements = base && key === "parentNode",
|
|
doneName = done++;
|
|
|
|
return combinator.first ?
|
|
// Check against closest ancestor/preceding element
|
|
function( elem, context, xml ) {
|
|
while ( (elem = elem[ dir ]) ) {
|
|
if ( elem.nodeType === 1 || checkNonElements ) {
|
|
return matcher( elem, context, xml );
|
|
}
|
|
}
|
|
return false;
|
|
} :
|
|
|
|
// Check against all ancestor/preceding elements
|
|
function( elem, context, xml ) {
|
|
var oldCache, uniqueCache, outerCache,
|
|
newCache = [ dirruns, doneName ];
|
|
|
|
// We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching
|
|
if ( xml ) {
|
|
while ( (elem = elem[ dir ]) ) {
|
|
if ( elem.nodeType === 1 || checkNonElements ) {
|
|
if ( matcher( elem, context, xml ) ) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
while ( (elem = elem[ dir ]) ) {
|
|
if ( elem.nodeType === 1 || checkNonElements ) {
|
|
outerCache = elem[ expando ] || (elem[ expando ] = {});
|
|
|
|
// Support: IE <9 only
|
|
// Defend against cloned attroperties (jQuery gh-1709)
|
|
uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {});
|
|
|
|
if ( skip && skip === elem.nodeName.toLowerCase() ) {
|
|
elem = elem[ dir ] || elem;
|
|
} else if ( (oldCache = uniqueCache[ key ]) &&
|
|
oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {
|
|
|
|
// Assign to newCache so results back-propagate to previous elements
|
|
return (newCache[ 2 ] = oldCache[ 2 ]);
|
|
} else {
|
|
// Reuse newcache so results back-propagate to previous elements
|
|
uniqueCache[ key ] = newCache;
|
|
|
|
// A match means we're done; a fail means we have to keep checking
|
|
if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
}
|
|
|
|
function elementMatcher( matchers ) {
|
|
return matchers.length > 1 ?
|
|
function( elem, context, xml ) {
|
|
var i = matchers.length;
|
|
while ( i-- ) {
|
|
if ( !matchers[i]( elem, context, xml ) ) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
} :
|
|
matchers[0];
|
|
}
|
|
|
|
function multipleContexts( selector, contexts, results ) {
|
|
var i = 0,
|
|
len = contexts.length;
|
|
for ( ; i < len; i++ ) {
|
|
Sizzle( selector, contexts[i], results );
|
|
}
|
|
return results;
|
|
}
|
|
|
|
function condense( unmatched, map, filter, context, xml ) {
|
|
var elem,
|
|
newUnmatched = [],
|
|
i = 0,
|
|
len = unmatched.length,
|
|
mapped = map != null;
|
|
|
|
for ( ; i < len; i++ ) {
|
|
if ( (elem = unmatched[i]) ) {
|
|
if ( !filter || filter( elem, context, xml ) ) {
|
|
newUnmatched.push( elem );
|
|
if ( mapped ) {
|
|
map.push( i );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return newUnmatched;
|
|
}
|
|
|
|
function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
|
|
if ( postFilter && !postFilter[ expando ] ) {
|
|
postFilter = setMatcher( postFilter );
|
|
}
|
|
if ( postFinder && !postFinder[ expando ] ) {
|
|
postFinder = setMatcher( postFinder, postSelector );
|
|
}
|
|
return markFunction(function( seed, results, context, xml ) {
|
|
var temp, i, elem,
|
|
preMap = [],
|
|
postMap = [],
|
|
preexisting = results.length,
|
|
|
|
// Get initial elements from seed or context
|
|
elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),
|
|
|
|
// Prefilter to get matcher input, preserving a map for seed-results synchronization
|
|
matcherIn = preFilter && ( seed || !selector ) ?
|
|
condense( elems, preMap, preFilter, context, xml ) :
|
|
elems,
|
|
|
|
matcherOut = matcher ?
|
|
// If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
|
|
postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
|
|
|
|
// ...intermediate processing is necessary
|
|
[] :
|
|
|
|
// ...otherwise use results directly
|
|
results :
|
|
matcherIn;
|
|
|
|
// Find primary matches
|
|
if ( matcher ) {
|
|
matcher( matcherIn, matcherOut, context, xml );
|
|
}
|
|
|
|
// Apply postFilter
|
|
if ( postFilter ) {
|
|
temp = condense( matcherOut, postMap );
|
|
postFilter( temp, [], context, xml );
|
|
|
|
// Un-match failing elements by moving them back to matcherIn
|
|
i = temp.length;
|
|
while ( i-- ) {
|
|
if ( (elem = temp[i]) ) {
|
|
matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( seed ) {
|
|
if ( postFinder || preFilter ) {
|
|
if ( postFinder ) {
|
|
// Get the final matcherOut by condensing this intermediate into postFinder contexts
|
|
temp = [];
|
|
i = matcherOut.length;
|
|
while ( i-- ) {
|
|
if ( (elem = matcherOut[i]) ) {
|
|
// Restore matcherIn since elem is not yet a final match
|
|
temp.push( (matcherIn[i] = elem) );
|
|
}
|
|
}
|
|
postFinder( null, (matcherOut = []), temp, xml );
|
|
}
|
|
|
|
// Move matched elements from seed to results to keep them synchronized
|
|
i = matcherOut.length;
|
|
while ( i-- ) {
|
|
if ( (elem = matcherOut[i]) &&
|
|
(temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) {
|
|
|
|
seed[temp] = !(results[temp] = elem);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add elements to results, through postFinder if defined
|
|
} else {
|
|
matcherOut = condense(
|
|
matcherOut === results ?
|
|
matcherOut.splice( preexisting, matcherOut.length ) :
|
|
matcherOut
|
|
);
|
|
if ( postFinder ) {
|
|
postFinder( null, results, matcherOut, xml );
|
|
} else {
|
|
push.apply( results, matcherOut );
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function matcherFromTokens( tokens ) {
|
|
var checkContext, matcher, j,
|
|
len = tokens.length,
|
|
leadingRelative = Expr.relative[ tokens[0].type ],
|
|
implicitRelative = leadingRelative || Expr.relative[" "],
|
|
i = leadingRelative ? 1 : 0,
|
|
|
|
// The foundational matcher ensures that elements are reachable from top-level context(s)
|
|
matchContext = addCombinator( function( elem ) {
|
|
return elem === checkContext;
|
|
}, implicitRelative, true ),
|
|
matchAnyContext = addCombinator( function( elem ) {
|
|
return indexOf( checkContext, elem ) > -1;
|
|
}, implicitRelative, true ),
|
|
matchers = [ function( elem, context, xml ) {
|
|
var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
|
|
(checkContext = context).nodeType ?
|
|
matchContext( elem, context, xml ) :
|
|
matchAnyContext( elem, context, xml ) );
|
|
// Avoid hanging onto element (issue #299)
|
|
checkContext = null;
|
|
return ret;
|
|
} ];
|
|
|
|
for ( ; i < len; i++ ) {
|
|
if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
|
|
matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
|
|
} else {
|
|
matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
|
|
|
|
// Return special upon seeing a positional matcher
|
|
if ( matcher[ expando ] ) {
|
|
// Find the next relative operator (if any) for proper handling
|
|
j = ++i;
|
|
for ( ; j < len; j++ ) {
|
|
if ( Expr.relative[ tokens[j].type ] ) {
|
|
break;
|
|
}
|
|
}
|
|
return setMatcher(
|
|
i > 1 && elementMatcher( matchers ),
|
|
i > 1 && toSelector(
|
|
// If the preceding token was a descendant combinator, insert an implicit any-element `*`
|
|
tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" })
|
|
).replace( rtrim, "$1" ),
|
|
matcher,
|
|
i < j && matcherFromTokens( tokens.slice( i, j ) ),
|
|
j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
|
|
j < len && toSelector( tokens )
|
|
);
|
|
}
|
|
matchers.push( matcher );
|
|
}
|
|
}
|
|
|
|
return elementMatcher( matchers );
|
|
}
|
|
|
|
function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
|
|
var bySet = setMatchers.length > 0,
|
|
byElement = elementMatchers.length > 0,
|
|
superMatcher = function( seed, context, xml, results, outermost ) {
|
|
var elem, j, matcher,
|
|
matchedCount = 0,
|
|
i = "0",
|
|
unmatched = seed && [],
|
|
setMatched = [],
|
|
contextBackup = outermostContext,
|
|
// We must always have either seed elements or outermost context
|
|
elems = seed || byElement && Expr.find["TAG"]( "*", outermost ),
|
|
// Use integer dirruns iff this is the outermost matcher
|
|
dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1),
|
|
len = elems.length;
|
|
|
|
if ( outermost ) {
|
|
outermostContext = context === document || context || outermost;
|
|
}
|
|
|
|
// Add elements passing elementMatchers directly to results
|
|
// Support: IE<9, Safari
|
|
// Tolerate NodeList properties (IE: "length"; Safari: <number>) matching elements by id
|
|
for ( ; i !== len && (elem = elems[i]) != null; i++ ) {
|
|
if ( byElement && elem ) {
|
|
j = 0;
|
|
if ( !context && elem.ownerDocument !== document ) {
|
|
setDocument( elem );
|
|
xml = !documentIsHTML;
|
|
}
|
|
while ( (matcher = elementMatchers[j++]) ) {
|
|
if ( matcher( elem, context || document, xml) ) {
|
|
results.push( elem );
|
|
break;
|
|
}
|
|
}
|
|
if ( outermost ) {
|
|
dirruns = dirrunsUnique;
|
|
}
|
|
}
|
|
|
|
// Track unmatched elements for set filters
|
|
if ( bySet ) {
|
|
// They will have gone through all possible matchers
|
|
if ( (elem = !matcher && elem) ) {
|
|
matchedCount--;
|
|
}
|
|
|
|
// Lengthen the array for every element, matched or not
|
|
if ( seed ) {
|
|
unmatched.push( elem );
|
|
}
|
|
}
|
|
}
|
|
|
|
// `i` is now the count of elements visited above, and adding it to `matchedCount`
|
|
// makes the latter nonnegative.
|
|
matchedCount += i;
|
|
|
|
// Apply set filters to unmatched elements
|
|
// NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount`
|
|
// equals `i`), unless we didn't visit _any_ elements in the above loop because we have
|
|
// no element matchers and no seed.
|
|
// Incrementing an initially-string "0" `i` allows `i` to remain a string only in that
|
|
// case, which will result in a "00" `matchedCount` that differs from `i` but is also
|
|
// numerically zero.
|
|
if ( bySet && i !== matchedCount ) {
|
|
j = 0;
|
|
while ( (matcher = setMatchers[j++]) ) {
|
|
matcher( unmatched, setMatched, context, xml );
|
|
}
|
|
|
|
if ( seed ) {
|
|
// Reintegrate element matches to eliminate the need for sorting
|
|
if ( matchedCount > 0 ) {
|
|
while ( i-- ) {
|
|
if ( !(unmatched[i] || setMatched[i]) ) {
|
|
setMatched[i] = pop.call( results );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Discard index placeholder values to get only actual matches
|
|
setMatched = condense( setMatched );
|
|
}
|
|
|
|
// Add matches to results
|
|
push.apply( results, setMatched );
|
|
|
|
// Seedless set matches succeeding multiple successful matchers stipulate sorting
|
|
if ( outermost && !seed && setMatched.length > 0 &&
|
|
( matchedCount + setMatchers.length ) > 1 ) {
|
|
|
|
Sizzle.uniqueSort( results );
|
|
}
|
|
}
|
|
|
|
// Override manipulation of globals by nested matchers
|
|
if ( outermost ) {
|
|
dirruns = dirrunsUnique;
|
|
outermostContext = contextBackup;
|
|
}
|
|
|
|
return unmatched;
|
|
};
|
|
|
|
return bySet ?
|
|
markFunction( superMatcher ) :
|
|
superMatcher;
|
|
}
|
|
|
|
compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) {
|
|
var i,
|
|
setMatchers = [],
|
|
elementMatchers = [],
|
|
cached = compilerCache[ selector + " " ];
|
|
|
|
if ( !cached ) {
|
|
// Generate a function of recursive functions that can be used to check each element
|
|
if ( !match ) {
|
|
match = tokenize( selector );
|
|
}
|
|
i = match.length;
|
|
while ( i-- ) {
|
|
cached = matcherFromTokens( match[i] );
|
|
if ( cached[ expando ] ) {
|
|
setMatchers.push( cached );
|
|
} else {
|
|
elementMatchers.push( cached );
|
|
}
|
|
}
|
|
|
|
// Cache the compiled function
|
|
cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
|
|
|
|
// Save selector and tokenization
|
|
cached.selector = selector;
|
|
}
|
|
return cached;
|
|
};
|
|
|
|
/**
|
|
* A low-level selection function that works with Sizzle's compiled
|
|
* selector functions
|
|
* @param {String|Function} selector A selector or a pre-compiled
|
|
* selector function built with Sizzle.compile
|
|
* @param {Element} context
|
|
* @param {Array} [results]
|
|
* @param {Array} [seed] A set of elements to match against
|
|
*/
|
|
select = Sizzle.select = function( selector, context, results, seed ) {
|
|
var i, tokens, token, type, find,
|
|
compiled = typeof selector === "function" && selector,
|
|
match = !seed && tokenize( (selector = compiled.selector || selector) );
|
|
|
|
results = results || [];
|
|
|
|
// Try to minimize operations if there is only one selector in the list and no seed
|
|
// (the latter of which guarantees us context)
|
|
if ( match.length === 1 ) {
|
|
|
|
// Reduce context if the leading compound selector is an ID
|
|
tokens = match[0] = match[0].slice( 0 );
|
|
if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
|
|
context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[1].type ] ) {
|
|
|
|
context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];
|
|
if ( !context ) {
|
|
return results;
|
|
|
|
// Precompiled matchers will still verify ancestry, so step up a level
|
|
} else if ( compiled ) {
|
|
context = context.parentNode;
|
|
}
|
|
|
|
selector = selector.slice( tokens.shift().value.length );
|
|
}
|
|
|
|
// Fetch a seed set for right-to-left matching
|
|
i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length;
|
|
while ( i-- ) {
|
|
token = tokens[i];
|
|
|
|
// Abort if we hit a combinator
|
|
if ( Expr.relative[ (type = token.type) ] ) {
|
|
break;
|
|
}
|
|
if ( (find = Expr.find[ type ]) ) {
|
|
// Search, expanding context for leading sibling combinators
|
|
if ( (seed = find(
|
|
token.matches[0].replace( runescape, funescape ),
|
|
rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context
|
|
)) ) {
|
|
|
|
// If seed is empty or no tokens remain, we can return early
|
|
tokens.splice( i, 1 );
|
|
selector = seed.length && toSelector( tokens );
|
|
if ( !selector ) {
|
|
push.apply( results, seed );
|
|
return results;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Compile and execute a filtering function if one is not provided
|
|
// Provide `match` to avoid retokenization if we modified the selector above
|
|
( compiled || compile( selector, match ) )(
|
|
seed,
|
|
context,
|
|
!documentIsHTML,
|
|
results,
|
|
!context || rsibling.test( selector ) && testContext( context.parentNode ) || context
|
|
);
|
|
return results;
|
|
};
|
|
|
|
// One-time assignments
|
|
|
|
// Sort stability
|
|
support.sortStable = expando.split("").sort( sortOrder ).join("") === expando;
|
|
|
|
// Support: Chrome 14-35+
|
|
// Always assume duplicates if they aren't passed to the comparison function
|
|
support.detectDuplicates = !!hasDuplicate;
|
|
|
|
// Initialize against the default document
|
|
setDocument();
|
|
|
|
// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
|
|
// Detached nodes confoundingly follow *each other*
|
|
support.sortDetached = assert(function( el ) {
|
|
// Should return 1, but returns 4 (following)
|
|
return el.compareDocumentPosition( document.createElement("fieldset") ) & 1;
|
|
});
|
|
|
|
// Support: IE<8
|
|
// Prevent attribute/property "interpolation"
|
|
// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
|
|
if ( !assert(function( el ) {
|
|
el.innerHTML = "<a href='#'></a>";
|
|
return el.firstChild.getAttribute("href") === "#" ;
|
|
}) ) {
|
|
addHandle( "type|href|height|width", function( elem, name, isXML ) {
|
|
if ( !isXML ) {
|
|
return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 );
|
|
}
|
|
});
|
|
}
|
|
|
|
// Support: IE<9
|
|
// Use defaultValue in place of getAttribute("value")
|
|
if ( !support.attributes || !assert(function( el ) {
|
|
el.innerHTML = "<input/>";
|
|
el.firstChild.setAttribute( "value", "" );
|
|
return el.firstChild.getAttribute( "value" ) === "";
|
|
}) ) {
|
|
addHandle( "value", function( elem, name, isXML ) {
|
|
if ( !isXML && elem.nodeName.toLowerCase() === "input" ) {
|
|
return elem.defaultValue;
|
|
}
|
|
});
|
|
}
|
|
|
|
// Support: IE<9
|
|
// Use getAttributeNode to fetch booleans when getAttribute lies
|
|
if ( !assert(function( el ) {
|
|
return el.getAttribute("disabled") == null;
|
|
}) ) {
|
|
addHandle( booleans, function( elem, name, isXML ) {
|
|
var val;
|
|
if ( !isXML ) {
|
|
return elem[ name ] === true ? name.toLowerCase() :
|
|
(val = elem.getAttributeNode( name )) && val.specified ?
|
|
val.value :
|
|
null;
|
|
}
|
|
});
|
|
}
|
|
|
|
return Sizzle;
|
|
|
|
})( window );
|
|
|
|
|
|
|
|
jQuery.find = Sizzle;
|
|
jQuery.expr = Sizzle.selectors;
|
|
|
|
// Deprecated
|
|
jQuery.expr[ ":" ] = jQuery.expr.pseudos;
|
|
jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort;
|
|
jQuery.text = Sizzle.getText;
|
|
jQuery.isXMLDoc = Sizzle.isXML;
|
|
jQuery.contains = Sizzle.contains;
|
|
jQuery.escapeSelector = Sizzle.escape;
|
|
|
|
|
|
|
|
|
|
var dir = function( elem, dir, until ) {
|
|
var matched = [],
|
|
truncate = until !== undefined;
|
|
|
|
while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) {
|
|
if ( elem.nodeType === 1 ) {
|
|
if ( truncate && jQuery( elem ).is( until ) ) {
|
|
break;
|
|
}
|
|
matched.push( elem );
|
|
}
|
|
}
|
|
return matched;
|
|
};
|
|
|
|
|
|
var siblings = function( n, elem ) {
|
|
var matched = [];
|
|
|
|
for ( ; n; n = n.nextSibling ) {
|
|
if ( n.nodeType === 1 && n !== elem ) {
|
|
matched.push( n );
|
|
}
|
|
}
|
|
|
|
return matched;
|
|
};
|
|
|
|
|
|
var rneedsContext = jQuery.expr.match.needsContext;
|
|
|
|
|
|
|
|
function nodeName( elem, name ) {
|
|
|
|
return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
|
|
|
|
};
|
|
var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i );
|
|
|
|
|
|
|
|
// Implement the identical functionality for filter and not
|
|
function winnow( elements, qualifier, not ) {
|
|
if ( isFunction( qualifier ) ) {
|
|
return jQuery.grep( elements, function( elem, i ) {
|
|
return !!qualifier.call( elem, i, elem ) !== not;
|
|
} );
|
|
}
|
|
|
|
// Single element
|
|
if ( qualifier.nodeType ) {
|
|
return jQuery.grep( elements, function( elem ) {
|
|
return ( elem === qualifier ) !== not;
|
|
} );
|
|
}
|
|
|
|
// Arraylike of elements (jQuery, arguments, Array)
|
|
if ( typeof qualifier !== "string" ) {
|
|
return jQuery.grep( elements, function( elem ) {
|
|
return ( indexOf.call( qualifier, elem ) > -1 ) !== not;
|
|
} );
|
|
}
|
|
|
|
// Filtered directly for both simple and complex selectors
|
|
return jQuery.filter( qualifier, elements, not );
|
|
}
|
|
|
|
jQuery.filter = function( expr, elems, not ) {
|
|
var elem = elems[ 0 ];
|
|
|
|
if ( not ) {
|
|
expr = ":not(" + expr + ")";
|
|
}
|
|
|
|
if ( elems.length === 1 && elem.nodeType === 1 ) {
|
|
return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [];
|
|
}
|
|
|
|
return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
|
|
return elem.nodeType === 1;
|
|
} ) );
|
|
};
|
|
|
|
jQuery.fn.extend( {
|
|
find: function( selector ) {
|
|
var i, ret,
|
|
len = this.length,
|
|
self = this;
|
|
|
|
if ( typeof selector !== "string" ) {
|
|
return this.pushStack( jQuery( selector ).filter( function() {
|
|
for ( i = 0; i < len; i++ ) {
|
|
if ( jQuery.contains( self[ i ], this ) ) {
|
|
return true;
|
|
}
|
|
}
|
|
} ) );
|
|
}
|
|
|
|
ret = this.pushStack( [] );
|
|
|
|
for ( i = 0; i < len; i++ ) {
|
|
jQuery.find( selector, self[ i ], ret );
|
|
}
|
|
|
|
return len > 1 ? jQuery.uniqueSort( ret ) : ret;
|
|
},
|
|
filter: function( selector ) {
|
|
return this.pushStack( winnow( this, selector || [], false ) );
|
|
},
|
|
not: function( selector ) {
|
|
return this.pushStack( winnow( this, selector || [], true ) );
|
|
},
|
|
is: function( selector ) {
|
|
return !!winnow(
|
|
this,
|
|
|
|
// If this is a positional/relative selector, check membership in the returned set
|
|
// so $("p:first").is("p:last") won't return true for a doc with two "p".
|
|
typeof selector === "string" && rneedsContext.test( selector ) ?
|
|
jQuery( selector ) :
|
|
selector || [],
|
|
false
|
|
).length;
|
|
}
|
|
} );
|
|
|
|
|
|
// Initialize a jQuery object
|
|
|
|
|
|
// A central reference to the root jQuery(document)
|
|
var rootjQuery,
|
|
|
|
// A simple way to check for HTML strings
|
|
// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
|
|
// Strict HTML recognition (#11290: must start with <)
|
|
// Shortcut simple #id case for speed
|
|
rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,
|
|
|
|
init = jQuery.fn.init = function( selector, context, root ) {
|
|
var match, elem;
|
|
|
|
// HANDLE: $(""), $(null), $(undefined), $(false)
|
|
if ( !selector ) {
|
|
return this;
|
|
}
|
|
|
|
// Method init() accepts an alternate rootjQuery
|
|
// so migrate can support jQuery.sub (gh-2101)
|
|
root = root || rootjQuery;
|
|
|
|
// Handle HTML strings
|
|
if ( typeof selector === "string" ) {
|
|
if ( selector[ 0 ] === "<" &&
|
|
selector[ selector.length - 1 ] === ">" &&
|
|
selector.length >= 3 ) {
|
|
|
|
// Assume that strings that start and end with <> are HTML and skip the regex check
|
|
match = [ null, selector, null ];
|
|
|
|
} else {
|
|
match = rquickExpr.exec( selector );
|
|
}
|
|
|
|
// Match html or make sure no context is specified for #id
|
|
if ( match && ( match[ 1 ] || !context ) ) {
|
|
|
|
// HANDLE: $(html) -> $(array)
|
|
if ( match[ 1 ] ) {
|
|
context = context instanceof jQuery ? context[ 0 ] : context;
|
|
|
|
// Option to run scripts is true for back-compat
|
|
// Intentionally let the error be thrown if parseHTML is not present
|
|
jQuery.merge( this, jQuery.parseHTML(
|
|
match[ 1 ],
|
|
context && context.nodeType ? context.ownerDocument || context : document,
|
|
true
|
|
) );
|
|
|
|
// HANDLE: $(html, props)
|
|
if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) {
|
|
for ( match in context ) {
|
|
|
|
// Properties of context are called as methods if possible
|
|
if ( isFunction( this[ match ] ) ) {
|
|
this[ match ]( context[ match ] );
|
|
|
|
// ...and otherwise set as attributes
|
|
} else {
|
|
this.attr( match, context[ match ] );
|
|
}
|
|
}
|
|
}
|
|
|
|
return this;
|
|
|
|
// HANDLE: $(#id)
|
|
} else {
|
|
elem = document.getElementById( match[ 2 ] );
|
|
|
|
if ( elem ) {
|
|
|
|
// Inject the element directly into the jQuery object
|
|
this[ 0 ] = elem;
|
|
this.length = 1;
|
|
}
|
|
return this;
|
|
}
|
|
|
|
// HANDLE: $(expr, $(...))
|
|
} else if ( !context || context.jquery ) {
|
|
return ( context || root ).find( selector );
|
|
|
|
// HANDLE: $(expr, context)
|
|
// (which is just equivalent to: $(context).find(expr)
|
|
} else {
|
|
return this.constructor( context ).find( selector );
|
|
}
|
|
|
|
// HANDLE: $(DOMElement)
|
|
} else if ( selector.nodeType ) {
|
|
this[ 0 ] = selector;
|
|
this.length = 1;
|
|
return this;
|
|
|
|
// HANDLE: $(function)
|
|
// Shortcut for document ready
|
|
} else if ( isFunction( selector ) ) {
|
|
return root.ready !== undefined ?
|
|
root.ready( selector ) :
|
|
|
|
// Execute immediately if ready is not present
|
|
selector( jQuery );
|
|
}
|
|
|
|
return jQuery.makeArray( selector, this );
|
|
};
|
|
|
|
// Give the init function the jQuery prototype for later instantiation
|
|
init.prototype = jQuery.fn;
|
|
|
|
// Initialize central reference
|
|
rootjQuery = jQuery( document );
|
|
|
|
|
|
var rparentsprev = /^(?:parents|prev(?:Until|All))/,
|
|
|
|
// Methods guaranteed to produce a unique set when starting from a unique set
|
|
guaranteedUnique = {
|
|
children: true,
|
|
contents: true,
|
|
next: true,
|
|
prev: true
|
|
};
|
|
|
|
jQuery.fn.extend( {
|
|
has: function( target ) {
|
|
var targets = jQuery( target, this ),
|
|
l = targets.length;
|
|
|
|
return this.filter( function() {
|
|
var i = 0;
|
|
for ( ; i < l; i++ ) {
|
|
if ( jQuery.contains( this, targets[ i ] ) ) {
|
|
return true;
|
|
}
|
|
}
|
|
} );
|
|
},
|
|
|
|
closest: function( selectors, context ) {
|
|
var cur,
|
|
i = 0,
|
|
l = this.length,
|
|
matched = [],
|
|
targets = typeof selectors !== "string" && jQuery( selectors );
|
|
|
|
// Positional selectors never match, since there's no _selection_ context
|
|
if ( !rneedsContext.test( selectors ) ) {
|
|
for ( ; i < l; i++ ) {
|
|
for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) {
|
|
|
|
// Always skip document fragments
|
|
if ( cur.nodeType < 11 && ( targets ?
|
|
targets.index( cur ) > -1 :
|
|
|
|
// Don't pass non-elements to Sizzle
|
|
cur.nodeType === 1 &&
|
|
jQuery.find.matchesSelector( cur, selectors ) ) ) {
|
|
|
|
matched.push( cur );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched );
|
|
},
|
|
|
|
// Determine the position of an element within the set
|
|
index: function( elem ) {
|
|
|
|
// No argument, return index in parent
|
|
if ( !elem ) {
|
|
return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1;
|
|
}
|
|
|
|
// Index in selector
|
|
if ( typeof elem === "string" ) {
|
|
return indexOf.call( jQuery( elem ), this[ 0 ] );
|
|
}
|
|
|
|
// Locate the position of the desired element
|
|
return indexOf.call( this,
|
|
|
|
// If it receives a jQuery object, the first element is used
|
|
elem.jquery ? elem[ 0 ] : elem
|
|
);
|
|
},
|
|
|
|
add: function( selector, context ) {
|
|
return this.pushStack(
|
|
jQuery.uniqueSort(
|
|
jQuery.merge( this.get(), jQuery( selector, context ) )
|
|
)
|
|
);
|
|
},
|
|
|
|
addBack: function( selector ) {
|
|
return this.add( selector == null ?
|
|
this.prevObject : this.prevObject.filter( selector )
|
|
);
|
|
}
|
|
} );
|
|
|
|
function sibling( cur, dir ) {
|
|
while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {}
|
|
return cur;
|
|
}
|
|
|
|
jQuery.each( {
|
|
parent: function( elem ) {
|
|
var parent = elem.parentNode;
|
|
return parent && parent.nodeType !== 11 ? parent : null;
|
|
},
|
|
parents: function( elem ) {
|
|
return dir( elem, "parentNode" );
|
|
},
|
|
parentsUntil: function( elem, i, until ) {
|
|
return dir( elem, "parentNode", until );
|
|
},
|
|
next: function( elem ) {
|
|
return sibling( elem, "nextSibling" );
|
|
},
|
|
prev: function( elem ) {
|
|
return sibling( elem, "previousSibling" );
|
|
},
|
|
nextAll: function( elem ) {
|
|
return dir( elem, "nextSibling" );
|
|
},
|
|
prevAll: function( elem ) {
|
|
return dir( elem, "previousSibling" );
|
|
},
|
|
nextUntil: function( elem, i, until ) {
|
|
return dir( elem, "nextSibling", until );
|
|
},
|
|
prevUntil: function( elem, i, until ) {
|
|
return dir( elem, "previousSibling", until );
|
|
},
|
|
siblings: function( elem ) {
|
|
return siblings( ( elem.parentNode || {} ).firstChild, elem );
|
|
},
|
|
children: function( elem ) {
|
|
return siblings( elem.firstChild );
|
|
},
|
|
contents: function( elem ) {
|
|
if ( typeof elem.contentDocument !== "undefined" ) {
|
|
return elem.contentDocument;
|
|
}
|
|
|
|
// Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only
|
|
// Treat the template element as a regular one in browsers that
|
|
// don't support it.
|
|
if ( nodeName( elem, "template" ) ) {
|
|
elem = elem.content || elem;
|
|
}
|
|
|
|
return jQuery.merge( [], elem.childNodes );
|
|
}
|
|
}, function( name, fn ) {
|
|
jQuery.fn[ name ] = function( until, selector ) {
|
|
var matched = jQuery.map( this, fn, until );
|
|
|
|
if ( name.slice( -5 ) !== "Until" ) {
|
|
selector = until;
|
|
}
|
|
|
|
if ( selector && typeof selector === "string" ) {
|
|
matched = jQuery.filter( selector, matched );
|
|
}
|
|
|
|
if ( this.length > 1 ) {
|
|
|
|
// Remove duplicates
|
|
if ( !guaranteedUnique[ name ] ) {
|
|
jQuery.uniqueSort( matched );
|
|
}
|
|
|
|
// Reverse order for parents* and prev-derivatives
|
|
if ( rparentsprev.test( name ) ) {
|
|
matched.reverse();
|
|
}
|
|
}
|
|
|
|
return this.pushStack( matched );
|
|
};
|
|
} );
|
|
var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g );
|
|
|
|
|
|
|
|
// Convert String-formatted options into Object-formatted ones
|
|
function createOptions( options ) {
|
|
var object = {};
|
|
jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) {
|
|
object[ flag ] = true;
|
|
} );
|
|
return object;
|
|
}
|
|
|
|
/*
|
|
* Create a callback list using the following parameters:
|
|
*
|
|
* options: an optional list of space-separated options that will change how
|
|
* the callback list behaves or a more traditional option object
|
|
*
|
|
* By default a callback list will act like an event callback list and can be
|
|
* "fired" multiple times.
|
|
*
|
|
* Possible options:
|
|
*
|
|
* once: will ensure the callback list can only be fired once (like a Deferred)
|
|
*
|
|
* memory: will keep track of previous values and will call any callback added
|
|
* after the list has been fired right away with the latest "memorized"
|
|
* values (like a Deferred)
|
|
*
|
|
* unique: will ensure a callback can only be added once (no duplicate in the list)
|
|
*
|
|
* stopOnFalse: interrupt callings when a callback returns false
|
|
*
|
|
*/
|
|
jQuery.Callbacks = function( options ) {
|
|
|
|
// Convert options from String-formatted to Object-formatted if needed
|
|
// (we check in cache first)
|
|
options = typeof options === "string" ?
|
|
createOptions( options ) :
|
|
jQuery.extend( {}, options );
|
|
|
|
var // Flag to know if list is currently firing
|
|
firing,
|
|
|
|
// Last fire value for non-forgettable lists
|
|
memory,
|
|
|
|
// Flag to know if list was already fired
|
|
fired,
|
|
|
|
// Flag to prevent firing
|
|
locked,
|
|
|
|
// Actual callback list
|
|
list = [],
|
|
|
|
// Queue of execution data for repeatable lists
|
|
queue = [],
|
|
|
|
// Index of currently firing callback (modified by add/remove as needed)
|
|
firingIndex = -1,
|
|
|
|
// Fire callbacks
|
|
fire = function() {
|
|
|
|
// Enforce single-firing
|
|
locked = locked || options.once;
|
|
|
|
// Execute callbacks for all pending executions,
|
|
// respecting firingIndex overrides and runtime changes
|
|
fired = firing = true;
|
|
for ( ; queue.length; firingIndex = -1 ) {
|
|
memory = queue.shift();
|
|
while ( ++firingIndex < list.length ) {
|
|
|
|
// Run callback and check for early termination
|
|
if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false &&
|
|
options.stopOnFalse ) {
|
|
|
|
// Jump to end and forget the data so .add doesn't re-fire
|
|
firingIndex = list.length;
|
|
memory = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Forget the data if we're done with it
|
|
if ( !options.memory ) {
|
|
memory = false;
|
|
}
|
|
|
|
firing = false;
|
|
|
|
// Clean up if we're done firing for good
|
|
if ( locked ) {
|
|
|
|
// Keep an empty list if we have data for future add calls
|
|
if ( memory ) {
|
|
list = [];
|
|
|
|
// Otherwise, this object is spent
|
|
} else {
|
|
list = "";
|
|
}
|
|
}
|
|
},
|
|
|
|
// Actual Callbacks object
|
|
self = {
|
|
|
|
// Add a callback or a collection of callbacks to the list
|
|
add: function() {
|
|
if ( list ) {
|
|
|
|
// If we have memory from a past run, we should fire after adding
|
|
if ( memory && !firing ) {
|
|
firingIndex = list.length - 1;
|
|
queue.push( memory );
|
|
}
|
|
|
|
( function add( args ) {
|
|
jQuery.each( args, function( _, arg ) {
|
|
if ( isFunction( arg ) ) {
|
|
if ( !options.unique || !self.has( arg ) ) {
|
|
list.push( arg );
|
|
}
|
|
} else if ( arg && arg.length && toType( arg ) !== "string" ) {
|
|
|
|
// Inspect recursively
|
|
add( arg );
|
|
}
|
|
} );
|
|
} )( arguments );
|
|
|
|
if ( memory && !firing ) {
|
|
fire();
|
|
}
|
|
}
|
|
return this;
|
|
},
|
|
|
|
// Remove a callback from the list
|
|
remove: function() {
|
|
jQuery.each( arguments, function( _, arg ) {
|
|
var index;
|
|
while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
|
|
list.splice( index, 1 );
|
|
|
|
// Handle firing indexes
|
|
if ( index <= firingIndex ) {
|
|
firingIndex--;
|
|
}
|
|
}
|
|
} );
|
|
return this;
|
|
},
|
|
|
|
// Check if a given callback is in the list.
|
|
// If no argument is given, return whether or not list has callbacks attached.
|
|
has: function( fn ) {
|
|
return fn ?
|
|
jQuery.inArray( fn, list ) > -1 :
|
|
list.length > 0;
|
|
},
|
|
|
|
// Remove all callbacks from the list
|
|
empty: function() {
|
|
if ( list ) {
|
|
list = [];
|
|
}
|
|
return this;
|
|
},
|
|
|
|
// Disable .fire and .add
|
|
// Abort any current/pending executions
|
|
// Clear all callbacks and values
|
|
disable: function() {
|
|
locked = queue = [];
|
|
list = memory = "";
|
|
return this;
|
|
},
|
|
disabled: function() {
|
|
return !list;
|
|
},
|
|
|
|
// Disable .fire
|
|
// Also disable .add unless we have memory (since it would have no effect)
|
|
// Abort any pending executions
|
|
lock: function() {
|
|
locked = queue = [];
|
|
if ( !memory && !firing ) {
|
|
list = memory = "";
|
|
}
|
|
return this;
|
|
},
|
|
locked: function() {
|
|
return !!locked;
|
|
},
|
|
|
|
// Call all callbacks with the given context and arguments
|
|
fireWith: function( context, args ) {
|
|
if ( !locked ) {
|
|
args = args || [];
|
|
args = [ context, args.slice ? args.slice() : args ];
|
|
queue.push( args );
|
|
if ( !firing ) {
|
|
fire();
|
|
}
|
|
}
|
|
return this;
|
|
},
|
|
|
|
// Call all the callbacks with the given arguments
|
|
fire: function() {
|
|
self.fireWith( this, arguments );
|
|
return this;
|
|
},
|
|
|
|
// To know if the callbacks have already been called at least once
|
|
fired: function() {
|
|
return !!fired;
|
|
}
|
|
};
|
|
|
|
return self;
|
|
};
|
|
|
|
|
|
function Identity( v ) {
|
|
return v;
|
|
}
|
|
function Thrower( ex ) {
|
|
throw ex;
|
|
}
|
|
|
|
function adoptValue( value, resolve, reject, noValue ) {
|
|
var method;
|
|
|
|
try {
|
|
|
|
// Check for promise aspect first to privilege synchronous behavior
|
|
if ( value && isFunction( ( method = value.promise ) ) ) {
|
|
method.call( value ).done( resolve ).fail( reject );
|
|
|
|
// Other thenables
|
|
} else if ( value && isFunction( ( method = value.then ) ) ) {
|
|
method.call( value, resolve, reject );
|
|
|
|
// Other non-thenables
|
|
} else {
|
|
|
|
// Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer:
|
|
// * false: [ value ].slice( 0 ) => resolve( value )
|
|
// * true: [ value ].slice( 1 ) => resolve()
|
|
resolve.apply( undefined, [ value ].slice( noValue ) );
|
|
}
|
|
|
|
// For Promises/A+, convert exceptions into rejections
|
|
// Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in
|
|
// Deferred#then to conditionally suppress rejection.
|
|
} catch ( value ) {
|
|
|
|
// Support: Android 4.0 only
|
|
// Strict mode functions invoked without .call/.apply get global-object context
|
|
reject.apply( undefined, [ value ] );
|
|
}
|
|
}
|
|
|
|
jQuery.extend( {
|
|
|
|
Deferred: function( func ) {
|
|
var tuples = [
|
|
|
|
// action, add listener, callbacks,
|
|
// ... .then handlers, argument index, [final state]
|
|
[ "notify", "progress", jQuery.Callbacks( "memory" ),
|
|
jQuery.Callbacks( "memory" ), 2 ],
|
|
[ "resolve", "done", jQuery.Callbacks( "once memory" ),
|
|
jQuery.Callbacks( "once memory" ), 0, "resolved" ],
|
|
[ "reject", "fail", jQuery.Callbacks( "once memory" ),
|
|
jQuery.Callbacks( "once memory" ), 1, "rejected" ]
|
|
],
|
|
state = "pending",
|
|
promise = {
|
|
state: function() {
|
|
return state;
|
|
},
|
|
always: function() {
|
|
deferred.done( arguments ).fail( arguments );
|
|
return this;
|
|
},
|
|
"catch": function( fn ) {
|
|
return promise.then( null, fn );
|
|
},
|
|
|
|
// Keep pipe for back-compat
|
|
pipe: function( /* fnDone, fnFail, fnProgress */ ) {
|
|
var fns = arguments;
|
|
|
|
return jQuery.Deferred( function( newDefer ) {
|
|
jQuery.each( tuples, function( i, tuple ) {
|
|
|
|
// Map tuples (progress, done, fail) to arguments (done, fail, progress)
|
|
var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ];
|
|
|
|
// deferred.progress(function() { bind to newDefer or newDefer.notify })
|
|
// deferred.done(function() { bind to newDefer or newDefer.resolve })
|
|
// deferred.fail(function() { bind to newDefer or newDefer.reject })
|
|
deferred[ tuple[ 1 ] ]( function() {
|
|
var returned = fn && fn.apply( this, arguments );
|
|
if ( returned && isFunction( returned.promise ) ) {
|
|
returned.promise()
|
|
.progress( newDefer.notify )
|
|
.done( newDefer.resolve )
|
|
.fail( newDefer.reject );
|
|
} else {
|
|
newDefer[ tuple[ 0 ] + "With" ](
|
|
this,
|
|
fn ? [ returned ] : arguments
|
|
);
|
|
}
|
|
} );
|
|
} );
|
|
fns = null;
|
|
} ).promise();
|
|
},
|
|
then: function( onFulfilled, onRejected, onProgress ) {
|
|
var maxDepth = 0;
|
|
function resolve( depth, deferred, handler, special ) {
|
|
return function() {
|
|
var that = this,
|
|
args = arguments,
|
|
mightThrow = function() {
|
|
var returned, then;
|
|
|
|
// Support: Promises/A+ section 2.3.3.3.3
|
|
// https://promisesaplus.com/#point-59
|
|
// Ignore double-resolution attempts
|
|
if ( depth < maxDepth ) {
|
|
return;
|
|
}
|
|
|
|
returned = handler.apply( that, args );
|
|
|
|
// Support: Promises/A+ section 2.3.1
|
|
// https://promisesaplus.com/#point-48
|
|
if ( returned === deferred.promise() ) {
|
|
throw new TypeError( "Thenable self-resolution" );
|
|
}
|
|
|
|
// Support: Promises/A+ sections 2.3.3.1, 3.5
|
|
// https://promisesaplus.com/#point-54
|
|
// https://promisesaplus.com/#point-75
|
|
// Retrieve `then` only once
|
|
then = returned &&
|
|
|
|
// Support: Promises/A+ section 2.3.4
|
|
// https://promisesaplus.com/#point-64
|
|
// Only check objects and functions for thenability
|
|
( typeof returned === "object" ||
|
|
typeof returned === "function" ) &&
|
|
returned.then;
|
|
|
|
// Handle a returned thenable
|
|
if ( isFunction( then ) ) {
|
|
|
|
// Special processors (notify) just wait for resolution
|
|
if ( special ) {
|
|
then.call(
|
|
returned,
|
|
resolve( maxDepth, deferred, Identity, special ),
|
|
resolve( maxDepth, deferred, Thrower, special )
|
|
);
|
|
|
|
// Normal processors (resolve) also hook into progress
|
|
} else {
|
|
|
|
// ...and disregard older resolution values
|
|
maxDepth++;
|
|
|
|
then.call(
|
|
returned,
|
|
resolve( maxDepth, deferred, Identity, special ),
|
|
resolve( maxDepth, deferred, Thrower, special ),
|
|
resolve( maxDepth, deferred, Identity,
|
|
deferred.notifyWith )
|
|
);
|
|
}
|
|
|
|
// Handle all other returned values
|
|
} else {
|
|
|
|
// Only substitute handlers pass on context
|
|
// and multiple values (non-spec behavior)
|
|
if ( handler !== Identity ) {
|
|
that = undefined;
|
|
args = [ returned ];
|
|
}
|
|
|
|
// Process the value(s)
|
|
// Default process is resolve
|
|
( special || deferred.resolveWith )( that, args );
|
|
}
|
|
},
|
|
|
|
// Only normal processors (resolve) catch and reject exceptions
|
|
process = special ?
|
|
mightThrow :
|
|
function() {
|
|
try {
|
|
mightThrow();
|
|
} catch ( e ) {
|
|
|
|
if ( jQuery.Deferred.exceptionHook ) {
|
|
jQuery.Deferred.exceptionHook( e,
|
|
process.stackTrace );
|
|
}
|
|
|
|
// Support: Promises/A+ section 2.3.3.3.4.1
|
|
// https://promisesaplus.com/#point-61
|
|
// Ignore post-resolution exceptions
|
|
if ( depth + 1 >= maxDepth ) {
|
|
|
|
// Only substitute handlers pass on context
|
|
// and multiple values (non-spec behavior)
|
|
if ( handler !== Thrower ) {
|
|
that = undefined;
|
|
args = [ e ];
|
|
}
|
|
|
|
deferred.rejectWith( that, args );
|
|
}
|
|
}
|
|
};
|
|
|
|
// Support: Promises/A+ section 2.3.3.3.1
|
|
// https://promisesaplus.com/#point-57
|
|
// Re-resolve promises immediately to dodge false rejection from
|
|
// subsequent errors
|
|
if ( depth ) {
|
|
process();
|
|
} else {
|
|
|
|
// Call an optional hook to record the stack, in case of exception
|
|
// since it's otherwise lost when execution goes async
|
|
if ( jQuery.Deferred.getStackHook ) {
|
|
process.stackTrace = jQuery.Deferred.getStackHook();
|
|
}
|
|
window.setTimeout( process );
|
|
}
|
|
};
|
|
}
|
|
|
|
return jQuery.Deferred( function( newDefer ) {
|
|
|
|
// progress_handlers.add( ... )
|
|
tuples[ 0 ][ 3 ].add(
|
|
resolve(
|
|
0,
|
|
newDefer,
|
|
isFunction( onProgress ) ?
|
|
onProgress :
|
|
Identity,
|
|
newDefer.notifyWith
|
|
)
|
|
);
|
|
|
|
// fulfilled_handlers.add( ... )
|
|
tuples[ 1 ][ 3 ].add(
|
|
resolve(
|
|
0,
|
|
newDefer,
|
|
isFunction( onFulfilled ) ?
|
|
onFulfilled :
|
|
Identity
|
|
)
|
|
);
|
|
|
|
// rejected_handlers.add( ... )
|
|
tuples[ 2 ][ 3 ].add(
|
|
resolve(
|
|
0,
|
|
newDefer,
|
|
isFunction( onRejected ) ?
|
|
onRejected :
|
|
Thrower
|
|
)
|
|
);
|
|
} ).promise();
|
|
},
|
|
|
|
// Get a promise for this deferred
|
|
// If obj is provided, the promise aspect is added to the object
|
|
promise: function( obj ) {
|
|
return obj != null ? jQuery.extend( obj, promise ) : promise;
|
|
}
|
|
},
|
|
deferred = {};
|
|
|
|
// Add list-specific methods
|
|
jQuery.each( tuples, function( i, tuple ) {
|
|
var list = tuple[ 2 ],
|
|
stateString = tuple[ 5 ];
|
|
|
|
// promise.progress = list.add
|
|
// promise.done = list.add
|
|
// promise.fail = list.add
|
|
promise[ tuple[ 1 ] ] = list.add;
|
|
|
|
// Handle state
|
|
if ( stateString ) {
|
|
list.add(
|
|
function() {
|
|
|
|
// state = "resolved" (i.e., fulfilled)
|
|
// state = "rejected"
|
|
state = stateString;
|
|
},
|
|
|
|
// rejected_callbacks.disable
|
|
// fulfilled_callbacks.disable
|
|
tuples[ 3 - i ][ 2 ].disable,
|
|
|
|
// rejected_handlers.disable
|
|
// fulfilled_handlers.disable
|
|
tuples[ 3 - i ][ 3 ].disable,
|
|
|
|
// progress_callbacks.lock
|
|
tuples[ 0 ][ 2 ].lock,
|
|
|
|
// progress_handlers.lock
|
|
tuples[ 0 ][ 3 ].lock
|
|
);
|
|
}
|
|
|
|
// progress_handlers.fire
|
|
// fulfilled_handlers.fire
|
|
// rejected_handlers.fire
|
|
list.add( tuple[ 3 ].fire );
|
|
|
|
// deferred.notify = function() { deferred.notifyWith(...) }
|
|
// deferred.resolve = function() { deferred.resolveWith(...) }
|
|
// deferred.reject = function() { deferred.rejectWith(...) }
|
|
deferred[ tuple[ 0 ] ] = function() {
|
|
deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments );
|
|
return this;
|
|
};
|
|
|
|
// deferred.notifyWith = list.fireWith
|
|
// deferred.resolveWith = list.fireWith
|
|
// deferred.rejectWith = list.fireWith
|
|
deferred[ tuple[ 0 ] + "With" ] = list.fireWith;
|
|
} );
|
|
|
|
// Make the deferred a promise
|
|
promise.promise( deferred );
|
|
|
|
// Call given func if any
|
|
if ( func ) {
|
|
func.call( deferred, deferred );
|
|
}
|
|
|
|
// All done!
|
|
return deferred;
|
|
},
|
|
|
|
// Deferred helper
|
|
when: function( singleValue ) {
|
|
var
|
|
|
|
// count of uncompleted subordinates
|
|
remaining = arguments.length,
|
|
|
|
// count of unprocessed arguments
|
|
i = remaining,
|
|
|
|
// subordinate fulfillment data
|
|
resolveContexts = Array( i ),
|
|
resolveValues = slice.call( arguments ),
|
|
|
|
// the master Deferred
|
|
master = jQuery.Deferred(),
|
|
|
|
// subordinate callback factory
|
|
updateFunc = function( i ) {
|
|
return function( value ) {
|
|
resolveContexts[ i ] = this;
|
|
resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
|
|
if ( !( --remaining ) ) {
|
|
master.resolveWith( resolveContexts, resolveValues );
|
|
}
|
|
};
|
|
};
|
|
|
|
// Single- and empty arguments are adopted like Promise.resolve
|
|
if ( remaining <= 1 ) {
|
|
adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject,
|
|
!remaining );
|
|
|
|
// Use .then() to unwrap secondary thenables (cf. gh-3000)
|
|
if ( master.state() === "pending" ||
|
|
isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) {
|
|
|
|
return master.then();
|
|
}
|
|
}
|
|
|
|
// Multiple arguments are aggregated like Promise.all array elements
|
|
while ( i-- ) {
|
|
adoptValue( resolveValues[ i ], updateFunc( i ), master.reject );
|
|
}
|
|
|
|
return master.promise();
|
|
}
|
|
} );
|
|
|
|
|
|
// These usually indicate a programmer mistake during development,
|
|
// warn about them ASAP rather than swallowing them by default.
|
|
var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;
|
|
|
|
jQuery.Deferred.exceptionHook = function( error, stack ) {
|
|
|
|
// Support: IE 8 - 9 only
|
|
// Console exists when dev tools are open, which can happen at any time
|
|
if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) {
|
|
window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack );
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
jQuery.readyException = function( error ) {
|
|
window.setTimeout( function() {
|
|
throw error;
|
|
} );
|
|
};
|
|
|
|
|
|
|
|
|
|
// The deferred used on DOM ready
|
|
var readyList = jQuery.Deferred();
|
|
|
|
jQuery.fn.ready = function( fn ) {
|
|
|
|
readyList
|
|
.then( fn )
|
|
|
|
// Wrap jQuery.readyException in a function so that the lookup
|
|
// happens at the time of error handling instead of callback
|
|
// registration.
|
|
.catch( function( error ) {
|
|
jQuery.readyException( error );
|
|
} );
|
|
|
|
return this;
|
|
};
|
|
|
|
jQuery.extend( {
|
|
|
|
// Is the DOM ready to be used? Set to true once it occurs.
|
|
isReady: false,
|
|
|
|
// A counter to track how many items to wait for before
|
|
// the ready event fires. See #6781
|
|
readyWait: 1,
|
|
|
|
// Handle when the DOM is ready
|
|
ready: function( wait ) {
|
|
|
|
// Abort if there are pending holds or we're already ready
|
|
if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
|
|
return;
|
|
}
|
|
|
|
// Remember that the DOM is ready
|
|
jQuery.isReady = true;
|
|
|
|
// If a normal DOM Ready event fired, decrement, and wait if need be
|
|
if ( wait !== true && --jQuery.readyWait > 0 ) {
|
|
return;
|
|
}
|
|
|
|
// If there are functions bound, to execute
|
|
readyList.resolveWith( document, [ jQuery ] );
|
|
}
|
|
} );
|
|
|
|
jQuery.ready.then = readyList.then;
|
|
|
|
// The ready event handler and self cleanup method
|
|
function completed() {
|
|
document.removeEventListener( "DOMContentLoaded", completed );
|
|
window.removeEventListener( "load", completed );
|
|
jQuery.ready();
|
|
}
|
|
|
|
// Catch cases where $(document).ready() is called
|
|
// after the browser event has already occurred.
|
|
// Support: IE <=9 - 10 only
|
|
// Older IE sometimes signals "interactive" too soon
|
|
if ( document.readyState === "complete" ||
|
|
( document.readyState !== "loading" && !document.documentElement.doScroll ) ) {
|
|
|
|
// Handle it asynchronously to allow scripts the opportunity to delay ready
|
|
window.setTimeout( jQuery.ready );
|
|
|
|
} else {
|
|
|
|
// Use the handy event callback
|
|
document.addEventListener( "DOMContentLoaded", completed );
|
|
|
|
// A fallback to window.onload, that will always work
|
|
window.addEventListener( "load", completed );
|
|
}
|
|
|
|
|
|
|
|
|
|
// Multifunctional method to get and set values of a collection
|
|
// The value/s can optionally be executed if it's a function
|
|
var access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
|
|
var i = 0,
|
|
len = elems.length,
|
|
bulk = key == null;
|
|
|
|
// Sets many values
|
|
if ( toType( key ) === "object" ) {
|
|
chainable = true;
|
|
for ( i in key ) {
|
|
access( elems, fn, i, key[ i ], true, emptyGet, raw );
|
|
}
|
|
|
|
// Sets one value
|
|
} else if ( value !== undefined ) {
|
|
chainable = true;
|
|
|
|
if ( !isFunction( value ) ) {
|
|
raw = true;
|
|
}
|
|
|
|
if ( bulk ) {
|
|
|
|
// Bulk operations run against the entire set
|
|
if ( raw ) {
|
|
fn.call( elems, value );
|
|
fn = null;
|
|
|
|
// ...except when executing function values
|
|
} else {
|
|
bulk = fn;
|
|
fn = function( elem, key, value ) {
|
|
return bulk.call( jQuery( elem ), value );
|
|
};
|
|
}
|
|
}
|
|
|
|
if ( fn ) {
|
|
for ( ; i < len; i++ ) {
|
|
fn(
|
|
elems[ i ], key, raw ?
|
|
value :
|
|
value.call( elems[ i ], i, fn( elems[ i ], key ) )
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( chainable ) {
|
|
return elems;
|
|
}
|
|
|
|
// Gets
|
|
if ( bulk ) {
|
|
return fn.call( elems );
|
|
}
|
|
|
|
return len ? fn( elems[ 0 ], key ) : emptyGet;
|
|
};
|
|
|
|
|
|
// Matches dashed string for camelizing
|
|
var rmsPrefix = /^-ms-/,
|
|
rdashAlpha = /-([a-z])/g;
|
|
|
|
// Used by camelCase as callback to replace()
|
|
function fcamelCase( all, letter ) {
|
|
return letter.toUpperCase();
|
|
}
|
|
|
|
// Convert dashed to camelCase; used by the css and data modules
|
|
// Support: IE <=9 - 11, Edge 12 - 15
|
|
// Microsoft forgot to hump their vendor prefix (#9572)
|
|
function camelCase( string ) {
|
|
return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
|
|
}
|
|
var acceptData = function( owner ) {
|
|
|
|
// Accepts only:
|
|
// - Node
|
|
// - Node.ELEMENT_NODE
|
|
// - Node.DOCUMENT_NODE
|
|
// - Object
|
|
// - Any
|
|
return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType );
|
|
};
|
|
|
|
|
|
|
|
|
|
function Data() {
|
|
this.expando = jQuery.expando + Data.uid++;
|
|
}
|
|
|
|
Data.uid = 1;
|
|
|
|
Data.prototype = {
|
|
|
|
cache: function( owner ) {
|
|
|
|
// Check if the owner object already has a cache
|
|
var value = owner[ this.expando ];
|
|
|
|
// If not, create one
|
|
if ( !value ) {
|
|
value = {};
|
|
|
|
// We can accept data for non-element nodes in modern browsers,
|
|
// but we should not, see #8335.
|
|
// Always return an empty object.
|
|
if ( acceptData( owner ) ) {
|
|
|
|
// If it is a node unlikely to be stringify-ed or looped over
|
|
// use plain assignment
|
|
if ( owner.nodeType ) {
|
|
owner[ this.expando ] = value;
|
|
|
|
// Otherwise secure it in a non-enumerable property
|
|
// configurable must be true to allow the property to be
|
|
// deleted when data is removed
|
|
} else {
|
|
Object.defineProperty( owner, this.expando, {
|
|
value: value,
|
|
configurable: true
|
|
} );
|
|
}
|
|
}
|
|
}
|
|
|
|
return value;
|
|
},
|
|
set: function( owner, data, value ) {
|
|
var prop,
|
|
cache = this.cache( owner );
|
|
|
|
// Handle: [ owner, key, value ] args
|
|
// Always use camelCase key (gh-2257)
|
|
if ( typeof data === "string" ) {
|
|
cache[ camelCase( data ) ] = value;
|
|
|
|
// Handle: [ owner, { properties } ] args
|
|
} else {
|
|
|
|
// Copy the properties one-by-one to the cache object
|
|
for ( prop in data ) {
|
|
cache[ camelCase( prop ) ] = data[ prop ];
|
|
}
|
|
}
|
|
return cache;
|
|
},
|
|
get: function( owner, key ) {
|
|
return key === undefined ?
|
|
this.cache( owner ) :
|
|
|
|
// Always use camelCase key (gh-2257)
|
|
owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ];
|
|
},
|
|
access: function( owner, key, value ) {
|
|
|
|
// In cases where either:
|
|
//
|
|
// 1. No key was specified
|
|
// 2. A string key was specified, but no value provided
|
|
//
|
|
// Take the "read" path and allow the get method to determine
|
|
// which value to return, respectively either:
|
|
//
|
|
// 1. The entire cache object
|
|
// 2. The data stored at the key
|
|
//
|
|
if ( key === undefined ||
|
|
( ( key && typeof key === "string" ) && value === undefined ) ) {
|
|
|
|
return this.get( owner, key );
|
|
}
|
|
|
|
// When the key is not a string, or both a key and value
|
|
// are specified, set or extend (existing objects) with either:
|
|
//
|
|
// 1. An object of properties
|
|
// 2. A key and value
|
|
//
|
|
this.set( owner, key, value );
|
|
|
|
// Since the "set" path can have two possible entry points
|
|
// return the expected data based on which path was taken[*]
|
|
return value !== undefined ? value : key;
|
|
},
|
|
remove: function( owner, key ) {
|
|
var i,
|
|
cache = owner[ this.expando ];
|
|
|
|
if ( cache === undefined ) {
|
|
return;
|
|
}
|
|
|
|
if ( key !== undefined ) {
|
|
|
|
// Support array or space separated string of keys
|
|
if ( Array.isArray( key ) ) {
|
|
|
|
// If key is an array of keys...
|
|
// We always set camelCase keys, so remove that.
|
|
key = key.map( camelCase );
|
|
} else {
|
|
key = camelCase( key );
|
|
|
|
// If a key with the spaces exists, use it.
|
|
// Otherwise, create an array by matching non-whitespace
|
|
key = key in cache ?
|
|
[ key ] :
|
|
( key.match( rnothtmlwhite ) || [] );
|
|
}
|
|
|
|
i = key.length;
|
|
|
|
while ( i-- ) {
|
|
delete cache[ key[ i ] ];
|
|
}
|
|
}
|
|
|
|
// Remove the expando if there's no more data
|
|
if ( key === undefined || jQuery.isEmptyObject( cache ) ) {
|
|
|
|
// Support: Chrome <=35 - 45
|
|
// Webkit & Blink performance suffers when deleting properties
|
|
// from DOM nodes, so set to undefined instead
|
|
// https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted)
|
|
if ( owner.nodeType ) {
|
|
owner[ this.expando ] = undefined;
|
|
} else {
|
|
delete owner[ this.expando ];
|
|
}
|
|
}
|
|
},
|
|
hasData: function( owner ) {
|
|
var cache = owner[ this.expando ];
|
|
return cache !== undefined && !jQuery.isEmptyObject( cache );
|
|
}
|
|
};
|
|
var dataPriv = new Data();
|
|
|
|
var dataUser = new Data();
|
|
|
|
|
|
|
|
// Implementation Summary
|
|
//
|
|
// 1. Enforce API surface and semantic compatibility with 1.9.x branch
|
|
// 2. Improve the module's maintainability by reducing the storage
|
|
// paths to a single mechanism.
|
|
// 3. Use the same single mechanism to support "private" and "user" data.
|
|
// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData)
|
|
// 5. Avoid exposing implementation details on user objects (eg. expando properties)
|
|
// 6. Provide a clear path for implementation upgrade to WeakMap in 2014
|
|
|
|
var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,
|
|
rmultiDash = /[A-Z]/g;
|
|
|
|
function getData( data ) {
|
|
if ( data === "true" ) {
|
|
return true;
|
|
}
|
|
|
|
if ( data === "false" ) {
|
|
return false;
|
|
}
|
|
|
|
if ( data === "null" ) {
|
|
return null;
|
|
}
|
|
|
|
// Only convert to a number if it doesn't change the string
|
|
if ( data === +data + "" ) {
|
|
return +data;
|
|
}
|
|
|
|
if ( rbrace.test( data ) ) {
|
|
return JSON.parse( data );
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
function dataAttr( elem, key, data ) {
|
|
var name;
|
|
|
|
// If nothing was found internally, try to fetch any
|
|
// data from the HTML5 data-* attribute
|
|
if ( data === undefined && elem.nodeType === 1 ) {
|
|
name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase();
|
|
data = elem.getAttribute( name );
|
|
|
|
if ( typeof data === "string" ) {
|
|
try {
|
|
data = getData( data );
|
|
} catch ( e ) {}
|
|
|
|
// Make sure we set the data so it isn't changed later
|
|
dataUser.set( elem, key, data );
|
|
} else {
|
|
data = undefined;
|
|
}
|
|
}
|
|
return data;
|
|
}
|
|
|
|
jQuery.extend( {
|
|
hasData: function( elem ) {
|
|
return dataUser.hasData( elem ) || dataPriv.hasData( elem );
|
|
},
|
|
|
|
data: function( elem, name, data ) {
|
|
return dataUser.access( elem, name, data );
|
|
},
|
|
|
|
removeData: function( elem, name ) {
|
|
dataUser.remove( elem, name );
|
|
},
|
|
|
|
// TODO: Now that all calls to _data and _removeData have been replaced
|
|
// with direct calls to dataPriv methods, these can be deprecated.
|
|
_data: function( elem, name, data ) {
|
|
return dataPriv.access( elem, name, data );
|
|
},
|
|
|
|
_removeData: function( elem, name ) {
|
|
dataPriv.remove( elem, name );
|
|
}
|
|
} );
|
|
|
|
jQuery.fn.extend( {
|
|
data: function( key, value ) {
|
|
var i, name, data,
|
|
elem = this[ 0 ],
|
|
attrs = elem && elem.attributes;
|
|
|
|
// Gets all values
|
|
if ( key === undefined ) {
|
|
if ( this.length ) {
|
|
data = dataUser.get( elem );
|
|
|
|
if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) {
|
|
i = attrs.length;
|
|
while ( i-- ) {
|
|
|
|
// Support: IE 11 only
|
|
// The attrs elements can be null (#14894)
|
|
if ( attrs[ i ] ) {
|
|
name = attrs[ i ].name;
|
|
if ( name.indexOf( "data-" ) === 0 ) {
|
|
name = camelCase( name.slice( 5 ) );
|
|
dataAttr( elem, name, data[ name ] );
|
|
}
|
|
}
|
|
}
|
|
dataPriv.set( elem, "hasDataAttrs", true );
|
|
}
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
// Sets multiple values
|
|
if ( typeof key === "object" ) {
|
|
return this.each( function() {
|
|
dataUser.set( this, key );
|
|
} );
|
|
}
|
|
|
|
return access( this, function( value ) {
|
|
var data;
|
|
|
|
// The calling jQuery object (element matches) is not empty
|
|
// (and therefore has an element appears at this[ 0 ]) and the
|
|
// `value` parameter was not undefined. An empty jQuery object
|
|
// will result in `undefined` for elem = this[ 0 ] which will
|
|
// throw an exception if an attempt to read a data cache is made.
|
|
if ( elem && value === undefined ) {
|
|
|
|
// Attempt to get data from the cache
|
|
// The key will always be camelCased in Data
|
|
data = dataUser.get( elem, key );
|
|
if ( data !== undefined ) {
|
|
return data;
|
|
}
|
|
|
|
// Attempt to "discover" the data in
|
|
// HTML5 custom data-* attrs
|
|
data = dataAttr( elem, key );
|
|
if ( data !== undefined ) {
|
|
return data;
|
|
}
|
|
|
|
// We tried really hard, but the data doesn't exist.
|
|
return;
|
|
}
|
|
|
|
// Set the data...
|
|
this.each( function() {
|
|
|
|
// We always store the camelCased key
|
|
dataUser.set( this, key, value );
|
|
} );
|
|
}, null, value, arguments.length > 1, null, true );
|
|
},
|
|
|
|
removeData: function( key ) {
|
|
return this.each( function() {
|
|
dataUser.remove( this, key );
|
|
} );
|
|
}
|
|
} );
|
|
|
|
|
|
jQuery.extend( {
|
|
queue: function( elem, type, data ) {
|
|
var queue;
|
|
|
|
if ( elem ) {
|
|
type = ( type || "fx" ) + "queue";
|
|
queue = dataPriv.get( elem, type );
|
|
|
|
// Speed up dequeue by getting out quickly if this is just a lookup
|
|
if ( data ) {
|
|
if ( !queue || Array.isArray( data ) ) {
|
|
queue = dataPriv.access( elem, type, jQuery.makeArray( data ) );
|
|
} else {
|
|
queue.push( data );
|
|
}
|
|
}
|
|
return queue || [];
|
|
}
|
|
},
|
|
|
|
dequeue: function( elem, type ) {
|
|
type = type || "fx";
|
|
|
|
var queue = jQuery.queue( elem, type ),
|
|
startLength = queue.length,
|
|
fn = queue.shift(),
|
|
hooks = jQuery._queueHooks( elem, type ),
|
|
next = function() {
|
|
jQuery.dequeue( elem, type );
|
|
};
|
|
|
|
// If the fx queue is dequeued, always remove the progress sentinel
|
|
if ( fn === "inprogress" ) {
|
|
fn = queue.shift();
|
|
startLength--;
|
|
}
|
|
|
|
if ( fn ) {
|
|
|
|
// Add a progress sentinel to prevent the fx queue from being
|
|
// automatically dequeued
|
|
if ( type === "fx" ) {
|
|
queue.unshift( "inprogress" );
|
|
}
|
|
|
|
// Clear up the last queue stop function
|
|
delete hooks.stop;
|
|
fn.call( elem, next, hooks );
|
|
}
|
|
|
|
if ( !startLength && hooks ) {
|
|
hooks.empty.fire();
|
|
}
|
|
},
|
|
|
|
// Not public - generate a queueHooks object, or return the current one
|
|
_queueHooks: function( elem, type ) {
|
|
var key = type + "queueHooks";
|
|
return dataPriv.get( elem, key ) || dataPriv.access( elem, key, {
|
|
empty: jQuery.Callbacks( "once memory" ).add( function() {
|
|
dataPriv.remove( elem, [ type + "queue", key ] );
|
|
} )
|
|
} );
|
|
}
|
|
} );
|
|
|
|
jQuery.fn.extend( {
|
|
queue: function( type, data ) {
|
|
var setter = 2;
|
|
|
|
if ( typeof type !== "string" ) {
|
|
data = type;
|
|
type = "fx";
|
|
setter--;
|
|
}
|
|
|
|
if ( arguments.length < setter ) {
|
|
return jQuery.queue( this[ 0 ], type );
|
|
}
|
|
|
|
return data === undefined ?
|
|
this :
|
|
this.each( function() {
|
|
var queue = jQuery.queue( this, type, data );
|
|
|
|
// Ensure a hooks for this queue
|
|
jQuery._queueHooks( this, type );
|
|
|
|
if ( type === "fx" && queue[ 0 ] !== "inprogress" ) {
|
|
jQuery.dequeue( this, type );
|
|
}
|
|
} );
|
|
},
|
|
dequeue: function( type ) {
|
|
return this.each( function() {
|
|
jQuery.dequeue( this, type );
|
|
} );
|
|
},
|
|
clearQueue: function( type ) {
|
|
return this.queue( type || "fx", [] );
|
|
},
|
|
|
|
// Get a promise resolved when queues of a certain type
|
|
// are emptied (fx is the type by default)
|
|
promise: function( type, obj ) {
|
|
var tmp,
|
|
count = 1,
|
|
defer = jQuery.Deferred(),
|
|
elements = this,
|
|
i = this.length,
|
|
resolve = function() {
|
|
if ( !( --count ) ) {
|
|
defer.resolveWith( elements, [ elements ] );
|
|
}
|
|
};
|
|
|
|
if ( typeof type !== "string" ) {
|
|
obj = type;
|
|
type = undefined;
|
|
}
|
|
type = type || "fx";
|
|
|
|
while ( i-- ) {
|
|
tmp = dataPriv.get( elements[ i ], type + "queueHooks" );
|
|
if ( tmp && tmp.empty ) {
|
|
count++;
|
|
tmp.empty.add( resolve );
|
|
}
|
|
}
|
|
resolve();
|
|
return defer.promise( obj );
|
|
}
|
|
} );
|
|
var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source;
|
|
|
|
var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" );
|
|
|
|
|
|
var cssExpand = [ "Top", "Right", "Bottom", "Left" ];
|
|
|
|
var documentElement = document.documentElement;
|
|
|
|
|
|
|
|
var isAttached = function( elem ) {
|
|
return jQuery.contains( elem.ownerDocument, elem );
|
|
},
|
|
composed = { composed: true };
|
|
|
|
// Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only
|
|
// Check attachment across shadow DOM boundaries when possible (gh-3504)
|
|
// Support: iOS 10.0-10.2 only
|
|
// Early iOS 10 versions support `attachShadow` but not `getRootNode`,
|
|
// leading to errors. We need to check for `getRootNode`.
|
|
if ( documentElement.getRootNode ) {
|
|
isAttached = function( elem ) {
|
|
return jQuery.contains( elem.ownerDocument, elem ) ||
|
|
elem.getRootNode( composed ) === elem.ownerDocument;
|
|
};
|
|
}
|
|
var isHiddenWithinTree = function( elem, el ) {
|
|
|
|
// isHiddenWithinTree might be called from jQuery#filter function;
|
|
// in that case, element will be second argument
|
|
elem = el || elem;
|
|
|
|
// Inline style trumps all
|
|
return elem.style.display === "none" ||
|
|
elem.style.display === "" &&
|
|
|
|
// Otherwise, check computed style
|
|
// Support: Firefox <=43 - 45
|
|
// Disconnected elements can have computed display: none, so first confirm that elem is
|
|
// in the document.
|
|
isAttached( elem ) &&
|
|
|
|
jQuery.css( elem, "display" ) === "none";
|
|
};
|
|
|
|
var swap = function( elem, options, callback, args ) {
|
|
var ret, name,
|
|
old = {};
|
|
|
|
// Remember the old values, and insert the new ones
|
|
for ( name in options ) {
|
|
old[ name ] = elem.style[ name ];
|
|
elem.style[ name ] = options[ name ];
|
|
}
|
|
|
|
ret = callback.apply( elem, args || [] );
|
|
|
|
// Revert the old values
|
|
for ( name in options ) {
|
|
elem.style[ name ] = old[ name ];
|
|
}
|
|
|
|
return ret;
|
|
};
|
|
|
|
|
|
|
|
|
|
function adjustCSS( elem, prop, valueParts, tween ) {
|
|
var adjusted, scale,
|
|
maxIterations = 20,
|
|
currentValue = tween ?
|
|
function() {
|
|
return tween.cur();
|
|
} :
|
|
function() {
|
|
return jQuery.css( elem, prop, "" );
|
|
},
|
|
initial = currentValue(),
|
|
unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),
|
|
|
|
// Starting value computation is required for potential unit mismatches
|
|
initialInUnit = elem.nodeType &&
|
|
( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) &&
|
|
rcssNum.exec( jQuery.css( elem, prop ) );
|
|
|
|
if ( initialInUnit && initialInUnit[ 3 ] !== unit ) {
|
|
|
|
// Support: Firefox <=54
|
|
// Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144)
|
|
initial = initial / 2;
|
|
|
|
// Trust units reported by jQuery.css
|
|
unit = unit || initialInUnit[ 3 ];
|
|
|
|
// Iteratively approximate from a nonzero starting point
|
|
initialInUnit = +initial || 1;
|
|
|
|
while ( maxIterations-- ) {
|
|
|
|
// Evaluate and update our best guess (doubling guesses that zero out).
|
|
// Finish if the scale equals or crosses 1 (making the old*new product non-positive).
|
|
jQuery.style( elem, prop, initialInUnit + unit );
|
|
if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) {
|
|
maxIterations = 0;
|
|
}
|
|
initialInUnit = initialInUnit / scale;
|
|
|
|
}
|
|
|
|
initialInUnit = initialInUnit * 2;
|
|
jQuery.style( elem, prop, initialInUnit + unit );
|
|
|
|
// Make sure we update the tween properties later on
|
|
valueParts = valueParts || [];
|
|
}
|
|
|
|
if ( valueParts ) {
|
|
initialInUnit = +initialInUnit || +initial || 0;
|
|
|
|
// Apply relative offset (+=/-=) if specified
|
|
adjusted = valueParts[ 1 ] ?
|
|
initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] :
|
|
+valueParts[ 2 ];
|
|
if ( tween ) {
|
|
tween.unit = unit;
|
|
tween.start = initialInUnit;
|
|
tween.end = adjusted;
|
|
}
|
|
}
|
|
return adjusted;
|
|
}
|
|
|
|
|
|
var defaultDisplayMap = {};
|
|
|
|
function getDefaultDisplay( elem ) {
|
|
var temp,
|
|
doc = elem.ownerDocument,
|
|
nodeName = elem.nodeName,
|
|
display = defaultDisplayMap[ nodeName ];
|
|
|
|
if ( display ) {
|
|
return display;
|
|
}
|
|
|
|
temp = doc.body.appendChild( doc.createElement( nodeName ) );
|
|
display = jQuery.css( temp, "display" );
|
|
|
|
temp.parentNode.removeChild( temp );
|
|
|
|
if ( display === "none" ) {
|
|
display = "block";
|
|
}
|
|
defaultDisplayMap[ nodeName ] = display;
|
|
|
|
return display;
|
|
}
|
|
|
|
function showHide( elements, show ) {
|
|
var display, elem,
|
|
values = [],
|
|
index = 0,
|
|
length = elements.length;
|
|
|
|
// Determine new display value for elements that need to change
|
|
for ( ; index < length; index++ ) {
|
|
elem = elements[ index ];
|
|
if ( !elem.style ) {
|
|
continue;
|
|
}
|
|
|
|
display = elem.style.display;
|
|
if ( show ) {
|
|
|
|
// Since we force visibility upon cascade-hidden elements, an immediate (and slow)
|
|
// check is required in this first loop unless we have a nonempty display value (either
|
|
// inline or about-to-be-restored)
|
|
if ( display === "none" ) {
|
|
values[ index ] = dataPriv.get( elem, "display" ) || null;
|
|
if ( !values[ index ] ) {
|
|
elem.style.display = "";
|
|
}
|
|
}
|
|
if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) {
|
|
values[ index ] = getDefaultDisplay( elem );
|
|
}
|
|
} else {
|
|
if ( display !== "none" ) {
|
|
values[ index ] = "none";
|
|
|
|
// Remember what we're overwriting
|
|
dataPriv.set( elem, "display", display );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set the display of the elements in a second loop to avoid constant reflow
|
|
for ( index = 0; index < length; index++ ) {
|
|
if ( values[ index ] != null ) {
|
|
elements[ index ].style.display = values[ index ];
|
|
}
|
|
}
|
|
|
|
return elements;
|
|
}
|
|
|
|
jQuery.fn.extend( {
|
|
show: function() {
|
|
return showHide( this, true );
|
|
},
|
|
hide: function() {
|
|
return showHide( this );
|
|
},
|
|
toggle: function( state ) {
|
|
if ( typeof state === "boolean" ) {
|
|
return state ? this.show() : this.hide();
|
|
}
|
|
|
|
return this.each( function() {
|
|
if ( isHiddenWithinTree( this ) ) {
|
|
jQuery( this ).show();
|
|
} else {
|
|
jQuery( this ).hide();
|
|
}
|
|
} );
|
|
}
|
|
} );
|
|
var rcheckableType = ( /^(?:checkbox|radio)$/i );
|
|
|
|
var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i );
|
|
|
|
var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i );
|
|
|
|
|
|
|
|
// We have to close these tags to support XHTML (#13200)
|
|
var wrapMap = {
|
|
|
|
// Support: IE <=9 only
|
|
option: [ 1, "<select multiple='multiple'>", "</select>" ],
|
|
|
|
// XHTML parsers do not magically insert elements in the
|
|
// same way that tag soup parsers do. So we cannot shorten
|
|
// this by omitting <tbody> or other required elements.
|
|
thead: [ 1, "<table>", "</table>" ],
|
|
col: [ 2, "<table><colgroup>", "</colgroup></table>" ],
|
|
tr: [ 2, "<table><tbody>", "</tbody></table>" ],
|
|
td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
|
|
|
|
_default: [ 0, "", "" ]
|
|
};
|
|
|
|
// Support: IE <=9 only
|
|
wrapMap.optgroup = wrapMap.option;
|
|
|
|
wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
|
|
wrapMap.th = wrapMap.td;
|
|
|
|
|
|
function getAll( context, tag ) {
|
|
|
|
// Support: IE <=9 - 11 only
|
|
// Use typeof to avoid zero-argument method invocation on host objects (#15151)
|
|
var ret;
|
|
|
|
if ( typeof context.getElementsByTagName !== "undefined" ) {
|
|
ret = context.getElementsByTagName( tag || "*" );
|
|
|
|
} else if ( typeof context.querySelectorAll !== "undefined" ) {
|
|
ret = context.querySelectorAll( tag || "*" );
|
|
|
|
} else {
|
|
ret = [];
|
|
}
|
|
|
|
if ( tag === undefined || tag && nodeName( context, tag ) ) {
|
|
return jQuery.merge( [ context ], ret );
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
// Mark scripts as having already been evaluated
|
|
function setGlobalEval( elems, refElements ) {
|
|
var i = 0,
|
|
l = elems.length;
|
|
|
|
for ( ; i < l; i++ ) {
|
|
dataPriv.set(
|
|
elems[ i ],
|
|
"globalEval",
|
|
!refElements || dataPriv.get( refElements[ i ], "globalEval" )
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
var rhtml = /<|&#?\w+;/;
|
|
|
|
function buildFragment( elems, context, scripts, selection, ignored ) {
|
|
var elem, tmp, tag, wrap, attached, j,
|
|
fragment = context.createDocumentFragment(),
|
|
nodes = [],
|
|
i = 0,
|
|
l = elems.length;
|
|
|
|
for ( ; i < l; i++ ) {
|
|
elem = elems[ i ];
|
|
|
|
if ( elem || elem === 0 ) {
|
|
|
|
// Add nodes directly
|
|
if ( toType( elem ) === "object" ) {
|
|
|
|
// Support: Android <=4.0 only, PhantomJS 1 only
|
|
// push.apply(_, arraylike) throws on ancient WebKit
|
|
jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );
|
|
|
|
// Convert non-html into a text node
|
|
} else if ( !rhtml.test( elem ) ) {
|
|
nodes.push( context.createTextNode( elem ) );
|
|
|
|
// Convert html into DOM nodes
|
|
} else {
|
|
tmp = tmp || fragment.appendChild( context.createElement( "div" ) );
|
|
|
|
// Deserialize a standard representation
|
|
tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase();
|
|
wrap = wrapMap[ tag ] || wrapMap._default;
|
|
tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ];
|
|
|
|
// Descend through wrappers to the right content
|
|
j = wrap[ 0 ];
|
|
while ( j-- ) {
|
|
tmp = tmp.lastChild;
|
|
}
|
|
|
|
// Support: Android <=4.0 only, PhantomJS 1 only
|
|
// push.apply(_, arraylike) throws on ancient WebKit
|
|
jQuery.merge( nodes, tmp.childNodes );
|
|
|
|
// Remember the top-level container
|
|
tmp = fragment.firstChild;
|
|
|
|
// Ensure the created nodes are orphaned (#12392)
|
|
tmp.textContent = "";
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove wrapper from fragment
|
|
fragment.textContent = "";
|
|
|
|
i = 0;
|
|
while ( ( elem = nodes[ i++ ] ) ) {
|
|
|
|
// Skip elements already in the context collection (trac-4087)
|
|
if ( selection && jQuery.inArray( elem, selection ) > -1 ) {
|
|
if ( ignored ) {
|
|
ignored.push( elem );
|
|
}
|
|
continue;
|
|
}
|
|
|
|
attached = isAttached( elem );
|
|
|
|
// Append to fragment
|
|
tmp = getAll( fragment.appendChild( elem ), "script" );
|
|
|
|
// Preserve script evaluation history
|
|
if ( attached ) {
|
|
setGlobalEval( tmp );
|
|
}
|
|
|
|
// Capture executables
|
|
if ( scripts ) {
|
|
j = 0;
|
|
while ( ( elem = tmp[ j++ ] ) ) {
|
|
if ( rscriptType.test( elem.type || "" ) ) {
|
|
scripts.push( elem );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return fragment;
|
|
}
|
|
|
|
|
|
( function() {
|
|
var fragment = document.createDocumentFragment(),
|
|
div = fragment.appendChild( document.createElement( "div" ) ),
|
|
input = document.createElement( "input" );
|
|
|
|
// Support: Android 4.0 - 4.3 only
|
|
// Check state lost if the name is set (#11217)
|
|
// Support: Windows Web Apps (WWA)
|
|
// `name` and `type` must use .setAttribute for WWA (#14901)
|
|
input.setAttribute( "type", "radio" );
|
|
input.setAttribute( "checked", "checked" );
|
|
input.setAttribute( "name", "t" );
|
|
|
|
div.appendChild( input );
|
|
|
|
// Support: Android <=4.1 only
|
|
// Older WebKit doesn't clone checked state correctly in fragments
|
|
support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;
|
|
|
|
// Support: IE <=11 only
|
|
// Make sure textarea (and checkbox) defaultValue is properly cloned
|
|
div.innerHTML = "<textarea>x</textarea>";
|
|
support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;
|
|
} )();
|
|
|
|
|
|
var
|
|
rkeyEvent = /^key/,
|
|
rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/,
|
|
rtypenamespace = /^([^.]*)(?:\.(.+)|)/;
|
|
|
|
function returnTrue() {
|
|
return true;
|
|
}
|
|
|
|
function returnFalse() {
|
|
return false;
|
|
}
|
|
|
|
// Support: IE <=9 - 11+
|
|
// focus() and blur() are asynchronous, except when they are no-op.
|
|
// So expect focus to be synchronous when the element is already active,
|
|
// and blur to be synchronous when the element is not already active.
|
|
// (focus and blur are always synchronous in other supported browsers,
|
|
// this just defines when we can count on it).
|
|
function expectSync( elem, type ) {
|
|
return ( elem === safeActiveElement() ) === ( type === "focus" );
|
|
}
|
|
|
|
// Support: IE <=9 only
|
|
// Accessing document.activeElement can throw unexpectedly
|
|
// https://bugs.jquery.com/ticket/13393
|
|
function safeActiveElement() {
|
|
try {
|
|
return document.activeElement;
|
|
} catch ( err ) { }
|
|
}
|
|
|
|
function on( elem, types, selector, data, fn, one ) {
|
|
var origFn, type;
|
|
|
|
// Types can be a map of types/handlers
|
|
if ( typeof types === "object" ) {
|
|
|
|
// ( types-Object, selector, data )
|
|
if ( typeof selector !== "string" ) {
|
|
|
|
// ( types-Object, data )
|
|
data = data || selector;
|
|
selector = undefined;
|
|
}
|
|
for ( type in types ) {
|
|
on( elem, type, selector, data, types[ type ], one );
|
|
}
|
|
return elem;
|
|
}
|
|
|
|
if ( data == null && fn == null ) {
|
|
|
|
// ( types, fn )
|
|
fn = selector;
|
|
data = selector = undefined;
|
|
} else if ( fn == null ) {
|
|
if ( typeof selector === "string" ) {
|
|
|
|
// ( types, selector, fn )
|
|
fn = data;
|
|
data = undefined;
|
|
} else {
|
|
|
|
// ( types, data, fn )
|
|
fn = data;
|
|
data = selector;
|
|
selector = undefined;
|
|
}
|
|
}
|
|
if ( fn === false ) {
|
|
fn = returnFalse;
|
|
} else if ( !fn ) {
|
|
return elem;
|
|
}
|
|
|
|
if ( one === 1 ) {
|
|
origFn = fn;
|
|
fn = function( event ) {
|
|
|
|
// Can use an empty set, since event contains the info
|
|
jQuery().off( event );
|
|
return origFn.apply( this, arguments );
|
|
};
|
|
|
|
// Use same guid so caller can remove using origFn
|
|
fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
|
|
}
|
|
return elem.each( function() {
|
|
jQuery.event.add( this, types, fn, data, selector );
|
|
} );
|
|
}
|
|
|
|
/*
|
|
* Helper functions for managing events -- not part of the public interface.
|
|
* Props to Dean Edwards' addEvent library for many of the ideas.
|
|
*/
|
|
jQuery.event = {
|
|
|
|
global: {},
|
|
|
|
add: function( elem, types, handler, data, selector ) {
|
|
|
|
var handleObjIn, eventHandle, tmp,
|
|
events, t, handleObj,
|
|
special, handlers, type, namespaces, origType,
|
|
elemData = dataPriv.get( elem );
|
|
|
|
// Don't attach events to noData or text/comment nodes (but allow plain objects)
|
|
if ( !elemData ) {
|
|
return;
|
|
}
|
|
|
|
// Caller can pass in an object of custom data in lieu of the handler
|
|
if ( handler.handler ) {
|
|
handleObjIn = handler;
|
|
handler = handleObjIn.handler;
|
|
selector = handleObjIn.selector;
|
|
}
|
|
|
|
// Ensure that invalid selectors throw exceptions at attach time
|
|
// Evaluate against documentElement in case elem is a non-element node (e.g., document)
|
|
if ( selector ) {
|
|
jQuery.find.matchesSelector( documentElement, selector );
|
|
}
|
|
|
|
// Make sure that the handler has a unique ID, used to find/remove it later
|
|
if ( !handler.guid ) {
|
|
handler.guid = jQuery.guid++;
|
|
}
|
|
|
|
// Init the element's event structure and main handler, if this is the first
|
|
if ( !( events = elemData.events ) ) {
|
|
events = elemData.events = {};
|
|
}
|
|
if ( !( eventHandle = elemData.handle ) ) {
|
|
eventHandle = elemData.handle = function( e ) {
|
|
|
|
// Discard the second event of a jQuery.event.trigger() and
|
|
// when an event is called after a page has unloaded
|
|
return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ?
|
|
jQuery.event.dispatch.apply( elem, arguments ) : undefined;
|
|
};
|
|
}
|
|
|
|
// Handle multiple events separated by a space
|
|
types = ( types || "" ).match( rnothtmlwhite ) || [ "" ];
|
|
t = types.length;
|
|
while ( t-- ) {
|
|
tmp = rtypenamespace.exec( types[ t ] ) || [];
|
|
type = origType = tmp[ 1 ];
|
|
namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
|
|
|
|
// There *must* be a type, no attaching namespace-only handlers
|
|
if ( !type ) {
|
|
continue;
|
|
}
|
|
|
|
// If event changes its type, use the special event handlers for the changed type
|
|
special = jQuery.event.special[ type ] || {};
|
|
|
|
// If selector defined, determine special event api type, otherwise given type
|
|
type = ( selector ? special.delegateType : special.bindType ) || type;
|
|
|
|
// Update special based on newly reset type
|
|
special = jQuery.event.special[ type ] || {};
|
|
|
|
// handleObj is passed to all event handlers
|
|
handleObj = jQuery.extend( {
|
|
type: type,
|
|
origType: origType,
|
|
data: data,
|
|
handler: handler,
|
|
guid: handler.guid,
|
|
selector: selector,
|
|
needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
|
|
namespace: namespaces.join( "." )
|
|
}, handleObjIn );
|
|
|
|
// Init the event handler queue if we're the first
|
|
if ( !( handlers = events[ type ] ) ) {
|
|
handlers = events[ type ] = [];
|
|
handlers.delegateCount = 0;
|
|
|
|
// Only use addEventListener if the special events handler returns false
|
|
if ( !special.setup ||
|
|
special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
|
|
|
|
if ( elem.addEventListener ) {
|
|
elem.addEventListener( type, eventHandle );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( special.add ) {
|
|
special.add.call( elem, handleObj );
|
|
|
|
if ( !handleObj.handler.guid ) {
|
|
handleObj.handler.guid = handler.guid;
|
|
}
|
|
}
|
|
|
|
// Add to the element's handler list, delegates in front
|
|
if ( selector ) {
|
|
handlers.splice( handlers.delegateCount++, 0, handleObj );
|
|
} else {
|
|
handlers.push( handleObj );
|
|
}
|
|
|
|
// Keep track of which events have ever been used, for event optimization
|
|
jQuery.event.global[ type ] = true;
|
|
}
|
|
|
|
},
|
|
|
|
// Detach an event or set of events from an element
|
|
remove: function( elem, types, handler, selector, mappedTypes ) {
|
|
|
|
var j, origCount, tmp,
|
|
events, t, handleObj,
|
|
special, handlers, type, namespaces, origType,
|
|
elemData = dataPriv.hasData( elem ) && dataPriv.get( elem );
|
|
|
|
if ( !elemData || !( events = elemData.events ) ) {
|
|
return;
|
|
}
|
|
|
|
// Once for each type.namespace in types; type may be omitted
|
|
types = ( types || "" ).match( rnothtmlwhite ) || [ "" ];
|
|
t = types.length;
|
|
while ( t-- ) {
|
|
tmp = rtypenamespace.exec( types[ t ] ) || [];
|
|
type = origType = tmp[ 1 ];
|
|
namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();
|
|
|
|
// Unbind all events (on this namespace, if provided) for the element
|
|
if ( !type ) {
|
|
for ( type in events ) {
|
|
jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
|
|
}
|
|
continue;
|
|
}
|
|
|
|
special = jQuery.event.special[ type ] || {};
|
|
type = ( selector ? special.delegateType : special.bindType ) || type;
|
|
handlers = events[ type ] || [];
|
|
tmp = tmp[ 2 ] &&
|
|
new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" );
|
|
|
|
// Remove matching events
|
|
origCount = j = handlers.length;
|
|
while ( j-- ) {
|
|
handleObj = handlers[ j ];
|
|
|
|
if ( ( mappedTypes || origType === handleObj.origType ) &&
|
|
( !handler || handler.guid === handleObj.guid ) &&
|
|
( !tmp || tmp.test( handleObj.namespace ) ) &&
|
|
( !selector || selector === handleObj.selector ||
|
|
selector === "**" && handleObj.selector ) ) {
|
|
handlers.splice( j, 1 );
|
|
|
|
if ( handleObj.selector ) {
|
|
handlers.delegateCount--;
|
|
}
|
|
if ( special.remove ) {
|
|
special.remove.call( elem, handleObj );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove generic event handler if we removed something and no more handlers exist
|
|
// (avoids potential for endless recursion during removal of special event handlers)
|
|
if ( origCount && !handlers.length ) {
|
|
if ( !special.teardown ||
|
|
special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
|
|
|
|
jQuery.removeEvent( elem, type, elemData.handle );
|
|
}
|
|
|
|
delete events[ type ];
|
|
}
|
|
}
|
|
|
|
// Remove data and the expando if it's no longer used
|
|
if ( jQuery.isEmptyObject( events ) ) {
|
|
dataPriv.remove( elem, "handle events" );
|
|
}
|
|
},
|
|
|
|
dispatch: function( nativeEvent ) {
|
|
|
|
// Make a writable jQuery.Event from the native event object
|
|
var event = jQuery.event.fix( nativeEvent );
|
|
|
|
var i, j, ret, matched, handleObj, handlerQueue,
|
|
args = new Array( arguments.length ),
|
|
handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [],
|
|
special = jQuery.event.special[ event.type ] || {};
|
|
|
|
// Use the fix-ed jQuery.Event rather than the (read-only) native event
|
|
args[ 0 ] = event;
|
|
|
|
for ( i = 1; i < arguments.length; i++ ) {
|
|
args[ i ] = arguments[ i ];
|
|
}
|
|
|
|
event.delegateTarget = this;
|
|
|
|
// Call the preDispatch hook for the mapped type, and let it bail if desired
|
|
if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
|
|
return;
|
|
}
|
|
|
|
// Determine handlers
|
|
handlerQueue = jQuery.event.handlers.call( this, event, handlers );
|
|
|
|
// Run delegates first; they may want to stop propagation beneath us
|
|
i = 0;
|
|
while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) {
|
|
event.currentTarget = matched.elem;
|
|
|
|
j = 0;
|
|
while ( ( handleObj = matched.handlers[ j++ ] ) &&
|
|
!event.isImmediatePropagationStopped() ) {
|
|
|
|
// If the event is namespaced, then each handler is only invoked if it is
|
|
// specially universal or its namespaces are a superset of the event's.
|
|
if ( !event.rnamespace || handleObj.namespace === false ||
|
|
event.rnamespace.test( handleObj.namespace ) ) {
|
|
|
|
event.handleObj = handleObj;
|
|
event.data = handleObj.data;
|
|
|
|
ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle ||
|
|
handleObj.handler ).apply( matched.elem, args );
|
|
|
|
if ( ret !== undefined ) {
|
|
if ( ( event.result = ret ) === false ) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Call the postDispatch hook for the mapped type
|
|
if ( special.postDispatch ) {
|
|
special.postDispatch.call( this, event );
|
|
}
|
|
|
|
return event.result;
|
|
},
|
|
|
|
handlers: function( event, handlers ) {
|
|
var i, handleObj, sel, matchedHandlers, matchedSelectors,
|
|
handlerQueue = [],
|
|
delegateCount = handlers.delegateCount,
|
|
cur = event.target;
|
|
|
|
// Find delegate handlers
|
|
if ( delegateCount &&
|
|
|
|
// Support: IE <=9
|
|
// Black-hole SVG <use> instance trees (trac-13180)
|
|
cur.nodeType &&
|
|
|
|
// Support: Firefox <=42
|
|
// Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861)
|
|
// https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click
|
|
// Support: IE 11 only
|
|
// ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343)
|
|
!( event.type === "click" && event.button >= 1 ) ) {
|
|
|
|
for ( ; cur !== this; cur = cur.parentNode || this ) {
|
|
|
|
// Don't check non-elements (#13208)
|
|
// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
|
|
if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) {
|
|
matchedHandlers = [];
|
|
matchedSelectors = {};
|
|
for ( i = 0; i < delegateCount; i++ ) {
|
|
handleObj = handlers[ i ];
|
|
|
|
// Don't conflict with Object.prototype properties (#13203)
|
|
sel = handleObj.selector + " ";
|
|
|
|
if ( matchedSelectors[ sel ] === undefined ) {
|
|
matchedSelectors[ sel ] = handleObj.needsContext ?
|
|
jQuery( sel, this ).index( cur ) > -1 :
|
|
jQuery.find( sel, this, null, [ cur ] ).length;
|
|
}
|
|
if ( matchedSelectors[ sel ] ) {
|
|
matchedHandlers.push( handleObj );
|
|
}
|
|
}
|
|
if ( matchedHandlers.length ) {
|
|
handlerQueue.push( { elem: cur, handlers: matchedHandlers } );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add the remaining (directly-bound) handlers
|
|
cur = this;
|
|
if ( delegateCount < handlers.length ) {
|
|
handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } );
|
|
}
|
|
|
|
return handlerQueue;
|
|
},
|
|
|
|
addProp: function( name, hook ) {
|
|
Object.defineProperty( jQuery.Event.prototype, name, {
|
|
enumerable: true,
|
|
configurable: true,
|
|
|
|
get: isFunction( hook ) ?
|
|
function() {
|
|
if ( this.originalEvent ) {
|
|
return hook( this.originalEvent );
|
|
}
|
|
} :
|
|
function() {
|
|
if ( this.originalEvent ) {
|
|
return this.originalEvent[ name ];
|
|
}
|
|
},
|
|
|
|
set: function( value ) {
|
|
Object.defineProperty( this, name, {
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true,
|
|
value: value
|
|
} );
|
|
}
|
|
} );
|
|
},
|
|
|
|
fix: function( originalEvent ) {
|
|
return originalEvent[ jQuery.expando ] ?
|
|
originalEvent :
|
|
new jQuery.Event( originalEvent );
|
|
},
|
|
|
|
special: {
|
|
load: {
|
|
|
|
// Prevent triggered image.load events from bubbling to window.load
|
|
noBubble: true
|
|
},
|
|
click: {
|
|
|
|
// Utilize native event to ensure correct state for checkable inputs
|
|
setup: function( data ) {
|
|
|
|
// For mutual compressibility with _default, replace `this` access with a local var.
|
|
// `|| data` is dead code meant only to preserve the variable through minification.
|
|
var el = this || data;
|
|
|
|
// Claim the first handler
|
|
if ( rcheckableType.test( el.type ) &&
|
|
el.click && nodeName( el, "input" ) ) {
|
|
|
|
// dataPriv.set( el, "click", ... )
|
|
leverageNative( el, "click", returnTrue );
|
|
}
|
|
|
|
// Return false to allow normal processing in the caller
|
|
return false;
|
|
},
|
|
trigger: function( data ) {
|
|
|
|
// For mutual compressibility with _default, replace `this` access with a local var.
|
|
// `|| data` is dead code meant only to preserve the variable through minification.
|
|
var el = this || data;
|
|
|
|
// Force setup before triggering a click
|
|
if ( rcheckableType.test( el.type ) &&
|
|
el.click && nodeName( el, "input" ) ) {
|
|
|
|
leverageNative( el, "click" );
|
|
}
|
|
|
|
// Return non-false to allow normal event-path propagation
|
|
return true;
|
|
},
|
|
|
|
// For cross-browser consistency, suppress native .click() on links
|
|
// Also prevent it if we're currently inside a leveraged native-event stack
|
|
_default: function( event ) {
|
|
var target = event.target;
|
|
return rcheckableType.test( target.type ) &&
|
|
target.click && nodeName( target, "input" ) &&
|
|
dataPriv.get( target, "click" ) ||
|
|
nodeName( target, "a" );
|
|
}
|
|
},
|
|
|
|
beforeunload: {
|
|
postDispatch: function( event ) {
|
|
|
|
// Support: Firefox 20+
|
|
// Firefox doesn't alert if the returnValue field is not set.
|
|
if ( event.result !== undefined && event.originalEvent ) {
|
|
event.originalEvent.returnValue = event.result;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// Ensure the presence of an event listener that handles manually-triggered
|
|
// synthetic events by interrupting progress until reinvoked in response to
|
|
// *native* events that it fires directly, ensuring that state changes have
|
|
// already occurred before other listeners are invoked.
|
|
function leverageNative( el, type, expectSync ) {
|
|
|
|
// Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add
|
|
if ( !expectSync ) {
|
|
if ( dataPriv.get( el, type ) === undefined ) {
|
|
jQuery.event.add( el, type, returnTrue );
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Register the controller as a special universal handler for all event namespaces
|
|
dataPriv.set( el, type, false );
|
|
jQuery.event.add( el, type, {
|
|
namespace: false,
|
|
handler: function( event ) {
|
|
var notAsync, result,
|
|
saved = dataPriv.get( this, type );
|
|
|
|
if ( ( event.isTrigger & 1 ) && this[ type ] ) {
|
|
|
|
// Interrupt processing of the outer synthetic .trigger()ed event
|
|
// Saved data should be false in such cases, but might be a leftover capture object
|
|
// from an async native handler (gh-4350)
|
|
if ( !saved.length ) {
|
|
|
|
// Store arguments for use when handling the inner native event
|
|
// There will always be at least one argument (an event object), so this array
|
|
// will not be confused with a leftover capture object.
|
|
saved = slice.call( arguments );
|
|
dataPriv.set( this, type, saved );
|
|
|
|
// Trigger the native event and capture its result
|
|
// Support: IE <=9 - 11+
|
|
// focus() and blur() are asynchronous
|
|
notAsync = expectSync( this, type );
|
|
this[ type ]();
|
|
result = dataPriv.get( this, type );
|
|
if ( saved !== result || notAsync ) {
|
|
dataPriv.set( this, type, false );
|
|
} else {
|
|
result = {};
|
|
}
|
|
if ( saved !== result ) {
|
|
|
|
// Cancel the outer synthetic event
|
|
event.stopImmediatePropagation();
|
|
event.preventDefault();
|
|
return result.value;
|
|
}
|
|
|
|
// If this is an inner synthetic event for an event with a bubbling surrogate
|
|
// (focus or blur), assume that the surrogate already propagated from triggering the
|
|
// native event and prevent that from happening again here.
|
|
// This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the
|
|
// bubbling surrogate propagates *after* the non-bubbling base), but that seems
|
|
// less bad than duplication.
|
|
} else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) {
|
|
event.stopPropagation();
|
|
}
|
|
|
|
// If this is a native event triggered above, everything is now in order
|
|
// Fire an inner synthetic event with the original arguments
|
|
} else if ( saved.length ) {
|
|
|
|
// ...and capture the result
|
|
dataPriv.set( this, type, {
|
|
value: jQuery.event.trigger(
|
|
|
|
// Support: IE <=9 - 11+
|
|
// Extend with the prototype to reset the above stopImmediatePropagation()
|
|
jQuery.extend( saved[ 0 ], jQuery.Event.prototype ),
|
|
saved.slice( 1 ),
|
|
this
|
|
)
|
|
} );
|
|
|
|
// Abort handling of the native event
|
|
event.stopImmediatePropagation();
|
|
}
|
|
}
|
|
} );
|
|
}
|
|
|
|
jQuery.removeEvent = function( elem, type, handle ) {
|
|
|
|
// This "if" is needed for plain objects
|
|
if ( elem.removeEventListener ) {
|
|
elem.removeEventListener( type, handle );
|
|
}
|
|
};
|
|
|
|
jQuery.Event = function( src, props ) {
|
|
|
|
// Allow instantiation without the 'new' keyword
|
|
if ( !( this instanceof jQuery.Event ) ) {
|
|
return new jQuery.Event( src, props );
|
|
}
|
|
|
|
// Event object
|
|
if ( src && src.type ) {
|
|
this.originalEvent = src;
|
|
this.type = src.type;
|
|
|
|
// Events bubbling up the document may have been marked as prevented
|
|
// by a handler lower down the tree; reflect the correct value.
|
|
this.isDefaultPrevented = src.defaultPrevented ||
|
|
src.defaultPrevented === undefined &&
|
|
|
|
// Support: Android <=2.3 only
|
|
src.returnValue === false ?
|
|
returnTrue :
|
|
returnFalse;
|
|
|
|
// Create target properties
|
|
// Support: Safari <=6 - 7 only
|
|
// Target should not be a text node (#504, #13143)
|
|
this.target = ( src.target && src.target.nodeType === 3 ) ?
|
|
src.target.parentNode :
|
|
src.target;
|
|
|
|
this.currentTarget = src.currentTarget;
|
|
this.relatedTarget = src.relatedTarget;
|
|
|
|
// Event type
|
|
} else {
|
|
this.type = src;
|
|
}
|
|
|
|
// Put explicitly provided properties onto the event object
|
|
if ( props ) {
|
|
jQuery.extend( this, props );
|
|
}
|
|
|
|
// Create a timestamp if incoming event doesn't have one
|
|
this.timeStamp = src && src.timeStamp || Date.now();
|
|
|
|
// Mark it as fixed
|
|
this[ jQuery.expando ] = true;
|
|
};
|
|
|
|
// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
|
|
// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
|
|
jQuery.Event.prototype = {
|
|
constructor: jQuery.Event,
|
|
isDefaultPrevented: returnFalse,
|
|
isPropagationStopped: returnFalse,
|
|
isImmediatePropagationStopped: returnFalse,
|
|
isSimulated: false,
|
|
|
|
preventDefault: function() {
|
|
var e = this.originalEvent;
|
|
|
|
this.isDefaultPrevented = returnTrue;
|
|
|
|
if ( e && !this.isSimulated ) {
|
|
e.preventDefault();
|
|
}
|
|
},
|
|
stopPropagation: function() {
|
|
var e = this.originalEvent;
|
|
|
|
this.isPropagationStopped = returnTrue;
|
|
|
|
if ( e && !this.isSimulated ) {
|
|
e.stopPropagation();
|
|
}
|
|
},
|
|
stopImmediatePropagation: function() {
|
|
var e = this.originalEvent;
|
|
|
|
this.isImmediatePropagationStopped = returnTrue;
|
|
|
|
if ( e && !this.isSimulated ) {
|
|
e.stopImmediatePropagation();
|
|
}
|
|
|
|
this.stopPropagation();
|
|
}
|
|
};
|
|
|
|
// Includes all common event props including KeyEvent and MouseEvent specific props
|
|
jQuery.each( {
|
|
altKey: true,
|
|
bubbles: true,
|
|
cancelable: true,
|
|
changedTouches: true,
|
|
ctrlKey: true,
|
|
detail: true,
|
|
eventPhase: true,
|
|
metaKey: true,
|
|
pageX: true,
|
|
pageY: true,
|
|
shiftKey: true,
|
|
view: true,
|
|
"char": true,
|
|
code: true,
|
|
charCode: true,
|
|
key: true,
|
|
keyCode: true,
|
|
button: true,
|
|
buttons: true,
|
|
clientX: true,
|
|
clientY: true,
|
|
offsetX: true,
|
|
offsetY: true,
|
|
pointerId: true,
|
|
pointerType: true,
|
|
screenX: true,
|
|
screenY: true,
|
|
targetTouches: true,
|
|
toElement: true,
|
|
touches: true,
|
|
|
|
which: function( event ) {
|
|
var button = event.button;
|
|
|
|
// Add which for key events
|
|
if ( event.which == null && rkeyEvent.test( event.type ) ) {
|
|
return event.charCode != null ? event.charCode : event.keyCode;
|
|
}
|
|
|
|
// Add which for click: 1 === left; 2 === middle; 3 === right
|
|
if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) {
|
|
if ( button & 1 ) {
|
|
return 1;
|
|
}
|
|
|
|
if ( button & 2 ) {
|
|
return 3;
|
|
}
|
|
|
|
if ( button & 4 ) {
|
|
return 2;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
return event.which;
|
|
}
|
|
}, jQuery.event.addProp );
|
|
|
|
jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) {
|
|
jQuery.event.special[ type ] = {
|
|
|
|
// Utilize native event if possible so blur/focus sequence is correct
|
|
setup: function() {
|
|
|
|
// Claim the first handler
|
|
// dataPriv.set( this, "focus", ... )
|
|
// dataPriv.set( this, "blur", ... )
|
|
leverageNative( this, type, expectSync );
|
|
|
|
// Return false to allow normal processing in the caller
|
|
return false;
|
|
},
|
|
trigger: function() {
|
|
|
|
// Force setup before trigger
|
|
leverageNative( this, type );
|
|
|
|
// Return non-false to allow normal event-path propagation
|
|
return true;
|
|
},
|
|
|
|
delegateType: delegateType
|
|
};
|
|
} );
|
|
|
|
// Create mouseenter/leave events using mouseover/out and event-time checks
|
|
// so that event delegation works in jQuery.
|
|
// Do the same for pointerenter/pointerleave and pointerover/pointerout
|
|
//
|
|
// Support: Safari 7 only
|
|
// Safari sends mouseenter too often; see:
|
|
// https://bugs.chromium.org/p/chromium/issues/detail?id=470258
|
|
// for the description of the bug (it existed in older Chrome versions as well).
|
|
jQuery.each( {
|
|
mouseenter: "mouseover",
|
|
mouseleave: "mouseout",
|
|
pointerenter: "pointerover",
|
|
pointerleave: "pointerout"
|
|
}, function( orig, fix ) {
|
|
jQuery.event.special[ orig ] = {
|
|
delegateType: fix,
|
|
bindType: fix,
|
|
|
|
handle: function( event ) {
|
|
var ret,
|
|
target = this,
|
|
related = event.relatedTarget,
|
|
handleObj = event.handleObj;
|
|
|
|
// For mouseenter/leave call the handler if related is outside the target.
|
|
// NB: No relatedTarget if the mouse left/entered the browser window
|
|
if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) {
|
|
event.type = handleObj.origType;
|
|
ret = handleObj.handler.apply( this, arguments );
|
|
event.type = fix;
|
|
}
|
|
return ret;
|
|
}
|
|
};
|
|
} );
|
|
|
|
jQuery.fn.extend( {
|
|
|
|
on: function( types, selector, data, fn ) {
|
|
return on( this, types, selector, data, fn );
|
|
},
|
|
one: function( types, selector, data, fn ) {
|
|
return on( this, types, selector, data, fn, 1 );
|
|
},
|
|
off: function( types, selector, fn ) {
|
|
var handleObj, type;
|
|
if ( types && types.preventDefault && types.handleObj ) {
|
|
|
|
// ( event ) dispatched jQuery.Event
|
|
handleObj = types.handleObj;
|
|
jQuery( types.delegateTarget ).off(
|
|
handleObj.namespace ?
|
|
handleObj.origType + "." + handleObj.namespace :
|
|
handleObj.origType,
|
|
handleObj.selector,
|
|
handleObj.handler
|
|
);
|
|
return this;
|
|
}
|
|
if ( typeof types === "object" ) {
|
|
|
|
// ( types-object [, selector] )
|
|
for ( type in types ) {
|
|
this.off( type, selector, types[ type ] );
|
|
}
|
|
return this;
|
|
}
|
|
if ( selector === false || typeof selector === "function" ) {
|
|
|
|
// ( types [, fn] )
|
|
fn = selector;
|
|
selector = undefined;
|
|
}
|
|
if ( fn === false ) {
|
|
fn = returnFalse;
|
|
}
|
|
return this.each( function() {
|
|
jQuery.event.remove( this, types, fn, selector );
|
|
} );
|
|
}
|
|
} );
|
|
|
|
|
|
var
|
|
|
|
/* eslint-disable max-len */
|
|
|
|
// See https://github.com/eslint/eslint/issues/3229
|
|
rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi,
|
|
|
|
/* eslint-enable */
|
|
|
|
// Support: IE <=10 - 11, Edge 12 - 13 only
|
|
// In IE/Edge using regex groups here causes severe slowdowns.
|
|
// See https://connect.microsoft.com/IE/feedback/details/1736512/
|
|
rnoInnerhtml = /<script|<style|<link/i,
|
|
|
|
// checked="checked" or checked
|
|
rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
|
|
rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;
|
|
|
|
// Prefer a tbody over its parent table for containing new rows
|
|
function manipulationTarget( elem, content ) {
|
|
if ( nodeName( elem, "table" ) &&
|
|
nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) {
|
|
|
|
return jQuery( elem ).children( "tbody" )[ 0 ] || elem;
|
|
}
|
|
|
|
return elem;
|
|
}
|
|
|
|
// Replace/restore the type attribute of script elements for safe DOM manipulation
|
|
function disableScript( elem ) {
|
|
elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type;
|
|
return elem;
|
|
}
|
|
function restoreScript( elem ) {
|
|
if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) {
|
|
elem.type = elem.type.slice( 5 );
|
|
} else {
|
|
elem.removeAttribute( "type" );
|
|
}
|
|
|
|
return elem;
|
|
}
|
|
|
|
function cloneCopyEvent( src, dest ) {
|
|
var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events;
|
|
|
|
if ( dest.nodeType !== 1 ) {
|
|
return;
|
|
}
|
|
|
|
// 1. Copy private data: events, handlers, etc.
|
|
if ( dataPriv.hasData( src ) ) {
|
|
pdataOld = dataPriv.access( src );
|
|
pdataCur = dataPriv.set( dest, pdataOld );
|
|
events = pdataOld.events;
|
|
|
|
if ( events ) {
|
|
delete pdataCur.handle;
|
|
pdataCur.events = {};
|
|
|
|
for ( type in events ) {
|
|
for ( i = 0, l = events[ type ].length; i < l; i++ ) {
|
|
jQuery.event.add( dest, type, events[ type ][ i ] );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 2. Copy user data
|
|
if ( dataUser.hasData( src ) ) {
|
|
udataOld = dataUser.access( src );
|
|
udataCur = jQuery.extend( {}, udataOld );
|
|
|
|
dataUser.set( dest, udataCur );
|
|
}
|
|
}
|
|
|
|
// Fix IE bugs, see support tests
|
|
function fixInput( src, dest ) {
|
|
var nodeName = dest.nodeName.toLowerCase();
|
|
|
|
// Fails to persist the checked state of a cloned checkbox or radio button.
|
|
if ( nodeName === "input" && rcheckableType.test( src.type ) ) {
|
|
dest.checked = src.checked;
|
|
|
|
// Fails to return the selected option to the default selected state when cloning options
|
|
} else if ( nodeName === "input" || nodeName === "textarea" ) {
|
|
dest.defaultValue = src.defaultValue;
|
|
}
|
|
}
|
|
|
|
function domManip( collection, args, callback, ignored ) {
|
|
|
|
// Flatten any nested arrays
|
|
args = concat.apply( [], args );
|
|
|
|
var fragment, first, scripts, hasScripts, node, doc,
|
|
i = 0,
|
|
l = collection.length,
|
|
iNoClone = l - 1,
|
|
value = args[ 0 ],
|
|
valueIsFunction = isFunction( value );
|
|
|
|
// We can't cloneNode fragments that contain checked, in WebKit
|
|
if ( valueIsFunction ||
|
|
( l > 1 && typeof value === "string" &&
|
|
!support.checkClone && rchecked.test( value ) ) ) {
|
|
return collection.each( function( index ) {
|
|
var self = collection.eq( index );
|
|
if ( valueIsFunction ) {
|
|
args[ 0 ] = value.call( this, index, self.html() );
|
|
}
|
|
domManip( self, args, callback, ignored );
|
|
} );
|
|
}
|
|
|
|
if ( l ) {
|
|
fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored );
|
|
first = fragment.firstChild;
|
|
|
|
if ( fragment.childNodes.length === 1 ) {
|
|
fragment = first;
|
|
}
|
|
|
|
// Require either new content or an interest in ignored elements to invoke the callback
|
|
if ( first || ignored ) {
|
|
scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
|
|
hasScripts = scripts.length;
|
|
|
|
// Use the original fragment for the last item
|
|
// instead of the first because it can end up
|
|
// being emptied incorrectly in certain situations (#8070).
|
|
for ( ; i < l; i++ ) {
|
|
node = fragment;
|
|
|
|
if ( i !== iNoClone ) {
|
|
node = jQuery.clone( node, true, true );
|
|
|
|
// Keep references to cloned scripts for later restoration
|
|
if ( hasScripts ) {
|
|
|
|
// Support: Android <=4.0 only, PhantomJS 1 only
|
|
// push.apply(_, arraylike) throws on ancient WebKit
|
|
jQuery.merge( scripts, getAll( node, "script" ) );
|
|
}
|
|
}
|
|
|
|
callback.call( collection[ i ], node, i );
|
|
}
|
|
|
|
if ( hasScripts ) {
|
|
doc = scripts[ scripts.length - 1 ].ownerDocument;
|
|
|
|
// Reenable scripts
|
|
jQuery.map( scripts, restoreScript );
|
|
|
|
// Evaluate executable scripts on first document insertion
|
|
for ( i = 0; i < hasScripts; i++ ) {
|
|
node = scripts[ i ];
|
|
if ( rscriptType.test( node.type || "" ) &&
|
|
!dataPriv.access( node, "globalEval" ) &&
|
|
jQuery.contains( doc, node ) ) {
|
|
|
|
if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) {
|
|
|
|
// Optional AJAX dependency, but won't run scripts if not present
|
|
if ( jQuery._evalUrl && !node.noModule ) {
|
|
jQuery._evalUrl( node.src, {
|
|
nonce: node.nonce || node.getAttribute( "nonce" )
|
|
} );
|
|
}
|
|
} else {
|
|
DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return collection;
|
|
}
|
|
|
|
function remove( elem, selector, keepData ) {
|
|
var node,
|
|
nodes = selector ? jQuery.filter( selector, elem ) : elem,
|
|
i = 0;
|
|
|
|
for ( ; ( node = nodes[ i ] ) != null; i++ ) {
|
|
if ( !keepData && node.nodeType === 1 ) {
|
|
jQuery.cleanData( getAll( node ) );
|
|
}
|
|
|
|
if ( node.parentNode ) {
|
|
if ( keepData && isAttached( node ) ) {
|
|
setGlobalEval( getAll( node, "script" ) );
|
|
}
|
|
node.parentNode.removeChild( node );
|
|
}
|
|
}
|
|
|
|
return elem;
|
|
}
|
|
|
|
jQuery.extend( {
|
|
htmlPrefilter: function( html ) {
|
|
return html.replace( rxhtmlTag, "<$1></$2>" );
|
|
},
|
|
|
|
clone: function( elem, dataAndEvents, deepDataAndEvents ) {
|
|
var i, l, srcElements, destElements,
|
|
clone = elem.cloneNode( true ),
|
|
inPage = isAttached( elem );
|
|
|
|
// Fix IE cloning issues
|
|
if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) &&
|
|
!jQuery.isXMLDoc( elem ) ) {
|
|
|
|
// We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2
|
|
destElements = getAll( clone );
|
|
srcElements = getAll( elem );
|
|
|
|
for ( i = 0, l = srcElements.length; i < l; i++ ) {
|
|
fixInput( srcElements[ i ], destElements[ i ] );
|
|
}
|
|
}
|
|
|
|
// Copy the events from the original to the clone
|
|
if ( dataAndEvents ) {
|
|
if ( deepDataAndEvents ) {
|
|
srcElements = srcElements || getAll( elem );
|
|
destElements = destElements || getAll( clone );
|
|
|
|
for ( i = 0, l = srcElements.length; i < l; i++ ) {
|
|
cloneCopyEvent( srcElements[ i ], destElements[ i ] );
|
|
}
|
|
} else {
|
|
cloneCopyEvent( elem, clone );
|
|
}
|
|
}
|
|
|
|
// Preserve script evaluation history
|
|
destElements = getAll( clone, "script" );
|
|
if ( destElements.length > 0 ) {
|
|
setGlobalEval( destElements, !inPage && getAll( elem, "script" ) );
|
|
}
|
|
|
|
// Return the cloned set
|
|
return clone;
|
|
},
|
|
|
|
cleanData: function( elems ) {
|
|
var data, elem, type,
|
|
special = jQuery.event.special,
|
|
i = 0;
|
|
|
|
for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) {
|
|
if ( acceptData( elem ) ) {
|
|
if ( ( data = elem[ dataPriv.expando ] ) ) {
|
|
if ( data.events ) {
|
|
for ( type in data.events ) {
|
|
if ( special[ type ] ) {
|
|
jQuery.event.remove( elem, type );
|
|
|
|
// This is a shortcut to avoid jQuery.event.remove's overhead
|
|
} else {
|
|
jQuery.removeEvent( elem, type, data.handle );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Support: Chrome <=35 - 45+
|
|
// Assign undefined instead of using delete, see Data#remove
|
|
elem[ dataPriv.expando ] = undefined;
|
|
}
|
|
if ( elem[ dataUser.expando ] ) {
|
|
|
|
// Support: Chrome <=35 - 45+
|
|
// Assign undefined instead of using delete, see Data#remove
|
|
elem[ dataUser.expando ] = undefined;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} );
|
|
|
|
jQuery.fn.extend( {
|
|
detach: function( selector ) {
|
|
return remove( this, selector, true );
|
|
},
|
|
|
|
remove: function( selector ) {
|
|
return remove( this, selector );
|
|
},
|
|
|
|
text: function( value ) {
|
|
return access( this, function( value ) {
|
|
return value === undefined ?
|
|
jQuery.text( this ) :
|
|
this.empty().each( function() {
|
|
if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
|
|
this.textContent = value;
|
|
}
|
|
} );
|
|
}, null, value, arguments.length );
|
|
},
|
|
|
|
append: function() {
|
|
return domManip( this, arguments, function( elem ) {
|
|
if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
|
|
var target = manipulationTarget( this, elem );
|
|
target.appendChild( elem );
|
|
}
|
|
} );
|
|
},
|
|
|
|
prepend: function() {
|
|
return domManip( this, arguments, function( elem ) {
|
|
if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
|
|
var target = manipulationTarget( this, elem );
|
|
target.insertBefore( elem, target.firstChild );
|
|
}
|
|
} );
|
|
},
|
|
|
|
before: function() {
|
|
return domManip( this, arguments, function( elem ) {
|
|
if ( this.parentNode ) {
|
|
this.parentNode.insertBefore( elem, this );
|
|
}
|
|
} );
|
|
},
|
|
|
|
after: function() {
|
|
return domManip( this, arguments, function( elem ) {
|
|
if ( this.parentNode ) {
|
|
this.parentNode.insertBefore( elem, this.nextSibling );
|
|
}
|
|
} );
|
|
},
|
|
|
|
empty: function() {
|
|
var elem,
|
|
i = 0;
|
|
|
|
for ( ; ( elem = this[ i ] ) != null; i++ ) {
|
|
if ( elem.nodeType === 1 ) {
|
|
|
|
// Prevent memory leaks
|
|
jQuery.cleanData( getAll( elem, false ) );
|
|
|
|
// Remove any remaining nodes
|
|
elem.textContent = "";
|
|
}
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
clone: function( dataAndEvents, deepDataAndEvents ) {
|
|
dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
|
|
deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
|
|
|
|
return this.map( function() {
|
|
return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
|
|
} );
|
|
},
|
|
|
|
html: function( value ) {
|
|
return access( this, function( value ) {
|
|
var elem = this[ 0 ] || {},
|
|
i = 0,
|
|
l = this.length;
|
|
|
|
if ( value === undefined && elem.nodeType === 1 ) {
|
|
return elem.innerHTML;
|
|
}
|
|
|
|
// See if we can take a shortcut and just use innerHTML
|
|
if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
|
|
!wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) {
|
|
|
|
value = jQuery.htmlPrefilter( value );
|
|
|
|
try {
|
|
for ( ; i < l; i++ ) {
|
|
elem = this[ i ] || {};
|
|
|
|
// Remove element nodes and prevent memory leaks
|
|
if ( elem.nodeType === 1 ) {
|
|
jQuery.cleanData( getAll( elem, false ) );
|
|
elem.innerHTML = value;
|
|
}
|
|
}
|
|
|
|
elem = 0;
|
|
|
|
// If using innerHTML throws an exception, use the fallback method
|
|
} catch ( e ) {}
|
|
}
|
|
|
|
if ( elem ) {
|
|
this.empty().append( value );
|
|
}
|
|
}, null, value, arguments.length );
|
|
},
|
|
|
|
replaceWith: function() {
|
|
var ignored = [];
|
|
|
|
// Make the changes, replacing each non-ignored context element with the new content
|
|
return domManip( this, arguments, function( elem ) {
|
|
var parent = this.parentNode;
|
|
|
|
if ( jQuery.inArray( this, ignored ) < 0 ) {
|
|
jQuery.cleanData( getAll( this ) );
|
|
if ( parent ) {
|
|
parent.replaceChild( elem, this );
|
|
}
|
|
}
|
|
|
|
// Force callback invocation
|
|
}, ignored );
|
|
}
|
|
} );
|
|
|
|
jQuery.each( {
|
|
appendTo: "append",
|
|
prependTo: "prepend",
|
|
insertBefore: "before",
|
|
insertAfter: "after",
|
|
replaceAll: "replaceWith"
|
|
}, function( name, original ) {
|
|
jQuery.fn[ name ] = function( selector ) {
|
|
var elems,
|
|
ret = [],
|
|
insert = jQuery( selector ),
|
|
last = insert.length - 1,
|
|
i = 0;
|
|
|
|
for ( ; i <= last; i++ ) {
|
|
elems = i === last ? this : this.clone( true );
|
|
jQuery( insert[ i ] )[ original ]( elems );
|
|
|
|
// Support: Android <=4.0 only, PhantomJS 1 only
|
|
// .get() because push.apply(_, arraylike) throws on ancient WebKit
|
|
push.apply( ret, elems.get() );
|
|
}
|
|
|
|
return this.pushStack( ret );
|
|
};
|
|
} );
|
|
var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" );
|
|
|
|
var getStyles = function( elem ) {
|
|
|
|
// Support: IE <=11 only, Firefox <=30 (#15098, #14150)
|
|
// IE throws on elements created in popups
|
|
// FF meanwhile throws on frame elements through "defaultView.getComputedStyle"
|
|
var view = elem.ownerDocument.defaultView;
|
|
|
|
if ( !view || !view.opener ) {
|
|
view = window;
|
|
}
|
|
|
|
return view.getComputedStyle( elem );
|
|
};
|
|
|
|
var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" );
|
|
|
|
|
|
|
|
( function() {
|
|
|
|
// Executing both pixelPosition & boxSizingReliable tests require only one layout
|
|
// so they're executed at the same time to save the second computation.
|
|
function computeStyleTests() {
|
|
|
|
// This is a singleton, we need to execute it only once
|
|
if ( !div ) {
|
|
return;
|
|
}
|
|
|
|
container.style.cssText = "position:absolute;left:-11111px;width:60px;" +
|
|
"margin-top:1px;padding:0;border:0";
|
|
div.style.cssText =
|
|
"position:relative;display:block;box-sizing:border-box;overflow:scroll;" +
|
|
"margin:auto;border:1px;padding:1px;" +
|
|
"width:60%;top:1%";
|
|
documentElement.appendChild( container ).appendChild( div );
|
|
|
|
var divStyle = window.getComputedStyle( div );
|
|
pixelPositionVal = divStyle.top !== "1%";
|
|
|
|
// Support: Android 4.0 - 4.3 only, Firefox <=3 - 44
|
|
reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12;
|
|
|
|
// Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3
|
|
// Some styles come back with percentage values, even though they shouldn't
|
|
div.style.right = "60%";
|
|
pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36;
|
|
|
|
// Support: IE 9 - 11 only
|
|
// Detect misreporting of content dimensions for box-sizing:border-box elements
|
|
boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36;
|
|
|
|
// Support: IE 9 only
|
|
// Detect overflow:scroll screwiness (gh-3699)
|
|
// Support: Chrome <=64
|
|
// Don't get tricked when zoom affects offsetWidth (gh-4029)
|
|
div.style.position = "absolute";
|
|
scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12;
|
|
|
|
documentElement.removeChild( container );
|
|
|
|
// Nullify the div so it wouldn't be stored in the memory and
|
|
// it will also be a sign that checks already performed
|
|
div = null;
|
|
}
|
|
|
|
function roundPixelMeasures( measure ) {
|
|
return Math.round( parseFloat( measure ) );
|
|
}
|
|
|
|
var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal,
|
|
reliableMarginLeftVal,
|
|
container = document.createElement( "div" ),
|
|
div = document.createElement( "div" );
|
|
|
|
// Finish early in limited (non-browser) environments
|
|
if ( !div.style ) {
|
|
return;
|
|
}
|
|
|
|
// Support: IE <=9 - 11 only
|
|
// Style of cloned element affects source element cloned (#8908)
|
|
div.style.backgroundClip = "content-box";
|
|
div.cloneNode( true ).style.backgroundClip = "";
|
|
support.clearCloneStyle = div.style.backgroundClip === "content-box";
|
|
|
|
jQuery.extend( support, {
|
|
boxSizingReliable: function() {
|
|
computeStyleTests();
|
|
return boxSizingReliableVal;
|
|
},
|
|
pixelBoxStyles: function() {
|
|
computeStyleTests();
|
|
return pixelBoxStylesVal;
|
|
},
|
|
pixelPosition: function() {
|
|
computeStyleTests();
|
|
return pixelPositionVal;
|
|
},
|
|
reliableMarginLeft: function() {
|
|
computeStyleTests();
|
|
return reliableMarginLeftVal;
|
|
},
|
|
scrollboxSize: function() {
|
|
computeStyleTests();
|
|
return scrollboxSizeVal;
|
|
}
|
|
} );
|
|
} )();
|
|
|
|
|
|
function curCSS( elem, name, computed ) {
|
|
var width, minWidth, maxWidth, ret,
|
|
|
|
// Support: Firefox 51+
|
|
// Retrieving style before computed somehow
|
|
// fixes an issue with getting wrong values
|
|
// on detached elements
|
|
style = elem.style;
|
|
|
|
computed = computed || getStyles( elem );
|
|
|
|
// getPropertyValue is needed for:
|
|
// .css('filter') (IE 9 only, #12537)
|
|
// .css('--customProperty) (#3144)
|
|
if ( computed ) {
|
|
ret = computed.getPropertyValue( name ) || computed[ name ];
|
|
|
|
if ( ret === "" && !isAttached( elem ) ) {
|
|
ret = jQuery.style( elem, name );
|
|
}
|
|
|
|
// A tribute to the "awesome hack by Dean Edwards"
|
|
// Android Browser returns percentage for some values,
|
|
// but width seems to be reliably pixels.
|
|
// This is against the CSSOM draft spec:
|
|
// https://drafts.csswg.org/cssom/#resolved-values
|
|
if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) {
|
|
|
|
// Remember the original values
|
|
width = style.width;
|
|
minWidth = style.minWidth;
|
|
maxWidth = style.maxWidth;
|
|
|
|
// Put in the new values to get a computed value out
|
|
style.minWidth = style.maxWidth = style.width = ret;
|
|
ret = computed.width;
|
|
|
|
// Revert the changed values
|
|
style.width = width;
|
|
style.minWidth = minWidth;
|
|
style.maxWidth = maxWidth;
|
|
}
|
|
}
|
|
|
|
return ret !== undefined ?
|
|
|
|
// Support: IE <=9 - 11 only
|
|
// IE returns zIndex value as an integer.
|
|
ret + "" :
|
|
ret;
|
|
}
|
|
|
|
|
|
function addGetHookIf( conditionFn, hookFn ) {
|
|
|
|
// Define the hook, we'll check on the first run if it's really needed.
|
|
return {
|
|
get: function() {
|
|
if ( conditionFn() ) {
|
|
|
|
// Hook not needed (or it's not possible to use it due
|
|
// to missing dependency), remove it.
|
|
delete this.get;
|
|
return;
|
|
}
|
|
|
|
// Hook needed; redefine it so that the support test is not executed again.
|
|
return ( this.get = hookFn ).apply( this, arguments );
|
|
}
|
|
};
|
|
}
|
|
|
|
|
|
var cssPrefixes = [ "Webkit", "Moz", "ms" ],
|
|
emptyStyle = document.createElement( "div" ).style,
|
|
vendorProps = {};
|
|
|
|
// Return a vendor-prefixed property or undefined
|
|
function vendorPropName( name ) {
|
|
|
|
// Check for vendor prefixed names
|
|
var capName = name[ 0 ].toUpperCase() + name.slice( 1 ),
|
|
i = cssPrefixes.length;
|
|
|
|
while ( i-- ) {
|
|
name = cssPrefixes[ i ] + capName;
|
|
if ( name in emptyStyle ) {
|
|
return name;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return a potentially-mapped jQuery.cssProps or vendor prefixed property
|
|
function finalPropName( name ) {
|
|
var final = jQuery.cssProps[ name ] || vendorProps[ name ];
|
|
|
|
if ( final ) {
|
|
return final;
|
|
}
|
|
if ( name in emptyStyle ) {
|
|
return name;
|
|
}
|
|
return vendorProps[ name ] = vendorPropName( name ) || name;
|
|
}
|
|
|
|
|
|
var
|
|
|
|
// Swappable if display is none or starts with table
|
|
// except "table", "table-cell", or "table-caption"
|
|
// See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
|
|
rdisplayswap = /^(none|table(?!-c[ea]).+)/,
|
|
rcustomProp = /^--/,
|
|
cssShow = { position: "absolute", visibility: "hidden", display: "block" },
|
|
cssNormalTransform = {
|
|
letterSpacing: "0",
|
|
fontWeight: "400"
|
|
};
|
|
|
|
function setPositiveNumber( elem, value, subtract ) {
|
|
|
|
// Any relative (+/-) values have already been
|
|
// normalized at this point
|
|
var matches = rcssNum.exec( value );
|
|
return matches ?
|
|
|
|
// Guard against undefined "subtract", e.g., when used as in cssHooks
|
|
Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) :
|
|
value;
|
|
}
|
|
|
|
function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) {
|
|
var i = dimension === "width" ? 1 : 0,
|
|
extra = 0,
|
|
delta = 0;
|
|
|
|
// Adjustment may not be necessary
|
|
if ( box === ( isBorderBox ? "border" : "content" ) ) {
|
|
return 0;
|
|
}
|
|
|
|
for ( ; i < 4; i += 2 ) {
|
|
|
|
// Both box models exclude margin
|
|
if ( box === "margin" ) {
|
|
delta += jQuery.css( elem, box + cssExpand[ i ], true, styles );
|
|
}
|
|
|
|
// If we get here with a content-box, we're seeking "padding" or "border" or "margin"
|
|
if ( !isBorderBox ) {
|
|
|
|
// Add padding
|
|
delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
|
|
|
|
// For "border" or "margin", add border
|
|
if ( box !== "padding" ) {
|
|
delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
|
|
|
|
// But still keep track of it otherwise
|
|
} else {
|
|
extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
|
|
}
|
|
|
|
// If we get here with a border-box (content + padding + border), we're seeking "content" or
|
|
// "padding" or "margin"
|
|
} else {
|
|
|
|
// For "content", subtract padding
|
|
if ( box === "content" ) {
|
|
delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
|
|
}
|
|
|
|
// For "content" or "padding", subtract border
|
|
if ( box !== "margin" ) {
|
|
delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Account for positive content-box scroll gutter when requested by providing computedVal
|
|
if ( !isBorderBox && computedVal >= 0 ) {
|
|
|
|
// offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border
|
|
// Assuming integer scroll gutter, subtract the rest and round down
|
|
delta += Math.max( 0, Math.ceil(
|
|
elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] -
|
|
computedVal -
|
|
delta -
|
|
extra -
|
|
0.5
|
|
|
|
// If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter
|
|
// Use an explicit zero to avoid NaN (gh-3964)
|
|
) ) || 0;
|
|
}
|
|
|
|
return delta;
|
|
}
|
|
|
|
function getWidthOrHeight( elem, dimension, extra ) {
|
|
|
|
// Start with computed style
|
|
var styles = getStyles( elem ),
|
|
|
|
// To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322).
|
|
// Fake content-box until we know it's needed to know the true value.
|
|
boxSizingNeeded = !support.boxSizingReliable() || extra,
|
|
isBorderBox = boxSizingNeeded &&
|
|
jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
|
|
valueIsBorderBox = isBorderBox,
|
|
|
|
val = curCSS( elem, dimension, styles ),
|
|
offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 );
|
|
|
|
// Support: Firefox <=54
|
|
// Return a confounding non-pixel value or feign ignorance, as appropriate.
|
|
if ( rnumnonpx.test( val ) ) {
|
|
if ( !extra ) {
|
|
return val;
|
|
}
|
|
val = "auto";
|
|
}
|
|
|
|
|
|
// Fall back to offsetWidth/offsetHeight when value is "auto"
|
|
// This happens for inline elements with no explicit setting (gh-3571)
|
|
// Support: Android <=4.1 - 4.3 only
|
|
// Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602)
|
|
// Support: IE 9-11 only
|
|
// Also use offsetWidth/offsetHeight for when box sizing is unreliable
|
|
// We use getClientRects() to check for hidden/disconnected.
|
|
// In those cases, the computed value can be trusted to be border-box
|
|
if ( ( !support.boxSizingReliable() && isBorderBox ||
|
|
val === "auto" ||
|
|
!parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) &&
|
|
elem.getClientRects().length ) {
|
|
|
|
isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box";
|
|
|
|
// Where available, offsetWidth/offsetHeight approximate border box dimensions.
|
|
// Where not available (e.g., SVG), assume unreliable box-sizing and interpret the
|
|
// retrieved value as a content box dimension.
|
|
valueIsBorderBox = offsetProp in elem;
|
|
if ( valueIsBorderBox ) {
|
|
val = elem[ offsetProp ];
|
|
}
|
|
}
|
|
|
|
// Normalize "" and auto
|
|
val = parseFloat( val ) || 0;
|
|
|
|
// Adjust for the element's box model
|
|
return ( val +
|
|
boxModelAdjustment(
|
|
elem,
|
|
dimension,
|
|
extra || ( isBorderBox ? "border" : "content" ),
|
|
valueIsBorderBox,
|
|
styles,
|
|
|
|
// Provide the current computed size to request scroll gutter calculation (gh-3589)
|
|
val
|
|
)
|
|
) + "px";
|
|
}
|
|
|
|
jQuery.extend( {
|
|
|
|
// Add in style property hooks for overriding the default
|
|
// behavior of getting and setting a style property
|
|
cssHooks: {
|
|
opacity: {
|
|
get: function( elem, computed ) {
|
|
if ( computed ) {
|
|
|
|
// We should always get a number back from opacity
|
|
var ret = curCSS( elem, "opacity" );
|
|
return ret === "" ? "1" : ret;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
// Don't automatically add "px" to these possibly-unitless properties
|
|
cssNumber: {
|
|
"animationIterationCount": true,
|
|
"columnCount": true,
|
|
"fillOpacity": true,
|
|
"flexGrow": true,
|
|
"flexShrink": true,
|
|
"fontWeight": true,
|
|
"gridArea": true,
|
|
"gridColumn": true,
|
|
"gridColumnEnd": true,
|
|
"gridColumnStart": true,
|
|
"gridRow": true,
|
|
"gridRowEnd": true,
|
|
"gridRowStart": true,
|
|
"lineHeight": true,
|
|
"opacity": true,
|
|
"order": true,
|
|
"orphans": true,
|
|
"widows": true,
|
|
"zIndex": true,
|
|
"zoom": true
|
|
},
|
|
|
|
// Add in properties whose names you wish to fix before
|
|
// setting or getting the value
|
|
cssProps: {},
|
|
|
|
// Get and set the style property on a DOM Node
|
|
style: function( elem, name, value, extra ) {
|
|
|
|
// Don't set styles on text and comment nodes
|
|
if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
|
|
return;
|
|
}
|
|
|
|
// Make sure that we're working with the right name
|
|
var ret, type, hooks,
|
|
origName = camelCase( name ),
|
|
isCustomProp = rcustomProp.test( name ),
|
|
style = elem.style;
|
|
|
|
// Make sure that we're working with the right name. We don't
|
|
// want to query the value if it is a CSS custom property
|
|
// since they are user-defined.
|
|
if ( !isCustomProp ) {
|
|
name = finalPropName( origName );
|
|
}
|
|
|
|
// Gets hook for the prefixed version, then unprefixed version
|
|
hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
|
|
|
|
// Check if we're setting a value
|
|
if ( value !== undefined ) {
|
|
type = typeof value;
|
|
|
|
// Convert "+=" or "-=" to relative numbers (#7345)
|
|
if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) {
|
|
value = adjustCSS( elem, name, ret );
|
|
|
|
// Fixes bug #9237
|
|
type = "number";
|
|
}
|
|
|
|
// Make sure that null and NaN values aren't set (#7116)
|
|
if ( value == null || value !== value ) {
|
|
return;
|
|
}
|
|
|
|
// If a number was passed in, add the unit (except for certain CSS properties)
|
|
// The isCustomProp check can be removed in jQuery 4.0 when we only auto-append
|
|
// "px" to a few hardcoded values.
|
|
if ( type === "number" && !isCustomProp ) {
|
|
value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" );
|
|
}
|
|
|
|
// background-* props affect original clone's values
|
|
if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) {
|
|
style[ name ] = "inherit";
|
|
}
|
|
|
|
// If a hook was provided, use that value, otherwise just set the specified value
|
|
if ( !hooks || !( "set" in hooks ) ||
|
|
( value = hooks.set( elem, value, extra ) ) !== undefined ) {
|
|
|
|
if ( isCustomProp ) {
|
|
style.setProperty( name, value );
|
|
} else {
|
|
style[ name ] = value;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
// If a hook was provided get the non-computed value from there
|
|
if ( hooks && "get" in hooks &&
|
|
( ret = hooks.get( elem, false, extra ) ) !== undefined ) {
|
|
|
|
return ret;
|
|
}
|
|
|
|
// Otherwise just get the value from the style object
|
|
return style[ name ];
|
|
}
|
|
},
|
|
|
|
css: function( elem, name, extra, styles ) {
|
|
var val, num, hooks,
|
|
origName = camelCase( name ),
|
|
isCustomProp = rcustomProp.test( name );
|
|
|
|
// Make sure that we're working with the right name. We don't
|
|
// want to modify the value if it is a CSS custom property
|
|
// since they are user-defined.
|
|
if ( !isCustomProp ) {
|
|
name = finalPropName( origName );
|
|
}
|
|
|
|
// Try prefixed name followed by the unprefixed name
|
|
hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
|
|
|
|
// If a hook was provided get the computed value from there
|
|
if ( hooks && "get" in hooks ) {
|
|
val = hooks.get( elem, true, extra );
|
|
}
|
|
|
|
// Otherwise, if a way to get the computed value exists, use that
|
|
if ( val === undefined ) {
|
|
val = curCSS( elem, name, styles );
|
|
}
|
|
|
|
// Convert "normal" to computed value
|
|
if ( val === "normal" && name in cssNormalTransform ) {
|
|
val = cssNormalTransform[ name ];
|
|
}
|
|
|
|
// Make numeric if forced or a qualifier was provided and val looks numeric
|
|
if ( extra === "" || extra ) {
|
|
num = parseFloat( val );
|
|
return extra === true || isFinite( num ) ? num || 0 : val;
|
|
}
|
|
|
|
return val;
|
|
}
|
|
} );
|
|
|
|
jQuery.each( [ "height", "width" ], function( i, dimension ) {
|
|
jQuery.cssHooks[ dimension ] = {
|
|
get: function( elem, computed, extra ) {
|
|
if ( computed ) {
|
|
|
|
// Certain elements can have dimension info if we invisibly show them
|
|
// but it must have a current display style that would benefit
|
|
return rdisplayswap.test( jQuery.css( elem, "display" ) ) &&
|
|
|
|
// Support: Safari 8+
|
|
// Table columns in Safari have non-zero offsetWidth & zero
|
|
// getBoundingClientRect().width unless display is changed.
|
|
// Support: IE <=11 only
|
|
// Running getBoundingClientRect on a disconnected node
|
|
// in IE throws an error.
|
|
( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ?
|
|
swap( elem, cssShow, function() {
|
|
return getWidthOrHeight( elem, dimension, extra );
|
|
} ) :
|
|
getWidthOrHeight( elem, dimension, extra );
|
|
}
|
|
},
|
|
|
|
set: function( elem, value, extra ) {
|
|
var matches,
|
|
styles = getStyles( elem ),
|
|
|
|
// Only read styles.position if the test has a chance to fail
|
|
// to avoid forcing a reflow.
|
|
scrollboxSizeBuggy = !support.scrollboxSize() &&
|
|
styles.position === "absolute",
|
|
|
|
// To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991)
|
|
boxSizingNeeded = scrollboxSizeBuggy || extra,
|
|
isBorderBox = boxSizingNeeded &&
|
|
jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
|
|
subtract = extra ?
|
|
boxModelAdjustment(
|
|
elem,
|
|
dimension,
|
|
extra,
|
|
isBorderBox,
|
|
styles
|
|
) :
|
|
0;
|
|
|
|
// Account for unreliable border-box dimensions by comparing offset* to computed and
|
|
// faking a content-box to get border and padding (gh-3699)
|
|
if ( isBorderBox && scrollboxSizeBuggy ) {
|
|
subtract -= Math.ceil(
|
|
elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] -
|
|
parseFloat( styles[ dimension ] ) -
|
|
boxModelAdjustment( elem, dimension, "border", false, styles ) -
|
|
0.5
|
|
);
|
|
}
|
|
|
|
// Convert to pixels if value adjustment is needed
|
|
if ( subtract && ( matches = rcssNum.exec( value ) ) &&
|
|
( matches[ 3 ] || "px" ) !== "px" ) {
|
|
|
|
elem.style[ dimension ] = value;
|
|
value = jQuery.css( elem, dimension );
|
|
}
|
|
|
|
return setPositiveNumber( elem, value, subtract );
|
|
}
|
|
};
|
|
} );
|
|
|
|
jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft,
|
|
function( elem, computed ) {
|
|
if ( computed ) {
|
|
return ( parseFloat( curCSS( elem, "marginLeft" ) ) ||
|
|
elem.getBoundingClientRect().left -
|
|
swap( elem, { marginLeft: 0 }, function() {
|
|
return elem.getBoundingClientRect().left;
|
|
} )
|
|
) + "px";
|
|
}
|
|
}
|
|
);
|
|
|
|
// These hooks are used by animate to expand properties
|
|
jQuery.each( {
|
|
margin: "",
|
|
padding: "",
|
|
border: "Width"
|
|
}, function( prefix, suffix ) {
|
|
jQuery.cssHooks[ prefix + suffix ] = {
|
|
expand: function( value ) {
|
|
var i = 0,
|
|
expanded = {},
|
|
|
|
// Assumes a single number if not a string
|
|
parts = typeof value === "string" ? value.split( " " ) : [ value ];
|
|
|
|
for ( ; i < 4; i++ ) {
|
|
expanded[ prefix + cssExpand[ i ] + suffix ] =
|
|
parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
|
|
}
|
|
|
|
return expanded;
|
|
}
|
|
};
|
|
|
|
if ( prefix !== "margin" ) {
|
|
jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
|
|
}
|
|
} );
|
|
|
|
jQuery.fn.extend( {
|
|
css: function( name, value ) {
|
|
return access( this, function( elem, name, value ) {
|
|
var styles, len,
|
|
map = {},
|
|
i = 0;
|
|
|
|
if ( Array.isArray( name ) ) {
|
|
styles = getStyles( elem );
|
|
len = name.length;
|
|
|
|
for ( ; i < len; i++ ) {
|
|
map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
|
|
}
|
|
|
|
return map;
|
|
}
|
|
|
|
return value !== undefined ?
|
|
jQuery.style( elem, name, value ) :
|
|
jQuery.css( elem, name );
|
|
}, name, value, arguments.length > 1 );
|
|
}
|
|
} );
|
|
|
|
|
|
function Tween( elem, options, prop, end, easing ) {
|
|
return new Tween.prototype.init( elem, options, prop, end, easing );
|
|
}
|
|
jQuery.Tween = Tween;
|
|
|
|
Tween.prototype = {
|
|
constructor: Tween,
|
|
init: function( elem, options, prop, end, easing, unit ) {
|
|
this.elem = elem;
|
|
this.prop = prop;
|
|
this.easing = easing || jQuery.easing._default;
|
|
this.options = options;
|
|
this.start = this.now = this.cur();
|
|
this.end = end;
|
|
this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
|
|
},
|
|
cur: function() {
|
|
var hooks = Tween.propHooks[ this.prop ];
|
|
|
|
return hooks && hooks.get ?
|
|
hooks.get( this ) :
|
|
Tween.propHooks._default.get( this );
|
|
},
|
|
run: function( percent ) {
|
|
var eased,
|
|
hooks = Tween.propHooks[ this.prop ];
|
|
|
|
if ( this.options.duration ) {
|
|
this.pos = eased = jQuery.easing[ this.easing ](
|
|
percent, this.options.duration * percent, 0, 1, this.options.duration
|
|
);
|
|
} else {
|
|
this.pos = eased = percent;
|
|
}
|
|
this.now = ( this.end - this.start ) * eased + this.start;
|
|
|
|
if ( this.options.step ) {
|
|
this.options.step.call( this.elem, this.now, this );
|
|
}
|
|
|
|
if ( hooks && hooks.set ) {
|
|
hooks.set( this );
|
|
} else {
|
|
Tween.propHooks._default.set( this );
|
|
}
|
|
return this;
|
|
}
|
|
};
|
|
|
|
Tween.prototype.init.prototype = Tween.prototype;
|
|
|
|
Tween.propHooks = {
|
|
_default: {
|
|
get: function( tween ) {
|
|
var result;
|
|
|
|
// Use a property on the element directly when it is not a DOM element,
|
|
// or when there is no matching style property that exists.
|
|
if ( tween.elem.nodeType !== 1 ||
|
|
tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) {
|
|
return tween.elem[ tween.prop ];
|
|
}
|
|
|
|
// Passing an empty string as a 3rd parameter to .css will automatically
|
|
// attempt a parseFloat and fallback to a string if the parse fails.
|
|
// Simple values such as "10px" are parsed to Float;
|
|
// complex values such as "rotate(1rad)" are returned as-is.
|
|
result = jQuery.css( tween.elem, tween.prop, "" );
|
|
|
|
// Empty strings, null, undefined and "auto" are converted to 0.
|
|
return !result || result === "auto" ? 0 : result;
|
|
},
|
|
set: function( tween ) {
|
|
|
|
// Use step hook for back compat.
|
|
// Use cssHook if its there.
|
|
// Use .style if available and use plain properties where available.
|
|
if ( jQuery.fx.step[ tween.prop ] ) {
|
|
jQuery.fx.step[ tween.prop ]( tween );
|
|
} else if ( tween.elem.nodeType === 1 && (
|
|
jQuery.cssHooks[ tween.prop ] ||
|
|
tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) {
|
|
jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
|
|
} else {
|
|
tween.elem[ tween.prop ] = tween.now;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// Support: IE <=9 only
|
|
// Panic based approach to setting things on disconnected nodes
|
|
Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
|
|
set: function( tween ) {
|
|
if ( tween.elem.nodeType && tween.elem.parentNode ) {
|
|
tween.elem[ tween.prop ] = tween.now;
|
|
}
|
|
}
|
|
};
|
|
|
|
jQuery.easing = {
|
|
linear: function( p ) {
|
|
return p;
|
|
},
|
|
swing: function( p ) {
|
|
return 0.5 - Math.cos( p * Math.PI ) / 2;
|
|
},
|
|
_default: "swing"
|
|
};
|
|
|
|
jQuery.fx = Tween.prototype.init;
|
|
|
|
// Back compat <1.8 extension point
|
|
jQuery.fx.step = {};
|
|
|
|
|
|
|
|
|
|
var
|
|
fxNow, inProgress,
|
|
rfxtypes = /^(?:toggle|show|hide)$/,
|
|
rrun = /queueHooks$/;
|
|
|
|
function schedule() {
|
|
if ( inProgress ) {
|
|
if ( document.hidden === false && window.requestAnimationFrame ) {
|
|
window.requestAnimationFrame( schedule );
|
|
} else {
|
|
window.setTimeout( schedule, jQuery.fx.interval );
|
|
}
|
|
|
|
jQuery.fx.tick();
|
|
}
|
|
}
|
|
|
|
// Animations created synchronously will run synchronously
|
|
function createFxNow() {
|
|
window.setTimeout( function() {
|
|
fxNow = undefined;
|
|
} );
|
|
return ( fxNow = Date.now() );
|
|
}
|
|
|
|
// Generate parameters to create a standard animation
|
|
function genFx( type, includeWidth ) {
|
|
var which,
|
|
i = 0,
|
|
attrs = { height: type };
|
|
|
|
// If we include width, step value is 1 to do all cssExpand values,
|
|
// otherwise step value is 2 to skip over Left and Right
|
|
includeWidth = includeWidth ? 1 : 0;
|
|
for ( ; i < 4; i += 2 - includeWidth ) {
|
|
which = cssExpand[ i ];
|
|
attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
|
|
}
|
|
|
|
if ( includeWidth ) {
|
|
attrs.opacity = attrs.width = type;
|
|
}
|
|
|
|
return attrs;
|
|
}
|
|
|
|
function createTween( value, prop, animation ) {
|
|
var tween,
|
|
collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ),
|
|
index = 0,
|
|
length = collection.length;
|
|
for ( ; index < length; index++ ) {
|
|
if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) {
|
|
|
|
// We're done with this property
|
|
return tween;
|
|
}
|
|
}
|
|
}
|
|
|
|
function defaultPrefilter( elem, props, opts ) {
|
|
var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display,
|
|
isBox = "width" in props || "height" in props,
|
|
anim = this,
|
|
orig = {},
|
|
style = elem.style,
|
|
hidden = elem.nodeType && isHiddenWithinTree( elem ),
|
|
dataShow = dataPriv.get( elem, "fxshow" );
|
|
|
|
// Queue-skipping animations hijack the fx hooks
|
|
if ( !opts.queue ) {
|
|
hooks = jQuery._queueHooks( elem, "fx" );
|
|
if ( hooks.unqueued == null ) {
|
|
hooks.unqueued = 0;
|
|
oldfire = hooks.empty.fire;
|
|
hooks.empty.fire = function() {
|
|
if ( !hooks.unqueued ) {
|
|
oldfire();
|
|
}
|
|
};
|
|
}
|
|
hooks.unqueued++;
|
|
|
|
anim.always( function() {
|
|
|
|
// Ensure the complete handler is called before this completes
|
|
anim.always( function() {
|
|
hooks.unqueued--;
|
|
if ( !jQuery.queue( elem, "fx" ).length ) {
|
|
hooks.empty.fire();
|
|
}
|
|
} );
|
|
} );
|
|
}
|
|
|
|
// Detect show/hide animations
|
|
for ( prop in props ) {
|
|
value = props[ prop ];
|
|
if ( rfxtypes.test( value ) ) {
|
|
delete props[ prop ];
|
|
toggle = toggle || value === "toggle";
|
|
if ( value === ( hidden ? "hide" : "show" ) ) {
|
|
|
|
// Pretend to be hidden if this is a "show" and
|
|
// there is still data from a stopped show/hide
|
|
if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) {
|
|
hidden = true;
|
|
|
|
// Ignore all other no-op show/hide data
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );
|
|
}
|
|
}
|
|
|
|
// Bail out if this is a no-op like .hide().hide()
|
|
propTween = !jQuery.isEmptyObject( props );
|
|
if ( !propTween && jQuery.isEmptyObject( orig ) ) {
|
|
return;
|
|
}
|
|
|
|
// Restrict "overflow" and "display" styles during box animations
|
|
if ( isBox && elem.nodeType === 1 ) {
|
|
|
|
// Support: IE <=9 - 11, Edge 12 - 15
|
|
// Record all 3 overflow attributes because IE does not infer the shorthand
|
|
// from identically-valued overflowX and overflowY and Edge just mirrors
|
|
// the overflowX value there.
|
|
opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
|
|
|
|
// Identify a display type, preferring old show/hide data over the CSS cascade
|
|
restoreDisplay = dataShow && dataShow.display;
|
|
if ( restoreDisplay == null ) {
|
|
restoreDisplay = dataPriv.get( elem, "display" );
|
|
}
|
|
display = jQuery.css( elem, "display" );
|
|
if ( display === "none" ) {
|
|
if ( restoreDisplay ) {
|
|
display = restoreDisplay;
|
|
} else {
|
|
|
|
// Get nonempty value(s) by temporarily forcing visibility
|
|
showHide( [ elem ], true );
|
|
restoreDisplay = elem.style.display || restoreDisplay;
|
|
display = jQuery.css( elem, "display" );
|
|
showHide( [ elem ] );
|
|
}
|
|
}
|
|
|
|
// Animate inline elements as inline-block
|
|
if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) {
|
|
if ( jQuery.css( elem, "float" ) === "none" ) {
|
|
|
|
// Restore the original display value at the end of pure show/hide animations
|
|
if ( !propTween ) {
|
|
anim.done( function() {
|
|
style.display = restoreDisplay;
|
|
} );
|
|
if ( restoreDisplay == null ) {
|
|
display = style.display;
|
|
restoreDisplay = display === "none" ? "" : display;
|
|
}
|
|
}
|
|
style.display = "inline-block";
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( opts.overflow ) {
|
|
style.overflow = "hidden";
|
|
anim.always( function() {
|
|
style.overflow = opts.overflow[ 0 ];
|
|
style.overflowX = opts.overflow[ 1 ];
|
|
style.overflowY = opts.overflow[ 2 ];
|
|
} );
|
|
}
|
|
|
|
// Implement show/hide animations
|
|
propTween = false;
|
|
for ( prop in orig ) {
|
|
|
|
// General show/hide setup for this element animation
|
|
if ( !propTween ) {
|
|
if ( dataShow ) {
|
|
if ( "hidden" in dataShow ) {
|
|
hidden = dataShow.hidden;
|
|
}
|
|
} else {
|
|
dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } );
|
|
}
|
|
|
|
// Store hidden/visible for toggle so `.stop().toggle()` "reverses"
|
|
if ( toggle ) {
|
|
dataShow.hidden = !hidden;
|
|
}
|
|
|
|
// Show elements before animating them
|
|
if ( hidden ) {
|
|
showHide( [ elem ], true );
|
|
}
|
|
|
|
/* eslint-disable no-loop-func */
|
|
|
|
anim.done( function() {
|
|
|
|
/* eslint-enable no-loop-func */
|
|
|
|
// The final step of a "hide" animation is actually hiding the element
|
|
if ( !hidden ) {
|
|
showHide( [ elem ] );
|
|
}
|
|
dataPriv.remove( elem, "fxshow" );
|
|
for ( prop in orig ) {
|
|
jQuery.style( elem, prop, orig[ prop ] );
|
|
}
|
|
} );
|
|
}
|
|
|
|
// Per-property setup
|
|
propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );
|
|
if ( !( prop in dataShow ) ) {
|
|
dataShow[ prop ] = propTween.start;
|
|
if ( hidden ) {
|
|
propTween.end = propTween.start;
|
|
propTween.start = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function propFilter( props, specialEasing ) {
|
|
var index, name, easing, value, hooks;
|
|
|
|
// camelCase, specialEasing and expand cssHook pass
|
|
for ( index in props ) {
|
|
name = camelCase( index );
|
|
easing = specialEasing[ name ];
|
|
value = props[ index ];
|
|
if ( Array.isArray( value ) ) {
|
|
easing = value[ 1 ];
|
|
value = props[ index ] = value[ 0 ];
|
|
}
|
|
|
|
if ( index !== name ) {
|
|
props[ name ] = value;
|
|
delete props[ index ];
|
|
}
|
|
|
|
hooks = jQuery.cssHooks[ name ];
|
|
if ( hooks && "expand" in hooks ) {
|
|
value = hooks.expand( value );
|
|
delete props[ name ];
|
|
|
|
// Not quite $.extend, this won't overwrite existing keys.
|
|
// Reusing 'index' because we have the correct "name"
|
|
for ( index in value ) {
|
|
if ( !( index in props ) ) {
|
|
props[ index ] = value[ index ];
|
|
specialEasing[ index ] = easing;
|
|
}
|
|
}
|
|
} else {
|
|
specialEasing[ name ] = easing;
|
|
}
|
|
}
|
|
}
|
|
|
|
function Animation( elem, properties, options ) {
|
|
var result,
|
|
stopped,
|
|
index = 0,
|
|
length = Animation.prefilters.length,
|
|
deferred = jQuery.Deferred().always( function() {
|
|
|
|
// Don't match elem in the :animated selector
|
|
delete tick.elem;
|
|
} ),
|
|
tick = function() {
|
|
if ( stopped ) {
|
|
return false;
|
|
}
|
|
var currentTime = fxNow || createFxNow(),
|
|
remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
|
|
|
|
// Support: Android 2.3 only
|
|
// Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497)
|
|
temp = remaining / animation.duration || 0,
|
|
percent = 1 - temp,
|
|
index = 0,
|
|
length = animation.tweens.length;
|
|
|
|
for ( ; index < length; index++ ) {
|
|
animation.tweens[ index ].run( percent );
|
|
}
|
|
|
|
deferred.notifyWith( elem, [ animation, percent, remaining ] );
|
|
|
|
// If there's more to do, yield
|
|
if ( percent < 1 && length ) {
|
|
return remaining;
|
|
}
|
|
|
|
// If this was an empty animation, synthesize a final progress notification
|
|
if ( !length ) {
|
|
deferred.notifyWith( elem, [ animation, 1, 0 ] );
|
|
}
|
|
|
|
// Resolve the animation and report its conclusion
|
|
deferred.resolveWith( elem, [ animation ] );
|
|
return false;
|
|
},
|
|
animation = deferred.promise( {
|
|
elem: elem,
|
|
props: jQuery.extend( {}, properties ),
|
|
opts: jQuery.extend( true, {
|
|
specialEasing: {},
|
|
easing: jQuery.easing._default
|
|
}, options ),
|
|
originalProperties: properties,
|
|
originalOptions: options,
|
|
startTime: fxNow || createFxNow(),
|
|
duration: options.duration,
|
|
tweens: [],
|
|
createTween: function( prop, end ) {
|
|
var tween = jQuery.Tween( elem, animation.opts, prop, end,
|
|
animation.opts.specialEasing[ prop ] || animation.opts.easing );
|
|
animation.tweens.push( tween );
|
|
return tween;
|
|
},
|
|
stop: function( gotoEnd ) {
|
|
var index = 0,
|
|
|
|
// If we are going to the end, we want to run all the tweens
|
|
// otherwise we skip this part
|
|
length = gotoEnd ? animation.tweens.length : 0;
|
|
if ( stopped ) {
|
|
return this;
|
|
}
|
|
stopped = true;
|
|
for ( ; index < length; index++ ) {
|
|
animation.tweens[ index ].run( 1 );
|
|
}
|
|
|
|
// Resolve when we played the last frame; otherwise, reject
|
|
if ( gotoEnd ) {
|
|
deferred.notifyWith( elem, [ animation, 1, 0 ] );
|
|
deferred.resolveWith( elem, [ animation, gotoEnd ] );
|
|
} else {
|
|
deferred.rejectWith( elem, [ animation, gotoEnd ] );
|
|
}
|
|
return this;
|
|
}
|
|
} ),
|
|
props = animation.props;
|
|
|
|
propFilter( props, animation.opts.specialEasing );
|
|
|
|
for ( ; index < length; index++ ) {
|
|
result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts );
|
|
if ( result ) {
|
|
if ( isFunction( result.stop ) ) {
|
|
jQuery._queueHooks( animation.elem, animation.opts.queue ).stop =
|
|
result.stop.bind( result );
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
jQuery.map( props, createTween, animation );
|
|
|
|
if ( isFunction( animation.opts.start ) ) {
|
|
animation.opts.start.call( elem, animation );
|
|
}
|
|
|
|
// Attach callbacks from options
|
|
animation
|
|
.progress( animation.opts.progress )
|
|
.done( animation.opts.done, animation.opts.complete )
|
|
.fail( animation.opts.fail )
|
|
.always( animation.opts.always );
|
|
|
|
jQuery.fx.timer(
|
|
jQuery.extend( tick, {
|
|
elem: elem,
|
|
anim: animation,
|
|
queue: animation.opts.queue
|
|
} )
|
|
);
|
|
|
|
return animation;
|
|
}
|
|
|
|
jQuery.Animation = jQuery.extend( Animation, {
|
|
|
|
tweeners: {
|
|
"*": [ function( prop, value ) {
|
|
var tween = this.createTween( prop, value );
|
|
adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween );
|
|
return tween;
|
|
} ]
|
|
},
|
|
|
|
tweener: function( props, callback ) {
|
|
if ( isFunction( props ) ) {
|
|
callback = props;
|
|
props = [ "*" ];
|
|
} else {
|
|
props = props.match( rnothtmlwhite );
|
|
}
|
|
|
|
var prop,
|
|
index = 0,
|
|
length = props.length;
|
|
|
|
for ( ; index < length; index++ ) {
|
|
prop = props[ index ];
|
|
Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || [];
|
|
Animation.tweeners[ prop ].unshift( callback );
|
|
}
|
|
},
|
|
|
|
prefilters: [ defaultPrefilter ],
|
|
|
|
prefilter: function( callback, prepend ) {
|
|
if ( prepend ) {
|
|
Animation.prefilters.unshift( callback );
|
|
} else {
|
|
Animation.prefilters.push( callback );
|
|
}
|
|
}
|
|
} );
|
|
|
|
jQuery.speed = function( speed, easing, fn ) {
|
|
var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
|
|
complete: fn || !fn && easing ||
|
|
isFunction( speed ) && speed,
|
|
duration: speed,
|
|
easing: fn && easing || easing && !isFunction( easing ) && easing
|
|
};
|
|
|
|
// Go to the end state if fx are off
|
|
if ( jQuery.fx.off ) {
|
|
opt.duration = 0;
|
|
|
|
} else {
|
|
if ( typeof opt.duration !== "number" ) {
|
|
if ( opt.duration in jQuery.fx.speeds ) {
|
|
opt.duration = jQuery.fx.speeds[ opt.duration ];
|
|
|
|
} else {
|
|
opt.duration = jQuery.fx.speeds._default;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Normalize opt.queue - true/undefined/null -> "fx"
|
|
if ( opt.queue == null || opt.queue === true ) {
|
|
opt.queue = "fx";
|
|
}
|
|
|
|
// Queueing
|
|
opt.old = opt.complete;
|
|
|
|
opt.complete = function() {
|
|
if ( isFunction( opt.old ) ) {
|
|
opt.old.call( this );
|
|
}
|
|
|
|
if ( opt.queue ) {
|
|
jQuery.dequeue( this, opt.queue );
|
|
}
|
|
};
|
|
|
|
return opt;
|
|
};
|
|
|
|
jQuery.fn.extend( {
|
|
fadeTo: function( speed, to, easing, callback ) {
|
|
|
|
// Show any hidden elements after setting opacity to 0
|
|
return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show()
|
|
|
|
// Animate to the value specified
|
|
.end().animate( { opacity: to }, speed, easing, callback );
|
|
},
|
|
animate: function( prop, speed, easing, callback ) {
|
|
var empty = jQuery.isEmptyObject( prop ),
|
|
optall = jQuery.speed( speed, easing, callback ),
|
|
doAnimation = function() {
|
|
|
|
// Operate on a copy of prop so per-property easing won't be lost
|
|
var anim = Animation( this, jQuery.extend( {}, prop ), optall );
|
|
|
|
// Empty animations, or finishing resolves immediately
|
|
if ( empty || dataPriv.get( this, "finish" ) ) {
|
|
anim.stop( true );
|
|
}
|
|
};
|
|
doAnimation.finish = doAnimation;
|
|
|
|
return empty || optall.queue === false ?
|
|
this.each( doAnimation ) :
|
|
this.queue( optall.queue, doAnimation );
|
|
},
|
|
stop: function( type, clearQueue, gotoEnd ) {
|
|
var stopQueue = function( hooks ) {
|
|
var stop = hooks.stop;
|
|
delete hooks.stop;
|
|
stop( gotoEnd );
|
|
};
|
|
|
|
if ( typeof type !== "string" ) {
|
|
gotoEnd = clearQueue;
|
|
clearQueue = type;
|
|
type = undefined;
|
|
}
|
|
if ( clearQueue && type !== false ) {
|
|
this.queue( type || "fx", [] );
|
|
}
|
|
|
|
return this.each( function() {
|
|
var dequeue = true,
|
|
index = type != null && type + "queueHooks",
|
|
timers = jQuery.timers,
|
|
data = dataPriv.get( this );
|
|
|
|
if ( index ) {
|
|
if ( data[ index ] && data[ index ].stop ) {
|
|
stopQueue( data[ index ] );
|
|
}
|
|
} else {
|
|
for ( index in data ) {
|
|
if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
|
|
stopQueue( data[ index ] );
|
|
}
|
|
}
|
|
}
|
|
|
|
for ( index = timers.length; index--; ) {
|
|
if ( timers[ index ].elem === this &&
|
|
( type == null || timers[ index ].queue === type ) ) {
|
|
|
|
timers[ index ].anim.stop( gotoEnd );
|
|
dequeue = false;
|
|
timers.splice( index, 1 );
|
|
}
|
|
}
|
|
|
|
// Start the next in the queue if the last step wasn't forced.
|
|
// Timers currently will call their complete callbacks, which
|
|
// will dequeue but only if they were gotoEnd.
|
|
if ( dequeue || !gotoEnd ) {
|
|
jQuery.dequeue( this, type );
|
|
}
|
|
} );
|
|
},
|
|
finish: function( type ) {
|
|
if ( type !== false ) {
|
|
type = type || "fx";
|
|
}
|
|
return this.each( function() {
|
|
var index,
|
|
data = dataPriv.get( this ),
|
|
queue = data[ type + "queue" ],
|
|
hooks = data[ type + "queueHooks" ],
|
|
timers = jQuery.timers,
|
|
length = queue ? queue.length : 0;
|
|
|
|
// Enable finishing flag on private data
|
|
data.finish = true;
|
|
|
|
// Empty the queue first
|
|
jQuery.queue( this, type, [] );
|
|
|
|
if ( hooks && hooks.stop ) {
|
|
hooks.stop.call( this, true );
|
|
}
|
|
|
|
// Look for any active animations, and finish them
|
|
for ( index = timers.length; index--; ) {
|
|
if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
|
|
timers[ index ].anim.stop( true );
|
|
timers.splice( index, 1 );
|
|
}
|
|
}
|
|
|
|
// Look for any animations in the old queue and finish them
|
|
for ( index = 0; index < length; index++ ) {
|
|
if ( queue[ index ] && queue[ index ].finish ) {
|
|
queue[ index ].finish.call( this );
|
|
}
|
|
}
|
|
|
|
// Turn off finishing flag
|
|
delete data.finish;
|
|
} );
|
|
}
|
|
} );
|
|
|
|
jQuery.each( [ "toggle", "show", "hide" ], function( i, name ) {
|
|
var cssFn = jQuery.fn[ name ];
|
|
jQuery.fn[ name ] = function( speed, easing, callback ) {
|
|
return speed == null || typeof speed === "boolean" ?
|
|
cssFn.apply( this, arguments ) :
|
|
this.animate( genFx( name, true ), speed, easing, callback );
|
|
};
|
|
} );
|
|
|
|
// Generate shortcuts for custom animations
|
|
jQuery.each( {
|
|
slideDown: genFx( "show" ),
|
|
slideUp: genFx( "hide" ),
|
|
slideToggle: genFx( "toggle" ),
|
|
fadeIn: { opacity: "show" },
|
|
fadeOut: { opacity: "hide" },
|
|
fadeToggle: { opacity: "toggle" }
|
|
}, function( name, props ) {
|
|
jQuery.fn[ name ] = function( speed, easing, callback ) {
|
|
return this.animate( props, speed, easing, callback );
|
|
};
|
|
} );
|
|
|
|
jQuery.timers = [];
|
|
jQuery.fx.tick = function() {
|
|
var timer,
|
|
i = 0,
|
|
timers = jQuery.timers;
|
|
|
|
fxNow = Date.now();
|
|
|
|
for ( ; i < timers.length; i++ ) {
|
|
timer = timers[ i ];
|
|
|
|
// Run the timer and safely remove it when done (allowing for external removal)
|
|
if ( !timer() && timers[ i ] === timer ) {
|
|
timers.splice( i--, 1 );
|
|
}
|
|
}
|
|
|
|
if ( !timers.length ) {
|
|
jQuery.fx.stop();
|
|
}
|
|
fxNow = undefined;
|
|
};
|
|
|
|
jQuery.fx.timer = function( timer ) {
|
|
jQuery.timers.push( timer );
|
|
jQuery.fx.start();
|
|
};
|
|
|
|
jQuery.fx.interval = 13;
|
|
jQuery.fx.start = function() {
|
|
if ( inProgress ) {
|
|
return;
|
|
}
|
|
|
|
inProgress = true;
|
|
schedule();
|
|
};
|
|
|
|
jQuery.fx.stop = function() {
|
|
inProgress = null;
|
|
};
|
|
|
|
jQuery.fx.speeds = {
|
|
slow: 600,
|
|
fast: 200,
|
|
|
|
// Default speed
|
|
_default: 400
|
|
};
|
|
|
|
|
|
// Based off of the plugin by Clint Helfers, with permission.
|
|
// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/
|
|
jQuery.fn.delay = function( time, type ) {
|
|
time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
|
|
type = type || "fx";
|
|
|
|
return this.queue( type, function( next, hooks ) {
|
|
var timeout = window.setTimeout( next, time );
|
|
hooks.stop = function() {
|
|
window.clearTimeout( timeout );
|
|
};
|
|
} );
|
|
};
|
|
|
|
|
|
( function() {
|
|
var input = document.createElement( "input" ),
|
|
select = document.createElement( "select" ),
|
|
opt = select.appendChild( document.createElement( "option" ) );
|
|
|
|
input.type = "checkbox";
|
|
|
|
// Support: Android <=4.3 only
|
|
// Default value for a checkbox should be "on"
|
|
support.checkOn = input.value !== "";
|
|
|
|
// Support: IE <=11 only
|
|
// Must access selectedIndex to make default options select
|
|
support.optSelected = opt.selected;
|
|
|
|
// Support: IE <=11 only
|
|
// An input loses its value after becoming a radio
|
|
input = document.createElement( "input" );
|
|
input.value = "t";
|
|
input.type = "radio";
|
|
support.radioValue = input.value === "t";
|
|
} )();
|
|
|
|
|
|
var boolHook,
|
|
attrHandle = jQuery.expr.attrHandle;
|
|
|
|
jQuery.fn.extend( {
|
|
attr: function( name, value ) {
|
|
return access( this, jQuery.attr, name, value, arguments.length > 1 );
|
|
},
|
|
|
|
removeAttr: function( name ) {
|
|
return this.each( function() {
|
|
jQuery.removeAttr( this, name );
|
|
} );
|
|
}
|
|
} );
|
|
|
|
jQuery.extend( {
|
|
attr: function( elem, name, value ) {
|
|
var ret, hooks,
|
|
nType = elem.nodeType;
|
|
|
|
// Don't get/set attributes on text, comment and attribute nodes
|
|
if ( nType === 3 || nType === 8 || nType === 2 ) {
|
|
return;
|
|
}
|
|
|
|
// Fallback to prop when attributes are not supported
|
|
if ( typeof elem.getAttribute === "undefined" ) {
|
|
return jQuery.prop( elem, name, value );
|
|
}
|
|
|
|
// Attribute hooks are determined by the lowercase version
|
|
// Grab necessary hook if one is defined
|
|
if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
|
|
hooks = jQuery.attrHooks[ name.toLowerCase() ] ||
|
|
( jQuery.expr.match.bool.test( name ) ? boolHook : undefined );
|
|
}
|
|
|
|
if ( value !== undefined ) {
|
|
if ( value === null ) {
|
|
jQuery.removeAttr( elem, name );
|
|
return;
|
|
}
|
|
|
|
if ( hooks && "set" in hooks &&
|
|
( ret = hooks.set( elem, value, name ) ) !== undefined ) {
|
|
return ret;
|
|
}
|
|
|
|
elem.setAttribute( name, value + "" );
|
|
return value;
|
|
}
|
|
|
|
if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {
|
|
return ret;
|
|
}
|
|
|
|
ret = jQuery.find.attr( elem, name );
|
|
|
|
// Non-existent attributes return null, we normalize to undefined
|
|
return ret == null ? undefined : ret;
|
|
},
|
|
|
|
attrHooks: {
|
|
type: {
|
|
set: function( elem, value ) {
|
|
if ( !support.radioValue && value === "radio" &&
|
|
nodeName( elem, "input" ) ) {
|
|
var val = elem.value;
|
|
elem.setAttribute( "type", value );
|
|
if ( val ) {
|
|
elem.value = val;
|
|
}
|
|
return value;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
removeAttr: function( elem, value ) {
|
|
var name,
|
|
i = 0,
|
|
|
|
// Attribute names can contain non-HTML whitespace characters
|
|
// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
|
|
attrNames = value && value.match( rnothtmlwhite );
|
|
|
|
if ( attrNames && elem.nodeType === 1 ) {
|
|
while ( ( name = attrNames[ i++ ] ) ) {
|
|
elem.removeAttribute( name );
|
|
}
|
|
}
|
|
}
|
|
} );
|
|
|
|
// Hooks for boolean attributes
|
|
boolHook = {
|
|
set: function( elem, value, name ) {
|
|
if ( value === false ) {
|
|
|
|
// Remove boolean attributes when set to false
|
|
jQuery.removeAttr( elem, name );
|
|
} else {
|
|
elem.setAttribute( name, name );
|
|
}
|
|
return name;
|
|
}
|
|
};
|
|
|
|
jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) {
|
|
var getter = attrHandle[ name ] || jQuery.find.attr;
|
|
|
|
attrHandle[ name ] = function( elem, name, isXML ) {
|
|
var ret, handle,
|
|
lowercaseName = name.toLowerCase();
|
|
|
|
if ( !isXML ) {
|
|
|
|
// Avoid an infinite loop by temporarily removing this function from the getter
|
|
handle = attrHandle[ lowercaseName ];
|
|
attrHandle[ lowercaseName ] = ret;
|
|
ret = getter( elem, name, isXML ) != null ?
|
|
lowercaseName :
|
|
null;
|
|
attrHandle[ lowercaseName ] = handle;
|
|
}
|
|
return ret;
|
|
};
|
|
} );
|
|
|
|
|
|
|
|
|
|
var rfocusable = /^(?:input|select|textarea|button)$/i,
|
|
rclickable = /^(?:a|area)$/i;
|
|
|
|
jQuery.fn.extend( {
|
|
prop: function( name, value ) {
|
|
return access( this, jQuery.prop, name, value, arguments.length > 1 );
|
|
},
|
|
|
|
removeProp: function( name ) {
|
|
return this.each( function() {
|
|
delete this[ jQuery.propFix[ name ] || name ];
|
|
} );
|
|
}
|
|
} );
|
|
|
|
jQuery.extend( {
|
|
prop: function( elem, name, value ) {
|
|
var ret, hooks,
|
|
nType = elem.nodeType;
|
|
|
|
// Don't get/set properties on text, comment and attribute nodes
|
|
if ( nType === 3 || nType === 8 || nType === 2 ) {
|
|
return;
|
|
}
|
|
|
|
if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
|
|
|
|
// Fix name and attach hooks
|
|
name = jQuery.propFix[ name ] || name;
|
|
hooks = jQuery.propHooks[ name ];
|
|
}
|
|
|
|
if ( value !== undefined ) {
|
|
if ( hooks && "set" in hooks &&
|
|
( ret = hooks.set( elem, value, name ) ) !== undefined ) {
|
|
return ret;
|
|
}
|
|
|
|
return ( elem[ name ] = value );
|
|
}
|
|
|
|
if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {
|
|
return ret;
|
|
}
|
|
|
|
return elem[ name ];
|
|
},
|
|
|
|
propHooks: {
|
|
tabIndex: {
|
|
get: function( elem ) {
|
|
|
|
// Support: IE <=9 - 11 only
|
|
// elem.tabIndex doesn't always return the
|
|
// correct value when it hasn't been explicitly set
|
|
// https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
|
|
// Use proper attribute retrieval(#12072)
|
|
var tabindex = jQuery.find.attr( elem, "tabindex" );
|
|
|
|
if ( tabindex ) {
|
|
return parseInt( tabindex, 10 );
|
|
}
|
|
|
|
if (
|
|
rfocusable.test( elem.nodeName ) ||
|
|
rclickable.test( elem.nodeName ) &&
|
|
elem.href
|
|
) {
|
|
return 0;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
}
|
|
},
|
|
|
|
propFix: {
|
|
"for": "htmlFor",
|
|
"class": "className"
|
|
}
|
|
} );
|
|
|
|
// Support: IE <=11 only
|
|
// Accessing the selectedIndex property
|
|
// forces the browser to respect setting selected
|
|
// on the option
|
|
// The getter ensures a default option is selected
|
|
// when in an optgroup
|
|
// eslint rule "no-unused-expressions" is disabled for this code
|
|
// since it considers such accessions noop
|
|
if ( !support.optSelected ) {
|
|
jQuery.propHooks.selected = {
|
|
get: function( elem ) {
|
|
|
|
/* eslint no-unused-expressions: "off" */
|
|
|
|
var parent = elem.parentNode;
|
|
if ( parent && parent.parentNode ) {
|
|
parent.parentNode.selectedIndex;
|
|
}
|
|
return null;
|
|
},
|
|
set: function( elem ) {
|
|
|
|
/* eslint no-unused-expressions: "off" */
|
|
|
|
var parent = elem.parentNode;
|
|
if ( parent ) {
|
|
parent.selectedIndex;
|
|
|
|
if ( parent.parentNode ) {
|
|
parent.parentNode.selectedIndex;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
jQuery.each( [
|
|
"tabIndex",
|
|
"readOnly",
|
|
"maxLength",
|
|
"cellSpacing",
|
|
"cellPadding",
|
|
"rowSpan",
|
|
"colSpan",
|
|
"useMap",
|
|
"frameBorder",
|
|
"contentEditable"
|
|
], function() {
|
|
jQuery.propFix[ this.toLowerCase() ] = this;
|
|
} );
|
|
|
|
|
|
|
|
|
|
// Strip and collapse whitespace according to HTML spec
|
|
// https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace
|
|
function stripAndCollapse( value ) {
|
|
var tokens = value.match( rnothtmlwhite ) || [];
|
|
return tokens.join( " " );
|
|
}
|
|
|
|
|
|
function getClass( elem ) {
|
|
return elem.getAttribute && elem.getAttribute( "class" ) || "";
|
|
}
|
|
|
|
function classesToArray( value ) {
|
|
if ( Array.isArray( value ) ) {
|
|
return value;
|
|
}
|
|
if ( typeof value === "string" ) {
|
|
return value.match( rnothtmlwhite ) || [];
|
|
}
|
|
return [];
|
|
}
|
|
|
|
jQuery.fn.extend( {
|
|
addClass: function( value ) {
|
|
var classes, elem, cur, curValue, clazz, j, finalValue,
|
|
i = 0;
|
|
|
|
if ( isFunction( value ) ) {
|
|
return this.each( function( j ) {
|
|
jQuery( this ).addClass( value.call( this, j, getClass( this ) ) );
|
|
} );
|
|
}
|
|
|
|
classes = classesToArray( value );
|
|
|
|
if ( classes.length ) {
|
|
while ( ( elem = this[ i++ ] ) ) {
|
|
curValue = getClass( elem );
|
|
cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
|
|
|
|
if ( cur ) {
|
|
j = 0;
|
|
while ( ( clazz = classes[ j++ ] ) ) {
|
|
if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
|
|
cur += clazz + " ";
|
|
}
|
|
}
|
|
|
|
// Only assign if different to avoid unneeded rendering.
|
|
finalValue = stripAndCollapse( cur );
|
|
if ( curValue !== finalValue ) {
|
|
elem.setAttribute( "class", finalValue );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
removeClass: function( value ) {
|
|
var classes, elem, cur, curValue, clazz, j, finalValue,
|
|
i = 0;
|
|
|
|
if ( isFunction( value ) ) {
|
|
return this.each( function( j ) {
|
|
jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) );
|
|
} );
|
|
}
|
|
|
|
if ( !arguments.length ) {
|
|
return this.attr( "class", "" );
|
|
}
|
|
|
|
classes = classesToArray( value );
|
|
|
|
if ( classes.length ) {
|
|
while ( ( elem = this[ i++ ] ) ) {
|
|
curValue = getClass( elem );
|
|
|
|
// This expression is here for better compressibility (see addClass)
|
|
cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );
|
|
|
|
if ( cur ) {
|
|
j = 0;
|
|
while ( ( clazz = classes[ j++ ] ) ) {
|
|
|
|
// Remove *all* instances
|
|
while ( cur.indexOf( " " + clazz + " " ) > -1 ) {
|
|
cur = cur.replace( " " + clazz + " ", " " );
|
|
}
|
|
}
|
|
|
|
// Only assign if different to avoid unneeded rendering.
|
|
finalValue = stripAndCollapse( cur );
|
|
if ( curValue !== finalValue ) {
|
|
elem.setAttribute( "class", finalValue );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
toggleClass: function( value, stateVal ) {
|
|
var type = typeof value,
|
|
isValidValue = type === "string" || Array.isArray( value );
|
|
|
|
if ( typeof stateVal === "boolean" && isValidValue ) {
|
|
return stateVal ? this.addClass( value ) : this.removeClass( value );
|
|
}
|
|
|
|
if ( isFunction( value ) ) {
|
|
return this.each( function( i ) {
|
|
jQuery( this ).toggleClass(
|
|
value.call( this, i, getClass( this ), stateVal ),
|
|
stateVal
|
|
);
|
|
} );
|
|
}
|
|
|
|
return this.each( function() {
|
|
var className, i, self, classNames;
|
|
|
|
if ( isValidValue ) {
|
|
|
|
// Toggle individual class names
|
|
i = 0;
|
|
self = jQuery( this );
|
|
classNames = classesToArray( value );
|
|
|
|
while ( ( className = classNames[ i++ ] ) ) {
|
|
|
|
// Check each className given, space separated list
|
|
if ( self.hasClass( className ) ) {
|
|
self.removeClass( className );
|
|
} else {
|
|
self.addClass( className );
|
|
}
|
|
}
|
|
|
|
// Toggle whole class name
|
|
} else if ( value === undefined || type === "boolean" ) {
|
|
className = getClass( this );
|
|
if ( className ) {
|
|
|
|
// Store className if set
|
|
dataPriv.set( this, "__className__", className );
|
|
}
|
|
|
|
// If the element has a class name or if we're passed `false`,
|
|
// then remove the whole classname (if there was one, the above saved it).
|
|
// Otherwise bring back whatever was previously saved (if anything),
|
|
// falling back to the empty string if nothing was stored.
|
|
if ( this.setAttribute ) {
|
|
this.setAttribute( "class",
|
|
className || value === false ?
|
|
"" :
|
|
dataPriv.get( this, "__className__" ) || ""
|
|
);
|
|
}
|
|
}
|
|
} );
|
|
},
|
|
|
|
hasClass: function( selector ) {
|
|
var className, elem,
|
|
i = 0;
|
|
|
|
className = " " + selector + " ";
|
|
while ( ( elem = this[ i++ ] ) ) {
|
|
if ( elem.nodeType === 1 &&
|
|
( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
} );
|
|
|
|
|
|
|
|
|
|
var rreturn = /\r/g;
|
|
|
|
jQuery.fn.extend( {
|
|
val: function( value ) {
|
|
var hooks, ret, valueIsFunction,
|
|
elem = this[ 0 ];
|
|
|
|
if ( !arguments.length ) {
|
|
if ( elem ) {
|
|
hooks = jQuery.valHooks[ elem.type ] ||
|
|
jQuery.valHooks[ elem.nodeName.toLowerCase() ];
|
|
|
|
if ( hooks &&
|
|
"get" in hooks &&
|
|
( ret = hooks.get( elem, "value" ) ) !== undefined
|
|
) {
|
|
return ret;
|
|
}
|
|
|
|
ret = elem.value;
|
|
|
|
// Handle most common string cases
|
|
if ( typeof ret === "string" ) {
|
|
return ret.replace( rreturn, "" );
|
|
}
|
|
|
|
// Handle cases where value is null/undef or number
|
|
return ret == null ? "" : ret;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
valueIsFunction = isFunction( value );
|
|
|
|
return this.each( function( i ) {
|
|
var val;
|
|
|
|
if ( this.nodeType !== 1 ) {
|
|
return;
|
|
}
|
|
|
|
if ( valueIsFunction ) {
|
|
val = value.call( this, i, jQuery( this ).val() );
|
|
} else {
|
|
val = value;
|
|
}
|
|
|
|
// Treat null/undefined as ""; convert numbers to string
|
|
if ( val == null ) {
|
|
val = "";
|
|
|
|
} else if ( typeof val === "number" ) {
|
|
val += "";
|
|
|
|
} else if ( Array.isArray( val ) ) {
|
|
val = jQuery.map( val, function( value ) {
|
|
return value == null ? "" : value + "";
|
|
} );
|
|
}
|
|
|
|
hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
|
|
|
|
// If set returns undefined, fall back to normal setting
|
|
if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) {
|
|
this.value = val;
|
|
}
|
|
} );
|
|
}
|
|
} );
|
|
|
|
jQuery.extend( {
|
|
valHooks: {
|
|
option: {
|
|
get: function( elem ) {
|
|
|
|
var val = jQuery.find.attr( elem, "value" );
|
|
return val != null ?
|
|
val :
|
|
|
|
// Support: IE <=10 - 11 only
|
|
// option.text throws exceptions (#14686, #14858)
|
|
// Strip and collapse whitespace
|
|
// https://html.spec.whatwg.org/#strip-and-collapse-whitespace
|
|
stripAndCollapse( jQuery.text( elem ) );
|
|
}
|
|
},
|
|
select: {
|
|
get: function( elem ) {
|
|
var value, option, i,
|
|
options = elem.options,
|
|
index = elem.selectedIndex,
|
|
one = elem.type === "select-one",
|
|
values = one ? null : [],
|
|
max = one ? index + 1 : options.length;
|
|
|
|
if ( index < 0 ) {
|
|
i = max;
|
|
|
|
} else {
|
|
i = one ? index : 0;
|
|
}
|
|
|
|
// Loop through all the selected options
|
|
for ( ; i < max; i++ ) {
|
|
option = options[ i ];
|
|
|
|
// Support: IE <=9 only
|
|
// IE8-9 doesn't update selected after form reset (#2551)
|
|
if ( ( option.selected || i === index ) &&
|
|
|
|
// Don't return options that are disabled or in a disabled optgroup
|
|
!option.disabled &&
|
|
( !option.parentNode.disabled ||
|
|
!nodeName( option.parentNode, "optgroup" ) ) ) {
|
|
|
|
// Get the specific value for the option
|
|
value = jQuery( option ).val();
|
|
|
|
// We don't need an array for one selects
|
|
if ( one ) {
|
|
return value;
|
|
}
|
|
|
|
// Multi-Selects return an array
|
|
values.push( value );
|
|
}
|
|
}
|
|
|
|
return values;
|
|
},
|
|
|
|
set: function( elem, value ) {
|
|
var optionSet, option,
|
|
options = elem.options,
|
|
values = jQuery.makeArray( value ),
|
|
i = options.length;
|
|
|
|
while ( i-- ) {
|
|
option = options[ i ];
|
|
|
|
/* eslint-disable no-cond-assign */
|
|
|
|
if ( option.selected =
|
|
jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1
|
|
) {
|
|
optionSet = true;
|
|
}
|
|
|
|
/* eslint-enable no-cond-assign */
|
|
}
|
|
|
|
// Force browsers to behave consistently when non-matching value is set
|
|
if ( !optionSet ) {
|
|
elem.selectedIndex = -1;
|
|
}
|
|
return values;
|
|
}
|
|
}
|
|
}
|
|
} );
|
|
|
|
// Radios and checkboxes getter/setter
|
|
jQuery.each( [ "radio", "checkbox" ], function() {
|
|
jQuery.valHooks[ this ] = {
|
|
set: function( elem, value ) {
|
|
if ( Array.isArray( value ) ) {
|
|
return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 );
|
|
}
|
|
}
|
|
};
|
|
if ( !support.checkOn ) {
|
|
jQuery.valHooks[ this ].get = function( elem ) {
|
|
return elem.getAttribute( "value" ) === null ? "on" : elem.value;
|
|
};
|
|
}
|
|
} );
|
|
|
|
|
|
|
|
|
|
// Return jQuery for attributes-only inclusion
|
|
|
|
|
|
support.focusin = "onfocusin" in window;
|
|
|
|
|
|
var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
|
|
stopPropagationCallback = function( e ) {
|
|
e.stopPropagation();
|
|
};
|
|
|
|
jQuery.extend( jQuery.event, {
|
|
|
|
trigger: function( event, data, elem, onlyHandlers ) {
|
|
|
|
var i, cur, tmp, bubbleType, ontype, handle, special, lastElement,
|
|
eventPath = [ elem || document ],
|
|
type = hasOwn.call( event, "type" ) ? event.type : event,
|
|
namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : [];
|
|
|
|
cur = lastElement = tmp = elem = elem || document;
|
|
|
|
// Don't do events on text and comment nodes
|
|
if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
|
|
return;
|
|
}
|
|
|
|
// focus/blur morphs to focusin/out; ensure we're not firing them right now
|
|
if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
|
|
return;
|
|
}
|
|
|
|
if ( type.indexOf( "." ) > -1 ) {
|
|
|
|
// Namespaced trigger; create a regexp to match event type in handle()
|
|
namespaces = type.split( "." );
|
|
type = namespaces.shift();
|
|
namespaces.sort();
|
|
}
|
|
ontype = type.indexOf( ":" ) < 0 && "on" + type;
|
|
|
|
// Caller can pass in a jQuery.Event object, Object, or just an event type string
|
|
event = event[ jQuery.expando ] ?
|
|
event :
|
|
new jQuery.Event( type, typeof event === "object" && event );
|
|
|
|
// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
|
|
event.isTrigger = onlyHandlers ? 2 : 3;
|
|
event.namespace = namespaces.join( "." );
|
|
event.rnamespace = event.namespace ?
|
|
new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) :
|
|
null;
|
|
|
|
// Clean up the event in case it is being reused
|
|
event.result = undefined;
|
|
if ( !event.target ) {
|
|
event.target = elem;
|
|
}
|
|
|
|
// Clone any incoming data and prepend the event, creating the handler arg list
|
|
data = data == null ?
|
|
[ event ] :
|
|
jQuery.makeArray( data, [ event ] );
|
|
|
|
// Allow special events to draw outside the lines
|
|
special = jQuery.event.special[ type ] || {};
|
|
if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
|
|
return;
|
|
}
|
|
|
|
// Determine event propagation path in advance, per W3C events spec (#9951)
|
|
// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
|
|
if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) {
|
|
|
|
bubbleType = special.delegateType || type;
|
|
if ( !rfocusMorph.test( bubbleType + type ) ) {
|
|
cur = cur.parentNode;
|
|
}
|
|
for ( ; cur; cur = cur.parentNode ) {
|
|
eventPath.push( cur );
|
|
tmp = cur;
|
|
}
|
|
|
|
// Only add window if we got to document (e.g., not plain obj or detached DOM)
|
|
if ( tmp === ( elem.ownerDocument || document ) ) {
|
|
eventPath.push( tmp.defaultView || tmp.parentWindow || window );
|
|
}
|
|
}
|
|
|
|
// Fire handlers on the event path
|
|
i = 0;
|
|
while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) {
|
|
lastElement = cur;
|
|
event.type = i > 1 ?
|
|
bubbleType :
|
|
special.bindType || type;
|
|
|
|
// jQuery handler
|
|
handle = ( dataPriv.get( cur, "events" ) || {} )[ event.type ] &&
|
|
dataPriv.get( cur, "handle" );
|
|
if ( handle ) {
|
|
handle.apply( cur, data );
|
|
}
|
|
|
|
// Native handler
|
|
handle = ontype && cur[ ontype ];
|
|
if ( handle && handle.apply && acceptData( cur ) ) {
|
|
event.result = handle.apply( cur, data );
|
|
if ( event.result === false ) {
|
|
event.preventDefault();
|
|
}
|
|
}
|
|
}
|
|
event.type = type;
|
|
|
|
// If nobody prevented the default action, do it now
|
|
if ( !onlyHandlers && !event.isDefaultPrevented() ) {
|
|
|
|
if ( ( !special._default ||
|
|
special._default.apply( eventPath.pop(), data ) === false ) &&
|
|
acceptData( elem ) ) {
|
|
|
|
// Call a native DOM method on the target with the same name as the event.
|
|
// Don't do default actions on window, that's where global variables be (#6170)
|
|
if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) {
|
|
|
|
// Don't re-trigger an onFOO event when we call its FOO() method
|
|
tmp = elem[ ontype ];
|
|
|
|
if ( tmp ) {
|
|
elem[ ontype ] = null;
|
|
}
|
|
|
|
// Prevent re-triggering of the same event, since we already bubbled it above
|
|
jQuery.event.triggered = type;
|
|
|
|
if ( event.isPropagationStopped() ) {
|
|
lastElement.addEventListener( type, stopPropagationCallback );
|
|
}
|
|
|
|
elem[ type ]();
|
|
|
|
if ( event.isPropagationStopped() ) {
|
|
lastElement.removeEventListener( type, stopPropagationCallback );
|
|
}
|
|
|
|
jQuery.event.triggered = undefined;
|
|
|
|
if ( tmp ) {
|
|
elem[ ontype ] = tmp;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return event.result;
|
|
},
|
|
|
|
// Piggyback on a donor event to simulate a different one
|
|
// Used only for `focus(in | out)` events
|
|
simulate: function( type, elem, event ) {
|
|
var e = jQuery.extend(
|
|
new jQuery.Event(),
|
|
event,
|
|
{
|
|
type: type,
|
|
isSimulated: true
|
|
}
|
|
);
|
|
|
|
jQuery.event.trigger( e, null, elem );
|
|
}
|
|
|
|
} );
|
|
|
|
jQuery.fn.extend( {
|
|
|
|
trigger: function( type, data ) {
|
|
return this.each( function() {
|
|
jQuery.event.trigger( type, data, this );
|
|
} );
|
|
},
|
|
triggerHandler: function( type, data ) {
|
|
var elem = this[ 0 ];
|
|
if ( elem ) {
|
|
return jQuery.event.trigger( type, data, elem, true );
|
|
}
|
|
}
|
|
} );
|
|
|
|
|
|
// Support: Firefox <=44
|
|
// Firefox doesn't have focus(in | out) events
|
|
// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787
|
|
//
|
|
// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1
|
|
// focus(in | out) events fire after focus & blur events,
|
|
// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order
|
|
// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857
|
|
if ( !support.focusin ) {
|
|
jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) {
|
|
|
|
// Attach a single capturing handler on the document while someone wants focusin/focusout
|
|
var handler = function( event ) {
|
|
jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) );
|
|
};
|
|
|
|
jQuery.event.special[ fix ] = {
|
|
setup: function() {
|
|
var doc = this.ownerDocument || this,
|
|
attaches = dataPriv.access( doc, fix );
|
|
|
|
if ( !attaches ) {
|
|
doc.addEventListener( orig, handler, true );
|
|
}
|
|
dataPriv.access( doc, fix, ( attaches || 0 ) + 1 );
|
|
},
|
|
teardown: function() {
|
|
var doc = this.ownerDocument || this,
|
|
attaches = dataPriv.access( doc, fix ) - 1;
|
|
|
|
if ( !attaches ) {
|
|
doc.removeEventListener( orig, handler, true );
|
|
dataPriv.remove( doc, fix );
|
|
|
|
} else {
|
|
dataPriv.access( doc, fix, attaches );
|
|
}
|
|
}
|
|
};
|
|
} );
|
|
}
|
|
var location = window.location;
|
|
|
|
var nonce = Date.now();
|
|
|
|
var rquery = ( /\?/ );
|
|
|
|
|
|
|
|
// Cross-browser xml parsing
|
|
jQuery.parseXML = function( data ) {
|
|
var xml;
|
|
if ( !data || typeof data !== "string" ) {
|
|
return null;
|
|
}
|
|
|
|
// Support: IE 9 - 11 only
|
|
// IE throws on parseFromString with invalid input.
|
|
try {
|
|
xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" );
|
|
} catch ( e ) {
|
|
xml = undefined;
|
|
}
|
|
|
|
if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) {
|
|
jQuery.error( "Invalid XML: " + data );
|
|
}
|
|
return xml;
|
|
};
|
|
|
|
|
|
var
|
|
rbracket = /\[\]$/,
|
|
rCRLF = /\r?\n/g,
|
|
rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,
|
|
rsubmittable = /^(?:input|select|textarea|keygen)/i;
|
|
|
|
function buildParams( prefix, obj, traditional, add ) {
|
|
var name;
|
|
|
|
if ( Array.isArray( obj ) ) {
|
|
|
|
// Serialize array item.
|
|
jQuery.each( obj, function( i, v ) {
|
|
if ( traditional || rbracket.test( prefix ) ) {
|
|
|
|
// Treat each array item as a scalar.
|
|
add( prefix, v );
|
|
|
|
} else {
|
|
|
|
// Item is non-scalar (array or object), encode its numeric index.
|
|
buildParams(
|
|
prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]",
|
|
v,
|
|
traditional,
|
|
add
|
|
);
|
|
}
|
|
} );
|
|
|
|
} else if ( !traditional && toType( obj ) === "object" ) {
|
|
|
|
// Serialize object item.
|
|
for ( name in obj ) {
|
|
buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
|
|
}
|
|
|
|
} else {
|
|
|
|
// Serialize scalar item.
|
|
add( prefix, obj );
|
|
}
|
|
}
|
|
|
|
// Serialize an array of form elements or a set of
|
|
// key/values into a query string
|
|
jQuery.param = function( a, traditional ) {
|
|
var prefix,
|
|
s = [],
|
|
add = function( key, valueOrFunction ) {
|
|
|
|
// If value is a function, invoke it and use its return value
|
|
var value = isFunction( valueOrFunction ) ?
|
|
valueOrFunction() :
|
|
valueOrFunction;
|
|
|
|
s[ s.length ] = encodeURIComponent( key ) + "=" +
|
|
encodeURIComponent( value == null ? "" : value );
|
|
};
|
|
|
|
if ( a == null ) {
|
|
return "";
|
|
}
|
|
|
|
// If an array was passed in, assume that it is an array of form elements.
|
|
if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
|
|
|
|
// Serialize the form elements
|
|
jQuery.each( a, function() {
|
|
add( this.name, this.value );
|
|
} );
|
|
|
|
} else {
|
|
|
|
// If traditional, encode the "old" way (the way 1.3.2 or older
|
|
// did it), otherwise encode params recursively.
|
|
for ( prefix in a ) {
|
|
buildParams( prefix, a[ prefix ], traditional, add );
|
|
}
|
|
}
|
|
|
|
// Return the resulting serialization
|
|
return s.join( "&" );
|
|
};
|
|
|
|
jQuery.fn.extend( {
|
|
serialize: function() {
|
|
return jQuery.param( this.serializeArray() );
|
|
},
|
|
serializeArray: function() {
|
|
return this.map( function() {
|
|
|
|
// Can add propHook for "elements" to filter or add form elements
|
|
var elements = jQuery.prop( this, "elements" );
|
|
return elements ? jQuery.makeArray( elements ) : this;
|
|
} )
|
|
.filter( function() {
|
|
var type = this.type;
|
|
|
|
// Use .is( ":disabled" ) so that fieldset[disabled] works
|
|
return this.name && !jQuery( this ).is( ":disabled" ) &&
|
|
rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&
|
|
( this.checked || !rcheckableType.test( type ) );
|
|
} )
|
|
.map( function( i, elem ) {
|
|
var val = jQuery( this ).val();
|
|
|
|
if ( val == null ) {
|
|
return null;
|
|
}
|
|
|
|
if ( Array.isArray( val ) ) {
|
|
return jQuery.map( val, function( val ) {
|
|
return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
|
|
} );
|
|
}
|
|
|
|
return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
|
|
} ).get();
|
|
}
|
|
} );
|
|
|
|
|
|
var
|
|
r20 = /%20/g,
|
|
rhash = /#.*$/,
|
|
rantiCache = /([?&])_=[^&]*/,
|
|
rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg,
|
|
|
|
// #7653, #8125, #8152: local protocol detection
|
|
rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,
|
|
rnoContent = /^(?:GET|HEAD)$/,
|
|
rprotocol = /^\/\//,
|
|
|
|
/* Prefilters
|
|
* 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
|
|
* 2) These are called:
|
|
* - BEFORE asking for a transport
|
|
* - AFTER param serialization (s.data is a string if s.processData is true)
|
|
* 3) key is the dataType
|
|
* 4) the catchall symbol "*" can be used
|
|
* 5) execution will start with transport dataType and THEN continue down to "*" if needed
|
|
*/
|
|
prefilters = {},
|
|
|
|
/* Transports bindings
|
|
* 1) key is the dataType
|
|
* 2) the catchall symbol "*" can be used
|
|
* 3) selection will start with transport dataType and THEN go to "*" if needed
|
|
*/
|
|
transports = {},
|
|
|
|
// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
|
|
allTypes = "*/".concat( "*" ),
|
|
|
|
// Anchor tag for parsing the document origin
|
|
originAnchor = document.createElement( "a" );
|
|
originAnchor.href = location.href;
|
|
|
|
// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
|
|
function addToPrefiltersOrTransports( structure ) {
|
|
|
|
// dataTypeExpression is optional and defaults to "*"
|
|
return function( dataTypeExpression, func ) {
|
|
|
|
if ( typeof dataTypeExpression !== "string" ) {
|
|
func = dataTypeExpression;
|
|
dataTypeExpression = "*";
|
|
}
|
|
|
|
var dataType,
|
|
i = 0,
|
|
dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || [];
|
|
|
|
if ( isFunction( func ) ) {
|
|
|
|
// For each dataType in the dataTypeExpression
|
|
while ( ( dataType = dataTypes[ i++ ] ) ) {
|
|
|
|
// Prepend if requested
|
|
if ( dataType[ 0 ] === "+" ) {
|
|
dataType = dataType.slice( 1 ) || "*";
|
|
( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func );
|
|
|
|
// Otherwise append
|
|
} else {
|
|
( structure[ dataType ] = structure[ dataType ] || [] ).push( func );
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
// Base inspection function for prefilters and transports
|
|
function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {
|
|
|
|
var inspected = {},
|
|
seekingTransport = ( structure === transports );
|
|
|
|
function inspect( dataType ) {
|
|
var selected;
|
|
inspected[ dataType ] = true;
|
|
jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {
|
|
var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );
|
|
if ( typeof dataTypeOrTransport === "string" &&
|
|
!seekingTransport && !inspected[ dataTypeOrTransport ] ) {
|
|
|
|
options.dataTypes.unshift( dataTypeOrTransport );
|
|
inspect( dataTypeOrTransport );
|
|
return false;
|
|
} else if ( seekingTransport ) {
|
|
return !( selected = dataTypeOrTransport );
|
|
}
|
|
} );
|
|
return selected;
|
|
}
|
|
|
|
return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" );
|
|
}
|
|
|
|
// A special extend for ajax options
|
|
// that takes "flat" options (not to be deep extended)
|
|
// Fixes #9887
|
|
function ajaxExtend( target, src ) {
|
|
var key, deep,
|
|
flatOptions = jQuery.ajaxSettings.flatOptions || {};
|
|
|
|
for ( key in src ) {
|
|
if ( src[ key ] !== undefined ) {
|
|
( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ];
|
|
}
|
|
}
|
|
if ( deep ) {
|
|
jQuery.extend( true, target, deep );
|
|
}
|
|
|
|
return target;
|
|
}
|
|
|
|
/* Handles responses to an ajax request:
|
|
* - finds the right dataType (mediates between content-type and expected dataType)
|
|
* - returns the corresponding response
|
|
*/
|
|
function ajaxHandleResponses( s, jqXHR, responses ) {
|
|
|
|
var ct, type, finalDataType, firstDataType,
|
|
contents = s.contents,
|
|
dataTypes = s.dataTypes;
|
|
|
|
// Remove auto dataType and get content-type in the process
|
|
while ( dataTypes[ 0 ] === "*" ) {
|
|
dataTypes.shift();
|
|
if ( ct === undefined ) {
|
|
ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" );
|
|
}
|
|
}
|
|
|
|
// Check if we're dealing with a known content-type
|
|
if ( ct ) {
|
|
for ( type in contents ) {
|
|
if ( contents[ type ] && contents[ type ].test( ct ) ) {
|
|
dataTypes.unshift( type );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check to see if we have a response for the expected dataType
|
|
if ( dataTypes[ 0 ] in responses ) {
|
|
finalDataType = dataTypes[ 0 ];
|
|
} else {
|
|
|
|
// Try convertible dataTypes
|
|
for ( type in responses ) {
|
|
if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) {
|
|
finalDataType = type;
|
|
break;
|
|
}
|
|
if ( !firstDataType ) {
|
|
firstDataType = type;
|
|
}
|
|
}
|
|
|
|
// Or just use first one
|
|
finalDataType = finalDataType || firstDataType;
|
|
}
|
|
|
|
// If we found a dataType
|
|
// We add the dataType to the list if needed
|
|
// and return the corresponding response
|
|
if ( finalDataType ) {
|
|
if ( finalDataType !== dataTypes[ 0 ] ) {
|
|
dataTypes.unshift( finalDataType );
|
|
}
|
|
return responses[ finalDataType ];
|
|
}
|
|
}
|
|
|
|
/* Chain conversions given the request and the original response
|
|
* Also sets the responseXXX fields on the jqXHR instance
|
|
*/
|
|
function ajaxConvert( s, response, jqXHR, isSuccess ) {
|
|
var conv2, current, conv, tmp, prev,
|
|
converters = {},
|
|
|
|
// Work with a copy of dataTypes in case we need to modify it for conversion
|
|
dataTypes = s.dataTypes.slice();
|
|
|
|
// Create converters map with lowercased keys
|
|
if ( dataTypes[ 1 ] ) {
|
|
for ( conv in s.converters ) {
|
|
converters[ conv.toLowerCase() ] = s.converters[ conv ];
|
|
}
|
|
}
|
|
|
|
current = dataTypes.shift();
|
|
|
|
// Convert to each sequential dataType
|
|
while ( current ) {
|
|
|
|
if ( s.responseFields[ current ] ) {
|
|
jqXHR[ s.responseFields[ current ] ] = response;
|
|
}
|
|
|
|
// Apply the dataFilter if provided
|
|
if ( !prev && isSuccess && s.dataFilter ) {
|
|
response = s.dataFilter( response, s.dataType );
|
|
}
|
|
|
|
prev = current;
|
|
current = dataTypes.shift();
|
|
|
|
if ( current ) {
|
|
|
|
// There's only work to do if current dataType is non-auto
|
|
if ( current === "*" ) {
|
|
|
|
current = prev;
|
|
|
|
// Convert response if prev dataType is non-auto and differs from current
|
|
} else if ( prev !== "*" && prev !== current ) {
|
|
|
|
// Seek a direct converter
|
|
conv = converters[ prev + " " + current ] || converters[ "* " + current ];
|
|
|
|
// If none found, seek a pair
|
|
if ( !conv ) {
|
|
for ( conv2 in converters ) {
|
|
|
|
// If conv2 outputs current
|
|
tmp = conv2.split( " " );
|
|
if ( tmp[ 1 ] === current ) {
|
|
|
|
// If prev can be converted to accepted input
|
|
conv = converters[ prev + " " + tmp[ 0 ] ] ||
|
|
converters[ "* " + tmp[ 0 ] ];
|
|
if ( conv ) {
|
|
|
|
// Condense equivalence converters
|
|
if ( conv === true ) {
|
|
conv = converters[ conv2 ];
|
|
|
|
// Otherwise, insert the intermediate dataType
|
|
} else if ( converters[ conv2 ] !== true ) {
|
|
current = tmp[ 0 ];
|
|
dataTypes.unshift( tmp[ 1 ] );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Apply converter (if not an equivalence)
|
|
if ( conv !== true ) {
|
|
|
|
// Unless errors are allowed to bubble, catch and return them
|
|
if ( conv && s.throws ) {
|
|
response = conv( response );
|
|
} else {
|
|
try {
|
|
response = conv( response );
|
|
} catch ( e ) {
|
|
return {
|
|
state: "parsererror",
|
|
error: conv ? e : "No conversion from " + prev + " to " + current
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return { state: "success", data: response };
|
|
}
|
|
|
|
jQuery.extend( {
|
|
|
|
// Counter for holding the number of active queries
|
|
active: 0,
|
|
|
|
// Last-Modified header cache for next request
|
|
lastModified: {},
|
|
etag: {},
|
|
|
|
ajaxSettings: {
|
|
url: location.href,
|
|
type: "GET",
|
|
isLocal: rlocalProtocol.test( location.protocol ),
|
|
global: true,
|
|
processData: true,
|
|
async: true,
|
|
contentType: "application/x-www-form-urlencoded; charset=UTF-8",
|
|
|
|
/*
|
|
timeout: 0,
|
|
data: null,
|
|
dataType: null,
|
|
username: null,
|
|
password: null,
|
|
cache: null,
|
|
throws: false,
|
|
traditional: false,
|
|
headers: {},
|
|
*/
|
|
|
|
accepts: {
|
|
"*": allTypes,
|
|
text: "text/plain",
|
|
html: "text/html",
|
|
xml: "application/xml, text/xml",
|
|
json: "application/json, text/javascript"
|
|
},
|
|
|
|
contents: {
|
|
xml: /\bxml\b/,
|
|
html: /\bhtml/,
|
|
json: /\bjson\b/
|
|
},
|
|
|
|
responseFields: {
|
|
xml: "responseXML",
|
|
text: "responseText",
|
|
json: "responseJSON"
|
|
},
|
|
|
|
// Data converters
|
|
// Keys separate source (or catchall "*") and destination types with a single space
|
|
converters: {
|
|
|
|
// Convert anything to text
|
|
"* text": String,
|
|
|
|
// Text to html (true = no transformation)
|
|
"text html": true,
|
|
|
|
// Evaluate text as a json expression
|
|
"text json": JSON.parse,
|
|
|
|
// Parse text as xml
|
|
"text xml": jQuery.parseXML
|
|
},
|
|
|
|
// For options that shouldn't be deep extended:
|
|
// you can add your own custom options here if
|
|
// and when you create one that shouldn't be
|
|
// deep extended (see ajaxExtend)
|
|
flatOptions: {
|
|
url: true,
|
|
context: true
|
|
}
|
|
},
|
|
|
|
// Creates a full fledged settings object into target
|
|
// with both ajaxSettings and settings fields.
|
|
// If target is omitted, writes into ajaxSettings.
|
|
ajaxSetup: function( target, settings ) {
|
|
return settings ?
|
|
|
|
// Building a settings object
|
|
ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :
|
|
|
|
// Extending ajaxSettings
|
|
ajaxExtend( jQuery.ajaxSettings, target );
|
|
},
|
|
|
|
ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
|
|
ajaxTransport: addToPrefiltersOrTransports( transports ),
|
|
|
|
// Main method
|
|
ajax: function( url, options ) {
|
|
|
|
// If url is an object, simulate pre-1.5 signature
|
|
if ( typeof url === "object" ) {
|
|
options = url;
|
|
url = undefined;
|
|
}
|
|
|
|
// Force options to be an object
|
|
options = options || {};
|
|
|
|
var transport,
|
|
|
|
// URL without anti-cache param
|
|
cacheURL,
|
|
|
|
// Response headers
|
|
responseHeadersString,
|
|
responseHeaders,
|
|
|
|
// timeout handle
|
|
timeoutTimer,
|
|
|
|
// Url cleanup var
|
|
urlAnchor,
|
|
|
|
// Request state (becomes false upon send and true upon completion)
|
|
completed,
|
|
|
|
// To know if global events are to be dispatched
|
|
fireGlobals,
|
|
|
|
// Loop variable
|
|
i,
|
|
|
|
// uncached part of the url
|
|
uncached,
|
|
|
|
// Create the final options object
|
|
s = jQuery.ajaxSetup( {}, options ),
|
|
|
|
// Callbacks context
|
|
callbackContext = s.context || s,
|
|
|
|
// Context for global events is callbackContext if it is a DOM node or jQuery collection
|
|
globalEventContext = s.context &&
|
|
( callbackContext.nodeType || callbackContext.jquery ) ?
|
|
jQuery( callbackContext ) :
|
|
jQuery.event,
|
|
|
|
// Deferreds
|
|
deferred = jQuery.Deferred(),
|
|
completeDeferred = jQuery.Callbacks( "once memory" ),
|
|
|
|
// Status-dependent callbacks
|
|
statusCode = s.statusCode || {},
|
|
|
|
// Headers (they are sent all at once)
|
|
requestHeaders = {},
|
|
requestHeadersNames = {},
|
|
|
|
// Default abort message
|
|
strAbort = "canceled",
|
|
|
|
// Fake xhr
|
|
jqXHR = {
|
|
readyState: 0,
|
|
|
|
// Builds headers hashtable if needed
|
|
getResponseHeader: function( key ) {
|
|
var match;
|
|
if ( completed ) {
|
|
if ( !responseHeaders ) {
|
|
responseHeaders = {};
|
|
while ( ( match = rheaders.exec( responseHeadersString ) ) ) {
|
|
responseHeaders[ match[ 1 ].toLowerCase() + " " ] =
|
|
( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] )
|
|
.concat( match[ 2 ] );
|
|
}
|
|
}
|
|
match = responseHeaders[ key.toLowerCase() + " " ];
|
|
}
|
|
return match == null ? null : match.join( ", " );
|
|
},
|
|
|
|
// Raw string
|
|
getAllResponseHeaders: function() {
|
|
return completed ? responseHeadersString : null;
|
|
},
|
|
|
|
// Caches the header
|
|
setRequestHeader: function( name, value ) {
|
|
if ( completed == null ) {
|
|
name = requestHeadersNames[ name.toLowerCase() ] =
|
|
requestHeadersNames[ name.toLowerCase() ] || name;
|
|
requestHeaders[ name ] = value;
|
|
}
|
|
return this;
|
|
},
|
|
|
|
// Overrides response content-type header
|
|
overrideMimeType: function( type ) {
|
|
if ( completed == null ) {
|
|
s.mimeType = type;
|
|
}
|
|
return this;
|
|
},
|
|
|
|
// Status-dependent callbacks
|
|
statusCode: function( map ) {
|
|
var code;
|
|
if ( map ) {
|
|
if ( completed ) {
|
|
|
|
// Execute the appropriate callbacks
|
|
jqXHR.always( map[ jqXHR.status ] );
|
|
} else {
|
|
|
|
// Lazy-add the new callbacks in a way that preserves old ones
|
|
for ( code in map ) {
|
|
statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
|
|
}
|
|
}
|
|
}
|
|
return this;
|
|
},
|
|
|
|
// Cancel the request
|
|
abort: function( statusText ) {
|
|
var finalText = statusText || strAbort;
|
|
if ( transport ) {
|
|
transport.abort( finalText );
|
|
}
|
|
done( 0, finalText );
|
|
return this;
|
|
}
|
|
};
|
|
|
|
// Attach deferreds
|
|
deferred.promise( jqXHR );
|
|
|
|
// Add protocol if not provided (prefilters might expect it)
|
|
// Handle falsy url in the settings object (#10093: consistency with old signature)
|
|
// We also use the url parameter if available
|
|
s.url = ( ( url || s.url || location.href ) + "" )
|
|
.replace( rprotocol, location.protocol + "//" );
|
|
|
|
// Alias method option to type as per ticket #12004
|
|
s.type = options.method || options.type || s.method || s.type;
|
|
|
|
// Extract dataTypes list
|
|
s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ];
|
|
|
|
// A cross-domain request is in order when the origin doesn't match the current origin.
|
|
if ( s.crossDomain == null ) {
|
|
urlAnchor = document.createElement( "a" );
|
|
|
|
// Support: IE <=8 - 11, Edge 12 - 15
|
|
// IE throws exception on accessing the href property if url is malformed,
|
|
// e.g. http://example.com:80x/
|
|
try {
|
|
urlAnchor.href = s.url;
|
|
|
|
// Support: IE <=8 - 11 only
|
|
// Anchor's host property isn't correctly set when s.url is relative
|
|
urlAnchor.href = urlAnchor.href;
|
|
s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !==
|
|
urlAnchor.protocol + "//" + urlAnchor.host;
|
|
} catch ( e ) {
|
|
|
|
// If there is an error parsing the URL, assume it is crossDomain,
|
|
// it can be rejected by the transport if it is invalid
|
|
s.crossDomain = true;
|
|
}
|
|
}
|
|
|
|
// Convert data if not already a string
|
|
if ( s.data && s.processData && typeof s.data !== "string" ) {
|
|
s.data = jQuery.param( s.data, s.traditional );
|
|
}
|
|
|
|
// Apply prefilters
|
|
inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
|
|
|
|
// If request was aborted inside a prefilter, stop there
|
|
if ( completed ) {
|
|
return jqXHR;
|
|
}
|
|
|
|
// We can fire global events as of now if asked to
|
|
// Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118)
|
|
fireGlobals = jQuery.event && s.global;
|
|
|
|
// Watch for a new set of requests
|
|
if ( fireGlobals && jQuery.active++ === 0 ) {
|
|
jQuery.event.trigger( "ajaxStart" );
|
|
}
|
|
|
|
// Uppercase the type
|
|
s.type = s.type.toUpperCase();
|
|
|
|
// Determine if request has content
|
|
s.hasContent = !rnoContent.test( s.type );
|
|
|
|
// Save the URL in case we're toying with the If-Modified-Since
|
|
// and/or If-None-Match header later on
|
|
// Remove hash to simplify url manipulation
|
|
cacheURL = s.url.replace( rhash, "" );
|
|
|
|
// More options handling for requests with no content
|
|
if ( !s.hasContent ) {
|
|
|
|
// Remember the hash so we can put it back
|
|
uncached = s.url.slice( cacheURL.length );
|
|
|
|
// If data is available and should be processed, append data to url
|
|
if ( s.data && ( s.processData || typeof s.data === "string" ) ) {
|
|
cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data;
|
|
|
|
// #9682: remove data so that it's not used in an eventual retry
|
|
delete s.data;
|
|
}
|
|
|
|
// Add or update anti-cache param if needed
|
|
if ( s.cache === false ) {
|
|
cacheURL = cacheURL.replace( rantiCache, "$1" );
|
|
uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce++ ) + uncached;
|
|
}
|
|
|
|
// Put hash and anti-cache on the URL that will be requested (gh-1732)
|
|
s.url = cacheURL + uncached;
|
|
|
|
// Change '%20' to '+' if this is encoded form body content (gh-2658)
|
|
} else if ( s.data && s.processData &&
|
|
( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) {
|
|
s.data = s.data.replace( r20, "+" );
|
|
}
|
|
|
|
// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
|
|
if ( s.ifModified ) {
|
|
if ( jQuery.lastModified[ cacheURL ] ) {
|
|
jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
|
|
}
|
|
if ( jQuery.etag[ cacheURL ] ) {
|
|
jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
|
|
}
|
|
}
|
|
|
|
// Set the correct header, if data is being sent
|
|
if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
|
|
jqXHR.setRequestHeader( "Content-Type", s.contentType );
|
|
}
|
|
|
|
// Set the Accepts header for the server, depending on the dataType
|
|
jqXHR.setRequestHeader(
|
|
"Accept",
|
|
s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ?
|
|
s.accepts[ s.dataTypes[ 0 ] ] +
|
|
( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
|
|
s.accepts[ "*" ]
|
|
);
|
|
|
|
// Check for headers option
|
|
for ( i in s.headers ) {
|
|
jqXHR.setRequestHeader( i, s.headers[ i ] );
|
|
}
|
|
|
|
// Allow custom headers/mimetypes and early abort
|
|
if ( s.beforeSend &&
|
|
( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) {
|
|
|
|
// Abort if not done already and return
|
|
return jqXHR.abort();
|
|
}
|
|
|
|
// Aborting is no longer a cancellation
|
|
strAbort = "abort";
|
|
|
|
// Install callbacks on deferreds
|
|
completeDeferred.add( s.complete );
|
|
jqXHR.done( s.success );
|
|
jqXHR.fail( s.error );
|
|
|
|
// Get transport
|
|
transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
|
|
|
|
// If no transport, we auto-abort
|
|
if ( !transport ) {
|
|
done( -1, "No Transport" );
|
|
} else {
|
|
jqXHR.readyState = 1;
|
|
|
|
// Send global event
|
|
if ( fireGlobals ) {
|
|
globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
|
|
}
|
|
|
|
// If request was aborted inside ajaxSend, stop there
|
|
if ( completed ) {
|
|
return jqXHR;
|
|
}
|
|
|
|
// Timeout
|
|
if ( s.async && s.timeout > 0 ) {
|
|
timeoutTimer = window.setTimeout( function() {
|
|
jqXHR.abort( "timeout" );
|
|
}, s.timeout );
|
|
}
|
|
|
|
try {
|
|
completed = false;
|
|
transport.send( requestHeaders, done );
|
|
} catch ( e ) {
|
|
|
|
// Rethrow post-completion exceptions
|
|
if ( completed ) {
|
|
throw e;
|
|
}
|
|
|
|
// Propagate others as results
|
|
done( -1, e );
|
|
}
|
|
}
|
|
|
|
// Callback for when everything is done
|
|
function done( status, nativeStatusText, responses, headers ) {
|
|
var isSuccess, success, error, response, modified,
|
|
statusText = nativeStatusText;
|
|
|
|
// Ignore repeat invocations
|
|
if ( completed ) {
|
|
return;
|
|
}
|
|
|
|
completed = true;
|
|
|
|
// Clear timeout if it exists
|
|
if ( timeoutTimer ) {
|
|
window.clearTimeout( timeoutTimer );
|
|
}
|
|
|
|
// Dereference transport for early garbage collection
|
|
// (no matter how long the jqXHR object will be used)
|
|
transport = undefined;
|
|
|
|
// Cache response headers
|
|
responseHeadersString = headers || "";
|
|
|
|
// Set readyState
|
|
jqXHR.readyState = status > 0 ? 4 : 0;
|
|
|
|
// Determine if successful
|
|
isSuccess = status >= 200 && status < 300 || status === 304;
|
|
|
|
// Get response data
|
|
if ( responses ) {
|
|
response = ajaxHandleResponses( s, jqXHR, responses );
|
|
}
|
|
|
|
// Convert no matter what (that way responseXXX fields are always set)
|
|
response = ajaxConvert( s, response, jqXHR, isSuccess );
|
|
|
|
// If successful, handle type chaining
|
|
if ( isSuccess ) {
|
|
|
|
// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
|
|
if ( s.ifModified ) {
|
|
modified = jqXHR.getResponseHeader( "Last-Modified" );
|
|
if ( modified ) {
|
|
jQuery.lastModified[ cacheURL ] = modified;
|
|
}
|
|
modified = jqXHR.getResponseHeader( "etag" );
|
|
if ( modified ) {
|
|
jQuery.etag[ cacheURL ] = modified;
|
|
}
|
|
}
|
|
|
|
// if no content
|
|
if ( status === 204 || s.type === "HEAD" ) {
|
|
statusText = "nocontent";
|
|
|
|
// if not modified
|
|
} else if ( status === 304 ) {
|
|
statusText = "notmodified";
|
|
|
|
// If we have data, let's convert it
|
|
} else {
|
|
statusText = response.state;
|
|
success = response.data;
|
|
error = response.error;
|
|
isSuccess = !error;
|
|
}
|
|
} else {
|
|
|
|
// Extract error from statusText and normalize for non-aborts
|
|
error = statusText;
|
|
if ( status || !statusText ) {
|
|
statusText = "error";
|
|
if ( status < 0 ) {
|
|
status = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set data for the fake xhr object
|
|
jqXHR.status = status;
|
|
jqXHR.statusText = ( nativeStatusText || statusText ) + "";
|
|
|
|
// Success/Error
|
|
if ( isSuccess ) {
|
|
deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
|
|
} else {
|
|
deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
|
|
}
|
|
|
|
// Status-dependent callbacks
|
|
jqXHR.statusCode( statusCode );
|
|
statusCode = undefined;
|
|
|
|
if ( fireGlobals ) {
|
|
globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
|
|
[ jqXHR, s, isSuccess ? success : error ] );
|
|
}
|
|
|
|
// Complete
|
|
completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
|
|
|
|
if ( fireGlobals ) {
|
|
globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
|
|
|
|
// Handle the global AJAX counter
|
|
if ( !( --jQuery.active ) ) {
|
|
jQuery.event.trigger( "ajaxStop" );
|
|
}
|
|
}
|
|
}
|
|
|
|
return jqXHR;
|
|
},
|
|
|
|
getJSON: function( url, data, callback ) {
|
|
return jQuery.get( url, data, callback, "json" );
|
|
},
|
|
|
|
getScript: function( url, callback ) {
|
|
return jQuery.get( url, undefined, callback, "script" );
|
|
}
|
|
} );
|
|
|
|
jQuery.each( [ "get", "post" ], function( i, method ) {
|
|
jQuery[ method ] = function( url, data, callback, type ) {
|
|
|
|
// Shift arguments if data argument was omitted
|
|
if ( isFunction( data ) ) {
|
|
type = type || callback;
|
|
callback = data;
|
|
data = undefined;
|
|
}
|
|
|
|
// The url can be an options object (which then must have .url)
|
|
return jQuery.ajax( jQuery.extend( {
|
|
url: url,
|
|
type: method,
|
|
dataType: type,
|
|
data: data,
|
|
success: callback
|
|
}, jQuery.isPlainObject( url ) && url ) );
|
|
};
|
|
} );
|
|
|
|
|
|
jQuery._evalUrl = function( url, options ) {
|
|
return jQuery.ajax( {
|
|
url: url,
|
|
|
|
// Make this explicit, since user can override this through ajaxSetup (#11264)
|
|
type: "GET",
|
|
dataType: "script",
|
|
cache: true,
|
|
async: false,
|
|
global: false,
|
|
|
|
// Only evaluate the response if it is successful (gh-4126)
|
|
// dataFilter is not invoked for failure responses, so using it instead
|
|
// of the default converter is kludgy but it works.
|
|
converters: {
|
|
"text script": function() {}
|
|
},
|
|
dataFilter: function( response ) {
|
|
jQuery.globalEval( response, options );
|
|
}
|
|
} );
|
|
};
|
|
|
|
|
|
jQuery.fn.extend( {
|
|
wrapAll: function( html ) {
|
|
var wrap;
|
|
|
|
if ( this[ 0 ] ) {
|
|
if ( isFunction( html ) ) {
|
|
html = html.call( this[ 0 ] );
|
|
}
|
|
|
|
// The elements to wrap the target around
|
|
wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true );
|
|
|
|
if ( this[ 0 ].parentNode ) {
|
|
wrap.insertBefore( this[ 0 ] );
|
|
}
|
|
|
|
wrap.map( function() {
|
|
var elem = this;
|
|
|
|
while ( elem.firstElementChild ) {
|
|
elem = elem.firstElementChild;
|
|
}
|
|
|
|
return elem;
|
|
} ).append( this );
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
wrapInner: function( html ) {
|
|
if ( isFunction( html ) ) {
|
|
return this.each( function( i ) {
|
|
jQuery( this ).wrapInner( html.call( this, i ) );
|
|
} );
|
|
}
|
|
|
|
return this.each( function() {
|
|
var self = jQuery( this ),
|
|
contents = self.contents();
|
|
|
|
if ( contents.length ) {
|
|
contents.wrapAll( html );
|
|
|
|
} else {
|
|
self.append( html );
|
|
}
|
|
} );
|
|
},
|
|
|
|
wrap: function( html ) {
|
|
var htmlIsFunction = isFunction( html );
|
|
|
|
return this.each( function( i ) {
|
|
jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html );
|
|
} );
|
|
},
|
|
|
|
unwrap: function( selector ) {
|
|
this.parent( selector ).not( "body" ).each( function() {
|
|
jQuery( this ).replaceWith( this.childNodes );
|
|
} );
|
|
return this;
|
|
}
|
|
} );
|
|
|
|
|
|
jQuery.expr.pseudos.hidden = function( elem ) {
|
|
return !jQuery.expr.pseudos.visible( elem );
|
|
};
|
|
jQuery.expr.pseudos.visible = function( elem ) {
|
|
return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length );
|
|
};
|
|
|
|
|
|
|
|
|
|
jQuery.ajaxSettings.xhr = function() {
|
|
try {
|
|
return new window.XMLHttpRequest();
|
|
} catch ( e ) {}
|
|
};
|
|
|
|
var xhrSuccessStatus = {
|
|
|
|
// File protocol always yields status code 0, assume 200
|
|
0: 200,
|
|
|
|
// Support: IE <=9 only
|
|
// #1450: sometimes IE returns 1223 when it should be 204
|
|
1223: 204
|
|
},
|
|
xhrSupported = jQuery.ajaxSettings.xhr();
|
|
|
|
support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported );
|
|
support.ajax = xhrSupported = !!xhrSupported;
|
|
|
|
jQuery.ajaxTransport( function( options ) {
|
|
var callback, errorCallback;
|
|
|
|
// Cross domain only allowed if supported through XMLHttpRequest
|
|
if ( support.cors || xhrSupported && !options.crossDomain ) {
|
|
return {
|
|
send: function( headers, complete ) {
|
|
var i,
|
|
xhr = options.xhr();
|
|
|
|
xhr.open(
|
|
options.type,
|
|
options.url,
|
|
options.async,
|
|
options.username,
|
|
options.password
|
|
);
|
|
|
|
// Apply custom fields if provided
|
|
if ( options.xhrFields ) {
|
|
for ( i in options.xhrFields ) {
|
|
xhr[ i ] = options.xhrFields[ i ];
|
|
}
|
|
}
|
|
|
|
// Override mime type if needed
|
|
if ( options.mimeType && xhr.overrideMimeType ) {
|
|
xhr.overrideMimeType( options.mimeType );
|
|
}
|
|
|
|
// X-Requested-With header
|
|
// For cross-domain requests, seeing as conditions for a preflight are
|
|
// akin to a jigsaw puzzle, we simply never set it to be sure.
|
|
// (it can always be set on a per-request basis or even using ajaxSetup)
|
|
// For same-domain requests, won't change header if already provided.
|
|
if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) {
|
|
headers[ "X-Requested-With" ] = "XMLHttpRequest";
|
|
}
|
|
|
|
// Set headers
|
|
for ( i in headers ) {
|
|
xhr.setRequestHeader( i, headers[ i ] );
|
|
}
|
|
|
|
// Callback
|
|
callback = function( type ) {
|
|
return function() {
|
|
if ( callback ) {
|
|
callback = errorCallback = xhr.onload =
|
|
xhr.onerror = xhr.onabort = xhr.ontimeout =
|
|
xhr.onreadystatechange = null;
|
|
|
|
if ( type === "abort" ) {
|
|
xhr.abort();
|
|
} else if ( type === "error" ) {
|
|
|
|
// Support: IE <=9 only
|
|
// On a manual native abort, IE9 throws
|
|
// errors on any property access that is not readyState
|
|
if ( typeof xhr.status !== "number" ) {
|
|
complete( 0, "error" );
|
|
} else {
|
|
complete(
|
|
|
|
// File: protocol always yields status 0; see #8605, #14207
|
|
xhr.status,
|
|
xhr.statusText
|
|
);
|
|
}
|
|
} else {
|
|
complete(
|
|
xhrSuccessStatus[ xhr.status ] || xhr.status,
|
|
xhr.statusText,
|
|
|
|
// Support: IE <=9 only
|
|
// IE9 has no XHR2 but throws on binary (trac-11426)
|
|
// For XHR2 non-text, let the caller handle it (gh-2498)
|
|
( xhr.responseType || "text" ) !== "text" ||
|
|
typeof xhr.responseText !== "string" ?
|
|
{ binary: xhr.response } :
|
|
{ text: xhr.responseText },
|
|
xhr.getAllResponseHeaders()
|
|
);
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
// Listen to events
|
|
xhr.onload = callback();
|
|
errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" );
|
|
|
|
// Support: IE 9 only
|
|
// Use onreadystatechange to replace onabort
|
|
// to handle uncaught aborts
|
|
if ( xhr.onabort !== undefined ) {
|
|
xhr.onabort = errorCallback;
|
|
} else {
|
|
xhr.onreadystatechange = function() {
|
|
|
|
// Check readyState before timeout as it changes
|
|
if ( xhr.readyState === 4 ) {
|
|
|
|
// Allow onerror to be called first,
|
|
// but that will not handle a native abort
|
|
// Also, save errorCallback to a variable
|
|
// as xhr.onerror cannot be accessed
|
|
window.setTimeout( function() {
|
|
if ( callback ) {
|
|
errorCallback();
|
|
}
|
|
} );
|
|
}
|
|
};
|
|
}
|
|
|
|
// Create the abort callback
|
|
callback = callback( "abort" );
|
|
|
|
try {
|
|
|
|
// Do send the request (this may raise an exception)
|
|
xhr.send( options.hasContent && options.data || null );
|
|
} catch ( e ) {
|
|
|
|
// #14683: Only rethrow if this hasn't been notified as an error yet
|
|
if ( callback ) {
|
|
throw e;
|
|
}
|
|
}
|
|
},
|
|
|
|
abort: function() {
|
|
if ( callback ) {
|
|
callback();
|
|
}
|
|
}
|
|
};
|
|
}
|
|
} );
|
|
|
|
|
|
|
|
|
|
// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432)
|
|
jQuery.ajaxPrefilter( function( s ) {
|
|
if ( s.crossDomain ) {
|
|
s.contents.script = false;
|
|
}
|
|
} );
|
|
|
|
// Install script dataType
|
|
jQuery.ajaxSetup( {
|
|
accepts: {
|
|
script: "text/javascript, application/javascript, " +
|
|
"application/ecmascript, application/x-ecmascript"
|
|
},
|
|
contents: {
|
|
script: /\b(?:java|ecma)script\b/
|
|
},
|
|
converters: {
|
|
"text script": function( text ) {
|
|
jQuery.globalEval( text );
|
|
return text;
|
|
}
|
|
}
|
|
} );
|
|
|
|
// Handle cache's special case and crossDomain
|
|
jQuery.ajaxPrefilter( "script", function( s ) {
|
|
if ( s.cache === undefined ) {
|
|
s.cache = false;
|
|
}
|
|
if ( s.crossDomain ) {
|
|
s.type = "GET";
|
|
}
|
|
} );
|
|
|
|
// Bind script tag hack transport
|
|
jQuery.ajaxTransport( "script", function( s ) {
|
|
|
|
// This transport only deals with cross domain or forced-by-attrs requests
|
|
if ( s.crossDomain || s.scriptAttrs ) {
|
|
var script, callback;
|
|
return {
|
|
send: function( _, complete ) {
|
|
script = jQuery( "<script>" )
|
|
.attr( s.scriptAttrs || {} )
|
|
.prop( { charset: s.scriptCharset, src: s.url } )
|
|
.on( "load error", callback = function( evt ) {
|
|
script.remove();
|
|
callback = null;
|
|
if ( evt ) {
|
|
complete( evt.type === "error" ? 404 : 200, evt.type );
|
|
}
|
|
} );
|
|
|
|
// Use native DOM manipulation to avoid our domManip AJAX trickery
|
|
document.head.appendChild( script[ 0 ] );
|
|
},
|
|
abort: function() {
|
|
if ( callback ) {
|
|
callback();
|
|
}
|
|
}
|
|
};
|
|
}
|
|
} );
|
|
|
|
|
|
|
|
|
|
var oldCallbacks = [],
|
|
rjsonp = /(=)\?(?=&|$)|\?\?/;
|
|
|
|
// Default jsonp settings
|
|
jQuery.ajaxSetup( {
|
|
jsonp: "callback",
|
|
jsonpCallback: function() {
|
|
var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce++ ) );
|
|
this[ callback ] = true;
|
|
return callback;
|
|
}
|
|
} );
|
|
|
|
// Detect, normalize options and install callbacks for jsonp requests
|
|
jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
|
|
|
|
var callbackName, overwritten, responseContainer,
|
|
jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?
|
|
"url" :
|
|
typeof s.data === "string" &&
|
|
( s.contentType || "" )
|
|
.indexOf( "application/x-www-form-urlencoded" ) === 0 &&
|
|
rjsonp.test( s.data ) && "data"
|
|
);
|
|
|
|
// Handle iff the expected data type is "jsonp" or we have a parameter to set
|
|
if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {
|
|
|
|
// Get callback name, remembering preexisting value associated with it
|
|
callbackName = s.jsonpCallback = isFunction( s.jsonpCallback ) ?
|
|
s.jsonpCallback() :
|
|
s.jsonpCallback;
|
|
|
|
// Insert callback into url or form data
|
|
if ( jsonProp ) {
|
|
s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
|
|
} else if ( s.jsonp !== false ) {
|
|
s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
|
|
}
|
|
|
|
// Use data converter to retrieve json after script execution
|
|
s.converters[ "script json" ] = function() {
|
|
if ( !responseContainer ) {
|
|
jQuery.error( callbackName + " was not called" );
|
|
}
|
|
return responseContainer[ 0 ];
|
|
};
|
|
|
|
// Force json dataType
|
|
s.dataTypes[ 0 ] = "json";
|
|
|
|
// Install callback
|
|
overwritten = window[ callbackName ];
|
|
window[ callbackName ] = function() {
|
|
responseContainer = arguments;
|
|
};
|
|
|
|
// Clean-up function (fires after converters)
|
|
jqXHR.always( function() {
|
|
|
|
// If previous value didn't exist - remove it
|
|
if ( overwritten === undefined ) {
|
|
jQuery( window ).removeProp( callbackName );
|
|
|
|
// Otherwise restore preexisting value
|
|
} else {
|
|
window[ callbackName ] = overwritten;
|
|
}
|
|
|
|
// Save back as free
|
|
if ( s[ callbackName ] ) {
|
|
|
|
// Make sure that re-using the options doesn't screw things around
|
|
s.jsonpCallback = originalSettings.jsonpCallback;
|
|
|
|
// Save the callback name for future use
|
|
oldCallbacks.push( callbackName );
|
|
}
|
|
|
|
// Call if it was a function and we have a response
|
|
if ( responseContainer && isFunction( overwritten ) ) {
|
|
overwritten( responseContainer[ 0 ] );
|
|
}
|
|
|
|
responseContainer = overwritten = undefined;
|
|
} );
|
|
|
|
// Delegate to script
|
|
return "script";
|
|
}
|
|
} );
|
|
|
|
|
|
|
|
|
|
// Support: Safari 8 only
|
|
// In Safari 8 documents created via document.implementation.createHTMLDocument
|
|
// collapse sibling forms: the second one becomes a child of the first one.
|
|
// Because of that, this security measure has to be disabled in Safari 8.
|
|
// https://bugs.webkit.org/show_bug.cgi?id=137337
|
|
support.createHTMLDocument = ( function() {
|
|
var body = document.implementation.createHTMLDocument( "" ).body;
|
|
body.innerHTML = "<form></form><form></form>";
|
|
return body.childNodes.length === 2;
|
|
} )();
|
|
|
|
|
|
// Argument "data" should be string of html
|
|
// context (optional): If specified, the fragment will be created in this context,
|
|
// defaults to document
|
|
// keepScripts (optional): If true, will include scripts passed in the html string
|
|
jQuery.parseHTML = function( data, context, keepScripts ) {
|
|
if ( typeof data !== "string" ) {
|
|
return [];
|
|
}
|
|
if ( typeof context === "boolean" ) {
|
|
keepScripts = context;
|
|
context = false;
|
|
}
|
|
|
|
var base, parsed, scripts;
|
|
|
|
if ( !context ) {
|
|
|
|
// Stop scripts or inline event handlers from being executed immediately
|
|
// by using document.implementation
|
|
if ( support.createHTMLDocument ) {
|
|
context = document.implementation.createHTMLDocument( "" );
|
|
|
|
// Set the base href for the created document
|
|
// so any parsed elements with URLs
|
|
// are based on the document's URL (gh-2965)
|
|
base = context.createElement( "base" );
|
|
base.href = document.location.href;
|
|
context.head.appendChild( base );
|
|
} else {
|
|
context = document;
|
|
}
|
|
}
|
|
|
|
parsed = rsingleTag.exec( data );
|
|
scripts = !keepScripts && [];
|
|
|
|
// Single tag
|
|
if ( parsed ) {
|
|
return [ context.createElement( parsed[ 1 ] ) ];
|
|
}
|
|
|
|
parsed = buildFragment( [ data ], context, scripts );
|
|
|
|
if ( scripts && scripts.length ) {
|
|
jQuery( scripts ).remove();
|
|
}
|
|
|
|
return jQuery.merge( [], parsed.childNodes );
|
|
};
|
|
|
|
|
|
/**
|
|
* Load a url into a page
|
|
*/
|
|
jQuery.fn.load = function( url, params, callback ) {
|
|
var selector, type, response,
|
|
self = this,
|
|
off = url.indexOf( " " );
|
|
|
|
if ( off > -1 ) {
|
|
selector = stripAndCollapse( url.slice( off ) );
|
|
url = url.slice( 0, off );
|
|
}
|
|
|
|
// If it's a function
|
|
if ( isFunction( params ) ) {
|
|
|
|
// We assume that it's the callback
|
|
callback = params;
|
|
params = undefined;
|
|
|
|
// Otherwise, build a param string
|
|
} else if ( params && typeof params === "object" ) {
|
|
type = "POST";
|
|
}
|
|
|
|
// If we have elements to modify, make the request
|
|
if ( self.length > 0 ) {
|
|
jQuery.ajax( {
|
|
url: url,
|
|
|
|
// If "type" variable is undefined, then "GET" method will be used.
|
|
// Make value of this field explicit since
|
|
// user can override it through ajaxSetup method
|
|
type: type || "GET",
|
|
dataType: "html",
|
|
data: params
|
|
} ).done( function( responseText ) {
|
|
|
|
// Save response for use in complete callback
|
|
response = arguments;
|
|
|
|
self.html( selector ?
|
|
|
|
// If a selector was specified, locate the right elements in a dummy div
|
|
// Exclude scripts to avoid IE 'Permission Denied' errors
|
|
jQuery( "<div>" ).append( jQuery.parseHTML( responseText ) ).find( selector ) :
|
|
|
|
// Otherwise use the full result
|
|
responseText );
|
|
|
|
// If the request succeeds, this function gets "data", "status", "jqXHR"
|
|
// but they are ignored because response was set above.
|
|
// If it fails, this function gets "jqXHR", "status", "error"
|
|
} ).always( callback && function( jqXHR, status ) {
|
|
self.each( function() {
|
|
callback.apply( this, response || [ jqXHR.responseText, status, jqXHR ] );
|
|
} );
|
|
} );
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
|
|
|
|
|
|
// Attach a bunch of functions for handling common AJAX events
|
|
jQuery.each( [
|
|
"ajaxStart",
|
|
"ajaxStop",
|
|
"ajaxComplete",
|
|
"ajaxError",
|
|
"ajaxSuccess",
|
|
"ajaxSend"
|
|
], function( i, type ) {
|
|
jQuery.fn[ type ] = function( fn ) {
|
|
return this.on( type, fn );
|
|
};
|
|
} );
|
|
|
|
|
|
|
|
|
|
jQuery.expr.pseudos.animated = function( elem ) {
|
|
return jQuery.grep( jQuery.timers, function( fn ) {
|
|
return elem === fn.elem;
|
|
} ).length;
|
|
};
|
|
|
|
|
|
|
|
|
|
jQuery.offset = {
|
|
setOffset: function( elem, options, i ) {
|
|
var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,
|
|
position = jQuery.css( elem, "position" ),
|
|
curElem = jQuery( elem ),
|
|
props = {};
|
|
|
|
// Set position first, in-case top/left are set even on static elem
|
|
if ( position === "static" ) {
|
|
elem.style.position = "relative";
|
|
}
|
|
|
|
curOffset = curElem.offset();
|
|
curCSSTop = jQuery.css( elem, "top" );
|
|
curCSSLeft = jQuery.css( elem, "left" );
|
|
calculatePosition = ( position === "absolute" || position === "fixed" ) &&
|
|
( curCSSTop + curCSSLeft ).indexOf( "auto" ) > -1;
|
|
|
|
// Need to be able to calculate position if either
|
|
// top or left is auto and position is either absolute or fixed
|
|
if ( calculatePosition ) {
|
|
curPosition = curElem.position();
|
|
curTop = curPosition.top;
|
|
curLeft = curPosition.left;
|
|
|
|
} else {
|
|
curTop = parseFloat( curCSSTop ) || 0;
|
|
curLeft = parseFloat( curCSSLeft ) || 0;
|
|
}
|
|
|
|
if ( isFunction( options ) ) {
|
|
|
|
// Use jQuery.extend here to allow modification of coordinates argument (gh-1848)
|
|
options = options.call( elem, i, jQuery.extend( {}, curOffset ) );
|
|
}
|
|
|
|
if ( options.top != null ) {
|
|
props.top = ( options.top - curOffset.top ) + curTop;
|
|
}
|
|
if ( options.left != null ) {
|
|
props.left = ( options.left - curOffset.left ) + curLeft;
|
|
}
|
|
|
|
if ( "using" in options ) {
|
|
options.using.call( elem, props );
|
|
|
|
} else {
|
|
curElem.css( props );
|
|
}
|
|
}
|
|
};
|
|
|
|
jQuery.fn.extend( {
|
|
|
|
// offset() relates an element's border box to the document origin
|
|
offset: function( options ) {
|
|
|
|
// Preserve chaining for setter
|
|
if ( arguments.length ) {
|
|
return options === undefined ?
|
|
this :
|
|
this.each( function( i ) {
|
|
jQuery.offset.setOffset( this, options, i );
|
|
} );
|
|
}
|
|
|
|
var rect, win,
|
|
elem = this[ 0 ];
|
|
|
|
if ( !elem ) {
|
|
return;
|
|
}
|
|
|
|
// Return zeros for disconnected and hidden (display: none) elements (gh-2310)
|
|
// Support: IE <=11 only
|
|
// Running getBoundingClientRect on a
|
|
// disconnected node in IE throws an error
|
|
if ( !elem.getClientRects().length ) {
|
|
return { top: 0, left: 0 };
|
|
}
|
|
|
|
// Get document-relative position by adding viewport scroll to viewport-relative gBCR
|
|
rect = elem.getBoundingClientRect();
|
|
win = elem.ownerDocument.defaultView;
|
|
return {
|
|
top: rect.top + win.pageYOffset,
|
|
left: rect.left + win.pageXOffset
|
|
};
|
|
},
|
|
|
|
// position() relates an element's margin box to its offset parent's padding box
|
|
// This corresponds to the behavior of CSS absolute positioning
|
|
position: function() {
|
|
if ( !this[ 0 ] ) {
|
|
return;
|
|
}
|
|
|
|
var offsetParent, offset, doc,
|
|
elem = this[ 0 ],
|
|
parentOffset = { top: 0, left: 0 };
|
|
|
|
// position:fixed elements are offset from the viewport, which itself always has zero offset
|
|
if ( jQuery.css( elem, "position" ) === "fixed" ) {
|
|
|
|
// Assume position:fixed implies availability of getBoundingClientRect
|
|
offset = elem.getBoundingClientRect();
|
|
|
|
} else {
|
|
offset = this.offset();
|
|
|
|
// Account for the *real* offset parent, which can be the document or its root element
|
|
// when a statically positioned element is identified
|
|
doc = elem.ownerDocument;
|
|
offsetParent = elem.offsetParent || doc.documentElement;
|
|
while ( offsetParent &&
|
|
( offsetParent === doc.body || offsetParent === doc.documentElement ) &&
|
|
jQuery.css( offsetParent, "position" ) === "static" ) {
|
|
|
|
offsetParent = offsetParent.parentNode;
|
|
}
|
|
if ( offsetParent && offsetParent !== elem && offsetParent.nodeType === 1 ) {
|
|
|
|
// Incorporate borders into its offset, since they are outside its content origin
|
|
parentOffset = jQuery( offsetParent ).offset();
|
|
parentOffset.top += jQuery.css( offsetParent, "borderTopWidth", true );
|
|
parentOffset.left += jQuery.css( offsetParent, "borderLeftWidth", true );
|
|
}
|
|
}
|
|
|
|
// Subtract parent offsets and element margins
|
|
return {
|
|
top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ),
|
|
left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true )
|
|
};
|
|
},
|
|
|
|
// This method will return documentElement in the following cases:
|
|
// 1) For the element inside the iframe without offsetParent, this method will return
|
|
// documentElement of the parent window
|
|
// 2) For the hidden or detached element
|
|
// 3) For body or html element, i.e. in case of the html node - it will return itself
|
|
//
|
|
// but those exceptions were never presented as a real life use-cases
|
|
// and might be considered as more preferable results.
|
|
//
|
|
// This logic, however, is not guaranteed and can change at any point in the future
|
|
offsetParent: function() {
|
|
return this.map( function() {
|
|
var offsetParent = this.offsetParent;
|
|
|
|
while ( offsetParent && jQuery.css( offsetParent, "position" ) === "static" ) {
|
|
offsetParent = offsetParent.offsetParent;
|
|
}
|
|
|
|
return offsetParent || documentElement;
|
|
} );
|
|
}
|
|
} );
|
|
|
|
// Create scrollLeft and scrollTop methods
|
|
jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( method, prop ) {
|
|
var top = "pageYOffset" === prop;
|
|
|
|
jQuery.fn[ method ] = function( val ) {
|
|
return access( this, function( elem, method, val ) {
|
|
|
|
// Coalesce documents and windows
|
|
var win;
|
|
if ( isWindow( elem ) ) {
|
|
win = elem;
|
|
} else if ( elem.nodeType === 9 ) {
|
|
win = elem.defaultView;
|
|
}
|
|
|
|
if ( val === undefined ) {
|
|
return win ? win[ prop ] : elem[ method ];
|
|
}
|
|
|
|
if ( win ) {
|
|
win.scrollTo(
|
|
!top ? val : win.pageXOffset,
|
|
top ? val : win.pageYOffset
|
|
);
|
|
|
|
} else {
|
|
elem[ method ] = val;
|
|
}
|
|
}, method, val, arguments.length );
|
|
};
|
|
} );
|
|
|
|
// Support: Safari <=7 - 9.1, Chrome <=37 - 49
|
|
// Add the top/left cssHooks using jQuery.fn.position
|
|
// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
|
|
// Blink bug: https://bugs.chromium.org/p/chromium/issues/detail?id=589347
|
|
// getComputedStyle returns percent when specified for top/left/bottom/right;
|
|
// rather than make the css module depend on the offset module, just check for it here
|
|
jQuery.each( [ "top", "left" ], function( i, prop ) {
|
|
jQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition,
|
|
function( elem, computed ) {
|
|
if ( computed ) {
|
|
computed = curCSS( elem, prop );
|
|
|
|
// If curCSS returns percentage, fallback to offset
|
|
return rnumnonpx.test( computed ) ?
|
|
jQuery( elem ).position()[ prop ] + "px" :
|
|
computed;
|
|
}
|
|
}
|
|
);
|
|
} );
|
|
|
|
|
|
// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
|
|
jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
|
|
jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name },
|
|
function( defaultExtra, funcName ) {
|
|
|
|
// Margin is only for outerHeight, outerWidth
|
|
jQuery.fn[ funcName ] = function( margin, value ) {
|
|
var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
|
|
extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
|
|
|
|
return access( this, function( elem, type, value ) {
|
|
var doc;
|
|
|
|
if ( isWindow( elem ) ) {
|
|
|
|
// $( window ).outerWidth/Height return w/h including scrollbars (gh-1729)
|
|
return funcName.indexOf( "outer" ) === 0 ?
|
|
elem[ "inner" + name ] :
|
|
elem.document.documentElement[ "client" + name ];
|
|
}
|
|
|
|
// Get document width or height
|
|
if ( elem.nodeType === 9 ) {
|
|
doc = elem.documentElement;
|
|
|
|
// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],
|
|
// whichever is greatest
|
|
return Math.max(
|
|
elem.body[ "scroll" + name ], doc[ "scroll" + name ],
|
|
elem.body[ "offset" + name ], doc[ "offset" + name ],
|
|
doc[ "client" + name ]
|
|
);
|
|
}
|
|
|
|
return value === undefined ?
|
|
|
|
// Get width or height on the element, requesting but not forcing parseFloat
|
|
jQuery.css( elem, type, extra ) :
|
|
|
|
// Set width or height on the element
|
|
jQuery.style( elem, type, value, extra );
|
|
}, type, chainable ? margin : undefined, chainable );
|
|
};
|
|
} );
|
|
} );
|
|
|
|
|
|
jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " +
|
|
"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
|
|
"change select submit keydown keypress keyup contextmenu" ).split( " " ),
|
|
function( i, name ) {
|
|
|
|
// Handle event binding
|
|
jQuery.fn[ name ] = function( data, fn ) {
|
|
return arguments.length > 0 ?
|
|
this.on( name, null, data, fn ) :
|
|
this.trigger( name );
|
|
};
|
|
} );
|
|
|
|
jQuery.fn.extend( {
|
|
hover: function( fnOver, fnOut ) {
|
|
return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
|
|
}
|
|
} );
|
|
|
|
|
|
|
|
|
|
jQuery.fn.extend( {
|
|
|
|
bind: function( types, data, fn ) {
|
|
return this.on( types, null, data, fn );
|
|
},
|
|
unbind: function( types, fn ) {
|
|
return this.off( types, null, fn );
|
|
},
|
|
|
|
delegate: function( selector, types, data, fn ) {
|
|
return this.on( types, selector, data, fn );
|
|
},
|
|
undelegate: function( selector, types, fn ) {
|
|
|
|
// ( namespace ) or ( selector, types [, fn] )
|
|
return arguments.length === 1 ?
|
|
this.off( selector, "**" ) :
|
|
this.off( types, selector || "**", fn );
|
|
}
|
|
} );
|
|
|
|
// Bind a function to a context, optionally partially applying any
|
|
// arguments.
|
|
// jQuery.proxy is deprecated to promote standards (specifically Function#bind)
|
|
// However, it is not slated for removal any time soon
|
|
jQuery.proxy = function( fn, context ) {
|
|
var tmp, args, proxy;
|
|
|
|
if ( typeof context === "string" ) {
|
|
tmp = fn[ context ];
|
|
context = fn;
|
|
fn = tmp;
|
|
}
|
|
|
|
// Quick check to determine if target is callable, in the spec
|
|
// this throws a TypeError, but we will just return undefined.
|
|
if ( !isFunction( fn ) ) {
|
|
return undefined;
|
|
}
|
|
|
|
// Simulated bind
|
|
args = slice.call( arguments, 2 );
|
|
proxy = function() {
|
|
return fn.apply( context || this, args.concat( slice.call( arguments ) ) );
|
|
};
|
|
|
|
// Set the guid of unique handler to the same of original handler, so it can be removed
|
|
proxy.guid = fn.guid = fn.guid || jQuery.guid++;
|
|
|
|
return proxy;
|
|
};
|
|
|
|
jQuery.holdReady = function( hold ) {
|
|
if ( hold ) {
|
|
jQuery.readyWait++;
|
|
} else {
|
|
jQuery.ready( true );
|
|
}
|
|
};
|
|
jQuery.isArray = Array.isArray;
|
|
jQuery.parseJSON = JSON.parse;
|
|
jQuery.nodeName = nodeName;
|
|
jQuery.isFunction = isFunction;
|
|
jQuery.isWindow = isWindow;
|
|
jQuery.camelCase = camelCase;
|
|
jQuery.type = toType;
|
|
|
|
jQuery.now = Date.now;
|
|
|
|
jQuery.isNumeric = function( obj ) {
|
|
|
|
// As of jQuery 3.0, isNumeric is limited to
|
|
// strings and numbers (primitives or objects)
|
|
// that can be coerced to finite numbers (gh-2662)
|
|
var type = jQuery.type( obj );
|
|
return ( type === "number" || type === "string" ) &&
|
|
|
|
// parseFloat NaNs numeric-cast false positives ("")
|
|
// ...but misinterprets leading-number strings, particularly hex literals ("0x...")
|
|
// subtraction forces infinities to NaN
|
|
!isNaN( obj - parseFloat( obj ) );
|
|
};
|
|
|
|
|
|
|
|
|
|
// Register as a named AMD module, since jQuery can be concatenated with other
|
|
// files that may use define, but not via a proper concatenation script that
|
|
// understands anonymous AMD modules. A named AMD is safest and most robust
|
|
// way to register. Lowercase jquery is used because AMD module names are
|
|
// derived from file names, and jQuery is normally delivered in a lowercase
|
|
// file name. Do this after creating the global so that if an AMD module wants
|
|
// to call noConflict to hide this version of jQuery, it will work.
|
|
|
|
// Note that for maximum portability, libraries that are not jQuery should
|
|
// declare themselves as anonymous modules, and avoid setting a global if an
|
|
// AMD loader is present. jQuery is a special case. For more information, see
|
|
// https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon
|
|
|
|
if ( typeof define === "function" && define.amd ) {
|
|
define( "jquery", [], function() {
|
|
return jQuery;
|
|
} );
|
|
}
|
|
|
|
|
|
|
|
|
|
var
|
|
|
|
// Map over jQuery in case of overwrite
|
|
_jQuery = window.jQuery,
|
|
|
|
// Map over the $ in case of overwrite
|
|
_$ = window.$;
|
|
|
|
jQuery.noConflict = function( deep ) {
|
|
if ( window.$ === jQuery ) {
|
|
window.$ = _$;
|
|
}
|
|
|
|
if ( deep && window.jQuery === jQuery ) {
|
|
window.jQuery = _jQuery;
|
|
}
|
|
|
|
return jQuery;
|
|
};
|
|
|
|
// Expose jQuery and $ identifiers, even in AMD
|
|
// (#7102#comment:10, https://github.com/jquery/jquery/pull/557)
|
|
// and CommonJS for browser emulators (#13566)
|
|
if ( !noGlobal ) {
|
|
window.jQuery = window.$ = jQuery;
|
|
}
|
|
|
|
|
|
|
|
|
|
return jQuery;
|
|
} );
|
|
/*
|
|
Copyright (C) Federico Zivolo 2019
|
|
Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT).
|
|
*/
|
|
(function(e,t){'object'==typeof exports&&'undefined'!=typeof module?module.exports=t():'function'==typeof define&&define.amd?define(t):e.Popper=t()})(this,function(){'use strict';function e(e){return e&&'[object Function]'==={}.toString.call(e)}function t(e,t){if(1!==e.nodeType)return[];var o=e.ownerDocument.defaultView,n=o.getComputedStyle(e,null);return t?n[t]:n}function o(e){return'HTML'===e.nodeName?e:e.parentNode||e.host}function n(e){if(!e)return document.body;switch(e.nodeName){case'HTML':case'BODY':return e.ownerDocument.body;case'#document':return e.body;}var i=t(e),r=i.overflow,p=i.overflowX,s=i.overflowY;return /(auto|scroll|overlay)/.test(r+s+p)?e:n(o(e))}function i(e){return e&&e.referenceNode?e.referenceNode:e}function r(e){return 11===e?re:10===e?pe:re||pe}function p(e){if(!e)return document.documentElement;for(var o=r(10)?document.body:null,n=e.offsetParent||null;n===o&&e.nextElementSibling;)n=(e=e.nextElementSibling).offsetParent;var i=n&&n.nodeName;return i&&'BODY'!==i&&'HTML'!==i?-1!==['TH','TD','TABLE'].indexOf(n.nodeName)&&'static'===t(n,'position')?p(n):n:e?e.ownerDocument.documentElement:document.documentElement}function s(e){var t=e.nodeName;return'BODY'!==t&&('HTML'===t||p(e.firstElementChild)===e)}function d(e){return null===e.parentNode?e:d(e.parentNode)}function a(e,t){if(!e||!e.nodeType||!t||!t.nodeType)return document.documentElement;var o=e.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_FOLLOWING,n=o?e:t,i=o?t:e,r=document.createRange();r.setStart(n,0),r.setEnd(i,0);var l=r.commonAncestorContainer;if(e!==l&&t!==l||n.contains(i))return s(l)?l:p(l);var f=d(e);return f.host?a(f.host,t):a(e,d(t).host)}function l(e){var t=1<arguments.length&&void 0!==arguments[1]?arguments[1]:'top',o='top'===t?'scrollTop':'scrollLeft',n=e.nodeName;if('BODY'===n||'HTML'===n){var i=e.ownerDocument.documentElement,r=e.ownerDocument.scrollingElement||i;return r[o]}return e[o]}function f(e,t){var o=2<arguments.length&&void 0!==arguments[2]&&arguments[2],n=l(t,'top'),i=l(t,'left'),r=o?-1:1;return e.top+=n*r,e.bottom+=n*r,e.left+=i*r,e.right+=i*r,e}function m(e,t){var o='x'===t?'Left':'Top',n='Left'==o?'Right':'Bottom';return parseFloat(e['border'+o+'Width'],10)+parseFloat(e['border'+n+'Width'],10)}function h(e,t,o,n){return ee(t['offset'+e],t['scroll'+e],o['client'+e],o['offset'+e],o['scroll'+e],r(10)?parseInt(o['offset'+e])+parseInt(n['margin'+('Height'===e?'Top':'Left')])+parseInt(n['margin'+('Height'===e?'Bottom':'Right')]):0)}function c(e){var t=e.body,o=e.documentElement,n=r(10)&&getComputedStyle(o);return{height:h('Height',t,o,n),width:h('Width',t,o,n)}}function g(e){return le({},e,{right:e.left+e.width,bottom:e.top+e.height})}function u(e){var o={};try{if(r(10)){o=e.getBoundingClientRect();var n=l(e,'top'),i=l(e,'left');o.top+=n,o.left+=i,o.bottom+=n,o.right+=i}else o=e.getBoundingClientRect()}catch(t){}var p={left:o.left,top:o.top,width:o.right-o.left,height:o.bottom-o.top},s='HTML'===e.nodeName?c(e.ownerDocument):{},d=s.width||e.clientWidth||p.width,a=s.height||e.clientHeight||p.height,f=e.offsetWidth-d,h=e.offsetHeight-a;if(f||h){var u=t(e);f-=m(u,'x'),h-=m(u,'y'),p.width-=f,p.height-=h}return g(p)}function b(e,o){var i=2<arguments.length&&void 0!==arguments[2]&&arguments[2],p=r(10),s='HTML'===o.nodeName,d=u(e),a=u(o),l=n(e),m=t(o),h=parseFloat(m.borderTopWidth,10),c=parseFloat(m.borderLeftWidth,10);i&&s&&(a.top=ee(a.top,0),a.left=ee(a.left,0));var b=g({top:d.top-a.top-h,left:d.left-a.left-c,width:d.width,height:d.height});if(b.marginTop=0,b.marginLeft=0,!p&&s){var w=parseFloat(m.marginTop,10),y=parseFloat(m.marginLeft,10);b.top-=h-w,b.bottom-=h-w,b.left-=c-y,b.right-=c-y,b.marginTop=w,b.marginLeft=y}return(p&&!i?o.contains(l):o===l&&'BODY'!==l.nodeName)&&(b=f(b,o)),b}function w(e){var t=1<arguments.length&&void 0!==arguments[1]&&arguments[1],o=e.ownerDocument.documentElement,n=b(e,o),i=ee(o.clientWidth,window.innerWidth||0),r=ee(o.clientHeight,window.innerHeight||0),p=t?0:l(o),s=t?0:l(o,'left'),d={top:p-n.top+n.marginTop,left:s-n.left+n.marginLeft,width:i,height:r};return g(d)}function y(e){var n=e.nodeName;if('BODY'===n||'HTML'===n)return!1;if('fixed'===t(e,'position'))return!0;var i=o(e);return!!i&&y(i)}function E(e){if(!e||!e.parentElement||r())return document.documentElement;for(var o=e.parentElement;o&&'none'===t(o,'transform');)o=o.parentElement;return o||document.documentElement}function v(e,t,r,p){var s=4<arguments.length&&void 0!==arguments[4]&&arguments[4],d={top:0,left:0},l=s?E(e):a(e,i(t));if('viewport'===p)d=w(l,s);else{var f;'scrollParent'===p?(f=n(o(t)),'BODY'===f.nodeName&&(f=e.ownerDocument.documentElement)):'window'===p?f=e.ownerDocument.documentElement:f=p;var m=b(f,l,s);if('HTML'===f.nodeName&&!y(l)){var h=c(e.ownerDocument),g=h.height,u=h.width;d.top+=m.top-m.marginTop,d.bottom=g+m.top,d.left+=m.left-m.marginLeft,d.right=u+m.left}else d=m}r=r||0;var v='number'==typeof r;return d.left+=v?r:r.left||0,d.top+=v?r:r.top||0,d.right-=v?r:r.right||0,d.bottom-=v?r:r.bottom||0,d}function x(e){var t=e.width,o=e.height;return t*o}function O(e,t,o,n,i){var r=5<arguments.length&&void 0!==arguments[5]?arguments[5]:0;if(-1===e.indexOf('auto'))return e;var p=v(o,n,r,i),s={top:{width:p.width,height:t.top-p.top},right:{width:p.right-t.right,height:p.height},bottom:{width:p.width,height:p.bottom-t.bottom},left:{width:t.left-p.left,height:p.height}},d=Object.keys(s).map(function(e){return le({key:e},s[e],{area:x(s[e])})}).sort(function(e,t){return t.area-e.area}),a=d.filter(function(e){var t=e.width,n=e.height;return t>=o.clientWidth&&n>=o.clientHeight}),l=0<a.length?a[0].key:d[0].key,f=e.split('-')[1];return l+(f?'-'+f:'')}function L(e,t,o){var n=3<arguments.length&&void 0!==arguments[3]?arguments[3]:null,r=n?E(t):a(t,i(o));return b(o,r,n)}function S(e){var t=e.ownerDocument.defaultView,o=t.getComputedStyle(e),n=parseFloat(o.marginTop||0)+parseFloat(o.marginBottom||0),i=parseFloat(o.marginLeft||0)+parseFloat(o.marginRight||0),r={width:e.offsetWidth+i,height:e.offsetHeight+n};return r}function T(e){var t={left:'right',right:'left',bottom:'top',top:'bottom'};return e.replace(/left|right|bottom|top/g,function(e){return t[e]})}function C(e,t,o){o=o.split('-')[0];var n=S(e),i={width:n.width,height:n.height},r=-1!==['right','left'].indexOf(o),p=r?'top':'left',s=r?'left':'top',d=r?'height':'width',a=r?'width':'height';return i[p]=t[p]+t[d]/2-n[d]/2,i[s]=o===s?t[s]-n[a]:t[T(s)],i}function D(e,t){return Array.prototype.find?e.find(t):e.filter(t)[0]}function N(e,t,o){if(Array.prototype.findIndex)return e.findIndex(function(e){return e[t]===o});var n=D(e,function(e){return e[t]===o});return e.indexOf(n)}function P(t,o,n){var i=void 0===n?t:t.slice(0,N(t,'name',n));return i.forEach(function(t){t['function']&&console.warn('`modifier.function` is deprecated, use `modifier.fn`!');var n=t['function']||t.fn;t.enabled&&e(n)&&(o.offsets.popper=g(o.offsets.popper),o.offsets.reference=g(o.offsets.reference),o=n(o,t))}),o}function k(){if(!this.state.isDestroyed){var e={instance:this,styles:{},arrowStyles:{},attributes:{},flipped:!1,offsets:{}};e.offsets.reference=L(this.state,this.popper,this.reference,this.options.positionFixed),e.placement=O(this.options.placement,e.offsets.reference,this.popper,this.reference,this.options.modifiers.flip.boundariesElement,this.options.modifiers.flip.padding),e.originalPlacement=e.placement,e.positionFixed=this.options.positionFixed,e.offsets.popper=C(this.popper,e.offsets.reference,e.placement),e.offsets.popper.position=this.options.positionFixed?'fixed':'absolute',e=P(this.modifiers,e),this.state.isCreated?this.options.onUpdate(e):(this.state.isCreated=!0,this.options.onCreate(e))}}function W(e,t){return e.some(function(e){var o=e.name,n=e.enabled;return n&&o===t})}function B(e){for(var t=[!1,'ms','Webkit','Moz','O'],o=e.charAt(0).toUpperCase()+e.slice(1),n=0;n<t.length;n++){var i=t[n],r=i?''+i+o:e;if('undefined'!=typeof document.body.style[r])return r}return null}function H(){return this.state.isDestroyed=!0,W(this.modifiers,'applyStyle')&&(this.popper.removeAttribute('x-placement'),this.popper.style.position='',this.popper.style.top='',this.popper.style.left='',this.popper.style.right='',this.popper.style.bottom='',this.popper.style.willChange='',this.popper.style[B('transform')]=''),this.disableEventListeners(),this.options.removeOnDestroy&&this.popper.parentNode.removeChild(this.popper),this}function A(e){var t=e.ownerDocument;return t?t.defaultView:window}function M(e,t,o,i){var r='BODY'===e.nodeName,p=r?e.ownerDocument.defaultView:e;p.addEventListener(t,o,{passive:!0}),r||M(n(p.parentNode),t,o,i),i.push(p)}function F(e,t,o,i){o.updateBound=i,A(e).addEventListener('resize',o.updateBound,{passive:!0});var r=n(e);return M(r,'scroll',o.updateBound,o.scrollParents),o.scrollElement=r,o.eventsEnabled=!0,o}function I(){this.state.eventsEnabled||(this.state=F(this.reference,this.options,this.state,this.scheduleUpdate))}function R(e,t){return A(e).removeEventListener('resize',t.updateBound),t.scrollParents.forEach(function(e){e.removeEventListener('scroll',t.updateBound)}),t.updateBound=null,t.scrollParents=[],t.scrollElement=null,t.eventsEnabled=!1,t}function U(){this.state.eventsEnabled&&(cancelAnimationFrame(this.scheduleUpdate),this.state=R(this.reference,this.state))}function Y(e){return''!==e&&!isNaN(parseFloat(e))&&isFinite(e)}function V(e,t){Object.keys(t).forEach(function(o){var n='';-1!==['width','height','top','right','bottom','left'].indexOf(o)&&Y(t[o])&&(n='px'),e.style[o]=t[o]+n})}function j(e,t){Object.keys(t).forEach(function(o){var n=t[o];!1===n?e.removeAttribute(o):e.setAttribute(o,t[o])})}function q(e,t){var o=e.offsets,n=o.popper,i=o.reference,r=$,p=function(e){return e},s=r(i.width),d=r(n.width),a=-1!==['left','right'].indexOf(e.placement),l=-1!==e.placement.indexOf('-'),f=t?a||l||s%2==d%2?r:Z:p,m=t?r:p;return{left:f(1==s%2&&1==d%2&&!l&&t?n.left-1:n.left),top:m(n.top),bottom:m(n.bottom),right:f(n.right)}}function K(e,t,o){var n=D(e,function(e){var o=e.name;return o===t}),i=!!n&&e.some(function(e){return e.name===o&&e.enabled&&e.order<n.order});if(!i){var r='`'+t+'`';console.warn('`'+o+'`'+' modifier is required by '+r+' modifier in order to work, be sure to include it before '+r+'!')}return i}function z(e){return'end'===e?'start':'start'===e?'end':e}function G(e){var t=1<arguments.length&&void 0!==arguments[1]&&arguments[1],o=he.indexOf(e),n=he.slice(o+1).concat(he.slice(0,o));return t?n.reverse():n}function _(e,t,o,n){var i=e.match(/((?:\-|\+)?\d*\.?\d*)(.*)/),r=+i[1],p=i[2];if(!r)return e;if(0===p.indexOf('%')){var s;switch(p){case'%p':s=o;break;case'%':case'%r':default:s=n;}var d=g(s);return d[t]/100*r}if('vh'===p||'vw'===p){var a;return a='vh'===p?ee(document.documentElement.clientHeight,window.innerHeight||0):ee(document.documentElement.clientWidth,window.innerWidth||0),a/100*r}return r}function X(e,t,o,n){var i=[0,0],r=-1!==['right','left'].indexOf(n),p=e.split(/(\+|\-)/).map(function(e){return e.trim()}),s=p.indexOf(D(p,function(e){return-1!==e.search(/,|\s/)}));p[s]&&-1===p[s].indexOf(',')&&console.warn('Offsets separated by white space(s) are deprecated, use a comma (,) instead.');var d=/\s*,\s*|\s+/,a=-1===s?[p]:[p.slice(0,s).concat([p[s].split(d)[0]]),[p[s].split(d)[1]].concat(p.slice(s+1))];return a=a.map(function(e,n){var i=(1===n?!r:r)?'height':'width',p=!1;return e.reduce(function(e,t){return''===e[e.length-1]&&-1!==['+','-'].indexOf(t)?(e[e.length-1]=t,p=!0,e):p?(e[e.length-1]+=t,p=!1,e):e.concat(t)},[]).map(function(e){return _(e,i,t,o)})}),a.forEach(function(e,t){e.forEach(function(o,n){Y(o)&&(i[t]+=o*('-'===e[n-1]?-1:1))})}),i}function J(e,t){var o,n=t.offset,i=e.placement,r=e.offsets,p=r.popper,s=r.reference,d=i.split('-')[0];return o=Y(+n)?[+n,0]:X(n,p,s,d),'left'===d?(p.top+=o[0],p.left-=o[1]):'right'===d?(p.top+=o[0],p.left+=o[1]):'top'===d?(p.left+=o[0],p.top-=o[1]):'bottom'===d&&(p.left+=o[0],p.top+=o[1]),e.popper=p,e}var Q=Math.min,Z=Math.floor,$=Math.round,ee=Math.max,te='undefined'!=typeof window&&'undefined'!=typeof document&&'undefined'!=typeof navigator,oe=function(){for(var e=['Edge','Trident','Firefox'],t=0;t<e.length;t+=1)if(te&&0<=navigator.userAgent.indexOf(e[t]))return 1;return 0}(),ne=te&&window.Promise,ie=ne?function(e){var t=!1;return function(){t||(t=!0,window.Promise.resolve().then(function(){t=!1,e()}))}}:function(e){var t=!1;return function(){t||(t=!0,setTimeout(function(){t=!1,e()},oe))}},re=te&&!!(window.MSInputMethodContext&&document.documentMode),pe=te&&/MSIE 10/.test(navigator.userAgent),se=function(e,t){if(!(e instanceof t))throw new TypeError('Cannot call a class as a function')},de=function(){function e(e,t){for(var o,n=0;n<t.length;n++)o=t[n],o.enumerable=o.enumerable||!1,o.configurable=!0,'value'in o&&(o.writable=!0),Object.defineProperty(e,o.key,o)}return function(t,o,n){return o&&e(t.prototype,o),n&&e(t,n),t}}(),ae=function(e,t,o){return t in e?Object.defineProperty(e,t,{value:o,enumerable:!0,configurable:!0,writable:!0}):e[t]=o,e},le=Object.assign||function(e){for(var t,o=1;o<arguments.length;o++)for(var n in t=arguments[o],t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n]);return e},fe=te&&/Firefox/i.test(navigator.userAgent),me=['auto-start','auto','auto-end','top-start','top','top-end','right-start','right','right-end','bottom-end','bottom','bottom-start','left-end','left','left-start'],he=me.slice(3),ce={FLIP:'flip',CLOCKWISE:'clockwise',COUNTERCLOCKWISE:'counterclockwise'},ge=function(){function t(o,n){var i=this,r=2<arguments.length&&void 0!==arguments[2]?arguments[2]:{};se(this,t),this.scheduleUpdate=function(){return requestAnimationFrame(i.update)},this.update=ie(this.update.bind(this)),this.options=le({},t.Defaults,r),this.state={isDestroyed:!1,isCreated:!1,scrollParents:[]},this.reference=o&&o.jquery?o[0]:o,this.popper=n&&n.jquery?n[0]:n,this.options.modifiers={},Object.keys(le({},t.Defaults.modifiers,r.modifiers)).forEach(function(e){i.options.modifiers[e]=le({},t.Defaults.modifiers[e]||{},r.modifiers?r.modifiers[e]:{})}),this.modifiers=Object.keys(this.options.modifiers).map(function(e){return le({name:e},i.options.modifiers[e])}).sort(function(e,t){return e.order-t.order}),this.modifiers.forEach(function(t){t.enabled&&e(t.onLoad)&&t.onLoad(i.reference,i.popper,i.options,t,i.state)}),this.update();var p=this.options.eventsEnabled;p&&this.enableEventListeners(),this.state.eventsEnabled=p}return de(t,[{key:'update',value:function(){return k.call(this)}},{key:'destroy',value:function(){return H.call(this)}},{key:'enableEventListeners',value:function(){return I.call(this)}},{key:'disableEventListeners',value:function(){return U.call(this)}}]),t}();return ge.Utils=('undefined'==typeof window?global:window).PopperUtils,ge.placements=me,ge.Defaults={placement:'bottom',positionFixed:!1,eventsEnabled:!0,removeOnDestroy:!1,onCreate:function(){},onUpdate:function(){},modifiers:{shift:{order:100,enabled:!0,fn:function(e){var t=e.placement,o=t.split('-')[0],n=t.split('-')[1];if(n){var i=e.offsets,r=i.reference,p=i.popper,s=-1!==['bottom','top'].indexOf(o),d=s?'left':'top',a=s?'width':'height',l={start:ae({},d,r[d]),end:ae({},d,r[d]+r[a]-p[a])};e.offsets.popper=le({},p,l[n])}return e}},offset:{order:200,enabled:!0,fn:J,offset:0},preventOverflow:{order:300,enabled:!0,fn:function(e,t){var o=t.boundariesElement||p(e.instance.popper);e.instance.reference===o&&(o=p(o));var n=B('transform'),i=e.instance.popper.style,r=i.top,s=i.left,d=i[n];i.top='',i.left='',i[n]='';var a=v(e.instance.popper,e.instance.reference,t.padding,o,e.positionFixed);i.top=r,i.left=s,i[n]=d,t.boundaries=a;var l=t.priority,f=e.offsets.popper,m={primary:function(e){var o=f[e];return f[e]<a[e]&&!t.escapeWithReference&&(o=ee(f[e],a[e])),ae({},e,o)},secondary:function(e){var o='right'===e?'left':'top',n=f[o];return f[e]>a[e]&&!t.escapeWithReference&&(n=Q(f[o],a[e]-('right'===e?f.width:f.height))),ae({},o,n)}};return l.forEach(function(e){var t=-1===['left','top'].indexOf(e)?'secondary':'primary';f=le({},f,m[t](e))}),e.offsets.popper=f,e},priority:['left','right','top','bottom'],padding:5,boundariesElement:'scrollParent'},keepTogether:{order:400,enabled:!0,fn:function(e){var t=e.offsets,o=t.popper,n=t.reference,i=e.placement.split('-')[0],r=Z,p=-1!==['top','bottom'].indexOf(i),s=p?'right':'bottom',d=p?'left':'top',a=p?'width':'height';return o[s]<r(n[d])&&(e.offsets.popper[d]=r(n[d])-o[a]),o[d]>r(n[s])&&(e.offsets.popper[d]=r(n[s])),e}},arrow:{order:500,enabled:!0,fn:function(e,o){var n;if(!K(e.instance.modifiers,'arrow','keepTogether'))return e;var i=o.element;if('string'==typeof i){if(i=e.instance.popper.querySelector(i),!i)return e;}else if(!e.instance.popper.contains(i))return console.warn('WARNING: `arrow.element` must be child of its popper element!'),e;var r=e.placement.split('-')[0],p=e.offsets,s=p.popper,d=p.reference,a=-1!==['left','right'].indexOf(r),l=a?'height':'width',f=a?'Top':'Left',m=f.toLowerCase(),h=a?'left':'top',c=a?'bottom':'right',u=S(i)[l];d[c]-u<s[m]&&(e.offsets.popper[m]-=s[m]-(d[c]-u)),d[m]+u>s[c]&&(e.offsets.popper[m]+=d[m]+u-s[c]),e.offsets.popper=g(e.offsets.popper);var b=d[m]+d[l]/2-u/2,w=t(e.instance.popper),y=parseFloat(w['margin'+f],10),E=parseFloat(w['border'+f+'Width'],10),v=b-e.offsets.popper[m]-y-E;return v=ee(Q(s[l]-u,v),0),e.arrowElement=i,e.offsets.arrow=(n={},ae(n,m,$(v)),ae(n,h,''),n),e},element:'[x-arrow]'},flip:{order:600,enabled:!0,fn:function(e,t){if(W(e.instance.modifiers,'inner'))return e;if(e.flipped&&e.placement===e.originalPlacement)return e;var o=v(e.instance.popper,e.instance.reference,t.padding,t.boundariesElement,e.positionFixed),n=e.placement.split('-')[0],i=T(n),r=e.placement.split('-')[1]||'',p=[];switch(t.behavior){case ce.FLIP:p=[n,i];break;case ce.CLOCKWISE:p=G(n);break;case ce.COUNTERCLOCKWISE:p=G(n,!0);break;default:p=t.behavior;}return p.forEach(function(s,d){if(n!==s||p.length===d+1)return e;n=e.placement.split('-')[0],i=T(n);var a=e.offsets.popper,l=e.offsets.reference,f=Z,m='left'===n&&f(a.right)>f(l.left)||'right'===n&&f(a.left)<f(l.right)||'top'===n&&f(a.bottom)>f(l.top)||'bottom'===n&&f(a.top)<f(l.bottom),h=f(a.left)<f(o.left),c=f(a.right)>f(o.right),g=f(a.top)<f(o.top),u=f(a.bottom)>f(o.bottom),b='left'===n&&h||'right'===n&&c||'top'===n&&g||'bottom'===n&&u,w=-1!==['top','bottom'].indexOf(n),y=!!t.flipVariations&&(w&&'start'===r&&h||w&&'end'===r&&c||!w&&'start'===r&&g||!w&&'end'===r&&u),E=!!t.flipVariationsByContent&&(w&&'start'===r&&c||w&&'end'===r&&h||!w&&'start'===r&&u||!w&&'end'===r&&g),v=y||E;(m||b||v)&&(e.flipped=!0,(m||b)&&(n=p[d+1]),v&&(r=z(r)),e.placement=n+(r?'-'+r:''),e.offsets.popper=le({},e.offsets.popper,C(e.instance.popper,e.offsets.reference,e.placement)),e=P(e.instance.modifiers,e,'flip'))}),e},behavior:'flip',padding:5,boundariesElement:'viewport',flipVariations:!1,flipVariationsByContent:!1},inner:{order:700,enabled:!1,fn:function(e){var t=e.placement,o=t.split('-')[0],n=e.offsets,i=n.popper,r=n.reference,p=-1!==['left','right'].indexOf(o),s=-1===['top','left'].indexOf(o);return i[p?'left':'top']=r[o]-(s?i[p?'width':'height']:0),e.placement=T(t),e.offsets.popper=g(i),e}},hide:{order:800,enabled:!0,fn:function(e){if(!K(e.instance.modifiers,'hide','preventOverflow'))return e;var t=e.offsets.reference,o=D(e.instance.modifiers,function(e){return'preventOverflow'===e.name}).boundaries;if(t.bottom<o.top||t.left>o.right||t.top>o.bottom||t.right<o.left){if(!0===e.hide)return e;e.hide=!0,e.attributes['x-out-of-boundaries']=''}else{if(!1===e.hide)return e;e.hide=!1,e.attributes['x-out-of-boundaries']=!1}return e}},computeStyle:{order:850,enabled:!0,fn:function(e,t){var o=t.x,n=t.y,i=e.offsets.popper,r=D(e.instance.modifiers,function(e){return'applyStyle'===e.name}).gpuAcceleration;void 0!==r&&console.warn('WARNING: `gpuAcceleration` option moved to `computeStyle` modifier and will not be supported in future versions of Popper.js!');var s,d,a=void 0===r?t.gpuAcceleration:r,l=p(e.instance.popper),f=u(l),m={position:i.position},h=q(e,2>window.devicePixelRatio||!fe),c='bottom'===o?'top':'bottom',g='right'===n?'left':'right',b=B('transform');if(d='bottom'==c?'HTML'===l.nodeName?-l.clientHeight+h.bottom:-f.height+h.bottom:h.top,s='right'==g?'HTML'===l.nodeName?-l.clientWidth+h.right:-f.width+h.right:h.left,a&&b)m[b]='translate3d('+s+'px, '+d+'px, 0)',m[c]=0,m[g]=0,m.willChange='transform';else{var w='bottom'==c?-1:1,y='right'==g?-1:1;m[c]=d*w,m[g]=s*y,m.willChange=c+', '+g}var E={"x-placement":e.placement};return e.attributes=le({},E,e.attributes),e.styles=le({},m,e.styles),e.arrowStyles=le({},e.offsets.arrow,e.arrowStyles),e},gpuAcceleration:!0,x:'bottom',y:'right'},applyStyle:{order:900,enabled:!0,fn:function(e){return V(e.instance.popper,e.styles),j(e.instance.popper,e.attributes),e.arrowElement&&Object.keys(e.arrowStyles).length&&V(e.arrowElement,e.arrowStyles),e},onLoad:function(e,t,o,n,i){var r=L(i,t,e,o.positionFixed),p=O(o.placement,r,t,e,o.modifiers.flip.boundariesElement,o.modifiers.flip.padding);return t.setAttribute('x-placement',p),V(t,{position:o.positionFixed?'fixed':'absolute'}),o},gpuAcceleration:void 0}}},ge});
|
|
/*!
|
|
* Bootstrap util.js v4.3.1 (https://getbootstrap.com/)
|
|
* Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
*/
|
|
|
|
(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('jquery')) :
|
|
typeof define === 'function' && define.amd ? define(['jquery'], factory) :
|
|
(global = global || self, global.Util = factory(global.jQuery));
|
|
}(this, function ($) { 'use strict';
|
|
|
|
$ = $ && $.hasOwnProperty('default') ? $['default'] : $;
|
|
|
|
/**
|
|
* --------------------------------------------------------------------------
|
|
* Bootstrap (v4.3.1): util.js
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
* --------------------------------------------------------------------------
|
|
*/
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* Private TransitionEnd Helpers
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
var TRANSITION_END = 'transitionend';
|
|
var MAX_UID = 1000000;
|
|
var MILLISECONDS_MULTIPLIER = 1000; // Shoutout AngusCroll (https://goo.gl/pxwQGp)
|
|
|
|
function toType(obj) {
|
|
return {}.toString.call(obj).match(/\s([a-z]+)/i)[1].toLowerCase();
|
|
}
|
|
|
|
function getSpecialTransitionEndEvent() {
|
|
return {
|
|
bindType: TRANSITION_END,
|
|
delegateType: TRANSITION_END,
|
|
handle: function handle(event) {
|
|
if ($(event.target).is(this)) {
|
|
return event.handleObj.handler.apply(this, arguments); // eslint-disable-line prefer-rest-params
|
|
}
|
|
|
|
return undefined; // eslint-disable-line no-undefined
|
|
}
|
|
};
|
|
}
|
|
|
|
function transitionEndEmulator(duration) {
|
|
var _this = this;
|
|
|
|
var called = false;
|
|
$(this).one(Util.TRANSITION_END, function () {
|
|
called = true;
|
|
});
|
|
setTimeout(function () {
|
|
if (!called) {
|
|
Util.triggerTransitionEnd(_this);
|
|
}
|
|
}, duration);
|
|
return this;
|
|
}
|
|
|
|
function setTransitionEndSupport() {
|
|
$.fn.emulateTransitionEnd = transitionEndEmulator;
|
|
$.event.special[Util.TRANSITION_END] = getSpecialTransitionEndEvent();
|
|
}
|
|
/**
|
|
* --------------------------------------------------------------------------
|
|
* Public Util Api
|
|
* --------------------------------------------------------------------------
|
|
*/
|
|
|
|
|
|
var Util = {
|
|
TRANSITION_END: 'bsTransitionEnd',
|
|
getUID: function getUID(prefix) {
|
|
do {
|
|
// eslint-disable-next-line no-bitwise
|
|
prefix += ~~(Math.random() * MAX_UID); // "~~" acts like a faster Math.floor() here
|
|
} while (document.getElementById(prefix));
|
|
|
|
return prefix;
|
|
},
|
|
getSelectorFromElement: function getSelectorFromElement(element) {
|
|
var selector = element.getAttribute('data-target');
|
|
|
|
if (!selector || selector === '#') {
|
|
var hrefAttr = element.getAttribute('href');
|
|
selector = hrefAttr && hrefAttr !== '#' ? hrefAttr.trim() : '';
|
|
}
|
|
|
|
try {
|
|
return document.querySelector(selector) ? selector : null;
|
|
} catch (err) {
|
|
return null;
|
|
}
|
|
},
|
|
getTransitionDurationFromElement: function getTransitionDurationFromElement(element) {
|
|
if (!element) {
|
|
return 0;
|
|
} // Get transition-duration of the element
|
|
|
|
|
|
var transitionDuration = $(element).css('transition-duration');
|
|
var transitionDelay = $(element).css('transition-delay');
|
|
var floatTransitionDuration = parseFloat(transitionDuration);
|
|
var floatTransitionDelay = parseFloat(transitionDelay); // Return 0 if element or transition duration is not found
|
|
|
|
if (!floatTransitionDuration && !floatTransitionDelay) {
|
|
return 0;
|
|
} // If multiple durations are defined, take the first
|
|
|
|
|
|
transitionDuration = transitionDuration.split(',')[0];
|
|
transitionDelay = transitionDelay.split(',')[0];
|
|
return (parseFloat(transitionDuration) + parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER;
|
|
},
|
|
reflow: function reflow(element) {
|
|
return element.offsetHeight;
|
|
},
|
|
triggerTransitionEnd: function triggerTransitionEnd(element) {
|
|
$(element).trigger(TRANSITION_END);
|
|
},
|
|
// TODO: Remove in v5
|
|
supportsTransitionEnd: function supportsTransitionEnd() {
|
|
return Boolean(TRANSITION_END);
|
|
},
|
|
isElement: function isElement(obj) {
|
|
return (obj[0] || obj).nodeType;
|
|
},
|
|
typeCheckConfig: function typeCheckConfig(componentName, config, configTypes) {
|
|
for (var property in configTypes) {
|
|
if (Object.prototype.hasOwnProperty.call(configTypes, property)) {
|
|
var expectedTypes = configTypes[property];
|
|
var value = config[property];
|
|
var valueType = value && Util.isElement(value) ? 'element' : toType(value);
|
|
|
|
if (!new RegExp(expectedTypes).test(valueType)) {
|
|
throw new Error(componentName.toUpperCase() + ": " + ("Option \"" + property + "\" provided type \"" + valueType + "\" ") + ("but expected type \"" + expectedTypes + "\"."));
|
|
}
|
|
}
|
|
}
|
|
},
|
|
findShadowRoot: function findShadowRoot(element) {
|
|
if (!document.documentElement.attachShadow) {
|
|
return null;
|
|
} // Can find the shadow root otherwise it'll return the document
|
|
|
|
|
|
if (typeof element.getRootNode === 'function') {
|
|
var root = element.getRootNode();
|
|
return root instanceof ShadowRoot ? root : null;
|
|
}
|
|
|
|
if (element instanceof ShadowRoot) {
|
|
return element;
|
|
} // when we don't find a shadow root
|
|
|
|
|
|
if (!element.parentNode) {
|
|
return null;
|
|
}
|
|
|
|
return Util.findShadowRoot(element.parentNode);
|
|
}
|
|
};
|
|
setTransitionEndSupport();
|
|
|
|
return Util;
|
|
|
|
}));
|
|
/*!
|
|
* Bootstrap alert.js v4.3.1 (https://getbootstrap.com/)
|
|
* Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
*/
|
|
|
|
(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('jquery'), require('./util.js')) :
|
|
typeof define === 'function' && define.amd ? define(['jquery', './util.js'], factory) :
|
|
(global = global || self, global.Alert = factory(global.jQuery, global.Util));
|
|
}(this, function ($, Util) { 'use strict';
|
|
|
|
$ = $ && $.hasOwnProperty('default') ? $['default'] : $;
|
|
Util = Util && Util.hasOwnProperty('default') ? Util['default'] : Util;
|
|
|
|
function _defineProperties(target, props) {
|
|
for (var i = 0; i < props.length; i++) {
|
|
var descriptor = props[i];
|
|
descriptor.enumerable = descriptor.enumerable || false;
|
|
descriptor.configurable = true;
|
|
if ("value" in descriptor) descriptor.writable = true;
|
|
Object.defineProperty(target, descriptor.key, descriptor);
|
|
}
|
|
}
|
|
|
|
function _createClass(Constructor, protoProps, staticProps) {
|
|
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
|
|
if (staticProps) _defineProperties(Constructor, staticProps);
|
|
return Constructor;
|
|
}
|
|
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* Constants
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
var NAME = 'alert';
|
|
var VERSION = '4.3.1';
|
|
var DATA_KEY = 'bs.alert';
|
|
var EVENT_KEY = "." + DATA_KEY;
|
|
var DATA_API_KEY = '.data-api';
|
|
var JQUERY_NO_CONFLICT = $.fn[NAME];
|
|
var Selector = {
|
|
DISMISS: '[data-dismiss="alert"]'
|
|
};
|
|
var Event = {
|
|
CLOSE: "close" + EVENT_KEY,
|
|
CLOSED: "closed" + EVENT_KEY,
|
|
CLICK_DATA_API: "click" + EVENT_KEY + DATA_API_KEY
|
|
};
|
|
var ClassName = {
|
|
ALERT: 'alert',
|
|
FADE: 'fade',
|
|
SHOW: 'show'
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* Class Definition
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
};
|
|
|
|
var Alert =
|
|
/*#__PURE__*/
|
|
function () {
|
|
function Alert(element) {
|
|
this._element = element;
|
|
} // Getters
|
|
|
|
|
|
var _proto = Alert.prototype;
|
|
|
|
// Public
|
|
_proto.close = function close(element) {
|
|
var rootElement = this._element;
|
|
|
|
if (element) {
|
|
rootElement = this._getRootElement(element);
|
|
}
|
|
|
|
var customEvent = this._triggerCloseEvent(rootElement);
|
|
|
|
if (customEvent.isDefaultPrevented()) {
|
|
return;
|
|
}
|
|
|
|
this._removeElement(rootElement);
|
|
};
|
|
|
|
_proto.dispose = function dispose() {
|
|
$.removeData(this._element, DATA_KEY);
|
|
this._element = null;
|
|
} // Private
|
|
;
|
|
|
|
_proto._getRootElement = function _getRootElement(element) {
|
|
var selector = Util.getSelectorFromElement(element);
|
|
var parent = false;
|
|
|
|
if (selector) {
|
|
parent = document.querySelector(selector);
|
|
}
|
|
|
|
if (!parent) {
|
|
parent = $(element).closest("." + ClassName.ALERT)[0];
|
|
}
|
|
|
|
return parent;
|
|
};
|
|
|
|
_proto._triggerCloseEvent = function _triggerCloseEvent(element) {
|
|
var closeEvent = $.Event(Event.CLOSE);
|
|
$(element).trigger(closeEvent);
|
|
return closeEvent;
|
|
};
|
|
|
|
_proto._removeElement = function _removeElement(element) {
|
|
var _this = this;
|
|
|
|
$(element).removeClass(ClassName.SHOW);
|
|
|
|
if (!$(element).hasClass(ClassName.FADE)) {
|
|
this._destroyElement(element);
|
|
|
|
return;
|
|
}
|
|
|
|
var transitionDuration = Util.getTransitionDurationFromElement(element);
|
|
$(element).one(Util.TRANSITION_END, function (event) {
|
|
return _this._destroyElement(element, event);
|
|
}).emulateTransitionEnd(transitionDuration);
|
|
};
|
|
|
|
_proto._destroyElement = function _destroyElement(element) {
|
|
$(element).detach().trigger(Event.CLOSED).remove();
|
|
} // Static
|
|
;
|
|
|
|
Alert._jQueryInterface = function _jQueryInterface(config) {
|
|
return this.each(function () {
|
|
var $element = $(this);
|
|
var data = $element.data(DATA_KEY);
|
|
|
|
if (!data) {
|
|
data = new Alert(this);
|
|
$element.data(DATA_KEY, data);
|
|
}
|
|
|
|
if (config === 'close') {
|
|
data[config](this);
|
|
}
|
|
});
|
|
};
|
|
|
|
Alert._handleDismiss = function _handleDismiss(alertInstance) {
|
|
return function (event) {
|
|
if (event) {
|
|
event.preventDefault();
|
|
}
|
|
|
|
alertInstance.close(this);
|
|
};
|
|
};
|
|
|
|
_createClass(Alert, null, [{
|
|
key: "VERSION",
|
|
get: function get() {
|
|
return VERSION;
|
|
}
|
|
}]);
|
|
|
|
return Alert;
|
|
}();
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* Data Api implementation
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
|
|
$(document).on(Event.CLICK_DATA_API, Selector.DISMISS, Alert._handleDismiss(new Alert()));
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* jQuery
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
$.fn[NAME] = Alert._jQueryInterface;
|
|
$.fn[NAME].Constructor = Alert;
|
|
|
|
$.fn[NAME].noConflict = function () {
|
|
$.fn[NAME] = JQUERY_NO_CONFLICT;
|
|
return Alert._jQueryInterface;
|
|
};
|
|
|
|
return Alert;
|
|
|
|
}));
|
|
/*!
|
|
* Bootstrap button.js v4.3.1 (https://getbootstrap.com/)
|
|
* Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
*/
|
|
|
|
(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('jquery')) :
|
|
typeof define === 'function' && define.amd ? define(['jquery'], factory) :
|
|
(global = global || self, global.Button = factory(global.jQuery));
|
|
}(this, function ($) { 'use strict';
|
|
|
|
$ = $ && $.hasOwnProperty('default') ? $['default'] : $;
|
|
|
|
function _defineProperties(target, props) {
|
|
for (var i = 0; i < props.length; i++) {
|
|
var descriptor = props[i];
|
|
descriptor.enumerable = descriptor.enumerable || false;
|
|
descriptor.configurable = true;
|
|
if ("value" in descriptor) descriptor.writable = true;
|
|
Object.defineProperty(target, descriptor.key, descriptor);
|
|
}
|
|
}
|
|
|
|
function _createClass(Constructor, protoProps, staticProps) {
|
|
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
|
|
if (staticProps) _defineProperties(Constructor, staticProps);
|
|
return Constructor;
|
|
}
|
|
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* Constants
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
var NAME = 'button';
|
|
var VERSION = '4.3.1';
|
|
var DATA_KEY = 'bs.button';
|
|
var EVENT_KEY = "." + DATA_KEY;
|
|
var DATA_API_KEY = '.data-api';
|
|
var JQUERY_NO_CONFLICT = $.fn[NAME];
|
|
var ClassName = {
|
|
ACTIVE: 'active',
|
|
BUTTON: 'btn',
|
|
FOCUS: 'focus'
|
|
};
|
|
var Selector = {
|
|
DATA_TOGGLE_CARROT: '[data-toggle^="button"]',
|
|
DATA_TOGGLE: '[data-toggle="buttons"]',
|
|
INPUT: 'input:not([type="hidden"])',
|
|
ACTIVE: '.active',
|
|
BUTTON: '.btn'
|
|
};
|
|
var Event = {
|
|
CLICK_DATA_API: "click" + EVENT_KEY + DATA_API_KEY,
|
|
FOCUS_BLUR_DATA_API: "focus" + EVENT_KEY + DATA_API_KEY + " " + ("blur" + EVENT_KEY + DATA_API_KEY)
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* Class Definition
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
};
|
|
|
|
var Button =
|
|
/*#__PURE__*/
|
|
function () {
|
|
function Button(element) {
|
|
this._element = element;
|
|
} // Getters
|
|
|
|
|
|
var _proto = Button.prototype;
|
|
|
|
// Public
|
|
_proto.toggle = function toggle() {
|
|
var triggerChangeEvent = true;
|
|
var addAriaPressed = true;
|
|
var rootElement = $(this._element).closest(Selector.DATA_TOGGLE)[0];
|
|
|
|
if (rootElement) {
|
|
var input = this._element.querySelector(Selector.INPUT);
|
|
|
|
if (input) {
|
|
if (input.type === 'radio') {
|
|
if (input.checked && this._element.classList.contains(ClassName.ACTIVE)) {
|
|
triggerChangeEvent = false;
|
|
} else {
|
|
var activeElement = rootElement.querySelector(Selector.ACTIVE);
|
|
|
|
if (activeElement) {
|
|
$(activeElement).removeClass(ClassName.ACTIVE);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (triggerChangeEvent) {
|
|
if (input.hasAttribute('disabled') || rootElement.hasAttribute('disabled') || input.classList.contains('disabled') || rootElement.classList.contains('disabled')) {
|
|
return;
|
|
}
|
|
|
|
input.checked = !this._element.classList.contains(ClassName.ACTIVE);
|
|
$(input).trigger('change');
|
|
}
|
|
|
|
input.focus();
|
|
addAriaPressed = false;
|
|
}
|
|
}
|
|
|
|
if (addAriaPressed) {
|
|
this._element.setAttribute('aria-pressed', !this._element.classList.contains(ClassName.ACTIVE));
|
|
}
|
|
|
|
if (triggerChangeEvent) {
|
|
$(this._element).toggleClass(ClassName.ACTIVE);
|
|
}
|
|
};
|
|
|
|
_proto.dispose = function dispose() {
|
|
$.removeData(this._element, DATA_KEY);
|
|
this._element = null;
|
|
} // Static
|
|
;
|
|
|
|
Button._jQueryInterface = function _jQueryInterface(config) {
|
|
return this.each(function () {
|
|
var data = $(this).data(DATA_KEY);
|
|
|
|
if (!data) {
|
|
data = new Button(this);
|
|
$(this).data(DATA_KEY, data);
|
|
}
|
|
|
|
if (config === 'toggle') {
|
|
data[config]();
|
|
}
|
|
});
|
|
};
|
|
|
|
_createClass(Button, null, [{
|
|
key: "VERSION",
|
|
get: function get() {
|
|
return VERSION;
|
|
}
|
|
}]);
|
|
|
|
return Button;
|
|
}();
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* Data Api implementation
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
|
|
$(document).on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE_CARROT, function (event) {
|
|
event.preventDefault();
|
|
var button = event.target;
|
|
|
|
if (!$(button).hasClass(ClassName.BUTTON)) {
|
|
button = $(button).closest(Selector.BUTTON);
|
|
}
|
|
|
|
Button._jQueryInterface.call($(button), 'toggle');
|
|
}).on(Event.FOCUS_BLUR_DATA_API, Selector.DATA_TOGGLE_CARROT, function (event) {
|
|
var button = $(event.target).closest(Selector.BUTTON)[0];
|
|
$(button).toggleClass(ClassName.FOCUS, /^focus(in)?$/.test(event.type));
|
|
});
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* jQuery
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
$.fn[NAME] = Button._jQueryInterface;
|
|
$.fn[NAME].Constructor = Button;
|
|
|
|
$.fn[NAME].noConflict = function () {
|
|
$.fn[NAME] = JQUERY_NO_CONFLICT;
|
|
return Button._jQueryInterface;
|
|
};
|
|
|
|
return Button;
|
|
|
|
}));
|
|
/*!
|
|
* Bootstrap carousel.js v4.3.1 (https://getbootstrap.com/)
|
|
* Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
*/
|
|
|
|
(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('jquery'), require('./util.js')) :
|
|
typeof define === 'function' && define.amd ? define(['jquery', './util.js'], factory) :
|
|
(global = global || self, global.Carousel = factory(global.jQuery, global.Util));
|
|
}(this, function ($, Util) { 'use strict';
|
|
|
|
$ = $ && $.hasOwnProperty('default') ? $['default'] : $;
|
|
Util = Util && Util.hasOwnProperty('default') ? Util['default'] : Util;
|
|
|
|
function _defineProperties(target, props) {
|
|
for (var i = 0; i < props.length; i++) {
|
|
var descriptor = props[i];
|
|
descriptor.enumerable = descriptor.enumerable || false;
|
|
descriptor.configurable = true;
|
|
if ("value" in descriptor) descriptor.writable = true;
|
|
Object.defineProperty(target, descriptor.key, descriptor);
|
|
}
|
|
}
|
|
|
|
function _createClass(Constructor, protoProps, staticProps) {
|
|
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
|
|
if (staticProps) _defineProperties(Constructor, staticProps);
|
|
return Constructor;
|
|
}
|
|
|
|
function _defineProperty(obj, key, value) {
|
|
if (key in obj) {
|
|
Object.defineProperty(obj, key, {
|
|
value: value,
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true
|
|
});
|
|
} else {
|
|
obj[key] = value;
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
function _objectSpread(target) {
|
|
for (var i = 1; i < arguments.length; i++) {
|
|
var source = arguments[i] != null ? arguments[i] : {};
|
|
var ownKeys = Object.keys(source);
|
|
|
|
if (typeof Object.getOwnPropertySymbols === 'function') {
|
|
ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) {
|
|
return Object.getOwnPropertyDescriptor(source, sym).enumerable;
|
|
}));
|
|
}
|
|
|
|
ownKeys.forEach(function (key) {
|
|
_defineProperty(target, key, source[key]);
|
|
});
|
|
}
|
|
|
|
return target;
|
|
}
|
|
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* Constants
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
var NAME = 'carousel';
|
|
var VERSION = '4.3.1';
|
|
var DATA_KEY = 'bs.carousel';
|
|
var EVENT_KEY = "." + DATA_KEY;
|
|
var DATA_API_KEY = '.data-api';
|
|
var JQUERY_NO_CONFLICT = $.fn[NAME];
|
|
var ARROW_LEFT_KEYCODE = 37; // KeyboardEvent.which value for left arrow key
|
|
|
|
var ARROW_RIGHT_KEYCODE = 39; // KeyboardEvent.which value for right arrow key
|
|
|
|
var TOUCHEVENT_COMPAT_WAIT = 500; // Time for mouse compat events to fire after touch
|
|
|
|
var SWIPE_THRESHOLD = 40;
|
|
var Default = {
|
|
interval: 5000,
|
|
keyboard: true,
|
|
slide: false,
|
|
pause: 'hover',
|
|
wrap: true,
|
|
touch: true
|
|
};
|
|
var DefaultType = {
|
|
interval: '(number|boolean)',
|
|
keyboard: 'boolean',
|
|
slide: '(boolean|string)',
|
|
pause: '(string|boolean)',
|
|
wrap: 'boolean',
|
|
touch: 'boolean'
|
|
};
|
|
var Direction = {
|
|
NEXT: 'next',
|
|
PREV: 'prev',
|
|
LEFT: 'left',
|
|
RIGHT: 'right'
|
|
};
|
|
var Event = {
|
|
SLIDE: "slide" + EVENT_KEY,
|
|
SLID: "slid" + EVENT_KEY,
|
|
KEYDOWN: "keydown" + EVENT_KEY,
|
|
MOUSEENTER: "mouseenter" + EVENT_KEY,
|
|
MOUSELEAVE: "mouseleave" + EVENT_KEY,
|
|
TOUCHSTART: "touchstart" + EVENT_KEY,
|
|
TOUCHMOVE: "touchmove" + EVENT_KEY,
|
|
TOUCHEND: "touchend" + EVENT_KEY,
|
|
POINTERDOWN: "pointerdown" + EVENT_KEY,
|
|
POINTERUP: "pointerup" + EVENT_KEY,
|
|
DRAG_START: "dragstart" + EVENT_KEY,
|
|
LOAD_DATA_API: "load" + EVENT_KEY + DATA_API_KEY,
|
|
CLICK_DATA_API: "click" + EVENT_KEY + DATA_API_KEY
|
|
};
|
|
var ClassName = {
|
|
CAROUSEL: 'carousel',
|
|
ACTIVE: 'active',
|
|
SLIDE: 'slide',
|
|
RIGHT: 'carousel-item-right',
|
|
LEFT: 'carousel-item-left',
|
|
NEXT: 'carousel-item-next',
|
|
PREV: 'carousel-item-prev',
|
|
ITEM: 'carousel-item',
|
|
POINTER_EVENT: 'pointer-event'
|
|
};
|
|
var Selector = {
|
|
ACTIVE: '.active',
|
|
ACTIVE_ITEM: '.active.carousel-item',
|
|
ITEM: '.carousel-item',
|
|
ITEM_IMG: '.carousel-item img',
|
|
NEXT_PREV: '.carousel-item-next, .carousel-item-prev',
|
|
INDICATORS: '.carousel-indicators',
|
|
DATA_SLIDE: '[data-slide], [data-slide-to]',
|
|
DATA_RIDE: '[data-ride="carousel"]'
|
|
};
|
|
var PointerType = {
|
|
TOUCH: 'touch',
|
|
PEN: 'pen'
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* Class Definition
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
};
|
|
|
|
var Carousel =
|
|
/*#__PURE__*/
|
|
function () {
|
|
function Carousel(element, config) {
|
|
this._items = null;
|
|
this._interval = null;
|
|
this._activeElement = null;
|
|
this._isPaused = false;
|
|
this._isSliding = false;
|
|
this.touchTimeout = null;
|
|
this.touchStartX = 0;
|
|
this.touchDeltaX = 0;
|
|
this._config = this._getConfig(config);
|
|
this._element = element;
|
|
this._indicatorsElement = this._element.querySelector(Selector.INDICATORS);
|
|
this._touchSupported = 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0;
|
|
this._pointerEvent = Boolean(window.PointerEvent || window.MSPointerEvent);
|
|
|
|
this._addEventListeners();
|
|
} // Getters
|
|
|
|
|
|
var _proto = Carousel.prototype;
|
|
|
|
// Public
|
|
_proto.next = function next() {
|
|
if (!this._isSliding) {
|
|
this._slide(Direction.NEXT);
|
|
}
|
|
};
|
|
|
|
_proto.nextWhenVisible = function nextWhenVisible() {
|
|
// Don't call next when the page isn't visible
|
|
// or the carousel or its parent isn't visible
|
|
if (!document.hidden && $(this._element).is(':visible') && $(this._element).css('visibility') !== 'hidden') {
|
|
this.next();
|
|
}
|
|
};
|
|
|
|
_proto.prev = function prev() {
|
|
if (!this._isSliding) {
|
|
this._slide(Direction.PREV);
|
|
}
|
|
};
|
|
|
|
_proto.pause = function pause(event) {
|
|
if (!event) {
|
|
this._isPaused = true;
|
|
}
|
|
|
|
if (this._element.querySelector(Selector.NEXT_PREV)) {
|
|
Util.triggerTransitionEnd(this._element);
|
|
this.cycle(true);
|
|
}
|
|
|
|
clearInterval(this._interval);
|
|
this._interval = null;
|
|
};
|
|
|
|
_proto.cycle = function cycle(event) {
|
|
if (!event) {
|
|
this._isPaused = false;
|
|
}
|
|
|
|
if (this._interval) {
|
|
clearInterval(this._interval);
|
|
this._interval = null;
|
|
}
|
|
|
|
if (this._config.interval && !this._isPaused) {
|
|
this._interval = setInterval((document.visibilityState ? this.nextWhenVisible : this.next).bind(this), this._config.interval);
|
|
}
|
|
};
|
|
|
|
_proto.to = function to(index) {
|
|
var _this = this;
|
|
|
|
this._activeElement = this._element.querySelector(Selector.ACTIVE_ITEM);
|
|
|
|
var activeIndex = this._getItemIndex(this._activeElement);
|
|
|
|
if (index > this._items.length - 1 || index < 0) {
|
|
return;
|
|
}
|
|
|
|
if (this._isSliding) {
|
|
$(this._element).one(Event.SLID, function () {
|
|
return _this.to(index);
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (activeIndex === index) {
|
|
this.pause();
|
|
this.cycle();
|
|
return;
|
|
}
|
|
|
|
var direction = index > activeIndex ? Direction.NEXT : Direction.PREV;
|
|
|
|
this._slide(direction, this._items[index]);
|
|
};
|
|
|
|
_proto.dispose = function dispose() {
|
|
$(this._element).off(EVENT_KEY);
|
|
$.removeData(this._element, DATA_KEY);
|
|
this._items = null;
|
|
this._config = null;
|
|
this._element = null;
|
|
this._interval = null;
|
|
this._isPaused = null;
|
|
this._isSliding = null;
|
|
this._activeElement = null;
|
|
this._indicatorsElement = null;
|
|
} // Private
|
|
;
|
|
|
|
_proto._getConfig = function _getConfig(config) {
|
|
config = _objectSpread({}, Default, config);
|
|
Util.typeCheckConfig(NAME, config, DefaultType);
|
|
return config;
|
|
};
|
|
|
|
_proto._handleSwipe = function _handleSwipe() {
|
|
var absDeltax = Math.abs(this.touchDeltaX);
|
|
|
|
if (absDeltax <= SWIPE_THRESHOLD) {
|
|
return;
|
|
}
|
|
|
|
var direction = absDeltax / this.touchDeltaX; // swipe left
|
|
|
|
if (direction > 0) {
|
|
this.prev();
|
|
} // swipe right
|
|
|
|
|
|
if (direction < 0) {
|
|
this.next();
|
|
}
|
|
};
|
|
|
|
_proto._addEventListeners = function _addEventListeners() {
|
|
var _this2 = this;
|
|
|
|
if (this._config.keyboard) {
|
|
$(this._element).on(Event.KEYDOWN, function (event) {
|
|
return _this2._keydown(event);
|
|
});
|
|
}
|
|
|
|
if (this._config.pause === 'hover') {
|
|
$(this._element).on(Event.MOUSEENTER, function (event) {
|
|
return _this2.pause(event);
|
|
}).on(Event.MOUSELEAVE, function (event) {
|
|
return _this2.cycle(event);
|
|
});
|
|
}
|
|
|
|
if (this._config.touch) {
|
|
this._addTouchEventListeners();
|
|
}
|
|
};
|
|
|
|
_proto._addTouchEventListeners = function _addTouchEventListeners() {
|
|
var _this3 = this;
|
|
|
|
if (!this._touchSupported) {
|
|
return;
|
|
}
|
|
|
|
var start = function start(event) {
|
|
if (_this3._pointerEvent && PointerType[event.originalEvent.pointerType.toUpperCase()]) {
|
|
_this3.touchStartX = event.originalEvent.clientX;
|
|
} else if (!_this3._pointerEvent) {
|
|
_this3.touchStartX = event.originalEvent.touches[0].clientX;
|
|
}
|
|
};
|
|
|
|
var move = function move(event) {
|
|
// ensure swiping with one touch and not pinching
|
|
if (event.originalEvent.touches && event.originalEvent.touches.length > 1) {
|
|
_this3.touchDeltaX = 0;
|
|
} else {
|
|
_this3.touchDeltaX = event.originalEvent.touches[0].clientX - _this3.touchStartX;
|
|
}
|
|
};
|
|
|
|
var end = function end(event) {
|
|
if (_this3._pointerEvent && PointerType[event.originalEvent.pointerType.toUpperCase()]) {
|
|
_this3.touchDeltaX = event.originalEvent.clientX - _this3.touchStartX;
|
|
}
|
|
|
|
_this3._handleSwipe();
|
|
|
|
if (_this3._config.pause === 'hover') {
|
|
// If it's a touch-enabled device, mouseenter/leave are fired as
|
|
// part of the mouse compatibility events on first tap - the carousel
|
|
// would stop cycling until user tapped out of it;
|
|
// here, we listen for touchend, explicitly pause the carousel
|
|
// (as if it's the second time we tap on it, mouseenter compat event
|
|
// is NOT fired) and after a timeout (to allow for mouse compatibility
|
|
// events to fire) we explicitly restart cycling
|
|
_this3.pause();
|
|
|
|
if (_this3.touchTimeout) {
|
|
clearTimeout(_this3.touchTimeout);
|
|
}
|
|
|
|
_this3.touchTimeout = setTimeout(function (event) {
|
|
return _this3.cycle(event);
|
|
}, TOUCHEVENT_COMPAT_WAIT + _this3._config.interval);
|
|
}
|
|
};
|
|
|
|
$(this._element.querySelectorAll(Selector.ITEM_IMG)).on(Event.DRAG_START, function (e) {
|
|
return e.preventDefault();
|
|
});
|
|
|
|
if (this._pointerEvent) {
|
|
$(this._element).on(Event.POINTERDOWN, function (event) {
|
|
return start(event);
|
|
});
|
|
$(this._element).on(Event.POINTERUP, function (event) {
|
|
return end(event);
|
|
});
|
|
|
|
this._element.classList.add(ClassName.POINTER_EVENT);
|
|
} else {
|
|
$(this._element).on(Event.TOUCHSTART, function (event) {
|
|
return start(event);
|
|
});
|
|
$(this._element).on(Event.TOUCHMOVE, function (event) {
|
|
return move(event);
|
|
});
|
|
$(this._element).on(Event.TOUCHEND, function (event) {
|
|
return end(event);
|
|
});
|
|
}
|
|
};
|
|
|
|
_proto._keydown = function _keydown(event) {
|
|
if (/input|textarea/i.test(event.target.tagName)) {
|
|
return;
|
|
}
|
|
|
|
switch (event.which) {
|
|
case ARROW_LEFT_KEYCODE:
|
|
event.preventDefault();
|
|
this.prev();
|
|
break;
|
|
|
|
case ARROW_RIGHT_KEYCODE:
|
|
event.preventDefault();
|
|
this.next();
|
|
break;
|
|
|
|
default:
|
|
}
|
|
};
|
|
|
|
_proto._getItemIndex = function _getItemIndex(element) {
|
|
this._items = element && element.parentNode ? [].slice.call(element.parentNode.querySelectorAll(Selector.ITEM)) : [];
|
|
return this._items.indexOf(element);
|
|
};
|
|
|
|
_proto._getItemByDirection = function _getItemByDirection(direction, activeElement) {
|
|
var isNextDirection = direction === Direction.NEXT;
|
|
var isPrevDirection = direction === Direction.PREV;
|
|
|
|
var activeIndex = this._getItemIndex(activeElement);
|
|
|
|
var lastItemIndex = this._items.length - 1;
|
|
var isGoingToWrap = isPrevDirection && activeIndex === 0 || isNextDirection && activeIndex === lastItemIndex;
|
|
|
|
if (isGoingToWrap && !this._config.wrap) {
|
|
return activeElement;
|
|
}
|
|
|
|
var delta = direction === Direction.PREV ? -1 : 1;
|
|
var itemIndex = (activeIndex + delta) % this._items.length;
|
|
return itemIndex === -1 ? this._items[this._items.length - 1] : this._items[itemIndex];
|
|
};
|
|
|
|
_proto._triggerSlideEvent = function _triggerSlideEvent(relatedTarget, eventDirectionName) {
|
|
var targetIndex = this._getItemIndex(relatedTarget);
|
|
|
|
var fromIndex = this._getItemIndex(this._element.querySelector(Selector.ACTIVE_ITEM));
|
|
|
|
var slideEvent = $.Event(Event.SLIDE, {
|
|
relatedTarget: relatedTarget,
|
|
direction: eventDirectionName,
|
|
from: fromIndex,
|
|
to: targetIndex
|
|
});
|
|
$(this._element).trigger(slideEvent);
|
|
return slideEvent;
|
|
};
|
|
|
|
_proto._setActiveIndicatorElement = function _setActiveIndicatorElement(element) {
|
|
if (this._indicatorsElement) {
|
|
var indicators = [].slice.call(this._indicatorsElement.querySelectorAll(Selector.ACTIVE));
|
|
$(indicators).removeClass(ClassName.ACTIVE);
|
|
|
|
var nextIndicator = this._indicatorsElement.children[this._getItemIndex(element)];
|
|
|
|
if (nextIndicator) {
|
|
$(nextIndicator).addClass(ClassName.ACTIVE);
|
|
}
|
|
}
|
|
};
|
|
|
|
_proto._slide = function _slide(direction, element) {
|
|
var _this4 = this;
|
|
|
|
var activeElement = this._element.querySelector(Selector.ACTIVE_ITEM);
|
|
|
|
var activeElementIndex = this._getItemIndex(activeElement);
|
|
|
|
var nextElement = element || activeElement && this._getItemByDirection(direction, activeElement);
|
|
|
|
var nextElementIndex = this._getItemIndex(nextElement);
|
|
|
|
var isCycling = Boolean(this._interval);
|
|
var directionalClassName;
|
|
var orderClassName;
|
|
var eventDirectionName;
|
|
|
|
if (direction === Direction.NEXT) {
|
|
directionalClassName = ClassName.LEFT;
|
|
orderClassName = ClassName.NEXT;
|
|
eventDirectionName = Direction.LEFT;
|
|
} else {
|
|
directionalClassName = ClassName.RIGHT;
|
|
orderClassName = ClassName.PREV;
|
|
eventDirectionName = Direction.RIGHT;
|
|
}
|
|
|
|
if (nextElement && $(nextElement).hasClass(ClassName.ACTIVE)) {
|
|
this._isSliding = false;
|
|
return;
|
|
}
|
|
|
|
var slideEvent = this._triggerSlideEvent(nextElement, eventDirectionName);
|
|
|
|
if (slideEvent.isDefaultPrevented()) {
|
|
return;
|
|
}
|
|
|
|
if (!activeElement || !nextElement) {
|
|
// Some weirdness is happening, so we bail
|
|
return;
|
|
}
|
|
|
|
this._isSliding = true;
|
|
|
|
if (isCycling) {
|
|
this.pause();
|
|
}
|
|
|
|
this._setActiveIndicatorElement(nextElement);
|
|
|
|
var slidEvent = $.Event(Event.SLID, {
|
|
relatedTarget: nextElement,
|
|
direction: eventDirectionName,
|
|
from: activeElementIndex,
|
|
to: nextElementIndex
|
|
});
|
|
|
|
if ($(this._element).hasClass(ClassName.SLIDE)) {
|
|
$(nextElement).addClass(orderClassName);
|
|
Util.reflow(nextElement);
|
|
$(activeElement).addClass(directionalClassName);
|
|
$(nextElement).addClass(directionalClassName);
|
|
var nextElementInterval = parseInt(nextElement.getAttribute('data-interval'), 10);
|
|
|
|
if (nextElementInterval) {
|
|
this._config.defaultInterval = this._config.defaultInterval || this._config.interval;
|
|
this._config.interval = nextElementInterval;
|
|
} else {
|
|
this._config.interval = this._config.defaultInterval || this._config.interval;
|
|
}
|
|
|
|
var transitionDuration = Util.getTransitionDurationFromElement(activeElement);
|
|
$(activeElement).one(Util.TRANSITION_END, function () {
|
|
$(nextElement).removeClass(directionalClassName + " " + orderClassName).addClass(ClassName.ACTIVE);
|
|
$(activeElement).removeClass(ClassName.ACTIVE + " " + orderClassName + " " + directionalClassName);
|
|
_this4._isSliding = false;
|
|
setTimeout(function () {
|
|
return $(_this4._element).trigger(slidEvent);
|
|
}, 0);
|
|
}).emulateTransitionEnd(transitionDuration);
|
|
} else {
|
|
$(activeElement).removeClass(ClassName.ACTIVE);
|
|
$(nextElement).addClass(ClassName.ACTIVE);
|
|
this._isSliding = false;
|
|
$(this._element).trigger(slidEvent);
|
|
}
|
|
|
|
if (isCycling) {
|
|
this.cycle();
|
|
}
|
|
} // Static
|
|
;
|
|
|
|
Carousel._jQueryInterface = function _jQueryInterface(config) {
|
|
return this.each(function () {
|
|
var data = $(this).data(DATA_KEY);
|
|
|
|
var _config = _objectSpread({}, Default, $(this).data());
|
|
|
|
if (typeof config === 'object') {
|
|
_config = _objectSpread({}, _config, config);
|
|
}
|
|
|
|
var action = typeof config === 'string' ? config : _config.slide;
|
|
|
|
if (!data) {
|
|
data = new Carousel(this, _config);
|
|
$(this).data(DATA_KEY, data);
|
|
}
|
|
|
|
if (typeof config === 'number') {
|
|
data.to(config);
|
|
} else if (typeof action === 'string') {
|
|
if (typeof data[action] === 'undefined') {
|
|
throw new TypeError("No method named \"" + action + "\"");
|
|
}
|
|
|
|
data[action]();
|
|
} else if (_config.interval && _config.ride) {
|
|
data.pause();
|
|
data.cycle();
|
|
}
|
|
});
|
|
};
|
|
|
|
Carousel._dataApiClickHandler = function _dataApiClickHandler(event) {
|
|
var selector = Util.getSelectorFromElement(this);
|
|
|
|
if (!selector) {
|
|
return;
|
|
}
|
|
|
|
var target = $(selector)[0];
|
|
|
|
if (!target || !$(target).hasClass(ClassName.CAROUSEL)) {
|
|
return;
|
|
}
|
|
|
|
var config = _objectSpread({}, $(target).data(), $(this).data());
|
|
|
|
var slideIndex = this.getAttribute('data-slide-to');
|
|
|
|
if (slideIndex) {
|
|
config.interval = false;
|
|
}
|
|
|
|
Carousel._jQueryInterface.call($(target), config);
|
|
|
|
if (slideIndex) {
|
|
$(target).data(DATA_KEY).to(slideIndex);
|
|
}
|
|
|
|
event.preventDefault();
|
|
};
|
|
|
|
_createClass(Carousel, null, [{
|
|
key: "VERSION",
|
|
get: function get() {
|
|
return VERSION;
|
|
}
|
|
}, {
|
|
key: "Default",
|
|
get: function get() {
|
|
return Default;
|
|
}
|
|
}]);
|
|
|
|
return Carousel;
|
|
}();
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* Data Api implementation
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
|
|
$(document).on(Event.CLICK_DATA_API, Selector.DATA_SLIDE, Carousel._dataApiClickHandler);
|
|
$(window).on(Event.LOAD_DATA_API, function () {
|
|
var carousels = [].slice.call(document.querySelectorAll(Selector.DATA_RIDE));
|
|
|
|
for (var i = 0, len = carousels.length; i < len; i++) {
|
|
var $carousel = $(carousels[i]);
|
|
|
|
Carousel._jQueryInterface.call($carousel, $carousel.data());
|
|
}
|
|
});
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* jQuery
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
$.fn[NAME] = Carousel._jQueryInterface;
|
|
$.fn[NAME].Constructor = Carousel;
|
|
|
|
$.fn[NAME].noConflict = function () {
|
|
$.fn[NAME] = JQUERY_NO_CONFLICT;
|
|
return Carousel._jQueryInterface;
|
|
};
|
|
|
|
return Carousel;
|
|
|
|
}));
|
|
/*!
|
|
* Bootstrap collapse.js v4.3.1 (https://getbootstrap.com/)
|
|
* Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
*/
|
|
|
|
(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('jquery'), require('./util.js')) :
|
|
typeof define === 'function' && define.amd ? define(['jquery', './util.js'], factory) :
|
|
(global = global || self, global.Collapse = factory(global.jQuery, global.Util));
|
|
}(this, function ($, Util) { 'use strict';
|
|
|
|
$ = $ && $.hasOwnProperty('default') ? $['default'] : $;
|
|
Util = Util && Util.hasOwnProperty('default') ? Util['default'] : Util;
|
|
|
|
function _defineProperties(target, props) {
|
|
for (var i = 0; i < props.length; i++) {
|
|
var descriptor = props[i];
|
|
descriptor.enumerable = descriptor.enumerable || false;
|
|
descriptor.configurable = true;
|
|
if ("value" in descriptor) descriptor.writable = true;
|
|
Object.defineProperty(target, descriptor.key, descriptor);
|
|
}
|
|
}
|
|
|
|
function _createClass(Constructor, protoProps, staticProps) {
|
|
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
|
|
if (staticProps) _defineProperties(Constructor, staticProps);
|
|
return Constructor;
|
|
}
|
|
|
|
function _defineProperty(obj, key, value) {
|
|
if (key in obj) {
|
|
Object.defineProperty(obj, key, {
|
|
value: value,
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true
|
|
});
|
|
} else {
|
|
obj[key] = value;
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
function _objectSpread(target) {
|
|
for (var i = 1; i < arguments.length; i++) {
|
|
var source = arguments[i] != null ? arguments[i] : {};
|
|
var ownKeys = Object.keys(source);
|
|
|
|
if (typeof Object.getOwnPropertySymbols === 'function') {
|
|
ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) {
|
|
return Object.getOwnPropertyDescriptor(source, sym).enumerable;
|
|
}));
|
|
}
|
|
|
|
ownKeys.forEach(function (key) {
|
|
_defineProperty(target, key, source[key]);
|
|
});
|
|
}
|
|
|
|
return target;
|
|
}
|
|
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* Constants
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
var NAME = 'collapse';
|
|
var VERSION = '4.3.1';
|
|
var DATA_KEY = 'bs.collapse';
|
|
var EVENT_KEY = "." + DATA_KEY;
|
|
var DATA_API_KEY = '.data-api';
|
|
var JQUERY_NO_CONFLICT = $.fn[NAME];
|
|
var Default = {
|
|
toggle: true,
|
|
parent: ''
|
|
};
|
|
var DefaultType = {
|
|
toggle: 'boolean',
|
|
parent: '(string|element)'
|
|
};
|
|
var Event = {
|
|
SHOW: "show" + EVENT_KEY,
|
|
SHOWN: "shown" + EVENT_KEY,
|
|
HIDE: "hide" + EVENT_KEY,
|
|
HIDDEN: "hidden" + EVENT_KEY,
|
|
CLICK_DATA_API: "click" + EVENT_KEY + DATA_API_KEY
|
|
};
|
|
var ClassName = {
|
|
SHOW: 'show',
|
|
COLLAPSE: 'collapse',
|
|
COLLAPSING: 'collapsing',
|
|
COLLAPSED: 'collapsed'
|
|
};
|
|
var Dimension = {
|
|
WIDTH: 'width',
|
|
HEIGHT: 'height'
|
|
};
|
|
var Selector = {
|
|
ACTIVES: '.show, .collapsing',
|
|
DATA_TOGGLE: '[data-toggle="collapse"]'
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* Class Definition
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
};
|
|
|
|
var Collapse =
|
|
/*#__PURE__*/
|
|
function () {
|
|
function Collapse(element, config) {
|
|
this._isTransitioning = false;
|
|
this._element = element;
|
|
this._config = this._getConfig(config);
|
|
this._triggerArray = [].slice.call(document.querySelectorAll("[data-toggle=\"collapse\"][href=\"#" + element.id + "\"]," + ("[data-toggle=\"collapse\"][data-target=\"#" + element.id + "\"]")));
|
|
var toggleList = [].slice.call(document.querySelectorAll(Selector.DATA_TOGGLE));
|
|
|
|
for (var i = 0, len = toggleList.length; i < len; i++) {
|
|
var elem = toggleList[i];
|
|
var selector = Util.getSelectorFromElement(elem);
|
|
var filterElement = [].slice.call(document.querySelectorAll(selector)).filter(function (foundElem) {
|
|
return foundElem === element;
|
|
});
|
|
|
|
if (selector !== null && filterElement.length > 0) {
|
|
this._selector = selector;
|
|
|
|
this._triggerArray.push(elem);
|
|
}
|
|
}
|
|
|
|
this._parent = this._config.parent ? this._getParent() : null;
|
|
|
|
if (!this._config.parent) {
|
|
this._addAriaAndCollapsedClass(this._element, this._triggerArray);
|
|
}
|
|
|
|
if (this._config.toggle) {
|
|
this.toggle();
|
|
}
|
|
} // Getters
|
|
|
|
|
|
var _proto = Collapse.prototype;
|
|
|
|
// Public
|
|
_proto.toggle = function toggle() {
|
|
if ($(this._element).hasClass(ClassName.SHOW)) {
|
|
this.hide();
|
|
} else {
|
|
this.show();
|
|
}
|
|
};
|
|
|
|
_proto.show = function show() {
|
|
var _this = this;
|
|
|
|
if (this._isTransitioning || $(this._element).hasClass(ClassName.SHOW)) {
|
|
return;
|
|
}
|
|
|
|
var actives;
|
|
var activesData;
|
|
|
|
if (this._parent) {
|
|
actives = [].slice.call(this._parent.querySelectorAll(Selector.ACTIVES)).filter(function (elem) {
|
|
if (typeof _this._config.parent === 'string') {
|
|
return elem.getAttribute('data-parent') === _this._config.parent;
|
|
}
|
|
|
|
return elem.classList.contains(ClassName.COLLAPSE);
|
|
});
|
|
|
|
if (actives.length === 0) {
|
|
actives = null;
|
|
}
|
|
}
|
|
|
|
if (actives) {
|
|
activesData = $(actives).not(this._selector).data(DATA_KEY);
|
|
|
|
if (activesData && activesData._isTransitioning) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
var startEvent = $.Event(Event.SHOW);
|
|
$(this._element).trigger(startEvent);
|
|
|
|
if (startEvent.isDefaultPrevented()) {
|
|
return;
|
|
}
|
|
|
|
if (actives) {
|
|
Collapse._jQueryInterface.call($(actives).not(this._selector), 'hide');
|
|
|
|
if (!activesData) {
|
|
$(actives).data(DATA_KEY, null);
|
|
}
|
|
}
|
|
|
|
var dimension = this._getDimension();
|
|
|
|
$(this._element).removeClass(ClassName.COLLAPSE).addClass(ClassName.COLLAPSING);
|
|
this._element.style[dimension] = 0;
|
|
|
|
if (this._triggerArray.length) {
|
|
$(this._triggerArray).removeClass(ClassName.COLLAPSED).attr('aria-expanded', true);
|
|
}
|
|
|
|
this.setTransitioning(true);
|
|
|
|
var complete = function complete() {
|
|
$(_this._element).removeClass(ClassName.COLLAPSING).addClass(ClassName.COLLAPSE).addClass(ClassName.SHOW);
|
|
_this._element.style[dimension] = '';
|
|
|
|
_this.setTransitioning(false);
|
|
|
|
$(_this._element).trigger(Event.SHOWN);
|
|
};
|
|
|
|
var capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1);
|
|
var scrollSize = "scroll" + capitalizedDimension;
|
|
var transitionDuration = Util.getTransitionDurationFromElement(this._element);
|
|
$(this._element).one(Util.TRANSITION_END, complete).emulateTransitionEnd(transitionDuration);
|
|
this._element.style[dimension] = this._element[scrollSize] + "px";
|
|
};
|
|
|
|
_proto.hide = function hide() {
|
|
var _this2 = this;
|
|
|
|
if (this._isTransitioning || !$(this._element).hasClass(ClassName.SHOW)) {
|
|
return;
|
|
}
|
|
|
|
var startEvent = $.Event(Event.HIDE);
|
|
$(this._element).trigger(startEvent);
|
|
|
|
if (startEvent.isDefaultPrevented()) {
|
|
return;
|
|
}
|
|
|
|
var dimension = this._getDimension();
|
|
|
|
this._element.style[dimension] = this._element.getBoundingClientRect()[dimension] + "px";
|
|
Util.reflow(this._element);
|
|
$(this._element).addClass(ClassName.COLLAPSING).removeClass(ClassName.COLLAPSE).removeClass(ClassName.SHOW);
|
|
var triggerArrayLength = this._triggerArray.length;
|
|
|
|
if (triggerArrayLength > 0) {
|
|
for (var i = 0; i < triggerArrayLength; i++) {
|
|
var trigger = this._triggerArray[i];
|
|
var selector = Util.getSelectorFromElement(trigger);
|
|
|
|
if (selector !== null) {
|
|
var $elem = $([].slice.call(document.querySelectorAll(selector)));
|
|
|
|
if (!$elem.hasClass(ClassName.SHOW)) {
|
|
$(trigger).addClass(ClassName.COLLAPSED).attr('aria-expanded', false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
this.setTransitioning(true);
|
|
|
|
var complete = function complete() {
|
|
_this2.setTransitioning(false);
|
|
|
|
$(_this2._element).removeClass(ClassName.COLLAPSING).addClass(ClassName.COLLAPSE).trigger(Event.HIDDEN);
|
|
};
|
|
|
|
this._element.style[dimension] = '';
|
|
var transitionDuration = Util.getTransitionDurationFromElement(this._element);
|
|
$(this._element).one(Util.TRANSITION_END, complete).emulateTransitionEnd(transitionDuration);
|
|
};
|
|
|
|
_proto.setTransitioning = function setTransitioning(isTransitioning) {
|
|
this._isTransitioning = isTransitioning;
|
|
};
|
|
|
|
_proto.dispose = function dispose() {
|
|
$.removeData(this._element, DATA_KEY);
|
|
this._config = null;
|
|
this._parent = null;
|
|
this._element = null;
|
|
this._triggerArray = null;
|
|
this._isTransitioning = null;
|
|
} // Private
|
|
;
|
|
|
|
_proto._getConfig = function _getConfig(config) {
|
|
config = _objectSpread({}, Default, config);
|
|
config.toggle = Boolean(config.toggle); // Coerce string values
|
|
|
|
Util.typeCheckConfig(NAME, config, DefaultType);
|
|
return config;
|
|
};
|
|
|
|
_proto._getDimension = function _getDimension() {
|
|
var hasWidth = $(this._element).hasClass(Dimension.WIDTH);
|
|
return hasWidth ? Dimension.WIDTH : Dimension.HEIGHT;
|
|
};
|
|
|
|
_proto._getParent = function _getParent() {
|
|
var _this3 = this;
|
|
|
|
var parent;
|
|
|
|
if (Util.isElement(this._config.parent)) {
|
|
parent = this._config.parent; // It's a jQuery object
|
|
|
|
if (typeof this._config.parent.jquery !== 'undefined') {
|
|
parent = this._config.parent[0];
|
|
}
|
|
} else {
|
|
parent = document.querySelector(this._config.parent);
|
|
}
|
|
|
|
var selector = "[data-toggle=\"collapse\"][data-parent=\"" + this._config.parent + "\"]";
|
|
var children = [].slice.call(parent.querySelectorAll(selector));
|
|
$(children).each(function (i, element) {
|
|
_this3._addAriaAndCollapsedClass(Collapse._getTargetFromElement(element), [element]);
|
|
});
|
|
return parent;
|
|
};
|
|
|
|
_proto._addAriaAndCollapsedClass = function _addAriaAndCollapsedClass(element, triggerArray) {
|
|
var isOpen = $(element).hasClass(ClassName.SHOW);
|
|
|
|
if (triggerArray.length) {
|
|
$(triggerArray).toggleClass(ClassName.COLLAPSED, !isOpen).attr('aria-expanded', isOpen);
|
|
}
|
|
} // Static
|
|
;
|
|
|
|
Collapse._getTargetFromElement = function _getTargetFromElement(element) {
|
|
var selector = Util.getSelectorFromElement(element);
|
|
return selector ? document.querySelector(selector) : null;
|
|
};
|
|
|
|
Collapse._jQueryInterface = function _jQueryInterface(config) {
|
|
return this.each(function () {
|
|
var $this = $(this);
|
|
var data = $this.data(DATA_KEY);
|
|
|
|
var _config = _objectSpread({}, Default, $this.data(), typeof config === 'object' && config ? config : {});
|
|
|
|
if (!data && _config.toggle && /show|hide/.test(config)) {
|
|
_config.toggle = false;
|
|
}
|
|
|
|
if (!data) {
|
|
data = new Collapse(this, _config);
|
|
$this.data(DATA_KEY, data);
|
|
}
|
|
|
|
if (typeof config === 'string') {
|
|
if (typeof data[config] === 'undefined') {
|
|
throw new TypeError("No method named \"" + config + "\"");
|
|
}
|
|
|
|
data[config]();
|
|
}
|
|
});
|
|
};
|
|
|
|
_createClass(Collapse, null, [{
|
|
key: "VERSION",
|
|
get: function get() {
|
|
return VERSION;
|
|
}
|
|
}, {
|
|
key: "Default",
|
|
get: function get() {
|
|
return Default;
|
|
}
|
|
}]);
|
|
|
|
return Collapse;
|
|
}();
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* Data Api implementation
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
|
|
$(document).on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (event) {
|
|
// preventDefault only for <a> elements (which change the URL) not inside the collapsible element
|
|
if (event.currentTarget.tagName === 'A') {
|
|
event.preventDefault();
|
|
}
|
|
|
|
var $trigger = $(this);
|
|
var selector = Util.getSelectorFromElement(this);
|
|
var selectors = [].slice.call(document.querySelectorAll(selector));
|
|
$(selectors).each(function () {
|
|
var $target = $(this);
|
|
var data = $target.data(DATA_KEY);
|
|
var config = data ? 'toggle' : $trigger.data();
|
|
|
|
Collapse._jQueryInterface.call($target, config);
|
|
});
|
|
});
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* jQuery
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
$.fn[NAME] = Collapse._jQueryInterface;
|
|
$.fn[NAME].Constructor = Collapse;
|
|
|
|
$.fn[NAME].noConflict = function () {
|
|
$.fn[NAME] = JQUERY_NO_CONFLICT;
|
|
return Collapse._jQueryInterface;
|
|
};
|
|
|
|
return Collapse;
|
|
|
|
}));
|
|
/*!
|
|
* Bootstrap dropdown.js v4.3.1 (https://getbootstrap.com/)
|
|
* Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
*/
|
|
|
|
(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('jquery'), require('popper.js'), require('./util.js')) :
|
|
typeof define === 'function' && define.amd ? define(['jquery', 'popper.js', './util.js'], factory) :
|
|
(global = global || self, global.Dropdown = factory(global.jQuery, global.Popper, global.Util));
|
|
}(this, function ($, Popper, Util) { 'use strict';
|
|
|
|
$ = $ && $.hasOwnProperty('default') ? $['default'] : $;
|
|
Popper = Popper && Popper.hasOwnProperty('default') ? Popper['default'] : Popper;
|
|
Util = Util && Util.hasOwnProperty('default') ? Util['default'] : Util;
|
|
|
|
function _defineProperties(target, props) {
|
|
for (var i = 0; i < props.length; i++) {
|
|
var descriptor = props[i];
|
|
descriptor.enumerable = descriptor.enumerable || false;
|
|
descriptor.configurable = true;
|
|
if ("value" in descriptor) descriptor.writable = true;
|
|
Object.defineProperty(target, descriptor.key, descriptor);
|
|
}
|
|
}
|
|
|
|
function _createClass(Constructor, protoProps, staticProps) {
|
|
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
|
|
if (staticProps) _defineProperties(Constructor, staticProps);
|
|
return Constructor;
|
|
}
|
|
|
|
function _defineProperty(obj, key, value) {
|
|
if (key in obj) {
|
|
Object.defineProperty(obj, key, {
|
|
value: value,
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true
|
|
});
|
|
} else {
|
|
obj[key] = value;
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
function _objectSpread(target) {
|
|
for (var i = 1; i < arguments.length; i++) {
|
|
var source = arguments[i] != null ? arguments[i] : {};
|
|
var ownKeys = Object.keys(source);
|
|
|
|
if (typeof Object.getOwnPropertySymbols === 'function') {
|
|
ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) {
|
|
return Object.getOwnPropertyDescriptor(source, sym).enumerable;
|
|
}));
|
|
}
|
|
|
|
ownKeys.forEach(function (key) {
|
|
_defineProperty(target, key, source[key]);
|
|
});
|
|
}
|
|
|
|
return target;
|
|
}
|
|
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* Constants
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
var NAME = 'dropdown';
|
|
var VERSION = '4.3.1';
|
|
var DATA_KEY = 'bs.dropdown';
|
|
var EVENT_KEY = "." + DATA_KEY;
|
|
var DATA_API_KEY = '.data-api';
|
|
var JQUERY_NO_CONFLICT = $.fn[NAME];
|
|
var ESCAPE_KEYCODE = 27; // KeyboardEvent.which value for Escape (Esc) key
|
|
|
|
var SPACE_KEYCODE = 32; // KeyboardEvent.which value for space key
|
|
|
|
var TAB_KEYCODE = 9; // KeyboardEvent.which value for tab key
|
|
|
|
var ARROW_UP_KEYCODE = 38; // KeyboardEvent.which value for up arrow key
|
|
|
|
var ARROW_DOWN_KEYCODE = 40; // KeyboardEvent.which value for down arrow key
|
|
|
|
var RIGHT_MOUSE_BUTTON_WHICH = 3; // MouseEvent.which value for the right button (assuming a right-handed mouse)
|
|
|
|
var REGEXP_KEYDOWN = new RegExp(ARROW_UP_KEYCODE + "|" + ARROW_DOWN_KEYCODE + "|" + ESCAPE_KEYCODE);
|
|
var Event = {
|
|
HIDE: "hide" + EVENT_KEY,
|
|
HIDDEN: "hidden" + EVENT_KEY,
|
|
SHOW: "show" + EVENT_KEY,
|
|
SHOWN: "shown" + EVENT_KEY,
|
|
CLICK: "click" + EVENT_KEY,
|
|
CLICK_DATA_API: "click" + EVENT_KEY + DATA_API_KEY,
|
|
KEYDOWN_DATA_API: "keydown" + EVENT_KEY + DATA_API_KEY,
|
|
KEYUP_DATA_API: "keyup" + EVENT_KEY + DATA_API_KEY
|
|
};
|
|
var ClassName = {
|
|
DISABLED: 'disabled',
|
|
SHOW: 'show',
|
|
DROPUP: 'dropup',
|
|
DROPRIGHT: 'dropright',
|
|
DROPLEFT: 'dropleft',
|
|
MENURIGHT: 'dropdown-menu-right',
|
|
MENULEFT: 'dropdown-menu-left',
|
|
POSITION_STATIC: 'position-static'
|
|
};
|
|
var Selector = {
|
|
DATA_TOGGLE: '[data-toggle="dropdown"]',
|
|
FORM_CHILD: '.dropdown form',
|
|
MENU: '.dropdown-menu',
|
|
NAVBAR_NAV: '.navbar-nav',
|
|
VISIBLE_ITEMS: '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)'
|
|
};
|
|
var AttachmentMap = {
|
|
TOP: 'top-start',
|
|
TOPEND: 'top-end',
|
|
BOTTOM: 'bottom-start',
|
|
BOTTOMEND: 'bottom-end',
|
|
RIGHT: 'right-start',
|
|
RIGHTEND: 'right-end',
|
|
LEFT: 'left-start',
|
|
LEFTEND: 'left-end'
|
|
};
|
|
var Default = {
|
|
offset: 0,
|
|
flip: true,
|
|
boundary: 'scrollParent',
|
|
reference: 'toggle',
|
|
display: 'dynamic'
|
|
};
|
|
var DefaultType = {
|
|
offset: '(number|string|function)',
|
|
flip: 'boolean',
|
|
boundary: '(string|element)',
|
|
reference: '(string|element)',
|
|
display: 'string'
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* Class Definition
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
};
|
|
|
|
var Dropdown =
|
|
/*#__PURE__*/
|
|
function () {
|
|
function Dropdown(element, config) {
|
|
this._element = element;
|
|
this._popper = null;
|
|
this._config = this._getConfig(config);
|
|
this._menu = this._getMenuElement();
|
|
this._inNavbar = this._detectNavbar();
|
|
|
|
this._addEventListeners();
|
|
} // Getters
|
|
|
|
|
|
var _proto = Dropdown.prototype;
|
|
|
|
// Public
|
|
_proto.toggle = function toggle() {
|
|
if (this._element.disabled || $(this._element).hasClass(ClassName.DISABLED)) {
|
|
return;
|
|
}
|
|
|
|
var parent = Dropdown._getParentFromElement(this._element);
|
|
|
|
var isActive = $(this._menu).hasClass(ClassName.SHOW);
|
|
|
|
Dropdown._clearMenus();
|
|
|
|
if (isActive) {
|
|
return;
|
|
}
|
|
|
|
var relatedTarget = {
|
|
relatedTarget: this._element
|
|
};
|
|
var showEvent = $.Event(Event.SHOW, relatedTarget);
|
|
$(parent).trigger(showEvent);
|
|
|
|
if (showEvent.isDefaultPrevented()) {
|
|
return;
|
|
} // Disable totally Popper.js for Dropdown in Navbar
|
|
|
|
|
|
if (!this._inNavbar) {
|
|
/**
|
|
* Check for Popper dependency
|
|
* Popper - https://popper.js.org
|
|
*/
|
|
if (typeof Popper === 'undefined') {
|
|
throw new TypeError('Bootstrap\'s dropdowns require Popper.js (https://popper.js.org/)');
|
|
}
|
|
|
|
var referenceElement = this._element;
|
|
|
|
if (this._config.reference === 'parent') {
|
|
referenceElement = parent;
|
|
} else if (Util.isElement(this._config.reference)) {
|
|
referenceElement = this._config.reference; // Check if it's jQuery element
|
|
|
|
if (typeof this._config.reference.jquery !== 'undefined') {
|
|
referenceElement = this._config.reference[0];
|
|
}
|
|
} // If boundary is not `scrollParent`, then set position to `static`
|
|
// to allow the menu to "escape" the scroll parent's boundaries
|
|
// https://github.com/twbs/bootstrap/issues/24251
|
|
|
|
|
|
if (this._config.boundary !== 'scrollParent') {
|
|
$(parent).addClass(ClassName.POSITION_STATIC);
|
|
}
|
|
|
|
this._popper = new Popper(referenceElement, this._menu, this._getPopperConfig());
|
|
} // If this is a touch-enabled device we add extra
|
|
// empty mouseover listeners to the body's immediate children;
|
|
// only needed because of broken event delegation on iOS
|
|
// https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
|
|
|
|
|
|
if ('ontouchstart' in document.documentElement && $(parent).closest(Selector.NAVBAR_NAV).length === 0) {
|
|
$(document.body).children().on('mouseover', null, $.noop);
|
|
}
|
|
|
|
this._element.focus();
|
|
|
|
this._element.setAttribute('aria-expanded', true);
|
|
|
|
$(this._menu).toggleClass(ClassName.SHOW);
|
|
$(parent).toggleClass(ClassName.SHOW).trigger($.Event(Event.SHOWN, relatedTarget));
|
|
};
|
|
|
|
_proto.show = function show() {
|
|
if (this._element.disabled || $(this._element).hasClass(ClassName.DISABLED) || $(this._menu).hasClass(ClassName.SHOW)) {
|
|
return;
|
|
}
|
|
|
|
var relatedTarget = {
|
|
relatedTarget: this._element
|
|
};
|
|
var showEvent = $.Event(Event.SHOW, relatedTarget);
|
|
|
|
var parent = Dropdown._getParentFromElement(this._element);
|
|
|
|
$(parent).trigger(showEvent);
|
|
|
|
if (showEvent.isDefaultPrevented()) {
|
|
return;
|
|
}
|
|
|
|
$(this._menu).toggleClass(ClassName.SHOW);
|
|
$(parent).toggleClass(ClassName.SHOW).trigger($.Event(Event.SHOWN, relatedTarget));
|
|
};
|
|
|
|
_proto.hide = function hide() {
|
|
if (this._element.disabled || $(this._element).hasClass(ClassName.DISABLED) || !$(this._menu).hasClass(ClassName.SHOW)) {
|
|
return;
|
|
}
|
|
|
|
var relatedTarget = {
|
|
relatedTarget: this._element
|
|
};
|
|
var hideEvent = $.Event(Event.HIDE, relatedTarget);
|
|
|
|
var parent = Dropdown._getParentFromElement(this._element);
|
|
|
|
$(parent).trigger(hideEvent);
|
|
|
|
if (hideEvent.isDefaultPrevented()) {
|
|
return;
|
|
}
|
|
|
|
$(this._menu).toggleClass(ClassName.SHOW);
|
|
$(parent).toggleClass(ClassName.SHOW).trigger($.Event(Event.HIDDEN, relatedTarget));
|
|
};
|
|
|
|
_proto.dispose = function dispose() {
|
|
$.removeData(this._element, DATA_KEY);
|
|
$(this._element).off(EVENT_KEY);
|
|
this._element = null;
|
|
this._menu = null;
|
|
|
|
if (this._popper !== null) {
|
|
this._popper.destroy();
|
|
|
|
this._popper = null;
|
|
}
|
|
};
|
|
|
|
_proto.update = function update() {
|
|
this._inNavbar = this._detectNavbar();
|
|
|
|
if (this._popper !== null) {
|
|
this._popper.scheduleUpdate();
|
|
}
|
|
} // Private
|
|
;
|
|
|
|
_proto._addEventListeners = function _addEventListeners() {
|
|
var _this = this;
|
|
|
|
$(this._element).on(Event.CLICK, function (event) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
|
|
_this.toggle();
|
|
});
|
|
};
|
|
|
|
_proto._getConfig = function _getConfig(config) {
|
|
config = _objectSpread({}, this.constructor.Default, $(this._element).data(), config);
|
|
Util.typeCheckConfig(NAME, config, this.constructor.DefaultType);
|
|
return config;
|
|
};
|
|
|
|
_proto._getMenuElement = function _getMenuElement() {
|
|
if (!this._menu) {
|
|
var parent = Dropdown._getParentFromElement(this._element);
|
|
|
|
if (parent) {
|
|
this._menu = parent.querySelector(Selector.MENU);
|
|
}
|
|
}
|
|
|
|
return this._menu;
|
|
};
|
|
|
|
_proto._getPlacement = function _getPlacement() {
|
|
var $parentDropdown = $(this._element.parentNode);
|
|
var placement = AttachmentMap.BOTTOM; // Handle dropup
|
|
|
|
if ($parentDropdown.hasClass(ClassName.DROPUP)) {
|
|
placement = AttachmentMap.TOP;
|
|
|
|
if ($(this._menu).hasClass(ClassName.MENURIGHT)) {
|
|
placement = AttachmentMap.TOPEND;
|
|
}
|
|
} else if ($parentDropdown.hasClass(ClassName.DROPRIGHT)) {
|
|
placement = AttachmentMap.RIGHT;
|
|
} else if ($parentDropdown.hasClass(ClassName.DROPLEFT)) {
|
|
placement = AttachmentMap.LEFT;
|
|
} else if ($(this._menu).hasClass(ClassName.MENURIGHT)) {
|
|
placement = AttachmentMap.BOTTOMEND;
|
|
}
|
|
|
|
return placement;
|
|
};
|
|
|
|
_proto._detectNavbar = function _detectNavbar() {
|
|
return $(this._element).closest('.navbar').length > 0;
|
|
};
|
|
|
|
_proto._getOffset = function _getOffset() {
|
|
var _this2 = this;
|
|
|
|
var offset = {};
|
|
|
|
if (typeof this._config.offset === 'function') {
|
|
offset.fn = function (data) {
|
|
data.offsets = _objectSpread({}, data.offsets, _this2._config.offset(data.offsets, _this2._element) || {});
|
|
return data;
|
|
};
|
|
} else {
|
|
offset.offset = this._config.offset;
|
|
}
|
|
|
|
return offset;
|
|
};
|
|
|
|
_proto._getPopperConfig = function _getPopperConfig() {
|
|
var popperConfig = {
|
|
placement: this._getPlacement(),
|
|
modifiers: {
|
|
offset: this._getOffset(),
|
|
flip: {
|
|
enabled: this._config.flip
|
|
},
|
|
preventOverflow: {
|
|
boundariesElement: this._config.boundary
|
|
}
|
|
} // Disable Popper.js if we have a static display
|
|
|
|
};
|
|
|
|
if (this._config.display === 'static') {
|
|
popperConfig.modifiers.applyStyle = {
|
|
enabled: false
|
|
};
|
|
}
|
|
|
|
return popperConfig;
|
|
} // Static
|
|
;
|
|
|
|
Dropdown._jQueryInterface = function _jQueryInterface(config) {
|
|
return this.each(function () {
|
|
var data = $(this).data(DATA_KEY);
|
|
|
|
var _config = typeof config === 'object' ? config : null;
|
|
|
|
if (!data) {
|
|
data = new Dropdown(this, _config);
|
|
$(this).data(DATA_KEY, data);
|
|
}
|
|
|
|
if (typeof config === 'string') {
|
|
if (typeof data[config] === 'undefined') {
|
|
throw new TypeError("No method named \"" + config + "\"");
|
|
}
|
|
|
|
data[config]();
|
|
}
|
|
});
|
|
};
|
|
|
|
Dropdown._clearMenus = function _clearMenus(event) {
|
|
if (event && (event.which === RIGHT_MOUSE_BUTTON_WHICH || event.type === 'keyup' && event.which !== TAB_KEYCODE)) {
|
|
return;
|
|
}
|
|
|
|
var toggles = [].slice.call(document.querySelectorAll(Selector.DATA_TOGGLE));
|
|
|
|
for (var i = 0, len = toggles.length; i < len; i++) {
|
|
var parent = Dropdown._getParentFromElement(toggles[i]);
|
|
|
|
var context = $(toggles[i]).data(DATA_KEY);
|
|
var relatedTarget = {
|
|
relatedTarget: toggles[i]
|
|
};
|
|
|
|
if (event && event.type === 'click') {
|
|
relatedTarget.clickEvent = event;
|
|
}
|
|
|
|
if (!context) {
|
|
continue;
|
|
}
|
|
|
|
var dropdownMenu = context._menu;
|
|
|
|
if (!$(parent).hasClass(ClassName.SHOW)) {
|
|
continue;
|
|
}
|
|
|
|
if (event && (event.type === 'click' && /input|textarea/i.test(event.target.tagName) || event.type === 'keyup' && event.which === TAB_KEYCODE) && $.contains(parent, event.target)) {
|
|
continue;
|
|
}
|
|
|
|
var hideEvent = $.Event(Event.HIDE, relatedTarget);
|
|
$(parent).trigger(hideEvent);
|
|
|
|
if (hideEvent.isDefaultPrevented()) {
|
|
continue;
|
|
} // If this is a touch-enabled device we remove the extra
|
|
// empty mouseover listeners we added for iOS support
|
|
|
|
|
|
if ('ontouchstart' in document.documentElement) {
|
|
$(document.body).children().off('mouseover', null, $.noop);
|
|
}
|
|
|
|
toggles[i].setAttribute('aria-expanded', 'false');
|
|
$(dropdownMenu).removeClass(ClassName.SHOW);
|
|
$(parent).removeClass(ClassName.SHOW).trigger($.Event(Event.HIDDEN, relatedTarget));
|
|
}
|
|
};
|
|
|
|
Dropdown._getParentFromElement = function _getParentFromElement(element) {
|
|
var parent;
|
|
var selector = Util.getSelectorFromElement(element);
|
|
|
|
if (selector) {
|
|
parent = document.querySelector(selector);
|
|
}
|
|
|
|
return parent || element.parentNode;
|
|
} // eslint-disable-next-line complexity
|
|
;
|
|
|
|
Dropdown._dataApiKeydownHandler = function _dataApiKeydownHandler(event) {
|
|
// If not input/textarea:
|
|
// - And not a key in REGEXP_KEYDOWN => not a dropdown command
|
|
// If input/textarea:
|
|
// - If space key => not a dropdown command
|
|
// - If key is other than escape
|
|
// - If key is not up or down => not a dropdown command
|
|
// - If trigger inside the menu => not a dropdown command
|
|
if (/input|textarea/i.test(event.target.tagName) ? event.which === SPACE_KEYCODE || event.which !== ESCAPE_KEYCODE && (event.which !== ARROW_DOWN_KEYCODE && event.which !== ARROW_UP_KEYCODE || $(event.target).closest(Selector.MENU).length) : !REGEXP_KEYDOWN.test(event.which)) {
|
|
return;
|
|
}
|
|
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
|
|
if (this.disabled || $(this).hasClass(ClassName.DISABLED)) {
|
|
return;
|
|
}
|
|
|
|
var parent = Dropdown._getParentFromElement(this);
|
|
|
|
var isActive = $(parent).hasClass(ClassName.SHOW);
|
|
|
|
if (!isActive || isActive && (event.which === ESCAPE_KEYCODE || event.which === SPACE_KEYCODE)) {
|
|
if (event.which === ESCAPE_KEYCODE) {
|
|
var toggle = parent.querySelector(Selector.DATA_TOGGLE);
|
|
$(toggle).trigger('focus');
|
|
}
|
|
|
|
$(this).trigger('click');
|
|
return;
|
|
}
|
|
|
|
var items = [].slice.call(parent.querySelectorAll(Selector.VISIBLE_ITEMS));
|
|
|
|
if (items.length === 0) {
|
|
return;
|
|
}
|
|
|
|
var index = items.indexOf(event.target);
|
|
|
|
if (event.which === ARROW_UP_KEYCODE && index > 0) {
|
|
// Up
|
|
index--;
|
|
}
|
|
|
|
if (event.which === ARROW_DOWN_KEYCODE && index < items.length - 1) {
|
|
// Down
|
|
index++;
|
|
}
|
|
|
|
if (index < 0) {
|
|
index = 0;
|
|
}
|
|
|
|
items[index].focus();
|
|
};
|
|
|
|
_createClass(Dropdown, null, [{
|
|
key: "VERSION",
|
|
get: function get() {
|
|
return VERSION;
|
|
}
|
|
}, {
|
|
key: "Default",
|
|
get: function get() {
|
|
return Default;
|
|
}
|
|
}, {
|
|
key: "DefaultType",
|
|
get: function get() {
|
|
return DefaultType;
|
|
}
|
|
}]);
|
|
|
|
return Dropdown;
|
|
}();
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* Data Api implementation
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
|
|
$(document).on(Event.KEYDOWN_DATA_API, Selector.DATA_TOGGLE, Dropdown._dataApiKeydownHandler).on(Event.KEYDOWN_DATA_API, Selector.MENU, Dropdown._dataApiKeydownHandler).on(Event.CLICK_DATA_API + " " + Event.KEYUP_DATA_API, Dropdown._clearMenus).on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (event) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
|
|
Dropdown._jQueryInterface.call($(this), 'toggle');
|
|
}).on(Event.CLICK_DATA_API, Selector.FORM_CHILD, function (e) {
|
|
e.stopPropagation();
|
|
});
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* jQuery
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
$.fn[NAME] = Dropdown._jQueryInterface;
|
|
$.fn[NAME].Constructor = Dropdown;
|
|
|
|
$.fn[NAME].noConflict = function () {
|
|
$.fn[NAME] = JQUERY_NO_CONFLICT;
|
|
return Dropdown._jQueryInterface;
|
|
};
|
|
|
|
return Dropdown;
|
|
|
|
}));
|
|
/*!
|
|
* Bootstrap modal.js v4.3.1 (https://getbootstrap.com/)
|
|
* Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
*/
|
|
|
|
(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('jquery'), require('./util.js')) :
|
|
typeof define === 'function' && define.amd ? define(['jquery', './util.js'], factory) :
|
|
(global = global || self, global.Modal = factory(global.jQuery, global.Util));
|
|
}(this, function ($, Util) { 'use strict';
|
|
|
|
$ = $ && $.hasOwnProperty('default') ? $['default'] : $;
|
|
Util = Util && Util.hasOwnProperty('default') ? Util['default'] : Util;
|
|
|
|
function _defineProperties(target, props) {
|
|
for (var i = 0; i < props.length; i++) {
|
|
var descriptor = props[i];
|
|
descriptor.enumerable = descriptor.enumerable || false;
|
|
descriptor.configurable = true;
|
|
if ("value" in descriptor) descriptor.writable = true;
|
|
Object.defineProperty(target, descriptor.key, descriptor);
|
|
}
|
|
}
|
|
|
|
function _createClass(Constructor, protoProps, staticProps) {
|
|
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
|
|
if (staticProps) _defineProperties(Constructor, staticProps);
|
|
return Constructor;
|
|
}
|
|
|
|
function _defineProperty(obj, key, value) {
|
|
if (key in obj) {
|
|
Object.defineProperty(obj, key, {
|
|
value: value,
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true
|
|
});
|
|
} else {
|
|
obj[key] = value;
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
function _objectSpread(target) {
|
|
for (var i = 1; i < arguments.length; i++) {
|
|
var source = arguments[i] != null ? arguments[i] : {};
|
|
var ownKeys = Object.keys(source);
|
|
|
|
if (typeof Object.getOwnPropertySymbols === 'function') {
|
|
ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) {
|
|
return Object.getOwnPropertyDescriptor(source, sym).enumerable;
|
|
}));
|
|
}
|
|
|
|
ownKeys.forEach(function (key) {
|
|
_defineProperty(target, key, source[key]);
|
|
});
|
|
}
|
|
|
|
return target;
|
|
}
|
|
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* Constants
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
var NAME = 'modal';
|
|
var VERSION = '4.3.1';
|
|
var DATA_KEY = 'bs.modal';
|
|
var EVENT_KEY = "." + DATA_KEY;
|
|
var DATA_API_KEY = '.data-api';
|
|
var JQUERY_NO_CONFLICT = $.fn[NAME];
|
|
var ESCAPE_KEYCODE = 27; // KeyboardEvent.which value for Escape (Esc) key
|
|
|
|
var Default = {
|
|
backdrop: true,
|
|
keyboard: true,
|
|
focus: true,
|
|
show: true
|
|
};
|
|
var DefaultType = {
|
|
backdrop: '(boolean|string)',
|
|
keyboard: 'boolean',
|
|
focus: 'boolean',
|
|
show: 'boolean'
|
|
};
|
|
var Event = {
|
|
HIDE: "hide" + EVENT_KEY,
|
|
HIDDEN: "hidden" + EVENT_KEY,
|
|
SHOW: "show" + EVENT_KEY,
|
|
SHOWN: "shown" + EVENT_KEY,
|
|
FOCUSIN: "focusin" + EVENT_KEY,
|
|
RESIZE: "resize" + EVENT_KEY,
|
|
CLICK_DISMISS: "click.dismiss" + EVENT_KEY,
|
|
KEYDOWN_DISMISS: "keydown.dismiss" + EVENT_KEY,
|
|
MOUSEUP_DISMISS: "mouseup.dismiss" + EVENT_KEY,
|
|
MOUSEDOWN_DISMISS: "mousedown.dismiss" + EVENT_KEY,
|
|
CLICK_DATA_API: "click" + EVENT_KEY + DATA_API_KEY
|
|
};
|
|
var ClassName = {
|
|
SCROLLABLE: 'modal-dialog-scrollable',
|
|
SCROLLBAR_MEASURER: 'modal-scrollbar-measure',
|
|
BACKDROP: 'modal-backdrop',
|
|
OPEN: 'modal-open',
|
|
FADE: 'fade',
|
|
SHOW: 'show'
|
|
};
|
|
var Selector = {
|
|
DIALOG: '.modal-dialog',
|
|
MODAL_BODY: '.modal-body',
|
|
DATA_TOGGLE: '[data-toggle="modal"]',
|
|
DATA_DISMISS: '[data-dismiss="modal"]',
|
|
FIXED_CONTENT: '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top',
|
|
STICKY_CONTENT: '.sticky-top'
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* Class Definition
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
};
|
|
|
|
var Modal =
|
|
/*#__PURE__*/
|
|
function () {
|
|
function Modal(element, config) {
|
|
this._config = this._getConfig(config);
|
|
this._element = element;
|
|
this._dialog = element.querySelector(Selector.DIALOG);
|
|
this._backdrop = null;
|
|
this._isShown = false;
|
|
this._isBodyOverflowing = false;
|
|
this._ignoreBackdropClick = false;
|
|
this._isTransitioning = false;
|
|
this._scrollbarWidth = 0;
|
|
} // Getters
|
|
|
|
|
|
var _proto = Modal.prototype;
|
|
|
|
// Public
|
|
_proto.toggle = function toggle(relatedTarget) {
|
|
return this._isShown ? this.hide() : this.show(relatedTarget);
|
|
};
|
|
|
|
_proto.show = function show(relatedTarget) {
|
|
var _this = this;
|
|
|
|
if (this._isShown || this._isTransitioning) {
|
|
return;
|
|
}
|
|
|
|
if ($(this._element).hasClass(ClassName.FADE)) {
|
|
this._isTransitioning = true;
|
|
}
|
|
|
|
var showEvent = $.Event(Event.SHOW, {
|
|
relatedTarget: relatedTarget
|
|
});
|
|
$(this._element).trigger(showEvent);
|
|
|
|
if (this._isShown || showEvent.isDefaultPrevented()) {
|
|
return;
|
|
}
|
|
|
|
this._isShown = true;
|
|
|
|
this._checkScrollbar();
|
|
|
|
this._setScrollbar();
|
|
|
|
this._adjustDialog();
|
|
|
|
this._setEscapeEvent();
|
|
|
|
this._setResizeEvent();
|
|
|
|
$(this._element).on(Event.CLICK_DISMISS, Selector.DATA_DISMISS, function (event) {
|
|
return _this.hide(event);
|
|
});
|
|
$(this._dialog).on(Event.MOUSEDOWN_DISMISS, function () {
|
|
$(_this._element).one(Event.MOUSEUP_DISMISS, function (event) {
|
|
if ($(event.target).is(_this._element)) {
|
|
_this._ignoreBackdropClick = true;
|
|
}
|
|
});
|
|
});
|
|
|
|
this._showBackdrop(function () {
|
|
return _this._showElement(relatedTarget);
|
|
});
|
|
};
|
|
|
|
_proto.hide = function hide(event) {
|
|
var _this2 = this;
|
|
|
|
if (event) {
|
|
event.preventDefault();
|
|
}
|
|
|
|
if (!this._isShown || this._isTransitioning) {
|
|
return;
|
|
}
|
|
|
|
var hideEvent = $.Event(Event.HIDE);
|
|
$(this._element).trigger(hideEvent);
|
|
|
|
if (!this._isShown || hideEvent.isDefaultPrevented()) {
|
|
return;
|
|
}
|
|
|
|
this._isShown = false;
|
|
var transition = $(this._element).hasClass(ClassName.FADE);
|
|
|
|
if (transition) {
|
|
this._isTransitioning = true;
|
|
}
|
|
|
|
this._setEscapeEvent();
|
|
|
|
this._setResizeEvent();
|
|
|
|
$(document).off(Event.FOCUSIN);
|
|
$(this._element).removeClass(ClassName.SHOW);
|
|
$(this._element).off(Event.CLICK_DISMISS);
|
|
$(this._dialog).off(Event.MOUSEDOWN_DISMISS);
|
|
|
|
if (transition) {
|
|
var transitionDuration = Util.getTransitionDurationFromElement(this._element);
|
|
$(this._element).one(Util.TRANSITION_END, function (event) {
|
|
return _this2._hideModal(event);
|
|
}).emulateTransitionEnd(transitionDuration);
|
|
} else {
|
|
this._hideModal();
|
|
}
|
|
};
|
|
|
|
_proto.dispose = function dispose() {
|
|
[window, this._element, this._dialog].forEach(function (htmlElement) {
|
|
return $(htmlElement).off(EVENT_KEY);
|
|
});
|
|
/**
|
|
* `document` has 2 events `Event.FOCUSIN` and `Event.CLICK_DATA_API`
|
|
* Do not move `document` in `htmlElements` array
|
|
* It will remove `Event.CLICK_DATA_API` event that should remain
|
|
*/
|
|
|
|
$(document).off(Event.FOCUSIN);
|
|
$.removeData(this._element, DATA_KEY);
|
|
this._config = null;
|
|
this._element = null;
|
|
this._dialog = null;
|
|
this._backdrop = null;
|
|
this._isShown = null;
|
|
this._isBodyOverflowing = null;
|
|
this._ignoreBackdropClick = null;
|
|
this._isTransitioning = null;
|
|
this._scrollbarWidth = null;
|
|
};
|
|
|
|
_proto.handleUpdate = function handleUpdate() {
|
|
this._adjustDialog();
|
|
} // Private
|
|
;
|
|
|
|
_proto._getConfig = function _getConfig(config) {
|
|
config = _objectSpread({}, Default, config);
|
|
Util.typeCheckConfig(NAME, config, DefaultType);
|
|
return config;
|
|
};
|
|
|
|
_proto._showElement = function _showElement(relatedTarget) {
|
|
var _this3 = this;
|
|
|
|
var transition = $(this._element).hasClass(ClassName.FADE);
|
|
|
|
if (!this._element.parentNode || this._element.parentNode.nodeType !== Node.ELEMENT_NODE) {
|
|
// Don't move modal's DOM position
|
|
document.body.appendChild(this._element);
|
|
}
|
|
|
|
this._element.style.display = 'block';
|
|
|
|
this._element.removeAttribute('aria-hidden');
|
|
|
|
this._element.setAttribute('aria-modal', true);
|
|
|
|
if ($(this._dialog).hasClass(ClassName.SCROLLABLE)) {
|
|
this._dialog.querySelector(Selector.MODAL_BODY).scrollTop = 0;
|
|
} else {
|
|
this._element.scrollTop = 0;
|
|
}
|
|
|
|
if (transition) {
|
|
Util.reflow(this._element);
|
|
}
|
|
|
|
$(this._element).addClass(ClassName.SHOW);
|
|
|
|
if (this._config.focus) {
|
|
this._enforceFocus();
|
|
}
|
|
|
|
var shownEvent = $.Event(Event.SHOWN, {
|
|
relatedTarget: relatedTarget
|
|
});
|
|
|
|
var transitionComplete = function transitionComplete() {
|
|
if (_this3._config.focus) {
|
|
_this3._element.focus();
|
|
}
|
|
|
|
_this3._isTransitioning = false;
|
|
$(_this3._element).trigger(shownEvent);
|
|
};
|
|
|
|
if (transition) {
|
|
var transitionDuration = Util.getTransitionDurationFromElement(this._dialog);
|
|
$(this._dialog).one(Util.TRANSITION_END, transitionComplete).emulateTransitionEnd(transitionDuration);
|
|
} else {
|
|
transitionComplete();
|
|
}
|
|
};
|
|
|
|
_proto._enforceFocus = function _enforceFocus() {
|
|
var _this4 = this;
|
|
|
|
$(document).off(Event.FOCUSIN) // Guard against infinite focus loop
|
|
.on(Event.FOCUSIN, function (event) {
|
|
if (document !== event.target && _this4._element !== event.target && $(_this4._element).has(event.target).length === 0) {
|
|
_this4._element.focus();
|
|
}
|
|
});
|
|
};
|
|
|
|
_proto._setEscapeEvent = function _setEscapeEvent() {
|
|
var _this5 = this;
|
|
|
|
if (this._isShown && this._config.keyboard) {
|
|
$(this._element).on(Event.KEYDOWN_DISMISS, function (event) {
|
|
if (event.which === ESCAPE_KEYCODE) {
|
|
event.preventDefault();
|
|
|
|
_this5.hide();
|
|
}
|
|
});
|
|
} else if (!this._isShown) {
|
|
$(this._element).off(Event.KEYDOWN_DISMISS);
|
|
}
|
|
};
|
|
|
|
_proto._setResizeEvent = function _setResizeEvent() {
|
|
var _this6 = this;
|
|
|
|
if (this._isShown) {
|
|
$(window).on(Event.RESIZE, function (event) {
|
|
return _this6.handleUpdate(event);
|
|
});
|
|
} else {
|
|
$(window).off(Event.RESIZE);
|
|
}
|
|
};
|
|
|
|
_proto._hideModal = function _hideModal() {
|
|
var _this7 = this;
|
|
|
|
this._element.style.display = 'none';
|
|
|
|
this._element.setAttribute('aria-hidden', true);
|
|
|
|
this._element.removeAttribute('aria-modal');
|
|
|
|
this._isTransitioning = false;
|
|
|
|
this._showBackdrop(function () {
|
|
$(document.body).removeClass(ClassName.OPEN);
|
|
|
|
_this7._resetAdjustments();
|
|
|
|
_this7._resetScrollbar();
|
|
|
|
$(_this7._element).trigger(Event.HIDDEN);
|
|
});
|
|
};
|
|
|
|
_proto._removeBackdrop = function _removeBackdrop() {
|
|
if (this._backdrop) {
|
|
$(this._backdrop).remove();
|
|
this._backdrop = null;
|
|
}
|
|
};
|
|
|
|
_proto._showBackdrop = function _showBackdrop(callback) {
|
|
var _this8 = this;
|
|
|
|
var animate = $(this._element).hasClass(ClassName.FADE) ? ClassName.FADE : '';
|
|
|
|
if (this._isShown && this._config.backdrop) {
|
|
this._backdrop = document.createElement('div');
|
|
this._backdrop.className = ClassName.BACKDROP;
|
|
|
|
if (animate) {
|
|
this._backdrop.classList.add(animate);
|
|
}
|
|
|
|
$(this._backdrop).appendTo(document.body);
|
|
$(this._element).on(Event.CLICK_DISMISS, function (event) {
|
|
if (_this8._ignoreBackdropClick) {
|
|
_this8._ignoreBackdropClick = false;
|
|
return;
|
|
}
|
|
|
|
if (event.target !== event.currentTarget) {
|
|
return;
|
|
}
|
|
|
|
if (_this8._config.backdrop === 'static') {
|
|
_this8._element.focus();
|
|
} else {
|
|
_this8.hide();
|
|
}
|
|
});
|
|
|
|
if (animate) {
|
|
Util.reflow(this._backdrop);
|
|
}
|
|
|
|
$(this._backdrop).addClass(ClassName.SHOW);
|
|
|
|
if (!callback) {
|
|
return;
|
|
}
|
|
|
|
if (!animate) {
|
|
callback();
|
|
return;
|
|
}
|
|
|
|
var backdropTransitionDuration = Util.getTransitionDurationFromElement(this._backdrop);
|
|
$(this._backdrop).one(Util.TRANSITION_END, callback).emulateTransitionEnd(backdropTransitionDuration);
|
|
} else if (!this._isShown && this._backdrop) {
|
|
$(this._backdrop).removeClass(ClassName.SHOW);
|
|
|
|
var callbackRemove = function callbackRemove() {
|
|
_this8._removeBackdrop();
|
|
|
|
if (callback) {
|
|
callback();
|
|
}
|
|
};
|
|
|
|
if ($(this._element).hasClass(ClassName.FADE)) {
|
|
var _backdropTransitionDuration = Util.getTransitionDurationFromElement(this._backdrop);
|
|
|
|
$(this._backdrop).one(Util.TRANSITION_END, callbackRemove).emulateTransitionEnd(_backdropTransitionDuration);
|
|
} else {
|
|
callbackRemove();
|
|
}
|
|
} else if (callback) {
|
|
callback();
|
|
}
|
|
} // ----------------------------------------------------------------------
|
|
// the following methods are used to handle overflowing modals
|
|
// todo (fat): these should probably be refactored out of modal.js
|
|
// ----------------------------------------------------------------------
|
|
;
|
|
|
|
_proto._adjustDialog = function _adjustDialog() {
|
|
var isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight;
|
|
|
|
if (!this._isBodyOverflowing && isModalOverflowing) {
|
|
this._element.style.paddingLeft = this._scrollbarWidth + "px";
|
|
}
|
|
|
|
if (this._isBodyOverflowing && !isModalOverflowing) {
|
|
this._element.style.paddingRight = this._scrollbarWidth + "px";
|
|
}
|
|
};
|
|
|
|
_proto._resetAdjustments = function _resetAdjustments() {
|
|
this._element.style.paddingLeft = '';
|
|
this._element.style.paddingRight = '';
|
|
};
|
|
|
|
_proto._checkScrollbar = function _checkScrollbar() {
|
|
var rect = document.body.getBoundingClientRect();
|
|
this._isBodyOverflowing = rect.left + rect.right < window.innerWidth;
|
|
this._scrollbarWidth = this._getScrollbarWidth();
|
|
};
|
|
|
|
_proto._setScrollbar = function _setScrollbar() {
|
|
var _this9 = this;
|
|
|
|
if (this._isBodyOverflowing) {
|
|
// Note: DOMNode.style.paddingRight returns the actual value or '' if not set
|
|
// while $(DOMNode).css('padding-right') returns the calculated value or 0 if not set
|
|
var fixedContent = [].slice.call(document.querySelectorAll(Selector.FIXED_CONTENT));
|
|
var stickyContent = [].slice.call(document.querySelectorAll(Selector.STICKY_CONTENT)); // Adjust fixed content padding
|
|
|
|
$(fixedContent).each(function (index, element) {
|
|
var actualPadding = element.style.paddingRight;
|
|
var calculatedPadding = $(element).css('padding-right');
|
|
$(element).data('padding-right', actualPadding).css('padding-right', parseFloat(calculatedPadding) + _this9._scrollbarWidth + "px");
|
|
}); // Adjust sticky content margin
|
|
|
|
$(stickyContent).each(function (index, element) {
|
|
var actualMargin = element.style.marginRight;
|
|
var calculatedMargin = $(element).css('margin-right');
|
|
$(element).data('margin-right', actualMargin).css('margin-right', parseFloat(calculatedMargin) - _this9._scrollbarWidth + "px");
|
|
}); // Adjust body padding
|
|
|
|
var actualPadding = document.body.style.paddingRight;
|
|
var calculatedPadding = $(document.body).css('padding-right');
|
|
$(document.body).data('padding-right', actualPadding).css('padding-right', parseFloat(calculatedPadding) + this._scrollbarWidth + "px");
|
|
}
|
|
|
|
$(document.body).addClass(ClassName.OPEN);
|
|
};
|
|
|
|
_proto._resetScrollbar = function _resetScrollbar() {
|
|
// Restore fixed content padding
|
|
var fixedContent = [].slice.call(document.querySelectorAll(Selector.FIXED_CONTENT));
|
|
$(fixedContent).each(function (index, element) {
|
|
var padding = $(element).data('padding-right');
|
|
$(element).removeData('padding-right');
|
|
element.style.paddingRight = padding ? padding : '';
|
|
}); // Restore sticky content
|
|
|
|
var elements = [].slice.call(document.querySelectorAll("" + Selector.STICKY_CONTENT));
|
|
$(elements).each(function (index, element) {
|
|
var margin = $(element).data('margin-right');
|
|
|
|
if (typeof margin !== 'undefined') {
|
|
$(element).css('margin-right', margin).removeData('margin-right');
|
|
}
|
|
}); // Restore body padding
|
|
|
|
var padding = $(document.body).data('padding-right');
|
|
$(document.body).removeData('padding-right');
|
|
document.body.style.paddingRight = padding ? padding : '';
|
|
};
|
|
|
|
_proto._getScrollbarWidth = function _getScrollbarWidth() {
|
|
// thx d.walsh
|
|
var scrollDiv = document.createElement('div');
|
|
scrollDiv.className = ClassName.SCROLLBAR_MEASURER;
|
|
document.body.appendChild(scrollDiv);
|
|
var scrollbarWidth = scrollDiv.getBoundingClientRect().width - scrollDiv.clientWidth;
|
|
document.body.removeChild(scrollDiv);
|
|
return scrollbarWidth;
|
|
} // Static
|
|
;
|
|
|
|
Modal._jQueryInterface = function _jQueryInterface(config, relatedTarget) {
|
|
return this.each(function () {
|
|
var data = $(this).data(DATA_KEY);
|
|
|
|
var _config = _objectSpread({}, Default, $(this).data(), typeof config === 'object' && config ? config : {});
|
|
|
|
if (!data) {
|
|
data = new Modal(this, _config);
|
|
$(this).data(DATA_KEY, data);
|
|
}
|
|
|
|
if (typeof config === 'string') {
|
|
if (typeof data[config] === 'undefined') {
|
|
throw new TypeError("No method named \"" + config + "\"");
|
|
}
|
|
|
|
data[config](relatedTarget);
|
|
} else if (_config.show) {
|
|
data.show(relatedTarget);
|
|
}
|
|
});
|
|
};
|
|
|
|
_createClass(Modal, null, [{
|
|
key: "VERSION",
|
|
get: function get() {
|
|
return VERSION;
|
|
}
|
|
}, {
|
|
key: "Default",
|
|
get: function get() {
|
|
return Default;
|
|
}
|
|
}]);
|
|
|
|
return Modal;
|
|
}();
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* Data Api implementation
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
|
|
$(document).on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (event) {
|
|
var _this10 = this;
|
|
|
|
var target;
|
|
var selector = Util.getSelectorFromElement(this);
|
|
|
|
if (selector) {
|
|
target = document.querySelector(selector);
|
|
}
|
|
|
|
var config = $(target).data(DATA_KEY) ? 'toggle' : _objectSpread({}, $(target).data(), $(this).data());
|
|
|
|
if (this.tagName === 'A' || this.tagName === 'AREA') {
|
|
event.preventDefault();
|
|
}
|
|
|
|
var $target = $(target).one(Event.SHOW, function (showEvent) {
|
|
if (showEvent.isDefaultPrevented()) {
|
|
// Only register focus restorer if modal will actually get shown
|
|
return;
|
|
}
|
|
|
|
$target.one(Event.HIDDEN, function () {
|
|
if ($(_this10).is(':visible')) {
|
|
_this10.focus();
|
|
}
|
|
});
|
|
});
|
|
|
|
Modal._jQueryInterface.call($(target), config, this);
|
|
});
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* jQuery
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
$.fn[NAME] = Modal._jQueryInterface;
|
|
$.fn[NAME].Constructor = Modal;
|
|
|
|
$.fn[NAME].noConflict = function () {
|
|
$.fn[NAME] = JQUERY_NO_CONFLICT;
|
|
return Modal._jQueryInterface;
|
|
};
|
|
|
|
return Modal;
|
|
|
|
}));
|
|
/*!
|
|
* Bootstrap tooltip.js v4.3.1 (https://getbootstrap.com/)
|
|
* Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
*/
|
|
|
|
(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('jquery'), require('popper.js'), require('./util.js')) :
|
|
typeof define === 'function' && define.amd ? define(['jquery', 'popper.js', './util.js'], factory) :
|
|
(global = global || self, global.Tooltip = factory(global.jQuery, global.Popper, global.Util));
|
|
}(this, function ($, Popper, Util) { 'use strict';
|
|
|
|
$ = $ && $.hasOwnProperty('default') ? $['default'] : $;
|
|
Popper = Popper && Popper.hasOwnProperty('default') ? Popper['default'] : Popper;
|
|
Util = Util && Util.hasOwnProperty('default') ? Util['default'] : Util;
|
|
|
|
function _defineProperties(target, props) {
|
|
for (var i = 0; i < props.length; i++) {
|
|
var descriptor = props[i];
|
|
descriptor.enumerable = descriptor.enumerable || false;
|
|
descriptor.configurable = true;
|
|
if ("value" in descriptor) descriptor.writable = true;
|
|
Object.defineProperty(target, descriptor.key, descriptor);
|
|
}
|
|
}
|
|
|
|
function _createClass(Constructor, protoProps, staticProps) {
|
|
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
|
|
if (staticProps) _defineProperties(Constructor, staticProps);
|
|
return Constructor;
|
|
}
|
|
|
|
function _defineProperty(obj, key, value) {
|
|
if (key in obj) {
|
|
Object.defineProperty(obj, key, {
|
|
value: value,
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true
|
|
});
|
|
} else {
|
|
obj[key] = value;
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
function _objectSpread(target) {
|
|
for (var i = 1; i < arguments.length; i++) {
|
|
var source = arguments[i] != null ? arguments[i] : {};
|
|
var ownKeys = Object.keys(source);
|
|
|
|
if (typeof Object.getOwnPropertySymbols === 'function') {
|
|
ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) {
|
|
return Object.getOwnPropertyDescriptor(source, sym).enumerable;
|
|
}));
|
|
}
|
|
|
|
ownKeys.forEach(function (key) {
|
|
_defineProperty(target, key, source[key]);
|
|
});
|
|
}
|
|
|
|
return target;
|
|
}
|
|
|
|
/**
|
|
* --------------------------------------------------------------------------
|
|
* Bootstrap (v4.3.1): tools/sanitizer.js
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
* --------------------------------------------------------------------------
|
|
*/
|
|
var uriAttrs = ['background', 'cite', 'href', 'itemtype', 'longdesc', 'poster', 'src', 'xlink:href'];
|
|
var ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i;
|
|
var DefaultWhitelist = {
|
|
// Global attributes allowed on any supplied element below.
|
|
'*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],
|
|
a: ['target', 'href', 'title', 'rel'],
|
|
area: [],
|
|
b: [],
|
|
br: [],
|
|
col: [],
|
|
code: [],
|
|
div: [],
|
|
em: [],
|
|
hr: [],
|
|
h1: [],
|
|
h2: [],
|
|
h3: [],
|
|
h4: [],
|
|
h5: [],
|
|
h6: [],
|
|
i: [],
|
|
img: ['src', 'alt', 'title', 'width', 'height'],
|
|
li: [],
|
|
ol: [],
|
|
p: [],
|
|
pre: [],
|
|
s: [],
|
|
small: [],
|
|
span: [],
|
|
sub: [],
|
|
sup: [],
|
|
strong: [],
|
|
u: [],
|
|
ul: []
|
|
/**
|
|
* A pattern that recognizes a commonly useful subset of URLs that are safe.
|
|
*
|
|
* Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts
|
|
*/
|
|
|
|
};
|
|
var SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file):|[^&:/?#]*(?:[/?#]|$))/gi;
|
|
/**
|
|
* A pattern that matches safe data URLs. Only matches image, video and audio types.
|
|
*
|
|
* Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts
|
|
*/
|
|
|
|
var DATA_URL_PATTERN = /^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[a-z0-9+/]+=*$/i;
|
|
|
|
function allowedAttribute(attr, allowedAttributeList) {
|
|
var attrName = attr.nodeName.toLowerCase();
|
|
|
|
if (allowedAttributeList.indexOf(attrName) !== -1) {
|
|
if (uriAttrs.indexOf(attrName) !== -1) {
|
|
return Boolean(attr.nodeValue.match(SAFE_URL_PATTERN) || attr.nodeValue.match(DATA_URL_PATTERN));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
var regExp = allowedAttributeList.filter(function (attrRegex) {
|
|
return attrRegex instanceof RegExp;
|
|
}); // Check if a regular expression validates the attribute.
|
|
|
|
for (var i = 0, l = regExp.length; i < l; i++) {
|
|
if (attrName.match(regExp[i])) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function sanitizeHtml(unsafeHtml, whiteList, sanitizeFn) {
|
|
if (unsafeHtml.length === 0) {
|
|
return unsafeHtml;
|
|
}
|
|
|
|
if (sanitizeFn && typeof sanitizeFn === 'function') {
|
|
return sanitizeFn(unsafeHtml);
|
|
}
|
|
|
|
var domParser = new window.DOMParser();
|
|
var createdDocument = domParser.parseFromString(unsafeHtml, 'text/html');
|
|
var whitelistKeys = Object.keys(whiteList);
|
|
var elements = [].slice.call(createdDocument.body.querySelectorAll('*'));
|
|
|
|
var _loop = function _loop(i, len) {
|
|
var el = elements[i];
|
|
var elName = el.nodeName.toLowerCase();
|
|
|
|
if (whitelistKeys.indexOf(el.nodeName.toLowerCase()) === -1) {
|
|
el.parentNode.removeChild(el);
|
|
return "continue";
|
|
}
|
|
|
|
var attributeList = [].slice.call(el.attributes);
|
|
var whitelistedAttributes = [].concat(whiteList['*'] || [], whiteList[elName] || []);
|
|
attributeList.forEach(function (attr) {
|
|
if (!allowedAttribute(attr, whitelistedAttributes)) {
|
|
el.removeAttribute(attr.nodeName);
|
|
}
|
|
});
|
|
};
|
|
|
|
for (var i = 0, len = elements.length; i < len; i++) {
|
|
var _ret = _loop(i, len);
|
|
|
|
if (_ret === "continue") continue;
|
|
}
|
|
|
|
return createdDocument.body.innerHTML;
|
|
}
|
|
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* Constants
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
var NAME = 'tooltip';
|
|
var VERSION = '4.3.1';
|
|
var DATA_KEY = 'bs.tooltip';
|
|
var EVENT_KEY = "." + DATA_KEY;
|
|
var JQUERY_NO_CONFLICT = $.fn[NAME];
|
|
var CLASS_PREFIX = 'bs-tooltip';
|
|
var BSCLS_PREFIX_REGEX = new RegExp("(^|\\s)" + CLASS_PREFIX + "\\S+", 'g');
|
|
var DISALLOWED_ATTRIBUTES = ['sanitize', 'whiteList', 'sanitizeFn'];
|
|
var DefaultType = {
|
|
animation: 'boolean',
|
|
template: 'string',
|
|
title: '(string|element|function)',
|
|
trigger: 'string',
|
|
delay: '(number|object)',
|
|
html: 'boolean',
|
|
selector: '(string|boolean)',
|
|
placement: '(string|function)',
|
|
offset: '(number|string|function)',
|
|
container: '(string|element|boolean)',
|
|
fallbackPlacement: '(string|array)',
|
|
boundary: '(string|element)',
|
|
sanitize: 'boolean',
|
|
sanitizeFn: '(null|function)',
|
|
whiteList: 'object'
|
|
};
|
|
var AttachmentMap = {
|
|
AUTO: 'auto',
|
|
TOP: 'top',
|
|
RIGHT: 'right',
|
|
BOTTOM: 'bottom',
|
|
LEFT: 'left'
|
|
};
|
|
var Default = {
|
|
animation: true,
|
|
template: '<div class="tooltip" role="tooltip">' + '<div class="arrow"></div>' + '<div class="tooltip-inner"></div></div>',
|
|
trigger: 'hover focus',
|
|
title: '',
|
|
delay: 0,
|
|
html: false,
|
|
selector: false,
|
|
placement: 'top',
|
|
offset: 0,
|
|
container: false,
|
|
fallbackPlacement: 'flip',
|
|
boundary: 'scrollParent',
|
|
sanitize: true,
|
|
sanitizeFn: null,
|
|
whiteList: DefaultWhitelist
|
|
};
|
|
var HoverState = {
|
|
SHOW: 'show',
|
|
OUT: 'out'
|
|
};
|
|
var Event = {
|
|
HIDE: "hide" + EVENT_KEY,
|
|
HIDDEN: "hidden" + EVENT_KEY,
|
|
SHOW: "show" + EVENT_KEY,
|
|
SHOWN: "shown" + EVENT_KEY,
|
|
INSERTED: "inserted" + EVENT_KEY,
|
|
CLICK: "click" + EVENT_KEY,
|
|
FOCUSIN: "focusin" + EVENT_KEY,
|
|
FOCUSOUT: "focusout" + EVENT_KEY,
|
|
MOUSEENTER: "mouseenter" + EVENT_KEY,
|
|
MOUSELEAVE: "mouseleave" + EVENT_KEY
|
|
};
|
|
var ClassName = {
|
|
FADE: 'fade',
|
|
SHOW: 'show'
|
|
};
|
|
var Selector = {
|
|
TOOLTIP: '.tooltip',
|
|
TOOLTIP_INNER: '.tooltip-inner',
|
|
ARROW: '.arrow'
|
|
};
|
|
var Trigger = {
|
|
HOVER: 'hover',
|
|
FOCUS: 'focus',
|
|
CLICK: 'click',
|
|
MANUAL: 'manual'
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* Class Definition
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
};
|
|
|
|
var Tooltip =
|
|
/*#__PURE__*/
|
|
function () {
|
|
function Tooltip(element, config) {
|
|
/**
|
|
* Check for Popper dependency
|
|
* Popper - https://popper.js.org
|
|
*/
|
|
if (typeof Popper === 'undefined') {
|
|
throw new TypeError('Bootstrap\'s tooltips require Popper.js (https://popper.js.org/)');
|
|
} // private
|
|
|
|
|
|
this._isEnabled = true;
|
|
this._timeout = 0;
|
|
this._hoverState = '';
|
|
this._activeTrigger = {};
|
|
this._popper = null; // Protected
|
|
|
|
this.element = element;
|
|
this.config = this._getConfig(config);
|
|
this.tip = null;
|
|
|
|
this._setListeners();
|
|
} // Getters
|
|
|
|
|
|
var _proto = Tooltip.prototype;
|
|
|
|
// Public
|
|
_proto.enable = function enable() {
|
|
this._isEnabled = true;
|
|
};
|
|
|
|
_proto.disable = function disable() {
|
|
this._isEnabled = false;
|
|
};
|
|
|
|
_proto.toggleEnabled = function toggleEnabled() {
|
|
this._isEnabled = !this._isEnabled;
|
|
};
|
|
|
|
_proto.toggle = function toggle(event) {
|
|
if (!this._isEnabled) {
|
|
return;
|
|
}
|
|
|
|
if (event) {
|
|
var dataKey = this.constructor.DATA_KEY;
|
|
var context = $(event.currentTarget).data(dataKey);
|
|
|
|
if (!context) {
|
|
context = new this.constructor(event.currentTarget, this._getDelegateConfig());
|
|
$(event.currentTarget).data(dataKey, context);
|
|
}
|
|
|
|
context._activeTrigger.click = !context._activeTrigger.click;
|
|
|
|
if (context._isWithActiveTrigger()) {
|
|
context._enter(null, context);
|
|
} else {
|
|
context._leave(null, context);
|
|
}
|
|
} else {
|
|
if ($(this.getTipElement()).hasClass(ClassName.SHOW)) {
|
|
this._leave(null, this);
|
|
|
|
return;
|
|
}
|
|
|
|
this._enter(null, this);
|
|
}
|
|
};
|
|
|
|
_proto.dispose = function dispose() {
|
|
clearTimeout(this._timeout);
|
|
$.removeData(this.element, this.constructor.DATA_KEY);
|
|
$(this.element).off(this.constructor.EVENT_KEY);
|
|
$(this.element).closest('.modal').off('hide.bs.modal');
|
|
|
|
if (this.tip) {
|
|
$(this.tip).remove();
|
|
}
|
|
|
|
this._isEnabled = null;
|
|
this._timeout = null;
|
|
this._hoverState = null;
|
|
this._activeTrigger = null;
|
|
|
|
if (this._popper !== null) {
|
|
this._popper.destroy();
|
|
}
|
|
|
|
this._popper = null;
|
|
this.element = null;
|
|
this.config = null;
|
|
this.tip = null;
|
|
};
|
|
|
|
_proto.show = function show() {
|
|
var _this = this;
|
|
|
|
if ($(this.element).css('display') === 'none') {
|
|
throw new Error('Please use show on visible elements');
|
|
}
|
|
|
|
var showEvent = $.Event(this.constructor.Event.SHOW);
|
|
|
|
if (this.isWithContent() && this._isEnabled) {
|
|
$(this.element).trigger(showEvent);
|
|
var shadowRoot = Util.findShadowRoot(this.element);
|
|
var isInTheDom = $.contains(shadowRoot !== null ? shadowRoot : this.element.ownerDocument.documentElement, this.element);
|
|
|
|
if (showEvent.isDefaultPrevented() || !isInTheDom) {
|
|
return;
|
|
}
|
|
|
|
var tip = this.getTipElement();
|
|
var tipId = Util.getUID(this.constructor.NAME);
|
|
tip.setAttribute('id', tipId);
|
|
this.element.setAttribute('aria-describedby', tipId);
|
|
this.setContent();
|
|
|
|
if (this.config.animation) {
|
|
$(tip).addClass(ClassName.FADE);
|
|
}
|
|
|
|
var placement = typeof this.config.placement === 'function' ? this.config.placement.call(this, tip, this.element) : this.config.placement;
|
|
|
|
var attachment = this._getAttachment(placement);
|
|
|
|
this.addAttachmentClass(attachment);
|
|
|
|
var container = this._getContainer();
|
|
|
|
$(tip).data(this.constructor.DATA_KEY, this);
|
|
|
|
if (!$.contains(this.element.ownerDocument.documentElement, this.tip)) {
|
|
$(tip).appendTo(container);
|
|
}
|
|
|
|
$(this.element).trigger(this.constructor.Event.INSERTED);
|
|
this._popper = new Popper(this.element, tip, {
|
|
placement: attachment,
|
|
modifiers: {
|
|
offset: this._getOffset(),
|
|
flip: {
|
|
behavior: this.config.fallbackPlacement
|
|
},
|
|
arrow: {
|
|
element: Selector.ARROW
|
|
},
|
|
preventOverflow: {
|
|
boundariesElement: this.config.boundary
|
|
}
|
|
},
|
|
onCreate: function onCreate(data) {
|
|
if (data.originalPlacement !== data.placement) {
|
|
_this._handlePopperPlacementChange(data);
|
|
}
|
|
},
|
|
onUpdate: function onUpdate(data) {
|
|
return _this._handlePopperPlacementChange(data);
|
|
}
|
|
});
|
|
$(tip).addClass(ClassName.SHOW); // If this is a touch-enabled device we add extra
|
|
// empty mouseover listeners to the body's immediate children;
|
|
// only needed because of broken event delegation on iOS
|
|
// https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
|
|
|
|
if ('ontouchstart' in document.documentElement) {
|
|
$(document.body).children().on('mouseover', null, $.noop);
|
|
}
|
|
|
|
var complete = function complete() {
|
|
if (_this.config.animation) {
|
|
_this._fixTransition();
|
|
}
|
|
|
|
var prevHoverState = _this._hoverState;
|
|
_this._hoverState = null;
|
|
$(_this.element).trigger(_this.constructor.Event.SHOWN);
|
|
|
|
if (prevHoverState === HoverState.OUT) {
|
|
_this._leave(null, _this);
|
|
}
|
|
};
|
|
|
|
if ($(this.tip).hasClass(ClassName.FADE)) {
|
|
var transitionDuration = Util.getTransitionDurationFromElement(this.tip);
|
|
$(this.tip).one(Util.TRANSITION_END, complete).emulateTransitionEnd(transitionDuration);
|
|
} else {
|
|
complete();
|
|
}
|
|
}
|
|
};
|
|
|
|
_proto.hide = function hide(callback) {
|
|
var _this2 = this;
|
|
|
|
var tip = this.getTipElement();
|
|
var hideEvent = $.Event(this.constructor.Event.HIDE);
|
|
|
|
var complete = function complete() {
|
|
if (_this2._hoverState !== HoverState.SHOW && tip.parentNode) {
|
|
tip.parentNode.removeChild(tip);
|
|
}
|
|
|
|
_this2._cleanTipClass();
|
|
|
|
_this2.element.removeAttribute('aria-describedby');
|
|
|
|
$(_this2.element).trigger(_this2.constructor.Event.HIDDEN);
|
|
|
|
if (_this2._popper !== null) {
|
|
_this2._popper.destroy();
|
|
}
|
|
|
|
if (callback) {
|
|
callback();
|
|
}
|
|
};
|
|
|
|
$(this.element).trigger(hideEvent);
|
|
|
|
if (hideEvent.isDefaultPrevented()) {
|
|
return;
|
|
}
|
|
|
|
$(tip).removeClass(ClassName.SHOW); // If this is a touch-enabled device we remove the extra
|
|
// empty mouseover listeners we added for iOS support
|
|
|
|
if ('ontouchstart' in document.documentElement) {
|
|
$(document.body).children().off('mouseover', null, $.noop);
|
|
}
|
|
|
|
this._activeTrigger[Trigger.CLICK] = false;
|
|
this._activeTrigger[Trigger.FOCUS] = false;
|
|
this._activeTrigger[Trigger.HOVER] = false;
|
|
|
|
if ($(this.tip).hasClass(ClassName.FADE)) {
|
|
var transitionDuration = Util.getTransitionDurationFromElement(tip);
|
|
$(tip).one(Util.TRANSITION_END, complete).emulateTransitionEnd(transitionDuration);
|
|
} else {
|
|
complete();
|
|
}
|
|
|
|
this._hoverState = '';
|
|
};
|
|
|
|
_proto.update = function update() {
|
|
if (this._popper !== null) {
|
|
this._popper.scheduleUpdate();
|
|
}
|
|
} // Protected
|
|
;
|
|
|
|
_proto.isWithContent = function isWithContent() {
|
|
return Boolean(this.getTitle());
|
|
};
|
|
|
|
_proto.addAttachmentClass = function addAttachmentClass(attachment) {
|
|
$(this.getTipElement()).addClass(CLASS_PREFIX + "-" + attachment);
|
|
};
|
|
|
|
_proto.getTipElement = function getTipElement() {
|
|
this.tip = this.tip || $(this.config.template)[0];
|
|
return this.tip;
|
|
};
|
|
|
|
_proto.setContent = function setContent() {
|
|
var tip = this.getTipElement();
|
|
this.setElementContent($(tip.querySelectorAll(Selector.TOOLTIP_INNER)), this.getTitle());
|
|
$(tip).removeClass(ClassName.FADE + " " + ClassName.SHOW);
|
|
};
|
|
|
|
_proto.setElementContent = function setElementContent($element, content) {
|
|
if (typeof content === 'object' && (content.nodeType || content.jquery)) {
|
|
// Content is a DOM node or a jQuery
|
|
if (this.config.html) {
|
|
if (!$(content).parent().is($element)) {
|
|
$element.empty().append(content);
|
|
}
|
|
} else {
|
|
$element.text($(content).text());
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (this.config.html) {
|
|
if (this.config.sanitize) {
|
|
content = sanitizeHtml(content, this.config.whiteList, this.config.sanitizeFn);
|
|
}
|
|
|
|
$element.html(content);
|
|
} else {
|
|
$element.text(content);
|
|
}
|
|
};
|
|
|
|
_proto.getTitle = function getTitle() {
|
|
var title = this.element.getAttribute('data-original-title');
|
|
|
|
if (!title) {
|
|
title = typeof this.config.title === 'function' ? this.config.title.call(this.element) : this.config.title;
|
|
}
|
|
|
|
return title;
|
|
} // Private
|
|
;
|
|
|
|
_proto._getOffset = function _getOffset() {
|
|
var _this3 = this;
|
|
|
|
var offset = {};
|
|
|
|
if (typeof this.config.offset === 'function') {
|
|
offset.fn = function (data) {
|
|
data.offsets = _objectSpread({}, data.offsets, _this3.config.offset(data.offsets, _this3.element) || {});
|
|
return data;
|
|
};
|
|
} else {
|
|
offset.offset = this.config.offset;
|
|
}
|
|
|
|
return offset;
|
|
};
|
|
|
|
_proto._getContainer = function _getContainer() {
|
|
if (this.config.container === false) {
|
|
return document.body;
|
|
}
|
|
|
|
if (Util.isElement(this.config.container)) {
|
|
return $(this.config.container);
|
|
}
|
|
|
|
return $(document).find(this.config.container);
|
|
};
|
|
|
|
_proto._getAttachment = function _getAttachment(placement) {
|
|
return AttachmentMap[placement.toUpperCase()];
|
|
};
|
|
|
|
_proto._setListeners = function _setListeners() {
|
|
var _this4 = this;
|
|
|
|
var triggers = this.config.trigger.split(' ');
|
|
triggers.forEach(function (trigger) {
|
|
if (trigger === 'click') {
|
|
$(_this4.element).on(_this4.constructor.Event.CLICK, _this4.config.selector, function (event) {
|
|
return _this4.toggle(event);
|
|
});
|
|
} else if (trigger !== Trigger.MANUAL) {
|
|
var eventIn = trigger === Trigger.HOVER ? _this4.constructor.Event.MOUSEENTER : _this4.constructor.Event.FOCUSIN;
|
|
var eventOut = trigger === Trigger.HOVER ? _this4.constructor.Event.MOUSELEAVE : _this4.constructor.Event.FOCUSOUT;
|
|
$(_this4.element).on(eventIn, _this4.config.selector, function (event) {
|
|
return _this4._enter(event);
|
|
}).on(eventOut, _this4.config.selector, function (event) {
|
|
return _this4._leave(event);
|
|
});
|
|
}
|
|
});
|
|
$(this.element).closest('.modal').on('hide.bs.modal', function () {
|
|
if (_this4.element) {
|
|
_this4.hide();
|
|
}
|
|
});
|
|
|
|
if (this.config.selector) {
|
|
this.config = _objectSpread({}, this.config, {
|
|
trigger: 'manual',
|
|
selector: ''
|
|
});
|
|
} else {
|
|
this._fixTitle();
|
|
}
|
|
};
|
|
|
|
_proto._fixTitle = function _fixTitle() {
|
|
var titleType = typeof this.element.getAttribute('data-original-title');
|
|
|
|
if (this.element.getAttribute('title') || titleType !== 'string') {
|
|
this.element.setAttribute('data-original-title', this.element.getAttribute('title') || '');
|
|
this.element.setAttribute('title', '');
|
|
}
|
|
};
|
|
|
|
_proto._enter = function _enter(event, context) {
|
|
var dataKey = this.constructor.DATA_KEY;
|
|
context = context || $(event.currentTarget).data(dataKey);
|
|
|
|
if (!context) {
|
|
context = new this.constructor(event.currentTarget, this._getDelegateConfig());
|
|
$(event.currentTarget).data(dataKey, context);
|
|
}
|
|
|
|
if (event) {
|
|
context._activeTrigger[event.type === 'focusin' ? Trigger.FOCUS : Trigger.HOVER] = true;
|
|
}
|
|
|
|
if ($(context.getTipElement()).hasClass(ClassName.SHOW) || context._hoverState === HoverState.SHOW) {
|
|
context._hoverState = HoverState.SHOW;
|
|
return;
|
|
}
|
|
|
|
clearTimeout(context._timeout);
|
|
context._hoverState = HoverState.SHOW;
|
|
|
|
if (!context.config.delay || !context.config.delay.show) {
|
|
context.show();
|
|
return;
|
|
}
|
|
|
|
context._timeout = setTimeout(function () {
|
|
if (context._hoverState === HoverState.SHOW) {
|
|
context.show();
|
|
}
|
|
}, context.config.delay.show);
|
|
};
|
|
|
|
_proto._leave = function _leave(event, context) {
|
|
var dataKey = this.constructor.DATA_KEY;
|
|
context = context || $(event.currentTarget).data(dataKey);
|
|
|
|
if (!context) {
|
|
context = new this.constructor(event.currentTarget, this._getDelegateConfig());
|
|
$(event.currentTarget).data(dataKey, context);
|
|
}
|
|
|
|
if (event) {
|
|
context._activeTrigger[event.type === 'focusout' ? Trigger.FOCUS : Trigger.HOVER] = false;
|
|
}
|
|
|
|
if (context._isWithActiveTrigger()) {
|
|
return;
|
|
}
|
|
|
|
clearTimeout(context._timeout);
|
|
context._hoverState = HoverState.OUT;
|
|
|
|
if (!context.config.delay || !context.config.delay.hide) {
|
|
context.hide();
|
|
return;
|
|
}
|
|
|
|
context._timeout = setTimeout(function () {
|
|
if (context._hoverState === HoverState.OUT) {
|
|
context.hide();
|
|
}
|
|
}, context.config.delay.hide);
|
|
};
|
|
|
|
_proto._isWithActiveTrigger = function _isWithActiveTrigger() {
|
|
for (var trigger in this._activeTrigger) {
|
|
if (this._activeTrigger[trigger]) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
_proto._getConfig = function _getConfig(config) {
|
|
var dataAttributes = $(this.element).data();
|
|
Object.keys(dataAttributes).forEach(function (dataAttr) {
|
|
if (DISALLOWED_ATTRIBUTES.indexOf(dataAttr) !== -1) {
|
|
delete dataAttributes[dataAttr];
|
|
}
|
|
});
|
|
config = _objectSpread({}, this.constructor.Default, dataAttributes, typeof config === 'object' && config ? config : {});
|
|
|
|
if (typeof config.delay === 'number') {
|
|
config.delay = {
|
|
show: config.delay,
|
|
hide: config.delay
|
|
};
|
|
}
|
|
|
|
if (typeof config.title === 'number') {
|
|
config.title = config.title.toString();
|
|
}
|
|
|
|
if (typeof config.content === 'number') {
|
|
config.content = config.content.toString();
|
|
}
|
|
|
|
Util.typeCheckConfig(NAME, config, this.constructor.DefaultType);
|
|
|
|
if (config.sanitize) {
|
|
config.template = sanitizeHtml(config.template, config.whiteList, config.sanitizeFn);
|
|
}
|
|
|
|
return config;
|
|
};
|
|
|
|
_proto._getDelegateConfig = function _getDelegateConfig() {
|
|
var config = {};
|
|
|
|
if (this.config) {
|
|
for (var key in this.config) {
|
|
if (this.constructor.Default[key] !== this.config[key]) {
|
|
config[key] = this.config[key];
|
|
}
|
|
}
|
|
}
|
|
|
|
return config;
|
|
};
|
|
|
|
_proto._cleanTipClass = function _cleanTipClass() {
|
|
var $tip = $(this.getTipElement());
|
|
var tabClass = $tip.attr('class').match(BSCLS_PREFIX_REGEX);
|
|
|
|
if (tabClass !== null && tabClass.length) {
|
|
$tip.removeClass(tabClass.join(''));
|
|
}
|
|
};
|
|
|
|
_proto._handlePopperPlacementChange = function _handlePopperPlacementChange(popperData) {
|
|
var popperInstance = popperData.instance;
|
|
this.tip = popperInstance.popper;
|
|
|
|
this._cleanTipClass();
|
|
|
|
this.addAttachmentClass(this._getAttachment(popperData.placement));
|
|
};
|
|
|
|
_proto._fixTransition = function _fixTransition() {
|
|
var tip = this.getTipElement();
|
|
var initConfigAnimation = this.config.animation;
|
|
|
|
if (tip.getAttribute('x-placement') !== null) {
|
|
return;
|
|
}
|
|
|
|
$(tip).removeClass(ClassName.FADE);
|
|
this.config.animation = false;
|
|
this.hide();
|
|
this.show();
|
|
this.config.animation = initConfigAnimation;
|
|
} // Static
|
|
;
|
|
|
|
Tooltip._jQueryInterface = function _jQueryInterface(config) {
|
|
return this.each(function () {
|
|
var data = $(this).data(DATA_KEY);
|
|
|
|
var _config = typeof config === 'object' && config;
|
|
|
|
if (!data && /dispose|hide/.test(config)) {
|
|
return;
|
|
}
|
|
|
|
if (!data) {
|
|
data = new Tooltip(this, _config);
|
|
$(this).data(DATA_KEY, data);
|
|
}
|
|
|
|
if (typeof config === 'string') {
|
|
if (typeof data[config] === 'undefined') {
|
|
throw new TypeError("No method named \"" + config + "\"");
|
|
}
|
|
|
|
data[config]();
|
|
}
|
|
});
|
|
};
|
|
|
|
_createClass(Tooltip, null, [{
|
|
key: "VERSION",
|
|
get: function get() {
|
|
return VERSION;
|
|
}
|
|
}, {
|
|
key: "Default",
|
|
get: function get() {
|
|
return Default;
|
|
}
|
|
}, {
|
|
key: "NAME",
|
|
get: function get() {
|
|
return NAME;
|
|
}
|
|
}, {
|
|
key: "DATA_KEY",
|
|
get: function get() {
|
|
return DATA_KEY;
|
|
}
|
|
}, {
|
|
key: "Event",
|
|
get: function get() {
|
|
return Event;
|
|
}
|
|
}, {
|
|
key: "EVENT_KEY",
|
|
get: function get() {
|
|
return EVENT_KEY;
|
|
}
|
|
}, {
|
|
key: "DefaultType",
|
|
get: function get() {
|
|
return DefaultType;
|
|
}
|
|
}]);
|
|
|
|
return Tooltip;
|
|
}();
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* jQuery
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
|
|
$.fn[NAME] = Tooltip._jQueryInterface;
|
|
$.fn[NAME].Constructor = Tooltip;
|
|
|
|
$.fn[NAME].noConflict = function () {
|
|
$.fn[NAME] = JQUERY_NO_CONFLICT;
|
|
return Tooltip._jQueryInterface;
|
|
};
|
|
|
|
return Tooltip;
|
|
|
|
}));
|
|
/*!
|
|
* Bootstrap popover.js v4.3.1 (https://getbootstrap.com/)
|
|
* Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
*/
|
|
|
|
(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('jquery'), require('./tooltip.js')) :
|
|
typeof define === 'function' && define.amd ? define(['jquery', './tooltip.js'], factory) :
|
|
(global = global || self, global.Popover = factory(global.jQuery, global.Tooltip));
|
|
}(this, function ($, Tooltip) { 'use strict';
|
|
|
|
$ = $ && $.hasOwnProperty('default') ? $['default'] : $;
|
|
Tooltip = Tooltip && Tooltip.hasOwnProperty('default') ? Tooltip['default'] : Tooltip;
|
|
|
|
function _defineProperties(target, props) {
|
|
for (var i = 0; i < props.length; i++) {
|
|
var descriptor = props[i];
|
|
descriptor.enumerable = descriptor.enumerable || false;
|
|
descriptor.configurable = true;
|
|
if ("value" in descriptor) descriptor.writable = true;
|
|
Object.defineProperty(target, descriptor.key, descriptor);
|
|
}
|
|
}
|
|
|
|
function _createClass(Constructor, protoProps, staticProps) {
|
|
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
|
|
if (staticProps) _defineProperties(Constructor, staticProps);
|
|
return Constructor;
|
|
}
|
|
|
|
function _defineProperty(obj, key, value) {
|
|
if (key in obj) {
|
|
Object.defineProperty(obj, key, {
|
|
value: value,
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true
|
|
});
|
|
} else {
|
|
obj[key] = value;
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
function _objectSpread(target) {
|
|
for (var i = 1; i < arguments.length; i++) {
|
|
var source = arguments[i] != null ? arguments[i] : {};
|
|
var ownKeys = Object.keys(source);
|
|
|
|
if (typeof Object.getOwnPropertySymbols === 'function') {
|
|
ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) {
|
|
return Object.getOwnPropertyDescriptor(source, sym).enumerable;
|
|
}));
|
|
}
|
|
|
|
ownKeys.forEach(function (key) {
|
|
_defineProperty(target, key, source[key]);
|
|
});
|
|
}
|
|
|
|
return target;
|
|
}
|
|
|
|
function _inheritsLoose(subClass, superClass) {
|
|
subClass.prototype = Object.create(superClass.prototype);
|
|
subClass.prototype.constructor = subClass;
|
|
subClass.__proto__ = superClass;
|
|
}
|
|
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* Constants
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
var NAME = 'popover';
|
|
var VERSION = '4.3.1';
|
|
var DATA_KEY = 'bs.popover';
|
|
var EVENT_KEY = "." + DATA_KEY;
|
|
var JQUERY_NO_CONFLICT = $.fn[NAME];
|
|
var CLASS_PREFIX = 'bs-popover';
|
|
var BSCLS_PREFIX_REGEX = new RegExp("(^|\\s)" + CLASS_PREFIX + "\\S+", 'g');
|
|
|
|
var Default = _objectSpread({}, Tooltip.Default, {
|
|
placement: 'right',
|
|
trigger: 'click',
|
|
content: '',
|
|
template: '<div class="popover" role="tooltip">' + '<div class="arrow"></div>' + '<h3 class="popover-header"></h3>' + '<div class="popover-body"></div></div>'
|
|
});
|
|
|
|
var DefaultType = _objectSpread({}, Tooltip.DefaultType, {
|
|
content: '(string|element|function)'
|
|
});
|
|
|
|
var ClassName = {
|
|
FADE: 'fade',
|
|
SHOW: 'show'
|
|
};
|
|
var Selector = {
|
|
TITLE: '.popover-header',
|
|
CONTENT: '.popover-body'
|
|
};
|
|
var Event = {
|
|
HIDE: "hide" + EVENT_KEY,
|
|
HIDDEN: "hidden" + EVENT_KEY,
|
|
SHOW: "show" + EVENT_KEY,
|
|
SHOWN: "shown" + EVENT_KEY,
|
|
INSERTED: "inserted" + EVENT_KEY,
|
|
CLICK: "click" + EVENT_KEY,
|
|
FOCUSIN: "focusin" + EVENT_KEY,
|
|
FOCUSOUT: "focusout" + EVENT_KEY,
|
|
MOUSEENTER: "mouseenter" + EVENT_KEY,
|
|
MOUSELEAVE: "mouseleave" + EVENT_KEY
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* Class Definition
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
};
|
|
|
|
var Popover =
|
|
/*#__PURE__*/
|
|
function (_Tooltip) {
|
|
_inheritsLoose(Popover, _Tooltip);
|
|
|
|
function Popover() {
|
|
return _Tooltip.apply(this, arguments) || this;
|
|
}
|
|
|
|
var _proto = Popover.prototype;
|
|
|
|
// Overrides
|
|
_proto.isWithContent = function isWithContent() {
|
|
return this.getTitle() || this._getContent();
|
|
};
|
|
|
|
_proto.addAttachmentClass = function addAttachmentClass(attachment) {
|
|
$(this.getTipElement()).addClass(CLASS_PREFIX + "-" + attachment);
|
|
};
|
|
|
|
_proto.getTipElement = function getTipElement() {
|
|
this.tip = this.tip || $(this.config.template)[0];
|
|
return this.tip;
|
|
};
|
|
|
|
_proto.setContent = function setContent() {
|
|
var $tip = $(this.getTipElement()); // We use append for html objects to maintain js events
|
|
|
|
this.setElementContent($tip.find(Selector.TITLE), this.getTitle());
|
|
|
|
var content = this._getContent();
|
|
|
|
if (typeof content === 'function') {
|
|
content = content.call(this.element);
|
|
}
|
|
|
|
this.setElementContent($tip.find(Selector.CONTENT), content);
|
|
$tip.removeClass(ClassName.FADE + " " + ClassName.SHOW);
|
|
} // Private
|
|
;
|
|
|
|
_proto._getContent = function _getContent() {
|
|
return this.element.getAttribute('data-content') || this.config.content;
|
|
};
|
|
|
|
_proto._cleanTipClass = function _cleanTipClass() {
|
|
var $tip = $(this.getTipElement());
|
|
var tabClass = $tip.attr('class').match(BSCLS_PREFIX_REGEX);
|
|
|
|
if (tabClass !== null && tabClass.length > 0) {
|
|
$tip.removeClass(tabClass.join(''));
|
|
}
|
|
} // Static
|
|
;
|
|
|
|
Popover._jQueryInterface = function _jQueryInterface(config) {
|
|
return this.each(function () {
|
|
var data = $(this).data(DATA_KEY);
|
|
|
|
var _config = typeof config === 'object' ? config : null;
|
|
|
|
if (!data && /dispose|hide/.test(config)) {
|
|
return;
|
|
}
|
|
|
|
if (!data) {
|
|
data = new Popover(this, _config);
|
|
$(this).data(DATA_KEY, data);
|
|
}
|
|
|
|
if (typeof config === 'string') {
|
|
if (typeof data[config] === 'undefined') {
|
|
throw new TypeError("No method named \"" + config + "\"");
|
|
}
|
|
|
|
data[config]();
|
|
}
|
|
});
|
|
};
|
|
|
|
_createClass(Popover, null, [{
|
|
key: "VERSION",
|
|
// Getters
|
|
get: function get() {
|
|
return VERSION;
|
|
}
|
|
}, {
|
|
key: "Default",
|
|
get: function get() {
|
|
return Default;
|
|
}
|
|
}, {
|
|
key: "NAME",
|
|
get: function get() {
|
|
return NAME;
|
|
}
|
|
}, {
|
|
key: "DATA_KEY",
|
|
get: function get() {
|
|
return DATA_KEY;
|
|
}
|
|
}, {
|
|
key: "Event",
|
|
get: function get() {
|
|
return Event;
|
|
}
|
|
}, {
|
|
key: "EVENT_KEY",
|
|
get: function get() {
|
|
return EVENT_KEY;
|
|
}
|
|
}, {
|
|
key: "DefaultType",
|
|
get: function get() {
|
|
return DefaultType;
|
|
}
|
|
}]);
|
|
|
|
return Popover;
|
|
}(Tooltip);
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* jQuery
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
|
|
$.fn[NAME] = Popover._jQueryInterface;
|
|
$.fn[NAME].Constructor = Popover;
|
|
|
|
$.fn[NAME].noConflict = function () {
|
|
$.fn[NAME] = JQUERY_NO_CONFLICT;
|
|
return Popover._jQueryInterface;
|
|
};
|
|
|
|
return Popover;
|
|
|
|
}));
|
|
/*!
|
|
* Bootstrap scrollspy.js v4.3.1 (https://getbootstrap.com/)
|
|
* Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
*/
|
|
|
|
(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('jquery'), require('./util.js')) :
|
|
typeof define === 'function' && define.amd ? define(['jquery', './util.js'], factory) :
|
|
(global = global || self, global.ScrollSpy = factory(global.jQuery, global.Util));
|
|
}(this, function ($, Util) { 'use strict';
|
|
|
|
$ = $ && $.hasOwnProperty('default') ? $['default'] : $;
|
|
Util = Util && Util.hasOwnProperty('default') ? Util['default'] : Util;
|
|
|
|
function _defineProperties(target, props) {
|
|
for (var i = 0; i < props.length; i++) {
|
|
var descriptor = props[i];
|
|
descriptor.enumerable = descriptor.enumerable || false;
|
|
descriptor.configurable = true;
|
|
if ("value" in descriptor) descriptor.writable = true;
|
|
Object.defineProperty(target, descriptor.key, descriptor);
|
|
}
|
|
}
|
|
|
|
function _createClass(Constructor, protoProps, staticProps) {
|
|
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
|
|
if (staticProps) _defineProperties(Constructor, staticProps);
|
|
return Constructor;
|
|
}
|
|
|
|
function _defineProperty(obj, key, value) {
|
|
if (key in obj) {
|
|
Object.defineProperty(obj, key, {
|
|
value: value,
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true
|
|
});
|
|
} else {
|
|
obj[key] = value;
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
function _objectSpread(target) {
|
|
for (var i = 1; i < arguments.length; i++) {
|
|
var source = arguments[i] != null ? arguments[i] : {};
|
|
var ownKeys = Object.keys(source);
|
|
|
|
if (typeof Object.getOwnPropertySymbols === 'function') {
|
|
ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) {
|
|
return Object.getOwnPropertyDescriptor(source, sym).enumerable;
|
|
}));
|
|
}
|
|
|
|
ownKeys.forEach(function (key) {
|
|
_defineProperty(target, key, source[key]);
|
|
});
|
|
}
|
|
|
|
return target;
|
|
}
|
|
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* Constants
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
var NAME = 'scrollspy';
|
|
var VERSION = '4.3.1';
|
|
var DATA_KEY = 'bs.scrollspy';
|
|
var EVENT_KEY = "." + DATA_KEY;
|
|
var DATA_API_KEY = '.data-api';
|
|
var JQUERY_NO_CONFLICT = $.fn[NAME];
|
|
var Default = {
|
|
offset: 10,
|
|
method: 'auto',
|
|
target: ''
|
|
};
|
|
var DefaultType = {
|
|
offset: 'number',
|
|
method: 'string',
|
|
target: '(string|element)'
|
|
};
|
|
var Event = {
|
|
ACTIVATE: "activate" + EVENT_KEY,
|
|
SCROLL: "scroll" + EVENT_KEY,
|
|
LOAD_DATA_API: "load" + EVENT_KEY + DATA_API_KEY
|
|
};
|
|
var ClassName = {
|
|
DROPDOWN_ITEM: 'dropdown-item',
|
|
DROPDOWN_MENU: 'dropdown-menu',
|
|
ACTIVE: 'active'
|
|
};
|
|
var Selector = {
|
|
DATA_SPY: '[data-spy="scroll"]',
|
|
ACTIVE: '.active',
|
|
NAV_LIST_GROUP: '.nav, .list-group',
|
|
NAV_LINKS: '.nav-link',
|
|
NAV_ITEMS: '.nav-item',
|
|
LIST_ITEMS: '.list-group-item',
|
|
DROPDOWN: '.dropdown',
|
|
DROPDOWN_ITEMS: '.dropdown-item',
|
|
DROPDOWN_TOGGLE: '.dropdown-toggle'
|
|
};
|
|
var OffsetMethod = {
|
|
OFFSET: 'offset',
|
|
POSITION: 'position'
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* Class Definition
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
};
|
|
|
|
var ScrollSpy =
|
|
/*#__PURE__*/
|
|
function () {
|
|
function ScrollSpy(element, config) {
|
|
var _this = this;
|
|
|
|
this._element = element;
|
|
this._scrollElement = element.tagName === 'BODY' ? window : element;
|
|
this._config = this._getConfig(config);
|
|
this._selector = this._config.target + " " + Selector.NAV_LINKS + "," + (this._config.target + " " + Selector.LIST_ITEMS + ",") + (this._config.target + " " + Selector.DROPDOWN_ITEMS);
|
|
this._offsets = [];
|
|
this._targets = [];
|
|
this._activeTarget = null;
|
|
this._scrollHeight = 0;
|
|
$(this._scrollElement).on(Event.SCROLL, function (event) {
|
|
return _this._process(event);
|
|
});
|
|
this.refresh();
|
|
|
|
this._process();
|
|
} // Getters
|
|
|
|
|
|
var _proto = ScrollSpy.prototype;
|
|
|
|
// Public
|
|
_proto.refresh = function refresh() {
|
|
var _this2 = this;
|
|
|
|
var autoMethod = this._scrollElement === this._scrollElement.window ? OffsetMethod.OFFSET : OffsetMethod.POSITION;
|
|
var offsetMethod = this._config.method === 'auto' ? autoMethod : this._config.method;
|
|
var offsetBase = offsetMethod === OffsetMethod.POSITION ? this._getScrollTop() : 0;
|
|
this._offsets = [];
|
|
this._targets = [];
|
|
this._scrollHeight = this._getScrollHeight();
|
|
var targets = [].slice.call(document.querySelectorAll(this._selector));
|
|
targets.map(function (element) {
|
|
var target;
|
|
var targetSelector = Util.getSelectorFromElement(element);
|
|
|
|
if (targetSelector) {
|
|
target = document.querySelector(targetSelector);
|
|
}
|
|
|
|
if (target) {
|
|
var targetBCR = target.getBoundingClientRect();
|
|
|
|
if (targetBCR.width || targetBCR.height) {
|
|
// TODO (fat): remove sketch reliance on jQuery position/offset
|
|
return [$(target)[offsetMethod]().top + offsetBase, targetSelector];
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}).filter(function (item) {
|
|
return item;
|
|
}).sort(function (a, b) {
|
|
return a[0] - b[0];
|
|
}).forEach(function (item) {
|
|
_this2._offsets.push(item[0]);
|
|
|
|
_this2._targets.push(item[1]);
|
|
});
|
|
};
|
|
|
|
_proto.dispose = function dispose() {
|
|
$.removeData(this._element, DATA_KEY);
|
|
$(this._scrollElement).off(EVENT_KEY);
|
|
this._element = null;
|
|
this._scrollElement = null;
|
|
this._config = null;
|
|
this._selector = null;
|
|
this._offsets = null;
|
|
this._targets = null;
|
|
this._activeTarget = null;
|
|
this._scrollHeight = null;
|
|
} // Private
|
|
;
|
|
|
|
_proto._getConfig = function _getConfig(config) {
|
|
config = _objectSpread({}, Default, typeof config === 'object' && config ? config : {});
|
|
|
|
if (typeof config.target !== 'string') {
|
|
var id = $(config.target).attr('id');
|
|
|
|
if (!id) {
|
|
id = Util.getUID(NAME);
|
|
$(config.target).attr('id', id);
|
|
}
|
|
|
|
config.target = "#" + id;
|
|
}
|
|
|
|
Util.typeCheckConfig(NAME, config, DefaultType);
|
|
return config;
|
|
};
|
|
|
|
_proto._getScrollTop = function _getScrollTop() {
|
|
return this._scrollElement === window ? this._scrollElement.pageYOffset : this._scrollElement.scrollTop;
|
|
};
|
|
|
|
_proto._getScrollHeight = function _getScrollHeight() {
|
|
return this._scrollElement.scrollHeight || Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);
|
|
};
|
|
|
|
_proto._getOffsetHeight = function _getOffsetHeight() {
|
|
return this._scrollElement === window ? window.innerHeight : this._scrollElement.getBoundingClientRect().height;
|
|
};
|
|
|
|
_proto._process = function _process() {
|
|
var scrollTop = this._getScrollTop() + this._config.offset;
|
|
|
|
var scrollHeight = this._getScrollHeight();
|
|
|
|
var maxScroll = this._config.offset + scrollHeight - this._getOffsetHeight();
|
|
|
|
if (this._scrollHeight !== scrollHeight) {
|
|
this.refresh();
|
|
}
|
|
|
|
if (scrollTop >= maxScroll) {
|
|
var target = this._targets[this._targets.length - 1];
|
|
|
|
if (this._activeTarget !== target) {
|
|
this._activate(target);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (this._activeTarget && scrollTop < this._offsets[0] && this._offsets[0] > 0) {
|
|
this._activeTarget = null;
|
|
|
|
this._clear();
|
|
|
|
return;
|
|
}
|
|
|
|
var offsetLength = this._offsets.length;
|
|
|
|
for (var i = offsetLength; i--;) {
|
|
var isActiveTarget = this._activeTarget !== this._targets[i] && scrollTop >= this._offsets[i] && (typeof this._offsets[i + 1] === 'undefined' || scrollTop < this._offsets[i + 1]);
|
|
|
|
if (isActiveTarget) {
|
|
this._activate(this._targets[i]);
|
|
}
|
|
}
|
|
};
|
|
|
|
_proto._activate = function _activate(target) {
|
|
this._activeTarget = target;
|
|
|
|
this._clear();
|
|
|
|
var queries = this._selector.split(',').map(function (selector) {
|
|
return selector + "[data-target=\"" + target + "\"]," + selector + "[href=\"" + target + "\"]";
|
|
});
|
|
|
|
var $link = $([].slice.call(document.querySelectorAll(queries.join(','))));
|
|
|
|
if ($link.hasClass(ClassName.DROPDOWN_ITEM)) {
|
|
$link.closest(Selector.DROPDOWN).find(Selector.DROPDOWN_TOGGLE).addClass(ClassName.ACTIVE);
|
|
$link.addClass(ClassName.ACTIVE);
|
|
} else {
|
|
// Set triggered link as active
|
|
$link.addClass(ClassName.ACTIVE); // Set triggered links parents as active
|
|
// With both <ul> and <nav> markup a parent is the previous sibling of any nav ancestor
|
|
|
|
$link.parents(Selector.NAV_LIST_GROUP).prev(Selector.NAV_LINKS + ", " + Selector.LIST_ITEMS).addClass(ClassName.ACTIVE); // Handle special case when .nav-link is inside .nav-item
|
|
|
|
$link.parents(Selector.NAV_LIST_GROUP).prev(Selector.NAV_ITEMS).children(Selector.NAV_LINKS).addClass(ClassName.ACTIVE);
|
|
}
|
|
|
|
$(this._scrollElement).trigger(Event.ACTIVATE, {
|
|
relatedTarget: target
|
|
});
|
|
};
|
|
|
|
_proto._clear = function _clear() {
|
|
[].slice.call(document.querySelectorAll(this._selector)).filter(function (node) {
|
|
return node.classList.contains(ClassName.ACTIVE);
|
|
}).forEach(function (node) {
|
|
return node.classList.remove(ClassName.ACTIVE);
|
|
});
|
|
} // Static
|
|
;
|
|
|
|
ScrollSpy._jQueryInterface = function _jQueryInterface(config) {
|
|
return this.each(function () {
|
|
var data = $(this).data(DATA_KEY);
|
|
|
|
var _config = typeof config === 'object' && config;
|
|
|
|
if (!data) {
|
|
data = new ScrollSpy(this, _config);
|
|
$(this).data(DATA_KEY, data);
|
|
}
|
|
|
|
if (typeof config === 'string') {
|
|
if (typeof data[config] === 'undefined') {
|
|
throw new TypeError("No method named \"" + config + "\"");
|
|
}
|
|
|
|
data[config]();
|
|
}
|
|
});
|
|
};
|
|
|
|
_createClass(ScrollSpy, null, [{
|
|
key: "VERSION",
|
|
get: function get() {
|
|
return VERSION;
|
|
}
|
|
}, {
|
|
key: "Default",
|
|
get: function get() {
|
|
return Default;
|
|
}
|
|
}]);
|
|
|
|
return ScrollSpy;
|
|
}();
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* Data Api implementation
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
|
|
$(window).on(Event.LOAD_DATA_API, function () {
|
|
var scrollSpys = [].slice.call(document.querySelectorAll(Selector.DATA_SPY));
|
|
var scrollSpysLength = scrollSpys.length;
|
|
|
|
for (var i = scrollSpysLength; i--;) {
|
|
var $spy = $(scrollSpys[i]);
|
|
|
|
ScrollSpy._jQueryInterface.call($spy, $spy.data());
|
|
}
|
|
});
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* jQuery
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
$.fn[NAME] = ScrollSpy._jQueryInterface;
|
|
$.fn[NAME].Constructor = ScrollSpy;
|
|
|
|
$.fn[NAME].noConflict = function () {
|
|
$.fn[NAME] = JQUERY_NO_CONFLICT;
|
|
return ScrollSpy._jQueryInterface;
|
|
};
|
|
|
|
return ScrollSpy;
|
|
|
|
}));
|
|
/*!
|
|
* Bootstrap tab.js v4.3.1 (https://getbootstrap.com/)
|
|
* Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
*/
|
|
|
|
(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('jquery'), require('./util.js')) :
|
|
typeof define === 'function' && define.amd ? define(['jquery', './util.js'], factory) :
|
|
(global = global || self, global.Tab = factory(global.jQuery, global.Util));
|
|
}(this, function ($, Util) { 'use strict';
|
|
|
|
$ = $ && $.hasOwnProperty('default') ? $['default'] : $;
|
|
Util = Util && Util.hasOwnProperty('default') ? Util['default'] : Util;
|
|
|
|
function _defineProperties(target, props) {
|
|
for (var i = 0; i < props.length; i++) {
|
|
var descriptor = props[i];
|
|
descriptor.enumerable = descriptor.enumerable || false;
|
|
descriptor.configurable = true;
|
|
if ("value" in descriptor) descriptor.writable = true;
|
|
Object.defineProperty(target, descriptor.key, descriptor);
|
|
}
|
|
}
|
|
|
|
function _createClass(Constructor, protoProps, staticProps) {
|
|
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
|
|
if (staticProps) _defineProperties(Constructor, staticProps);
|
|
return Constructor;
|
|
}
|
|
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* Constants
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
var NAME = 'tab';
|
|
var VERSION = '4.3.1';
|
|
var DATA_KEY = 'bs.tab';
|
|
var EVENT_KEY = "." + DATA_KEY;
|
|
var DATA_API_KEY = '.data-api';
|
|
var JQUERY_NO_CONFLICT = $.fn[NAME];
|
|
var Event = {
|
|
HIDE: "hide" + EVENT_KEY,
|
|
HIDDEN: "hidden" + EVENT_KEY,
|
|
SHOW: "show" + EVENT_KEY,
|
|
SHOWN: "shown" + EVENT_KEY,
|
|
CLICK_DATA_API: "click" + EVENT_KEY + DATA_API_KEY
|
|
};
|
|
var ClassName = {
|
|
DROPDOWN_MENU: 'dropdown-menu',
|
|
ACTIVE: 'active',
|
|
DISABLED: 'disabled',
|
|
FADE: 'fade',
|
|
SHOW: 'show'
|
|
};
|
|
var Selector = {
|
|
DROPDOWN: '.dropdown',
|
|
NAV_LIST_GROUP: '.nav, .list-group',
|
|
ACTIVE: '.active',
|
|
ACTIVE_UL: '> li > .active',
|
|
DATA_TOGGLE: '[data-toggle="tab"], [data-toggle="pill"], [data-toggle="list"]',
|
|
DROPDOWN_TOGGLE: '.dropdown-toggle',
|
|
DROPDOWN_ACTIVE_CHILD: '> .dropdown-menu .active'
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* Class Definition
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
};
|
|
|
|
var Tab =
|
|
/*#__PURE__*/
|
|
function () {
|
|
function Tab(element) {
|
|
this._element = element;
|
|
} // Getters
|
|
|
|
|
|
var _proto = Tab.prototype;
|
|
|
|
// Public
|
|
_proto.show = function show() {
|
|
var _this = this;
|
|
|
|
if (this._element.parentNode && this._element.parentNode.nodeType === Node.ELEMENT_NODE && $(this._element).hasClass(ClassName.ACTIVE) || $(this._element).hasClass(ClassName.DISABLED)) {
|
|
return;
|
|
}
|
|
|
|
var target;
|
|
var previous;
|
|
var listElement = $(this._element).closest(Selector.NAV_LIST_GROUP)[0];
|
|
var selector = Util.getSelectorFromElement(this._element);
|
|
|
|
if (listElement) {
|
|
var itemSelector = listElement.nodeName === 'UL' || listElement.nodeName === 'OL' ? Selector.ACTIVE_UL : Selector.ACTIVE;
|
|
previous = $.makeArray($(listElement).find(itemSelector));
|
|
previous = previous[previous.length - 1];
|
|
}
|
|
|
|
var hideEvent = $.Event(Event.HIDE, {
|
|
relatedTarget: this._element
|
|
});
|
|
var showEvent = $.Event(Event.SHOW, {
|
|
relatedTarget: previous
|
|
});
|
|
|
|
if (previous) {
|
|
$(previous).trigger(hideEvent);
|
|
}
|
|
|
|
$(this._element).trigger(showEvent);
|
|
|
|
if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) {
|
|
return;
|
|
}
|
|
|
|
if (selector) {
|
|
target = document.querySelector(selector);
|
|
}
|
|
|
|
this._activate(this._element, listElement);
|
|
|
|
var complete = function complete() {
|
|
var hiddenEvent = $.Event(Event.HIDDEN, {
|
|
relatedTarget: _this._element
|
|
});
|
|
var shownEvent = $.Event(Event.SHOWN, {
|
|
relatedTarget: previous
|
|
});
|
|
$(previous).trigger(hiddenEvent);
|
|
$(_this._element).trigger(shownEvent);
|
|
};
|
|
|
|
if (target) {
|
|
this._activate(target, target.parentNode, complete);
|
|
} else {
|
|
complete();
|
|
}
|
|
};
|
|
|
|
_proto.dispose = function dispose() {
|
|
$.removeData(this._element, DATA_KEY);
|
|
this._element = null;
|
|
} // Private
|
|
;
|
|
|
|
_proto._activate = function _activate(element, container, callback) {
|
|
var _this2 = this;
|
|
|
|
var activeElements = container && (container.nodeName === 'UL' || container.nodeName === 'OL') ? $(container).find(Selector.ACTIVE_UL) : $(container).children(Selector.ACTIVE);
|
|
var active = activeElements[0];
|
|
var isTransitioning = callback && active && $(active).hasClass(ClassName.FADE);
|
|
|
|
var complete = function complete() {
|
|
return _this2._transitionComplete(element, active, callback);
|
|
};
|
|
|
|
if (active && isTransitioning) {
|
|
var transitionDuration = Util.getTransitionDurationFromElement(active);
|
|
$(active).removeClass(ClassName.SHOW).one(Util.TRANSITION_END, complete).emulateTransitionEnd(transitionDuration);
|
|
} else {
|
|
complete();
|
|
}
|
|
};
|
|
|
|
_proto._transitionComplete = function _transitionComplete(element, active, callback) {
|
|
if (active) {
|
|
$(active).removeClass(ClassName.ACTIVE);
|
|
var dropdownChild = $(active.parentNode).find(Selector.DROPDOWN_ACTIVE_CHILD)[0];
|
|
|
|
if (dropdownChild) {
|
|
$(dropdownChild).removeClass(ClassName.ACTIVE);
|
|
}
|
|
|
|
if (active.getAttribute('role') === 'tab') {
|
|
active.setAttribute('aria-selected', false);
|
|
}
|
|
}
|
|
|
|
$(element).addClass(ClassName.ACTIVE);
|
|
|
|
if (element.getAttribute('role') === 'tab') {
|
|
element.setAttribute('aria-selected', true);
|
|
}
|
|
|
|
Util.reflow(element);
|
|
|
|
if (element.classList.contains(ClassName.FADE)) {
|
|
element.classList.add(ClassName.SHOW);
|
|
}
|
|
|
|
if (element.parentNode && $(element.parentNode).hasClass(ClassName.DROPDOWN_MENU)) {
|
|
var dropdownElement = $(element).closest(Selector.DROPDOWN)[0];
|
|
|
|
if (dropdownElement) {
|
|
var dropdownToggleList = [].slice.call(dropdownElement.querySelectorAll(Selector.DROPDOWN_TOGGLE));
|
|
$(dropdownToggleList).addClass(ClassName.ACTIVE);
|
|
}
|
|
|
|
element.setAttribute('aria-expanded', true);
|
|
}
|
|
|
|
if (callback) {
|
|
callback();
|
|
}
|
|
} // Static
|
|
;
|
|
|
|
Tab._jQueryInterface = function _jQueryInterface(config) {
|
|
return this.each(function () {
|
|
var $this = $(this);
|
|
var data = $this.data(DATA_KEY);
|
|
|
|
if (!data) {
|
|
data = new Tab(this);
|
|
$this.data(DATA_KEY, data);
|
|
}
|
|
|
|
if (typeof config === 'string') {
|
|
if (typeof data[config] === 'undefined') {
|
|
throw new TypeError("No method named \"" + config + "\"");
|
|
}
|
|
|
|
data[config]();
|
|
}
|
|
});
|
|
};
|
|
|
|
_createClass(Tab, null, [{
|
|
key: "VERSION",
|
|
get: function get() {
|
|
return VERSION;
|
|
}
|
|
}]);
|
|
|
|
return Tab;
|
|
}();
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* Data Api implementation
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
|
|
$(document).on(Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (event) {
|
|
event.preventDefault();
|
|
|
|
Tab._jQueryInterface.call($(this), 'show');
|
|
});
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* jQuery
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
$.fn[NAME] = Tab._jQueryInterface;
|
|
$.fn[NAME].Constructor = Tab;
|
|
|
|
$.fn[NAME].noConflict = function () {
|
|
$.fn[NAME] = JQUERY_NO_CONFLICT;
|
|
return Tab._jQueryInterface;
|
|
};
|
|
|
|
return Tab;
|
|
|
|
}));
|
|
/*!
|
|
* Bootstrap toast.js v4.3.1 (https://getbootstrap.com/)
|
|
* Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
|
*/
|
|
|
|
(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('jquery'), require('./util.js')) :
|
|
typeof define === 'function' && define.amd ? define(['jquery', './util.js'], factory) :
|
|
(global = global || self, global.Toast = factory(global.jQuery, global.Util));
|
|
}(this, function ($, Util) { 'use strict';
|
|
|
|
$ = $ && $.hasOwnProperty('default') ? $['default'] : $;
|
|
Util = Util && Util.hasOwnProperty('default') ? Util['default'] : Util;
|
|
|
|
function _defineProperties(target, props) {
|
|
for (var i = 0; i < props.length; i++) {
|
|
var descriptor = props[i];
|
|
descriptor.enumerable = descriptor.enumerable || false;
|
|
descriptor.configurable = true;
|
|
if ("value" in descriptor) descriptor.writable = true;
|
|
Object.defineProperty(target, descriptor.key, descriptor);
|
|
}
|
|
}
|
|
|
|
function _createClass(Constructor, protoProps, staticProps) {
|
|
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
|
|
if (staticProps) _defineProperties(Constructor, staticProps);
|
|
return Constructor;
|
|
}
|
|
|
|
function _defineProperty(obj, key, value) {
|
|
if (key in obj) {
|
|
Object.defineProperty(obj, key, {
|
|
value: value,
|
|
enumerable: true,
|
|
configurable: true,
|
|
writable: true
|
|
});
|
|
} else {
|
|
obj[key] = value;
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
function _objectSpread(target) {
|
|
for (var i = 1; i < arguments.length; i++) {
|
|
var source = arguments[i] != null ? arguments[i] : {};
|
|
var ownKeys = Object.keys(source);
|
|
|
|
if (typeof Object.getOwnPropertySymbols === 'function') {
|
|
ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) {
|
|
return Object.getOwnPropertyDescriptor(source, sym).enumerable;
|
|
}));
|
|
}
|
|
|
|
ownKeys.forEach(function (key) {
|
|
_defineProperty(target, key, source[key]);
|
|
});
|
|
}
|
|
|
|
return target;
|
|
}
|
|
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* Constants
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
var NAME = 'toast';
|
|
var VERSION = '4.3.1';
|
|
var DATA_KEY = 'bs.toast';
|
|
var EVENT_KEY = "." + DATA_KEY;
|
|
var JQUERY_NO_CONFLICT = $.fn[NAME];
|
|
var Event = {
|
|
CLICK_DISMISS: "click.dismiss" + EVENT_KEY,
|
|
HIDE: "hide" + EVENT_KEY,
|
|
HIDDEN: "hidden" + EVENT_KEY,
|
|
SHOW: "show" + EVENT_KEY,
|
|
SHOWN: "shown" + EVENT_KEY
|
|
};
|
|
var ClassName = {
|
|
FADE: 'fade',
|
|
HIDE: 'hide',
|
|
SHOW: 'show',
|
|
SHOWING: 'showing'
|
|
};
|
|
var DefaultType = {
|
|
animation: 'boolean',
|
|
autohide: 'boolean',
|
|
delay: 'number'
|
|
};
|
|
var Default = {
|
|
animation: true,
|
|
autohide: true,
|
|
delay: 500
|
|
};
|
|
var Selector = {
|
|
DATA_DISMISS: '[data-dismiss="toast"]'
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* Class Definition
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
};
|
|
|
|
var Toast =
|
|
/*#__PURE__*/
|
|
function () {
|
|
function Toast(element, config) {
|
|
this._element = element;
|
|
this._config = this._getConfig(config);
|
|
this._timeout = null;
|
|
|
|
this._setListeners();
|
|
} // Getters
|
|
|
|
|
|
var _proto = Toast.prototype;
|
|
|
|
// Public
|
|
_proto.show = function show() {
|
|
var _this = this;
|
|
|
|
$(this._element).trigger(Event.SHOW);
|
|
|
|
if (this._config.animation) {
|
|
this._element.classList.add(ClassName.FADE);
|
|
}
|
|
|
|
var complete = function complete() {
|
|
_this._element.classList.remove(ClassName.SHOWING);
|
|
|
|
_this._element.classList.add(ClassName.SHOW);
|
|
|
|
$(_this._element).trigger(Event.SHOWN);
|
|
|
|
if (_this._config.autohide) {
|
|
_this.hide();
|
|
}
|
|
};
|
|
|
|
this._element.classList.remove(ClassName.HIDE);
|
|
|
|
this._element.classList.add(ClassName.SHOWING);
|
|
|
|
if (this._config.animation) {
|
|
var transitionDuration = Util.getTransitionDurationFromElement(this._element);
|
|
$(this._element).one(Util.TRANSITION_END, complete).emulateTransitionEnd(transitionDuration);
|
|
} else {
|
|
complete();
|
|
}
|
|
};
|
|
|
|
_proto.hide = function hide(withoutTimeout) {
|
|
var _this2 = this;
|
|
|
|
if (!this._element.classList.contains(ClassName.SHOW)) {
|
|
return;
|
|
}
|
|
|
|
$(this._element).trigger(Event.HIDE);
|
|
|
|
if (withoutTimeout) {
|
|
this._close();
|
|
} else {
|
|
this._timeout = setTimeout(function () {
|
|
_this2._close();
|
|
}, this._config.delay);
|
|
}
|
|
};
|
|
|
|
_proto.dispose = function dispose() {
|
|
clearTimeout(this._timeout);
|
|
this._timeout = null;
|
|
|
|
if (this._element.classList.contains(ClassName.SHOW)) {
|
|
this._element.classList.remove(ClassName.SHOW);
|
|
}
|
|
|
|
$(this._element).off(Event.CLICK_DISMISS);
|
|
$.removeData(this._element, DATA_KEY);
|
|
this._element = null;
|
|
this._config = null;
|
|
} // Private
|
|
;
|
|
|
|
_proto._getConfig = function _getConfig(config) {
|
|
config = _objectSpread({}, Default, $(this._element).data(), typeof config === 'object' && config ? config : {});
|
|
Util.typeCheckConfig(NAME, config, this.constructor.DefaultType);
|
|
return config;
|
|
};
|
|
|
|
_proto._setListeners = function _setListeners() {
|
|
var _this3 = this;
|
|
|
|
$(this._element).on(Event.CLICK_DISMISS, Selector.DATA_DISMISS, function () {
|
|
return _this3.hide(true);
|
|
});
|
|
};
|
|
|
|
_proto._close = function _close() {
|
|
var _this4 = this;
|
|
|
|
var complete = function complete() {
|
|
_this4._element.classList.add(ClassName.HIDE);
|
|
|
|
$(_this4._element).trigger(Event.HIDDEN);
|
|
};
|
|
|
|
this._element.classList.remove(ClassName.SHOW);
|
|
|
|
if (this._config.animation) {
|
|
var transitionDuration = Util.getTransitionDurationFromElement(this._element);
|
|
$(this._element).one(Util.TRANSITION_END, complete).emulateTransitionEnd(transitionDuration);
|
|
} else {
|
|
complete();
|
|
}
|
|
} // Static
|
|
;
|
|
|
|
Toast._jQueryInterface = function _jQueryInterface(config) {
|
|
return this.each(function () {
|
|
var $element = $(this);
|
|
var data = $element.data(DATA_KEY);
|
|
|
|
var _config = typeof config === 'object' && config;
|
|
|
|
if (!data) {
|
|
data = new Toast(this, _config);
|
|
$element.data(DATA_KEY, data);
|
|
}
|
|
|
|
if (typeof config === 'string') {
|
|
if (typeof data[config] === 'undefined') {
|
|
throw new TypeError("No method named \"" + config + "\"");
|
|
}
|
|
|
|
data[config](this);
|
|
}
|
|
});
|
|
};
|
|
|
|
_createClass(Toast, null, [{
|
|
key: "VERSION",
|
|
get: function get() {
|
|
return VERSION;
|
|
}
|
|
}, {
|
|
key: "DefaultType",
|
|
get: function get() {
|
|
return DefaultType;
|
|
}
|
|
}, {
|
|
key: "Default",
|
|
get: function get() {
|
|
return Default;
|
|
}
|
|
}]);
|
|
|
|
return Toast;
|
|
}();
|
|
/**
|
|
* ------------------------------------------------------------------------
|
|
* jQuery
|
|
* ------------------------------------------------------------------------
|
|
*/
|
|
|
|
|
|
$.fn[NAME] = Toast._jQueryInterface;
|
|
$.fn[NAME].Constructor = Toast;
|
|
|
|
$.fn[NAME].noConflict = function () {
|
|
$.fn[NAME] = JQUERY_NO_CONFLICT;
|
|
return Toast._jQueryInterface;
|
|
};
|
|
|
|
return Toast;
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
* Chartkick.js
|
|
* Create beautiful charts with one line of JavaScript
|
|
* https://github.com/ankane/chartkick.js
|
|
* v3.2.0
|
|
* MIT License
|
|
*/
|
|
|
|
|
|
(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
|
typeof define === 'function' && define.amd ? define(factory) :
|
|
(global = global || self, global.Chartkick = factory());
|
|
}(this, (function () { 'use strict';
|
|
|
|
function isArray(variable) {
|
|
return Object.prototype.toString.call(variable) === "[object Array]";
|
|
}
|
|
|
|
function isFunction(variable) {
|
|
return variable instanceof Function;
|
|
}
|
|
|
|
function isPlainObject(variable) {
|
|
// protect against prototype pollution, defense 2
|
|
return Object.prototype.toString.call(variable) === "[object Object]" && !isFunction(variable) && variable instanceof Object;
|
|
}
|
|
|
|
// https://github.com/madrobby/zepto/blob/master/src/zepto.js
|
|
function extend(target, source) {
|
|
var key;
|
|
for (key in source) {
|
|
// protect against prototype pollution, defense 1
|
|
if (key === "__proto__") { continue; }
|
|
|
|
if (isPlainObject(source[key]) || isArray(source[key])) {
|
|
if (isPlainObject(source[key]) && !isPlainObject(target[key])) {
|
|
target[key] = {};
|
|
}
|
|
if (isArray(source[key]) && !isArray(target[key])) {
|
|
target[key] = [];
|
|
}
|
|
extend(target[key], source[key]);
|
|
} else if (source[key] !== undefined) {
|
|
target[key] = source[key];
|
|
}
|
|
}
|
|
}
|
|
|
|
function merge(obj1, obj2) {
|
|
var target = {};
|
|
extend(target, obj1);
|
|
extend(target, obj2);
|
|
return target;
|
|
}
|
|
|
|
var DATE_PATTERN = /^(\d\d\d\d)(-)?(\d\d)(-)?(\d\d)$/i;
|
|
|
|
// https://github.com/Do/iso8601.js
|
|
var ISO8601_PATTERN = /(\d\d\d\d)(-)?(\d\d)(-)?(\d\d)(T)?(\d\d)(:)?(\d\d)?(:)?(\d\d)?([.,]\d+)?($|Z|([+-])(\d\d)(:)?(\d\d)?)/i;
|
|
var DECIMAL_SEPARATOR = String(1.5).charAt(1);
|
|
|
|
function parseISO8601(input) {
|
|
var day, hour, matches, milliseconds, minutes, month, offset, result, seconds, type, year;
|
|
type = Object.prototype.toString.call(input);
|
|
if (type === "[object Date]") {
|
|
return input;
|
|
}
|
|
if (type !== "[object String]") {
|
|
return;
|
|
}
|
|
matches = input.match(ISO8601_PATTERN);
|
|
if (matches) {
|
|
year = parseInt(matches[1], 10);
|
|
month = parseInt(matches[3], 10) - 1;
|
|
day = parseInt(matches[5], 10);
|
|
hour = parseInt(matches[7], 10);
|
|
minutes = matches[9] ? parseInt(matches[9], 10) : 0;
|
|
seconds = matches[11] ? parseInt(matches[11], 10) : 0;
|
|
milliseconds = matches[12] ? parseFloat(DECIMAL_SEPARATOR + matches[12].slice(1)) * 1000 : 0;
|
|
result = Date.UTC(year, month, day, hour, minutes, seconds, milliseconds);
|
|
if (matches[13] && matches[14]) {
|
|
offset = matches[15] * 60;
|
|
if (matches[17]) {
|
|
offset += parseInt(matches[17], 10);
|
|
}
|
|
offset *= matches[14] === "-" ? -1 : 1;
|
|
result -= offset * 60 * 1000;
|
|
}
|
|
return new Date(result);
|
|
}
|
|
}
|
|
// end iso8601.js
|
|
|
|
function negativeValues(series) {
|
|
var i, j, data;
|
|
for (i = 0; i < series.length; i++) {
|
|
data = series[i].data;
|
|
for (j = 0; j < data.length; j++) {
|
|
if (data[j][1] < 0) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function toStr(n) {
|
|
return "" + n;
|
|
}
|
|
|
|
function toFloat(n) {
|
|
return parseFloat(n);
|
|
}
|
|
|
|
function toDate(n) {
|
|
var matches, year, month, day;
|
|
if (typeof n !== "object") {
|
|
if (typeof n === "number") {
|
|
n = new Date(n * 1000); // ms
|
|
} else {
|
|
n = toStr(n);
|
|
if ((matches = n.match(DATE_PATTERN))) {
|
|
year = parseInt(matches[1], 10);
|
|
month = parseInt(matches[3], 10) - 1;
|
|
day = parseInt(matches[5], 10);
|
|
return new Date(year, month, day);
|
|
} else { // str
|
|
// try our best to get the str into iso8601
|
|
// TODO be smarter about this
|
|
var str = n.replace(/ /, "T").replace(" ", "").replace("UTC", "Z");
|
|
n = parseISO8601(str) || new Date(n);
|
|
}
|
|
}
|
|
}
|
|
return n;
|
|
}
|
|
|
|
function toArr(n) {
|
|
if (!isArray(n)) {
|
|
var arr = [], i;
|
|
for (i in n) {
|
|
if (n.hasOwnProperty(i)) {
|
|
arr.push([i, n[i]]);
|
|
}
|
|
}
|
|
n = arr;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
function jsOptionsFunc(defaultOptions, hideLegend, setTitle, setMin, setMax, setStacked, setXtitle, setYtitle) {
|
|
return function (chart, opts, chartOptions) {
|
|
var series = chart.data;
|
|
var options = merge({}, defaultOptions);
|
|
options = merge(options, chartOptions || {});
|
|
|
|
if (chart.hideLegend || "legend" in opts) {
|
|
hideLegend(options, opts.legend, chart.hideLegend);
|
|
}
|
|
|
|
if (opts.title) {
|
|
setTitle(options, opts.title);
|
|
}
|
|
|
|
// min
|
|
if ("min" in opts) {
|
|
setMin(options, opts.min);
|
|
} else if (!negativeValues(series)) {
|
|
setMin(options, 0);
|
|
}
|
|
|
|
// max
|
|
if (opts.max) {
|
|
setMax(options, opts.max);
|
|
}
|
|
|
|
if ("stacked" in opts) {
|
|
setStacked(options, opts.stacked);
|
|
}
|
|
|
|
if (opts.colors) {
|
|
options.colors = opts.colors;
|
|
}
|
|
|
|
if (opts.xtitle) {
|
|
setXtitle(options, opts.xtitle);
|
|
}
|
|
|
|
if (opts.ytitle) {
|
|
setYtitle(options, opts.ytitle);
|
|
}
|
|
|
|
// merge library last
|
|
options = merge(options, opts.library || {});
|
|
|
|
return options;
|
|
};
|
|
}
|
|
|
|
function sortByTime(a, b) {
|
|
return a[0].getTime() - b[0].getTime();
|
|
}
|
|
|
|
function sortByNumberSeries(a, b) {
|
|
return a[0] - b[0];
|
|
}
|
|
|
|
function sortByNumber(a, b) {
|
|
return a - b;
|
|
}
|
|
|
|
function isMinute(d) {
|
|
return d.getMilliseconds() === 0 && d.getSeconds() === 0;
|
|
}
|
|
|
|
function isHour(d) {
|
|
return isMinute(d) && d.getMinutes() === 0;
|
|
}
|
|
|
|
function isDay(d) {
|
|
return isHour(d) && d.getHours() === 0;
|
|
}
|
|
|
|
function isWeek(d, dayOfWeek) {
|
|
return isDay(d) && d.getDay() === dayOfWeek;
|
|
}
|
|
|
|
function isMonth(d) {
|
|
return isDay(d) && d.getDate() === 1;
|
|
}
|
|
|
|
function isYear(d) {
|
|
return isMonth(d) && d.getMonth() === 0;
|
|
}
|
|
|
|
function isDate(obj) {
|
|
return !isNaN(toDate(obj)) && toStr(obj).length >= 6;
|
|
}
|
|
|
|
function isNumber(obj) {
|
|
return typeof obj === "number";
|
|
}
|
|
|
|
function formatValue(pre, value, options, axis) {
|
|
pre = pre || "";
|
|
if (options.prefix) {
|
|
if (value < 0) {
|
|
value = value * -1;
|
|
pre += "-";
|
|
}
|
|
pre += options.prefix;
|
|
}
|
|
|
|
var suffix = options.suffix || "";
|
|
var precision = options.precision;
|
|
var round = options.round;
|
|
|
|
if (options.byteScale) {
|
|
var baseValue = axis ? options.byteScale : value;
|
|
if (baseValue >= 1099511627776) {
|
|
value /= 1099511627776;
|
|
suffix = " TB";
|
|
} else if (baseValue >= 1073741824) {
|
|
value /= 1073741824;
|
|
suffix = " GB";
|
|
} else if (baseValue >= 1048576) {
|
|
value /= 1048576;
|
|
suffix = " MB";
|
|
} else if (baseValue >= 1024) {
|
|
value /= 1024;
|
|
suffix = " KB";
|
|
} else {
|
|
suffix = " bytes";
|
|
}
|
|
|
|
if (precision === undefined && round === undefined) {
|
|
precision = 3;
|
|
}
|
|
}
|
|
|
|
if (precision !== undefined && round !== undefined) {
|
|
throw Error("Use either round or precision, not both");
|
|
}
|
|
|
|
if (!axis) {
|
|
if (precision !== undefined) {
|
|
value = value.toPrecision(precision);
|
|
if (!options.zeros) {
|
|
value = parseFloat(value);
|
|
}
|
|
}
|
|
|
|
if (round !== undefined) {
|
|
if (round < 0) {
|
|
var num = Math.pow(10, -1 * round);
|
|
value = parseInt((1.0 * value / num).toFixed(0)) * num;
|
|
} else {
|
|
value = value.toFixed(round);
|
|
if (!options.zeros) {
|
|
value = parseFloat(value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (options.thousands || options.decimal) {
|
|
value = toStr(value);
|
|
var parts = value.split(".");
|
|
value = parts[0];
|
|
if (options.thousands) {
|
|
value = value.replace(/\B(?=(\d{3})+(?!\d))/g, options.thousands);
|
|
}
|
|
if (parts.length > 1) {
|
|
value += (options.decimal || ".") + parts[1];
|
|
}
|
|
}
|
|
|
|
return pre + value + suffix;
|
|
}
|
|
|
|
function seriesOption(chart, series, option) {
|
|
if (option in series) {
|
|
return series[option];
|
|
} else if (option in chart.options) {
|
|
return chart.options[option];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function allZeros(data) {
|
|
var i, j, d;
|
|
for (i = 0; i < data.length; i++) {
|
|
d = data[i].data;
|
|
for (j = 0; j < d.length; j++) {
|
|
if (d[j][1] != 0) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
var baseOptions = {
|
|
maintainAspectRatio: false,
|
|
animation: false,
|
|
tooltips: {
|
|
displayColors: false,
|
|
callbacks: {}
|
|
},
|
|
legend: {},
|
|
title: {fontSize: 20, fontColor: "#333"}
|
|
};
|
|
|
|
var defaultOptions = {
|
|
scales: {
|
|
yAxes: [
|
|
{
|
|
ticks: {
|
|
maxTicksLimit: 4
|
|
},
|
|
scaleLabel: {
|
|
fontSize: 16,
|
|
// fontStyle: "bold",
|
|
fontColor: "#333"
|
|
}
|
|
}
|
|
],
|
|
xAxes: [
|
|
{
|
|
gridLines: {
|
|
drawOnChartArea: false
|
|
},
|
|
scaleLabel: {
|
|
fontSize: 16,
|
|
// fontStyle: "bold",
|
|
fontColor: "#333"
|
|
},
|
|
time: {},
|
|
ticks: {}
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
// http://there4.io/2012/05/02/google-chart-color-list/
|
|
var defaultColors = [
|
|
"#3366CC", "#DC3912", "#FF9900", "#109618", "#990099", "#3B3EAC", "#0099C6",
|
|
"#DD4477", "#66AA00", "#B82E2E", "#316395", "#994499", "#22AA99", "#AAAA11",
|
|
"#6633CC", "#E67300", "#8B0707", "#329262", "#5574A6", "#651067"
|
|
];
|
|
|
|
var hideLegend = function (options, legend, hideLegend) {
|
|
if (legend !== undefined) {
|
|
options.legend.display = !!legend;
|
|
if (legend && legend !== true) {
|
|
options.legend.position = legend;
|
|
}
|
|
} else if (hideLegend) {
|
|
options.legend.display = false;
|
|
}
|
|
};
|
|
|
|
var setTitle = function (options, title) {
|
|
options.title.display = true;
|
|
options.title.text = title;
|
|
};
|
|
|
|
var setMin = function (options, min) {
|
|
if (min !== null) {
|
|
options.scales.yAxes[0].ticks.min = toFloat(min);
|
|
}
|
|
};
|
|
|
|
var setMax = function (options, max) {
|
|
options.scales.yAxes[0].ticks.max = toFloat(max);
|
|
};
|
|
|
|
var setBarMin = function (options, min) {
|
|
if (min !== null) {
|
|
options.scales.xAxes[0].ticks.min = toFloat(min);
|
|
}
|
|
};
|
|
|
|
var setBarMax = function (options, max) {
|
|
options.scales.xAxes[0].ticks.max = toFloat(max);
|
|
};
|
|
|
|
var setStacked = function (options, stacked) {
|
|
options.scales.xAxes[0].stacked = !!stacked;
|
|
options.scales.yAxes[0].stacked = !!stacked;
|
|
};
|
|
|
|
var setXtitle = function (options, title) {
|
|
options.scales.xAxes[0].scaleLabel.display = true;
|
|
options.scales.xAxes[0].scaleLabel.labelString = title;
|
|
};
|
|
|
|
var setYtitle = function (options, title) {
|
|
options.scales.yAxes[0].scaleLabel.display = true;
|
|
options.scales.yAxes[0].scaleLabel.labelString = title;
|
|
};
|
|
|
|
// https://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
|
|
var addOpacity = function(hex, opacity) {
|
|
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
return result ? "rgba(" + parseInt(result[1], 16) + ", " + parseInt(result[2], 16) + ", " + parseInt(result[3], 16) + ", " + opacity + ")" : hex;
|
|
};
|
|
|
|
// check if not null or undefined
|
|
// https://stackoverflow.com/a/27757708/1177228
|
|
var notnull = function(x) {
|
|
return x != null;
|
|
};
|
|
|
|
var setLabelSize = function (chart, data, options) {
|
|
var maxLabelSize = Math.ceil(chart.element.offsetWidth / 4.0 / data.labels.length);
|
|
if (maxLabelSize > 25) {
|
|
maxLabelSize = 25;
|
|
} else if (maxLabelSize < 10) {
|
|
maxLabelSize = 10;
|
|
}
|
|
if (!options.scales.xAxes[0].ticks.callback) {
|
|
options.scales.xAxes[0].ticks.callback = function (value) {
|
|
value = toStr(value);
|
|
if (value.length > maxLabelSize) {
|
|
return value.substring(0, maxLabelSize - 2) + "...";
|
|
} else {
|
|
return value;
|
|
}
|
|
};
|
|
}
|
|
};
|
|
|
|
var setFormatOptions = function(chart, options, chartType) {
|
|
var formatOptions = {
|
|
prefix: chart.options.prefix,
|
|
suffix: chart.options.suffix,
|
|
thousands: chart.options.thousands,
|
|
decimal: chart.options.decimal,
|
|
precision: chart.options.precision,
|
|
round: chart.options.round,
|
|
zeros: chart.options.zeros
|
|
};
|
|
|
|
if (chart.options.bytes) {
|
|
var series = chart.data;
|
|
if (chartType === "pie") {
|
|
series = [{data: series}];
|
|
}
|
|
|
|
// calculate max
|
|
var max = 0;
|
|
for (var i = 0; i < series.length; i++) {
|
|
var s = series[i];
|
|
for (var j = 0; j < s.data.length; j++) {
|
|
if (s.data[j][1] > max) {
|
|
max = s.data[j][1];
|
|
}
|
|
}
|
|
}
|
|
|
|
// calculate scale
|
|
var scale = 1;
|
|
while (max >= 1024) {
|
|
scale *= 1024;
|
|
max /= 1024;
|
|
}
|
|
|
|
// set step size
|
|
formatOptions.byteScale = scale;
|
|
}
|
|
|
|
if (chartType !== "pie") {
|
|
var myAxes = options.scales.yAxes;
|
|
if (chartType === "bar") {
|
|
myAxes = options.scales.xAxes;
|
|
}
|
|
|
|
if (formatOptions.byteScale) {
|
|
if (!myAxes[0].ticks.stepSize) {
|
|
myAxes[0].ticks.stepSize = formatOptions.byteScale / 2;
|
|
}
|
|
if (!myAxes[0].ticks.maxTicksLimit) {
|
|
myAxes[0].ticks.maxTicksLimit = 4;
|
|
}
|
|
}
|
|
|
|
if (!myAxes[0].ticks.callback) {
|
|
myAxes[0].ticks.callback = function (value) {
|
|
return formatValue("", value, formatOptions, true);
|
|
};
|
|
}
|
|
}
|
|
|
|
if (!options.tooltips.callbacks.label) {
|
|
if (chartType === "scatter") {
|
|
options.tooltips.callbacks.label = function (item, data) {
|
|
var label = data.datasets[item.datasetIndex].label || '';
|
|
if (label) {
|
|
label += ': ';
|
|
}
|
|
return label + '(' + item.xLabel + ', ' + item.yLabel + ')';
|
|
};
|
|
} else if (chartType === "bubble") {
|
|
options.tooltips.callbacks.label = function (item, data) {
|
|
var label = data.datasets[item.datasetIndex].label || '';
|
|
if (label) {
|
|
label += ': ';
|
|
}
|
|
var dataPoint = data.datasets[item.datasetIndex].data[item.index];
|
|
return label + '(' + item.xLabel + ', ' + item.yLabel + ', ' + dataPoint.v + ')';
|
|
};
|
|
} else if (chartType === "pie") {
|
|
// need to use separate label for pie charts
|
|
options.tooltips.callbacks.label = function (tooltipItem, data) {
|
|
var dataLabel = data.labels[tooltipItem.index];
|
|
var value = ': ';
|
|
|
|
if (isArray(dataLabel)) {
|
|
// show value on first line of multiline label
|
|
// need to clone because we are changing the value
|
|
dataLabel = dataLabel.slice();
|
|
dataLabel[0] += value;
|
|
} else {
|
|
dataLabel += value;
|
|
}
|
|
|
|
return formatValue(dataLabel, data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index], formatOptions);
|
|
};
|
|
} else {
|
|
var valueLabel = chartType === "bar" ? "xLabel" : "yLabel";
|
|
options.tooltips.callbacks.label = function (tooltipItem, data) {
|
|
var label = data.datasets[tooltipItem.datasetIndex].label || '';
|
|
if (label) {
|
|
label += ': ';
|
|
}
|
|
return formatValue(label, tooltipItem[valueLabel], formatOptions);
|
|
};
|
|
}
|
|
}
|
|
};
|
|
|
|
var jsOptions = jsOptionsFunc(merge(baseOptions, defaultOptions), hideLegend, setTitle, setMin, setMax, setStacked, setXtitle, setYtitle);
|
|
|
|
var createDataTable = function (chart, options, chartType, library) {
|
|
var datasets = [];
|
|
var labels = [];
|
|
|
|
var colors = chart.options.colors || defaultColors;
|
|
|
|
var day = true;
|
|
var week = true;
|
|
var dayOfWeek;
|
|
var month = true;
|
|
var year = true;
|
|
var hour = true;
|
|
var minute = true;
|
|
|
|
var series = chart.data;
|
|
|
|
var max = 0;
|
|
if (chartType === "bubble") {
|
|
for (var i$1 = 0; i$1 < series.length; i$1++) {
|
|
var s$1 = series[i$1];
|
|
for (var j$1 = 0; j$1 < s$1.data.length; j$1++) {
|
|
if (s$1.data[j$1][2] > max) {
|
|
max = s$1.data[j$1][2];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var i, j, s, d, key, rows = [], rows2 = [];
|
|
|
|
if (chartType === "bar" || chartType === "column" || (chart.xtype !== "number" && chart.xtype !== "bubble")) {
|
|
var sortedLabels = [];
|
|
|
|
for (i = 0; i < series.length; i++) {
|
|
s = series[i];
|
|
|
|
for (j = 0; j < s.data.length; j++) {
|
|
d = s.data[j];
|
|
key = chart.xtype == "datetime" ? d[0].getTime() : d[0];
|
|
if (!rows[key]) {
|
|
rows[key] = new Array(series.length);
|
|
}
|
|
rows[key][i] = toFloat(d[1]);
|
|
if (sortedLabels.indexOf(key) === -1) {
|
|
sortedLabels.push(key);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (chart.xtype === "datetime" || chart.xtype === "number") {
|
|
sortedLabels.sort(sortByNumber);
|
|
}
|
|
|
|
for (j = 0; j < series.length; j++) {
|
|
rows2.push([]);
|
|
}
|
|
|
|
var value;
|
|
var k;
|
|
for (k = 0; k < sortedLabels.length; k++) {
|
|
i = sortedLabels[k];
|
|
if (chart.xtype === "datetime") {
|
|
value = new Date(toFloat(i));
|
|
// TODO make this efficient
|
|
day = day && isDay(value);
|
|
if (!dayOfWeek) {
|
|
dayOfWeek = value.getDay();
|
|
}
|
|
week = week && isWeek(value, dayOfWeek);
|
|
month = month && isMonth(value);
|
|
year = year && isYear(value);
|
|
hour = hour && isHour(value);
|
|
minute = minute && isMinute(value);
|
|
} else {
|
|
value = i;
|
|
}
|
|
labels.push(value);
|
|
for (j = 0; j < series.length; j++) {
|
|
// Chart.js doesn't like undefined
|
|
rows2[j].push(rows[i][j] === undefined ? null : rows[i][j]);
|
|
}
|
|
}
|
|
} else {
|
|
for (var i$2 = 0; i$2 < series.length; i$2++) {
|
|
var s$2 = series[i$2];
|
|
var d$1 = [];
|
|
for (var j$2 = 0; j$2 < s$2.data.length; j$2++) {
|
|
var point = {
|
|
x: toFloat(s$2.data[j$2][0]),
|
|
y: toFloat(s$2.data[j$2][1])
|
|
};
|
|
if (chartType === "bubble") {
|
|
point.r = toFloat(s$2.data[j$2][2]) * 20 / max;
|
|
// custom attribute, for tooltip
|
|
point.v = s$2.data[j$2][2];
|
|
}
|
|
d$1.push(point);
|
|
}
|
|
rows2.push(d$1);
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < series.length; i++) {
|
|
s = series[i];
|
|
|
|
var color = s.color || colors[i];
|
|
var backgroundColor = chartType !== "line" ? addOpacity(color, 0.5) : color;
|
|
|
|
var dataset = {
|
|
label: s.name || "",
|
|
data: rows2[i],
|
|
fill: chartType === "area",
|
|
borderColor: color,
|
|
backgroundColor: backgroundColor,
|
|
pointBackgroundColor: color,
|
|
borderWidth: 2,
|
|
pointHoverBackgroundColor: color
|
|
};
|
|
|
|
if (s.stack) {
|
|
dataset.stack = s.stack;
|
|
}
|
|
|
|
var curve = seriesOption(chart, s, "curve");
|
|
if (curve === false) {
|
|
dataset.lineTension = 0;
|
|
}
|
|
|
|
var points = seriesOption(chart, s, "points");
|
|
if (points === false) {
|
|
dataset.pointRadius = 0;
|
|
dataset.pointHitRadius = 5;
|
|
}
|
|
|
|
dataset = merge(dataset, chart.options.dataset || {});
|
|
dataset = merge(dataset, s.library || {});
|
|
dataset = merge(dataset, s.dataset || {});
|
|
|
|
datasets.push(dataset);
|
|
}
|
|
|
|
var xmin = chart.options.xmin;
|
|
var xmax = chart.options.xmax;
|
|
|
|
if (chart.xtype === "datetime") {
|
|
// hacky check for Chart.js >= 2.9.0
|
|
// https://github.com/chartjs/Chart.js/compare/v2.8.0...v2.9.0
|
|
var gte29 = "math" in library.helpers;
|
|
var ticksKey = gte29 ? "ticks" : "time";
|
|
if (notnull(xmin)) {
|
|
options.scales.xAxes[0][ticksKey].min = toDate(xmin).getTime();
|
|
}
|
|
if (notnull(xmax)) {
|
|
options.scales.xAxes[0][ticksKey].max = toDate(xmax).getTime();
|
|
}
|
|
} else if (chart.xtype === "number") {
|
|
if (notnull(xmin)) {
|
|
options.scales.xAxes[0].ticks.min = xmin;
|
|
}
|
|
if (notnull(xmax)) {
|
|
options.scales.xAxes[0].ticks.max = xmax;
|
|
}
|
|
}
|
|
|
|
if (chart.xtype === "datetime" && labels.length > 0) {
|
|
var minTime = (notnull(xmin) ? toDate(xmin) : labels[0]).getTime();
|
|
var maxTime = (notnull(xmax) ? toDate(xmax) : labels[0]).getTime();
|
|
|
|
for (i = 1; i < labels.length; i++) {
|
|
var value$1 = labels[i].getTime();
|
|
if (value$1 < minTime) {
|
|
minTime = value$1;
|
|
}
|
|
if (value$1 > maxTime) {
|
|
maxTime = value$1;
|
|
}
|
|
}
|
|
|
|
var timeDiff = (maxTime - minTime) / (86400 * 1000.0);
|
|
|
|
if (!options.scales.xAxes[0].time.unit) {
|
|
var step;
|
|
if (year || timeDiff > 365 * 10) {
|
|
options.scales.xAxes[0].time.unit = "year";
|
|
step = 365;
|
|
} else if (month || timeDiff > 30 * 10) {
|
|
options.scales.xAxes[0].time.unit = "month";
|
|
step = 30;
|
|
} else if (day || timeDiff > 10) {
|
|
options.scales.xAxes[0].time.unit = "day";
|
|
step = 1;
|
|
} else if (hour || timeDiff > 0.5) {
|
|
options.scales.xAxes[0].time.displayFormats = {hour: "MMM D, h a"};
|
|
options.scales.xAxes[0].time.unit = "hour";
|
|
step = 1 / 24.0;
|
|
} else if (minute) {
|
|
options.scales.xAxes[0].time.displayFormats = {minute: "h:mm a"};
|
|
options.scales.xAxes[0].time.unit = "minute";
|
|
step = 1 / 24.0 / 60.0;
|
|
}
|
|
|
|
if (step && timeDiff > 0) {
|
|
var unitStepSize = Math.ceil(timeDiff / step / (chart.element.offsetWidth / 100.0));
|
|
if (week && step === 1) {
|
|
unitStepSize = Math.ceil(unitStepSize / 7.0) * 7;
|
|
}
|
|
options.scales.xAxes[0].time.unitStepSize = unitStepSize;
|
|
}
|
|
}
|
|
|
|
if (!options.scales.xAxes[0].time.tooltipFormat) {
|
|
if (day) {
|
|
options.scales.xAxes[0].time.tooltipFormat = "ll";
|
|
} else if (hour) {
|
|
options.scales.xAxes[0].time.tooltipFormat = "MMM D, h a";
|
|
} else if (minute) {
|
|
options.scales.xAxes[0].time.tooltipFormat = "h:mm a";
|
|
}
|
|
}
|
|
}
|
|
|
|
var data = {
|
|
labels: labels,
|
|
datasets: datasets
|
|
};
|
|
|
|
return data;
|
|
};
|
|
|
|
var defaultExport = function defaultExport(library) {
|
|
this.name = "chartjs";
|
|
this.library = library;
|
|
};
|
|
|
|
defaultExport.prototype.renderLineChart = function renderLineChart (chart, chartType) {
|
|
var chartOptions = {};
|
|
// fix for https://github.com/chartjs/Chart.js/issues/2441
|
|
if (!chart.options.max && allZeros(chart.data)) {
|
|
chartOptions.max = 1;
|
|
}
|
|
|
|
var options = jsOptions(chart, merge(chartOptions, chart.options));
|
|
setFormatOptions(chart, options, chartType);
|
|
|
|
var data = createDataTable(chart, options, chartType || "line", this.library);
|
|
|
|
if (chart.xtype === "number") {
|
|
options.scales.xAxes[0].type = "linear";
|
|
options.scales.xAxes[0].position = "bottom";
|
|
} else {
|
|
options.scales.xAxes[0].type = chart.xtype === "string" ? "category" : "time";
|
|
}
|
|
|
|
this.drawChart(chart, "line", data, options);
|
|
};
|
|
|
|
defaultExport.prototype.renderPieChart = function renderPieChart (chart) {
|
|
var options = merge({}, baseOptions);
|
|
if (chart.options.donut) {
|
|
options.cutoutPercentage = 50;
|
|
}
|
|
|
|
if ("legend" in chart.options) {
|
|
hideLegend(options, chart.options.legend);
|
|
}
|
|
|
|
if (chart.options.title) {
|
|
setTitle(options, chart.options.title);
|
|
}
|
|
|
|
options = merge(options, chart.options.library || {});
|
|
setFormatOptions(chart, options, "pie");
|
|
|
|
var labels = [];
|
|
var values = [];
|
|
for (var i = 0; i < chart.data.length; i++) {
|
|
var point = chart.data[i];
|
|
labels.push(point[0]);
|
|
values.push(point[1]);
|
|
}
|
|
|
|
var dataset = {
|
|
data: values,
|
|
backgroundColor: chart.options.colors || defaultColors
|
|
};
|
|
dataset = merge(dataset, chart.options.dataset || {});
|
|
|
|
var data = {
|
|
labels: labels,
|
|
datasets: [dataset]
|
|
};
|
|
|
|
this.drawChart(chart, "pie", data, options);
|
|
};
|
|
|
|
defaultExport.prototype.renderColumnChart = function renderColumnChart (chart, chartType) {
|
|
var options;
|
|
if (chartType === "bar") {
|
|
var barOptions = merge(baseOptions, defaultOptions);
|
|
delete barOptions.scales.yAxes[0].ticks.maxTicksLimit;
|
|
options = jsOptionsFunc(barOptions, hideLegend, setTitle, setBarMin, setBarMax, setStacked, setXtitle, setYtitle)(chart, chart.options);
|
|
} else {
|
|
options = jsOptions(chart, chart.options);
|
|
}
|
|
setFormatOptions(chart, options, chartType);
|
|
var data = createDataTable(chart, options, "column", this.library);
|
|
if (chartType !== "bar") {
|
|
setLabelSize(chart, data, options);
|
|
}
|
|
this.drawChart(chart, (chartType === "bar" ? "horizontalBar" : "bar"), data, options);
|
|
};
|
|
|
|
defaultExport.prototype.renderAreaChart = function renderAreaChart (chart) {
|
|
this.renderLineChart(chart, "area");
|
|
};
|
|
|
|
defaultExport.prototype.renderBarChart = function renderBarChart (chart) {
|
|
this.renderColumnChart(chart, "bar");
|
|
};
|
|
|
|
defaultExport.prototype.renderScatterChart = function renderScatterChart (chart, chartType) {
|
|
chartType = chartType || "scatter";
|
|
|
|
var options = jsOptions(chart, chart.options);
|
|
setFormatOptions(chart, options, chartType);
|
|
|
|
if (!("showLines" in options)) {
|
|
options.showLines = false;
|
|
}
|
|
|
|
var data = createDataTable(chart, options, chartType, this.library);
|
|
|
|
options.scales.xAxes[0].type = "linear";
|
|
options.scales.xAxes[0].position = "bottom";
|
|
|
|
this.drawChart(chart, chartType, data, options);
|
|
};
|
|
|
|
defaultExport.prototype.renderBubbleChart = function renderBubbleChart (chart) {
|
|
this.renderScatterChart(chart, "bubble");
|
|
};
|
|
|
|
defaultExport.prototype.destroy = function destroy (chart) {
|
|
if (chart.chart) {
|
|
chart.chart.destroy();
|
|
}
|
|
};
|
|
|
|
defaultExport.prototype.drawChart = function drawChart (chart, type, data, options) {
|
|
this.destroy(chart);
|
|
|
|
var chartOptions = {
|
|
type: type,
|
|
data: data,
|
|
options: options
|
|
};
|
|
|
|
if (chart.options.code) {
|
|
window.console.log("new Chart(ctx, " + JSON.stringify(chartOptions) + ");");
|
|
}
|
|
|
|
chart.element.innerHTML = "<canvas></canvas>";
|
|
var ctx = chart.element.getElementsByTagName("CANVAS")[0];
|
|
chart.chart = new this.library(ctx, chartOptions);
|
|
};
|
|
|
|
var defaultOptions$1 = {
|
|
chart: {},
|
|
xAxis: {
|
|
title: {
|
|
text: null
|
|
},
|
|
labels: {
|
|
style: {
|
|
fontSize: "12px"
|
|
}
|
|
}
|
|
},
|
|
yAxis: {
|
|
title: {
|
|
text: null
|
|
},
|
|
labels: {
|
|
style: {
|
|
fontSize: "12px"
|
|
}
|
|
}
|
|
},
|
|
title: {
|
|
text: null
|
|
},
|
|
credits: {
|
|
enabled: false
|
|
},
|
|
legend: {
|
|
borderWidth: 0
|
|
},
|
|
tooltip: {
|
|
style: {
|
|
fontSize: "12px"
|
|
}
|
|
},
|
|
plotOptions: {
|
|
areaspline: {},
|
|
area: {},
|
|
series: {
|
|
marker: {}
|
|
}
|
|
}
|
|
};
|
|
|
|
var hideLegend$1 = function (options, legend, hideLegend) {
|
|
if (legend !== undefined) {
|
|
options.legend.enabled = !!legend;
|
|
if (legend && legend !== true) {
|
|
if (legend === "top" || legend === "bottom") {
|
|
options.legend.verticalAlign = legend;
|
|
} else {
|
|
options.legend.layout = "vertical";
|
|
options.legend.verticalAlign = "middle";
|
|
options.legend.align = legend;
|
|
}
|
|
}
|
|
} else if (hideLegend) {
|
|
options.legend.enabled = false;
|
|
}
|
|
};
|
|
|
|
var setTitle$1 = function (options, title) {
|
|
options.title.text = title;
|
|
};
|
|
|
|
var setMin$1 = function (options, min) {
|
|
options.yAxis.min = min;
|
|
};
|
|
|
|
var setMax$1 = function (options, max) {
|
|
options.yAxis.max = max;
|
|
};
|
|
|
|
var setStacked$1 = function (options, stacked) {
|
|
var stackedValue = stacked ? (stacked === true ? "normal" : stacked) : null;
|
|
options.plotOptions.series.stacking = stackedValue;
|
|
options.plotOptions.area.stacking = stackedValue;
|
|
options.plotOptions.areaspline.stacking = stackedValue;
|
|
};
|
|
|
|
var setXtitle$1 = function (options, title) {
|
|
options.xAxis.title.text = title;
|
|
};
|
|
|
|
var setYtitle$1 = function (options, title) {
|
|
options.yAxis.title.text = title;
|
|
};
|
|
|
|
var jsOptions$1 = jsOptionsFunc(defaultOptions$1, hideLegend$1, setTitle$1, setMin$1, setMax$1, setStacked$1, setXtitle$1, setYtitle$1);
|
|
|
|
var setFormatOptions$1 = function(chart, options, chartType) {
|
|
var formatOptions = {
|
|
prefix: chart.options.prefix,
|
|
suffix: chart.options.suffix,
|
|
thousands: chart.options.thousands,
|
|
decimal: chart.options.decimal,
|
|
precision: chart.options.precision,
|
|
round: chart.options.round,
|
|
zeros: chart.options.zeros
|
|
};
|
|
|
|
if (chartType !== "pie" && !options.yAxis.labels.formatter) {
|
|
options.yAxis.labels.formatter = function () {
|
|
return formatValue("", this.value, formatOptions);
|
|
};
|
|
}
|
|
|
|
if (!options.tooltip.pointFormatter) {
|
|
options.tooltip.pointFormatter = function () {
|
|
return '<span style="color:' + this.color + '">\u25CF</span> ' + formatValue(this.series.name + ': <b>', this.y, formatOptions) + '</b><br/>';
|
|
};
|
|
}
|
|
};
|
|
|
|
var defaultExport$1 = function defaultExport(library) {
|
|
this.name = "highcharts";
|
|
this.library = library;
|
|
};
|
|
|
|
defaultExport$1.prototype.renderLineChart = function renderLineChart (chart, chartType) {
|
|
chartType = chartType || "spline";
|
|
var chartOptions = {};
|
|
if (chartType === "areaspline") {
|
|
chartOptions = {
|
|
plotOptions: {
|
|
areaspline: {
|
|
stacking: "normal"
|
|
},
|
|
area: {
|
|
stacking: "normal"
|
|
},
|
|
series: {
|
|
marker: {
|
|
enabled: false
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
if (chart.options.curve === false) {
|
|
if (chartType === "areaspline") {
|
|
chartType = "area";
|
|
} else if (chartType === "spline") {
|
|
chartType = "line";
|
|
}
|
|
}
|
|
|
|
var options = jsOptions$1(chart, chart.options, chartOptions), data, i, j;
|
|
options.xAxis.type = chart.xtype === "string" ? "category" : (chart.xtype === "number" ? "linear" : "datetime");
|
|
if (!options.chart.type) {
|
|
options.chart.type = chartType;
|
|
}
|
|
setFormatOptions$1(chart, options, chartType);
|
|
|
|
var series = chart.data;
|
|
for (i = 0; i < series.length; i++) {
|
|
series[i].name = series[i].name || "Value";
|
|
data = series[i].data;
|
|
if (chart.xtype === "datetime") {
|
|
for (j = 0; j < data.length; j++) {
|
|
data[j][0] = data[j][0].getTime();
|
|
}
|
|
}
|
|
series[i].marker = {symbol: "circle"};
|
|
if (chart.options.points === false) {
|
|
series[i].marker.enabled = false;
|
|
}
|
|
}
|
|
|
|
this.drawChart(chart, series, options);
|
|
};
|
|
|
|
defaultExport$1.prototype.renderScatterChart = function renderScatterChart (chart) {
|
|
var options = jsOptions$1(chart, chart.options, {});
|
|
options.chart.type = "scatter";
|
|
this.drawChart(chart, chart.data, options);
|
|
};
|
|
|
|
defaultExport$1.prototype.renderPieChart = function renderPieChart (chart) {
|
|
var chartOptions = merge(defaultOptions$1, {});
|
|
|
|
if (chart.options.colors) {
|
|
chartOptions.colors = chart.options.colors;
|
|
}
|
|
if (chart.options.donut) {
|
|
chartOptions.plotOptions = {pie: {innerSize: "50%"}};
|
|
}
|
|
|
|
if ("legend" in chart.options) {
|
|
hideLegend$1(chartOptions, chart.options.legend);
|
|
}
|
|
|
|
if (chart.options.title) {
|
|
setTitle$1(chartOptions, chart.options.title);
|
|
}
|
|
|
|
var options = merge(chartOptions, chart.options.library || {});
|
|
setFormatOptions$1(chart, options, "pie");
|
|
var series = [{
|
|
type: "pie",
|
|
name: chart.options.label || "Value",
|
|
data: chart.data
|
|
}];
|
|
|
|
this.drawChart(chart, series, options);
|
|
};
|
|
|
|
defaultExport$1.prototype.renderColumnChart = function renderColumnChart (chart, chartType) {
|
|
chartType = chartType || "column";
|
|
var series = chart.data;
|
|
var options = jsOptions$1(chart, chart.options), i, j, s, d, rows = [], categories = [];
|
|
options.chart.type = chartType;
|
|
setFormatOptions$1(chart, options, chartType);
|
|
|
|
for (i = 0; i < series.length; i++) {
|
|
s = series[i];
|
|
|
|
for (j = 0; j < s.data.length; j++) {
|
|
d = s.data[j];
|
|
if (!rows[d[0]]) {
|
|
rows[d[0]] = new Array(series.length);
|
|
categories.push(d[0]);
|
|
}
|
|
rows[d[0]][i] = d[1];
|
|
}
|
|
}
|
|
|
|
if (chart.xtype === "number") {
|
|
categories.sort(sortByNumber);
|
|
}
|
|
|
|
options.xAxis.categories = categories;
|
|
|
|
var newSeries = [], d2;
|
|
for (i = 0; i < series.length; i++) {
|
|
d = [];
|
|
for (j = 0; j < categories.length; j++) {
|
|
d.push(rows[categories[j]][i] || 0);
|
|
}
|
|
|
|
d2 = {
|
|
name: series[i].name || "Value",
|
|
data: d
|
|
};
|
|
if (series[i].stack) {
|
|
d2.stack = series[i].stack;
|
|
}
|
|
|
|
newSeries.push(d2);
|
|
}
|
|
|
|
this.drawChart(chart, newSeries, options);
|
|
};
|
|
|
|
defaultExport$1.prototype.renderBarChart = function renderBarChart (chart) {
|
|
this.renderColumnChart(chart, "bar");
|
|
};
|
|
|
|
defaultExport$1.prototype.renderAreaChart = function renderAreaChart (chart) {
|
|
this.renderLineChart(chart, "areaspline");
|
|
};
|
|
|
|
defaultExport$1.prototype.destroy = function destroy (chart) {
|
|
if (chart.chart) {
|
|
chart.chart.destroy();
|
|
}
|
|
};
|
|
|
|
defaultExport$1.prototype.drawChart = function drawChart (chart, data, options) {
|
|
this.destroy(chart);
|
|
|
|
options.chart.renderTo = chart.element.id;
|
|
options.series = data;
|
|
|
|
if (chart.options.code) {
|
|
window.console.log("new Highcharts.Chart(" + JSON.stringify(options) + ");");
|
|
}
|
|
|
|
chart.chart = new this.library.Chart(options);
|
|
};
|
|
|
|
var loaded = {};
|
|
var callbacks = [];
|
|
|
|
// Set chart options
|
|
var defaultOptions$2 = {
|
|
chartArea: {},
|
|
fontName: "'Lucida Grande', 'Lucida Sans Unicode', Verdana, Arial, Helvetica, sans-serif",
|
|
pointSize: 6,
|
|
legend: {
|
|
textStyle: {
|
|
fontSize: 12,
|
|
color: "#444"
|
|
},
|
|
alignment: "center",
|
|
position: "right"
|
|
},
|
|
curveType: "function",
|
|
hAxis: {
|
|
textStyle: {
|
|
color: "#666",
|
|
fontSize: 12
|
|
},
|
|
titleTextStyle: {},
|
|
gridlines: {
|
|
color: "transparent"
|
|
},
|
|
baselineColor: "#ccc",
|
|
viewWindow: {}
|
|
},
|
|
vAxis: {
|
|
textStyle: {
|
|
color: "#666",
|
|
fontSize: 12
|
|
},
|
|
titleTextStyle: {},
|
|
baselineColor: "#ccc",
|
|
viewWindow: {}
|
|
},
|
|
tooltip: {
|
|
textStyle: {
|
|
color: "#666",
|
|
fontSize: 12
|
|
}
|
|
}
|
|
};
|
|
|
|
var hideLegend$2 = function (options, legend, hideLegend) {
|
|
if (legend !== undefined) {
|
|
var position;
|
|
if (!legend) {
|
|
position = "none";
|
|
} else if (legend === true) {
|
|
position = "right";
|
|
} else {
|
|
position = legend;
|
|
}
|
|
options.legend.position = position;
|
|
} else if (hideLegend) {
|
|
options.legend.position = "none";
|
|
}
|
|
};
|
|
|
|
var setTitle$2 = function (options, title) {
|
|
options.title = title;
|
|
options.titleTextStyle = {color: "#333", fontSize: "20px"};
|
|
};
|
|
|
|
var setMin$2 = function (options, min) {
|
|
options.vAxis.viewWindow.min = min;
|
|
};
|
|
|
|
var setMax$2 = function (options, max) {
|
|
options.vAxis.viewWindow.max = max;
|
|
};
|
|
|
|
var setBarMin$1 = function (options, min) {
|
|
options.hAxis.viewWindow.min = min;
|
|
};
|
|
|
|
var setBarMax$1 = function (options, max) {
|
|
options.hAxis.viewWindow.max = max;
|
|
};
|
|
|
|
var setStacked$2 = function (options, stacked) {
|
|
options.isStacked = stacked ? stacked : false;
|
|
};
|
|
|
|
var setXtitle$2 = function (options, title) {
|
|
options.hAxis.title = title;
|
|
options.hAxis.titleTextStyle.italic = false;
|
|
};
|
|
|
|
var setYtitle$2 = function (options, title) {
|
|
options.vAxis.title = title;
|
|
options.vAxis.titleTextStyle.italic = false;
|
|
};
|
|
|
|
var jsOptions$2 = jsOptionsFunc(defaultOptions$2, hideLegend$2, setTitle$2, setMin$2, setMax$2, setStacked$2, setXtitle$2, setYtitle$2);
|
|
|
|
var resize = function (callback) {
|
|
if (window.attachEvent) {
|
|
window.attachEvent("onresize", callback);
|
|
} else if (window.addEventListener) {
|
|
window.addEventListener("resize", callback, true);
|
|
}
|
|
callback();
|
|
};
|
|
|
|
var defaultExport$2 = function defaultExport(library) {
|
|
this.name = "google";
|
|
this.library = library;
|
|
};
|
|
|
|
defaultExport$2.prototype.renderLineChart = function renderLineChart (chart) {
|
|
var this$1 = this;
|
|
|
|
this.waitForLoaded(chart, function () {
|
|
var chartOptions = {};
|
|
|
|
if (chart.options.curve === false) {
|
|
chartOptions.curveType = "none";
|
|
}
|
|
|
|
if (chart.options.points === false) {
|
|
chartOptions.pointSize = 0;
|
|
}
|
|
|
|
var options = jsOptions$2(chart, chart.options, chartOptions);
|
|
var data = this$1.createDataTable(chart.data, chart.xtype);
|
|
|
|
this$1.drawChart(chart, "LineChart", data, options);
|
|
});
|
|
};
|
|
|
|
defaultExport$2.prototype.renderPieChart = function renderPieChart (chart) {
|
|
var this$1 = this;
|
|
|
|
this.waitForLoaded(chart, function () {
|
|
var chartOptions = {
|
|
chartArea: {
|
|
top: "10%",
|
|
height: "80%"
|
|
},
|
|
legend: {}
|
|
};
|
|
if (chart.options.colors) {
|
|
chartOptions.colors = chart.options.colors;
|
|
}
|
|
if (chart.options.donut) {
|
|
chartOptions.pieHole = 0.5;
|
|
}
|
|
if ("legend" in chart.options) {
|
|
hideLegend$2(chartOptions, chart.options.legend);
|
|
}
|
|
if (chart.options.title) {
|
|
setTitle$2(chartOptions, chart.options.title);
|
|
}
|
|
var options = merge(merge(defaultOptions$2, chartOptions), chart.options.library || {});
|
|
|
|
var data = new this$1.library.visualization.DataTable();
|
|
data.addColumn("string", "");
|
|
data.addColumn("number", "Value");
|
|
data.addRows(chart.data);
|
|
|
|
this$1.drawChart(chart, "PieChart", data, options);
|
|
});
|
|
};
|
|
|
|
defaultExport$2.prototype.renderColumnChart = function renderColumnChart (chart) {
|
|
var this$1 = this;
|
|
|
|
this.waitForLoaded(chart, function () {
|
|
var options = jsOptions$2(chart, chart.options);
|
|
var data = this$1.createDataTable(chart.data, chart.xtype);
|
|
|
|
this$1.drawChart(chart, "ColumnChart", data, options);
|
|
});
|
|
};
|
|
|
|
defaultExport$2.prototype.renderBarChart = function renderBarChart (chart) {
|
|
var this$1 = this;
|
|
|
|
this.waitForLoaded(chart, function () {
|
|
var chartOptions = {
|
|
hAxis: {
|
|
gridlines: {
|
|
color: "#ccc"
|
|
}
|
|
}
|
|
};
|
|
var options = jsOptionsFunc(defaultOptions$2, hideLegend$2, setTitle$2, setBarMin$1, setBarMax$1, setStacked$2, setXtitle$2, setYtitle$2)(chart, chart.options, chartOptions);
|
|
var data = this$1.createDataTable(chart.data, chart.xtype);
|
|
|
|
this$1.drawChart(chart, "BarChart", data, options);
|
|
});
|
|
};
|
|
|
|
defaultExport$2.prototype.renderAreaChart = function renderAreaChart (chart) {
|
|
var this$1 = this;
|
|
|
|
this.waitForLoaded(chart, function () {
|
|
var chartOptions = {
|
|
isStacked: true,
|
|
pointSize: 0,
|
|
areaOpacity: 0.5
|
|
};
|
|
|
|
var options = jsOptions$2(chart, chart.options, chartOptions);
|
|
var data = this$1.createDataTable(chart.data, chart.xtype);
|
|
|
|
this$1.drawChart(chart, "AreaChart", data, options);
|
|
});
|
|
};
|
|
|
|
defaultExport$2.prototype.renderGeoChart = function renderGeoChart (chart) {
|
|
var this$1 = this;
|
|
|
|
this.waitForLoaded(chart, function () {
|
|
var chartOptions = {
|
|
legend: "none",
|
|
colorAxis: {
|
|
colors: chart.options.colors || ["#f6c7b6", "#ce502d"]
|
|
}
|
|
};
|
|
var options = merge(merge(defaultOptions$2, chartOptions), chart.options.library || {});
|
|
|
|
var data = new this$1.library.visualization.DataTable();
|
|
data.addColumn("string", "");
|
|
data.addColumn("number", chart.options.label || "Value");
|
|
data.addRows(chart.data);
|
|
|
|
this$1.drawChart(chart, "GeoChart", data, options);
|
|
});
|
|
};
|
|
|
|
defaultExport$2.prototype.renderScatterChart = function renderScatterChart (chart) {
|
|
var this$1 = this;
|
|
|
|
this.waitForLoaded(chart, function () {
|
|
var chartOptions = {};
|
|
var options = jsOptions$2(chart, chart.options, chartOptions);
|
|
|
|
var series = chart.data, rows2 = [], i, j, data, d;
|
|
for (i = 0; i < series.length; i++) {
|
|
series[i].name = series[i].name || "Value";
|
|
d = series[i].data;
|
|
for (j = 0; j < d.length; j++) {
|
|
var row = new Array(series.length + 1);
|
|
row[0] = d[j][0];
|
|
row[i + 1] = d[j][1];
|
|
rows2.push(row);
|
|
}
|
|
}
|
|
|
|
data = new this$1.library.visualization.DataTable();
|
|
data.addColumn("number", "");
|
|
for (i = 0; i < series.length; i++) {
|
|
data.addColumn("number", series[i].name);
|
|
}
|
|
data.addRows(rows2);
|
|
|
|
this$1.drawChart(chart, "ScatterChart", data, options);
|
|
});
|
|
};
|
|
|
|
defaultExport$2.prototype.renderTimeline = function renderTimeline (chart) {
|
|
var this$1 = this;
|
|
|
|
this.waitForLoaded(chart, "timeline", function () {
|
|
var chartOptions = {
|
|
legend: "none"
|
|
};
|
|
|
|
if (chart.options.colors) {
|
|
chartOptions.colors = chart.options.colors;
|
|
}
|
|
var options = merge(merge(defaultOptions$2, chartOptions), chart.options.library || {});
|
|
|
|
var data = new this$1.library.visualization.DataTable();
|
|
data.addColumn({type: "string", id: "Name"});
|
|
data.addColumn({type: "date", id: "Start"});
|
|
data.addColumn({type: "date", id: "End"});
|
|
data.addRows(chart.data);
|
|
|
|
chart.element.style.lineHeight = "normal";
|
|
|
|
this$1.drawChart(chart, "Timeline", data, options);
|
|
});
|
|
};
|
|
|
|
defaultExport$2.prototype.destroy = function destroy (chart) {
|
|
if (chart.chart) {
|
|
chart.chart.clearChart();
|
|
}
|
|
};
|
|
|
|
defaultExport$2.prototype.drawChart = function drawChart (chart, type, data, options) {
|
|
this.destroy(chart);
|
|
|
|
if (chart.options.code) {
|
|
window.console.log("var data = new google.visualization.DataTable(" + data.toJSON() + ");\nvar chart = new google.visualization." + type + "(element);\nchart.draw(data, " + JSON.stringify(options) + ");");
|
|
}
|
|
|
|
chart.chart = new this.library.visualization[type](chart.element);
|
|
resize(function () {
|
|
chart.chart.draw(data, options);
|
|
});
|
|
};
|
|
|
|
defaultExport$2.prototype.waitForLoaded = function waitForLoaded (chart, pack, callback) {
|
|
var this$1 = this;
|
|
|
|
if (!callback) {
|
|
callback = pack;
|
|
pack = "corechart";
|
|
}
|
|
|
|
callbacks.push({pack: pack, callback: callback});
|
|
|
|
if (loaded[pack]) {
|
|
this.runCallbacks();
|
|
} else {
|
|
loaded[pack] = true;
|
|
|
|
// https://groups.google.com/forum/#!topic/google-visualization-api/fMKJcyA2yyI
|
|
var loadOptions = {
|
|
packages: [pack],
|
|
callback: function () { this$1.runCallbacks(); }
|
|
};
|
|
var config = chart.__config();
|
|
if (config.language) {
|
|
loadOptions.language = config.language;
|
|
}
|
|
if (pack === "corechart" && config.mapsApiKey) {
|
|
loadOptions.mapsApiKey = config.mapsApiKey;
|
|
}
|
|
|
|
this.library.charts.load("current", loadOptions);
|
|
}
|
|
};
|
|
|
|
defaultExport$2.prototype.runCallbacks = function runCallbacks () {
|
|
var cb, call;
|
|
for (var i = 0; i < callbacks.length; i++) {
|
|
cb = callbacks[i];
|
|
call = this.library.visualization && ((cb.pack === "corechart" && this.library.visualization.LineChart) || (cb.pack === "timeline" && this.library.visualization.Timeline));
|
|
if (call) {
|
|
cb.callback();
|
|
callbacks.splice(i, 1);
|
|
i--;
|
|
}
|
|
}
|
|
};
|
|
|
|
// cant use object as key
|
|
defaultExport$2.prototype.createDataTable = function createDataTable (series, columnType) {
|
|
var i, j, s, d, key, rows = [], sortedLabels = [];
|
|
for (i = 0; i < series.length; i++) {
|
|
s = series[i];
|
|
series[i].name = series[i].name || "Value";
|
|
|
|
for (j = 0; j < s.data.length; j++) {
|
|
d = s.data[j];
|
|
key = (columnType === "datetime") ? d[0].getTime() : d[0];
|
|
if (!rows[key]) {
|
|
rows[key] = new Array(series.length);
|
|
sortedLabels.push(key);
|
|
}
|
|
rows[key][i] = toFloat(d[1]);
|
|
}
|
|
}
|
|
|
|
var rows2 = [];
|
|
var day = true;
|
|
var value;
|
|
for (j = 0; j < sortedLabels.length; j++) {
|
|
i = sortedLabels[j];
|
|
if (columnType === "datetime") {
|
|
value = new Date(toFloat(i));
|
|
day = day && isDay(value);
|
|
} else if (columnType === "number") {
|
|
value = toFloat(i);
|
|
} else {
|
|
value = i;
|
|
}
|
|
rows2.push([value].concat(rows[i]));
|
|
}
|
|
if (columnType === "datetime") {
|
|
rows2.sort(sortByTime);
|
|
} else if (columnType === "number") {
|
|
rows2.sort(sortByNumberSeries);
|
|
|
|
for (i = 0; i < rows2.length; i++) {
|
|
rows2[i][0] = toStr(rows2[i][0]);
|
|
}
|
|
|
|
columnType = "string";
|
|
}
|
|
|
|
// create datatable
|
|
var data = new this.library.visualization.DataTable();
|
|
columnType = columnType === "datetime" && day ? "date" : columnType;
|
|
data.addColumn(columnType, "");
|
|
for (i = 0; i < series.length; i++) {
|
|
data.addColumn("number", series[i].name);
|
|
}
|
|
data.addRows(rows2);
|
|
|
|
return data;
|
|
};
|
|
|
|
var pendingRequests = [], runningRequests = 0, maxRequests = 4;
|
|
|
|
function pushRequest(url, success, error) {
|
|
pendingRequests.push([url, success, error]);
|
|
runNext();
|
|
}
|
|
|
|
function runNext() {
|
|
if (runningRequests < maxRequests) {
|
|
var request = pendingRequests.shift();
|
|
if (request) {
|
|
runningRequests++;
|
|
getJSON(request[0], request[1], request[2]);
|
|
runNext();
|
|
}
|
|
}
|
|
}
|
|
|
|
function requestComplete() {
|
|
runningRequests--;
|
|
runNext();
|
|
}
|
|
|
|
function getJSON(url, success, error) {
|
|
ajaxCall(url, success, function (jqXHR, textStatus, errorThrown) {
|
|
var message = (typeof errorThrown === "string") ? errorThrown : errorThrown.message;
|
|
error(message);
|
|
});
|
|
}
|
|
|
|
function ajaxCall(url, success, error) {
|
|
var $ = window.jQuery || window.Zepto || window.$;
|
|
|
|
if ($ && $.ajax) {
|
|
$.ajax({
|
|
dataType: "json",
|
|
url: url,
|
|
success: success,
|
|
error: error,
|
|
complete: requestComplete
|
|
});
|
|
} else {
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.open("GET", url, true);
|
|
xhr.setRequestHeader("Content-Type", "application/json");
|
|
xhr.onload = function () {
|
|
requestComplete();
|
|
if (xhr.status === 200) {
|
|
success(JSON.parse(xhr.responseText), xhr.statusText, xhr);
|
|
} else {
|
|
error(xhr, "error", xhr.statusText);
|
|
}
|
|
};
|
|
xhr.send();
|
|
}
|
|
}
|
|
|
|
var config = {};
|
|
var adapters = [];
|
|
|
|
// helpers
|
|
|
|
function setText(element, text) {
|
|
if (document.body.innerText) {
|
|
element.innerText = text;
|
|
} else {
|
|
element.textContent = text;
|
|
}
|
|
}
|
|
|
|
// TODO remove prefix for all messages
|
|
function chartError(element, message, noPrefix) {
|
|
if (!noPrefix) {
|
|
message = "Error Loading Chart: " + message;
|
|
}
|
|
setText(element, message);
|
|
element.style.color = "#ff0000";
|
|
}
|
|
|
|
function errorCatcher(chart) {
|
|
try {
|
|
chart.__render();
|
|
} catch (err) {
|
|
chartError(chart.element, err.message);
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
function fetchDataSource(chart, dataSource) {
|
|
if (typeof dataSource === "string") {
|
|
pushRequest(dataSource, function (data) {
|
|
chart.rawData = data;
|
|
errorCatcher(chart);
|
|
}, function (message) {
|
|
chartError(chart.element, message);
|
|
});
|
|
} else if (typeof dataSource === "function") {
|
|
try {
|
|
dataSource(function (data) {
|
|
chart.rawData = data;
|
|
errorCatcher(chart);
|
|
}, function (message) {
|
|
chartError(chart.element, message, true);
|
|
});
|
|
} catch (err) {
|
|
chartError(chart.element, err, true);
|
|
}
|
|
} else {
|
|
chart.rawData = dataSource;
|
|
errorCatcher(chart);
|
|
}
|
|
}
|
|
|
|
function addDownloadButton(chart) {
|
|
var element = chart.element;
|
|
var link = document.createElement("a");
|
|
|
|
var download = chart.options.download;
|
|
if (download === true) {
|
|
download = {};
|
|
} else if (typeof download === "string") {
|
|
download = {filename: download};
|
|
}
|
|
link.download = download.filename || "chart.png"; // https://caniuse.com/download
|
|
|
|
link.style.position = "absolute";
|
|
link.style.top = "20px";
|
|
link.style.right = "20px";
|
|
link.style.zIndex = 1000;
|
|
link.style.lineHeight = "20px";
|
|
link.target = "_blank"; // for safari
|
|
var image = document.createElement("img");
|
|
image.alt = "Download";
|
|
image.style.border = "none";
|
|
// icon from font-awesome
|
|
// http://fa2png.io/
|
|
image.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAABCFBMVEUAAADMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMywEsqxAAAAV3RSTlMAAQIDBggJCgsMDQ4PERQaHB0eISIjJCouLzE0OTo/QUJHSUpLTU5PUllhYmltcHh5foWLjI+SlaCio6atr7S1t7m6vsHHyM7R2tze5Obo7fHz9ff5+/1hlxK2AAAA30lEQVQYGUXBhVYCQQBA0TdYWAt2d3d3YWAHyur7/z9xgD16Lw0DW+XKx+1GgX+FRzM3HWQWrHl5N/oapW5RPe0PkBu+UYeICvozTWZVK23Ao04B79oJrOsJDOoxkZoQPWgX29pHpCZEk7rEvQYiNSFq1UMqvlCjJkRBS1R8hb00Vb/TajtBL7nTHE1X1vyMQF732dQhyF2o6SAwrzP06iUQzvwsArlnzcOdrgBhJyHa1QOgO9U1GsKuvjUTjavliZYQ8nNPapG6sap/3nrIdJ6bOWzmX/fy0XVpfzZP3S8OJT3g9EEiJwAAAABJRU5ErkJggg==";
|
|
link.appendChild(image);
|
|
element.style.position = "relative";
|
|
|
|
chart.__downloadAttached = true;
|
|
|
|
// mouseenter
|
|
chart.__enterEvent = addEvent(element, "mouseover", function(e) {
|
|
var related = e.relatedTarget;
|
|
// check download option again to ensure it wasn't changed
|
|
if ((!related || (related !== this && !childOf(this, related))) && chart.options.download) {
|
|
link.href = chart.toImage(download);
|
|
element.appendChild(link);
|
|
}
|
|
});
|
|
|
|
// mouseleave
|
|
chart.__leaveEvent = addEvent(element, "mouseout", function(e) {
|
|
var related = e.relatedTarget;
|
|
if (!related || (related !== this && !childOf(this, related))) {
|
|
if (link.parentNode) {
|
|
link.parentNode.removeChild(link);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// https://stackoverflow.com/questions/10149963/adding-event-listener-cross-browser
|
|
function addEvent(elem, event, fn) {
|
|
if (elem.addEventListener) {
|
|
elem.addEventListener(event, fn, false);
|
|
return fn;
|
|
} else {
|
|
var fn2 = function() {
|
|
// set the this pointer same as addEventListener when fn is called
|
|
return(fn.call(elem, window.event));
|
|
};
|
|
elem.attachEvent("on" + event, fn2);
|
|
return fn2;
|
|
}
|
|
}
|
|
|
|
function removeEvent(elem, event, fn) {
|
|
if (elem.removeEventListener) {
|
|
elem.removeEventListener(event, fn, false);
|
|
} else {
|
|
elem.detachEvent("on" + event, fn);
|
|
}
|
|
}
|
|
|
|
// https://gist.github.com/shawnbot/4166283
|
|
function childOf(p, c) {
|
|
if (p === c) { return false; }
|
|
while (c && c !== p) { c = c.parentNode; }
|
|
return c === p;
|
|
}
|
|
|
|
function getAdapterType(library) {
|
|
if (library) {
|
|
if (library.product === "Highcharts") {
|
|
return defaultExport$1;
|
|
} else if (library.charts) {
|
|
return defaultExport$2;
|
|
} else if (isFunction(library)) {
|
|
return defaultExport;
|
|
}
|
|
}
|
|
throw new Error("Unknown adapter");
|
|
}
|
|
|
|
function addAdapter(library) {
|
|
var adapterType = getAdapterType(library);
|
|
var adapter = new adapterType(library);
|
|
|
|
if (adapters.indexOf(adapter) === -1) {
|
|
adapters.push(adapter);
|
|
}
|
|
}
|
|
|
|
function loadAdapters() {
|
|
if ("Chart" in window) {
|
|
addAdapter(window.Chart);
|
|
}
|
|
|
|
if ("Highcharts" in window) {
|
|
addAdapter(window.Highcharts);
|
|
}
|
|
|
|
if (window.google && window.google.charts) {
|
|
addAdapter(window.google);
|
|
}
|
|
}
|
|
|
|
function dataEmpty(data, chartType) {
|
|
if (chartType === "PieChart" || chartType === "GeoChart" || chartType === "Timeline") {
|
|
return data.length === 0;
|
|
} else {
|
|
for (var i = 0; i < data.length; i++) {
|
|
if (data[i].data.length > 0) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
function renderChart(chartType, chart) {
|
|
if (chart.options.messages && chart.options.messages.empty && dataEmpty(chart.data, chartType)) {
|
|
setText(chart.element, chart.options.messages.empty);
|
|
} else {
|
|
callAdapter(chartType, chart);
|
|
if (chart.options.download && !chart.__downloadAttached && chart.adapter === "chartjs") {
|
|
addDownloadButton(chart);
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO remove chartType if cross-browser way
|
|
// to get the name of the chart class
|
|
function callAdapter(chartType, chart) {
|
|
var i, adapter, fnName, adapterName;
|
|
fnName = "render" + chartType;
|
|
adapterName = chart.options.adapter;
|
|
|
|
loadAdapters();
|
|
|
|
for (i = 0; i < adapters.length; i++) {
|
|
adapter = adapters[i];
|
|
if ((!adapterName || adapterName === adapter.name) && isFunction(adapter[fnName])) {
|
|
chart.adapter = adapter.name;
|
|
chart.__adapterObject = adapter;
|
|
return adapter[fnName](chart);
|
|
}
|
|
}
|
|
|
|
if (adapters.length > 0) {
|
|
throw new Error("No charting library found for " + chartType);
|
|
} else {
|
|
throw new Error("No charting libraries found - be sure to include one before your charts");
|
|
}
|
|
}
|
|
|
|
// process data
|
|
|
|
var toFormattedKey = function (key, keyType) {
|
|
if (keyType === "number") {
|
|
key = toFloat(key);
|
|
} else if (keyType === "datetime") {
|
|
key = toDate(key);
|
|
} else {
|
|
key = toStr(key);
|
|
}
|
|
return key;
|
|
};
|
|
|
|
var formatSeriesData = function (data, keyType) {
|
|
var r = [], key, j;
|
|
for (j = 0; j < data.length; j++) {
|
|
if (keyType === "bubble") {
|
|
r.push([toFloat(data[j][0]), toFloat(data[j][1]), toFloat(data[j][2])]);
|
|
} else {
|
|
key = toFormattedKey(data[j][0], keyType);
|
|
r.push([key, toFloat(data[j][1])]);
|
|
}
|
|
}
|
|
if (keyType === "datetime") {
|
|
r.sort(sortByTime);
|
|
} else if (keyType === "number") {
|
|
r.sort(sortByNumberSeries);
|
|
}
|
|
return r;
|
|
};
|
|
|
|
function detectXType(series, noDatetime) {
|
|
if (detectXTypeWithFunction(series, isNumber)) {
|
|
return "number";
|
|
} else if (!noDatetime && detectXTypeWithFunction(series, isDate)) {
|
|
return "datetime";
|
|
} else {
|
|
return "string";
|
|
}
|
|
}
|
|
|
|
function detectXTypeWithFunction(series, func) {
|
|
var i, j, data;
|
|
for (i = 0; i < series.length; i++) {
|
|
data = toArr(series[i].data);
|
|
for (j = 0; j < data.length; j++) {
|
|
if (!func(data[j][0])) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// creates a shallow copy of each element of the array
|
|
// elements are expected to be objects
|
|
function copySeries(series) {
|
|
var newSeries = [], i, j;
|
|
for (i = 0; i < series.length; i++) {
|
|
var copy = {};
|
|
for (j in series[i]) {
|
|
if (series[i].hasOwnProperty(j)) {
|
|
copy[j] = series[i][j];
|
|
}
|
|
}
|
|
newSeries.push(copy);
|
|
}
|
|
return newSeries;
|
|
}
|
|
|
|
function processSeries(chart, keyType, noDatetime) {
|
|
var i;
|
|
|
|
var opts = chart.options;
|
|
var series = chart.rawData;
|
|
|
|
// see if one series or multiple
|
|
if (!isArray(series) || typeof series[0] !== "object" || isArray(series[0])) {
|
|
series = [{name: opts.label, data: series}];
|
|
chart.hideLegend = true;
|
|
} else {
|
|
chart.hideLegend = false;
|
|
}
|
|
|
|
chart.xtype = keyType ? keyType : (opts.discrete ? "string" : detectXType(series, noDatetime));
|
|
|
|
// right format
|
|
series = copySeries(series);
|
|
for (i = 0; i < series.length; i++) {
|
|
series[i].data = formatSeriesData(toArr(series[i].data), chart.xtype);
|
|
}
|
|
|
|
return series;
|
|
}
|
|
|
|
function processSimple(chart) {
|
|
var perfectData = toArr(chart.rawData), i;
|
|
for (i = 0; i < perfectData.length; i++) {
|
|
perfectData[i] = [toStr(perfectData[i][0]), toFloat(perfectData[i][1])];
|
|
}
|
|
return perfectData;
|
|
}
|
|
|
|
// define classes
|
|
|
|
var Chart = function Chart(element, dataSource, options) {
|
|
var elementId;
|
|
if (typeof element === "string") {
|
|
elementId = element;
|
|
element = document.getElementById(element);
|
|
if (!element) {
|
|
throw new Error("No element with id " + elementId);
|
|
}
|
|
}
|
|
this.element = element;
|
|
this.options = merge(Chartkick.options, options || {});
|
|
this.dataSource = dataSource;
|
|
|
|
Chartkick.charts[element.id] = this;
|
|
|
|
fetchDataSource(this, dataSource);
|
|
|
|
if (this.options.refresh) {
|
|
this.startRefresh();
|
|
}
|
|
};
|
|
|
|
Chart.prototype.getElement = function getElement () {
|
|
return this.element;
|
|
};
|
|
|
|
Chart.prototype.getDataSource = function getDataSource () {
|
|
return this.dataSource;
|
|
};
|
|
|
|
Chart.prototype.getData = function getData () {
|
|
return this.data;
|
|
};
|
|
|
|
Chart.prototype.getOptions = function getOptions () {
|
|
return this.options;
|
|
};
|
|
|
|
Chart.prototype.getChartObject = function getChartObject () {
|
|
return this.chart;
|
|
};
|
|
|
|
Chart.prototype.getAdapter = function getAdapter () {
|
|
return this.adapter;
|
|
};
|
|
|
|
Chart.prototype.updateData = function updateData (dataSource, options) {
|
|
this.dataSource = dataSource;
|
|
if (options) {
|
|
this.__updateOptions(options);
|
|
}
|
|
fetchDataSource(this, dataSource);
|
|
};
|
|
|
|
Chart.prototype.setOptions = function setOptions (options) {
|
|
this.__updateOptions(options);
|
|
this.redraw();
|
|
};
|
|
|
|
Chart.prototype.redraw = function redraw () {
|
|
fetchDataSource(this, this.rawData);
|
|
};
|
|
|
|
Chart.prototype.refreshData = function refreshData () {
|
|
if (typeof this.dataSource === "string") {
|
|
// prevent browser from caching
|
|
var sep = this.dataSource.indexOf("?") === -1 ? "?" : "&";
|
|
var url = this.dataSource + sep + "_=" + (new Date()).getTime();
|
|
fetchDataSource(this, url);
|
|
} else if (typeof this.dataSource === "function") {
|
|
fetchDataSource(this, this.dataSource);
|
|
}
|
|
};
|
|
|
|
Chart.prototype.startRefresh = function startRefresh () {
|
|
var this$1 = this;
|
|
|
|
var refresh = this.options.refresh;
|
|
|
|
if (refresh && typeof this.dataSource !== "string" && typeof this.dataSource !== "function") {
|
|
throw new Error("Data source must be a URL or callback for refresh");
|
|
}
|
|
|
|
if (!this.intervalId) {
|
|
if (refresh) {
|
|
this.intervalId = setInterval( function () {
|
|
this$1.refreshData();
|
|
}, refresh * 1000);
|
|
} else {
|
|
throw new Error("No refresh interval");
|
|
}
|
|
}
|
|
};
|
|
|
|
Chart.prototype.stopRefresh = function stopRefresh () {
|
|
if (this.intervalId) {
|
|
clearInterval(this.intervalId);
|
|
this.intervalId = null;
|
|
}
|
|
};
|
|
|
|
Chart.prototype.toImage = function toImage (download) {
|
|
if (this.adapter === "chartjs") {
|
|
if (download && download.background && download.background !== "transparent") {
|
|
// https://stackoverflow.com/questions/30464750/chartjs-line-chart-set-background-color
|
|
var canvas = this.chart.chart.canvas;
|
|
var ctx = this.chart.chart.ctx;
|
|
var tmpCanvas = document.createElement("canvas");
|
|
var tmpCtx = tmpCanvas.getContext("2d");
|
|
tmpCanvas.width = ctx.canvas.width;
|
|
tmpCanvas.height = ctx.canvas.height;
|
|
tmpCtx.fillStyle = download.background;
|
|
tmpCtx.fillRect(0, 0, tmpCanvas.width, tmpCanvas.height);
|
|
tmpCtx.drawImage(canvas, 0, 0);
|
|
return tmpCanvas.toDataURL("image/png");
|
|
} else {
|
|
return this.chart.toBase64Image();
|
|
}
|
|
} else {
|
|
// TODO throw error in next major version
|
|
// throw new Error("Feature only available for Chart.js");
|
|
return null;
|
|
}
|
|
};
|
|
|
|
Chart.prototype.destroy = function destroy () {
|
|
if (this.__adapterObject) {
|
|
this.__adapterObject.destroy(this);
|
|
}
|
|
|
|
if (this.__enterEvent) {
|
|
removeEvent(this.element, "mouseover", this.__enterEvent);
|
|
}
|
|
|
|
if (this.__leaveEvent) {
|
|
removeEvent(this.element, "mouseout", this.__leaveEvent);
|
|
}
|
|
};
|
|
|
|
Chart.prototype.__updateOptions = function __updateOptions (options) {
|
|
var updateRefresh = options.refresh && options.refresh !== this.options.refresh;
|
|
this.options = merge(Chartkick.options, options);
|
|
if (updateRefresh) {
|
|
this.stopRefresh();
|
|
this.startRefresh();
|
|
}
|
|
};
|
|
|
|
Chart.prototype.__render = function __render () {
|
|
this.data = this.__processData();
|
|
renderChart(this.__chartName(), this);
|
|
};
|
|
|
|
Chart.prototype.__config = function __config () {
|
|
return config;
|
|
};
|
|
|
|
var LineChart = /*@__PURE__*/(function (Chart) {
|
|
function LineChart () {
|
|
Chart.apply(this, arguments);
|
|
}
|
|
|
|
if ( Chart ) LineChart.__proto__ = Chart;
|
|
LineChart.prototype = Object.create( Chart && Chart.prototype );
|
|
LineChart.prototype.constructor = LineChart;
|
|
|
|
LineChart.prototype.__processData = function __processData () {
|
|
return processSeries(this);
|
|
};
|
|
|
|
LineChart.prototype.__chartName = function __chartName () {
|
|
return "LineChart";
|
|
};
|
|
|
|
return LineChart;
|
|
}(Chart));
|
|
|
|
var PieChart = /*@__PURE__*/(function (Chart) {
|
|
function PieChart () {
|
|
Chart.apply(this, arguments);
|
|
}
|
|
|
|
if ( Chart ) PieChart.__proto__ = Chart;
|
|
PieChart.prototype = Object.create( Chart && Chart.prototype );
|
|
PieChart.prototype.constructor = PieChart;
|
|
|
|
PieChart.prototype.__processData = function __processData () {
|
|
return processSimple(this);
|
|
};
|
|
|
|
PieChart.prototype.__chartName = function __chartName () {
|
|
return "PieChart";
|
|
};
|
|
|
|
return PieChart;
|
|
}(Chart));
|
|
|
|
var ColumnChart = /*@__PURE__*/(function (Chart) {
|
|
function ColumnChart () {
|
|
Chart.apply(this, arguments);
|
|
}
|
|
|
|
if ( Chart ) ColumnChart.__proto__ = Chart;
|
|
ColumnChart.prototype = Object.create( Chart && Chart.prototype );
|
|
ColumnChart.prototype.constructor = ColumnChart;
|
|
|
|
ColumnChart.prototype.__processData = function __processData () {
|
|
return processSeries(this, null, true);
|
|
};
|
|
|
|
ColumnChart.prototype.__chartName = function __chartName () {
|
|
return "ColumnChart";
|
|
};
|
|
|
|
return ColumnChart;
|
|
}(Chart));
|
|
|
|
var BarChart = /*@__PURE__*/(function (Chart) {
|
|
function BarChart () {
|
|
Chart.apply(this, arguments);
|
|
}
|
|
|
|
if ( Chart ) BarChart.__proto__ = Chart;
|
|
BarChart.prototype = Object.create( Chart && Chart.prototype );
|
|
BarChart.prototype.constructor = BarChart;
|
|
|
|
BarChart.prototype.__processData = function __processData () {
|
|
return processSeries(this, null, true);
|
|
};
|
|
|
|
BarChart.prototype.__chartName = function __chartName () {
|
|
return "BarChart";
|
|
};
|
|
|
|
return BarChart;
|
|
}(Chart));
|
|
|
|
var AreaChart = /*@__PURE__*/(function (Chart) {
|
|
function AreaChart () {
|
|
Chart.apply(this, arguments);
|
|
}
|
|
|
|
if ( Chart ) AreaChart.__proto__ = Chart;
|
|
AreaChart.prototype = Object.create( Chart && Chart.prototype );
|
|
AreaChart.prototype.constructor = AreaChart;
|
|
|
|
AreaChart.prototype.__processData = function __processData () {
|
|
return processSeries(this);
|
|
};
|
|
|
|
AreaChart.prototype.__chartName = function __chartName () {
|
|
return "AreaChart";
|
|
};
|
|
|
|
return AreaChart;
|
|
}(Chart));
|
|
|
|
var GeoChart = /*@__PURE__*/(function (Chart) {
|
|
function GeoChart () {
|
|
Chart.apply(this, arguments);
|
|
}
|
|
|
|
if ( Chart ) GeoChart.__proto__ = Chart;
|
|
GeoChart.prototype = Object.create( Chart && Chart.prototype );
|
|
GeoChart.prototype.constructor = GeoChart;
|
|
|
|
GeoChart.prototype.__processData = function __processData () {
|
|
return processSimple(this);
|
|
};
|
|
|
|
GeoChart.prototype.__chartName = function __chartName () {
|
|
return "GeoChart";
|
|
};
|
|
|
|
return GeoChart;
|
|
}(Chart));
|
|
|
|
var ScatterChart = /*@__PURE__*/(function (Chart) {
|
|
function ScatterChart () {
|
|
Chart.apply(this, arguments);
|
|
}
|
|
|
|
if ( Chart ) ScatterChart.__proto__ = Chart;
|
|
ScatterChart.prototype = Object.create( Chart && Chart.prototype );
|
|
ScatterChart.prototype.constructor = ScatterChart;
|
|
|
|
ScatterChart.prototype.__processData = function __processData () {
|
|
return processSeries(this, "number");
|
|
};
|
|
|
|
ScatterChart.prototype.__chartName = function __chartName () {
|
|
return "ScatterChart";
|
|
};
|
|
|
|
return ScatterChart;
|
|
}(Chart));
|
|
|
|
var BubbleChart = /*@__PURE__*/(function (Chart) {
|
|
function BubbleChart () {
|
|
Chart.apply(this, arguments);
|
|
}
|
|
|
|
if ( Chart ) BubbleChart.__proto__ = Chart;
|
|
BubbleChart.prototype = Object.create( Chart && Chart.prototype );
|
|
BubbleChart.prototype.constructor = BubbleChart;
|
|
|
|
BubbleChart.prototype.__processData = function __processData () {
|
|
return processSeries(this, "bubble");
|
|
};
|
|
|
|
BubbleChart.prototype.__chartName = function __chartName () {
|
|
return "BubbleChart";
|
|
};
|
|
|
|
return BubbleChart;
|
|
}(Chart));
|
|
|
|
var Timeline = /*@__PURE__*/(function (Chart) {
|
|
function Timeline () {
|
|
Chart.apply(this, arguments);
|
|
}
|
|
|
|
if ( Chart ) Timeline.__proto__ = Chart;
|
|
Timeline.prototype = Object.create( Chart && Chart.prototype );
|
|
Timeline.prototype.constructor = Timeline;
|
|
|
|
Timeline.prototype.__processData = function __processData () {
|
|
var i, data = this.rawData;
|
|
for (i = 0; i < data.length; i++) {
|
|
data[i][1] = toDate(data[i][1]);
|
|
data[i][2] = toDate(data[i][2]);
|
|
}
|
|
return data;
|
|
};
|
|
|
|
Timeline.prototype.__chartName = function __chartName () {
|
|
return "Timeline";
|
|
};
|
|
|
|
return Timeline;
|
|
}(Chart));
|
|
|
|
var Chartkick = {
|
|
LineChart: LineChart,
|
|
PieChart: PieChart,
|
|
ColumnChart: ColumnChart,
|
|
BarChart: BarChart,
|
|
AreaChart: AreaChart,
|
|
GeoChart: GeoChart,
|
|
ScatterChart: ScatterChart,
|
|
BubbleChart: BubbleChart,
|
|
Timeline: Timeline,
|
|
charts: {},
|
|
configure: function (options) {
|
|
for (var key in options) {
|
|
if (options.hasOwnProperty(key)) {
|
|
config[key] = options[key];
|
|
}
|
|
}
|
|
},
|
|
setDefaultOptions: function (opts) {
|
|
Chartkick.options = opts;
|
|
},
|
|
eachChart: function (callback) {
|
|
for (var chartId in Chartkick.charts) {
|
|
if (Chartkick.charts.hasOwnProperty(chartId)) {
|
|
callback(Chartkick.charts[chartId]);
|
|
}
|
|
}
|
|
},
|
|
config: config,
|
|
options: {},
|
|
adapters: adapters,
|
|
addAdapter: addAdapter,
|
|
use: function(adapter) {
|
|
addAdapter(adapter);
|
|
return Chartkick;
|
|
}
|
|
};
|
|
|
|
// not ideal, but allows for simpler integration
|
|
if (typeof window !== "undefined" && !window.Chartkick) {
|
|
window.Chartkick = Chartkick;
|
|
}
|
|
|
|
// backwards compatibility for esm require
|
|
Chartkick.default = Chartkick;
|
|
|
|
return Chartkick;
|
|
|
|
})));
|
|
/*!
|
|
* Chart.js v2.9.3
|
|
* https://www.chartjs.org
|
|
* (c) 2019 Chart.js Contributors
|
|
* Released under the MIT License
|
|
*/
|
|
|
|
(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
|
typeof define === 'function' && define.amd ? define(factory) :
|
|
(global = global || self, global.Chart = factory());
|
|
}(this, (function () { 'use strict';
|
|
|
|
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
|
|
|
|
function commonjsRequire () {
|
|
throw new Error('Dynamic requires are not currently supported by rollup-plugin-commonjs');
|
|
}
|
|
|
|
function createCommonjsModule(fn, module) {
|
|
return module = { exports: {} }, fn(module, module.exports), module.exports;
|
|
}
|
|
|
|
function getCjsExportFromNamespace (n) {
|
|
return n && n['default'] || n;
|
|
}
|
|
|
|
var colorName = {
|
|
"aliceblue": [240, 248, 255],
|
|
"antiquewhite": [250, 235, 215],
|
|
"aqua": [0, 255, 255],
|
|
"aquamarine": [127, 255, 212],
|
|
"azure": [240, 255, 255],
|
|
"beige": [245, 245, 220],
|
|
"bisque": [255, 228, 196],
|
|
"black": [0, 0, 0],
|
|
"blanchedalmond": [255, 235, 205],
|
|
"blue": [0, 0, 255],
|
|
"blueviolet": [138, 43, 226],
|
|
"brown": [165, 42, 42],
|
|
"burlywood": [222, 184, 135],
|
|
"cadetblue": [95, 158, 160],
|
|
"chartreuse": [127, 255, 0],
|
|
"chocolate": [210, 105, 30],
|
|
"coral": [255, 127, 80],
|
|
"cornflowerblue": [100, 149, 237],
|
|
"cornsilk": [255, 248, 220],
|
|
"crimson": [220, 20, 60],
|
|
"cyan": [0, 255, 255],
|
|
"darkblue": [0, 0, 139],
|
|
"darkcyan": [0, 139, 139],
|
|
"darkgoldenrod": [184, 134, 11],
|
|
"darkgray": [169, 169, 169],
|
|
"darkgreen": [0, 100, 0],
|
|
"darkgrey": [169, 169, 169],
|
|
"darkkhaki": [189, 183, 107],
|
|
"darkmagenta": [139, 0, 139],
|
|
"darkolivegreen": [85, 107, 47],
|
|
"darkorange": [255, 140, 0],
|
|
"darkorchid": [153, 50, 204],
|
|
"darkred": [139, 0, 0],
|
|
"darksalmon": [233, 150, 122],
|
|
"darkseagreen": [143, 188, 143],
|
|
"darkslateblue": [72, 61, 139],
|
|
"darkslategray": [47, 79, 79],
|
|
"darkslategrey": [47, 79, 79],
|
|
"darkturquoise": [0, 206, 209],
|
|
"darkviolet": [148, 0, 211],
|
|
"deeppink": [255, 20, 147],
|
|
"deepskyblue": [0, 191, 255],
|
|
"dimgray": [105, 105, 105],
|
|
"dimgrey": [105, 105, 105],
|
|
"dodgerblue": [30, 144, 255],
|
|
"firebrick": [178, 34, 34],
|
|
"floralwhite": [255, 250, 240],
|
|
"forestgreen": [34, 139, 34],
|
|
"fuchsia": [255, 0, 255],
|
|
"gainsboro": [220, 220, 220],
|
|
"ghostwhite": [248, 248, 255],
|
|
"gold": [255, 215, 0],
|
|
"goldenrod": [218, 165, 32],
|
|
"gray": [128, 128, 128],
|
|
"green": [0, 128, 0],
|
|
"greenyellow": [173, 255, 47],
|
|
"grey": [128, 128, 128],
|
|
"honeydew": [240, 255, 240],
|
|
"hotpink": [255, 105, 180],
|
|
"indianred": [205, 92, 92],
|
|
"indigo": [75, 0, 130],
|
|
"ivory": [255, 255, 240],
|
|
"khaki": [240, 230, 140],
|
|
"lavender": [230, 230, 250],
|
|
"lavenderblush": [255, 240, 245],
|
|
"lawngreen": [124, 252, 0],
|
|
"lemonchiffon": [255, 250, 205],
|
|
"lightblue": [173, 216, 230],
|
|
"lightcoral": [240, 128, 128],
|
|
"lightcyan": [224, 255, 255],
|
|
"lightgoldenrodyellow": [250, 250, 210],
|
|
"lightgray": [211, 211, 211],
|
|
"lightgreen": [144, 238, 144],
|
|
"lightgrey": [211, 211, 211],
|
|
"lightpink": [255, 182, 193],
|
|
"lightsalmon": [255, 160, 122],
|
|
"lightseagreen": [32, 178, 170],
|
|
"lightskyblue": [135, 206, 250],
|
|
"lightslategray": [119, 136, 153],
|
|
"lightslategrey": [119, 136, 153],
|
|
"lightsteelblue": [176, 196, 222],
|
|
"lightyellow": [255, 255, 224],
|
|
"lime": [0, 255, 0],
|
|
"limegreen": [50, 205, 50],
|
|
"linen": [250, 240, 230],
|
|
"magenta": [255, 0, 255],
|
|
"maroon": [128, 0, 0],
|
|
"mediumaquamarine": [102, 205, 170],
|
|
"mediumblue": [0, 0, 205],
|
|
"mediumorchid": [186, 85, 211],
|
|
"mediumpurple": [147, 112, 219],
|
|
"mediumseagreen": [60, 179, 113],
|
|
"mediumslateblue": [123, 104, 238],
|
|
"mediumspringgreen": [0, 250, 154],
|
|
"mediumturquoise": [72, 209, 204],
|
|
"mediumvioletred": [199, 21, 133],
|
|
"midnightblue": [25, 25, 112],
|
|
"mintcream": [245, 255, 250],
|
|
"mistyrose": [255, 228, 225],
|
|
"moccasin": [255, 228, 181],
|
|
"navajowhite": [255, 222, 173],
|
|
"navy": [0, 0, 128],
|
|
"oldlace": [253, 245, 230],
|
|
"olive": [128, 128, 0],
|
|
"olivedrab": [107, 142, 35],
|
|
"orange": [255, 165, 0],
|
|
"orangered": [255, 69, 0],
|
|
"orchid": [218, 112, 214],
|
|
"palegoldenrod": [238, 232, 170],
|
|
"palegreen": [152, 251, 152],
|
|
"paleturquoise": [175, 238, 238],
|
|
"palevioletred": [219, 112, 147],
|
|
"papayawhip": [255, 239, 213],
|
|
"peachpuff": [255, 218, 185],
|
|
"peru": [205, 133, 63],
|
|
"pink": [255, 192, 203],
|
|
"plum": [221, 160, 221],
|
|
"powderblue": [176, 224, 230],
|
|
"purple": [128, 0, 128],
|
|
"rebeccapurple": [102, 51, 153],
|
|
"red": [255, 0, 0],
|
|
"rosybrown": [188, 143, 143],
|
|
"royalblue": [65, 105, 225],
|
|
"saddlebrown": [139, 69, 19],
|
|
"salmon": [250, 128, 114],
|
|
"sandybrown": [244, 164, 96],
|
|
"seagreen": [46, 139, 87],
|
|
"seashell": [255, 245, 238],
|
|
"sienna": [160, 82, 45],
|
|
"silver": [192, 192, 192],
|
|
"skyblue": [135, 206, 235],
|
|
"slateblue": [106, 90, 205],
|
|
"slategray": [112, 128, 144],
|
|
"slategrey": [112, 128, 144],
|
|
"snow": [255, 250, 250],
|
|
"springgreen": [0, 255, 127],
|
|
"steelblue": [70, 130, 180],
|
|
"tan": [210, 180, 140],
|
|
"teal": [0, 128, 128],
|
|
"thistle": [216, 191, 216],
|
|
"tomato": [255, 99, 71],
|
|
"turquoise": [64, 224, 208],
|
|
"violet": [238, 130, 238],
|
|
"wheat": [245, 222, 179],
|
|
"white": [255, 255, 255],
|
|
"whitesmoke": [245, 245, 245],
|
|
"yellow": [255, 255, 0],
|
|
"yellowgreen": [154, 205, 50]
|
|
};
|
|
|
|
var conversions = createCommonjsModule(function (module) {
|
|
/* MIT license */
|
|
|
|
|
|
// NOTE: conversions should only return primitive values (i.e. arrays, or
|
|
// values that give correct `typeof` results).
|
|
// do not use box values types (i.e. Number(), String(), etc.)
|
|
|
|
var reverseKeywords = {};
|
|
for (var key in colorName) {
|
|
if (colorName.hasOwnProperty(key)) {
|
|
reverseKeywords[colorName[key]] = key;
|
|
}
|
|
}
|
|
|
|
var convert = module.exports = {
|
|
rgb: {channels: 3, labels: 'rgb'},
|
|
hsl: {channels: 3, labels: 'hsl'},
|
|
hsv: {channels: 3, labels: 'hsv'},
|
|
hwb: {channels: 3, labels: 'hwb'},
|
|
cmyk: {channels: 4, labels: 'cmyk'},
|
|
xyz: {channels: 3, labels: 'xyz'},
|
|
lab: {channels: 3, labels: 'lab'},
|
|
lch: {channels: 3, labels: 'lch'},
|
|
hex: {channels: 1, labels: ['hex']},
|
|
keyword: {channels: 1, labels: ['keyword']},
|
|
ansi16: {channels: 1, labels: ['ansi16']},
|
|
ansi256: {channels: 1, labels: ['ansi256']},
|
|
hcg: {channels: 3, labels: ['h', 'c', 'g']},
|
|
apple: {channels: 3, labels: ['r16', 'g16', 'b16']},
|
|
gray: {channels: 1, labels: ['gray']}
|
|
};
|
|
|
|
// hide .channels and .labels properties
|
|
for (var model in convert) {
|
|
if (convert.hasOwnProperty(model)) {
|
|
if (!('channels' in convert[model])) {
|
|
throw new Error('missing channels property: ' + model);
|
|
}
|
|
|
|
if (!('labels' in convert[model])) {
|
|
throw new Error('missing channel labels property: ' + model);
|
|
}
|
|
|
|
if (convert[model].labels.length !== convert[model].channels) {
|
|
throw new Error('channel and label counts mismatch: ' + model);
|
|
}
|
|
|
|
var channels = convert[model].channels;
|
|
var labels = convert[model].labels;
|
|
delete convert[model].channels;
|
|
delete convert[model].labels;
|
|
Object.defineProperty(convert[model], 'channels', {value: channels});
|
|
Object.defineProperty(convert[model], 'labels', {value: labels});
|
|
}
|
|
}
|
|
|
|
convert.rgb.hsl = function (rgb) {
|
|
var r = rgb[0] / 255;
|
|
var g = rgb[1] / 255;
|
|
var b = rgb[2] / 255;
|
|
var min = Math.min(r, g, b);
|
|
var max = Math.max(r, g, b);
|
|
var delta = max - min;
|
|
var h;
|
|
var s;
|
|
var l;
|
|
|
|
if (max === min) {
|
|
h = 0;
|
|
} else if (r === max) {
|
|
h = (g - b) / delta;
|
|
} else if (g === max) {
|
|
h = 2 + (b - r) / delta;
|
|
} else if (b === max) {
|
|
h = 4 + (r - g) / delta;
|
|
}
|
|
|
|
h = Math.min(h * 60, 360);
|
|
|
|
if (h < 0) {
|
|
h += 360;
|
|
}
|
|
|
|
l = (min + max) / 2;
|
|
|
|
if (max === min) {
|
|
s = 0;
|
|
} else if (l <= 0.5) {
|
|
s = delta / (max + min);
|
|
} else {
|
|
s = delta / (2 - max - min);
|
|
}
|
|
|
|
return [h, s * 100, l * 100];
|
|
};
|
|
|
|
convert.rgb.hsv = function (rgb) {
|
|
var rdif;
|
|
var gdif;
|
|
var bdif;
|
|
var h;
|
|
var s;
|
|
|
|
var r = rgb[0] / 255;
|
|
var g = rgb[1] / 255;
|
|
var b = rgb[2] / 255;
|
|
var v = Math.max(r, g, b);
|
|
var diff = v - Math.min(r, g, b);
|
|
var diffc = function (c) {
|
|
return (v - c) / 6 / diff + 1 / 2;
|
|
};
|
|
|
|
if (diff === 0) {
|
|
h = s = 0;
|
|
} else {
|
|
s = diff / v;
|
|
rdif = diffc(r);
|
|
gdif = diffc(g);
|
|
bdif = diffc(b);
|
|
|
|
if (r === v) {
|
|
h = bdif - gdif;
|
|
} else if (g === v) {
|
|
h = (1 / 3) + rdif - bdif;
|
|
} else if (b === v) {
|
|
h = (2 / 3) + gdif - rdif;
|
|
}
|
|
if (h < 0) {
|
|
h += 1;
|
|
} else if (h > 1) {
|
|
h -= 1;
|
|
}
|
|
}
|
|
|
|
return [
|
|
h * 360,
|
|
s * 100,
|
|
v * 100
|
|
];
|
|
};
|
|
|
|
convert.rgb.hwb = function (rgb) {
|
|
var r = rgb[0];
|
|
var g = rgb[1];
|
|
var b = rgb[2];
|
|
var h = convert.rgb.hsl(rgb)[0];
|
|
var w = 1 / 255 * Math.min(r, Math.min(g, b));
|
|
|
|
b = 1 - 1 / 255 * Math.max(r, Math.max(g, b));
|
|
|
|
return [h, w * 100, b * 100];
|
|
};
|
|
|
|
convert.rgb.cmyk = function (rgb) {
|
|
var r = rgb[0] / 255;
|
|
var g = rgb[1] / 255;
|
|
var b = rgb[2] / 255;
|
|
var c;
|
|
var m;
|
|
var y;
|
|
var k;
|
|
|
|
k = Math.min(1 - r, 1 - g, 1 - b);
|
|
c = (1 - r - k) / (1 - k) || 0;
|
|
m = (1 - g - k) / (1 - k) || 0;
|
|
y = (1 - b - k) / (1 - k) || 0;
|
|
|
|
return [c * 100, m * 100, y * 100, k * 100];
|
|
};
|
|
|
|
/**
|
|
* See https://en.m.wikipedia.org/wiki/Euclidean_distance#Squared_Euclidean_distance
|
|
* */
|
|
function comparativeDistance(x, y) {
|
|
return (
|
|
Math.pow(x[0] - y[0], 2) +
|
|
Math.pow(x[1] - y[1], 2) +
|
|
Math.pow(x[2] - y[2], 2)
|
|
);
|
|
}
|
|
|
|
convert.rgb.keyword = function (rgb) {
|
|
var reversed = reverseKeywords[rgb];
|
|
if (reversed) {
|
|
return reversed;
|
|
}
|
|
|
|
var currentClosestDistance = Infinity;
|
|
var currentClosestKeyword;
|
|
|
|
for (var keyword in colorName) {
|
|
if (colorName.hasOwnProperty(keyword)) {
|
|
var value = colorName[keyword];
|
|
|
|
// Compute comparative distance
|
|
var distance = comparativeDistance(rgb, value);
|
|
|
|
// Check if its less, if so set as closest
|
|
if (distance < currentClosestDistance) {
|
|
currentClosestDistance = distance;
|
|
currentClosestKeyword = keyword;
|
|
}
|
|
}
|
|
}
|
|
|
|
return currentClosestKeyword;
|
|
};
|
|
|
|
convert.keyword.rgb = function (keyword) {
|
|
return colorName[keyword];
|
|
};
|
|
|
|
convert.rgb.xyz = function (rgb) {
|
|
var r = rgb[0] / 255;
|
|
var g = rgb[1] / 255;
|
|
var b = rgb[2] / 255;
|
|
|
|
// assume sRGB
|
|
r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92);
|
|
g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92);
|
|
b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92);
|
|
|
|
var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805);
|
|
var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722);
|
|
var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505);
|
|
|
|
return [x * 100, y * 100, z * 100];
|
|
};
|
|
|
|
convert.rgb.lab = function (rgb) {
|
|
var xyz = convert.rgb.xyz(rgb);
|
|
var x = xyz[0];
|
|
var y = xyz[1];
|
|
var z = xyz[2];
|
|
var l;
|
|
var a;
|
|
var b;
|
|
|
|
x /= 95.047;
|
|
y /= 100;
|
|
z /= 108.883;
|
|
|
|
x = x > 0.008856 ? Math.pow(x, 1 / 3) : (7.787 * x) + (16 / 116);
|
|
y = y > 0.008856 ? Math.pow(y, 1 / 3) : (7.787 * y) + (16 / 116);
|
|
z = z > 0.008856 ? Math.pow(z, 1 / 3) : (7.787 * z) + (16 / 116);
|
|
|
|
l = (116 * y) - 16;
|
|
a = 500 * (x - y);
|
|
b = 200 * (y - z);
|
|
|
|
return [l, a, b];
|
|
};
|
|
|
|
convert.hsl.rgb = function (hsl) {
|
|
var h = hsl[0] / 360;
|
|
var s = hsl[1] / 100;
|
|
var l = hsl[2] / 100;
|
|
var t1;
|
|
var t2;
|
|
var t3;
|
|
var rgb;
|
|
var val;
|
|
|
|
if (s === 0) {
|
|
val = l * 255;
|
|
return [val, val, val];
|
|
}
|
|
|
|
if (l < 0.5) {
|
|
t2 = l * (1 + s);
|
|
} else {
|
|
t2 = l + s - l * s;
|
|
}
|
|
|
|
t1 = 2 * l - t2;
|
|
|
|
rgb = [0, 0, 0];
|
|
for (var i = 0; i < 3; i++) {
|
|
t3 = h + 1 / 3 * -(i - 1);
|
|
if (t3 < 0) {
|
|
t3++;
|
|
}
|
|
if (t3 > 1) {
|
|
t3--;
|
|
}
|
|
|
|
if (6 * t3 < 1) {
|
|
val = t1 + (t2 - t1) * 6 * t3;
|
|
} else if (2 * t3 < 1) {
|
|
val = t2;
|
|
} else if (3 * t3 < 2) {
|
|
val = t1 + (t2 - t1) * (2 / 3 - t3) * 6;
|
|
} else {
|
|
val = t1;
|
|
}
|
|
|
|
rgb[i] = val * 255;
|
|
}
|
|
|
|
return rgb;
|
|
};
|
|
|
|
convert.hsl.hsv = function (hsl) {
|
|
var h = hsl[0];
|
|
var s = hsl[1] / 100;
|
|
var l = hsl[2] / 100;
|
|
var smin = s;
|
|
var lmin = Math.max(l, 0.01);
|
|
var sv;
|
|
var v;
|
|
|
|
l *= 2;
|
|
s *= (l <= 1) ? l : 2 - l;
|
|
smin *= lmin <= 1 ? lmin : 2 - lmin;
|
|
v = (l + s) / 2;
|
|
sv = l === 0 ? (2 * smin) / (lmin + smin) : (2 * s) / (l + s);
|
|
|
|
return [h, sv * 100, v * 100];
|
|
};
|
|
|
|
convert.hsv.rgb = function (hsv) {
|
|
var h = hsv[0] / 60;
|
|
var s = hsv[1] / 100;
|
|
var v = hsv[2] / 100;
|
|
var hi = Math.floor(h) % 6;
|
|
|
|
var f = h - Math.floor(h);
|
|
var p = 255 * v * (1 - s);
|
|
var q = 255 * v * (1 - (s * f));
|
|
var t = 255 * v * (1 - (s * (1 - f)));
|
|
v *= 255;
|
|
|
|
switch (hi) {
|
|
case 0:
|
|
return [v, t, p];
|
|
case 1:
|
|
return [q, v, p];
|
|
case 2:
|
|
return [p, v, t];
|
|
case 3:
|
|
return [p, q, v];
|
|
case 4:
|
|
return [t, p, v];
|
|
case 5:
|
|
return [v, p, q];
|
|
}
|
|
};
|
|
|
|
convert.hsv.hsl = function (hsv) {
|
|
var h = hsv[0];
|
|
var s = hsv[1] / 100;
|
|
var v = hsv[2] / 100;
|
|
var vmin = Math.max(v, 0.01);
|
|
var lmin;
|
|
var sl;
|
|
var l;
|
|
|
|
l = (2 - s) * v;
|
|
lmin = (2 - s) * vmin;
|
|
sl = s * vmin;
|
|
sl /= (lmin <= 1) ? lmin : 2 - lmin;
|
|
sl = sl || 0;
|
|
l /= 2;
|
|
|
|
return [h, sl * 100, l * 100];
|
|
};
|
|
|
|
// http://dev.w3.org/csswg/css-color/#hwb-to-rgb
|
|
convert.hwb.rgb = function (hwb) {
|
|
var h = hwb[0] / 360;
|
|
var wh = hwb[1] / 100;
|
|
var bl = hwb[2] / 100;
|
|
var ratio = wh + bl;
|
|
var i;
|
|
var v;
|
|
var f;
|
|
var n;
|
|
|
|
// wh + bl cant be > 1
|
|
if (ratio > 1) {
|
|
wh /= ratio;
|
|
bl /= ratio;
|
|
}
|
|
|
|
i = Math.floor(6 * h);
|
|
v = 1 - bl;
|
|
f = 6 * h - i;
|
|
|
|
if ((i & 0x01) !== 0) {
|
|
f = 1 - f;
|
|
}
|
|
|
|
n = wh + f * (v - wh); // linear interpolation
|
|
|
|
var r;
|
|
var g;
|
|
var b;
|
|
switch (i) {
|
|
default:
|
|
case 6:
|
|
case 0: r = v; g = n; b = wh; break;
|
|
case 1: r = n; g = v; b = wh; break;
|
|
case 2: r = wh; g = v; b = n; break;
|
|
case 3: r = wh; g = n; b = v; break;
|
|
case 4: r = n; g = wh; b = v; break;
|
|
case 5: r = v; g = wh; b = n; break;
|
|
}
|
|
|
|
return [r * 255, g * 255, b * 255];
|
|
};
|
|
|
|
convert.cmyk.rgb = function (cmyk) {
|
|
var c = cmyk[0] / 100;
|
|
var m = cmyk[1] / 100;
|
|
var y = cmyk[2] / 100;
|
|
var k = cmyk[3] / 100;
|
|
var r;
|
|
var g;
|
|
var b;
|
|
|
|
r = 1 - Math.min(1, c * (1 - k) + k);
|
|
g = 1 - Math.min(1, m * (1 - k) + k);
|
|
b = 1 - Math.min(1, y * (1 - k) + k);
|
|
|
|
return [r * 255, g * 255, b * 255];
|
|
};
|
|
|
|
convert.xyz.rgb = function (xyz) {
|
|
var x = xyz[0] / 100;
|
|
var y = xyz[1] / 100;
|
|
var z = xyz[2] / 100;
|
|
var r;
|
|
var g;
|
|
var b;
|
|
|
|
r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986);
|
|
g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415);
|
|
b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570);
|
|
|
|
// assume sRGB
|
|
r = r > 0.0031308
|
|
? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055)
|
|
: r * 12.92;
|
|
|
|
g = g > 0.0031308
|
|
? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055)
|
|
: g * 12.92;
|
|
|
|
b = b > 0.0031308
|
|
? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055)
|
|
: b * 12.92;
|
|
|
|
r = Math.min(Math.max(0, r), 1);
|
|
g = Math.min(Math.max(0, g), 1);
|
|
b = Math.min(Math.max(0, b), 1);
|
|
|
|
return [r * 255, g * 255, b * 255];
|
|
};
|
|
|
|
convert.xyz.lab = function (xyz) {
|
|
var x = xyz[0];
|
|
var y = xyz[1];
|
|
var z = xyz[2];
|
|
var l;
|
|
var a;
|
|
var b;
|
|
|
|
x /= 95.047;
|
|
y /= 100;
|
|
z /= 108.883;
|
|
|
|
x = x > 0.008856 ? Math.pow(x, 1 / 3) : (7.787 * x) + (16 / 116);
|
|
y = y > 0.008856 ? Math.pow(y, 1 / 3) : (7.787 * y) + (16 / 116);
|
|
z = z > 0.008856 ? Math.pow(z, 1 / 3) : (7.787 * z) + (16 / 116);
|
|
|
|
l = (116 * y) - 16;
|
|
a = 500 * (x - y);
|
|
b = 200 * (y - z);
|
|
|
|
return [l, a, b];
|
|
};
|
|
|
|
convert.lab.xyz = function (lab) {
|
|
var l = lab[0];
|
|
var a = lab[1];
|
|
var b = lab[2];
|
|
var x;
|
|
var y;
|
|
var z;
|
|
|
|
y = (l + 16) / 116;
|
|
x = a / 500 + y;
|
|
z = y - b / 200;
|
|
|
|
var y2 = Math.pow(y, 3);
|
|
var x2 = Math.pow(x, 3);
|
|
var z2 = Math.pow(z, 3);
|
|
y = y2 > 0.008856 ? y2 : (y - 16 / 116) / 7.787;
|
|
x = x2 > 0.008856 ? x2 : (x - 16 / 116) / 7.787;
|
|
z = z2 > 0.008856 ? z2 : (z - 16 / 116) / 7.787;
|
|
|
|
x *= 95.047;
|
|
y *= 100;
|
|
z *= 108.883;
|
|
|
|
return [x, y, z];
|
|
};
|
|
|
|
convert.lab.lch = function (lab) {
|
|
var l = lab[0];
|
|
var a = lab[1];
|
|
var b = lab[2];
|
|
var hr;
|
|
var h;
|
|
var c;
|
|
|
|
hr = Math.atan2(b, a);
|
|
h = hr * 360 / 2 / Math.PI;
|
|
|
|
if (h < 0) {
|
|
h += 360;
|
|
}
|
|
|
|
c = Math.sqrt(a * a + b * b);
|
|
|
|
return [l, c, h];
|
|
};
|
|
|
|
convert.lch.lab = function (lch) {
|
|
var l = lch[0];
|
|
var c = lch[1];
|
|
var h = lch[2];
|
|
var a;
|
|
var b;
|
|
var hr;
|
|
|
|
hr = h / 360 * 2 * Math.PI;
|
|
a = c * Math.cos(hr);
|
|
b = c * Math.sin(hr);
|
|
|
|
return [l, a, b];
|
|
};
|
|
|
|
convert.rgb.ansi16 = function (args) {
|
|
var r = args[0];
|
|
var g = args[1];
|
|
var b = args[2];
|
|
var value = 1 in arguments ? arguments[1] : convert.rgb.hsv(args)[2]; // hsv -> ansi16 optimization
|
|
|
|
value = Math.round(value / 50);
|
|
|
|
if (value === 0) {
|
|
return 30;
|
|
}
|
|
|
|
var ansi = 30
|
|
+ ((Math.round(b / 255) << 2)
|
|
| (Math.round(g / 255) << 1)
|
|
| Math.round(r / 255));
|
|
|
|
if (value === 2) {
|
|
ansi += 60;
|
|
}
|
|
|
|
return ansi;
|
|
};
|
|
|
|
convert.hsv.ansi16 = function (args) {
|
|
// optimization here; we already know the value and don't need to get
|
|
// it converted for us.
|
|
return convert.rgb.ansi16(convert.hsv.rgb(args), args[2]);
|
|
};
|
|
|
|
convert.rgb.ansi256 = function (args) {
|
|
var r = args[0];
|
|
var g = args[1];
|
|
var b = args[2];
|
|
|
|
// we use the extended greyscale palette here, with the exception of
|
|
// black and white. normal palette only has 4 greyscale shades.
|
|
if (r === g && g === b) {
|
|
if (r < 8) {
|
|
return 16;
|
|
}
|
|
|
|
if (r > 248) {
|
|
return 231;
|
|
}
|
|
|
|
return Math.round(((r - 8) / 247) * 24) + 232;
|
|
}
|
|
|
|
var ansi = 16
|
|
+ (36 * Math.round(r / 255 * 5))
|
|
+ (6 * Math.round(g / 255 * 5))
|
|
+ Math.round(b / 255 * 5);
|
|
|
|
return ansi;
|
|
};
|
|
|
|
convert.ansi16.rgb = function (args) {
|
|
var color = args % 10;
|
|
|
|
// handle greyscale
|
|
if (color === 0 || color === 7) {
|
|
if (args > 50) {
|
|
color += 3.5;
|
|
}
|
|
|
|
color = color / 10.5 * 255;
|
|
|
|
return [color, color, color];
|
|
}
|
|
|
|
var mult = (~~(args > 50) + 1) * 0.5;
|
|
var r = ((color & 1) * mult) * 255;
|
|
var g = (((color >> 1) & 1) * mult) * 255;
|
|
var b = (((color >> 2) & 1) * mult) * 255;
|
|
|
|
return [r, g, b];
|
|
};
|
|
|
|
convert.ansi256.rgb = function (args) {
|
|
// handle greyscale
|
|
if (args >= 232) {
|
|
var c = (args - 232) * 10 + 8;
|
|
return [c, c, c];
|
|
}
|
|
|
|
args -= 16;
|
|
|
|
var rem;
|
|
var r = Math.floor(args / 36) / 5 * 255;
|
|
var g = Math.floor((rem = args % 36) / 6) / 5 * 255;
|
|
var b = (rem % 6) / 5 * 255;
|
|
|
|
return [r, g, b];
|
|
};
|
|
|
|
convert.rgb.hex = function (args) {
|
|
var integer = ((Math.round(args[0]) & 0xFF) << 16)
|
|
+ ((Math.round(args[1]) & 0xFF) << 8)
|
|
+ (Math.round(args[2]) & 0xFF);
|
|
|
|
var string = integer.toString(16).toUpperCase();
|
|
return '000000'.substring(string.length) + string;
|
|
};
|
|
|
|
convert.hex.rgb = function (args) {
|
|
var match = args.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i);
|
|
if (!match) {
|
|
return [0, 0, 0];
|
|
}
|
|
|
|
var colorString = match[0];
|
|
|
|
if (match[0].length === 3) {
|
|
colorString = colorString.split('').map(function (char) {
|
|
return char + char;
|
|
}).join('');
|
|
}
|
|
|
|
var integer = parseInt(colorString, 16);
|
|
var r = (integer >> 16) & 0xFF;
|
|
var g = (integer >> 8) & 0xFF;
|
|
var b = integer & 0xFF;
|
|
|
|
return [r, g, b];
|
|
};
|
|
|
|
convert.rgb.hcg = function (rgb) {
|
|
var r = rgb[0] / 255;
|
|
var g = rgb[1] / 255;
|
|
var b = rgb[2] / 255;
|
|
var max = Math.max(Math.max(r, g), b);
|
|
var min = Math.min(Math.min(r, g), b);
|
|
var chroma = (max - min);
|
|
var grayscale;
|
|
var hue;
|
|
|
|
if (chroma < 1) {
|
|
grayscale = min / (1 - chroma);
|
|
} else {
|
|
grayscale = 0;
|
|
}
|
|
|
|
if (chroma <= 0) {
|
|
hue = 0;
|
|
} else
|
|
if (max === r) {
|
|
hue = ((g - b) / chroma) % 6;
|
|
} else
|
|
if (max === g) {
|
|
hue = 2 + (b - r) / chroma;
|
|
} else {
|
|
hue = 4 + (r - g) / chroma + 4;
|
|
}
|
|
|
|
hue /= 6;
|
|
hue %= 1;
|
|
|
|
return [hue * 360, chroma * 100, grayscale * 100];
|
|
};
|
|
|
|
convert.hsl.hcg = function (hsl) {
|
|
var s = hsl[1] / 100;
|
|
var l = hsl[2] / 100;
|
|
var c = 1;
|
|
var f = 0;
|
|
|
|
if (l < 0.5) {
|
|
c = 2.0 * s * l;
|
|
} else {
|
|
c = 2.0 * s * (1.0 - l);
|
|
}
|
|
|
|
if (c < 1.0) {
|
|
f = (l - 0.5 * c) / (1.0 - c);
|
|
}
|
|
|
|
return [hsl[0], c * 100, f * 100];
|
|
};
|
|
|
|
convert.hsv.hcg = function (hsv) {
|
|
var s = hsv[1] / 100;
|
|
var v = hsv[2] / 100;
|
|
|
|
var c = s * v;
|
|
var f = 0;
|
|
|
|
if (c < 1.0) {
|
|
f = (v - c) / (1 - c);
|
|
}
|
|
|
|
return [hsv[0], c * 100, f * 100];
|
|
};
|
|
|
|
convert.hcg.rgb = function (hcg) {
|
|
var h = hcg[0] / 360;
|
|
var c = hcg[1] / 100;
|
|
var g = hcg[2] / 100;
|
|
|
|
if (c === 0.0) {
|
|
return [g * 255, g * 255, g * 255];
|
|
}
|
|
|
|
var pure = [0, 0, 0];
|
|
var hi = (h % 1) * 6;
|
|
var v = hi % 1;
|
|
var w = 1 - v;
|
|
var mg = 0;
|
|
|
|
switch (Math.floor(hi)) {
|
|
case 0:
|
|
pure[0] = 1; pure[1] = v; pure[2] = 0; break;
|
|
case 1:
|
|
pure[0] = w; pure[1] = 1; pure[2] = 0; break;
|
|
case 2:
|
|
pure[0] = 0; pure[1] = 1; pure[2] = v; break;
|
|
case 3:
|
|
pure[0] = 0; pure[1] = w; pure[2] = 1; break;
|
|
case 4:
|
|
pure[0] = v; pure[1] = 0; pure[2] = 1; break;
|
|
default:
|
|
pure[0] = 1; pure[1] = 0; pure[2] = w;
|
|
}
|
|
|
|
mg = (1.0 - c) * g;
|
|
|
|
return [
|
|
(c * pure[0] + mg) * 255,
|
|
(c * pure[1] + mg) * 255,
|
|
(c * pure[2] + mg) * 255
|
|
];
|
|
};
|
|
|
|
convert.hcg.hsv = function (hcg) {
|
|
var c = hcg[1] / 100;
|
|
var g = hcg[2] / 100;
|
|
|
|
var v = c + g * (1.0 - c);
|
|
var f = 0;
|
|
|
|
if (v > 0.0) {
|
|
f = c / v;
|
|
}
|
|
|
|
return [hcg[0], f * 100, v * 100];
|
|
};
|
|
|
|
convert.hcg.hsl = function (hcg) {
|
|
var c = hcg[1] / 100;
|
|
var g = hcg[2] / 100;
|
|
|
|
var l = g * (1.0 - c) + 0.5 * c;
|
|
var s = 0;
|
|
|
|
if (l > 0.0 && l < 0.5) {
|
|
s = c / (2 * l);
|
|
} else
|
|
if (l >= 0.5 && l < 1.0) {
|
|
s = c / (2 * (1 - l));
|
|
}
|
|
|
|
return [hcg[0], s * 100, l * 100];
|
|
};
|
|
|
|
convert.hcg.hwb = function (hcg) {
|
|
var c = hcg[1] / 100;
|
|
var g = hcg[2] / 100;
|
|
var v = c + g * (1.0 - c);
|
|
return [hcg[0], (v - c) * 100, (1 - v) * 100];
|
|
};
|
|
|
|
convert.hwb.hcg = function (hwb) {
|
|
var w = hwb[1] / 100;
|
|
var b = hwb[2] / 100;
|
|
var v = 1 - b;
|
|
var c = v - w;
|
|
var g = 0;
|
|
|
|
if (c < 1) {
|
|
g = (v - c) / (1 - c);
|
|
}
|
|
|
|
return [hwb[0], c * 100, g * 100];
|
|
};
|
|
|
|
convert.apple.rgb = function (apple) {
|
|
return [(apple[0] / 65535) * 255, (apple[1] / 65535) * 255, (apple[2] / 65535) * 255];
|
|
};
|
|
|
|
convert.rgb.apple = function (rgb) {
|
|
return [(rgb[0] / 255) * 65535, (rgb[1] / 255) * 65535, (rgb[2] / 255) * 65535];
|
|
};
|
|
|
|
convert.gray.rgb = function (args) {
|
|
return [args[0] / 100 * 255, args[0] / 100 * 255, args[0] / 100 * 255];
|
|
};
|
|
|
|
convert.gray.hsl = convert.gray.hsv = function (args) {
|
|
return [0, 0, args[0]];
|
|
};
|
|
|
|
convert.gray.hwb = function (gray) {
|
|
return [0, 100, gray[0]];
|
|
};
|
|
|
|
convert.gray.cmyk = function (gray) {
|
|
return [0, 0, 0, gray[0]];
|
|
};
|
|
|
|
convert.gray.lab = function (gray) {
|
|
return [gray[0], 0, 0];
|
|
};
|
|
|
|
convert.gray.hex = function (gray) {
|
|
var val = Math.round(gray[0] / 100 * 255) & 0xFF;
|
|
var integer = (val << 16) + (val << 8) + val;
|
|
|
|
var string = integer.toString(16).toUpperCase();
|
|
return '000000'.substring(string.length) + string;
|
|
};
|
|
|
|
convert.rgb.gray = function (rgb) {
|
|
var val = (rgb[0] + rgb[1] + rgb[2]) / 3;
|
|
return [val / 255 * 100];
|
|
};
|
|
});
|
|
var conversions_1 = conversions.rgb;
|
|
var conversions_2 = conversions.hsl;
|
|
var conversions_3 = conversions.hsv;
|
|
var conversions_4 = conversions.hwb;
|
|
var conversions_5 = conversions.cmyk;
|
|
var conversions_6 = conversions.xyz;
|
|
var conversions_7 = conversions.lab;
|
|
var conversions_8 = conversions.lch;
|
|
var conversions_9 = conversions.hex;
|
|
var conversions_10 = conversions.keyword;
|
|
var conversions_11 = conversions.ansi16;
|
|
var conversions_12 = conversions.ansi256;
|
|
var conversions_13 = conversions.hcg;
|
|
var conversions_14 = conversions.apple;
|
|
var conversions_15 = conversions.gray;
|
|
|
|
/*
|
|
this function routes a model to all other models.
|
|
|
|
all functions that are routed have a property `.conversion` attached
|
|
to the returned synthetic function. This property is an array
|
|
of strings, each with the steps in between the 'from' and 'to'
|
|
color models (inclusive).
|
|
|
|
conversions that are not possible simply are not included.
|
|
*/
|
|
|
|
function buildGraph() {
|
|
var graph = {};
|
|
// https://jsperf.com/object-keys-vs-for-in-with-closure/3
|
|
var models = Object.keys(conversions);
|
|
|
|
for (var len = models.length, i = 0; i < len; i++) {
|
|
graph[models[i]] = {
|
|
// http://jsperf.com/1-vs-infinity
|
|
// micro-opt, but this is simple.
|
|
distance: -1,
|
|
parent: null
|
|
};
|
|
}
|
|
|
|
return graph;
|
|
}
|
|
|
|
// https://en.wikipedia.org/wiki/Breadth-first_search
|
|
function deriveBFS(fromModel) {
|
|
var graph = buildGraph();
|
|
var queue = [fromModel]; // unshift -> queue -> pop
|
|
|
|
graph[fromModel].distance = 0;
|
|
|
|
while (queue.length) {
|
|
var current = queue.pop();
|
|
var adjacents = Object.keys(conversions[current]);
|
|
|
|
for (var len = adjacents.length, i = 0; i < len; i++) {
|
|
var adjacent = adjacents[i];
|
|
var node = graph[adjacent];
|
|
|
|
if (node.distance === -1) {
|
|
node.distance = graph[current].distance + 1;
|
|
node.parent = current;
|
|
queue.unshift(adjacent);
|
|
}
|
|
}
|
|
}
|
|
|
|
return graph;
|
|
}
|
|
|
|
function link(from, to) {
|
|
return function (args) {
|
|
return to(from(args));
|
|
};
|
|
}
|
|
|
|
function wrapConversion(toModel, graph) {
|
|
var path = [graph[toModel].parent, toModel];
|
|
var fn = conversions[graph[toModel].parent][toModel];
|
|
|
|
var cur = graph[toModel].parent;
|
|
while (graph[cur].parent) {
|
|
path.unshift(graph[cur].parent);
|
|
fn = link(conversions[graph[cur].parent][cur], fn);
|
|
cur = graph[cur].parent;
|
|
}
|
|
|
|
fn.conversion = path;
|
|
return fn;
|
|
}
|
|
|
|
var route = function (fromModel) {
|
|
var graph = deriveBFS(fromModel);
|
|
var conversion = {};
|
|
|
|
var models = Object.keys(graph);
|
|
for (var len = models.length, i = 0; i < len; i++) {
|
|
var toModel = models[i];
|
|
var node = graph[toModel];
|
|
|
|
if (node.parent === null) {
|
|
// no possible conversion, or this node is the source model.
|
|
continue;
|
|
}
|
|
|
|
conversion[toModel] = wrapConversion(toModel, graph);
|
|
}
|
|
|
|
return conversion;
|
|
};
|
|
|
|
var convert = {};
|
|
|
|
var models = Object.keys(conversions);
|
|
|
|
function wrapRaw(fn) {
|
|
var wrappedFn = function (args) {
|
|
if (args === undefined || args === null) {
|
|
return args;
|
|
}
|
|
|
|
if (arguments.length > 1) {
|
|
args = Array.prototype.slice.call(arguments);
|
|
}
|
|
|
|
return fn(args);
|
|
};
|
|
|
|
// preserve .conversion property if there is one
|
|
if ('conversion' in fn) {
|
|
wrappedFn.conversion = fn.conversion;
|
|
}
|
|
|
|
return wrappedFn;
|
|
}
|
|
|
|
function wrapRounded(fn) {
|
|
var wrappedFn = function (args) {
|
|
if (args === undefined || args === null) {
|
|
return args;
|
|
}
|
|
|
|
if (arguments.length > 1) {
|
|
args = Array.prototype.slice.call(arguments);
|
|
}
|
|
|
|
var result = fn(args);
|
|
|
|
// we're assuming the result is an array here.
|
|
// see notice in conversions.js; don't use box types
|
|
// in conversion functions.
|
|
if (typeof result === 'object') {
|
|
for (var len = result.length, i = 0; i < len; i++) {
|
|
result[i] = Math.round(result[i]);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
// preserve .conversion property if there is one
|
|
if ('conversion' in fn) {
|
|
wrappedFn.conversion = fn.conversion;
|
|
}
|
|
|
|
return wrappedFn;
|
|
}
|
|
|
|
models.forEach(function (fromModel) {
|
|
convert[fromModel] = {};
|
|
|
|
Object.defineProperty(convert[fromModel], 'channels', {value: conversions[fromModel].channels});
|
|
Object.defineProperty(convert[fromModel], 'labels', {value: conversions[fromModel].labels});
|
|
|
|
var routes = route(fromModel);
|
|
var routeModels = Object.keys(routes);
|
|
|
|
routeModels.forEach(function (toModel) {
|
|
var fn = routes[toModel];
|
|
|
|
convert[fromModel][toModel] = wrapRounded(fn);
|
|
convert[fromModel][toModel].raw = wrapRaw(fn);
|
|
});
|
|
});
|
|
|
|
var colorConvert = convert;
|
|
|
|
var colorName$1 = {
|
|
"aliceblue": [240, 248, 255],
|
|
"antiquewhite": [250, 235, 215],
|
|
"aqua": [0, 255, 255],
|
|
"aquamarine": [127, 255, 212],
|
|
"azure": [240, 255, 255],
|
|
"beige": [245, 245, 220],
|
|
"bisque": [255, 228, 196],
|
|
"black": [0, 0, 0],
|
|
"blanchedalmond": [255, 235, 205],
|
|
"blue": [0, 0, 255],
|
|
"blueviolet": [138, 43, 226],
|
|
"brown": [165, 42, 42],
|
|
"burlywood": [222, 184, 135],
|
|
"cadetblue": [95, 158, 160],
|
|
"chartreuse": [127, 255, 0],
|
|
"chocolate": [210, 105, 30],
|
|
"coral": [255, 127, 80],
|
|
"cornflowerblue": [100, 149, 237],
|
|
"cornsilk": [255, 248, 220],
|
|
"crimson": [220, 20, 60],
|
|
"cyan": [0, 255, 255],
|
|
"darkblue": [0, 0, 139],
|
|
"darkcyan": [0, 139, 139],
|
|
"darkgoldenrod": [184, 134, 11],
|
|
"darkgray": [169, 169, 169],
|
|
"darkgreen": [0, 100, 0],
|
|
"darkgrey": [169, 169, 169],
|
|
"darkkhaki": [189, 183, 107],
|
|
"darkmagenta": [139, 0, 139],
|
|
"darkolivegreen": [85, 107, 47],
|
|
"darkorange": [255, 140, 0],
|
|
"darkorchid": [153, 50, 204],
|
|
"darkred": [139, 0, 0],
|
|
"darksalmon": [233, 150, 122],
|
|
"darkseagreen": [143, 188, 143],
|
|
"darkslateblue": [72, 61, 139],
|
|
"darkslategray": [47, 79, 79],
|
|
"darkslategrey": [47, 79, 79],
|
|
"darkturquoise": [0, 206, 209],
|
|
"darkviolet": [148, 0, 211],
|
|
"deeppink": [255, 20, 147],
|
|
"deepskyblue": [0, 191, 255],
|
|
"dimgray": [105, 105, 105],
|
|
"dimgrey": [105, 105, 105],
|
|
"dodgerblue": [30, 144, 255],
|
|
"firebrick": [178, 34, 34],
|
|
"floralwhite": [255, 250, 240],
|
|
"forestgreen": [34, 139, 34],
|
|
"fuchsia": [255, 0, 255],
|
|
"gainsboro": [220, 220, 220],
|
|
"ghostwhite": [248, 248, 255],
|
|
"gold": [255, 215, 0],
|
|
"goldenrod": [218, 165, 32],
|
|
"gray": [128, 128, 128],
|
|
"green": [0, 128, 0],
|
|
"greenyellow": [173, 255, 47],
|
|
"grey": [128, 128, 128],
|
|
"honeydew": [240, 255, 240],
|
|
"hotpink": [255, 105, 180],
|
|
"indianred": [205, 92, 92],
|
|
"indigo": [75, 0, 130],
|
|
"ivory": [255, 255, 240],
|
|
"khaki": [240, 230, 140],
|
|
"lavender": [230, 230, 250],
|
|
"lavenderblush": [255, 240, 245],
|
|
"lawngreen": [124, 252, 0],
|
|
"lemonchiffon": [255, 250, 205],
|
|
"lightblue": [173, 216, 230],
|
|
"lightcoral": [240, 128, 128],
|
|
"lightcyan": [224, 255, 255],
|
|
"lightgoldenrodyellow": [250, 250, 210],
|
|
"lightgray": [211, 211, 211],
|
|
"lightgreen": [144, 238, 144],
|
|
"lightgrey": [211, 211, 211],
|
|
"lightpink": [255, 182, 193],
|
|
"lightsalmon": [255, 160, 122],
|
|
"lightseagreen": [32, 178, 170],
|
|
"lightskyblue": [135, 206, 250],
|
|
"lightslategray": [119, 136, 153],
|
|
"lightslategrey": [119, 136, 153],
|
|
"lightsteelblue": [176, 196, 222],
|
|
"lightyellow": [255, 255, 224],
|
|
"lime": [0, 255, 0],
|
|
"limegreen": [50, 205, 50],
|
|
"linen": [250, 240, 230],
|
|
"magenta": [255, 0, 255],
|
|
"maroon": [128, 0, 0],
|
|
"mediumaquamarine": [102, 205, 170],
|
|
"mediumblue": [0, 0, 205],
|
|
"mediumorchid": [186, 85, 211],
|
|
"mediumpurple": [147, 112, 219],
|
|
"mediumseagreen": [60, 179, 113],
|
|
"mediumslateblue": [123, 104, 238],
|
|
"mediumspringgreen": [0, 250, 154],
|
|
"mediumturquoise": [72, 209, 204],
|
|
"mediumvioletred": [199, 21, 133],
|
|
"midnightblue": [25, 25, 112],
|
|
"mintcream": [245, 255, 250],
|
|
"mistyrose": [255, 228, 225],
|
|
"moccasin": [255, 228, 181],
|
|
"navajowhite": [255, 222, 173],
|
|
"navy": [0, 0, 128],
|
|
"oldlace": [253, 245, 230],
|
|
"olive": [128, 128, 0],
|
|
"olivedrab": [107, 142, 35],
|
|
"orange": [255, 165, 0],
|
|
"orangered": [255, 69, 0],
|
|
"orchid": [218, 112, 214],
|
|
"palegoldenrod": [238, 232, 170],
|
|
"palegreen": [152, 251, 152],
|
|
"paleturquoise": [175, 238, 238],
|
|
"palevioletred": [219, 112, 147],
|
|
"papayawhip": [255, 239, 213],
|
|
"peachpuff": [255, 218, 185],
|
|
"peru": [205, 133, 63],
|
|
"pink": [255, 192, 203],
|
|
"plum": [221, 160, 221],
|
|
"powderblue": [176, 224, 230],
|
|
"purple": [128, 0, 128],
|
|
"rebeccapurple": [102, 51, 153],
|
|
"red": [255, 0, 0],
|
|
"rosybrown": [188, 143, 143],
|
|
"royalblue": [65, 105, 225],
|
|
"saddlebrown": [139, 69, 19],
|
|
"salmon": [250, 128, 114],
|
|
"sandybrown": [244, 164, 96],
|
|
"seagreen": [46, 139, 87],
|
|
"seashell": [255, 245, 238],
|
|
"sienna": [160, 82, 45],
|
|
"silver": [192, 192, 192],
|
|
"skyblue": [135, 206, 235],
|
|
"slateblue": [106, 90, 205],
|
|
"slategray": [112, 128, 144],
|
|
"slategrey": [112, 128, 144],
|
|
"snow": [255, 250, 250],
|
|
"springgreen": [0, 255, 127],
|
|
"steelblue": [70, 130, 180],
|
|
"tan": [210, 180, 140],
|
|
"teal": [0, 128, 128],
|
|
"thistle": [216, 191, 216],
|
|
"tomato": [255, 99, 71],
|
|
"turquoise": [64, 224, 208],
|
|
"violet": [238, 130, 238],
|
|
"wheat": [245, 222, 179],
|
|
"white": [255, 255, 255],
|
|
"whitesmoke": [245, 245, 245],
|
|
"yellow": [255, 255, 0],
|
|
"yellowgreen": [154, 205, 50]
|
|
};
|
|
|
|
/* MIT license */
|
|
|
|
|
|
var colorString = {
|
|
getRgba: getRgba,
|
|
getHsla: getHsla,
|
|
getRgb: getRgb,
|
|
getHsl: getHsl,
|
|
getHwb: getHwb,
|
|
getAlpha: getAlpha,
|
|
|
|
hexString: hexString,
|
|
rgbString: rgbString,
|
|
rgbaString: rgbaString,
|
|
percentString: percentString,
|
|
percentaString: percentaString,
|
|
hslString: hslString,
|
|
hslaString: hslaString,
|
|
hwbString: hwbString,
|
|
keyword: keyword
|
|
};
|
|
|
|
function getRgba(string) {
|
|
if (!string) {
|
|
return;
|
|
}
|
|
var abbr = /^#([a-fA-F0-9]{3,4})$/i,
|
|
hex = /^#([a-fA-F0-9]{6}([a-fA-F0-9]{2})?)$/i,
|
|
rgba = /^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i,
|
|
per = /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i,
|
|
keyword = /(\w+)/;
|
|
|
|
var rgb = [0, 0, 0],
|
|
a = 1,
|
|
match = string.match(abbr),
|
|
hexAlpha = "";
|
|
if (match) {
|
|
match = match[1];
|
|
hexAlpha = match[3];
|
|
for (var i = 0; i < rgb.length; i++) {
|
|
rgb[i] = parseInt(match[i] + match[i], 16);
|
|
}
|
|
if (hexAlpha) {
|
|
a = Math.round((parseInt(hexAlpha + hexAlpha, 16) / 255) * 100) / 100;
|
|
}
|
|
}
|
|
else if (match = string.match(hex)) {
|
|
hexAlpha = match[2];
|
|
match = match[1];
|
|
for (var i = 0; i < rgb.length; i++) {
|
|
rgb[i] = parseInt(match.slice(i * 2, i * 2 + 2), 16);
|
|
}
|
|
if (hexAlpha) {
|
|
a = Math.round((parseInt(hexAlpha, 16) / 255) * 100) / 100;
|
|
}
|
|
}
|
|
else if (match = string.match(rgba)) {
|
|
for (var i = 0; i < rgb.length; i++) {
|
|
rgb[i] = parseInt(match[i + 1]);
|
|
}
|
|
a = parseFloat(match[4]);
|
|
}
|
|
else if (match = string.match(per)) {
|
|
for (var i = 0; i < rgb.length; i++) {
|
|
rgb[i] = Math.round(parseFloat(match[i + 1]) * 2.55);
|
|
}
|
|
a = parseFloat(match[4]);
|
|
}
|
|
else if (match = string.match(keyword)) {
|
|
if (match[1] == "transparent") {
|
|
return [0, 0, 0, 0];
|
|
}
|
|
rgb = colorName$1[match[1]];
|
|
if (!rgb) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (var i = 0; i < rgb.length; i++) {
|
|
rgb[i] = scale(rgb[i], 0, 255);
|
|
}
|
|
if (!a && a != 0) {
|
|
a = 1;
|
|
}
|
|
else {
|
|
a = scale(a, 0, 1);
|
|
}
|
|
rgb[3] = a;
|
|
return rgb;
|
|
}
|
|
|
|
function getHsla(string) {
|
|
if (!string) {
|
|
return;
|
|
}
|
|
var hsl = /^hsla?\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/;
|
|
var match = string.match(hsl);
|
|
if (match) {
|
|
var alpha = parseFloat(match[4]);
|
|
var h = scale(parseInt(match[1]), 0, 360),
|
|
s = scale(parseFloat(match[2]), 0, 100),
|
|
l = scale(parseFloat(match[3]), 0, 100),
|
|
a = scale(isNaN(alpha) ? 1 : alpha, 0, 1);
|
|
return [h, s, l, a];
|
|
}
|
|
}
|
|
|
|
function getHwb(string) {
|
|
if (!string) {
|
|
return;
|
|
}
|
|
var hwb = /^hwb\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/;
|
|
var match = string.match(hwb);
|
|
if (match) {
|
|
var alpha = parseFloat(match[4]);
|
|
var h = scale(parseInt(match[1]), 0, 360),
|
|
w = scale(parseFloat(match[2]), 0, 100),
|
|
b = scale(parseFloat(match[3]), 0, 100),
|
|
a = scale(isNaN(alpha) ? 1 : alpha, 0, 1);
|
|
return [h, w, b, a];
|
|
}
|
|
}
|
|
|
|
function getRgb(string) {
|
|
var rgba = getRgba(string);
|
|
return rgba && rgba.slice(0, 3);
|
|
}
|
|
|
|
function getHsl(string) {
|
|
var hsla = getHsla(string);
|
|
return hsla && hsla.slice(0, 3);
|
|
}
|
|
|
|
function getAlpha(string) {
|
|
var vals = getRgba(string);
|
|
if (vals) {
|
|
return vals[3];
|
|
}
|
|
else if (vals = getHsla(string)) {
|
|
return vals[3];
|
|
}
|
|
else if (vals = getHwb(string)) {
|
|
return vals[3];
|
|
}
|
|
}
|
|
|
|
// generators
|
|
function hexString(rgba, a) {
|
|
var a = (a !== undefined && rgba.length === 3) ? a : rgba[3];
|
|
return "#" + hexDouble(rgba[0])
|
|
+ hexDouble(rgba[1])
|
|
+ hexDouble(rgba[2])
|
|
+ (
|
|
(a >= 0 && a < 1)
|
|
? hexDouble(Math.round(a * 255))
|
|
: ""
|
|
);
|
|
}
|
|
|
|
function rgbString(rgba, alpha) {
|
|
if (alpha < 1 || (rgba[3] && rgba[3] < 1)) {
|
|
return rgbaString(rgba, alpha);
|
|
}
|
|
return "rgb(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + ")";
|
|
}
|
|
|
|
function rgbaString(rgba, alpha) {
|
|
if (alpha === undefined) {
|
|
alpha = (rgba[3] !== undefined ? rgba[3] : 1);
|
|
}
|
|
return "rgba(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2]
|
|
+ ", " + alpha + ")";
|
|
}
|
|
|
|
function percentString(rgba, alpha) {
|
|
if (alpha < 1 || (rgba[3] && rgba[3] < 1)) {
|
|
return percentaString(rgba, alpha);
|
|
}
|
|
var r = Math.round(rgba[0]/255 * 100),
|
|
g = Math.round(rgba[1]/255 * 100),
|
|
b = Math.round(rgba[2]/255 * 100);
|
|
|
|
return "rgb(" + r + "%, " + g + "%, " + b + "%)";
|
|
}
|
|
|
|
function percentaString(rgba, alpha) {
|
|
var r = Math.round(rgba[0]/255 * 100),
|
|
g = Math.round(rgba[1]/255 * 100),
|
|
b = Math.round(rgba[2]/255 * 100);
|
|
return "rgba(" + r + "%, " + g + "%, " + b + "%, " + (alpha || rgba[3] || 1) + ")";
|
|
}
|
|
|
|
function hslString(hsla, alpha) {
|
|
if (alpha < 1 || (hsla[3] && hsla[3] < 1)) {
|
|
return hslaString(hsla, alpha);
|
|
}
|
|
return "hsl(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%)";
|
|
}
|
|
|
|
function hslaString(hsla, alpha) {
|
|
if (alpha === undefined) {
|
|
alpha = (hsla[3] !== undefined ? hsla[3] : 1);
|
|
}
|
|
return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, "
|
|
+ alpha + ")";
|
|
}
|
|
|
|
// hwb is a bit different than rgb(a) & hsl(a) since there is no alpha specific syntax
|
|
// (hwb have alpha optional & 1 is default value)
|
|
function hwbString(hwb, alpha) {
|
|
if (alpha === undefined) {
|
|
alpha = (hwb[3] !== undefined ? hwb[3] : 1);
|
|
}
|
|
return "hwb(" + hwb[0] + ", " + hwb[1] + "%, " + hwb[2] + "%"
|
|
+ (alpha !== undefined && alpha !== 1 ? ", " + alpha : "") + ")";
|
|
}
|
|
|
|
function keyword(rgb) {
|
|
return reverseNames[rgb.slice(0, 3)];
|
|
}
|
|
|
|
// helpers
|
|
function scale(num, min, max) {
|
|
return Math.min(Math.max(min, num), max);
|
|
}
|
|
|
|
function hexDouble(num) {
|
|
var str = num.toString(16).toUpperCase();
|
|
return (str.length < 2) ? "0" + str : str;
|
|
}
|
|
|
|
|
|
//create a list of reverse color names
|
|
var reverseNames = {};
|
|
for (var name in colorName$1) {
|
|
reverseNames[colorName$1[name]] = name;
|
|
}
|
|
|
|
/* MIT license */
|
|
|
|
|
|
|
|
var Color = function (obj) {
|
|
if (obj instanceof Color) {
|
|
return obj;
|
|
}
|
|
if (!(this instanceof Color)) {
|
|
return new Color(obj);
|
|
}
|
|
|
|
this.valid = false;
|
|
this.values = {
|
|
rgb: [0, 0, 0],
|
|
hsl: [0, 0, 0],
|
|
hsv: [0, 0, 0],
|
|
hwb: [0, 0, 0],
|
|
cmyk: [0, 0, 0, 0],
|
|
alpha: 1
|
|
};
|
|
|
|
// parse Color() argument
|
|
var vals;
|
|
if (typeof obj === 'string') {
|
|
vals = colorString.getRgba(obj);
|
|
if (vals) {
|
|
this.setValues('rgb', vals);
|
|
} else if (vals = colorString.getHsla(obj)) {
|
|
this.setValues('hsl', vals);
|
|
} else if (vals = colorString.getHwb(obj)) {
|
|
this.setValues('hwb', vals);
|
|
}
|
|
} else if (typeof obj === 'object') {
|
|
vals = obj;
|
|
if (vals.r !== undefined || vals.red !== undefined) {
|
|
this.setValues('rgb', vals);
|
|
} else if (vals.l !== undefined || vals.lightness !== undefined) {
|
|
this.setValues('hsl', vals);
|
|
} else if (vals.v !== undefined || vals.value !== undefined) {
|
|
this.setValues('hsv', vals);
|
|
} else if (vals.w !== undefined || vals.whiteness !== undefined) {
|
|
this.setValues('hwb', vals);
|
|
} else if (vals.c !== undefined || vals.cyan !== undefined) {
|
|
this.setValues('cmyk', vals);
|
|
}
|
|
}
|
|
};
|
|
|
|
Color.prototype = {
|
|
isValid: function () {
|
|
return this.valid;
|
|
},
|
|
rgb: function () {
|
|
return this.setSpace('rgb', arguments);
|
|
},
|
|
hsl: function () {
|
|
return this.setSpace('hsl', arguments);
|
|
},
|
|
hsv: function () {
|
|
return this.setSpace('hsv', arguments);
|
|
},
|
|
hwb: function () {
|
|
return this.setSpace('hwb', arguments);
|
|
},
|
|
cmyk: function () {
|
|
return this.setSpace('cmyk', arguments);
|
|
},
|
|
|
|
rgbArray: function () {
|
|
return this.values.rgb;
|
|
},
|
|
hslArray: function () {
|
|
return this.values.hsl;
|
|
},
|
|
hsvArray: function () {
|
|
return this.values.hsv;
|
|
},
|
|
hwbArray: function () {
|
|
var values = this.values;
|
|
if (values.alpha !== 1) {
|
|
return values.hwb.concat([values.alpha]);
|
|
}
|
|
return values.hwb;
|
|
},
|
|
cmykArray: function () {
|
|
return this.values.cmyk;
|
|
},
|
|
rgbaArray: function () {
|
|
var values = this.values;
|
|
return values.rgb.concat([values.alpha]);
|
|
},
|
|
hslaArray: function () {
|
|
var values = this.values;
|
|
return values.hsl.concat([values.alpha]);
|
|
},
|
|
alpha: function (val) {
|
|
if (val === undefined) {
|
|
return this.values.alpha;
|
|
}
|
|
this.setValues('alpha', val);
|
|
return this;
|
|
},
|
|
|
|
red: function (val) {
|
|
return this.setChannel('rgb', 0, val);
|
|
},
|
|
green: function (val) {
|
|
return this.setChannel('rgb', 1, val);
|
|
},
|
|
blue: function (val) {
|
|
return this.setChannel('rgb', 2, val);
|
|
},
|
|
hue: function (val) {
|
|
if (val) {
|
|
val %= 360;
|
|
val = val < 0 ? 360 + val : val;
|
|
}
|
|
return this.setChannel('hsl', 0, val);
|
|
},
|
|
saturation: function (val) {
|
|
return this.setChannel('hsl', 1, val);
|
|
},
|
|
lightness: function (val) {
|
|
return this.setChannel('hsl', 2, val);
|
|
},
|
|
saturationv: function (val) {
|
|
return this.setChannel('hsv', 1, val);
|
|
},
|
|
whiteness: function (val) {
|
|
return this.setChannel('hwb', 1, val);
|
|
},
|
|
blackness: function (val) {
|
|
return this.setChannel('hwb', 2, val);
|
|
},
|
|
value: function (val) {
|
|
return this.setChannel('hsv', 2, val);
|
|
},
|
|
cyan: function (val) {
|
|
return this.setChannel('cmyk', 0, val);
|
|
},
|
|
magenta: function (val) {
|
|
return this.setChannel('cmyk', 1, val);
|
|
},
|
|
yellow: function (val) {
|
|
return this.setChannel('cmyk', 2, val);
|
|
},
|
|
black: function (val) {
|
|
return this.setChannel('cmyk', 3, val);
|
|
},
|
|
|
|
hexString: function () {
|
|
return colorString.hexString(this.values.rgb);
|
|
},
|
|
rgbString: function () {
|
|
return colorString.rgbString(this.values.rgb, this.values.alpha);
|
|
},
|
|
rgbaString: function () {
|
|
return colorString.rgbaString(this.values.rgb, this.values.alpha);
|
|
},
|
|
percentString: function () {
|
|
return colorString.percentString(this.values.rgb, this.values.alpha);
|
|
},
|
|
hslString: function () {
|
|
return colorString.hslString(this.values.hsl, this.values.alpha);
|
|
},
|
|
hslaString: function () {
|
|
return colorString.hslaString(this.values.hsl, this.values.alpha);
|
|
},
|
|
hwbString: function () {
|
|
return colorString.hwbString(this.values.hwb, this.values.alpha);
|
|
},
|
|
keyword: function () {
|
|
return colorString.keyword(this.values.rgb, this.values.alpha);
|
|
},
|
|
|
|
rgbNumber: function () {
|
|
var rgb = this.values.rgb;
|
|
return (rgb[0] << 16) | (rgb[1] << 8) | rgb[2];
|
|
},
|
|
|
|
luminosity: function () {
|
|
// http://www.w3.org/TR/WCAG20/#relativeluminancedef
|
|
var rgb = this.values.rgb;
|
|
var lum = [];
|
|
for (var i = 0; i < rgb.length; i++) {
|
|
var chan = rgb[i] / 255;
|
|
lum[i] = (chan <= 0.03928) ? chan / 12.92 : Math.pow(((chan + 0.055) / 1.055), 2.4);
|
|
}
|
|
return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2];
|
|
},
|
|
|
|
contrast: function (color2) {
|
|
// http://www.w3.org/TR/WCAG20/#contrast-ratiodef
|
|
var lum1 = this.luminosity();
|
|
var lum2 = color2.luminosity();
|
|
if (lum1 > lum2) {
|
|
return (lum1 + 0.05) / (lum2 + 0.05);
|
|
}
|
|
return (lum2 + 0.05) / (lum1 + 0.05);
|
|
},
|
|
|
|
level: function (color2) {
|
|
var contrastRatio = this.contrast(color2);
|
|
if (contrastRatio >= 7.1) {
|
|
return 'AAA';
|
|
}
|
|
|
|
return (contrastRatio >= 4.5) ? 'AA' : '';
|
|
},
|
|
|
|
dark: function () {
|
|
// YIQ equation from http://24ways.org/2010/calculating-color-contrast
|
|
var rgb = this.values.rgb;
|
|
var yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000;
|
|
return yiq < 128;
|
|
},
|
|
|
|
light: function () {
|
|
return !this.dark();
|
|
},
|
|
|
|
negate: function () {
|
|
var rgb = [];
|
|
for (var i = 0; i < 3; i++) {
|
|
rgb[i] = 255 - this.values.rgb[i];
|
|
}
|
|
this.setValues('rgb', rgb);
|
|
return this;
|
|
},
|
|
|
|
lighten: function (ratio) {
|
|
var hsl = this.values.hsl;
|
|
hsl[2] += hsl[2] * ratio;
|
|
this.setValues('hsl', hsl);
|
|
return this;
|
|
},
|
|
|
|
darken: function (ratio) {
|
|
var hsl = this.values.hsl;
|
|
hsl[2] -= hsl[2] * ratio;
|
|
this.setValues('hsl', hsl);
|
|
return this;
|
|
},
|
|
|
|
saturate: function (ratio) {
|
|
var hsl = this.values.hsl;
|
|
hsl[1] += hsl[1] * ratio;
|
|
this.setValues('hsl', hsl);
|
|
return this;
|
|
},
|
|
|
|
desaturate: function (ratio) {
|
|
var hsl = this.values.hsl;
|
|
hsl[1] -= hsl[1] * ratio;
|
|
this.setValues('hsl', hsl);
|
|
return this;
|
|
},
|
|
|
|
whiten: function (ratio) {
|
|
var hwb = this.values.hwb;
|
|
hwb[1] += hwb[1] * ratio;
|
|
this.setValues('hwb', hwb);
|
|
return this;
|
|
},
|
|
|
|
blacken: function (ratio) {
|
|
var hwb = this.values.hwb;
|
|
hwb[2] += hwb[2] * ratio;
|
|
this.setValues('hwb', hwb);
|
|
return this;
|
|
},
|
|
|
|
greyscale: function () {
|
|
var rgb = this.values.rgb;
|
|
// http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
|
|
var val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11;
|
|
this.setValues('rgb', [val, val, val]);
|
|
return this;
|
|
},
|
|
|
|
clearer: function (ratio) {
|
|
var alpha = this.values.alpha;
|
|
this.setValues('alpha', alpha - (alpha * ratio));
|
|
return this;
|
|
},
|
|
|
|
opaquer: function (ratio) {
|
|
var alpha = this.values.alpha;
|
|
this.setValues('alpha', alpha + (alpha * ratio));
|
|
return this;
|
|
},
|
|
|
|
rotate: function (degrees) {
|
|
var hsl = this.values.hsl;
|
|
var hue = (hsl[0] + degrees) % 360;
|
|
hsl[0] = hue < 0 ? 360 + hue : hue;
|
|
this.setValues('hsl', hsl);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Ported from sass implementation in C
|
|
* https://github.com/sass/libsass/blob/0e6b4a2850092356aa3ece07c6b249f0221caced/functions.cpp#L209
|
|
*/
|
|
mix: function (mixinColor, weight) {
|
|
var color1 = this;
|
|
var color2 = mixinColor;
|
|
var p = weight === undefined ? 0.5 : weight;
|
|
|
|
var w = 2 * p - 1;
|
|
var a = color1.alpha() - color2.alpha();
|
|
|
|
var w1 = (((w * a === -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0;
|
|
var w2 = 1 - w1;
|
|
|
|
return this
|
|
.rgb(
|
|
w1 * color1.red() + w2 * color2.red(),
|
|
w1 * color1.green() + w2 * color2.green(),
|
|
w1 * color1.blue() + w2 * color2.blue()
|
|
)
|
|
.alpha(color1.alpha() * p + color2.alpha() * (1 - p));
|
|
},
|
|
|
|
toJSON: function () {
|
|
return this.rgb();
|
|
},
|
|
|
|
clone: function () {
|
|
// NOTE(SB): using node-clone creates a dependency to Buffer when using browserify,
|
|
// making the final build way to big to embed in Chart.js. So let's do it manually,
|
|
// assuming that values to clone are 1 dimension arrays containing only numbers,
|
|
// except 'alpha' which is a number.
|
|
var result = new Color();
|
|
var source = this.values;
|
|
var target = result.values;
|
|
var value, type;
|
|
|
|
for (var prop in source) {
|
|
if (source.hasOwnProperty(prop)) {
|
|
value = source[prop];
|
|
type = ({}).toString.call(value);
|
|
if (type === '[object Array]') {
|
|
target[prop] = value.slice(0);
|
|
} else if (type === '[object Number]') {
|
|
target[prop] = value;
|
|
} else {
|
|
console.error('unexpected color value:', value);
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
};
|
|
|
|
Color.prototype.spaces = {
|
|
rgb: ['red', 'green', 'blue'],
|
|
hsl: ['hue', 'saturation', 'lightness'],
|
|
hsv: ['hue', 'saturation', 'value'],
|
|
hwb: ['hue', 'whiteness', 'blackness'],
|
|
cmyk: ['cyan', 'magenta', 'yellow', 'black']
|
|
};
|
|
|
|
Color.prototype.maxes = {
|
|
rgb: [255, 255, 255],
|
|
hsl: [360, 100, 100],
|
|
hsv: [360, 100, 100],
|
|
hwb: [360, 100, 100],
|
|
cmyk: [100, 100, 100, 100]
|
|
};
|
|
|
|
Color.prototype.getValues = function (space) {
|
|
var values = this.values;
|
|
var vals = {};
|
|
|
|
for (var i = 0; i < space.length; i++) {
|
|
vals[space.charAt(i)] = values[space][i];
|
|
}
|
|
|
|
if (values.alpha !== 1) {
|
|
vals.a = values.alpha;
|
|
}
|
|
|
|
// {r: 255, g: 255, b: 255, a: 0.4}
|
|
return vals;
|
|
};
|
|
|
|
Color.prototype.setValues = function (space, vals) {
|
|
var values = this.values;
|
|
var spaces = this.spaces;
|
|
var maxes = this.maxes;
|
|
var alpha = 1;
|
|
var i;
|
|
|
|
this.valid = true;
|
|
|
|
if (space === 'alpha') {
|
|
alpha = vals;
|
|
} else if (vals.length) {
|
|
// [10, 10, 10]
|
|
values[space] = vals.slice(0, space.length);
|
|
alpha = vals[space.length];
|
|
} else if (vals[space.charAt(0)] !== undefined) {
|
|
// {r: 10, g: 10, b: 10}
|
|
for (i = 0; i < space.length; i++) {
|
|
values[space][i] = vals[space.charAt(i)];
|
|
}
|
|
|
|
alpha = vals.a;
|
|
} else if (vals[spaces[space][0]] !== undefined) {
|
|
// {red: 10, green: 10, blue: 10}
|
|
var chans = spaces[space];
|
|
|
|
for (i = 0; i < space.length; i++) {
|
|
values[space][i] = vals[chans[i]];
|
|
}
|
|
|
|
alpha = vals.alpha;
|
|
}
|
|
|
|
values.alpha = Math.max(0, Math.min(1, (alpha === undefined ? values.alpha : alpha)));
|
|
|
|
if (space === 'alpha') {
|
|
return false;
|
|
}
|
|
|
|
var capped;
|
|
|
|
// cap values of the space prior converting all values
|
|
for (i = 0; i < space.length; i++) {
|
|
capped = Math.max(0, Math.min(maxes[space][i], values[space][i]));
|
|
values[space][i] = Math.round(capped);
|
|
}
|
|
|
|
// convert to all the other color spaces
|
|
for (var sname in spaces) {
|
|
if (sname !== space) {
|
|
values[sname] = colorConvert[space][sname](values[space]);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
Color.prototype.setSpace = function (space, args) {
|
|
var vals = args[0];
|
|
|
|
if (vals === undefined) {
|
|
// color.rgb()
|
|
return this.getValues(space);
|
|
}
|
|
|
|
// color.rgb(10, 10, 10)
|
|
if (typeof vals === 'number') {
|
|
vals = Array.prototype.slice.call(args);
|
|
}
|
|
|
|
this.setValues(space, vals);
|
|
return this;
|
|
};
|
|
|
|
Color.prototype.setChannel = function (space, index, val) {
|
|
var svalues = this.values[space];
|
|
if (val === undefined) {
|
|
// color.red()
|
|
return svalues[index];
|
|
} else if (val === svalues[index]) {
|
|
// color.red(color.red())
|
|
return this;
|
|
}
|
|
|
|
// color.red(100)
|
|
svalues[index] = val;
|
|
this.setValues(space, svalues);
|
|
|
|
return this;
|
|
};
|
|
|
|
if (typeof window !== 'undefined') {
|
|
window.Color = Color;
|
|
}
|
|
|
|
var chartjsColor = Color;
|
|
|
|
/**
|
|
* @namespace Chart.helpers
|
|
*/
|
|
var helpers = {
|
|
/**
|
|
* An empty function that can be used, for example, for optional callback.
|
|
*/
|
|
noop: function() {},
|
|
|
|
/**
|
|
* Returns a unique id, sequentially generated from a global variable.
|
|
* @returns {number}
|
|
* @function
|
|
*/
|
|
uid: (function() {
|
|
var id = 0;
|
|
return function() {
|
|
return id++;
|
|
};
|
|
}()),
|
|
|
|
/**
|
|
* Returns true if `value` is neither null nor undefined, else returns false.
|
|
* @param {*} value - The value to test.
|
|
* @returns {boolean}
|
|
* @since 2.7.0
|
|
*/
|
|
isNullOrUndef: function(value) {
|
|
return value === null || typeof value === 'undefined';
|
|
},
|
|
|
|
/**
|
|
* Returns true if `value` is an array (including typed arrays), else returns false.
|
|
* @param {*} value - The value to test.
|
|
* @returns {boolean}
|
|
* @function
|
|
*/
|
|
isArray: function(value) {
|
|
if (Array.isArray && Array.isArray(value)) {
|
|
return true;
|
|
}
|
|
var type = Object.prototype.toString.call(value);
|
|
if (type.substr(0, 7) === '[object' && type.substr(-6) === 'Array]') {
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
|
|
/**
|
|
* Returns true if `value` is an object (excluding null), else returns false.
|
|
* @param {*} value - The value to test.
|
|
* @returns {boolean}
|
|
* @since 2.7.0
|
|
*/
|
|
isObject: function(value) {
|
|
return value !== null && Object.prototype.toString.call(value) === '[object Object]';
|
|
},
|
|
|
|
/**
|
|
* Returns true if `value` is a finite number, else returns false
|
|
* @param {*} value - The value to test.
|
|
* @returns {boolean}
|
|
*/
|
|
isFinite: function(value) {
|
|
return (typeof value === 'number' || value instanceof Number) && isFinite(value);
|
|
},
|
|
|
|
/**
|
|
* Returns `value` if defined, else returns `defaultValue`.
|
|
* @param {*} value - The value to return if defined.
|
|
* @param {*} defaultValue - The value to return if `value` is undefined.
|
|
* @returns {*}
|
|
*/
|
|
valueOrDefault: function(value, defaultValue) {
|
|
return typeof value === 'undefined' ? defaultValue : value;
|
|
},
|
|
|
|
/**
|
|
* Returns value at the given `index` in array if defined, else returns `defaultValue`.
|
|
* @param {Array} value - The array to lookup for value at `index`.
|
|
* @param {number} index - The index in `value` to lookup for value.
|
|
* @param {*} defaultValue - The value to return if `value[index]` is undefined.
|
|
* @returns {*}
|
|
*/
|
|
valueAtIndexOrDefault: function(value, index, defaultValue) {
|
|
return helpers.valueOrDefault(helpers.isArray(value) ? value[index] : value, defaultValue);
|
|
},
|
|
|
|
/**
|
|
* Calls `fn` with the given `args` in the scope defined by `thisArg` and returns the
|
|
* value returned by `fn`. If `fn` is not a function, this method returns undefined.
|
|
* @param {function} fn - The function to call.
|
|
* @param {Array|undefined|null} args - The arguments with which `fn` should be called.
|
|
* @param {object} [thisArg] - The value of `this` provided for the call to `fn`.
|
|
* @returns {*}
|
|
*/
|
|
callback: function(fn, args, thisArg) {
|
|
if (fn && typeof fn.call === 'function') {
|
|
return fn.apply(thisArg, args);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Note(SB) for performance sake, this method should only be used when loopable type
|
|
* is unknown or in none intensive code (not called often and small loopable). Else
|
|
* it's preferable to use a regular for() loop and save extra function calls.
|
|
* @param {object|Array} loopable - The object or array to be iterated.
|
|
* @param {function} fn - The function to call for each item.
|
|
* @param {object} [thisArg] - The value of `this` provided for the call to `fn`.
|
|
* @param {boolean} [reverse] - If true, iterates backward on the loopable.
|
|
*/
|
|
each: function(loopable, fn, thisArg, reverse) {
|
|
var i, len, keys;
|
|
if (helpers.isArray(loopable)) {
|
|
len = loopable.length;
|
|
if (reverse) {
|
|
for (i = len - 1; i >= 0; i--) {
|
|
fn.call(thisArg, loopable[i], i);
|
|
}
|
|
} else {
|
|
for (i = 0; i < len; i++) {
|
|
fn.call(thisArg, loopable[i], i);
|
|
}
|
|
}
|
|
} else if (helpers.isObject(loopable)) {
|
|
keys = Object.keys(loopable);
|
|
len = keys.length;
|
|
for (i = 0; i < len; i++) {
|
|
fn.call(thisArg, loopable[keys[i]], keys[i]);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Returns true if the `a0` and `a1` arrays have the same content, else returns false.
|
|
* @see https://stackoverflow.com/a/14853974
|
|
* @param {Array} a0 - The array to compare
|
|
* @param {Array} a1 - The array to compare
|
|
* @returns {boolean}
|
|
*/
|
|
arrayEquals: function(a0, a1) {
|
|
var i, ilen, v0, v1;
|
|
|
|
if (!a0 || !a1 || a0.length !== a1.length) {
|
|
return false;
|
|
}
|
|
|
|
for (i = 0, ilen = a0.length; i < ilen; ++i) {
|
|
v0 = a0[i];
|
|
v1 = a1[i];
|
|
|
|
if (v0 instanceof Array && v1 instanceof Array) {
|
|
if (!helpers.arrayEquals(v0, v1)) {
|
|
return false;
|
|
}
|
|
} else if (v0 !== v1) {
|
|
// NOTE: two different object instances will never be equal: {x:20} != {x:20}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
},
|
|
|
|
/**
|
|
* Returns a deep copy of `source` without keeping references on objects and arrays.
|
|
* @param {*} source - The value to clone.
|
|
* @returns {*}
|
|
*/
|
|
clone: function(source) {
|
|
if (helpers.isArray(source)) {
|
|
return source.map(helpers.clone);
|
|
}
|
|
|
|
if (helpers.isObject(source)) {
|
|
var target = {};
|
|
var keys = Object.keys(source);
|
|
var klen = keys.length;
|
|
var k = 0;
|
|
|
|
for (; k < klen; ++k) {
|
|
target[keys[k]] = helpers.clone(source[keys[k]]);
|
|
}
|
|
|
|
return target;
|
|
}
|
|
|
|
return source;
|
|
},
|
|
|
|
/**
|
|
* The default merger when Chart.helpers.merge is called without merger option.
|
|
* Note(SB): also used by mergeConfig and mergeScaleConfig as fallback.
|
|
* @private
|
|
*/
|
|
_merger: function(key, target, source, options) {
|
|
var tval = target[key];
|
|
var sval = source[key];
|
|
|
|
if (helpers.isObject(tval) && helpers.isObject(sval)) {
|
|
helpers.merge(tval, sval, options);
|
|
} else {
|
|
target[key] = helpers.clone(sval);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Merges source[key] in target[key] only if target[key] is undefined.
|
|
* @private
|
|
*/
|
|
_mergerIf: function(key, target, source) {
|
|
var tval = target[key];
|
|
var sval = source[key];
|
|
|
|
if (helpers.isObject(tval) && helpers.isObject(sval)) {
|
|
helpers.mergeIf(tval, sval);
|
|
} else if (!target.hasOwnProperty(key)) {
|
|
target[key] = helpers.clone(sval);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Recursively deep copies `source` properties into `target` with the given `options`.
|
|
* IMPORTANT: `target` is not cloned and will be updated with `source` properties.
|
|
* @param {object} target - The target object in which all sources are merged into.
|
|
* @param {object|object[]} source - Object(s) to merge into `target`.
|
|
* @param {object} [options] - Merging options:
|
|
* @param {function} [options.merger] - The merge method (key, target, source, options)
|
|
* @returns {object} The `target` object.
|
|
*/
|
|
merge: function(target, source, options) {
|
|
var sources = helpers.isArray(source) ? source : [source];
|
|
var ilen = sources.length;
|
|
var merge, i, keys, klen, k;
|
|
|
|
if (!helpers.isObject(target)) {
|
|
return target;
|
|
}
|
|
|
|
options = options || {};
|
|
merge = options.merger || helpers._merger;
|
|
|
|
for (i = 0; i < ilen; ++i) {
|
|
source = sources[i];
|
|
if (!helpers.isObject(source)) {
|
|
continue;
|
|
}
|
|
|
|
keys = Object.keys(source);
|
|
for (k = 0, klen = keys.length; k < klen; ++k) {
|
|
merge(keys[k], target, source, options);
|
|
}
|
|
}
|
|
|
|
return target;
|
|
},
|
|
|
|
/**
|
|
* Recursively deep copies `source` properties into `target` *only* if not defined in target.
|
|
* IMPORTANT: `target` is not cloned and will be updated with `source` properties.
|
|
* @param {object} target - The target object in which all sources are merged into.
|
|
* @param {object|object[]} source - Object(s) to merge into `target`.
|
|
* @returns {object} The `target` object.
|
|
*/
|
|
mergeIf: function(target, source) {
|
|
return helpers.merge(target, source, {merger: helpers._mergerIf});
|
|
},
|
|
|
|
/**
|
|
* Applies the contents of two or more objects together into the first object.
|
|
* @param {object} target - The target object in which all objects are merged into.
|
|
* @param {object} arg1 - Object containing additional properties to merge in target.
|
|
* @param {object} argN - Additional objects containing properties to merge in target.
|
|
* @returns {object} The `target` object.
|
|
*/
|
|
extend: Object.assign || function(target) {
|
|
return helpers.merge(target, [].slice.call(arguments, 1), {
|
|
merger: function(key, dst, src) {
|
|
dst[key] = src[key];
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Basic javascript inheritance based on the model created in Backbone.js
|
|
*/
|
|
inherits: function(extensions) {
|
|
var me = this;
|
|
var ChartElement = (extensions && extensions.hasOwnProperty('constructor')) ? extensions.constructor : function() {
|
|
return me.apply(this, arguments);
|
|
};
|
|
|
|
var Surrogate = function() {
|
|
this.constructor = ChartElement;
|
|
};
|
|
|
|
Surrogate.prototype = me.prototype;
|
|
ChartElement.prototype = new Surrogate();
|
|
ChartElement.extend = helpers.inherits;
|
|
|
|
if (extensions) {
|
|
helpers.extend(ChartElement.prototype, extensions);
|
|
}
|
|
|
|
ChartElement.__super__ = me.prototype;
|
|
return ChartElement;
|
|
},
|
|
|
|
_deprecated: function(scope, value, previous, current) {
|
|
if (value !== undefined) {
|
|
console.warn(scope + ': "' + previous +
|
|
'" is deprecated. Please use "' + current + '" instead');
|
|
}
|
|
}
|
|
};
|
|
|
|
var helpers_core = helpers;
|
|
|
|
// DEPRECATIONS
|
|
|
|
/**
|
|
* Provided for backward compatibility, use Chart.helpers.callback instead.
|
|
* @function Chart.helpers.callCallback
|
|
* @deprecated since version 2.6.0
|
|
* @todo remove at version 3
|
|
* @private
|
|
*/
|
|
helpers.callCallback = helpers.callback;
|
|
|
|
/**
|
|
* Provided for backward compatibility, use Array.prototype.indexOf instead.
|
|
* Array.prototype.indexOf compatibility: Chrome, Opera, Safari, FF1.5+, IE9+
|
|
* @function Chart.helpers.indexOf
|
|
* @deprecated since version 2.7.0
|
|
* @todo remove at version 3
|
|
* @private
|
|
*/
|
|
helpers.indexOf = function(array, item, fromIndex) {
|
|
return Array.prototype.indexOf.call(array, item, fromIndex);
|
|
};
|
|
|
|
/**
|
|
* Provided for backward compatibility, use Chart.helpers.valueOrDefault instead.
|
|
* @function Chart.helpers.getValueOrDefault
|
|
* @deprecated since version 2.7.0
|
|
* @todo remove at version 3
|
|
* @private
|
|
*/
|
|
helpers.getValueOrDefault = helpers.valueOrDefault;
|
|
|
|
/**
|
|
* Provided for backward compatibility, use Chart.helpers.valueAtIndexOrDefault instead.
|
|
* @function Chart.helpers.getValueAtIndexOrDefault
|
|
* @deprecated since version 2.7.0
|
|
* @todo remove at version 3
|
|
* @private
|
|
*/
|
|
helpers.getValueAtIndexOrDefault = helpers.valueAtIndexOrDefault;
|
|
|
|
/**
|
|
* Easing functions adapted from Robert Penner's easing equations.
|
|
* @namespace Chart.helpers.easingEffects
|
|
* @see http://www.robertpenner.com/easing/
|
|
*/
|
|
var effects = {
|
|
linear: function(t) {
|
|
return t;
|
|
},
|
|
|
|
easeInQuad: function(t) {
|
|
return t * t;
|
|
},
|
|
|
|
easeOutQuad: function(t) {
|
|
return -t * (t - 2);
|
|
},
|
|
|
|
easeInOutQuad: function(t) {
|
|
if ((t /= 0.5) < 1) {
|
|
return 0.5 * t * t;
|
|
}
|
|
return -0.5 * ((--t) * (t - 2) - 1);
|
|
},
|
|
|
|
easeInCubic: function(t) {
|
|
return t * t * t;
|
|
},
|
|
|
|
easeOutCubic: function(t) {
|
|
return (t = t - 1) * t * t + 1;
|
|
},
|
|
|
|
easeInOutCubic: function(t) {
|
|
if ((t /= 0.5) < 1) {
|
|
return 0.5 * t * t * t;
|
|
}
|
|
return 0.5 * ((t -= 2) * t * t + 2);
|
|
},
|
|
|
|
easeInQuart: function(t) {
|
|
return t * t * t * t;
|
|
},
|
|
|
|
easeOutQuart: function(t) {
|
|
return -((t = t - 1) * t * t * t - 1);
|
|
},
|
|
|
|
easeInOutQuart: function(t) {
|
|
if ((t /= 0.5) < 1) {
|
|
return 0.5 * t * t * t * t;
|
|
}
|
|
return -0.5 * ((t -= 2) * t * t * t - 2);
|
|
},
|
|
|
|
easeInQuint: function(t) {
|
|
return t * t * t * t * t;
|
|
},
|
|
|
|
easeOutQuint: function(t) {
|
|
return (t = t - 1) * t * t * t * t + 1;
|
|
},
|
|
|
|
easeInOutQuint: function(t) {
|
|
if ((t /= 0.5) < 1) {
|
|
return 0.5 * t * t * t * t * t;
|
|
}
|
|
return 0.5 * ((t -= 2) * t * t * t * t + 2);
|
|
},
|
|
|
|
easeInSine: function(t) {
|
|
return -Math.cos(t * (Math.PI / 2)) + 1;
|
|
},
|
|
|
|
easeOutSine: function(t) {
|
|
return Math.sin(t * (Math.PI / 2));
|
|
},
|
|
|
|
easeInOutSine: function(t) {
|
|
return -0.5 * (Math.cos(Math.PI * t) - 1);
|
|
},
|
|
|
|
easeInExpo: function(t) {
|
|
return (t === 0) ? 0 : Math.pow(2, 10 * (t - 1));
|
|
},
|
|
|
|
easeOutExpo: function(t) {
|
|
return (t === 1) ? 1 : -Math.pow(2, -10 * t) + 1;
|
|
},
|
|
|
|
easeInOutExpo: function(t) {
|
|
if (t === 0) {
|
|
return 0;
|
|
}
|
|
if (t === 1) {
|
|
return 1;
|
|
}
|
|
if ((t /= 0.5) < 1) {
|
|
return 0.5 * Math.pow(2, 10 * (t - 1));
|
|
}
|
|
return 0.5 * (-Math.pow(2, -10 * --t) + 2);
|
|
},
|
|
|
|
easeInCirc: function(t) {
|
|
if (t >= 1) {
|
|
return t;
|
|
}
|
|
return -(Math.sqrt(1 - t * t) - 1);
|
|
},
|
|
|
|
easeOutCirc: function(t) {
|
|
return Math.sqrt(1 - (t = t - 1) * t);
|
|
},
|
|
|
|
easeInOutCirc: function(t) {
|
|
if ((t /= 0.5) < 1) {
|
|
return -0.5 * (Math.sqrt(1 - t * t) - 1);
|
|
}
|
|
return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1);
|
|
},
|
|
|
|
easeInElastic: function(t) {
|
|
var s = 1.70158;
|
|
var p = 0;
|
|
var a = 1;
|
|
if (t === 0) {
|
|
return 0;
|
|
}
|
|
if (t === 1) {
|
|
return 1;
|
|
}
|
|
if (!p) {
|
|
p = 0.3;
|
|
}
|
|
if (a < 1) {
|
|
a = 1;
|
|
s = p / 4;
|
|
} else {
|
|
s = p / (2 * Math.PI) * Math.asin(1 / a);
|
|
}
|
|
return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p));
|
|
},
|
|
|
|
easeOutElastic: function(t) {
|
|
var s = 1.70158;
|
|
var p = 0;
|
|
var a = 1;
|
|
if (t === 0) {
|
|
return 0;
|
|
}
|
|
if (t === 1) {
|
|
return 1;
|
|
}
|
|
if (!p) {
|
|
p = 0.3;
|
|
}
|
|
if (a < 1) {
|
|
a = 1;
|
|
s = p / 4;
|
|
} else {
|
|
s = p / (2 * Math.PI) * Math.asin(1 / a);
|
|
}
|
|
return a * Math.pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / p) + 1;
|
|
},
|
|
|
|
easeInOutElastic: function(t) {
|
|
var s = 1.70158;
|
|
var p = 0;
|
|
var a = 1;
|
|
if (t === 0) {
|
|
return 0;
|
|
}
|
|
if ((t /= 0.5) === 2) {
|
|
return 1;
|
|
}
|
|
if (!p) {
|
|
p = 0.45;
|
|
}
|
|
if (a < 1) {
|
|
a = 1;
|
|
s = p / 4;
|
|
} else {
|
|
s = p / (2 * Math.PI) * Math.asin(1 / a);
|
|
}
|
|
if (t < 1) {
|
|
return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p));
|
|
}
|
|
return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p) * 0.5 + 1;
|
|
},
|
|
easeInBack: function(t) {
|
|
var s = 1.70158;
|
|
return t * t * ((s + 1) * t - s);
|
|
},
|
|
|
|
easeOutBack: function(t) {
|
|
var s = 1.70158;
|
|
return (t = t - 1) * t * ((s + 1) * t + s) + 1;
|
|
},
|
|
|
|
easeInOutBack: function(t) {
|
|
var s = 1.70158;
|
|
if ((t /= 0.5) < 1) {
|
|
return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s));
|
|
}
|
|
return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
|
|
},
|
|
|
|
easeInBounce: function(t) {
|
|
return 1 - effects.easeOutBounce(1 - t);
|
|
},
|
|
|
|
easeOutBounce: function(t) {
|
|
if (t < (1 / 2.75)) {
|
|
return 7.5625 * t * t;
|
|
}
|
|
if (t < (2 / 2.75)) {
|
|
return 7.5625 * (t -= (1.5 / 2.75)) * t + 0.75;
|
|
}
|
|
if (t < (2.5 / 2.75)) {
|
|
return 7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375;
|
|
}
|
|
return 7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375;
|
|
},
|
|
|
|
easeInOutBounce: function(t) {
|
|
if (t < 0.5) {
|
|
return effects.easeInBounce(t * 2) * 0.5;
|
|
}
|
|
return effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5;
|
|
}
|
|
};
|
|
|
|
var helpers_easing = {
|
|
effects: effects
|
|
};
|
|
|
|
// DEPRECATIONS
|
|
|
|
/**
|
|
* Provided for backward compatibility, use Chart.helpers.easing.effects instead.
|
|
* @function Chart.helpers.easingEffects
|
|
* @deprecated since version 2.7.0
|
|
* @todo remove at version 3
|
|
* @private
|
|
*/
|
|
helpers_core.easingEffects = effects;
|
|
|
|
var PI = Math.PI;
|
|
var RAD_PER_DEG = PI / 180;
|
|
var DOUBLE_PI = PI * 2;
|
|
var HALF_PI = PI / 2;
|
|
var QUARTER_PI = PI / 4;
|
|
var TWO_THIRDS_PI = PI * 2 / 3;
|
|
|
|
/**
|
|
* @namespace Chart.helpers.canvas
|
|
*/
|
|
var exports$1 = {
|
|
/**
|
|
* Clears the entire canvas associated to the given `chart`.
|
|
* @param {Chart} chart - The chart for which to clear the canvas.
|
|
*/
|
|
clear: function(chart) {
|
|
chart.ctx.clearRect(0, 0, chart.width, chart.height);
|
|
},
|
|
|
|
/**
|
|
* Creates a "path" for a rectangle with rounded corners at position (x, y) with a
|
|
* given size (width, height) and the same `radius` for all corners.
|
|
* @param {CanvasRenderingContext2D} ctx - The canvas 2D Context.
|
|
* @param {number} x - The x axis of the coordinate for the rectangle starting point.
|
|
* @param {number} y - The y axis of the coordinate for the rectangle starting point.
|
|
* @param {number} width - The rectangle's width.
|
|
* @param {number} height - The rectangle's height.
|
|
* @param {number} radius - The rounded amount (in pixels) for the four corners.
|
|
* @todo handle `radius` as top-left, top-right, bottom-right, bottom-left array/object?
|
|
*/
|
|
roundedRect: function(ctx, x, y, width, height, radius) {
|
|
if (radius) {
|
|
var r = Math.min(radius, height / 2, width / 2);
|
|
var left = x + r;
|
|
var top = y + r;
|
|
var right = x + width - r;
|
|
var bottom = y + height - r;
|
|
|
|
ctx.moveTo(x, top);
|
|
if (left < right && top < bottom) {
|
|
ctx.arc(left, top, r, -PI, -HALF_PI);
|
|
ctx.arc(right, top, r, -HALF_PI, 0);
|
|
ctx.arc(right, bottom, r, 0, HALF_PI);
|
|
ctx.arc(left, bottom, r, HALF_PI, PI);
|
|
} else if (left < right) {
|
|
ctx.moveTo(left, y);
|
|
ctx.arc(right, top, r, -HALF_PI, HALF_PI);
|
|
ctx.arc(left, top, r, HALF_PI, PI + HALF_PI);
|
|
} else if (top < bottom) {
|
|
ctx.arc(left, top, r, -PI, 0);
|
|
ctx.arc(left, bottom, r, 0, PI);
|
|
} else {
|
|
ctx.arc(left, top, r, -PI, PI);
|
|
}
|
|
ctx.closePath();
|
|
ctx.moveTo(x, y);
|
|
} else {
|
|
ctx.rect(x, y, width, height);
|
|
}
|
|
},
|
|
|
|
drawPoint: function(ctx, style, radius, x, y, rotation) {
|
|
var type, xOffset, yOffset, size, cornerRadius;
|
|
var rad = (rotation || 0) * RAD_PER_DEG;
|
|
|
|
if (style && typeof style === 'object') {
|
|
type = style.toString();
|
|
if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') {
|
|
ctx.save();
|
|
ctx.translate(x, y);
|
|
ctx.rotate(rad);
|
|
ctx.drawImage(style, -style.width / 2, -style.height / 2, style.width, style.height);
|
|
ctx.restore();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (isNaN(radius) || radius <= 0) {
|
|
return;
|
|
}
|
|
|
|
ctx.beginPath();
|
|
|
|
switch (style) {
|
|
// Default includes circle
|
|
default:
|
|
ctx.arc(x, y, radius, 0, DOUBLE_PI);
|
|
ctx.closePath();
|
|
break;
|
|
case 'triangle':
|
|
ctx.moveTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius);
|
|
rad += TWO_THIRDS_PI;
|
|
ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius);
|
|
rad += TWO_THIRDS_PI;
|
|
ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius);
|
|
ctx.closePath();
|
|
break;
|
|
case 'rectRounded':
|
|
// NOTE: the rounded rect implementation changed to use `arc` instead of
|
|
// `quadraticCurveTo` since it generates better results when rect is
|
|
// almost a circle. 0.516 (instead of 0.5) produces results with visually
|
|
// closer proportion to the previous impl and it is inscribed in the
|
|
// circle with `radius`. For more details, see the following PRs:
|
|
// https://github.com/chartjs/Chart.js/issues/5597
|
|
// https://github.com/chartjs/Chart.js/issues/5858
|
|
cornerRadius = radius * 0.516;
|
|
size = radius - cornerRadius;
|
|
xOffset = Math.cos(rad + QUARTER_PI) * size;
|
|
yOffset = Math.sin(rad + QUARTER_PI) * size;
|
|
ctx.arc(x - xOffset, y - yOffset, cornerRadius, rad - PI, rad - HALF_PI);
|
|
ctx.arc(x + yOffset, y - xOffset, cornerRadius, rad - HALF_PI, rad);
|
|
ctx.arc(x + xOffset, y + yOffset, cornerRadius, rad, rad + HALF_PI);
|
|
ctx.arc(x - yOffset, y + xOffset, cornerRadius, rad + HALF_PI, rad + PI);
|
|
ctx.closePath();
|
|
break;
|
|
case 'rect':
|
|
if (!rotation) {
|
|
size = Math.SQRT1_2 * radius;
|
|
ctx.rect(x - size, y - size, 2 * size, 2 * size);
|
|
break;
|
|
}
|
|
rad += QUARTER_PI;
|
|
/* falls through */
|
|
case 'rectRot':
|
|
xOffset = Math.cos(rad) * radius;
|
|
yOffset = Math.sin(rad) * radius;
|
|
ctx.moveTo(x - xOffset, y - yOffset);
|
|
ctx.lineTo(x + yOffset, y - xOffset);
|
|
ctx.lineTo(x + xOffset, y + yOffset);
|
|
ctx.lineTo(x - yOffset, y + xOffset);
|
|
ctx.closePath();
|
|
break;
|
|
case 'crossRot':
|
|
rad += QUARTER_PI;
|
|
/* falls through */
|
|
case 'cross':
|
|
xOffset = Math.cos(rad) * radius;
|
|
yOffset = Math.sin(rad) * radius;
|
|
ctx.moveTo(x - xOffset, y - yOffset);
|
|
ctx.lineTo(x + xOffset, y + yOffset);
|
|
ctx.moveTo(x + yOffset, y - xOffset);
|
|
ctx.lineTo(x - yOffset, y + xOffset);
|
|
break;
|
|
case 'star':
|
|
xOffset = Math.cos(rad) * radius;
|
|
yOffset = Math.sin(rad) * radius;
|
|
ctx.moveTo(x - xOffset, y - yOffset);
|
|
ctx.lineTo(x + xOffset, y + yOffset);
|
|
ctx.moveTo(x + yOffset, y - xOffset);
|
|
ctx.lineTo(x - yOffset, y + xOffset);
|
|
rad += QUARTER_PI;
|
|
xOffset = Math.cos(rad) * radius;
|
|
yOffset = Math.sin(rad) * radius;
|
|
ctx.moveTo(x - xOffset, y - yOffset);
|
|
ctx.lineTo(x + xOffset, y + yOffset);
|
|
ctx.moveTo(x + yOffset, y - xOffset);
|
|
ctx.lineTo(x - yOffset, y + xOffset);
|
|
break;
|
|
case 'line':
|
|
xOffset = Math.cos(rad) * radius;
|
|
yOffset = Math.sin(rad) * radius;
|
|
ctx.moveTo(x - xOffset, y - yOffset);
|
|
ctx.lineTo(x + xOffset, y + yOffset);
|
|
break;
|
|
case 'dash':
|
|
ctx.moveTo(x, y);
|
|
ctx.lineTo(x + Math.cos(rad) * radius, y + Math.sin(rad) * radius);
|
|
break;
|
|
}
|
|
|
|
ctx.fill();
|
|
ctx.stroke();
|
|
},
|
|
|
|
/**
|
|
* Returns true if the point is inside the rectangle
|
|
* @param {object} point - The point to test
|
|
* @param {object} area - The rectangle
|
|
* @returns {boolean}
|
|
* @private
|
|
*/
|
|
_isPointInArea: function(point, area) {
|
|
var epsilon = 1e-6; // 1e-6 is margin in pixels for accumulated error.
|
|
|
|
return point.x > area.left - epsilon && point.x < area.right + epsilon &&
|
|
point.y > area.top - epsilon && point.y < area.bottom + epsilon;
|
|
},
|
|
|
|
clipArea: function(ctx, area) {
|
|
ctx.save();
|
|
ctx.beginPath();
|
|
ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top);
|
|
ctx.clip();
|
|
},
|
|
|
|
unclipArea: function(ctx) {
|
|
ctx.restore();
|
|
},
|
|
|
|
lineTo: function(ctx, previous, target, flip) {
|
|
var stepped = target.steppedLine;
|
|
if (stepped) {
|
|
if (stepped === 'middle') {
|
|
var midpoint = (previous.x + target.x) / 2.0;
|
|
ctx.lineTo(midpoint, flip ? target.y : previous.y);
|
|
ctx.lineTo(midpoint, flip ? previous.y : target.y);
|
|
} else if ((stepped === 'after' && !flip) || (stepped !== 'after' && flip)) {
|
|
ctx.lineTo(previous.x, target.y);
|
|
} else {
|
|
ctx.lineTo(target.x, previous.y);
|
|
}
|
|
ctx.lineTo(target.x, target.y);
|
|
return;
|
|
}
|
|
|
|
if (!target.tension) {
|
|
ctx.lineTo(target.x, target.y);
|
|
return;
|
|
}
|
|
|
|
ctx.bezierCurveTo(
|
|
flip ? previous.controlPointPreviousX : previous.controlPointNextX,
|
|
flip ? previous.controlPointPreviousY : previous.controlPointNextY,
|
|
flip ? target.controlPointNextX : target.controlPointPreviousX,
|
|
flip ? target.controlPointNextY : target.controlPointPreviousY,
|
|
target.x,
|
|
target.y);
|
|
}
|
|
};
|
|
|
|
var helpers_canvas = exports$1;
|
|
|
|
// DEPRECATIONS
|
|
|
|
/**
|
|
* Provided for backward compatibility, use Chart.helpers.canvas.clear instead.
|
|
* @namespace Chart.helpers.clear
|
|
* @deprecated since version 2.7.0
|
|
* @todo remove at version 3
|
|
* @private
|
|
*/
|
|
helpers_core.clear = exports$1.clear;
|
|
|
|
/**
|
|
* Provided for backward compatibility, use Chart.helpers.canvas.roundedRect instead.
|
|
* @namespace Chart.helpers.drawRoundedRectangle
|
|
* @deprecated since version 2.7.0
|
|
* @todo remove at version 3
|
|
* @private
|
|
*/
|
|
helpers_core.drawRoundedRectangle = function(ctx) {
|
|
ctx.beginPath();
|
|
exports$1.roundedRect.apply(exports$1, arguments);
|
|
};
|
|
|
|
var defaults = {
|
|
/**
|
|
* @private
|
|
*/
|
|
_set: function(scope, values) {
|
|
return helpers_core.merge(this[scope] || (this[scope] = {}), values);
|
|
}
|
|
};
|
|
|
|
// TODO(v3): remove 'global' from namespace. all default are global and
|
|
// there's inconsistency around which options are under 'global'
|
|
defaults._set('global', {
|
|
defaultColor: 'rgba(0,0,0,0.1)',
|
|
defaultFontColor: '#666',
|
|
defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
|
|
defaultFontSize: 12,
|
|
defaultFontStyle: 'normal',
|
|
defaultLineHeight: 1.2,
|
|
showLines: true
|
|
});
|
|
|
|
var core_defaults = defaults;
|
|
|
|
var valueOrDefault = helpers_core.valueOrDefault;
|
|
|
|
/**
|
|
* Converts the given font object into a CSS font string.
|
|
* @param {object} font - A font object.
|
|
* @return {string} The CSS font string. See https://developer.mozilla.org/en-US/docs/Web/CSS/font
|
|
* @private
|
|
*/
|
|
function toFontString(font) {
|
|
if (!font || helpers_core.isNullOrUndef(font.size) || helpers_core.isNullOrUndef(font.family)) {
|
|
return null;
|
|
}
|
|
|
|
return (font.style ? font.style + ' ' : '')
|
|
+ (font.weight ? font.weight + ' ' : '')
|
|
+ font.size + 'px '
|
|
+ font.family;
|
|
}
|
|
|
|
/**
|
|
* @alias Chart.helpers.options
|
|
* @namespace
|
|
*/
|
|
var helpers_options = {
|
|
/**
|
|
* Converts the given line height `value` in pixels for a specific font `size`.
|
|
* @param {number|string} value - The lineHeight to parse (eg. 1.6, '14px', '75%', '1.6em').
|
|
* @param {number} size - The font size (in pixels) used to resolve relative `value`.
|
|
* @returns {number} The effective line height in pixels (size * 1.2 if value is invalid).
|
|
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/line-height
|
|
* @since 2.7.0
|
|
*/
|
|
toLineHeight: function(value, size) {
|
|
var matches = ('' + value).match(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/);
|
|
if (!matches || matches[1] === 'normal') {
|
|
return size * 1.2;
|
|
}
|
|
|
|
value = +matches[2];
|
|
|
|
switch (matches[3]) {
|
|
case 'px':
|
|
return value;
|
|
case '%':
|
|
value /= 100;
|
|
break;
|
|
}
|
|
|
|
return size * value;
|
|
},
|
|
|
|
/**
|
|
* Converts the given value into a padding object with pre-computed width/height.
|
|
* @param {number|object} value - If a number, set the value to all TRBL component,
|
|
* else, if and object, use defined properties and sets undefined ones to 0.
|
|
* @returns {object} The padding values (top, right, bottom, left, width, height)
|
|
* @since 2.7.0
|
|
*/
|
|
toPadding: function(value) {
|
|
var t, r, b, l;
|
|
|
|
if (helpers_core.isObject(value)) {
|
|
t = +value.top || 0;
|
|
r = +value.right || 0;
|
|
b = +value.bottom || 0;
|
|
l = +value.left || 0;
|
|
} else {
|
|
t = r = b = l = +value || 0;
|
|
}
|
|
|
|
return {
|
|
top: t,
|
|
right: r,
|
|
bottom: b,
|
|
left: l,
|
|
height: t + b,
|
|
width: l + r
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Parses font options and returns the font object.
|
|
* @param {object} options - A object that contains font options to be parsed.
|
|
* @return {object} The font object.
|
|
* @todo Support font.* options and renamed to toFont().
|
|
* @private
|
|
*/
|
|
_parseFont: function(options) {
|
|
var globalDefaults = core_defaults.global;
|
|
var size = valueOrDefault(options.fontSize, globalDefaults.defaultFontSize);
|
|
var font = {
|
|
family: valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily),
|
|
lineHeight: helpers_core.options.toLineHeight(valueOrDefault(options.lineHeight, globalDefaults.defaultLineHeight), size),
|
|
size: size,
|
|
style: valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle),
|
|
weight: null,
|
|
string: ''
|
|
};
|
|
|
|
font.string = toFontString(font);
|
|
return font;
|
|
},
|
|
|
|
/**
|
|
* Evaluates the given `inputs` sequentially and returns the first defined value.
|
|
* @param {Array} inputs - An array of values, falling back to the last value.
|
|
* @param {object} [context] - If defined and the current value is a function, the value
|
|
* is called with `context` as first argument and the result becomes the new input.
|
|
* @param {number} [index] - If defined and the current value is an array, the value
|
|
* at `index` become the new input.
|
|
* @param {object} [info] - object to return information about resolution in
|
|
* @param {boolean} [info.cacheable] - Will be set to `false` if option is not cacheable.
|
|
* @since 2.7.0
|
|
*/
|
|
resolve: function(inputs, context, index, info) {
|
|
var cacheable = true;
|
|
var i, ilen, value;
|
|
|
|
for (i = 0, ilen = inputs.length; i < ilen; ++i) {
|
|
value = inputs[i];
|
|
if (value === undefined) {
|
|
continue;
|
|
}
|
|
if (context !== undefined && typeof value === 'function') {
|
|
value = value(context);
|
|
cacheable = false;
|
|
}
|
|
if (index !== undefined && helpers_core.isArray(value)) {
|
|
value = value[index];
|
|
cacheable = false;
|
|
}
|
|
if (value !== undefined) {
|
|
if (info && !cacheable) {
|
|
info.cacheable = false;
|
|
}
|
|
return value;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @alias Chart.helpers.math
|
|
* @namespace
|
|
*/
|
|
var exports$2 = {
|
|
/**
|
|
* Returns an array of factors sorted from 1 to sqrt(value)
|
|
* @private
|
|
*/
|
|
_factorize: function(value) {
|
|
var result = [];
|
|
var sqrt = Math.sqrt(value);
|
|
var i;
|
|
|
|
for (i = 1; i < sqrt; i++) {
|
|
if (value % i === 0) {
|
|
result.push(i);
|
|
result.push(value / i);
|
|
}
|
|
}
|
|
if (sqrt === (sqrt | 0)) { // if value is a square number
|
|
result.push(sqrt);
|
|
}
|
|
|
|
result.sort(function(a, b) {
|
|
return a - b;
|
|
}).pop();
|
|
return result;
|
|
},
|
|
|
|
log10: Math.log10 || function(x) {
|
|
var exponent = Math.log(x) * Math.LOG10E; // Math.LOG10E = 1 / Math.LN10.
|
|
// Check for whole powers of 10,
|
|
// which due to floating point rounding error should be corrected.
|
|
var powerOf10 = Math.round(exponent);
|
|
var isPowerOf10 = x === Math.pow(10, powerOf10);
|
|
|
|
return isPowerOf10 ? powerOf10 : exponent;
|
|
}
|
|
};
|
|
|
|
var helpers_math = exports$2;
|
|
|
|
// DEPRECATIONS
|
|
|
|
/**
|
|
* Provided for backward compatibility, use Chart.helpers.math.log10 instead.
|
|
* @namespace Chart.helpers.log10
|
|
* @deprecated since version 2.9.0
|
|
* @todo remove at version 3
|
|
* @private
|
|
*/
|
|
helpers_core.log10 = exports$2.log10;
|
|
|
|
var getRtlAdapter = function(rectX, width) {
|
|
return {
|
|
x: function(x) {
|
|
return rectX + rectX + width - x;
|
|
},
|
|
setWidth: function(w) {
|
|
width = w;
|
|
},
|
|
textAlign: function(align) {
|
|
if (align === 'center') {
|
|
return align;
|
|
}
|
|
return align === 'right' ? 'left' : 'right';
|
|
},
|
|
xPlus: function(x, value) {
|
|
return x - value;
|
|
},
|
|
leftForLtr: function(x, itemWidth) {
|
|
return x - itemWidth;
|
|
},
|
|
};
|
|
};
|
|
|
|
var getLtrAdapter = function() {
|
|
return {
|
|
x: function(x) {
|
|
return x;
|
|
},
|
|
setWidth: function(w) { // eslint-disable-line no-unused-vars
|
|
},
|
|
textAlign: function(align) {
|
|
return align;
|
|
},
|
|
xPlus: function(x, value) {
|
|
return x + value;
|
|
},
|
|
leftForLtr: function(x, _itemWidth) { // eslint-disable-line no-unused-vars
|
|
return x;
|
|
},
|
|
};
|
|
};
|
|
|
|
var getAdapter = function(rtl, rectX, width) {
|
|
return rtl ? getRtlAdapter(rectX, width) : getLtrAdapter();
|
|
};
|
|
|
|
var overrideTextDirection = function(ctx, direction) {
|
|
var style, original;
|
|
if (direction === 'ltr' || direction === 'rtl') {
|
|
style = ctx.canvas.style;
|
|
original = [
|
|
style.getPropertyValue('direction'),
|
|
style.getPropertyPriority('direction'),
|
|
];
|
|
|
|
style.setProperty('direction', direction, 'important');
|
|
ctx.prevTextDirection = original;
|
|
}
|
|
};
|
|
|
|
var restoreTextDirection = function(ctx) {
|
|
var original = ctx.prevTextDirection;
|
|
if (original !== undefined) {
|
|
delete ctx.prevTextDirection;
|
|
ctx.canvas.style.setProperty('direction', original[0], original[1]);
|
|
}
|
|
};
|
|
|
|
var helpers_rtl = {
|
|
getRtlAdapter: getAdapter,
|
|
overrideTextDirection: overrideTextDirection,
|
|
restoreTextDirection: restoreTextDirection,
|
|
};
|
|
|
|
var helpers$1 = helpers_core;
|
|
var easing = helpers_easing;
|
|
var canvas = helpers_canvas;
|
|
var options = helpers_options;
|
|
var math = helpers_math;
|
|
var rtl = helpers_rtl;
|
|
helpers$1.easing = easing;
|
|
helpers$1.canvas = canvas;
|
|
helpers$1.options = options;
|
|
helpers$1.math = math;
|
|
helpers$1.rtl = rtl;
|
|
|
|
function interpolate(start, view, model, ease) {
|
|
var keys = Object.keys(model);
|
|
var i, ilen, key, actual, origin, target, type, c0, c1;
|
|
|
|
for (i = 0, ilen = keys.length; i < ilen; ++i) {
|
|
key = keys[i];
|
|
|
|
target = model[key];
|
|
|
|
// if a value is added to the model after pivot() has been called, the view
|
|
// doesn't contain it, so let's initialize the view to the target value.
|
|
if (!view.hasOwnProperty(key)) {
|
|
view[key] = target;
|
|
}
|
|
|
|
actual = view[key];
|
|
|
|
if (actual === target || key[0] === '_') {
|
|
continue;
|
|
}
|
|
|
|
if (!start.hasOwnProperty(key)) {
|
|
start[key] = actual;
|
|
}
|
|
|
|
origin = start[key];
|
|
|
|
type = typeof target;
|
|
|
|
if (type === typeof origin) {
|
|
if (type === 'string') {
|
|
c0 = chartjsColor(origin);
|
|
if (c0.valid) {
|
|
c1 = chartjsColor(target);
|
|
if (c1.valid) {
|
|
view[key] = c1.mix(c0, ease).rgbString();
|
|
continue;
|
|
}
|
|
}
|
|
} else if (helpers$1.isFinite(origin) && helpers$1.isFinite(target)) {
|
|
view[key] = origin + (target - origin) * ease;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
view[key] = target;
|
|
}
|
|
}
|
|
|
|
var Element = function(configuration) {
|
|
helpers$1.extend(this, configuration);
|
|
this.initialize.apply(this, arguments);
|
|
};
|
|
|
|
helpers$1.extend(Element.prototype, {
|
|
_type: undefined,
|
|
|
|
initialize: function() {
|
|
this.hidden = false;
|
|
},
|
|
|
|
pivot: function() {
|
|
var me = this;
|
|
if (!me._view) {
|
|
me._view = helpers$1.extend({}, me._model);
|
|
}
|
|
me._start = {};
|
|
return me;
|
|
},
|
|
|
|
transition: function(ease) {
|
|
var me = this;
|
|
var model = me._model;
|
|
var start = me._start;
|
|
var view = me._view;
|
|
|
|
// No animation -> No Transition
|
|
if (!model || ease === 1) {
|
|
me._view = helpers$1.extend({}, model);
|
|
me._start = null;
|
|
return me;
|
|
}
|
|
|
|
if (!view) {
|
|
view = me._view = {};
|
|
}
|
|
|
|
if (!start) {
|
|
start = me._start = {};
|
|
}
|
|
|
|
interpolate(start, view, model, ease);
|
|
|
|
return me;
|
|
},
|
|
|
|
tooltipPosition: function() {
|
|
return {
|
|
x: this._model.x,
|
|
y: this._model.y
|
|
};
|
|
},
|
|
|
|
hasValue: function() {
|
|
return helpers$1.isNumber(this._model.x) && helpers$1.isNumber(this._model.y);
|
|
}
|
|
});
|
|
|
|
Element.extend = helpers$1.inherits;
|
|
|
|
var core_element = Element;
|
|
|
|
var exports$3 = core_element.extend({
|
|
chart: null, // the animation associated chart instance
|
|
currentStep: 0, // the current animation step
|
|
numSteps: 60, // default number of steps
|
|
easing: '', // the easing to use for this animation
|
|
render: null, // render function used by the animation service
|
|
|
|
onAnimationProgress: null, // user specified callback to fire on each step of the animation
|
|
onAnimationComplete: null, // user specified callback to fire when the animation finishes
|
|
});
|
|
|
|
var core_animation = exports$3;
|
|
|
|
// DEPRECATIONS
|
|
|
|
/**
|
|
* Provided for backward compatibility, use Chart.Animation instead
|
|
* @prop Chart.Animation#animationObject
|
|
* @deprecated since version 2.6.0
|
|
* @todo remove at version 3
|
|
*/
|
|
Object.defineProperty(exports$3.prototype, 'animationObject', {
|
|
get: function() {
|
|
return this;
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Provided for backward compatibility, use Chart.Animation#chart instead
|
|
* @prop Chart.Animation#chartInstance
|
|
* @deprecated since version 2.6.0
|
|
* @todo remove at version 3
|
|
*/
|
|
Object.defineProperty(exports$3.prototype, 'chartInstance', {
|
|
get: function() {
|
|
return this.chart;
|
|
},
|
|
set: function(value) {
|
|
this.chart = value;
|
|
}
|
|
});
|
|
|
|
core_defaults._set('global', {
|
|
animation: {
|
|
duration: 1000,
|
|
easing: 'easeOutQuart',
|
|
onProgress: helpers$1.noop,
|
|
onComplete: helpers$1.noop
|
|
}
|
|
});
|
|
|
|
var core_animations = {
|
|
animations: [],
|
|
request: null,
|
|
|
|
/**
|
|
* @param {Chart} chart - The chart to animate.
|
|
* @param {Chart.Animation} animation - The animation that we will animate.
|
|
* @param {number} duration - The animation duration in ms.
|
|
* @param {boolean} lazy - if true, the chart is not marked as animating to enable more responsive interactions
|
|
*/
|
|
addAnimation: function(chart, animation, duration, lazy) {
|
|
var animations = this.animations;
|
|
var i, ilen;
|
|
|
|
animation.chart = chart;
|
|
animation.startTime = Date.now();
|
|
animation.duration = duration;
|
|
|
|
if (!lazy) {
|
|
chart.animating = true;
|
|
}
|
|
|
|
for (i = 0, ilen = animations.length; i < ilen; ++i) {
|
|
if (animations[i].chart === chart) {
|
|
animations[i] = animation;
|
|
return;
|
|
}
|
|
}
|
|
|
|
animations.push(animation);
|
|
|
|
// If there are no animations queued, manually kickstart a digest, for lack of a better word
|
|
if (animations.length === 1) {
|
|
this.requestAnimationFrame();
|
|
}
|
|
},
|
|
|
|
cancelAnimation: function(chart) {
|
|
var index = helpers$1.findIndex(this.animations, function(animation) {
|
|
return animation.chart === chart;
|
|
});
|
|
|
|
if (index !== -1) {
|
|
this.animations.splice(index, 1);
|
|
chart.animating = false;
|
|
}
|
|
},
|
|
|
|
requestAnimationFrame: function() {
|
|
var me = this;
|
|
if (me.request === null) {
|
|
// Skip animation frame requests until the active one is executed.
|
|
// This can happen when processing mouse events, e.g. 'mousemove'
|
|
// and 'mouseout' events will trigger multiple renders.
|
|
me.request = helpers$1.requestAnimFrame.call(window, function() {
|
|
me.request = null;
|
|
me.startDigest();
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
startDigest: function() {
|
|
var me = this;
|
|
|
|
me.advance();
|
|
|
|
// Do we have more stuff to animate?
|
|
if (me.animations.length > 0) {
|
|
me.requestAnimationFrame();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
advance: function() {
|
|
var animations = this.animations;
|
|
var animation, chart, numSteps, nextStep;
|
|
var i = 0;
|
|
|
|
// 1 animation per chart, so we are looping charts here
|
|
while (i < animations.length) {
|
|
animation = animations[i];
|
|
chart = animation.chart;
|
|
numSteps = animation.numSteps;
|
|
|
|
// Make sure that currentStep starts at 1
|
|
// https://github.com/chartjs/Chart.js/issues/6104
|
|
nextStep = Math.floor((Date.now() - animation.startTime) / animation.duration * numSteps) + 1;
|
|
animation.currentStep = Math.min(nextStep, numSteps);
|
|
|
|
helpers$1.callback(animation.render, [chart, animation], chart);
|
|
helpers$1.callback(animation.onAnimationProgress, [animation], chart);
|
|
|
|
if (animation.currentStep >= numSteps) {
|
|
helpers$1.callback(animation.onAnimationComplete, [animation], chart);
|
|
chart.animating = false;
|
|
animations.splice(i, 1);
|
|
} else {
|
|
++i;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
var resolve = helpers$1.options.resolve;
|
|
|
|
var arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift'];
|
|
|
|
/**
|
|
* Hooks the array methods that add or remove values ('push', pop', 'shift', 'splice',
|
|
* 'unshift') and notify the listener AFTER the array has been altered. Listeners are
|
|
* called on the 'onData*' callbacks (e.g. onDataPush, etc.) with same arguments.
|
|
*/
|
|
function listenArrayEvents(array, listener) {
|
|
if (array._chartjs) {
|
|
array._chartjs.listeners.push(listener);
|
|
return;
|
|
}
|
|
|
|
Object.defineProperty(array, '_chartjs', {
|
|
configurable: true,
|
|
enumerable: false,
|
|
value: {
|
|
listeners: [listener]
|
|
}
|
|
});
|
|
|
|
arrayEvents.forEach(function(key) {
|
|
var method = 'onData' + key.charAt(0).toUpperCase() + key.slice(1);
|
|
var base = array[key];
|
|
|
|
Object.defineProperty(array, key, {
|
|
configurable: true,
|
|
enumerable: false,
|
|
value: function() {
|
|
var args = Array.prototype.slice.call(arguments);
|
|
var res = base.apply(this, args);
|
|
|
|
helpers$1.each(array._chartjs.listeners, function(object) {
|
|
if (typeof object[method] === 'function') {
|
|
object[method].apply(object, args);
|
|
}
|
|
});
|
|
|
|
return res;
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Removes the given array event listener and cleanup extra attached properties (such as
|
|
* the _chartjs stub and overridden methods) if array doesn't have any more listeners.
|
|
*/
|
|
function unlistenArrayEvents(array, listener) {
|
|
var stub = array._chartjs;
|
|
if (!stub) {
|
|
return;
|
|
}
|
|
|
|
var listeners = stub.listeners;
|
|
var index = listeners.indexOf(listener);
|
|
if (index !== -1) {
|
|
listeners.splice(index, 1);
|
|
}
|
|
|
|
if (listeners.length > 0) {
|
|
return;
|
|
}
|
|
|
|
arrayEvents.forEach(function(key) {
|
|
delete array[key];
|
|
});
|
|
|
|
delete array._chartjs;
|
|
}
|
|
|
|
// Base class for all dataset controllers (line, bar, etc)
|
|
var DatasetController = function(chart, datasetIndex) {
|
|
this.initialize(chart, datasetIndex);
|
|
};
|
|
|
|
helpers$1.extend(DatasetController.prototype, {
|
|
|
|
/**
|
|
* Element type used to generate a meta dataset (e.g. Chart.element.Line).
|
|
* @type {Chart.core.element}
|
|
*/
|
|
datasetElementType: null,
|
|
|
|
/**
|
|
* Element type used to generate a meta data (e.g. Chart.element.Point).
|
|
* @type {Chart.core.element}
|
|
*/
|
|
dataElementType: null,
|
|
|
|
/**
|
|
* Dataset element option keys to be resolved in _resolveDatasetElementOptions.
|
|
* A derived controller may override this to resolve controller-specific options.
|
|
* The keys defined here are for backward compatibility for legend styles.
|
|
* @private
|
|
*/
|
|
_datasetElementOptions: [
|
|
'backgroundColor',
|
|
'borderCapStyle',
|
|
'borderColor',
|
|
'borderDash',
|
|
'borderDashOffset',
|
|
'borderJoinStyle',
|
|
'borderWidth'
|
|
],
|
|
|
|
/**
|
|
* Data element option keys to be resolved in _resolveDataElementOptions.
|
|
* A derived controller may override this to resolve controller-specific options.
|
|
* The keys defined here are for backward compatibility for legend styles.
|
|
* @private
|
|
*/
|
|
_dataElementOptions: [
|
|
'backgroundColor',
|
|
'borderColor',
|
|
'borderWidth',
|
|
'pointStyle'
|
|
],
|
|
|
|
initialize: function(chart, datasetIndex) {
|
|
var me = this;
|
|
me.chart = chart;
|
|
me.index = datasetIndex;
|
|
me.linkScales();
|
|
me.addElements();
|
|
me._type = me.getMeta().type;
|
|
},
|
|
|
|
updateIndex: function(datasetIndex) {
|
|
this.index = datasetIndex;
|
|
},
|
|
|
|
linkScales: function() {
|
|
var me = this;
|
|
var meta = me.getMeta();
|
|
var chart = me.chart;
|
|
var scales = chart.scales;
|
|
var dataset = me.getDataset();
|
|
var scalesOpts = chart.options.scales;
|
|
|
|
if (meta.xAxisID === null || !(meta.xAxisID in scales) || dataset.xAxisID) {
|
|
meta.xAxisID = dataset.xAxisID || scalesOpts.xAxes[0].id;
|
|
}
|
|
if (meta.yAxisID === null || !(meta.yAxisID in scales) || dataset.yAxisID) {
|
|
meta.yAxisID = dataset.yAxisID || scalesOpts.yAxes[0].id;
|
|
}
|
|
},
|
|
|
|
getDataset: function() {
|
|
return this.chart.data.datasets[this.index];
|
|
},
|
|
|
|
getMeta: function() {
|
|
return this.chart.getDatasetMeta(this.index);
|
|
},
|
|
|
|
getScaleForId: function(scaleID) {
|
|
return this.chart.scales[scaleID];
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_getValueScaleId: function() {
|
|
return this.getMeta().yAxisID;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_getIndexScaleId: function() {
|
|
return this.getMeta().xAxisID;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_getValueScale: function() {
|
|
return this.getScaleForId(this._getValueScaleId());
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_getIndexScale: function() {
|
|
return this.getScaleForId(this._getIndexScaleId());
|
|
},
|
|
|
|
reset: function() {
|
|
this._update(true);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
destroy: function() {
|
|
if (this._data) {
|
|
unlistenArrayEvents(this._data, this);
|
|
}
|
|
},
|
|
|
|
createMetaDataset: function() {
|
|
var me = this;
|
|
var type = me.datasetElementType;
|
|
return type && new type({
|
|
_chart: me.chart,
|
|
_datasetIndex: me.index
|
|
});
|
|
},
|
|
|
|
createMetaData: function(index) {
|
|
var me = this;
|
|
var type = me.dataElementType;
|
|
return type && new type({
|
|
_chart: me.chart,
|
|
_datasetIndex: me.index,
|
|
_index: index
|
|
});
|
|
},
|
|
|
|
addElements: function() {
|
|
var me = this;
|
|
var meta = me.getMeta();
|
|
var data = me.getDataset().data || [];
|
|
var metaData = meta.data;
|
|
var i, ilen;
|
|
|
|
for (i = 0, ilen = data.length; i < ilen; ++i) {
|
|
metaData[i] = metaData[i] || me.createMetaData(i);
|
|
}
|
|
|
|
meta.dataset = meta.dataset || me.createMetaDataset();
|
|
},
|
|
|
|
addElementAndReset: function(index) {
|
|
var element = this.createMetaData(index);
|
|
this.getMeta().data.splice(index, 0, element);
|
|
this.updateElement(element, index, true);
|
|
},
|
|
|
|
buildOrUpdateElements: function() {
|
|
var me = this;
|
|
var dataset = me.getDataset();
|
|
var data = dataset.data || (dataset.data = []);
|
|
|
|
// In order to correctly handle data addition/deletion animation (an thus simulate
|
|
// real-time charts), we need to monitor these data modifications and synchronize
|
|
// the internal meta data accordingly.
|
|
if (me._data !== data) {
|
|
if (me._data) {
|
|
// This case happens when the user replaced the data array instance.
|
|
unlistenArrayEvents(me._data, me);
|
|
}
|
|
|
|
if (data && Object.isExtensible(data)) {
|
|
listenArrayEvents(data, me);
|
|
}
|
|
me._data = data;
|
|
}
|
|
|
|
// Re-sync meta data in case the user replaced the data array or if we missed
|
|
// any updates and so make sure that we handle number of datapoints changing.
|
|
me.resyncElements();
|
|
},
|
|
|
|
/**
|
|
* Returns the merged user-supplied and default dataset-level options
|
|
* @private
|
|
*/
|
|
_configure: function() {
|
|
var me = this;
|
|
me._config = helpers$1.merge({}, [
|
|
me.chart.options.datasets[me._type],
|
|
me.getDataset(),
|
|
], {
|
|
merger: function(key, target, source) {
|
|
if (key !== '_meta' && key !== 'data') {
|
|
helpers$1._merger(key, target, source);
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
_update: function(reset) {
|
|
var me = this;
|
|
me._configure();
|
|
me._cachedDataOpts = null;
|
|
me.update(reset);
|
|
},
|
|
|
|
update: helpers$1.noop,
|
|
|
|
transition: function(easingValue) {
|
|
var meta = this.getMeta();
|
|
var elements = meta.data || [];
|
|
var ilen = elements.length;
|
|
var i = 0;
|
|
|
|
for (; i < ilen; ++i) {
|
|
elements[i].transition(easingValue);
|
|
}
|
|
|
|
if (meta.dataset) {
|
|
meta.dataset.transition(easingValue);
|
|
}
|
|
},
|
|
|
|
draw: function() {
|
|
var meta = this.getMeta();
|
|
var elements = meta.data || [];
|
|
var ilen = elements.length;
|
|
var i = 0;
|
|
|
|
if (meta.dataset) {
|
|
meta.dataset.draw();
|
|
}
|
|
|
|
for (; i < ilen; ++i) {
|
|
elements[i].draw();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Returns a set of predefined style properties that should be used to represent the dataset
|
|
* or the data if the index is specified
|
|
* @param {number} index - data index
|
|
* @return {IStyleInterface} style object
|
|
*/
|
|
getStyle: function(index) {
|
|
var me = this;
|
|
var meta = me.getMeta();
|
|
var dataset = meta.dataset;
|
|
var style;
|
|
|
|
me._configure();
|
|
if (dataset && index === undefined) {
|
|
style = me._resolveDatasetElementOptions(dataset || {});
|
|
} else {
|
|
index = index || 0;
|
|
style = me._resolveDataElementOptions(meta.data[index] || {}, index);
|
|
}
|
|
|
|
if (style.fill === false || style.fill === null) {
|
|
style.backgroundColor = style.borderColor;
|
|
}
|
|
|
|
return style;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_resolveDatasetElementOptions: function(element, hover) {
|
|
var me = this;
|
|
var chart = me.chart;
|
|
var datasetOpts = me._config;
|
|
var custom = element.custom || {};
|
|
var options = chart.options.elements[me.datasetElementType.prototype._type] || {};
|
|
var elementOptions = me._datasetElementOptions;
|
|
var values = {};
|
|
var i, ilen, key, readKey;
|
|
|
|
// Scriptable options
|
|
var context = {
|
|
chart: chart,
|
|
dataset: me.getDataset(),
|
|
datasetIndex: me.index,
|
|
hover: hover
|
|
};
|
|
|
|
for (i = 0, ilen = elementOptions.length; i < ilen; ++i) {
|
|
key = elementOptions[i];
|
|
readKey = hover ? 'hover' + key.charAt(0).toUpperCase() + key.slice(1) : key;
|
|
values[key] = resolve([
|
|
custom[readKey],
|
|
datasetOpts[readKey],
|
|
options[readKey]
|
|
], context);
|
|
}
|
|
|
|
return values;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_resolveDataElementOptions: function(element, index) {
|
|
var me = this;
|
|
var custom = element && element.custom;
|
|
var cached = me._cachedDataOpts;
|
|
if (cached && !custom) {
|
|
return cached;
|
|
}
|
|
var chart = me.chart;
|
|
var datasetOpts = me._config;
|
|
var options = chart.options.elements[me.dataElementType.prototype._type] || {};
|
|
var elementOptions = me._dataElementOptions;
|
|
var values = {};
|
|
|
|
// Scriptable options
|
|
var context = {
|
|
chart: chart,
|
|
dataIndex: index,
|
|
dataset: me.getDataset(),
|
|
datasetIndex: me.index
|
|
};
|
|
|
|
// `resolve` sets cacheable to `false` if any option is indexed or scripted
|
|
var info = {cacheable: !custom};
|
|
|
|
var keys, i, ilen, key;
|
|
|
|
custom = custom || {};
|
|
|
|
if (helpers$1.isArray(elementOptions)) {
|
|
for (i = 0, ilen = elementOptions.length; i < ilen; ++i) {
|
|
key = elementOptions[i];
|
|
values[key] = resolve([
|
|
custom[key],
|
|
datasetOpts[key],
|
|
options[key]
|
|
], context, index, info);
|
|
}
|
|
} else {
|
|
keys = Object.keys(elementOptions);
|
|
for (i = 0, ilen = keys.length; i < ilen; ++i) {
|
|
key = keys[i];
|
|
values[key] = resolve([
|
|
custom[key],
|
|
datasetOpts[elementOptions[key]],
|
|
datasetOpts[key],
|
|
options[key]
|
|
], context, index, info);
|
|
}
|
|
}
|
|
|
|
if (info.cacheable) {
|
|
me._cachedDataOpts = Object.freeze(values);
|
|
}
|
|
|
|
return values;
|
|
},
|
|
|
|
removeHoverStyle: function(element) {
|
|
helpers$1.merge(element._model, element.$previousStyle || {});
|
|
delete element.$previousStyle;
|
|
},
|
|
|
|
setHoverStyle: function(element) {
|
|
var dataset = this.chart.data.datasets[element._datasetIndex];
|
|
var index = element._index;
|
|
var custom = element.custom || {};
|
|
var model = element._model;
|
|
var getHoverColor = helpers$1.getHoverColor;
|
|
|
|
element.$previousStyle = {
|
|
backgroundColor: model.backgroundColor,
|
|
borderColor: model.borderColor,
|
|
borderWidth: model.borderWidth
|
|
};
|
|
|
|
model.backgroundColor = resolve([custom.hoverBackgroundColor, dataset.hoverBackgroundColor, getHoverColor(model.backgroundColor)], undefined, index);
|
|
model.borderColor = resolve([custom.hoverBorderColor, dataset.hoverBorderColor, getHoverColor(model.borderColor)], undefined, index);
|
|
model.borderWidth = resolve([custom.hoverBorderWidth, dataset.hoverBorderWidth, model.borderWidth], undefined, index);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_removeDatasetHoverStyle: function() {
|
|
var element = this.getMeta().dataset;
|
|
|
|
if (element) {
|
|
this.removeHoverStyle(element);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_setDatasetHoverStyle: function() {
|
|
var element = this.getMeta().dataset;
|
|
var prev = {};
|
|
var i, ilen, key, keys, hoverOptions, model;
|
|
|
|
if (!element) {
|
|
return;
|
|
}
|
|
|
|
model = element._model;
|
|
hoverOptions = this._resolveDatasetElementOptions(element, true);
|
|
|
|
keys = Object.keys(hoverOptions);
|
|
for (i = 0, ilen = keys.length; i < ilen; ++i) {
|
|
key = keys[i];
|
|
prev[key] = model[key];
|
|
model[key] = hoverOptions[key];
|
|
}
|
|
|
|
element.$previousStyle = prev;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
resyncElements: function() {
|
|
var me = this;
|
|
var meta = me.getMeta();
|
|
var data = me.getDataset().data;
|
|
var numMeta = meta.data.length;
|
|
var numData = data.length;
|
|
|
|
if (numData < numMeta) {
|
|
meta.data.splice(numData, numMeta - numData);
|
|
} else if (numData > numMeta) {
|
|
me.insertElements(numMeta, numData - numMeta);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
insertElements: function(start, count) {
|
|
for (var i = 0; i < count; ++i) {
|
|
this.addElementAndReset(start + i);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
onDataPush: function() {
|
|
var count = arguments.length;
|
|
this.insertElements(this.getDataset().data.length - count, count);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
onDataPop: function() {
|
|
this.getMeta().data.pop();
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
onDataShift: function() {
|
|
this.getMeta().data.shift();
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
onDataSplice: function(start, count) {
|
|
this.getMeta().data.splice(start, count);
|
|
this.insertElements(start, arguments.length - 2);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
onDataUnshift: function() {
|
|
this.insertElements(0, arguments.length);
|
|
}
|
|
});
|
|
|
|
DatasetController.extend = helpers$1.inherits;
|
|
|
|
var core_datasetController = DatasetController;
|
|
|
|
var TAU = Math.PI * 2;
|
|
|
|
core_defaults._set('global', {
|
|
elements: {
|
|
arc: {
|
|
backgroundColor: core_defaults.global.defaultColor,
|
|
borderColor: '#fff',
|
|
borderWidth: 2,
|
|
borderAlign: 'center'
|
|
}
|
|
}
|
|
});
|
|
|
|
function clipArc(ctx, arc) {
|
|
var startAngle = arc.startAngle;
|
|
var endAngle = arc.endAngle;
|
|
var pixelMargin = arc.pixelMargin;
|
|
var angleMargin = pixelMargin / arc.outerRadius;
|
|
var x = arc.x;
|
|
var y = arc.y;
|
|
|
|
// Draw an inner border by cliping the arc and drawing a double-width border
|
|
// Enlarge the clipping arc by 0.33 pixels to eliminate glitches between borders
|
|
ctx.beginPath();
|
|
ctx.arc(x, y, arc.outerRadius, startAngle - angleMargin, endAngle + angleMargin);
|
|
if (arc.innerRadius > pixelMargin) {
|
|
angleMargin = pixelMargin / arc.innerRadius;
|
|
ctx.arc(x, y, arc.innerRadius - pixelMargin, endAngle + angleMargin, startAngle - angleMargin, true);
|
|
} else {
|
|
ctx.arc(x, y, pixelMargin, endAngle + Math.PI / 2, startAngle - Math.PI / 2);
|
|
}
|
|
ctx.closePath();
|
|
ctx.clip();
|
|
}
|
|
|
|
function drawFullCircleBorders(ctx, vm, arc, inner) {
|
|
var endAngle = arc.endAngle;
|
|
var i;
|
|
|
|
if (inner) {
|
|
arc.endAngle = arc.startAngle + TAU;
|
|
clipArc(ctx, arc);
|
|
arc.endAngle = endAngle;
|
|
if (arc.endAngle === arc.startAngle && arc.fullCircles) {
|
|
arc.endAngle += TAU;
|
|
arc.fullCircles--;
|
|
}
|
|
}
|
|
|
|
ctx.beginPath();
|
|
ctx.arc(arc.x, arc.y, arc.innerRadius, arc.startAngle + TAU, arc.startAngle, true);
|
|
for (i = 0; i < arc.fullCircles; ++i) {
|
|
ctx.stroke();
|
|
}
|
|
|
|
ctx.beginPath();
|
|
ctx.arc(arc.x, arc.y, vm.outerRadius, arc.startAngle, arc.startAngle + TAU);
|
|
for (i = 0; i < arc.fullCircles; ++i) {
|
|
ctx.stroke();
|
|
}
|
|
}
|
|
|
|
function drawBorder(ctx, vm, arc) {
|
|
var inner = vm.borderAlign === 'inner';
|
|
|
|
if (inner) {
|
|
ctx.lineWidth = vm.borderWidth * 2;
|
|
ctx.lineJoin = 'round';
|
|
} else {
|
|
ctx.lineWidth = vm.borderWidth;
|
|
ctx.lineJoin = 'bevel';
|
|
}
|
|
|
|
if (arc.fullCircles) {
|
|
drawFullCircleBorders(ctx, vm, arc, inner);
|
|
}
|
|
|
|
if (inner) {
|
|
clipArc(ctx, arc);
|
|
}
|
|
|
|
ctx.beginPath();
|
|
ctx.arc(arc.x, arc.y, vm.outerRadius, arc.startAngle, arc.endAngle);
|
|
ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true);
|
|
ctx.closePath();
|
|
ctx.stroke();
|
|
}
|
|
|
|
var element_arc = core_element.extend({
|
|
_type: 'arc',
|
|
|
|
inLabelRange: function(mouseX) {
|
|
var vm = this._view;
|
|
|
|
if (vm) {
|
|
return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2));
|
|
}
|
|
return false;
|
|
},
|
|
|
|
inRange: function(chartX, chartY) {
|
|
var vm = this._view;
|
|
|
|
if (vm) {
|
|
var pointRelativePosition = helpers$1.getAngleFromPoint(vm, {x: chartX, y: chartY});
|
|
var angle = pointRelativePosition.angle;
|
|
var distance = pointRelativePosition.distance;
|
|
|
|
// Sanitise angle range
|
|
var startAngle = vm.startAngle;
|
|
var endAngle = vm.endAngle;
|
|
while (endAngle < startAngle) {
|
|
endAngle += TAU;
|
|
}
|
|
while (angle > endAngle) {
|
|
angle -= TAU;
|
|
}
|
|
while (angle < startAngle) {
|
|
angle += TAU;
|
|
}
|
|
|
|
// Check if within the range of the open/close angle
|
|
var betweenAngles = (angle >= startAngle && angle <= endAngle);
|
|
var withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius);
|
|
|
|
return (betweenAngles && withinRadius);
|
|
}
|
|
return false;
|
|
},
|
|
|
|
getCenterPoint: function() {
|
|
var vm = this._view;
|
|
var halfAngle = (vm.startAngle + vm.endAngle) / 2;
|
|
var halfRadius = (vm.innerRadius + vm.outerRadius) / 2;
|
|
return {
|
|
x: vm.x + Math.cos(halfAngle) * halfRadius,
|
|
y: vm.y + Math.sin(halfAngle) * halfRadius
|
|
};
|
|
},
|
|
|
|
getArea: function() {
|
|
var vm = this._view;
|
|
return Math.PI * ((vm.endAngle - vm.startAngle) / (2 * Math.PI)) * (Math.pow(vm.outerRadius, 2) - Math.pow(vm.innerRadius, 2));
|
|
},
|
|
|
|
tooltipPosition: function() {
|
|
var vm = this._view;
|
|
var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2);
|
|
var rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius;
|
|
|
|
return {
|
|
x: vm.x + (Math.cos(centreAngle) * rangeFromCentre),
|
|
y: vm.y + (Math.sin(centreAngle) * rangeFromCentre)
|
|
};
|
|
},
|
|
|
|
draw: function() {
|
|
var ctx = this._chart.ctx;
|
|
var vm = this._view;
|
|
var pixelMargin = (vm.borderAlign === 'inner') ? 0.33 : 0;
|
|
var arc = {
|
|
x: vm.x,
|
|
y: vm.y,
|
|
innerRadius: vm.innerRadius,
|
|
outerRadius: Math.max(vm.outerRadius - pixelMargin, 0),
|
|
pixelMargin: pixelMargin,
|
|
startAngle: vm.startAngle,
|
|
endAngle: vm.endAngle,
|
|
fullCircles: Math.floor(vm.circumference / TAU)
|
|
};
|
|
var i;
|
|
|
|
ctx.save();
|
|
|
|
ctx.fillStyle = vm.backgroundColor;
|
|
ctx.strokeStyle = vm.borderColor;
|
|
|
|
if (arc.fullCircles) {
|
|
arc.endAngle = arc.startAngle + TAU;
|
|
ctx.beginPath();
|
|
ctx.arc(arc.x, arc.y, arc.outerRadius, arc.startAngle, arc.endAngle);
|
|
ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true);
|
|
ctx.closePath();
|
|
for (i = 0; i < arc.fullCircles; ++i) {
|
|
ctx.fill();
|
|
}
|
|
arc.endAngle = arc.startAngle + vm.circumference % TAU;
|
|
}
|
|
|
|
ctx.beginPath();
|
|
ctx.arc(arc.x, arc.y, arc.outerRadius, arc.startAngle, arc.endAngle);
|
|
ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
if (vm.borderWidth) {
|
|
drawBorder(ctx, vm, arc);
|
|
}
|
|
|
|
ctx.restore();
|
|
}
|
|
});
|
|
|
|
var valueOrDefault$1 = helpers$1.valueOrDefault;
|
|
|
|
var defaultColor = core_defaults.global.defaultColor;
|
|
|
|
core_defaults._set('global', {
|
|
elements: {
|
|
line: {
|
|
tension: 0.4,
|
|
backgroundColor: defaultColor,
|
|
borderWidth: 3,
|
|
borderColor: defaultColor,
|
|
borderCapStyle: 'butt',
|
|
borderDash: [],
|
|
borderDashOffset: 0.0,
|
|
borderJoinStyle: 'miter',
|
|
capBezierPoints: true,
|
|
fill: true, // do we fill in the area between the line and its base axis
|
|
}
|
|
}
|
|
});
|
|
|
|
var element_line = core_element.extend({
|
|
_type: 'line',
|
|
|
|
draw: function() {
|
|
var me = this;
|
|
var vm = me._view;
|
|
var ctx = me._chart.ctx;
|
|
var spanGaps = vm.spanGaps;
|
|
var points = me._children.slice(); // clone array
|
|
var globalDefaults = core_defaults.global;
|
|
var globalOptionLineElements = globalDefaults.elements.line;
|
|
var lastDrawnIndex = -1;
|
|
var closePath = me._loop;
|
|
var index, previous, currentVM;
|
|
|
|
if (!points.length) {
|
|
return;
|
|
}
|
|
|
|
if (me._loop) {
|
|
for (index = 0; index < points.length; ++index) {
|
|
previous = helpers$1.previousItem(points, index);
|
|
// If the line has an open path, shift the point array
|
|
if (!points[index]._view.skip && previous._view.skip) {
|
|
points = points.slice(index).concat(points.slice(0, index));
|
|
closePath = spanGaps;
|
|
break;
|
|
}
|
|
}
|
|
// If the line has a close path, add the first point again
|
|
if (closePath) {
|
|
points.push(points[0]);
|
|
}
|
|
}
|
|
|
|
ctx.save();
|
|
|
|
// Stroke Line Options
|
|
ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle;
|
|
|
|
// IE 9 and 10 do not support line dash
|
|
if (ctx.setLineDash) {
|
|
ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash);
|
|
}
|
|
|
|
ctx.lineDashOffset = valueOrDefault$1(vm.borderDashOffset, globalOptionLineElements.borderDashOffset);
|
|
ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle;
|
|
ctx.lineWidth = valueOrDefault$1(vm.borderWidth, globalOptionLineElements.borderWidth);
|
|
ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor;
|
|
|
|
// Stroke Line
|
|
ctx.beginPath();
|
|
|
|
// First point moves to it's starting position no matter what
|
|
currentVM = points[0]._view;
|
|
if (!currentVM.skip) {
|
|
ctx.moveTo(currentVM.x, currentVM.y);
|
|
lastDrawnIndex = 0;
|
|
}
|
|
|
|
for (index = 1; index < points.length; ++index) {
|
|
currentVM = points[index]._view;
|
|
previous = lastDrawnIndex === -1 ? helpers$1.previousItem(points, index) : points[lastDrawnIndex];
|
|
|
|
if (!currentVM.skip) {
|
|
if ((lastDrawnIndex !== (index - 1) && !spanGaps) || lastDrawnIndex === -1) {
|
|
// There was a gap and this is the first point after the gap
|
|
ctx.moveTo(currentVM.x, currentVM.y);
|
|
} else {
|
|
// Line to next point
|
|
helpers$1.canvas.lineTo(ctx, previous._view, currentVM);
|
|
}
|
|
lastDrawnIndex = index;
|
|
}
|
|
}
|
|
|
|
if (closePath) {
|
|
ctx.closePath();
|
|
}
|
|
|
|
ctx.stroke();
|
|
ctx.restore();
|
|
}
|
|
});
|
|
|
|
var valueOrDefault$2 = helpers$1.valueOrDefault;
|
|
|
|
var defaultColor$1 = core_defaults.global.defaultColor;
|
|
|
|
core_defaults._set('global', {
|
|
elements: {
|
|
point: {
|
|
radius: 3,
|
|
pointStyle: 'circle',
|
|
backgroundColor: defaultColor$1,
|
|
borderColor: defaultColor$1,
|
|
borderWidth: 1,
|
|
// Hover
|
|
hitRadius: 1,
|
|
hoverRadius: 4,
|
|
hoverBorderWidth: 1
|
|
}
|
|
}
|
|
});
|
|
|
|
function xRange(mouseX) {
|
|
var vm = this._view;
|
|
return vm ? (Math.abs(mouseX - vm.x) < vm.radius + vm.hitRadius) : false;
|
|
}
|
|
|
|
function yRange(mouseY) {
|
|
var vm = this._view;
|
|
return vm ? (Math.abs(mouseY - vm.y) < vm.radius + vm.hitRadius) : false;
|
|
}
|
|
|
|
var element_point = core_element.extend({
|
|
_type: 'point',
|
|
|
|
inRange: function(mouseX, mouseY) {
|
|
var vm = this._view;
|
|
return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false;
|
|
},
|
|
|
|
inLabelRange: xRange,
|
|
inXRange: xRange,
|
|
inYRange: yRange,
|
|
|
|
getCenterPoint: function() {
|
|
var vm = this._view;
|
|
return {
|
|
x: vm.x,
|
|
y: vm.y
|
|
};
|
|
},
|
|
|
|
getArea: function() {
|
|
return Math.PI * Math.pow(this._view.radius, 2);
|
|
},
|
|
|
|
tooltipPosition: function() {
|
|
var vm = this._view;
|
|
return {
|
|
x: vm.x,
|
|
y: vm.y,
|
|
padding: vm.radius + vm.borderWidth
|
|
};
|
|
},
|
|
|
|
draw: function(chartArea) {
|
|
var vm = this._view;
|
|
var ctx = this._chart.ctx;
|
|
var pointStyle = vm.pointStyle;
|
|
var rotation = vm.rotation;
|
|
var radius = vm.radius;
|
|
var x = vm.x;
|
|
var y = vm.y;
|
|
var globalDefaults = core_defaults.global;
|
|
var defaultColor = globalDefaults.defaultColor; // eslint-disable-line no-shadow
|
|
|
|
if (vm.skip) {
|
|
return;
|
|
}
|
|
|
|
// Clipping for Points.
|
|
if (chartArea === undefined || helpers$1.canvas._isPointInArea(vm, chartArea)) {
|
|
ctx.strokeStyle = vm.borderColor || defaultColor;
|
|
ctx.lineWidth = valueOrDefault$2(vm.borderWidth, globalDefaults.elements.point.borderWidth);
|
|
ctx.fillStyle = vm.backgroundColor || defaultColor;
|
|
helpers$1.canvas.drawPoint(ctx, pointStyle, radius, x, y, rotation);
|
|
}
|
|
}
|
|
});
|
|
|
|
var defaultColor$2 = core_defaults.global.defaultColor;
|
|
|
|
core_defaults._set('global', {
|
|
elements: {
|
|
rectangle: {
|
|
backgroundColor: defaultColor$2,
|
|
borderColor: defaultColor$2,
|
|
borderSkipped: 'bottom',
|
|
borderWidth: 0
|
|
}
|
|
}
|
|
});
|
|
|
|
function isVertical(vm) {
|
|
return vm && vm.width !== undefined;
|
|
}
|
|
|
|
/**
|
|
* Helper function to get the bounds of the bar regardless of the orientation
|
|
* @param bar {Chart.Element.Rectangle} the bar
|
|
* @return {Bounds} bounds of the bar
|
|
* @private
|
|
*/
|
|
function getBarBounds(vm) {
|
|
var x1, x2, y1, y2, half;
|
|
|
|
if (isVertical(vm)) {
|
|
half = vm.width / 2;
|
|
x1 = vm.x - half;
|
|
x2 = vm.x + half;
|
|
y1 = Math.min(vm.y, vm.base);
|
|
y2 = Math.max(vm.y, vm.base);
|
|
} else {
|
|
half = vm.height / 2;
|
|
x1 = Math.min(vm.x, vm.base);
|
|
x2 = Math.max(vm.x, vm.base);
|
|
y1 = vm.y - half;
|
|
y2 = vm.y + half;
|
|
}
|
|
|
|
return {
|
|
left: x1,
|
|
top: y1,
|
|
right: x2,
|
|
bottom: y2
|
|
};
|
|
}
|
|
|
|
function swap(orig, v1, v2) {
|
|
return orig === v1 ? v2 : orig === v2 ? v1 : orig;
|
|
}
|
|
|
|
function parseBorderSkipped(vm) {
|
|
var edge = vm.borderSkipped;
|
|
var res = {};
|
|
|
|
if (!edge) {
|
|
return res;
|
|
}
|
|
|
|
if (vm.horizontal) {
|
|
if (vm.base > vm.x) {
|
|
edge = swap(edge, 'left', 'right');
|
|
}
|
|
} else if (vm.base < vm.y) {
|
|
edge = swap(edge, 'bottom', 'top');
|
|
}
|
|
|
|
res[edge] = true;
|
|
return res;
|
|
}
|
|
|
|
function parseBorderWidth(vm, maxW, maxH) {
|
|
var value = vm.borderWidth;
|
|
var skip = parseBorderSkipped(vm);
|
|
var t, r, b, l;
|
|
|
|
if (helpers$1.isObject(value)) {
|
|
t = +value.top || 0;
|
|
r = +value.right || 0;
|
|
b = +value.bottom || 0;
|
|
l = +value.left || 0;
|
|
} else {
|
|
t = r = b = l = +value || 0;
|
|
}
|
|
|
|
return {
|
|
t: skip.top || (t < 0) ? 0 : t > maxH ? maxH : t,
|
|
r: skip.right || (r < 0) ? 0 : r > maxW ? maxW : r,
|
|
b: skip.bottom || (b < 0) ? 0 : b > maxH ? maxH : b,
|
|
l: skip.left || (l < 0) ? 0 : l > maxW ? maxW : l
|
|
};
|
|
}
|
|
|
|
function boundingRects(vm) {
|
|
var bounds = getBarBounds(vm);
|
|
var width = bounds.right - bounds.left;
|
|
var height = bounds.bottom - bounds.top;
|
|
var border = parseBorderWidth(vm, width / 2, height / 2);
|
|
|
|
return {
|
|
outer: {
|
|
x: bounds.left,
|
|
y: bounds.top,
|
|
w: width,
|
|
h: height
|
|
},
|
|
inner: {
|
|
x: bounds.left + border.l,
|
|
y: bounds.top + border.t,
|
|
w: width - border.l - border.r,
|
|
h: height - border.t - border.b
|
|
}
|
|
};
|
|
}
|
|
|
|
function inRange(vm, x, y) {
|
|
var skipX = x === null;
|
|
var skipY = y === null;
|
|
var bounds = !vm || (skipX && skipY) ? false : getBarBounds(vm);
|
|
|
|
return bounds
|
|
&& (skipX || x >= bounds.left && x <= bounds.right)
|
|
&& (skipY || y >= bounds.top && y <= bounds.bottom);
|
|
}
|
|
|
|
var element_rectangle = core_element.extend({
|
|
_type: 'rectangle',
|
|
|
|
draw: function() {
|
|
var ctx = this._chart.ctx;
|
|
var vm = this._view;
|
|
var rects = boundingRects(vm);
|
|
var outer = rects.outer;
|
|
var inner = rects.inner;
|
|
|
|
ctx.fillStyle = vm.backgroundColor;
|
|
ctx.fillRect(outer.x, outer.y, outer.w, outer.h);
|
|
|
|
if (outer.w === inner.w && outer.h === inner.h) {
|
|
return;
|
|
}
|
|
|
|
ctx.save();
|
|
ctx.beginPath();
|
|
ctx.rect(outer.x, outer.y, outer.w, outer.h);
|
|
ctx.clip();
|
|
ctx.fillStyle = vm.borderColor;
|
|
ctx.rect(inner.x, inner.y, inner.w, inner.h);
|
|
ctx.fill('evenodd');
|
|
ctx.restore();
|
|
},
|
|
|
|
height: function() {
|
|
var vm = this._view;
|
|
return vm.base - vm.y;
|
|
},
|
|
|
|
inRange: function(mouseX, mouseY) {
|
|
return inRange(this._view, mouseX, mouseY);
|
|
},
|
|
|
|
inLabelRange: function(mouseX, mouseY) {
|
|
var vm = this._view;
|
|
return isVertical(vm)
|
|
? inRange(vm, mouseX, null)
|
|
: inRange(vm, null, mouseY);
|
|
},
|
|
|
|
inXRange: function(mouseX) {
|
|
return inRange(this._view, mouseX, null);
|
|
},
|
|
|
|
inYRange: function(mouseY) {
|
|
return inRange(this._view, null, mouseY);
|
|
},
|
|
|
|
getCenterPoint: function() {
|
|
var vm = this._view;
|
|
var x, y;
|
|
if (isVertical(vm)) {
|
|
x = vm.x;
|
|
y = (vm.y + vm.base) / 2;
|
|
} else {
|
|
x = (vm.x + vm.base) / 2;
|
|
y = vm.y;
|
|
}
|
|
|
|
return {x: x, y: y};
|
|
},
|
|
|
|
getArea: function() {
|
|
var vm = this._view;
|
|
|
|
return isVertical(vm)
|
|
? vm.width * Math.abs(vm.y - vm.base)
|
|
: vm.height * Math.abs(vm.x - vm.base);
|
|
},
|
|
|
|
tooltipPosition: function() {
|
|
var vm = this._view;
|
|
return {
|
|
x: vm.x,
|
|
y: vm.y
|
|
};
|
|
}
|
|
});
|
|
|
|
var elements = {};
|
|
var Arc = element_arc;
|
|
var Line = element_line;
|
|
var Point = element_point;
|
|
var Rectangle = element_rectangle;
|
|
elements.Arc = Arc;
|
|
elements.Line = Line;
|
|
elements.Point = Point;
|
|
elements.Rectangle = Rectangle;
|
|
|
|
var deprecated = helpers$1._deprecated;
|
|
var valueOrDefault$3 = helpers$1.valueOrDefault;
|
|
|
|
core_defaults._set('bar', {
|
|
hover: {
|
|
mode: 'label'
|
|
},
|
|
|
|
scales: {
|
|
xAxes: [{
|
|
type: 'category',
|
|
offset: true,
|
|
gridLines: {
|
|
offsetGridLines: true
|
|
}
|
|
}],
|
|
|
|
yAxes: [{
|
|
type: 'linear'
|
|
}]
|
|
}
|
|
});
|
|
|
|
core_defaults._set('global', {
|
|
datasets: {
|
|
bar: {
|
|
categoryPercentage: 0.8,
|
|
barPercentage: 0.9
|
|
}
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Computes the "optimal" sample size to maintain bars equally sized while preventing overlap.
|
|
* @private
|
|
*/
|
|
function computeMinSampleSize(scale, pixels) {
|
|
var min = scale._length;
|
|
var prev, curr, i, ilen;
|
|
|
|
for (i = 1, ilen = pixels.length; i < ilen; ++i) {
|
|
min = Math.min(min, Math.abs(pixels[i] - pixels[i - 1]));
|
|
}
|
|
|
|
for (i = 0, ilen = scale.getTicks().length; i < ilen; ++i) {
|
|
curr = scale.getPixelForTick(i);
|
|
min = i > 0 ? Math.min(min, Math.abs(curr - prev)) : min;
|
|
prev = curr;
|
|
}
|
|
|
|
return min;
|
|
}
|
|
|
|
/**
|
|
* Computes an "ideal" category based on the absolute bar thickness or, if undefined or null,
|
|
* uses the smallest interval (see computeMinSampleSize) that prevents bar overlapping. This
|
|
* mode currently always generates bars equally sized (until we introduce scriptable options?).
|
|
* @private
|
|
*/
|
|
function computeFitCategoryTraits(index, ruler, options) {
|
|
var thickness = options.barThickness;
|
|
var count = ruler.stackCount;
|
|
var curr = ruler.pixels[index];
|
|
var min = helpers$1.isNullOrUndef(thickness)
|
|
? computeMinSampleSize(ruler.scale, ruler.pixels)
|
|
: -1;
|
|
var size, ratio;
|
|
|
|
if (helpers$1.isNullOrUndef(thickness)) {
|
|
size = min * options.categoryPercentage;
|
|
ratio = options.barPercentage;
|
|
} else {
|
|
// When bar thickness is enforced, category and bar percentages are ignored.
|
|
// Note(SB): we could add support for relative bar thickness (e.g. barThickness: '50%')
|
|
// and deprecate barPercentage since this value is ignored when thickness is absolute.
|
|
size = thickness * count;
|
|
ratio = 1;
|
|
}
|
|
|
|
return {
|
|
chunk: size / count,
|
|
ratio: ratio,
|
|
start: curr - (size / 2)
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Computes an "optimal" category that globally arranges bars side by side (no gap when
|
|
* percentage options are 1), based on the previous and following categories. This mode
|
|
* generates bars with different widths when data are not evenly spaced.
|
|
* @private
|
|
*/
|
|
function computeFlexCategoryTraits(index, ruler, options) {
|
|
var pixels = ruler.pixels;
|
|
var curr = pixels[index];
|
|
var prev = index > 0 ? pixels[index - 1] : null;
|
|
var next = index < pixels.length - 1 ? pixels[index + 1] : null;
|
|
var percent = options.categoryPercentage;
|
|
var start, size;
|
|
|
|
if (prev === null) {
|
|
// first data: its size is double based on the next point or,
|
|
// if it's also the last data, we use the scale size.
|
|
prev = curr - (next === null ? ruler.end - ruler.start : next - curr);
|
|
}
|
|
|
|
if (next === null) {
|
|
// last data: its size is also double based on the previous point.
|
|
next = curr + curr - prev;
|
|
}
|
|
|
|
start = curr - (curr - Math.min(prev, next)) / 2 * percent;
|
|
size = Math.abs(next - prev) / 2 * percent;
|
|
|
|
return {
|
|
chunk: size / ruler.stackCount,
|
|
ratio: options.barPercentage,
|
|
start: start
|
|
};
|
|
}
|
|
|
|
var controller_bar = core_datasetController.extend({
|
|
|
|
dataElementType: elements.Rectangle,
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_dataElementOptions: [
|
|
'backgroundColor',
|
|
'borderColor',
|
|
'borderSkipped',
|
|
'borderWidth',
|
|
'barPercentage',
|
|
'barThickness',
|
|
'categoryPercentage',
|
|
'maxBarThickness',
|
|
'minBarLength'
|
|
],
|
|
|
|
initialize: function() {
|
|
var me = this;
|
|
var meta, scaleOpts;
|
|
|
|
core_datasetController.prototype.initialize.apply(me, arguments);
|
|
|
|
meta = me.getMeta();
|
|
meta.stack = me.getDataset().stack;
|
|
meta.bar = true;
|
|
|
|
scaleOpts = me._getIndexScale().options;
|
|
deprecated('bar chart', scaleOpts.barPercentage, 'scales.[x/y]Axes.barPercentage', 'dataset.barPercentage');
|
|
deprecated('bar chart', scaleOpts.barThickness, 'scales.[x/y]Axes.barThickness', 'dataset.barThickness');
|
|
deprecated('bar chart', scaleOpts.categoryPercentage, 'scales.[x/y]Axes.categoryPercentage', 'dataset.categoryPercentage');
|
|
deprecated('bar chart', me._getValueScale().options.minBarLength, 'scales.[x/y]Axes.minBarLength', 'dataset.minBarLength');
|
|
deprecated('bar chart', scaleOpts.maxBarThickness, 'scales.[x/y]Axes.maxBarThickness', 'dataset.maxBarThickness');
|
|
},
|
|
|
|
update: function(reset) {
|
|
var me = this;
|
|
var rects = me.getMeta().data;
|
|
var i, ilen;
|
|
|
|
me._ruler = me.getRuler();
|
|
|
|
for (i = 0, ilen = rects.length; i < ilen; ++i) {
|
|
me.updateElement(rects[i], i, reset);
|
|
}
|
|
},
|
|
|
|
updateElement: function(rectangle, index, reset) {
|
|
var me = this;
|
|
var meta = me.getMeta();
|
|
var dataset = me.getDataset();
|
|
var options = me._resolveDataElementOptions(rectangle, index);
|
|
|
|
rectangle._xScale = me.getScaleForId(meta.xAxisID);
|
|
rectangle._yScale = me.getScaleForId(meta.yAxisID);
|
|
rectangle._datasetIndex = me.index;
|
|
rectangle._index = index;
|
|
rectangle._model = {
|
|
backgroundColor: options.backgroundColor,
|
|
borderColor: options.borderColor,
|
|
borderSkipped: options.borderSkipped,
|
|
borderWidth: options.borderWidth,
|
|
datasetLabel: dataset.label,
|
|
label: me.chart.data.labels[index]
|
|
};
|
|
|
|
if (helpers$1.isArray(dataset.data[index])) {
|
|
rectangle._model.borderSkipped = null;
|
|
}
|
|
|
|
me._updateElementGeometry(rectangle, index, reset, options);
|
|
|
|
rectangle.pivot();
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_updateElementGeometry: function(rectangle, index, reset, options) {
|
|
var me = this;
|
|
var model = rectangle._model;
|
|
var vscale = me._getValueScale();
|
|
var base = vscale.getBasePixel();
|
|
var horizontal = vscale.isHorizontal();
|
|
var ruler = me._ruler || me.getRuler();
|
|
var vpixels = me.calculateBarValuePixels(me.index, index, options);
|
|
var ipixels = me.calculateBarIndexPixels(me.index, index, ruler, options);
|
|
|
|
model.horizontal = horizontal;
|
|
model.base = reset ? base : vpixels.base;
|
|
model.x = horizontal ? reset ? base : vpixels.head : ipixels.center;
|
|
model.y = horizontal ? ipixels.center : reset ? base : vpixels.head;
|
|
model.height = horizontal ? ipixels.size : undefined;
|
|
model.width = horizontal ? undefined : ipixels.size;
|
|
},
|
|
|
|
/**
|
|
* Returns the stacks based on groups and bar visibility.
|
|
* @param {number} [last] - The dataset index
|
|
* @returns {string[]} The list of stack IDs
|
|
* @private
|
|
*/
|
|
_getStacks: function(last) {
|
|
var me = this;
|
|
var scale = me._getIndexScale();
|
|
var metasets = scale._getMatchingVisibleMetas(me._type);
|
|
var stacked = scale.options.stacked;
|
|
var ilen = metasets.length;
|
|
var stacks = [];
|
|
var i, meta;
|
|
|
|
for (i = 0; i < ilen; ++i) {
|
|
meta = metasets[i];
|
|
// stacked | meta.stack
|
|
// | found | not found | undefined
|
|
// false | x | x | x
|
|
// true | | x |
|
|
// undefined | | x | x
|
|
if (stacked === false || stacks.indexOf(meta.stack) === -1 ||
|
|
(stacked === undefined && meta.stack === undefined)) {
|
|
stacks.push(meta.stack);
|
|
}
|
|
if (meta.index === last) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return stacks;
|
|
},
|
|
|
|
/**
|
|
* Returns the effective number of stacks based on groups and bar visibility.
|
|
* @private
|
|
*/
|
|
getStackCount: function() {
|
|
return this._getStacks().length;
|
|
},
|
|
|
|
/**
|
|
* Returns the stack index for the given dataset based on groups and bar visibility.
|
|
* @param {number} [datasetIndex] - The dataset index
|
|
* @param {string} [name] - The stack name to find
|
|
* @returns {number} The stack index
|
|
* @private
|
|
*/
|
|
getStackIndex: function(datasetIndex, name) {
|
|
var stacks = this._getStacks(datasetIndex);
|
|
var index = (name !== undefined)
|
|
? stacks.indexOf(name)
|
|
: -1; // indexOf returns -1 if element is not present
|
|
|
|
return (index === -1)
|
|
? stacks.length - 1
|
|
: index;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
getRuler: function() {
|
|
var me = this;
|
|
var scale = me._getIndexScale();
|
|
var pixels = [];
|
|
var i, ilen;
|
|
|
|
for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) {
|
|
pixels.push(scale.getPixelForValue(null, i, me.index));
|
|
}
|
|
|
|
return {
|
|
pixels: pixels,
|
|
start: scale._startPixel,
|
|
end: scale._endPixel,
|
|
stackCount: me.getStackCount(),
|
|
scale: scale
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Note: pixel values are not clamped to the scale area.
|
|
* @private
|
|
*/
|
|
calculateBarValuePixels: function(datasetIndex, index, options) {
|
|
var me = this;
|
|
var chart = me.chart;
|
|
var scale = me._getValueScale();
|
|
var isHorizontal = scale.isHorizontal();
|
|
var datasets = chart.data.datasets;
|
|
var metasets = scale._getMatchingVisibleMetas(me._type);
|
|
var value = scale._parseValue(datasets[datasetIndex].data[index]);
|
|
var minBarLength = options.minBarLength;
|
|
var stacked = scale.options.stacked;
|
|
var stack = me.getMeta().stack;
|
|
var start = value.start === undefined ? 0 : value.max >= 0 && value.min >= 0 ? value.min : value.max;
|
|
var length = value.start === undefined ? value.end : value.max >= 0 && value.min >= 0 ? value.max - value.min : value.min - value.max;
|
|
var ilen = metasets.length;
|
|
var i, imeta, ivalue, base, head, size, stackLength;
|
|
|
|
if (stacked || (stacked === undefined && stack !== undefined)) {
|
|
for (i = 0; i < ilen; ++i) {
|
|
imeta = metasets[i];
|
|
|
|
if (imeta.index === datasetIndex) {
|
|
break;
|
|
}
|
|
|
|
if (imeta.stack === stack) {
|
|
stackLength = scale._parseValue(datasets[imeta.index].data[index]);
|
|
ivalue = stackLength.start === undefined ? stackLength.end : stackLength.min >= 0 && stackLength.max >= 0 ? stackLength.max : stackLength.min;
|
|
|
|
if ((value.min < 0 && ivalue < 0) || (value.max >= 0 && ivalue > 0)) {
|
|
start += ivalue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
base = scale.getPixelForValue(start);
|
|
head = scale.getPixelForValue(start + length);
|
|
size = head - base;
|
|
|
|
if (minBarLength !== undefined && Math.abs(size) < minBarLength) {
|
|
size = minBarLength;
|
|
if (length >= 0 && !isHorizontal || length < 0 && isHorizontal) {
|
|
head = base - minBarLength;
|
|
} else {
|
|
head = base + minBarLength;
|
|
}
|
|
}
|
|
|
|
return {
|
|
size: size,
|
|
base: base,
|
|
head: head,
|
|
center: head + size / 2
|
|
};
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
calculateBarIndexPixels: function(datasetIndex, index, ruler, options) {
|
|
var me = this;
|
|
var range = options.barThickness === 'flex'
|
|
? computeFlexCategoryTraits(index, ruler, options)
|
|
: computeFitCategoryTraits(index, ruler, options);
|
|
|
|
var stackIndex = me.getStackIndex(datasetIndex, me.getMeta().stack);
|
|
var center = range.start + (range.chunk * stackIndex) + (range.chunk / 2);
|
|
var size = Math.min(
|
|
valueOrDefault$3(options.maxBarThickness, Infinity),
|
|
range.chunk * range.ratio);
|
|
|
|
return {
|
|
base: center - size / 2,
|
|
head: center + size / 2,
|
|
center: center,
|
|
size: size
|
|
};
|
|
},
|
|
|
|
draw: function() {
|
|
var me = this;
|
|
var chart = me.chart;
|
|
var scale = me._getValueScale();
|
|
var rects = me.getMeta().data;
|
|
var dataset = me.getDataset();
|
|
var ilen = rects.length;
|
|
var i = 0;
|
|
|
|
helpers$1.canvas.clipArea(chart.ctx, chart.chartArea);
|
|
|
|
for (; i < ilen; ++i) {
|
|
var val = scale._parseValue(dataset.data[i]);
|
|
if (!isNaN(val.min) && !isNaN(val.max)) {
|
|
rects[i].draw();
|
|
}
|
|
}
|
|
|
|
helpers$1.canvas.unclipArea(chart.ctx);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_resolveDataElementOptions: function() {
|
|
var me = this;
|
|
var values = helpers$1.extend({}, core_datasetController.prototype._resolveDataElementOptions.apply(me, arguments));
|
|
var indexOpts = me._getIndexScale().options;
|
|
var valueOpts = me._getValueScale().options;
|
|
|
|
values.barPercentage = valueOrDefault$3(indexOpts.barPercentage, values.barPercentage);
|
|
values.barThickness = valueOrDefault$3(indexOpts.barThickness, values.barThickness);
|
|
values.categoryPercentage = valueOrDefault$3(indexOpts.categoryPercentage, values.categoryPercentage);
|
|
values.maxBarThickness = valueOrDefault$3(indexOpts.maxBarThickness, values.maxBarThickness);
|
|
values.minBarLength = valueOrDefault$3(valueOpts.minBarLength, values.minBarLength);
|
|
|
|
return values;
|
|
}
|
|
|
|
});
|
|
|
|
var valueOrDefault$4 = helpers$1.valueOrDefault;
|
|
var resolve$1 = helpers$1.options.resolve;
|
|
|
|
core_defaults._set('bubble', {
|
|
hover: {
|
|
mode: 'single'
|
|
},
|
|
|
|
scales: {
|
|
xAxes: [{
|
|
type: 'linear', // bubble should probably use a linear scale by default
|
|
position: 'bottom',
|
|
id: 'x-axis-0' // need an ID so datasets can reference the scale
|
|
}],
|
|
yAxes: [{
|
|
type: 'linear',
|
|
position: 'left',
|
|
id: 'y-axis-0'
|
|
}]
|
|
},
|
|
|
|
tooltips: {
|
|
callbacks: {
|
|
title: function() {
|
|
// Title doesn't make sense for scatter since we format the data as a point
|
|
return '';
|
|
},
|
|
label: function(item, data) {
|
|
var datasetLabel = data.datasets[item.datasetIndex].label || '';
|
|
var dataPoint = data.datasets[item.datasetIndex].data[item.index];
|
|
return datasetLabel + ': (' + item.xLabel + ', ' + item.yLabel + ', ' + dataPoint.r + ')';
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
var controller_bubble = core_datasetController.extend({
|
|
/**
|
|
* @protected
|
|
*/
|
|
dataElementType: elements.Point,
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_dataElementOptions: [
|
|
'backgroundColor',
|
|
'borderColor',
|
|
'borderWidth',
|
|
'hoverBackgroundColor',
|
|
'hoverBorderColor',
|
|
'hoverBorderWidth',
|
|
'hoverRadius',
|
|
'hitRadius',
|
|
'pointStyle',
|
|
'rotation'
|
|
],
|
|
|
|
/**
|
|
* @protected
|
|
*/
|
|
update: function(reset) {
|
|
var me = this;
|
|
var meta = me.getMeta();
|
|
var points = meta.data;
|
|
|
|
// Update Points
|
|
helpers$1.each(points, function(point, index) {
|
|
me.updateElement(point, index, reset);
|
|
});
|
|
},
|
|
|
|
/**
|
|
* @protected
|
|
*/
|
|
updateElement: function(point, index, reset) {
|
|
var me = this;
|
|
var meta = me.getMeta();
|
|
var custom = point.custom || {};
|
|
var xScale = me.getScaleForId(meta.xAxisID);
|
|
var yScale = me.getScaleForId(meta.yAxisID);
|
|
var options = me._resolveDataElementOptions(point, index);
|
|
var data = me.getDataset().data[index];
|
|
var dsIndex = me.index;
|
|
|
|
var x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex);
|
|
var y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex);
|
|
|
|
point._xScale = xScale;
|
|
point._yScale = yScale;
|
|
point._options = options;
|
|
point._datasetIndex = dsIndex;
|
|
point._index = index;
|
|
point._model = {
|
|
backgroundColor: options.backgroundColor,
|
|
borderColor: options.borderColor,
|
|
borderWidth: options.borderWidth,
|
|
hitRadius: options.hitRadius,
|
|
pointStyle: options.pointStyle,
|
|
rotation: options.rotation,
|
|
radius: reset ? 0 : options.radius,
|
|
skip: custom.skip || isNaN(x) || isNaN(y),
|
|
x: x,
|
|
y: y,
|
|
};
|
|
|
|
point.pivot();
|
|
},
|
|
|
|
/**
|
|
* @protected
|
|
*/
|
|
setHoverStyle: function(point) {
|
|
var model = point._model;
|
|
var options = point._options;
|
|
var getHoverColor = helpers$1.getHoverColor;
|
|
|
|
point.$previousStyle = {
|
|
backgroundColor: model.backgroundColor,
|
|
borderColor: model.borderColor,
|
|
borderWidth: model.borderWidth,
|
|
radius: model.radius
|
|
};
|
|
|
|
model.backgroundColor = valueOrDefault$4(options.hoverBackgroundColor, getHoverColor(options.backgroundColor));
|
|
model.borderColor = valueOrDefault$4(options.hoverBorderColor, getHoverColor(options.borderColor));
|
|
model.borderWidth = valueOrDefault$4(options.hoverBorderWidth, options.borderWidth);
|
|
model.radius = options.radius + options.hoverRadius;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_resolveDataElementOptions: function(point, index) {
|
|
var me = this;
|
|
var chart = me.chart;
|
|
var dataset = me.getDataset();
|
|
var custom = point.custom || {};
|
|
var data = dataset.data[index] || {};
|
|
var values = core_datasetController.prototype._resolveDataElementOptions.apply(me, arguments);
|
|
|
|
// Scriptable options
|
|
var context = {
|
|
chart: chart,
|
|
dataIndex: index,
|
|
dataset: dataset,
|
|
datasetIndex: me.index
|
|
};
|
|
|
|
// In case values were cached (and thus frozen), we need to clone the values
|
|
if (me._cachedDataOpts === values) {
|
|
values = helpers$1.extend({}, values);
|
|
}
|
|
|
|
// Custom radius resolution
|
|
values.radius = resolve$1([
|
|
custom.radius,
|
|
data.r,
|
|
me._config.radius,
|
|
chart.options.elements.point.radius
|
|
], context, index);
|
|
|
|
return values;
|
|
}
|
|
});
|
|
|
|
var valueOrDefault$5 = helpers$1.valueOrDefault;
|
|
|
|
var PI$1 = Math.PI;
|
|
var DOUBLE_PI$1 = PI$1 * 2;
|
|
var HALF_PI$1 = PI$1 / 2;
|
|
|
|
core_defaults._set('doughnut', {
|
|
animation: {
|
|
// Boolean - Whether we animate the rotation of the Doughnut
|
|
animateRotate: true,
|
|
// Boolean - Whether we animate scaling the Doughnut from the centre
|
|
animateScale: false
|
|
},
|
|
hover: {
|
|
mode: 'single'
|
|
},
|
|
legendCallback: function(chart) {
|
|
var list = document.createElement('ul');
|
|
var data = chart.data;
|
|
var datasets = data.datasets;
|
|
var labels = data.labels;
|
|
var i, ilen, listItem, listItemSpan;
|
|
|
|
list.setAttribute('class', chart.id + '-legend');
|
|
if (datasets.length) {
|
|
for (i = 0, ilen = datasets[0].data.length; i < ilen; ++i) {
|
|
listItem = list.appendChild(document.createElement('li'));
|
|
listItemSpan = listItem.appendChild(document.createElement('span'));
|
|
listItemSpan.style.backgroundColor = datasets[0].backgroundColor[i];
|
|
if (labels[i]) {
|
|
listItem.appendChild(document.createTextNode(labels[i]));
|
|
}
|
|
}
|
|
}
|
|
|
|
return list.outerHTML;
|
|
},
|
|
legend: {
|
|
labels: {
|
|
generateLabels: function(chart) {
|
|
var data = chart.data;
|
|
if (data.labels.length && data.datasets.length) {
|
|
return data.labels.map(function(label, i) {
|
|
var meta = chart.getDatasetMeta(0);
|
|
var style = meta.controller.getStyle(i);
|
|
|
|
return {
|
|
text: label,
|
|
fillStyle: style.backgroundColor,
|
|
strokeStyle: style.borderColor,
|
|
lineWidth: style.borderWidth,
|
|
hidden: isNaN(data.datasets[0].data[i]) || meta.data[i].hidden,
|
|
|
|
// Extra data used for toggling the correct item
|
|
index: i
|
|
};
|
|
});
|
|
}
|
|
return [];
|
|
}
|
|
},
|
|
|
|
onClick: function(e, legendItem) {
|
|
var index = legendItem.index;
|
|
var chart = this.chart;
|
|
var i, ilen, meta;
|
|
|
|
for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
|
|
meta = chart.getDatasetMeta(i);
|
|
// toggle visibility of index if exists
|
|
if (meta.data[index]) {
|
|
meta.data[index].hidden = !meta.data[index].hidden;
|
|
}
|
|
}
|
|
|
|
chart.update();
|
|
}
|
|
},
|
|
|
|
// The percentage of the chart that we cut out of the middle.
|
|
cutoutPercentage: 50,
|
|
|
|
// The rotation of the chart, where the first data arc begins.
|
|
rotation: -HALF_PI$1,
|
|
|
|
// The total circumference of the chart.
|
|
circumference: DOUBLE_PI$1,
|
|
|
|
// Need to override these to give a nice default
|
|
tooltips: {
|
|
callbacks: {
|
|
title: function() {
|
|
return '';
|
|
},
|
|
label: function(tooltipItem, data) {
|
|
var dataLabel = data.labels[tooltipItem.index];
|
|
var value = ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
|
|
|
|
if (helpers$1.isArray(dataLabel)) {
|
|
// show value on first line of multiline label
|
|
// need to clone because we are changing the value
|
|
dataLabel = dataLabel.slice();
|
|
dataLabel[0] += value;
|
|
} else {
|
|
dataLabel += value;
|
|
}
|
|
|
|
return dataLabel;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
var controller_doughnut = core_datasetController.extend({
|
|
|
|
dataElementType: elements.Arc,
|
|
|
|
linkScales: helpers$1.noop,
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_dataElementOptions: [
|
|
'backgroundColor',
|
|
'borderColor',
|
|
'borderWidth',
|
|
'borderAlign',
|
|
'hoverBackgroundColor',
|
|
'hoverBorderColor',
|
|
'hoverBorderWidth',
|
|
],
|
|
|
|
// Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly
|
|
getRingIndex: function(datasetIndex) {
|
|
var ringIndex = 0;
|
|
|
|
for (var j = 0; j < datasetIndex; ++j) {
|
|
if (this.chart.isDatasetVisible(j)) {
|
|
++ringIndex;
|
|
}
|
|
}
|
|
|
|
return ringIndex;
|
|
},
|
|
|
|
update: function(reset) {
|
|
var me = this;
|
|
var chart = me.chart;
|
|
var chartArea = chart.chartArea;
|
|
var opts = chart.options;
|
|
var ratioX = 1;
|
|
var ratioY = 1;
|
|
var offsetX = 0;
|
|
var offsetY = 0;
|
|
var meta = me.getMeta();
|
|
var arcs = meta.data;
|
|
var cutout = opts.cutoutPercentage / 100 || 0;
|
|
var circumference = opts.circumference;
|
|
var chartWeight = me._getRingWeight(me.index);
|
|
var maxWidth, maxHeight, i, ilen;
|
|
|
|
// If the chart's circumference isn't a full circle, calculate size as a ratio of the width/height of the arc
|
|
if (circumference < DOUBLE_PI$1) {
|
|
var startAngle = opts.rotation % DOUBLE_PI$1;
|
|
startAngle += startAngle >= PI$1 ? -DOUBLE_PI$1 : startAngle < -PI$1 ? DOUBLE_PI$1 : 0;
|
|
var endAngle = startAngle + circumference;
|
|
var startX = Math.cos(startAngle);
|
|
var startY = Math.sin(startAngle);
|
|
var endX = Math.cos(endAngle);
|
|
var endY = Math.sin(endAngle);
|
|
var contains0 = (startAngle <= 0 && endAngle >= 0) || endAngle >= DOUBLE_PI$1;
|
|
var contains90 = (startAngle <= HALF_PI$1 && endAngle >= HALF_PI$1) || endAngle >= DOUBLE_PI$1 + HALF_PI$1;
|
|
var contains180 = startAngle === -PI$1 || endAngle >= PI$1;
|
|
var contains270 = (startAngle <= -HALF_PI$1 && endAngle >= -HALF_PI$1) || endAngle >= PI$1 + HALF_PI$1;
|
|
var minX = contains180 ? -1 : Math.min(startX, startX * cutout, endX, endX * cutout);
|
|
var minY = contains270 ? -1 : Math.min(startY, startY * cutout, endY, endY * cutout);
|
|
var maxX = contains0 ? 1 : Math.max(startX, startX * cutout, endX, endX * cutout);
|
|
var maxY = contains90 ? 1 : Math.max(startY, startY * cutout, endY, endY * cutout);
|
|
ratioX = (maxX - minX) / 2;
|
|
ratioY = (maxY - minY) / 2;
|
|
offsetX = -(maxX + minX) / 2;
|
|
offsetY = -(maxY + minY) / 2;
|
|
}
|
|
|
|
for (i = 0, ilen = arcs.length; i < ilen; ++i) {
|
|
arcs[i]._options = me._resolveDataElementOptions(arcs[i], i);
|
|
}
|
|
|
|
chart.borderWidth = me.getMaxBorderWidth();
|
|
maxWidth = (chartArea.right - chartArea.left - chart.borderWidth) / ratioX;
|
|
maxHeight = (chartArea.bottom - chartArea.top - chart.borderWidth) / ratioY;
|
|
chart.outerRadius = Math.max(Math.min(maxWidth, maxHeight) / 2, 0);
|
|
chart.innerRadius = Math.max(chart.outerRadius * cutout, 0);
|
|
chart.radiusLength = (chart.outerRadius - chart.innerRadius) / (me._getVisibleDatasetWeightTotal() || 1);
|
|
chart.offsetX = offsetX * chart.outerRadius;
|
|
chart.offsetY = offsetY * chart.outerRadius;
|
|
|
|
meta.total = me.calculateTotal();
|
|
|
|
me.outerRadius = chart.outerRadius - chart.radiusLength * me._getRingWeightOffset(me.index);
|
|
me.innerRadius = Math.max(me.outerRadius - chart.radiusLength * chartWeight, 0);
|
|
|
|
for (i = 0, ilen = arcs.length; i < ilen; ++i) {
|
|
me.updateElement(arcs[i], i, reset);
|
|
}
|
|
},
|
|
|
|
updateElement: function(arc, index, reset) {
|
|
var me = this;
|
|
var chart = me.chart;
|
|
var chartArea = chart.chartArea;
|
|
var opts = chart.options;
|
|
var animationOpts = opts.animation;
|
|
var centerX = (chartArea.left + chartArea.right) / 2;
|
|
var centerY = (chartArea.top + chartArea.bottom) / 2;
|
|
var startAngle = opts.rotation; // non reset case handled later
|
|
var endAngle = opts.rotation; // non reset case handled later
|
|
var dataset = me.getDataset();
|
|
var circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / DOUBLE_PI$1);
|
|
var innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius;
|
|
var outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius;
|
|
var options = arc._options || {};
|
|
|
|
helpers$1.extend(arc, {
|
|
// Utility
|
|
_datasetIndex: me.index,
|
|
_index: index,
|
|
|
|
// Desired view properties
|
|
_model: {
|
|
backgroundColor: options.backgroundColor,
|
|
borderColor: options.borderColor,
|
|
borderWidth: options.borderWidth,
|
|
borderAlign: options.borderAlign,
|
|
x: centerX + chart.offsetX,
|
|
y: centerY + chart.offsetY,
|
|
startAngle: startAngle,
|
|
endAngle: endAngle,
|
|
circumference: circumference,
|
|
outerRadius: outerRadius,
|
|
innerRadius: innerRadius,
|
|
label: helpers$1.valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index])
|
|
}
|
|
});
|
|
|
|
var model = arc._model;
|
|
|
|
// Set correct angles if not resetting
|
|
if (!reset || !animationOpts.animateRotate) {
|
|
if (index === 0) {
|
|
model.startAngle = opts.rotation;
|
|
} else {
|
|
model.startAngle = me.getMeta().data[index - 1]._model.endAngle;
|
|
}
|
|
|
|
model.endAngle = model.startAngle + model.circumference;
|
|
}
|
|
|
|
arc.pivot();
|
|
},
|
|
|
|
calculateTotal: function() {
|
|
var dataset = this.getDataset();
|
|
var meta = this.getMeta();
|
|
var total = 0;
|
|
var value;
|
|
|
|
helpers$1.each(meta.data, function(element, index) {
|
|
value = dataset.data[index];
|
|
if (!isNaN(value) && !element.hidden) {
|
|
total += Math.abs(value);
|
|
}
|
|
});
|
|
|
|
/* if (total === 0) {
|
|
total = NaN;
|
|
}*/
|
|
|
|
return total;
|
|
},
|
|
|
|
calculateCircumference: function(value) {
|
|
var total = this.getMeta().total;
|
|
if (total > 0 && !isNaN(value)) {
|
|
return DOUBLE_PI$1 * (Math.abs(value) / total);
|
|
}
|
|
return 0;
|
|
},
|
|
|
|
// gets the max border or hover width to properly scale pie charts
|
|
getMaxBorderWidth: function(arcs) {
|
|
var me = this;
|
|
var max = 0;
|
|
var chart = me.chart;
|
|
var i, ilen, meta, arc, controller, options, borderWidth, hoverWidth;
|
|
|
|
if (!arcs) {
|
|
// Find the outmost visible dataset
|
|
for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) {
|
|
if (chart.isDatasetVisible(i)) {
|
|
meta = chart.getDatasetMeta(i);
|
|
arcs = meta.data;
|
|
if (i !== me.index) {
|
|
controller = meta.controller;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!arcs) {
|
|
return 0;
|
|
}
|
|
|
|
for (i = 0, ilen = arcs.length; i < ilen; ++i) {
|
|
arc = arcs[i];
|
|
if (controller) {
|
|
controller._configure();
|
|
options = controller._resolveDataElementOptions(arc, i);
|
|
} else {
|
|
options = arc._options;
|
|
}
|
|
if (options.borderAlign !== 'inner') {
|
|
borderWidth = options.borderWidth;
|
|
hoverWidth = options.hoverBorderWidth;
|
|
|
|
max = borderWidth > max ? borderWidth : max;
|
|
max = hoverWidth > max ? hoverWidth : max;
|
|
}
|
|
}
|
|
return max;
|
|
},
|
|
|
|
/**
|
|
* @protected
|
|
*/
|
|
setHoverStyle: function(arc) {
|
|
var model = arc._model;
|
|
var options = arc._options;
|
|
var getHoverColor = helpers$1.getHoverColor;
|
|
|
|
arc.$previousStyle = {
|
|
backgroundColor: model.backgroundColor,
|
|
borderColor: model.borderColor,
|
|
borderWidth: model.borderWidth,
|
|
};
|
|
|
|
model.backgroundColor = valueOrDefault$5(options.hoverBackgroundColor, getHoverColor(options.backgroundColor));
|
|
model.borderColor = valueOrDefault$5(options.hoverBorderColor, getHoverColor(options.borderColor));
|
|
model.borderWidth = valueOrDefault$5(options.hoverBorderWidth, options.borderWidth);
|
|
},
|
|
|
|
/**
|
|
* Get radius length offset of the dataset in relation to the visible datasets weights. This allows determining the inner and outer radius correctly
|
|
* @private
|
|
*/
|
|
_getRingWeightOffset: function(datasetIndex) {
|
|
var ringWeightOffset = 0;
|
|
|
|
for (var i = 0; i < datasetIndex; ++i) {
|
|
if (this.chart.isDatasetVisible(i)) {
|
|
ringWeightOffset += this._getRingWeight(i);
|
|
}
|
|
}
|
|
|
|
return ringWeightOffset;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_getRingWeight: function(dataSetIndex) {
|
|
return Math.max(valueOrDefault$5(this.chart.data.datasets[dataSetIndex].weight, 1), 0);
|
|
},
|
|
|
|
/**
|
|
* Returns the sum of all visibile data set weights. This value can be 0.
|
|
* @private
|
|
*/
|
|
_getVisibleDatasetWeightTotal: function() {
|
|
return this._getRingWeightOffset(this.chart.data.datasets.length);
|
|
}
|
|
});
|
|
|
|
core_defaults._set('horizontalBar', {
|
|
hover: {
|
|
mode: 'index',
|
|
axis: 'y'
|
|
},
|
|
|
|
scales: {
|
|
xAxes: [{
|
|
type: 'linear',
|
|
position: 'bottom'
|
|
}],
|
|
|
|
yAxes: [{
|
|
type: 'category',
|
|
position: 'left',
|
|
offset: true,
|
|
gridLines: {
|
|
offsetGridLines: true
|
|
}
|
|
}]
|
|
},
|
|
|
|
elements: {
|
|
rectangle: {
|
|
borderSkipped: 'left'
|
|
}
|
|
},
|
|
|
|
tooltips: {
|
|
mode: 'index',
|
|
axis: 'y'
|
|
}
|
|
});
|
|
|
|
core_defaults._set('global', {
|
|
datasets: {
|
|
horizontalBar: {
|
|
categoryPercentage: 0.8,
|
|
barPercentage: 0.9
|
|
}
|
|
}
|
|
});
|
|
|
|
var controller_horizontalBar = controller_bar.extend({
|
|
/**
|
|
* @private
|
|
*/
|
|
_getValueScaleId: function() {
|
|
return this.getMeta().xAxisID;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_getIndexScaleId: function() {
|
|
return this.getMeta().yAxisID;
|
|
}
|
|
});
|
|
|
|
var valueOrDefault$6 = helpers$1.valueOrDefault;
|
|
var resolve$2 = helpers$1.options.resolve;
|
|
var isPointInArea = helpers$1.canvas._isPointInArea;
|
|
|
|
core_defaults._set('line', {
|
|
showLines: true,
|
|
spanGaps: false,
|
|
|
|
hover: {
|
|
mode: 'label'
|
|
},
|
|
|
|
scales: {
|
|
xAxes: [{
|
|
type: 'category',
|
|
id: 'x-axis-0'
|
|
}],
|
|
yAxes: [{
|
|
type: 'linear',
|
|
id: 'y-axis-0'
|
|
}]
|
|
}
|
|
});
|
|
|
|
function scaleClip(scale, halfBorderWidth) {
|
|
var tickOpts = scale && scale.options.ticks || {};
|
|
var reverse = tickOpts.reverse;
|
|
var min = tickOpts.min === undefined ? halfBorderWidth : 0;
|
|
var max = tickOpts.max === undefined ? halfBorderWidth : 0;
|
|
return {
|
|
start: reverse ? max : min,
|
|
end: reverse ? min : max
|
|
};
|
|
}
|
|
|
|
function defaultClip(xScale, yScale, borderWidth) {
|
|
var halfBorderWidth = borderWidth / 2;
|
|
var x = scaleClip(xScale, halfBorderWidth);
|
|
var y = scaleClip(yScale, halfBorderWidth);
|
|
|
|
return {
|
|
top: y.end,
|
|
right: x.end,
|
|
bottom: y.start,
|
|
left: x.start
|
|
};
|
|
}
|
|
|
|
function toClip(value) {
|
|
var t, r, b, l;
|
|
|
|
if (helpers$1.isObject(value)) {
|
|
t = value.top;
|
|
r = value.right;
|
|
b = value.bottom;
|
|
l = value.left;
|
|
} else {
|
|
t = r = b = l = value;
|
|
}
|
|
|
|
return {
|
|
top: t,
|
|
right: r,
|
|
bottom: b,
|
|
left: l
|
|
};
|
|
}
|
|
|
|
|
|
var controller_line = core_datasetController.extend({
|
|
|
|
datasetElementType: elements.Line,
|
|
|
|
dataElementType: elements.Point,
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_datasetElementOptions: [
|
|
'backgroundColor',
|
|
'borderCapStyle',
|
|
'borderColor',
|
|
'borderDash',
|
|
'borderDashOffset',
|
|
'borderJoinStyle',
|
|
'borderWidth',
|
|
'cubicInterpolationMode',
|
|
'fill'
|
|
],
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_dataElementOptions: {
|
|
backgroundColor: 'pointBackgroundColor',
|
|
borderColor: 'pointBorderColor',
|
|
borderWidth: 'pointBorderWidth',
|
|
hitRadius: 'pointHitRadius',
|
|
hoverBackgroundColor: 'pointHoverBackgroundColor',
|
|
hoverBorderColor: 'pointHoverBorderColor',
|
|
hoverBorderWidth: 'pointHoverBorderWidth',
|
|
hoverRadius: 'pointHoverRadius',
|
|
pointStyle: 'pointStyle',
|
|
radius: 'pointRadius',
|
|
rotation: 'pointRotation'
|
|
},
|
|
|
|
update: function(reset) {
|
|
var me = this;
|
|
var meta = me.getMeta();
|
|
var line = meta.dataset;
|
|
var points = meta.data || [];
|
|
var options = me.chart.options;
|
|
var config = me._config;
|
|
var showLine = me._showLine = valueOrDefault$6(config.showLine, options.showLines);
|
|
var i, ilen;
|
|
|
|
me._xScale = me.getScaleForId(meta.xAxisID);
|
|
me._yScale = me.getScaleForId(meta.yAxisID);
|
|
|
|
// Update Line
|
|
if (showLine) {
|
|
// Compatibility: If the properties are defined with only the old name, use those values
|
|
if (config.tension !== undefined && config.lineTension === undefined) {
|
|
config.lineTension = config.tension;
|
|
}
|
|
|
|
// Utility
|
|
line._scale = me._yScale;
|
|
line._datasetIndex = me.index;
|
|
// Data
|
|
line._children = points;
|
|
// Model
|
|
line._model = me._resolveDatasetElementOptions(line);
|
|
|
|
line.pivot();
|
|
}
|
|
|
|
// Update Points
|
|
for (i = 0, ilen = points.length; i < ilen; ++i) {
|
|
me.updateElement(points[i], i, reset);
|
|
}
|
|
|
|
if (showLine && line._model.tension !== 0) {
|
|
me.updateBezierControlPoints();
|
|
}
|
|
|
|
// Now pivot the point for animation
|
|
for (i = 0, ilen = points.length; i < ilen; ++i) {
|
|
points[i].pivot();
|
|
}
|
|
},
|
|
|
|
updateElement: function(point, index, reset) {
|
|
var me = this;
|
|
var meta = me.getMeta();
|
|
var custom = point.custom || {};
|
|
var dataset = me.getDataset();
|
|
var datasetIndex = me.index;
|
|
var value = dataset.data[index];
|
|
var xScale = me._xScale;
|
|
var yScale = me._yScale;
|
|
var lineModel = meta.dataset._model;
|
|
var x, y;
|
|
|
|
var options = me._resolveDataElementOptions(point, index);
|
|
|
|
x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex);
|
|
y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex);
|
|
|
|
// Utility
|
|
point._xScale = xScale;
|
|
point._yScale = yScale;
|
|
point._options = options;
|
|
point._datasetIndex = datasetIndex;
|
|
point._index = index;
|
|
|
|
// Desired view properties
|
|
point._model = {
|
|
x: x,
|
|
y: y,
|
|
skip: custom.skip || isNaN(x) || isNaN(y),
|
|
// Appearance
|
|
radius: options.radius,
|
|
pointStyle: options.pointStyle,
|
|
rotation: options.rotation,
|
|
backgroundColor: options.backgroundColor,
|
|
borderColor: options.borderColor,
|
|
borderWidth: options.borderWidth,
|
|
tension: valueOrDefault$6(custom.tension, lineModel ? lineModel.tension : 0),
|
|
steppedLine: lineModel ? lineModel.steppedLine : false,
|
|
// Tooltip
|
|
hitRadius: options.hitRadius
|
|
};
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_resolveDatasetElementOptions: function(element) {
|
|
var me = this;
|
|
var config = me._config;
|
|
var custom = element.custom || {};
|
|
var options = me.chart.options;
|
|
var lineOptions = options.elements.line;
|
|
var values = core_datasetController.prototype._resolveDatasetElementOptions.apply(me, arguments);
|
|
|
|
// The default behavior of lines is to break at null values, according
|
|
// to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158
|
|
// This option gives lines the ability to span gaps
|
|
values.spanGaps = valueOrDefault$6(config.spanGaps, options.spanGaps);
|
|
values.tension = valueOrDefault$6(config.lineTension, lineOptions.tension);
|
|
values.steppedLine = resolve$2([custom.steppedLine, config.steppedLine, lineOptions.stepped]);
|
|
values.clip = toClip(valueOrDefault$6(config.clip, defaultClip(me._xScale, me._yScale, values.borderWidth)));
|
|
|
|
return values;
|
|
},
|
|
|
|
calculatePointY: function(value, index, datasetIndex) {
|
|
var me = this;
|
|
var chart = me.chart;
|
|
var yScale = me._yScale;
|
|
var sumPos = 0;
|
|
var sumNeg = 0;
|
|
var i, ds, dsMeta, stackedRightValue, rightValue, metasets, ilen;
|
|
|
|
if (yScale.options.stacked) {
|
|
rightValue = +yScale.getRightValue(value);
|
|
metasets = chart._getSortedVisibleDatasetMetas();
|
|
ilen = metasets.length;
|
|
|
|
for (i = 0; i < ilen; ++i) {
|
|
dsMeta = metasets[i];
|
|
if (dsMeta.index === datasetIndex) {
|
|
break;
|
|
}
|
|
|
|
ds = chart.data.datasets[dsMeta.index];
|
|
if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id) {
|
|
stackedRightValue = +yScale.getRightValue(ds.data[index]);
|
|
if (stackedRightValue < 0) {
|
|
sumNeg += stackedRightValue || 0;
|
|
} else {
|
|
sumPos += stackedRightValue || 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (rightValue < 0) {
|
|
return yScale.getPixelForValue(sumNeg + rightValue);
|
|
}
|
|
return yScale.getPixelForValue(sumPos + rightValue);
|
|
}
|
|
return yScale.getPixelForValue(value);
|
|
},
|
|
|
|
updateBezierControlPoints: function() {
|
|
var me = this;
|
|
var chart = me.chart;
|
|
var meta = me.getMeta();
|
|
var lineModel = meta.dataset._model;
|
|
var area = chart.chartArea;
|
|
var points = meta.data || [];
|
|
var i, ilen, model, controlPoints;
|
|
|
|
// Only consider points that are drawn in case the spanGaps option is used
|
|
if (lineModel.spanGaps) {
|
|
points = points.filter(function(pt) {
|
|
return !pt._model.skip;
|
|
});
|
|
}
|
|
|
|
function capControlPoint(pt, min, max) {
|
|
return Math.max(Math.min(pt, max), min);
|
|
}
|
|
|
|
if (lineModel.cubicInterpolationMode === 'monotone') {
|
|
helpers$1.splineCurveMonotone(points);
|
|
} else {
|
|
for (i = 0, ilen = points.length; i < ilen; ++i) {
|
|
model = points[i]._model;
|
|
controlPoints = helpers$1.splineCurve(
|
|
helpers$1.previousItem(points, i)._model,
|
|
model,
|
|
helpers$1.nextItem(points, i)._model,
|
|
lineModel.tension
|
|
);
|
|
model.controlPointPreviousX = controlPoints.previous.x;
|
|
model.controlPointPreviousY = controlPoints.previous.y;
|
|
model.controlPointNextX = controlPoints.next.x;
|
|
model.controlPointNextY = controlPoints.next.y;
|
|
}
|
|
}
|
|
|
|
if (chart.options.elements.line.capBezierPoints) {
|
|
for (i = 0, ilen = points.length; i < ilen; ++i) {
|
|
model = points[i]._model;
|
|
if (isPointInArea(model, area)) {
|
|
if (i > 0 && isPointInArea(points[i - 1]._model, area)) {
|
|
model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right);
|
|
model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom);
|
|
}
|
|
if (i < points.length - 1 && isPointInArea(points[i + 1]._model, area)) {
|
|
model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right);
|
|
model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
draw: function() {
|
|
var me = this;
|
|
var chart = me.chart;
|
|
var meta = me.getMeta();
|
|
var points = meta.data || [];
|
|
var area = chart.chartArea;
|
|
var canvas = chart.canvas;
|
|
var i = 0;
|
|
var ilen = points.length;
|
|
var clip;
|
|
|
|
if (me._showLine) {
|
|
clip = meta.dataset._model.clip;
|
|
|
|
helpers$1.canvas.clipArea(chart.ctx, {
|
|
left: clip.left === false ? 0 : area.left - clip.left,
|
|
right: clip.right === false ? canvas.width : area.right + clip.right,
|
|
top: clip.top === false ? 0 : area.top - clip.top,
|
|
bottom: clip.bottom === false ? canvas.height : area.bottom + clip.bottom
|
|
});
|
|
|
|
meta.dataset.draw();
|
|
|
|
helpers$1.canvas.unclipArea(chart.ctx);
|
|
}
|
|
|
|
// Draw the points
|
|
for (; i < ilen; ++i) {
|
|
points[i].draw(area);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @protected
|
|
*/
|
|
setHoverStyle: function(point) {
|
|
var model = point._model;
|
|
var options = point._options;
|
|
var getHoverColor = helpers$1.getHoverColor;
|
|
|
|
point.$previousStyle = {
|
|
backgroundColor: model.backgroundColor,
|
|
borderColor: model.borderColor,
|
|
borderWidth: model.borderWidth,
|
|
radius: model.radius
|
|
};
|
|
|
|
model.backgroundColor = valueOrDefault$6(options.hoverBackgroundColor, getHoverColor(options.backgroundColor));
|
|
model.borderColor = valueOrDefault$6(options.hoverBorderColor, getHoverColor(options.borderColor));
|
|
model.borderWidth = valueOrDefault$6(options.hoverBorderWidth, options.borderWidth);
|
|
model.radius = valueOrDefault$6(options.hoverRadius, options.radius);
|
|
},
|
|
});
|
|
|
|
var resolve$3 = helpers$1.options.resolve;
|
|
|
|
core_defaults._set('polarArea', {
|
|
scale: {
|
|
type: 'radialLinear',
|
|
angleLines: {
|
|
display: false
|
|
},
|
|
gridLines: {
|
|
circular: true
|
|
},
|
|
pointLabels: {
|
|
display: false
|
|
},
|
|
ticks: {
|
|
beginAtZero: true
|
|
}
|
|
},
|
|
|
|
// Boolean - Whether to animate the rotation of the chart
|
|
animation: {
|
|
animateRotate: true,
|
|
animateScale: true
|
|
},
|
|
|
|
startAngle: -0.5 * Math.PI,
|
|
legendCallback: function(chart) {
|
|
var list = document.createElement('ul');
|
|
var data = chart.data;
|
|
var datasets = data.datasets;
|
|
var labels = data.labels;
|
|
var i, ilen, listItem, listItemSpan;
|
|
|
|
list.setAttribute('class', chart.id + '-legend');
|
|
if (datasets.length) {
|
|
for (i = 0, ilen = datasets[0].data.length; i < ilen; ++i) {
|
|
listItem = list.appendChild(document.createElement('li'));
|
|
listItemSpan = listItem.appendChild(document.createElement('span'));
|
|
listItemSpan.style.backgroundColor = datasets[0].backgroundColor[i];
|
|
if (labels[i]) {
|
|
listItem.appendChild(document.createTextNode(labels[i]));
|
|
}
|
|
}
|
|
}
|
|
|
|
return list.outerHTML;
|
|
},
|
|
legend: {
|
|
labels: {
|
|
generateLabels: function(chart) {
|
|
var data = chart.data;
|
|
if (data.labels.length && data.datasets.length) {
|
|
return data.labels.map(function(label, i) {
|
|
var meta = chart.getDatasetMeta(0);
|
|
var style = meta.controller.getStyle(i);
|
|
|
|
return {
|
|
text: label,
|
|
fillStyle: style.backgroundColor,
|
|
strokeStyle: style.borderColor,
|
|
lineWidth: style.borderWidth,
|
|
hidden: isNaN(data.datasets[0].data[i]) || meta.data[i].hidden,
|
|
|
|
// Extra data used for toggling the correct item
|
|
index: i
|
|
};
|
|
});
|
|
}
|
|
return [];
|
|
}
|
|
},
|
|
|
|
onClick: function(e, legendItem) {
|
|
var index = legendItem.index;
|
|
var chart = this.chart;
|
|
var i, ilen, meta;
|
|
|
|
for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
|
|
meta = chart.getDatasetMeta(i);
|
|
meta.data[index].hidden = !meta.data[index].hidden;
|
|
}
|
|
|
|
chart.update();
|
|
}
|
|
},
|
|
|
|
// Need to override these to give a nice default
|
|
tooltips: {
|
|
callbacks: {
|
|
title: function() {
|
|
return '';
|
|
},
|
|
label: function(item, data) {
|
|
return data.labels[item.index] + ': ' + item.yLabel;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
var controller_polarArea = core_datasetController.extend({
|
|
|
|
dataElementType: elements.Arc,
|
|
|
|
linkScales: helpers$1.noop,
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_dataElementOptions: [
|
|
'backgroundColor',
|
|
'borderColor',
|
|
'borderWidth',
|
|
'borderAlign',
|
|
'hoverBackgroundColor',
|
|
'hoverBorderColor',
|
|
'hoverBorderWidth',
|
|
],
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_getIndexScaleId: function() {
|
|
return this.chart.scale.id;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_getValueScaleId: function() {
|
|
return this.chart.scale.id;
|
|
},
|
|
|
|
update: function(reset) {
|
|
var me = this;
|
|
var dataset = me.getDataset();
|
|
var meta = me.getMeta();
|
|
var start = me.chart.options.startAngle || 0;
|
|
var starts = me._starts = [];
|
|
var angles = me._angles = [];
|
|
var arcs = meta.data;
|
|
var i, ilen, angle;
|
|
|
|
me._updateRadius();
|
|
|
|
meta.count = me.countVisibleElements();
|
|
|
|
for (i = 0, ilen = dataset.data.length; i < ilen; i++) {
|
|
starts[i] = start;
|
|
angle = me._computeAngle(i);
|
|
angles[i] = angle;
|
|
start += angle;
|
|
}
|
|
|
|
for (i = 0, ilen = arcs.length; i < ilen; ++i) {
|
|
arcs[i]._options = me._resolveDataElementOptions(arcs[i], i);
|
|
me.updateElement(arcs[i], i, reset);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_updateRadius: function() {
|
|
var me = this;
|
|
var chart = me.chart;
|
|
var chartArea = chart.chartArea;
|
|
var opts = chart.options;
|
|
var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top);
|
|
|
|
chart.outerRadius = Math.max(minSize / 2, 0);
|
|
chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0);
|
|
chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount();
|
|
|
|
me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index);
|
|
me.innerRadius = me.outerRadius - chart.radiusLength;
|
|
},
|
|
|
|
updateElement: function(arc, index, reset) {
|
|
var me = this;
|
|
var chart = me.chart;
|
|
var dataset = me.getDataset();
|
|
var opts = chart.options;
|
|
var animationOpts = opts.animation;
|
|
var scale = chart.scale;
|
|
var labels = chart.data.labels;
|
|
|
|
var centerX = scale.xCenter;
|
|
var centerY = scale.yCenter;
|
|
|
|
// var negHalfPI = -0.5 * Math.PI;
|
|
var datasetStartAngle = opts.startAngle;
|
|
var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);
|
|
var startAngle = me._starts[index];
|
|
var endAngle = startAngle + (arc.hidden ? 0 : me._angles[index]);
|
|
|
|
var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]);
|
|
var options = arc._options || {};
|
|
|
|
helpers$1.extend(arc, {
|
|
// Utility
|
|
_datasetIndex: me.index,
|
|
_index: index,
|
|
_scale: scale,
|
|
|
|
// Desired view properties
|
|
_model: {
|
|
backgroundColor: options.backgroundColor,
|
|
borderColor: options.borderColor,
|
|
borderWidth: options.borderWidth,
|
|
borderAlign: options.borderAlign,
|
|
x: centerX,
|
|
y: centerY,
|
|
innerRadius: 0,
|
|
outerRadius: reset ? resetRadius : distance,
|
|
startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle,
|
|
endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle,
|
|
label: helpers$1.valueAtIndexOrDefault(labels, index, labels[index])
|
|
}
|
|
});
|
|
|
|
arc.pivot();
|
|
},
|
|
|
|
countVisibleElements: function() {
|
|
var dataset = this.getDataset();
|
|
var meta = this.getMeta();
|
|
var count = 0;
|
|
|
|
helpers$1.each(meta.data, function(element, index) {
|
|
if (!isNaN(dataset.data[index]) && !element.hidden) {
|
|
count++;
|
|
}
|
|
});
|
|
|
|
return count;
|
|
},
|
|
|
|
/**
|
|
* @protected
|
|
*/
|
|
setHoverStyle: function(arc) {
|
|
var model = arc._model;
|
|
var options = arc._options;
|
|
var getHoverColor = helpers$1.getHoverColor;
|
|
var valueOrDefault = helpers$1.valueOrDefault;
|
|
|
|
arc.$previousStyle = {
|
|
backgroundColor: model.backgroundColor,
|
|
borderColor: model.borderColor,
|
|
borderWidth: model.borderWidth,
|
|
};
|
|
|
|
model.backgroundColor = valueOrDefault(options.hoverBackgroundColor, getHoverColor(options.backgroundColor));
|
|
model.borderColor = valueOrDefault(options.hoverBorderColor, getHoverColor(options.borderColor));
|
|
model.borderWidth = valueOrDefault(options.hoverBorderWidth, options.borderWidth);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_computeAngle: function(index) {
|
|
var me = this;
|
|
var count = this.getMeta().count;
|
|
var dataset = me.getDataset();
|
|
var meta = me.getMeta();
|
|
|
|
if (isNaN(dataset.data[index]) || meta.data[index].hidden) {
|
|
return 0;
|
|
}
|
|
|
|
// Scriptable options
|
|
var context = {
|
|
chart: me.chart,
|
|
dataIndex: index,
|
|
dataset: dataset,
|
|
datasetIndex: me.index
|
|
};
|
|
|
|
return resolve$3([
|
|
me.chart.options.elements.arc.angle,
|
|
(2 * Math.PI) / count
|
|
], context, index);
|
|
}
|
|
});
|
|
|
|
core_defaults._set('pie', helpers$1.clone(core_defaults.doughnut));
|
|
core_defaults._set('pie', {
|
|
cutoutPercentage: 0
|
|
});
|
|
|
|
// Pie charts are Doughnut chart with different defaults
|
|
var controller_pie = controller_doughnut;
|
|
|
|
var valueOrDefault$7 = helpers$1.valueOrDefault;
|
|
|
|
core_defaults._set('radar', {
|
|
spanGaps: false,
|
|
scale: {
|
|
type: 'radialLinear'
|
|
},
|
|
elements: {
|
|
line: {
|
|
fill: 'start',
|
|
tension: 0 // no bezier in radar
|
|
}
|
|
}
|
|
});
|
|
|
|
var controller_radar = core_datasetController.extend({
|
|
datasetElementType: elements.Line,
|
|
|
|
dataElementType: elements.Point,
|
|
|
|
linkScales: helpers$1.noop,
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_datasetElementOptions: [
|
|
'backgroundColor',
|
|
'borderWidth',
|
|
'borderColor',
|
|
'borderCapStyle',
|
|
'borderDash',
|
|
'borderDashOffset',
|
|
'borderJoinStyle',
|
|
'fill'
|
|
],
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_dataElementOptions: {
|
|
backgroundColor: 'pointBackgroundColor',
|
|
borderColor: 'pointBorderColor',
|
|
borderWidth: 'pointBorderWidth',
|
|
hitRadius: 'pointHitRadius',
|
|
hoverBackgroundColor: 'pointHoverBackgroundColor',
|
|
hoverBorderColor: 'pointHoverBorderColor',
|
|
hoverBorderWidth: 'pointHoverBorderWidth',
|
|
hoverRadius: 'pointHoverRadius',
|
|
pointStyle: 'pointStyle',
|
|
radius: 'pointRadius',
|
|
rotation: 'pointRotation'
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_getIndexScaleId: function() {
|
|
return this.chart.scale.id;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_getValueScaleId: function() {
|
|
return this.chart.scale.id;
|
|
},
|
|
|
|
update: function(reset) {
|
|
var me = this;
|
|
var meta = me.getMeta();
|
|
var line = meta.dataset;
|
|
var points = meta.data || [];
|
|
var scale = me.chart.scale;
|
|
var config = me._config;
|
|
var i, ilen;
|
|
|
|
// Compatibility: If the properties are defined with only the old name, use those values
|
|
if (config.tension !== undefined && config.lineTension === undefined) {
|
|
config.lineTension = config.tension;
|
|
}
|
|
|
|
// Utility
|
|
line._scale = scale;
|
|
line._datasetIndex = me.index;
|
|
// Data
|
|
line._children = points;
|
|
line._loop = true;
|
|
// Model
|
|
line._model = me._resolveDatasetElementOptions(line);
|
|
|
|
line.pivot();
|
|
|
|
// Update Points
|
|
for (i = 0, ilen = points.length; i < ilen; ++i) {
|
|
me.updateElement(points[i], i, reset);
|
|
}
|
|
|
|
// Update bezier control points
|
|
me.updateBezierControlPoints();
|
|
|
|
// Now pivot the point for animation
|
|
for (i = 0, ilen = points.length; i < ilen; ++i) {
|
|
points[i].pivot();
|
|
}
|
|
},
|
|
|
|
updateElement: function(point, index, reset) {
|
|
var me = this;
|
|
var custom = point.custom || {};
|
|
var dataset = me.getDataset();
|
|
var scale = me.chart.scale;
|
|
var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]);
|
|
var options = me._resolveDataElementOptions(point, index);
|
|
var lineModel = me.getMeta().dataset._model;
|
|
var x = reset ? scale.xCenter : pointPosition.x;
|
|
var y = reset ? scale.yCenter : pointPosition.y;
|
|
|
|
// Utility
|
|
point._scale = scale;
|
|
point._options = options;
|
|
point._datasetIndex = me.index;
|
|
point._index = index;
|
|
|
|
// Desired view properties
|
|
point._model = {
|
|
x: x, // value not used in dataset scale, but we want a consistent API between scales
|
|
y: y,
|
|
skip: custom.skip || isNaN(x) || isNaN(y),
|
|
// Appearance
|
|
radius: options.radius,
|
|
pointStyle: options.pointStyle,
|
|
rotation: options.rotation,
|
|
backgroundColor: options.backgroundColor,
|
|
borderColor: options.borderColor,
|
|
borderWidth: options.borderWidth,
|
|
tension: valueOrDefault$7(custom.tension, lineModel ? lineModel.tension : 0),
|
|
|
|
// Tooltip
|
|
hitRadius: options.hitRadius
|
|
};
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_resolveDatasetElementOptions: function() {
|
|
var me = this;
|
|
var config = me._config;
|
|
var options = me.chart.options;
|
|
var values = core_datasetController.prototype._resolveDatasetElementOptions.apply(me, arguments);
|
|
|
|
values.spanGaps = valueOrDefault$7(config.spanGaps, options.spanGaps);
|
|
values.tension = valueOrDefault$7(config.lineTension, options.elements.line.tension);
|
|
|
|
return values;
|
|
},
|
|
|
|
updateBezierControlPoints: function() {
|
|
var me = this;
|
|
var meta = me.getMeta();
|
|
var area = me.chart.chartArea;
|
|
var points = meta.data || [];
|
|
var i, ilen, model, controlPoints;
|
|
|
|
// Only consider points that are drawn in case the spanGaps option is used
|
|
if (meta.dataset._model.spanGaps) {
|
|
points = points.filter(function(pt) {
|
|
return !pt._model.skip;
|
|
});
|
|
}
|
|
|
|
function capControlPoint(pt, min, max) {
|
|
return Math.max(Math.min(pt, max), min);
|
|
}
|
|
|
|
for (i = 0, ilen = points.length; i < ilen; ++i) {
|
|
model = points[i]._model;
|
|
controlPoints = helpers$1.splineCurve(
|
|
helpers$1.previousItem(points, i, true)._model,
|
|
model,
|
|
helpers$1.nextItem(points, i, true)._model,
|
|
model.tension
|
|
);
|
|
|
|
// Prevent the bezier going outside of the bounds of the graph
|
|
model.controlPointPreviousX = capControlPoint(controlPoints.previous.x, area.left, area.right);
|
|
model.controlPointPreviousY = capControlPoint(controlPoints.previous.y, area.top, area.bottom);
|
|
model.controlPointNextX = capControlPoint(controlPoints.next.x, area.left, area.right);
|
|
model.controlPointNextY = capControlPoint(controlPoints.next.y, area.top, area.bottom);
|
|
}
|
|
},
|
|
|
|
setHoverStyle: function(point) {
|
|
var model = point._model;
|
|
var options = point._options;
|
|
var getHoverColor = helpers$1.getHoverColor;
|
|
|
|
point.$previousStyle = {
|
|
backgroundColor: model.backgroundColor,
|
|
borderColor: model.borderColor,
|
|
borderWidth: model.borderWidth,
|
|
radius: model.radius
|
|
};
|
|
|
|
model.backgroundColor = valueOrDefault$7(options.hoverBackgroundColor, getHoverColor(options.backgroundColor));
|
|
model.borderColor = valueOrDefault$7(options.hoverBorderColor, getHoverColor(options.borderColor));
|
|
model.borderWidth = valueOrDefault$7(options.hoverBorderWidth, options.borderWidth);
|
|
model.radius = valueOrDefault$7(options.hoverRadius, options.radius);
|
|
}
|
|
});
|
|
|
|
core_defaults._set('scatter', {
|
|
hover: {
|
|
mode: 'single'
|
|
},
|
|
|
|
scales: {
|
|
xAxes: [{
|
|
id: 'x-axis-1', // need an ID so datasets can reference the scale
|
|
type: 'linear', // scatter should not use a category axis
|
|
position: 'bottom'
|
|
}],
|
|
yAxes: [{
|
|
id: 'y-axis-1',
|
|
type: 'linear',
|
|
position: 'left'
|
|
}]
|
|
},
|
|
|
|
tooltips: {
|
|
callbacks: {
|
|
title: function() {
|
|
return ''; // doesn't make sense for scatter since data are formatted as a point
|
|
},
|
|
label: function(item) {
|
|
return '(' + item.xLabel + ', ' + item.yLabel + ')';
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
core_defaults._set('global', {
|
|
datasets: {
|
|
scatter: {
|
|
showLine: false
|
|
}
|
|
}
|
|
});
|
|
|
|
// Scatter charts use line controllers
|
|
var controller_scatter = controller_line;
|
|
|
|
// NOTE export a map in which the key represents the controller type, not
|
|
// the class, and so must be CamelCase in order to be correctly retrieved
|
|
// by the controller in core.controller.js (`controllers[meta.type]`).
|
|
|
|
var controllers = {
|
|
bar: controller_bar,
|
|
bubble: controller_bubble,
|
|
doughnut: controller_doughnut,
|
|
horizontalBar: controller_horizontalBar,
|
|
line: controller_line,
|
|
polarArea: controller_polarArea,
|
|
pie: controller_pie,
|
|
radar: controller_radar,
|
|
scatter: controller_scatter
|
|
};
|
|
|
|
/**
|
|
* Helper function to get relative position for an event
|
|
* @param {Event|IEvent} event - The event to get the position for
|
|
* @param {Chart} chart - The chart
|
|
* @returns {object} the event position
|
|
*/
|
|
function getRelativePosition(e, chart) {
|
|
if (e.native) {
|
|
return {
|
|
x: e.x,
|
|
y: e.y
|
|
};
|
|
}
|
|
|
|
return helpers$1.getRelativePosition(e, chart);
|
|
}
|
|
|
|
/**
|
|
* Helper function to traverse all of the visible elements in the chart
|
|
* @param {Chart} chart - the chart
|
|
* @param {function} handler - the callback to execute for each visible item
|
|
*/
|
|
function parseVisibleItems(chart, handler) {
|
|
var metasets = chart._getSortedVisibleDatasetMetas();
|
|
var metadata, i, j, ilen, jlen, element;
|
|
|
|
for (i = 0, ilen = metasets.length; i < ilen; ++i) {
|
|
metadata = metasets[i].data;
|
|
for (j = 0, jlen = metadata.length; j < jlen; ++j) {
|
|
element = metadata[j];
|
|
if (!element._view.skip) {
|
|
handler(element);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper function to get the items that intersect the event position
|
|
* @param {ChartElement[]} items - elements to filter
|
|
* @param {object} position - the point to be nearest to
|
|
* @return {ChartElement[]} the nearest items
|
|
*/
|
|
function getIntersectItems(chart, position) {
|
|
var elements = [];
|
|
|
|
parseVisibleItems(chart, function(element) {
|
|
if (element.inRange(position.x, position.y)) {
|
|
elements.push(element);
|
|
}
|
|
});
|
|
|
|
return elements;
|
|
}
|
|
|
|
/**
|
|
* Helper function to get the items nearest to the event position considering all visible items in teh chart
|
|
* @param {Chart} chart - the chart to look at elements from
|
|
* @param {object} position - the point to be nearest to
|
|
* @param {boolean} intersect - if true, only consider items that intersect the position
|
|
* @param {function} distanceMetric - function to provide the distance between points
|
|
* @return {ChartElement[]} the nearest items
|
|
*/
|
|
function getNearestItems(chart, position, intersect, distanceMetric) {
|
|
var minDistance = Number.POSITIVE_INFINITY;
|
|
var nearestItems = [];
|
|
|
|
parseVisibleItems(chart, function(element) {
|
|
if (intersect && !element.inRange(position.x, position.y)) {
|
|
return;
|
|
}
|
|
|
|
var center = element.getCenterPoint();
|
|
var distance = distanceMetric(position, center);
|
|
if (distance < minDistance) {
|
|
nearestItems = [element];
|
|
minDistance = distance;
|
|
} else if (distance === minDistance) {
|
|
// Can have multiple items at the same distance in which case we sort by size
|
|
nearestItems.push(element);
|
|
}
|
|
});
|
|
|
|
return nearestItems;
|
|
}
|
|
|
|
/**
|
|
* Get a distance metric function for two points based on the
|
|
* axis mode setting
|
|
* @param {string} axis - the axis mode. x|y|xy
|
|
*/
|
|
function getDistanceMetricForAxis(axis) {
|
|
var useX = axis.indexOf('x') !== -1;
|
|
var useY = axis.indexOf('y') !== -1;
|
|
|
|
return function(pt1, pt2) {
|
|
var deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0;
|
|
var deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0;
|
|
return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
|
|
};
|
|
}
|
|
|
|
function indexMode(chart, e, options) {
|
|
var position = getRelativePosition(e, chart);
|
|
// Default axis for index mode is 'x' to match old behaviour
|
|
options.axis = options.axis || 'x';
|
|
var distanceMetric = getDistanceMetricForAxis(options.axis);
|
|
var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric);
|
|
var elements = [];
|
|
|
|
if (!items.length) {
|
|
return [];
|
|
}
|
|
|
|
chart._getSortedVisibleDatasetMetas().forEach(function(meta) {
|
|
var element = meta.data[items[0]._index];
|
|
|
|
// don't count items that are skipped (null data)
|
|
if (element && !element._view.skip) {
|
|
elements.push(element);
|
|
}
|
|
});
|
|
|
|
return elements;
|
|
}
|
|
|
|
/**
|
|
* @interface IInteractionOptions
|
|
*/
|
|
/**
|
|
* If true, only consider items that intersect the point
|
|
* @name IInterfaceOptions#boolean
|
|
* @type Boolean
|
|
*/
|
|
|
|
/**
|
|
* Contains interaction related functions
|
|
* @namespace Chart.Interaction
|
|
*/
|
|
var core_interaction = {
|
|
// Helper function for different modes
|
|
modes: {
|
|
single: function(chart, e) {
|
|
var position = getRelativePosition(e, chart);
|
|
var elements = [];
|
|
|
|
parseVisibleItems(chart, function(element) {
|
|
if (element.inRange(position.x, position.y)) {
|
|
elements.push(element);
|
|
return elements;
|
|
}
|
|
});
|
|
|
|
return elements.slice(0, 1);
|
|
},
|
|
|
|
/**
|
|
* @function Chart.Interaction.modes.label
|
|
* @deprecated since version 2.4.0
|
|
* @todo remove at version 3
|
|
* @private
|
|
*/
|
|
label: indexMode,
|
|
|
|
/**
|
|
* Returns items at the same index. If the options.intersect parameter is true, we only return items if we intersect something
|
|
* If the options.intersect mode is false, we find the nearest item and return the items at the same index as that item
|
|
* @function Chart.Interaction.modes.index
|
|
* @since v2.4.0
|
|
* @param {Chart} chart - the chart we are returning items from
|
|
* @param {Event} e - the event we are find things at
|
|
* @param {IInteractionOptions} options - options to use during interaction
|
|
* @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
|
|
*/
|
|
index: indexMode,
|
|
|
|
/**
|
|
* Returns items in the same dataset. If the options.intersect parameter is true, we only return items if we intersect something
|
|
* If the options.intersect is false, we find the nearest item and return the items in that dataset
|
|
* @function Chart.Interaction.modes.dataset
|
|
* @param {Chart} chart - the chart we are returning items from
|
|
* @param {Event} e - the event we are find things at
|
|
* @param {IInteractionOptions} options - options to use during interaction
|
|
* @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
|
|
*/
|
|
dataset: function(chart, e, options) {
|
|
var position = getRelativePosition(e, chart);
|
|
options.axis = options.axis || 'xy';
|
|
var distanceMetric = getDistanceMetricForAxis(options.axis);
|
|
var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric);
|
|
|
|
if (items.length > 0) {
|
|
items = chart.getDatasetMeta(items[0]._datasetIndex).data;
|
|
}
|
|
|
|
return items;
|
|
},
|
|
|
|
/**
|
|
* @function Chart.Interaction.modes.x-axis
|
|
* @deprecated since version 2.4.0. Use index mode and intersect == true
|
|
* @todo remove at version 3
|
|
* @private
|
|
*/
|
|
'x-axis': function(chart, e) {
|
|
return indexMode(chart, e, {intersect: false});
|
|
},
|
|
|
|
/**
|
|
* Point mode returns all elements that hit test based on the event position
|
|
* of the event
|
|
* @function Chart.Interaction.modes.intersect
|
|
* @param {Chart} chart - the chart we are returning items from
|
|
* @param {Event} e - the event we are find things at
|
|
* @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
|
|
*/
|
|
point: function(chart, e) {
|
|
var position = getRelativePosition(e, chart);
|
|
return getIntersectItems(chart, position);
|
|
},
|
|
|
|
/**
|
|
* nearest mode returns the element closest to the point
|
|
* @function Chart.Interaction.modes.intersect
|
|
* @param {Chart} chart - the chart we are returning items from
|
|
* @param {Event} e - the event we are find things at
|
|
* @param {IInteractionOptions} options - options to use
|
|
* @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
|
|
*/
|
|
nearest: function(chart, e, options) {
|
|
var position = getRelativePosition(e, chart);
|
|
options.axis = options.axis || 'xy';
|
|
var distanceMetric = getDistanceMetricForAxis(options.axis);
|
|
return getNearestItems(chart, position, options.intersect, distanceMetric);
|
|
},
|
|
|
|
/**
|
|
* x mode returns the elements that hit-test at the current x coordinate
|
|
* @function Chart.Interaction.modes.x
|
|
* @param {Chart} chart - the chart we are returning items from
|
|
* @param {Event} e - the event we are find things at
|
|
* @param {IInteractionOptions} options - options to use
|
|
* @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
|
|
*/
|
|
x: function(chart, e, options) {
|
|
var position = getRelativePosition(e, chart);
|
|
var items = [];
|
|
var intersectsItem = false;
|
|
|
|
parseVisibleItems(chart, function(element) {
|
|
if (element.inXRange(position.x)) {
|
|
items.push(element);
|
|
}
|
|
|
|
if (element.inRange(position.x, position.y)) {
|
|
intersectsItem = true;
|
|
}
|
|
});
|
|
|
|
// If we want to trigger on an intersect and we don't have any items
|
|
// that intersect the position, return nothing
|
|
if (options.intersect && !intersectsItem) {
|
|
items = [];
|
|
}
|
|
return items;
|
|
},
|
|
|
|
/**
|
|
* y mode returns the elements that hit-test at the current y coordinate
|
|
* @function Chart.Interaction.modes.y
|
|
* @param {Chart} chart - the chart we are returning items from
|
|
* @param {Event} e - the event we are find things at
|
|
* @param {IInteractionOptions} options - options to use
|
|
* @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned
|
|
*/
|
|
y: function(chart, e, options) {
|
|
var position = getRelativePosition(e, chart);
|
|
var items = [];
|
|
var intersectsItem = false;
|
|
|
|
parseVisibleItems(chart, function(element) {
|
|
if (element.inYRange(position.y)) {
|
|
items.push(element);
|
|
}
|
|
|
|
if (element.inRange(position.x, position.y)) {
|
|
intersectsItem = true;
|
|
}
|
|
});
|
|
|
|
// If we want to trigger on an intersect and we don't have any items
|
|
// that intersect the position, return nothing
|
|
if (options.intersect && !intersectsItem) {
|
|
items = [];
|
|
}
|
|
return items;
|
|
}
|
|
}
|
|
};
|
|
|
|
var extend = helpers$1.extend;
|
|
|
|
function filterByPosition(array, position) {
|
|
return helpers$1.where(array, function(v) {
|
|
return v.pos === position;
|
|
});
|
|
}
|
|
|
|
function sortByWeight(array, reverse) {
|
|
return array.sort(function(a, b) {
|
|
var v0 = reverse ? b : a;
|
|
var v1 = reverse ? a : b;
|
|
return v0.weight === v1.weight ?
|
|
v0.index - v1.index :
|
|
v0.weight - v1.weight;
|
|
});
|
|
}
|
|
|
|
function wrapBoxes(boxes) {
|
|
var layoutBoxes = [];
|
|
var i, ilen, box;
|
|
|
|
for (i = 0, ilen = (boxes || []).length; i < ilen; ++i) {
|
|
box = boxes[i];
|
|
layoutBoxes.push({
|
|
index: i,
|
|
box: box,
|
|
pos: box.position,
|
|
horizontal: box.isHorizontal(),
|
|
weight: box.weight
|
|
});
|
|
}
|
|
return layoutBoxes;
|
|
}
|
|
|
|
function setLayoutDims(layouts, params) {
|
|
var i, ilen, layout;
|
|
for (i = 0, ilen = layouts.length; i < ilen; ++i) {
|
|
layout = layouts[i];
|
|
// store width used instead of chartArea.w in fitBoxes
|
|
layout.width = layout.horizontal
|
|
? layout.box.fullWidth && params.availableWidth
|
|
: params.vBoxMaxWidth;
|
|
// store height used instead of chartArea.h in fitBoxes
|
|
layout.height = layout.horizontal && params.hBoxMaxHeight;
|
|
}
|
|
}
|
|
|
|
function buildLayoutBoxes(boxes) {
|
|
var layoutBoxes = wrapBoxes(boxes);
|
|
var left = sortByWeight(filterByPosition(layoutBoxes, 'left'), true);
|
|
var right = sortByWeight(filterByPosition(layoutBoxes, 'right'));
|
|
var top = sortByWeight(filterByPosition(layoutBoxes, 'top'), true);
|
|
var bottom = sortByWeight(filterByPosition(layoutBoxes, 'bottom'));
|
|
|
|
return {
|
|
leftAndTop: left.concat(top),
|
|
rightAndBottom: right.concat(bottom),
|
|
chartArea: filterByPosition(layoutBoxes, 'chartArea'),
|
|
vertical: left.concat(right),
|
|
horizontal: top.concat(bottom)
|
|
};
|
|
}
|
|
|
|
function getCombinedMax(maxPadding, chartArea, a, b) {
|
|
return Math.max(maxPadding[a], chartArea[a]) + Math.max(maxPadding[b], chartArea[b]);
|
|
}
|
|
|
|
function updateDims(chartArea, params, layout) {
|
|
var box = layout.box;
|
|
var maxPadding = chartArea.maxPadding;
|
|
var newWidth, newHeight;
|
|
|
|
if (layout.size) {
|
|
// this layout was already counted for, lets first reduce old size
|
|
chartArea[layout.pos] -= layout.size;
|
|
}
|
|
layout.size = layout.horizontal ? box.height : box.width;
|
|
chartArea[layout.pos] += layout.size;
|
|
|
|
if (box.getPadding) {
|
|
var boxPadding = box.getPadding();
|
|
maxPadding.top = Math.max(maxPadding.top, boxPadding.top);
|
|
maxPadding.left = Math.max(maxPadding.left, boxPadding.left);
|
|
maxPadding.bottom = Math.max(maxPadding.bottom, boxPadding.bottom);
|
|
maxPadding.right = Math.max(maxPadding.right, boxPadding.right);
|
|
}
|
|
|
|
newWidth = params.outerWidth - getCombinedMax(maxPadding, chartArea, 'left', 'right');
|
|
newHeight = params.outerHeight - getCombinedMax(maxPadding, chartArea, 'top', 'bottom');
|
|
|
|
if (newWidth !== chartArea.w || newHeight !== chartArea.h) {
|
|
chartArea.w = newWidth;
|
|
chartArea.h = newHeight;
|
|
|
|
// return true if chart area changed in layout's direction
|
|
return layout.horizontal ? newWidth !== chartArea.w : newHeight !== chartArea.h;
|
|
}
|
|
}
|
|
|
|
function handleMaxPadding(chartArea) {
|
|
var maxPadding = chartArea.maxPadding;
|
|
|
|
function updatePos(pos) {
|
|
var change = Math.max(maxPadding[pos] - chartArea[pos], 0);
|
|
chartArea[pos] += change;
|
|
return change;
|
|
}
|
|
chartArea.y += updatePos('top');
|
|
chartArea.x += updatePos('left');
|
|
updatePos('right');
|
|
updatePos('bottom');
|
|
}
|
|
|
|
function getMargins(horizontal, chartArea) {
|
|
var maxPadding = chartArea.maxPadding;
|
|
|
|
function marginForPositions(positions) {
|
|
var margin = {left: 0, top: 0, right: 0, bottom: 0};
|
|
positions.forEach(function(pos) {
|
|
margin[pos] = Math.max(chartArea[pos], maxPadding[pos]);
|
|
});
|
|
return margin;
|
|
}
|
|
|
|
return horizontal
|
|
? marginForPositions(['left', 'right'])
|
|
: marginForPositions(['top', 'bottom']);
|
|
}
|
|
|
|
function fitBoxes(boxes, chartArea, params) {
|
|
var refitBoxes = [];
|
|
var i, ilen, layout, box, refit, changed;
|
|
|
|
for (i = 0, ilen = boxes.length; i < ilen; ++i) {
|
|
layout = boxes[i];
|
|
box = layout.box;
|
|
|
|
box.update(
|
|
layout.width || chartArea.w,
|
|
layout.height || chartArea.h,
|
|
getMargins(layout.horizontal, chartArea)
|
|
);
|
|
if (updateDims(chartArea, params, layout)) {
|
|
changed = true;
|
|
if (refitBoxes.length) {
|
|
// Dimensions changed and there were non full width boxes before this
|
|
// -> we have to refit those
|
|
refit = true;
|
|
}
|
|
}
|
|
if (!box.fullWidth) { // fullWidth boxes don't need to be re-fitted in any case
|
|
refitBoxes.push(layout);
|
|
}
|
|
}
|
|
|
|
return refit ? fitBoxes(refitBoxes, chartArea, params) || changed : changed;
|
|
}
|
|
|
|
function placeBoxes(boxes, chartArea, params) {
|
|
var userPadding = params.padding;
|
|
var x = chartArea.x;
|
|
var y = chartArea.y;
|
|
var i, ilen, layout, box;
|
|
|
|
for (i = 0, ilen = boxes.length; i < ilen; ++i) {
|
|
layout = boxes[i];
|
|
box = layout.box;
|
|
if (layout.horizontal) {
|
|
box.left = box.fullWidth ? userPadding.left : chartArea.left;
|
|
box.right = box.fullWidth ? params.outerWidth - userPadding.right : chartArea.left + chartArea.w;
|
|
box.top = y;
|
|
box.bottom = y + box.height;
|
|
box.width = box.right - box.left;
|
|
y = box.bottom;
|
|
} else {
|
|
box.left = x;
|
|
box.right = x + box.width;
|
|
box.top = chartArea.top;
|
|
box.bottom = chartArea.top + chartArea.h;
|
|
box.height = box.bottom - box.top;
|
|
x = box.right;
|
|
}
|
|
}
|
|
|
|
chartArea.x = x;
|
|
chartArea.y = y;
|
|
}
|
|
|
|
core_defaults._set('global', {
|
|
layout: {
|
|
padding: {
|
|
top: 0,
|
|
right: 0,
|
|
bottom: 0,
|
|
left: 0
|
|
}
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @interface ILayoutItem
|
|
* @prop {string} position - The position of the item in the chart layout. Possible values are
|
|
* 'left', 'top', 'right', 'bottom', and 'chartArea'
|
|
* @prop {number} weight - The weight used to sort the item. Higher weights are further away from the chart area
|
|
* @prop {boolean} fullWidth - if true, and the item is horizontal, then push vertical boxes down
|
|
* @prop {function} isHorizontal - returns true if the layout item is horizontal (ie. top or bottom)
|
|
* @prop {function} update - Takes two parameters: width and height. Returns size of item
|
|
* @prop {function} getPadding - Returns an object with padding on the edges
|
|
* @prop {number} width - Width of item. Must be valid after update()
|
|
* @prop {number} height - Height of item. Must be valid after update()
|
|
* @prop {number} left - Left edge of the item. Set by layout system and cannot be used in update
|
|
* @prop {number} top - Top edge of the item. Set by layout system and cannot be used in update
|
|
* @prop {number} right - Right edge of the item. Set by layout system and cannot be used in update
|
|
* @prop {number} bottom - Bottom edge of the item. Set by layout system and cannot be used in update
|
|
*/
|
|
|
|
// The layout service is very self explanatory. It's responsible for the layout within a chart.
|
|
// Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need
|
|
// It is this service's responsibility of carrying out that layout.
|
|
var core_layouts = {
|
|
defaults: {},
|
|
|
|
/**
|
|
* Register a box to a chart.
|
|
* A box is simply a reference to an object that requires layout. eg. Scales, Legend, Title.
|
|
* @param {Chart} chart - the chart to use
|
|
* @param {ILayoutItem} item - the item to add to be layed out
|
|
*/
|
|
addBox: function(chart, item) {
|
|
if (!chart.boxes) {
|
|
chart.boxes = [];
|
|
}
|
|
|
|
// initialize item with default values
|
|
item.fullWidth = item.fullWidth || false;
|
|
item.position = item.position || 'top';
|
|
item.weight = item.weight || 0;
|
|
item._layers = item._layers || function() {
|
|
return [{
|
|
z: 0,
|
|
draw: function() {
|
|
item.draw.apply(item, arguments);
|
|
}
|
|
}];
|
|
};
|
|
|
|
chart.boxes.push(item);
|
|
},
|
|
|
|
/**
|
|
* Remove a layoutItem from a chart
|
|
* @param {Chart} chart - the chart to remove the box from
|
|
* @param {ILayoutItem} layoutItem - the item to remove from the layout
|
|
*/
|
|
removeBox: function(chart, layoutItem) {
|
|
var index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1;
|
|
if (index !== -1) {
|
|
chart.boxes.splice(index, 1);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Sets (or updates) options on the given `item`.
|
|
* @param {Chart} chart - the chart in which the item lives (or will be added to)
|
|
* @param {ILayoutItem} item - the item to configure with the given options
|
|
* @param {object} options - the new item options.
|
|
*/
|
|
configure: function(chart, item, options) {
|
|
var props = ['fullWidth', 'position', 'weight'];
|
|
var ilen = props.length;
|
|
var i = 0;
|
|
var prop;
|
|
|
|
for (; i < ilen; ++i) {
|
|
prop = props[i];
|
|
if (options.hasOwnProperty(prop)) {
|
|
item[prop] = options[prop];
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Fits boxes of the given chart into the given size by having each box measure itself
|
|
* then running a fitting algorithm
|
|
* @param {Chart} chart - the chart
|
|
* @param {number} width - the width to fit into
|
|
* @param {number} height - the height to fit into
|
|
*/
|
|
update: function(chart, width, height) {
|
|
if (!chart) {
|
|
return;
|
|
}
|
|
|
|
var layoutOptions = chart.options.layout || {};
|
|
var padding = helpers$1.options.toPadding(layoutOptions.padding);
|
|
|
|
var availableWidth = width - padding.width;
|
|
var availableHeight = height - padding.height;
|
|
var boxes = buildLayoutBoxes(chart.boxes);
|
|
var verticalBoxes = boxes.vertical;
|
|
var horizontalBoxes = boxes.horizontal;
|
|
|
|
// Essentially we now have any number of boxes on each of the 4 sides.
|
|
// Our canvas looks like the following.
|
|
// The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and
|
|
// B1 is the bottom axis
|
|
// There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays
|
|
// These locations are single-box locations only, when trying to register a chartArea location that is already taken,
|
|
// an error will be thrown.
|
|
//
|
|
// |----------------------------------------------------|
|
|
// | T1 (Full Width) |
|
|
// |----------------------------------------------------|
|
|
// | | | T2 | |
|
|
// | |----|-------------------------------------|----|
|
|
// | | | C1 | | C2 | |
|
|
// | | |----| |----| |
|
|
// | | | | |
|
|
// | L1 | L2 | ChartArea (C0) | R1 |
|
|
// | | | | |
|
|
// | | |----| |----| |
|
|
// | | | C3 | | C4 | |
|
|
// | |----|-------------------------------------|----|
|
|
// | | | B1 | |
|
|
// |----------------------------------------------------|
|
|
// | B2 (Full Width) |
|
|
// |----------------------------------------------------|
|
|
//
|
|
|
|
var params = Object.freeze({
|
|
outerWidth: width,
|
|
outerHeight: height,
|
|
padding: padding,
|
|
availableWidth: availableWidth,
|
|
vBoxMaxWidth: availableWidth / 2 / verticalBoxes.length,
|
|
hBoxMaxHeight: availableHeight / 2
|
|
});
|
|
var chartArea = extend({
|
|
maxPadding: extend({}, padding),
|
|
w: availableWidth,
|
|
h: availableHeight,
|
|
x: padding.left,
|
|
y: padding.top
|
|
}, padding);
|
|
|
|
setLayoutDims(verticalBoxes.concat(horizontalBoxes), params);
|
|
|
|
// First fit vertical boxes
|
|
fitBoxes(verticalBoxes, chartArea, params);
|
|
|
|
// Then fit horizontal boxes
|
|
if (fitBoxes(horizontalBoxes, chartArea, params)) {
|
|
// if the area changed, re-fit vertical boxes
|
|
fitBoxes(verticalBoxes, chartArea, params);
|
|
}
|
|
|
|
handleMaxPadding(chartArea);
|
|
|
|
// Finally place the boxes to correct coordinates
|
|
placeBoxes(boxes.leftAndTop, chartArea, params);
|
|
|
|
// Move to opposite side of chart
|
|
chartArea.x += chartArea.w;
|
|
chartArea.y += chartArea.h;
|
|
|
|
placeBoxes(boxes.rightAndBottom, chartArea, params);
|
|
|
|
chart.chartArea = {
|
|
left: chartArea.left,
|
|
top: chartArea.top,
|
|
right: chartArea.left + chartArea.w,
|
|
bottom: chartArea.top + chartArea.h
|
|
};
|
|
|
|
// Finally update boxes in chartArea (radial scale for example)
|
|
helpers$1.each(boxes.chartArea, function(layout) {
|
|
var box = layout.box;
|
|
extend(box, chart.chartArea);
|
|
box.update(chartArea.w, chartArea.h);
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Platform fallback implementation (minimal).
|
|
* @see https://github.com/chartjs/Chart.js/pull/4591#issuecomment-319575939
|
|
*/
|
|
|
|
var platform_basic = {
|
|
acquireContext: function(item) {
|
|
if (item && item.canvas) {
|
|
// Support for any object associated to a canvas (including a context2d)
|
|
item = item.canvas;
|
|
}
|
|
|
|
return item && item.getContext('2d') || null;
|
|
}
|
|
};
|
|
|
|
var platform_dom = "/*\n * DOM element rendering detection\n * https://davidwalsh.name/detect-node-insertion\n */\n@keyframes chartjs-render-animation {\n\tfrom { opacity: 0.99; }\n\tto { opacity: 1; }\n}\n\n.chartjs-render-monitor {\n\tanimation: chartjs-render-animation 0.001s;\n}\n\n/*\n * DOM element resizing detection\n * https://github.com/marcj/css-element-queries\n */\n.chartjs-size-monitor,\n.chartjs-size-monitor-expand,\n.chartjs-size-monitor-shrink {\n\tposition: absolute;\n\tdirection: ltr;\n\tleft: 0;\n\ttop: 0;\n\tright: 0;\n\tbottom: 0;\n\toverflow: hidden;\n\tpointer-events: none;\n\tvisibility: hidden;\n\tz-index: -1;\n}\n\n.chartjs-size-monitor-expand > div {\n\tposition: absolute;\n\twidth: 1000000px;\n\theight: 1000000px;\n\tleft: 0;\n\ttop: 0;\n}\n\n.chartjs-size-monitor-shrink > div {\n\tposition: absolute;\n\twidth: 200%;\n\theight: 200%;\n\tleft: 0;\n\ttop: 0;\n}\n";
|
|
|
|
var platform_dom$1 = /*#__PURE__*/Object.freeze({
|
|
__proto__: null,
|
|
'default': platform_dom
|
|
});
|
|
|
|
var stylesheet = getCjsExportFromNamespace(platform_dom$1);
|
|
|
|
var EXPANDO_KEY = '$chartjs';
|
|
var CSS_PREFIX = 'chartjs-';
|
|
var CSS_SIZE_MONITOR = CSS_PREFIX + 'size-monitor';
|
|
var CSS_RENDER_MONITOR = CSS_PREFIX + 'render-monitor';
|
|
var CSS_RENDER_ANIMATION = CSS_PREFIX + 'render-animation';
|
|
var ANIMATION_START_EVENTS = ['animationstart', 'webkitAnimationStart'];
|
|
|
|
/**
|
|
* DOM event types -> Chart.js event types.
|
|
* Note: only events with different types are mapped.
|
|
* @see https://developer.mozilla.org/en-US/docs/Web/Events
|
|
*/
|
|
var EVENT_TYPES = {
|
|
touchstart: 'mousedown',
|
|
touchmove: 'mousemove',
|
|
touchend: 'mouseup',
|
|
pointerenter: 'mouseenter',
|
|
pointerdown: 'mousedown',
|
|
pointermove: 'mousemove',
|
|
pointerup: 'mouseup',
|
|
pointerleave: 'mouseout',
|
|
pointerout: 'mouseout'
|
|
};
|
|
|
|
/**
|
|
* The "used" size is the final value of a dimension property after all calculations have
|
|
* been performed. This method uses the computed style of `element` but returns undefined
|
|
* if the computed style is not expressed in pixels. That can happen in some cases where
|
|
* `element` has a size relative to its parent and this last one is not yet displayed,
|
|
* for example because of `display: none` on a parent node.
|
|
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value
|
|
* @returns {number} Size in pixels or undefined if unknown.
|
|
*/
|
|
function readUsedSize(element, property) {
|
|
var value = helpers$1.getStyle(element, property);
|
|
var matches = value && value.match(/^(\d+)(\.\d+)?px$/);
|
|
return matches ? Number(matches[1]) : undefined;
|
|
}
|
|
|
|
/**
|
|
* Initializes the canvas style and render size without modifying the canvas display size,
|
|
* since responsiveness is handled by the controller.resize() method. The config is used
|
|
* to determine the aspect ratio to apply in case no explicit height has been specified.
|
|
*/
|
|
function initCanvas(canvas, config) {
|
|
var style = canvas.style;
|
|
|
|
// NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it
|
|
// returns null or '' if no explicit value has been set to the canvas attribute.
|
|
var renderHeight = canvas.getAttribute('height');
|
|
var renderWidth = canvas.getAttribute('width');
|
|
|
|
// Chart.js modifies some canvas values that we want to restore on destroy
|
|
canvas[EXPANDO_KEY] = {
|
|
initial: {
|
|
height: renderHeight,
|
|
width: renderWidth,
|
|
style: {
|
|
display: style.display,
|
|
height: style.height,
|
|
width: style.width
|
|
}
|
|
}
|
|
};
|
|
|
|
// Force canvas to display as block to avoid extra space caused by inline
|
|
// elements, which would interfere with the responsive resize process.
|
|
// https://github.com/chartjs/Chart.js/issues/2538
|
|
style.display = style.display || 'block';
|
|
|
|
if (renderWidth === null || renderWidth === '') {
|
|
var displayWidth = readUsedSize(canvas, 'width');
|
|
if (displayWidth !== undefined) {
|
|
canvas.width = displayWidth;
|
|
}
|
|
}
|
|
|
|
if (renderHeight === null || renderHeight === '') {
|
|
if (canvas.style.height === '') {
|
|
// If no explicit render height and style height, let's apply the aspect ratio,
|
|
// which one can be specified by the user but also by charts as default option
|
|
// (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2.
|
|
canvas.height = canvas.width / (config.options.aspectRatio || 2);
|
|
} else {
|
|
var displayHeight = readUsedSize(canvas, 'height');
|
|
if (displayWidth !== undefined) {
|
|
canvas.height = displayHeight;
|
|
}
|
|
}
|
|
}
|
|
|
|
return canvas;
|
|
}
|
|
|
|
/**
|
|
* Detects support for options object argument in addEventListener.
|
|
* https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support
|
|
* @private
|
|
*/
|
|
var supportsEventListenerOptions = (function() {
|
|
var supports = false;
|
|
try {
|
|
var options = Object.defineProperty({}, 'passive', {
|
|
// eslint-disable-next-line getter-return
|
|
get: function() {
|
|
supports = true;
|
|
}
|
|
});
|
|
window.addEventListener('e', null, options);
|
|
} catch (e) {
|
|
// continue regardless of error
|
|
}
|
|
return supports;
|
|
}());
|
|
|
|
// Default passive to true as expected by Chrome for 'touchstart' and 'touchend' events.
|
|
// https://github.com/chartjs/Chart.js/issues/4287
|
|
var eventListenerOptions = supportsEventListenerOptions ? {passive: true} : false;
|
|
|
|
function addListener(node, type, listener) {
|
|
node.addEventListener(type, listener, eventListenerOptions);
|
|
}
|
|
|
|
function removeListener(node, type, listener) {
|
|
node.removeEventListener(type, listener, eventListenerOptions);
|
|
}
|
|
|
|
function createEvent(type, chart, x, y, nativeEvent) {
|
|
return {
|
|
type: type,
|
|
chart: chart,
|
|
native: nativeEvent || null,
|
|
x: x !== undefined ? x : null,
|
|
y: y !== undefined ? y : null,
|
|
};
|
|
}
|
|
|
|
function fromNativeEvent(event, chart) {
|
|
var type = EVENT_TYPES[event.type] || event.type;
|
|
var pos = helpers$1.getRelativePosition(event, chart);
|
|
return createEvent(type, chart, pos.x, pos.y, event);
|
|
}
|
|
|
|
function throttled(fn, thisArg) {
|
|
var ticking = false;
|
|
var args = [];
|
|
|
|
return function() {
|
|
args = Array.prototype.slice.call(arguments);
|
|
thisArg = thisArg || this;
|
|
|
|
if (!ticking) {
|
|
ticking = true;
|
|
helpers$1.requestAnimFrame.call(window, function() {
|
|
ticking = false;
|
|
fn.apply(thisArg, args);
|
|
});
|
|
}
|
|
};
|
|
}
|
|
|
|
function createDiv(cls) {
|
|
var el = document.createElement('div');
|
|
el.className = cls || '';
|
|
return el;
|
|
}
|
|
|
|
// Implementation based on https://github.com/marcj/css-element-queries
|
|
function createResizer(handler) {
|
|
var maxSize = 1000000;
|
|
|
|
// NOTE(SB) Don't use innerHTML because it could be considered unsafe.
|
|
// https://github.com/chartjs/Chart.js/issues/5902
|
|
var resizer = createDiv(CSS_SIZE_MONITOR);
|
|
var expand = createDiv(CSS_SIZE_MONITOR + '-expand');
|
|
var shrink = createDiv(CSS_SIZE_MONITOR + '-shrink');
|
|
|
|
expand.appendChild(createDiv());
|
|
shrink.appendChild(createDiv());
|
|
|
|
resizer.appendChild(expand);
|
|
resizer.appendChild(shrink);
|
|
resizer._reset = function() {
|
|
expand.scrollLeft = maxSize;
|
|
expand.scrollTop = maxSize;
|
|
shrink.scrollLeft = maxSize;
|
|
shrink.scrollTop = maxSize;
|
|
};
|
|
|
|
var onScroll = function() {
|
|
resizer._reset();
|
|
handler();
|
|
};
|
|
|
|
addListener(expand, 'scroll', onScroll.bind(expand, 'expand'));
|
|
addListener(shrink, 'scroll', onScroll.bind(shrink, 'shrink'));
|
|
|
|
return resizer;
|
|
}
|
|
|
|
// https://davidwalsh.name/detect-node-insertion
|
|
function watchForRender(node, handler) {
|
|
var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {});
|
|
var proxy = expando.renderProxy = function(e) {
|
|
if (e.animationName === CSS_RENDER_ANIMATION) {
|
|
handler();
|
|
}
|
|
};
|
|
|
|
helpers$1.each(ANIMATION_START_EVENTS, function(type) {
|
|
addListener(node, type, proxy);
|
|
});
|
|
|
|
// #4737: Chrome might skip the CSS animation when the CSS_RENDER_MONITOR class
|
|
// is removed then added back immediately (same animation frame?). Accessing the
|
|
// `offsetParent` property will force a reflow and re-evaluate the CSS animation.
|
|
// https://gist.github.com/paulirish/5d52fb081b3570c81e3a#box-metrics
|
|
// https://github.com/chartjs/Chart.js/issues/4737
|
|
expando.reflow = !!node.offsetParent;
|
|
|
|
node.classList.add(CSS_RENDER_MONITOR);
|
|
}
|
|
|
|
function unwatchForRender(node) {
|
|
var expando = node[EXPANDO_KEY] || {};
|
|
var proxy = expando.renderProxy;
|
|
|
|
if (proxy) {
|
|
helpers$1.each(ANIMATION_START_EVENTS, function(type) {
|
|
removeListener(node, type, proxy);
|
|
});
|
|
|
|
delete expando.renderProxy;
|
|
}
|
|
|
|
node.classList.remove(CSS_RENDER_MONITOR);
|
|
}
|
|
|
|
function addResizeListener(node, listener, chart) {
|
|
var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {});
|
|
|
|
// Let's keep track of this added resizer and thus avoid DOM query when removing it.
|
|
var resizer = expando.resizer = createResizer(throttled(function() {
|
|
if (expando.resizer) {
|
|
var container = chart.options.maintainAspectRatio && node.parentNode;
|
|
var w = container ? container.clientWidth : 0;
|
|
listener(createEvent('resize', chart));
|
|
if (container && container.clientWidth < w && chart.canvas) {
|
|
// If the container size shrank during chart resize, let's assume
|
|
// scrollbar appeared. So we resize again with the scrollbar visible -
|
|
// effectively making chart smaller and the scrollbar hidden again.
|
|
// Because we are inside `throttled`, and currently `ticking`, scroll
|
|
// events are ignored during this whole 2 resize process.
|
|
// If we assumed wrong and something else happened, we are resizing
|
|
// twice in a frame (potential performance issue)
|
|
listener(createEvent('resize', chart));
|
|
}
|
|
}
|
|
}));
|
|
|
|
// The resizer needs to be attached to the node parent, so we first need to be
|
|
// sure that `node` is attached to the DOM before injecting the resizer element.
|
|
watchForRender(node, function() {
|
|
if (expando.resizer) {
|
|
var container = node.parentNode;
|
|
if (container && container !== resizer.parentNode) {
|
|
container.insertBefore(resizer, container.firstChild);
|
|
}
|
|
|
|
// The container size might have changed, let's reset the resizer state.
|
|
resizer._reset();
|
|
}
|
|
});
|
|
}
|
|
|
|
function removeResizeListener(node) {
|
|
var expando = node[EXPANDO_KEY] || {};
|
|
var resizer = expando.resizer;
|
|
|
|
delete expando.resizer;
|
|
unwatchForRender(node);
|
|
|
|
if (resizer && resizer.parentNode) {
|
|
resizer.parentNode.removeChild(resizer);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Injects CSS styles inline if the styles are not already present.
|
|
* @param {HTMLDocument|ShadowRoot} rootNode - the node to contain the <style>.
|
|
* @param {string} css - the CSS to be injected.
|
|
*/
|
|
function injectCSS(rootNode, css) {
|
|
// https://stackoverflow.com/q/3922139
|
|
var expando = rootNode[EXPANDO_KEY] || (rootNode[EXPANDO_KEY] = {});
|
|
if (!expando.containsStyles) {
|
|
expando.containsStyles = true;
|
|
css = '/* Chart.js */\n' + css;
|
|
var style = document.createElement('style');
|
|
style.setAttribute('type', 'text/css');
|
|
style.appendChild(document.createTextNode(css));
|
|
rootNode.appendChild(style);
|
|
}
|
|
}
|
|
|
|
var platform_dom$2 = {
|
|
/**
|
|
* When `true`, prevents the automatic injection of the stylesheet required to
|
|
* correctly detect when the chart is added to the DOM and then resized. This
|
|
* switch has been added to allow external stylesheet (`dist/Chart(.min)?.js`)
|
|
* to be manually imported to make this library compatible with any CSP.
|
|
* See https://github.com/chartjs/Chart.js/issues/5208
|
|
*/
|
|
disableCSSInjection: false,
|
|
|
|
/**
|
|
* This property holds whether this platform is enabled for the current environment.
|
|
* Currently used by platform.js to select the proper implementation.
|
|
* @private
|
|
*/
|
|
_enabled: typeof window !== 'undefined' && typeof document !== 'undefined',
|
|
|
|
/**
|
|
* Initializes resources that depend on platform options.
|
|
* @param {HTMLCanvasElement} canvas - The Canvas element.
|
|
* @private
|
|
*/
|
|
_ensureLoaded: function(canvas) {
|
|
if (!this.disableCSSInjection) {
|
|
// If the canvas is in a shadow DOM, then the styles must also be inserted
|
|
// into the same shadow DOM.
|
|
// https://github.com/chartjs/Chart.js/issues/5763
|
|
var root = canvas.getRootNode ? canvas.getRootNode() : document;
|
|
var targetNode = root.host ? root : document.head;
|
|
injectCSS(targetNode, stylesheet);
|
|
}
|
|
},
|
|
|
|
acquireContext: function(item, config) {
|
|
if (typeof item === 'string') {
|
|
item = document.getElementById(item);
|
|
} else if (item.length) {
|
|
// Support for array based queries (such as jQuery)
|
|
item = item[0];
|
|
}
|
|
|
|
if (item && item.canvas) {
|
|
// Support for any object associated to a canvas (including a context2d)
|
|
item = item.canvas;
|
|
}
|
|
|
|
// To prevent canvas fingerprinting, some add-ons undefine the getContext
|
|
// method, for example: https://github.com/kkapsner/CanvasBlocker
|
|
// https://github.com/chartjs/Chart.js/issues/2807
|
|
var context = item && item.getContext && item.getContext('2d');
|
|
|
|
// `instanceof HTMLCanvasElement/CanvasRenderingContext2D` fails when the item is
|
|
// inside an iframe or when running in a protected environment. We could guess the
|
|
// types from their toString() value but let's keep things flexible and assume it's
|
|
// a sufficient condition if the item has a context2D which has item as `canvas`.
|
|
// https://github.com/chartjs/Chart.js/issues/3887
|
|
// https://github.com/chartjs/Chart.js/issues/4102
|
|
// https://github.com/chartjs/Chart.js/issues/4152
|
|
if (context && context.canvas === item) {
|
|
// Load platform resources on first chart creation, to make it possible to
|
|
// import the library before setting platform options.
|
|
this._ensureLoaded(item);
|
|
initCanvas(item, config);
|
|
return context;
|
|
}
|
|
|
|
return null;
|
|
},
|
|
|
|
releaseContext: function(context) {
|
|
var canvas = context.canvas;
|
|
if (!canvas[EXPANDO_KEY]) {
|
|
return;
|
|
}
|
|
|
|
var initial = canvas[EXPANDO_KEY].initial;
|
|
['height', 'width'].forEach(function(prop) {
|
|
var value = initial[prop];
|
|
if (helpers$1.isNullOrUndef(value)) {
|
|
canvas.removeAttribute(prop);
|
|
} else {
|
|
canvas.setAttribute(prop, value);
|
|
}
|
|
});
|
|
|
|
helpers$1.each(initial.style || {}, function(value, key) {
|
|
canvas.style[key] = value;
|
|
});
|
|
|
|
// The canvas render size might have been changed (and thus the state stack discarded),
|
|
// we can't use save() and restore() to restore the initial state. So make sure that at
|
|
// least the canvas context is reset to the default state by setting the canvas width.
|
|
// https://www.w3.org/TR/2011/WD-html5-20110525/the-canvas-element.html
|
|
// eslint-disable-next-line no-self-assign
|
|
canvas.width = canvas.width;
|
|
|
|
delete canvas[EXPANDO_KEY];
|
|
},
|
|
|
|
addEventListener: function(chart, type, listener) {
|
|
var canvas = chart.canvas;
|
|
if (type === 'resize') {
|
|
// Note: the resize event is not supported on all browsers.
|
|
addResizeListener(canvas, listener, chart);
|
|
return;
|
|
}
|
|
|
|
var expando = listener[EXPANDO_KEY] || (listener[EXPANDO_KEY] = {});
|
|
var proxies = expando.proxies || (expando.proxies = {});
|
|
var proxy = proxies[chart.id + '_' + type] = function(event) {
|
|
listener(fromNativeEvent(event, chart));
|
|
};
|
|
|
|
addListener(canvas, type, proxy);
|
|
},
|
|
|
|
removeEventListener: function(chart, type, listener) {
|
|
var canvas = chart.canvas;
|
|
if (type === 'resize') {
|
|
// Note: the resize event is not supported on all browsers.
|
|
removeResizeListener(canvas);
|
|
return;
|
|
}
|
|
|
|
var expando = listener[EXPANDO_KEY] || {};
|
|
var proxies = expando.proxies || {};
|
|
var proxy = proxies[chart.id + '_' + type];
|
|
if (!proxy) {
|
|
return;
|
|
}
|
|
|
|
removeListener(canvas, type, proxy);
|
|
}
|
|
};
|
|
|
|
// DEPRECATIONS
|
|
|
|
/**
|
|
* Provided for backward compatibility, use EventTarget.addEventListener instead.
|
|
* EventTarget.addEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+
|
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
|
|
* @function Chart.helpers.addEvent
|
|
* @deprecated since version 2.7.0
|
|
* @todo remove at version 3
|
|
* @private
|
|
*/
|
|
helpers$1.addEvent = addListener;
|
|
|
|
/**
|
|
* Provided for backward compatibility, use EventTarget.removeEventListener instead.
|
|
* EventTarget.removeEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+
|
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener
|
|
* @function Chart.helpers.removeEvent
|
|
* @deprecated since version 2.7.0
|
|
* @todo remove at version 3
|
|
* @private
|
|
*/
|
|
helpers$1.removeEvent = removeListener;
|
|
|
|
// @TODO Make possible to select another platform at build time.
|
|
var implementation = platform_dom$2._enabled ? platform_dom$2 : platform_basic;
|
|
|
|
/**
|
|
* @namespace Chart.platform
|
|
* @see https://chartjs.gitbooks.io/proposals/content/Platform.html
|
|
* @since 2.4.0
|
|
*/
|
|
var platform = helpers$1.extend({
|
|
/**
|
|
* @since 2.7.0
|
|
*/
|
|
initialize: function() {},
|
|
|
|
/**
|
|
* Called at chart construction time, returns a context2d instance implementing
|
|
* the [W3C Canvas 2D Context API standard]{@link https://www.w3.org/TR/2dcontext/}.
|
|
* @param {*} item - The native item from which to acquire context (platform specific)
|
|
* @param {object} options - The chart options
|
|
* @returns {CanvasRenderingContext2D} context2d instance
|
|
*/
|
|
acquireContext: function() {},
|
|
|
|
/**
|
|
* Called at chart destruction time, releases any resources associated to the context
|
|
* previously returned by the acquireContext() method.
|
|
* @param {CanvasRenderingContext2D} context - The context2d instance
|
|
* @returns {boolean} true if the method succeeded, else false
|
|
*/
|
|
releaseContext: function() {},
|
|
|
|
/**
|
|
* Registers the specified listener on the given chart.
|
|
* @param {Chart} chart - Chart from which to listen for event
|
|
* @param {string} type - The ({@link IEvent}) type to listen for
|
|
* @param {function} listener - Receives a notification (an object that implements
|
|
* the {@link IEvent} interface) when an event of the specified type occurs.
|
|
*/
|
|
addEventListener: function() {},
|
|
|
|
/**
|
|
* Removes the specified listener previously registered with addEventListener.
|
|
* @param {Chart} chart - Chart from which to remove the listener
|
|
* @param {string} type - The ({@link IEvent}) type to remove
|
|
* @param {function} listener - The listener function to remove from the event target.
|
|
*/
|
|
removeEventListener: function() {}
|
|
|
|
}, implementation);
|
|
|
|
core_defaults._set('global', {
|
|
plugins: {}
|
|
});
|
|
|
|
/**
|
|
* The plugin service singleton
|
|
* @namespace Chart.plugins
|
|
* @since 2.1.0
|
|
*/
|
|
var core_plugins = {
|
|
/**
|
|
* Globally registered plugins.
|
|
* @private
|
|
*/
|
|
_plugins: [],
|
|
|
|
/**
|
|
* This identifier is used to invalidate the descriptors cache attached to each chart
|
|
* when a global plugin is registered or unregistered. In this case, the cache ID is
|
|
* incremented and descriptors are regenerated during following API calls.
|
|
* @private
|
|
*/
|
|
_cacheId: 0,
|
|
|
|
/**
|
|
* Registers the given plugin(s) if not already registered.
|
|
* @param {IPlugin[]|IPlugin} plugins plugin instance(s).
|
|
*/
|
|
register: function(plugins) {
|
|
var p = this._plugins;
|
|
([]).concat(plugins).forEach(function(plugin) {
|
|
if (p.indexOf(plugin) === -1) {
|
|
p.push(plugin);
|
|
}
|
|
});
|
|
|
|
this._cacheId++;
|
|
},
|
|
|
|
/**
|
|
* Unregisters the given plugin(s) only if registered.
|
|
* @param {IPlugin[]|IPlugin} plugins plugin instance(s).
|
|
*/
|
|
unregister: function(plugins) {
|
|
var p = this._plugins;
|
|
([]).concat(plugins).forEach(function(plugin) {
|
|
var idx = p.indexOf(plugin);
|
|
if (idx !== -1) {
|
|
p.splice(idx, 1);
|
|
}
|
|
});
|
|
|
|
this._cacheId++;
|
|
},
|
|
|
|
/**
|
|
* Remove all registered plugins.
|
|
* @since 2.1.5
|
|
*/
|
|
clear: function() {
|
|
this._plugins = [];
|
|
this._cacheId++;
|
|
},
|
|
|
|
/**
|
|
* Returns the number of registered plugins?
|
|
* @returns {number}
|
|
* @since 2.1.5
|
|
*/
|
|
count: function() {
|
|
return this._plugins.length;
|
|
},
|
|
|
|
/**
|
|
* Returns all registered plugin instances.
|
|
* @returns {IPlugin[]} array of plugin objects.
|
|
* @since 2.1.5
|
|
*/
|
|
getAll: function() {
|
|
return this._plugins;
|
|
},
|
|
|
|
/**
|
|
* Calls enabled plugins for `chart` on the specified hook and with the given args.
|
|
* This method immediately returns as soon as a plugin explicitly returns false. The
|
|
* returned value can be used, for instance, to interrupt the current action.
|
|
* @param {Chart} chart - The chart instance for which plugins should be called.
|
|
* @param {string} hook - The name of the plugin method to call (e.g. 'beforeUpdate').
|
|
* @param {Array} [args] - Extra arguments to apply to the hook call.
|
|
* @returns {boolean} false if any of the plugins return false, else returns true.
|
|
*/
|
|
notify: function(chart, hook, args) {
|
|
var descriptors = this.descriptors(chart);
|
|
var ilen = descriptors.length;
|
|
var i, descriptor, plugin, params, method;
|
|
|
|
for (i = 0; i < ilen; ++i) {
|
|
descriptor = descriptors[i];
|
|
plugin = descriptor.plugin;
|
|
method = plugin[hook];
|
|
if (typeof method === 'function') {
|
|
params = [chart].concat(args || []);
|
|
params.push(descriptor.options);
|
|
if (method.apply(plugin, params) === false) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
},
|
|
|
|
/**
|
|
* Returns descriptors of enabled plugins for the given chart.
|
|
* @returns {object[]} [{ plugin, options }]
|
|
* @private
|
|
*/
|
|
descriptors: function(chart) {
|
|
var cache = chart.$plugins || (chart.$plugins = {});
|
|
if (cache.id === this._cacheId) {
|
|
return cache.descriptors;
|
|
}
|
|
|
|
var plugins = [];
|
|
var descriptors = [];
|
|
var config = (chart && chart.config) || {};
|
|
var options = (config.options && config.options.plugins) || {};
|
|
|
|
this._plugins.concat(config.plugins || []).forEach(function(plugin) {
|
|
var idx = plugins.indexOf(plugin);
|
|
if (idx !== -1) {
|
|
return;
|
|
}
|
|
|
|
var id = plugin.id;
|
|
var opts = options[id];
|
|
if (opts === false) {
|
|
return;
|
|
}
|
|
|
|
if (opts === true) {
|
|
opts = helpers$1.clone(core_defaults.global.plugins[id]);
|
|
}
|
|
|
|
plugins.push(plugin);
|
|
descriptors.push({
|
|
plugin: plugin,
|
|
options: opts || {}
|
|
});
|
|
});
|
|
|
|
cache.descriptors = descriptors;
|
|
cache.id = this._cacheId;
|
|
return descriptors;
|
|
},
|
|
|
|
/**
|
|
* Invalidates cache for the given chart: descriptors hold a reference on plugin option,
|
|
* but in some cases, this reference can be changed by the user when updating options.
|
|
* https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167
|
|
* @private
|
|
*/
|
|
_invalidate: function(chart) {
|
|
delete chart.$plugins;
|
|
}
|
|
};
|
|
|
|
var core_scaleService = {
|
|
// Scale registration object. Extensions can register new scale types (such as log or DB scales) and then
|
|
// use the new chart options to grab the correct scale
|
|
constructors: {},
|
|
// Use a registration function so that we can move to an ES6 map when we no longer need to support
|
|
// old browsers
|
|
|
|
// Scale config defaults
|
|
defaults: {},
|
|
registerScaleType: function(type, scaleConstructor, scaleDefaults) {
|
|
this.constructors[type] = scaleConstructor;
|
|
this.defaults[type] = helpers$1.clone(scaleDefaults);
|
|
},
|
|
getScaleConstructor: function(type) {
|
|
return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined;
|
|
},
|
|
getScaleDefaults: function(type) {
|
|
// Return the scale defaults merged with the global settings so that we always use the latest ones
|
|
return this.defaults.hasOwnProperty(type) ? helpers$1.merge({}, [core_defaults.scale, this.defaults[type]]) : {};
|
|
},
|
|
updateScaleDefaults: function(type, additions) {
|
|
var me = this;
|
|
if (me.defaults.hasOwnProperty(type)) {
|
|
me.defaults[type] = helpers$1.extend(me.defaults[type], additions);
|
|
}
|
|
},
|
|
addScalesToLayout: function(chart) {
|
|
// Adds each scale to the chart.boxes array to be sized accordingly
|
|
helpers$1.each(chart.scales, function(scale) {
|
|
// Set ILayoutItem parameters for backwards compatibility
|
|
scale.fullWidth = scale.options.fullWidth;
|
|
scale.position = scale.options.position;
|
|
scale.weight = scale.options.weight;
|
|
core_layouts.addBox(chart, scale);
|
|
});
|
|
}
|
|
};
|
|
|
|
var valueOrDefault$8 = helpers$1.valueOrDefault;
|
|
var getRtlHelper = helpers$1.rtl.getRtlAdapter;
|
|
|
|
core_defaults._set('global', {
|
|
tooltips: {
|
|
enabled: true,
|
|
custom: null,
|
|
mode: 'nearest',
|
|
position: 'average',
|
|
intersect: true,
|
|
backgroundColor: 'rgba(0,0,0,0.8)',
|
|
titleFontStyle: 'bold',
|
|
titleSpacing: 2,
|
|
titleMarginBottom: 6,
|
|
titleFontColor: '#fff',
|
|
titleAlign: 'left',
|
|
bodySpacing: 2,
|
|
bodyFontColor: '#fff',
|
|
bodyAlign: 'left',
|
|
footerFontStyle: 'bold',
|
|
footerSpacing: 2,
|
|
footerMarginTop: 6,
|
|
footerFontColor: '#fff',
|
|
footerAlign: 'left',
|
|
yPadding: 6,
|
|
xPadding: 6,
|
|
caretPadding: 2,
|
|
caretSize: 5,
|
|
cornerRadius: 6,
|
|
multiKeyBackground: '#fff',
|
|
displayColors: true,
|
|
borderColor: 'rgba(0,0,0,0)',
|
|
borderWidth: 0,
|
|
callbacks: {
|
|
// Args are: (tooltipItems, data)
|
|
beforeTitle: helpers$1.noop,
|
|
title: function(tooltipItems, data) {
|
|
var title = '';
|
|
var labels = data.labels;
|
|
var labelCount = labels ? labels.length : 0;
|
|
|
|
if (tooltipItems.length > 0) {
|
|
var item = tooltipItems[0];
|
|
if (item.label) {
|
|
title = item.label;
|
|
} else if (item.xLabel) {
|
|
title = item.xLabel;
|
|
} else if (labelCount > 0 && item.index < labelCount) {
|
|
title = labels[item.index];
|
|
}
|
|
}
|
|
|
|
return title;
|
|
},
|
|
afterTitle: helpers$1.noop,
|
|
|
|
// Args are: (tooltipItems, data)
|
|
beforeBody: helpers$1.noop,
|
|
|
|
// Args are: (tooltipItem, data)
|
|
beforeLabel: helpers$1.noop,
|
|
label: function(tooltipItem, data) {
|
|
var label = data.datasets[tooltipItem.datasetIndex].label || '';
|
|
|
|
if (label) {
|
|
label += ': ';
|
|
}
|
|
if (!helpers$1.isNullOrUndef(tooltipItem.value)) {
|
|
label += tooltipItem.value;
|
|
} else {
|
|
label += tooltipItem.yLabel;
|
|
}
|
|
return label;
|
|
},
|
|
labelColor: function(tooltipItem, chart) {
|
|
var meta = chart.getDatasetMeta(tooltipItem.datasetIndex);
|
|
var activeElement = meta.data[tooltipItem.index];
|
|
var view = activeElement._view;
|
|
return {
|
|
borderColor: view.borderColor,
|
|
backgroundColor: view.backgroundColor
|
|
};
|
|
},
|
|
labelTextColor: function() {
|
|
return this._options.bodyFontColor;
|
|
},
|
|
afterLabel: helpers$1.noop,
|
|
|
|
// Args are: (tooltipItems, data)
|
|
afterBody: helpers$1.noop,
|
|
|
|
// Args are: (tooltipItems, data)
|
|
beforeFooter: helpers$1.noop,
|
|
footer: helpers$1.noop,
|
|
afterFooter: helpers$1.noop
|
|
}
|
|
}
|
|
});
|
|
|
|
var positioners = {
|
|
/**
|
|
* Average mode places the tooltip at the average position of the elements shown
|
|
* @function Chart.Tooltip.positioners.average
|
|
* @param elements {ChartElement[]} the elements being displayed in the tooltip
|
|
* @returns {object} tooltip position
|
|
*/
|
|
average: function(elements) {
|
|
if (!elements.length) {
|
|
return false;
|
|
}
|
|
|
|
var i, len;
|
|
var x = 0;
|
|
var y = 0;
|
|
var count = 0;
|
|
|
|
for (i = 0, len = elements.length; i < len; ++i) {
|
|
var el = elements[i];
|
|
if (el && el.hasValue()) {
|
|
var pos = el.tooltipPosition();
|
|
x += pos.x;
|
|
y += pos.y;
|
|
++count;
|
|
}
|
|
}
|
|
|
|
return {
|
|
x: x / count,
|
|
y: y / count
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Gets the tooltip position nearest of the item nearest to the event position
|
|
* @function Chart.Tooltip.positioners.nearest
|
|
* @param elements {Chart.Element[]} the tooltip elements
|
|
* @param eventPosition {object} the position of the event in canvas coordinates
|
|
* @returns {object} the tooltip position
|
|
*/
|
|
nearest: function(elements, eventPosition) {
|
|
var x = eventPosition.x;
|
|
var y = eventPosition.y;
|
|
var minDistance = Number.POSITIVE_INFINITY;
|
|
var i, len, nearestElement;
|
|
|
|
for (i = 0, len = elements.length; i < len; ++i) {
|
|
var el = elements[i];
|
|
if (el && el.hasValue()) {
|
|
var center = el.getCenterPoint();
|
|
var d = helpers$1.distanceBetweenPoints(eventPosition, center);
|
|
|
|
if (d < minDistance) {
|
|
minDistance = d;
|
|
nearestElement = el;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (nearestElement) {
|
|
var tp = nearestElement.tooltipPosition();
|
|
x = tp.x;
|
|
y = tp.y;
|
|
}
|
|
|
|
return {
|
|
x: x,
|
|
y: y
|
|
};
|
|
}
|
|
};
|
|
|
|
// Helper to push or concat based on if the 2nd parameter is an array or not
|
|
function pushOrConcat(base, toPush) {
|
|
if (toPush) {
|
|
if (helpers$1.isArray(toPush)) {
|
|
// base = base.concat(toPush);
|
|
Array.prototype.push.apply(base, toPush);
|
|
} else {
|
|
base.push(toPush);
|
|
}
|
|
}
|
|
|
|
return base;
|
|
}
|
|
|
|
/**
|
|
* Returns array of strings split by newline
|
|
* @param {string} value - The value to split by newline.
|
|
* @returns {string[]} value if newline present - Returned from String split() method
|
|
* @function
|
|
*/
|
|
function splitNewlines(str) {
|
|
if ((typeof str === 'string' || str instanceof String) && str.indexOf('\n') > -1) {
|
|
return str.split('\n');
|
|
}
|
|
return str;
|
|
}
|
|
|
|
|
|
/**
|
|
* Private helper to create a tooltip item model
|
|
* @param element - the chart element (point, arc, bar) to create the tooltip item for
|
|
* @return new tooltip item
|
|
*/
|
|
function createTooltipItem(element) {
|
|
var xScale = element._xScale;
|
|
var yScale = element._yScale || element._scale; // handle radar || polarArea charts
|
|
var index = element._index;
|
|
var datasetIndex = element._datasetIndex;
|
|
var controller = element._chart.getDatasetMeta(datasetIndex).controller;
|
|
var indexScale = controller._getIndexScale();
|
|
var valueScale = controller._getValueScale();
|
|
|
|
return {
|
|
xLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '',
|
|
yLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '',
|
|
label: indexScale ? '' + indexScale.getLabelForIndex(index, datasetIndex) : '',
|
|
value: valueScale ? '' + valueScale.getLabelForIndex(index, datasetIndex) : '',
|
|
index: index,
|
|
datasetIndex: datasetIndex,
|
|
x: element._model.x,
|
|
y: element._model.y
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Helper to get the reset model for the tooltip
|
|
* @param tooltipOpts {object} the tooltip options
|
|
*/
|
|
function getBaseModel(tooltipOpts) {
|
|
var globalDefaults = core_defaults.global;
|
|
|
|
return {
|
|
// Positioning
|
|
xPadding: tooltipOpts.xPadding,
|
|
yPadding: tooltipOpts.yPadding,
|
|
xAlign: tooltipOpts.xAlign,
|
|
yAlign: tooltipOpts.yAlign,
|
|
|
|
// Drawing direction and text direction
|
|
rtl: tooltipOpts.rtl,
|
|
textDirection: tooltipOpts.textDirection,
|
|
|
|
// Body
|
|
bodyFontColor: tooltipOpts.bodyFontColor,
|
|
_bodyFontFamily: valueOrDefault$8(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily),
|
|
_bodyFontStyle: valueOrDefault$8(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle),
|
|
_bodyAlign: tooltipOpts.bodyAlign,
|
|
bodyFontSize: valueOrDefault$8(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize),
|
|
bodySpacing: tooltipOpts.bodySpacing,
|
|
|
|
// Title
|
|
titleFontColor: tooltipOpts.titleFontColor,
|
|
_titleFontFamily: valueOrDefault$8(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily),
|
|
_titleFontStyle: valueOrDefault$8(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle),
|
|
titleFontSize: valueOrDefault$8(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize),
|
|
_titleAlign: tooltipOpts.titleAlign,
|
|
titleSpacing: tooltipOpts.titleSpacing,
|
|
titleMarginBottom: tooltipOpts.titleMarginBottom,
|
|
|
|
// Footer
|
|
footerFontColor: tooltipOpts.footerFontColor,
|
|
_footerFontFamily: valueOrDefault$8(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily),
|
|
_footerFontStyle: valueOrDefault$8(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle),
|
|
footerFontSize: valueOrDefault$8(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize),
|
|
_footerAlign: tooltipOpts.footerAlign,
|
|
footerSpacing: tooltipOpts.footerSpacing,
|
|
footerMarginTop: tooltipOpts.footerMarginTop,
|
|
|
|
// Appearance
|
|
caretSize: tooltipOpts.caretSize,
|
|
cornerRadius: tooltipOpts.cornerRadius,
|
|
backgroundColor: tooltipOpts.backgroundColor,
|
|
opacity: 0,
|
|
legendColorBackground: tooltipOpts.multiKeyBackground,
|
|
displayColors: tooltipOpts.displayColors,
|
|
borderColor: tooltipOpts.borderColor,
|
|
borderWidth: tooltipOpts.borderWidth
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get the size of the tooltip
|
|
*/
|
|
function getTooltipSize(tooltip, model) {
|
|
var ctx = tooltip._chart.ctx;
|
|
|
|
var height = model.yPadding * 2; // Tooltip Padding
|
|
var width = 0;
|
|
|
|
// Count of all lines in the body
|
|
var body = model.body;
|
|
var combinedBodyLength = body.reduce(function(count, bodyItem) {
|
|
return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length;
|
|
}, 0);
|
|
combinedBodyLength += model.beforeBody.length + model.afterBody.length;
|
|
|
|
var titleLineCount = model.title.length;
|
|
var footerLineCount = model.footer.length;
|
|
var titleFontSize = model.titleFontSize;
|
|
var bodyFontSize = model.bodyFontSize;
|
|
var footerFontSize = model.footerFontSize;
|
|
|
|
height += titleLineCount * titleFontSize; // Title Lines
|
|
height += titleLineCount ? (titleLineCount - 1) * model.titleSpacing : 0; // Title Line Spacing
|
|
height += titleLineCount ? model.titleMarginBottom : 0; // Title's bottom Margin
|
|
height += combinedBodyLength * bodyFontSize; // Body Lines
|
|
height += combinedBodyLength ? (combinedBodyLength - 1) * model.bodySpacing : 0; // Body Line Spacing
|
|
height += footerLineCount ? model.footerMarginTop : 0; // Footer Margin
|
|
height += footerLineCount * (footerFontSize); // Footer Lines
|
|
height += footerLineCount ? (footerLineCount - 1) * model.footerSpacing : 0; // Footer Line Spacing
|
|
|
|
// Title width
|
|
var widthPadding = 0;
|
|
var maxLineWidth = function(line) {
|
|
width = Math.max(width, ctx.measureText(line).width + widthPadding);
|
|
};
|
|
|
|
ctx.font = helpers$1.fontString(titleFontSize, model._titleFontStyle, model._titleFontFamily);
|
|
helpers$1.each(model.title, maxLineWidth);
|
|
|
|
// Body width
|
|
ctx.font = helpers$1.fontString(bodyFontSize, model._bodyFontStyle, model._bodyFontFamily);
|
|
helpers$1.each(model.beforeBody.concat(model.afterBody), maxLineWidth);
|
|
|
|
// Body lines may include some extra width due to the color box
|
|
widthPadding = model.displayColors ? (bodyFontSize + 2) : 0;
|
|
helpers$1.each(body, function(bodyItem) {
|
|
helpers$1.each(bodyItem.before, maxLineWidth);
|
|
helpers$1.each(bodyItem.lines, maxLineWidth);
|
|
helpers$1.each(bodyItem.after, maxLineWidth);
|
|
});
|
|
|
|
// Reset back to 0
|
|
widthPadding = 0;
|
|
|
|
// Footer width
|
|
ctx.font = helpers$1.fontString(footerFontSize, model._footerFontStyle, model._footerFontFamily);
|
|
helpers$1.each(model.footer, maxLineWidth);
|
|
|
|
// Add padding
|
|
width += 2 * model.xPadding;
|
|
|
|
return {
|
|
width: width,
|
|
height: height
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Helper to get the alignment of a tooltip given the size
|
|
*/
|
|
function determineAlignment(tooltip, size) {
|
|
var model = tooltip._model;
|
|
var chart = tooltip._chart;
|
|
var chartArea = tooltip._chart.chartArea;
|
|
var xAlign = 'center';
|
|
var yAlign = 'center';
|
|
|
|
if (model.y < size.height) {
|
|
yAlign = 'top';
|
|
} else if (model.y > (chart.height - size.height)) {
|
|
yAlign = 'bottom';
|
|
}
|
|
|
|
var lf, rf; // functions to determine left, right alignment
|
|
var olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart
|
|
var yf; // function to get the y alignment if the tooltip goes outside of the left or right edges
|
|
var midX = (chartArea.left + chartArea.right) / 2;
|
|
var midY = (chartArea.top + chartArea.bottom) / 2;
|
|
|
|
if (yAlign === 'center') {
|
|
lf = function(x) {
|
|
return x <= midX;
|
|
};
|
|
rf = function(x) {
|
|
return x > midX;
|
|
};
|
|
} else {
|
|
lf = function(x) {
|
|
return x <= (size.width / 2);
|
|
};
|
|
rf = function(x) {
|
|
return x >= (chart.width - (size.width / 2));
|
|
};
|
|
}
|
|
|
|
olf = function(x) {
|
|
return x + size.width + model.caretSize + model.caretPadding > chart.width;
|
|
};
|
|
orf = function(x) {
|
|
return x - size.width - model.caretSize - model.caretPadding < 0;
|
|
};
|
|
yf = function(y) {
|
|
return y <= midY ? 'top' : 'bottom';
|
|
};
|
|
|
|
if (lf(model.x)) {
|
|
xAlign = 'left';
|
|
|
|
// Is tooltip too wide and goes over the right side of the chart.?
|
|
if (olf(model.x)) {
|
|
xAlign = 'center';
|
|
yAlign = yf(model.y);
|
|
}
|
|
} else if (rf(model.x)) {
|
|
xAlign = 'right';
|
|
|
|
// Is tooltip too wide and goes outside left edge of canvas?
|
|
if (orf(model.x)) {
|
|
xAlign = 'center';
|
|
yAlign = yf(model.y);
|
|
}
|
|
}
|
|
|
|
var opts = tooltip._options;
|
|
return {
|
|
xAlign: opts.xAlign ? opts.xAlign : xAlign,
|
|
yAlign: opts.yAlign ? opts.yAlign : yAlign
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Helper to get the location a tooltip needs to be placed at given the initial position (via the vm) and the size and alignment
|
|
*/
|
|
function getBackgroundPoint(vm, size, alignment, chart) {
|
|
// Background Position
|
|
var x = vm.x;
|
|
var y = vm.y;
|
|
|
|
var caretSize = vm.caretSize;
|
|
var caretPadding = vm.caretPadding;
|
|
var cornerRadius = vm.cornerRadius;
|
|
var xAlign = alignment.xAlign;
|
|
var yAlign = alignment.yAlign;
|
|
var paddingAndSize = caretSize + caretPadding;
|
|
var radiusAndPadding = cornerRadius + caretPadding;
|
|
|
|
if (xAlign === 'right') {
|
|
x -= size.width;
|
|
} else if (xAlign === 'center') {
|
|
x -= (size.width / 2);
|
|
if (x + size.width > chart.width) {
|
|
x = chart.width - size.width;
|
|
}
|
|
if (x < 0) {
|
|
x = 0;
|
|
}
|
|
}
|
|
|
|
if (yAlign === 'top') {
|
|
y += paddingAndSize;
|
|
} else if (yAlign === 'bottom') {
|
|
y -= size.height + paddingAndSize;
|
|
} else {
|
|
y -= (size.height / 2);
|
|
}
|
|
|
|
if (yAlign === 'center') {
|
|
if (xAlign === 'left') {
|
|
x += paddingAndSize;
|
|
} else if (xAlign === 'right') {
|
|
x -= paddingAndSize;
|
|
}
|
|
} else if (xAlign === 'left') {
|
|
x -= radiusAndPadding;
|
|
} else if (xAlign === 'right') {
|
|
x += radiusAndPadding;
|
|
}
|
|
|
|
return {
|
|
x: x,
|
|
y: y
|
|
};
|
|
}
|
|
|
|
function getAlignedX(vm, align) {
|
|
return align === 'center'
|
|
? vm.x + vm.width / 2
|
|
: align === 'right'
|
|
? vm.x + vm.width - vm.xPadding
|
|
: vm.x + vm.xPadding;
|
|
}
|
|
|
|
/**
|
|
* Helper to build before and after body lines
|
|
*/
|
|
function getBeforeAfterBodyLines(callback) {
|
|
return pushOrConcat([], splitNewlines(callback));
|
|
}
|
|
|
|
var exports$4 = core_element.extend({
|
|
initialize: function() {
|
|
this._model = getBaseModel(this._options);
|
|
this._lastActive = [];
|
|
},
|
|
|
|
// Get the title
|
|
// Args are: (tooltipItem, data)
|
|
getTitle: function() {
|
|
var me = this;
|
|
var opts = me._options;
|
|
var callbacks = opts.callbacks;
|
|
|
|
var beforeTitle = callbacks.beforeTitle.apply(me, arguments);
|
|
var title = callbacks.title.apply(me, arguments);
|
|
var afterTitle = callbacks.afterTitle.apply(me, arguments);
|
|
|
|
var lines = [];
|
|
lines = pushOrConcat(lines, splitNewlines(beforeTitle));
|
|
lines = pushOrConcat(lines, splitNewlines(title));
|
|
lines = pushOrConcat(lines, splitNewlines(afterTitle));
|
|
|
|
return lines;
|
|
},
|
|
|
|
// Args are: (tooltipItem, data)
|
|
getBeforeBody: function() {
|
|
return getBeforeAfterBodyLines(this._options.callbacks.beforeBody.apply(this, arguments));
|
|
},
|
|
|
|
// Args are: (tooltipItem, data)
|
|
getBody: function(tooltipItems, data) {
|
|
var me = this;
|
|
var callbacks = me._options.callbacks;
|
|
var bodyItems = [];
|
|
|
|
helpers$1.each(tooltipItems, function(tooltipItem) {
|
|
var bodyItem = {
|
|
before: [],
|
|
lines: [],
|
|
after: []
|
|
};
|
|
pushOrConcat(bodyItem.before, splitNewlines(callbacks.beforeLabel.call(me, tooltipItem, data)));
|
|
pushOrConcat(bodyItem.lines, callbacks.label.call(me, tooltipItem, data));
|
|
pushOrConcat(bodyItem.after, splitNewlines(callbacks.afterLabel.call(me, tooltipItem, data)));
|
|
|
|
bodyItems.push(bodyItem);
|
|
});
|
|
|
|
return bodyItems;
|
|
},
|
|
|
|
// Args are: (tooltipItem, data)
|
|
getAfterBody: function() {
|
|
return getBeforeAfterBodyLines(this._options.callbacks.afterBody.apply(this, arguments));
|
|
},
|
|
|
|
// Get the footer and beforeFooter and afterFooter lines
|
|
// Args are: (tooltipItem, data)
|
|
getFooter: function() {
|
|
var me = this;
|
|
var callbacks = me._options.callbacks;
|
|
|
|
var beforeFooter = callbacks.beforeFooter.apply(me, arguments);
|
|
var footer = callbacks.footer.apply(me, arguments);
|
|
var afterFooter = callbacks.afterFooter.apply(me, arguments);
|
|
|
|
var lines = [];
|
|
lines = pushOrConcat(lines, splitNewlines(beforeFooter));
|
|
lines = pushOrConcat(lines, splitNewlines(footer));
|
|
lines = pushOrConcat(lines, splitNewlines(afterFooter));
|
|
|
|
return lines;
|
|
},
|
|
|
|
update: function(changed) {
|
|
var me = this;
|
|
var opts = me._options;
|
|
|
|
// Need to regenerate the model because its faster than using extend and it is necessary due to the optimization in Chart.Element.transition
|
|
// that does _view = _model if ease === 1. This causes the 2nd tooltip update to set properties in both the view and model at the same time
|
|
// which breaks any animations.
|
|
var existingModel = me._model;
|
|
var model = me._model = getBaseModel(opts);
|
|
var active = me._active;
|
|
|
|
var data = me._data;
|
|
|
|
// In the case where active.length === 0 we need to keep these at existing values for good animations
|
|
var alignment = {
|
|
xAlign: existingModel.xAlign,
|
|
yAlign: existingModel.yAlign
|
|
};
|
|
var backgroundPoint = {
|
|
x: existingModel.x,
|
|
y: existingModel.y
|
|
};
|
|
var tooltipSize = {
|
|
width: existingModel.width,
|
|
height: existingModel.height
|
|
};
|
|
var tooltipPosition = {
|
|
x: existingModel.caretX,
|
|
y: existingModel.caretY
|
|
};
|
|
|
|
var i, len;
|
|
|
|
if (active.length) {
|
|
model.opacity = 1;
|
|
|
|
var labelColors = [];
|
|
var labelTextColors = [];
|
|
tooltipPosition = positioners[opts.position].call(me, active, me._eventPosition);
|
|
|
|
var tooltipItems = [];
|
|
for (i = 0, len = active.length; i < len; ++i) {
|
|
tooltipItems.push(createTooltipItem(active[i]));
|
|
}
|
|
|
|
// If the user provided a filter function, use it to modify the tooltip items
|
|
if (opts.filter) {
|
|
tooltipItems = tooltipItems.filter(function(a) {
|
|
return opts.filter(a, data);
|
|
});
|
|
}
|
|
|
|
// If the user provided a sorting function, use it to modify the tooltip items
|
|
if (opts.itemSort) {
|
|
tooltipItems = tooltipItems.sort(function(a, b) {
|
|
return opts.itemSort(a, b, data);
|
|
});
|
|
}
|
|
|
|
// Determine colors for boxes
|
|
helpers$1.each(tooltipItems, function(tooltipItem) {
|
|
labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, me._chart));
|
|
labelTextColors.push(opts.callbacks.labelTextColor.call(me, tooltipItem, me._chart));
|
|
});
|
|
|
|
|
|
// Build the Text Lines
|
|
model.title = me.getTitle(tooltipItems, data);
|
|
model.beforeBody = me.getBeforeBody(tooltipItems, data);
|
|
model.body = me.getBody(tooltipItems, data);
|
|
model.afterBody = me.getAfterBody(tooltipItems, data);
|
|
model.footer = me.getFooter(tooltipItems, data);
|
|
|
|
// Initial positioning and colors
|
|
model.x = tooltipPosition.x;
|
|
model.y = tooltipPosition.y;
|
|
model.caretPadding = opts.caretPadding;
|
|
model.labelColors = labelColors;
|
|
model.labelTextColors = labelTextColors;
|
|
|
|
// data points
|
|
model.dataPoints = tooltipItems;
|
|
|
|
// We need to determine alignment of the tooltip
|
|
tooltipSize = getTooltipSize(this, model);
|
|
alignment = determineAlignment(this, tooltipSize);
|
|
// Final Size and Position
|
|
backgroundPoint = getBackgroundPoint(model, tooltipSize, alignment, me._chart);
|
|
} else {
|
|
model.opacity = 0;
|
|
}
|
|
|
|
model.xAlign = alignment.xAlign;
|
|
model.yAlign = alignment.yAlign;
|
|
model.x = backgroundPoint.x;
|
|
model.y = backgroundPoint.y;
|
|
model.width = tooltipSize.width;
|
|
model.height = tooltipSize.height;
|
|
|
|
// Point where the caret on the tooltip points to
|
|
model.caretX = tooltipPosition.x;
|
|
model.caretY = tooltipPosition.y;
|
|
|
|
me._model = model;
|
|
|
|
if (changed && opts.custom) {
|
|
opts.custom.call(me, model);
|
|
}
|
|
|
|
return me;
|
|
},
|
|
|
|
drawCaret: function(tooltipPoint, size) {
|
|
var ctx = this._chart.ctx;
|
|
var vm = this._view;
|
|
var caretPosition = this.getCaretPosition(tooltipPoint, size, vm);
|
|
|
|
ctx.lineTo(caretPosition.x1, caretPosition.y1);
|
|
ctx.lineTo(caretPosition.x2, caretPosition.y2);
|
|
ctx.lineTo(caretPosition.x3, caretPosition.y3);
|
|
},
|
|
getCaretPosition: function(tooltipPoint, size, vm) {
|
|
var x1, x2, x3, y1, y2, y3;
|
|
var caretSize = vm.caretSize;
|
|
var cornerRadius = vm.cornerRadius;
|
|
var xAlign = vm.xAlign;
|
|
var yAlign = vm.yAlign;
|
|
var ptX = tooltipPoint.x;
|
|
var ptY = tooltipPoint.y;
|
|
var width = size.width;
|
|
var height = size.height;
|
|
|
|
if (yAlign === 'center') {
|
|
y2 = ptY + (height / 2);
|
|
|
|
if (xAlign === 'left') {
|
|
x1 = ptX;
|
|
x2 = x1 - caretSize;
|
|
x3 = x1;
|
|
|
|
y1 = y2 + caretSize;
|
|
y3 = y2 - caretSize;
|
|
} else {
|
|
x1 = ptX + width;
|
|
x2 = x1 + caretSize;
|
|
x3 = x1;
|
|
|
|
y1 = y2 - caretSize;
|
|
y3 = y2 + caretSize;
|
|
}
|
|
} else {
|
|
if (xAlign === 'left') {
|
|
x2 = ptX + cornerRadius + (caretSize);
|
|
x1 = x2 - caretSize;
|
|
x3 = x2 + caretSize;
|
|
} else if (xAlign === 'right') {
|
|
x2 = ptX + width - cornerRadius - caretSize;
|
|
x1 = x2 - caretSize;
|
|
x3 = x2 + caretSize;
|
|
} else {
|
|
x2 = vm.caretX;
|
|
x1 = x2 - caretSize;
|
|
x3 = x2 + caretSize;
|
|
}
|
|
if (yAlign === 'top') {
|
|
y1 = ptY;
|
|
y2 = y1 - caretSize;
|
|
y3 = y1;
|
|
} else {
|
|
y1 = ptY + height;
|
|
y2 = y1 + caretSize;
|
|
y3 = y1;
|
|
// invert drawing order
|
|
var tmp = x3;
|
|
x3 = x1;
|
|
x1 = tmp;
|
|
}
|
|
}
|
|
return {x1: x1, x2: x2, x3: x3, y1: y1, y2: y2, y3: y3};
|
|
},
|
|
|
|
drawTitle: function(pt, vm, ctx) {
|
|
var title = vm.title;
|
|
var length = title.length;
|
|
var titleFontSize, titleSpacing, i;
|
|
|
|
if (length) {
|
|
var rtlHelper = getRtlHelper(vm.rtl, vm.x, vm.width);
|
|
|
|
pt.x = getAlignedX(vm, vm._titleAlign);
|
|
|
|
ctx.textAlign = rtlHelper.textAlign(vm._titleAlign);
|
|
ctx.textBaseline = 'middle';
|
|
|
|
titleFontSize = vm.titleFontSize;
|
|
titleSpacing = vm.titleSpacing;
|
|
|
|
ctx.fillStyle = vm.titleFontColor;
|
|
ctx.font = helpers$1.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily);
|
|
|
|
for (i = 0; i < length; ++i) {
|
|
ctx.fillText(title[i], rtlHelper.x(pt.x), pt.y + titleFontSize / 2);
|
|
pt.y += titleFontSize + titleSpacing; // Line Height and spacing
|
|
|
|
if (i + 1 === length) {
|
|
pt.y += vm.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
drawBody: function(pt, vm, ctx) {
|
|
var bodyFontSize = vm.bodyFontSize;
|
|
var bodySpacing = vm.bodySpacing;
|
|
var bodyAlign = vm._bodyAlign;
|
|
var body = vm.body;
|
|
var drawColorBoxes = vm.displayColors;
|
|
var xLinePadding = 0;
|
|
var colorX = drawColorBoxes ? getAlignedX(vm, 'left') : 0;
|
|
|
|
var rtlHelper = getRtlHelper(vm.rtl, vm.x, vm.width);
|
|
|
|
var fillLineOfText = function(line) {
|
|
ctx.fillText(line, rtlHelper.x(pt.x + xLinePadding), pt.y + bodyFontSize / 2);
|
|
pt.y += bodyFontSize + bodySpacing;
|
|
};
|
|
|
|
var bodyItem, textColor, labelColors, lines, i, j, ilen, jlen;
|
|
var bodyAlignForCalculation = rtlHelper.textAlign(bodyAlign);
|
|
|
|
ctx.textAlign = bodyAlign;
|
|
ctx.textBaseline = 'middle';
|
|
ctx.font = helpers$1.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
|
|
|
|
pt.x = getAlignedX(vm, bodyAlignForCalculation);
|
|
|
|
// Before body lines
|
|
ctx.fillStyle = vm.bodyFontColor;
|
|
helpers$1.each(vm.beforeBody, fillLineOfText);
|
|
|
|
xLinePadding = drawColorBoxes && bodyAlignForCalculation !== 'right'
|
|
? bodyAlign === 'center' ? (bodyFontSize / 2 + 1) : (bodyFontSize + 2)
|
|
: 0;
|
|
|
|
// Draw body lines now
|
|
for (i = 0, ilen = body.length; i < ilen; ++i) {
|
|
bodyItem = body[i];
|
|
textColor = vm.labelTextColors[i];
|
|
labelColors = vm.labelColors[i];
|
|
|
|
ctx.fillStyle = textColor;
|
|
helpers$1.each(bodyItem.before, fillLineOfText);
|
|
|
|
lines = bodyItem.lines;
|
|
for (j = 0, jlen = lines.length; j < jlen; ++j) {
|
|
// Draw Legend-like boxes if needed
|
|
if (drawColorBoxes) {
|
|
var rtlColorX = rtlHelper.x(colorX);
|
|
|
|
// Fill a white rect so that colours merge nicely if the opacity is < 1
|
|
ctx.fillStyle = vm.legendColorBackground;
|
|
ctx.fillRect(rtlHelper.leftForLtr(rtlColorX, bodyFontSize), pt.y, bodyFontSize, bodyFontSize);
|
|
|
|
// Border
|
|
ctx.lineWidth = 1;
|
|
ctx.strokeStyle = labelColors.borderColor;
|
|
ctx.strokeRect(rtlHelper.leftForLtr(rtlColorX, bodyFontSize), pt.y, bodyFontSize, bodyFontSize);
|
|
|
|
// Inner square
|
|
ctx.fillStyle = labelColors.backgroundColor;
|
|
ctx.fillRect(rtlHelper.leftForLtr(rtlHelper.xPlus(rtlColorX, 1), bodyFontSize - 2), pt.y + 1, bodyFontSize - 2, bodyFontSize - 2);
|
|
ctx.fillStyle = textColor;
|
|
}
|
|
|
|
fillLineOfText(lines[j]);
|
|
}
|
|
|
|
helpers$1.each(bodyItem.after, fillLineOfText);
|
|
}
|
|
|
|
// Reset back to 0 for after body
|
|
xLinePadding = 0;
|
|
|
|
// After body lines
|
|
helpers$1.each(vm.afterBody, fillLineOfText);
|
|
pt.y -= bodySpacing; // Remove last body spacing
|
|
},
|
|
|
|
drawFooter: function(pt, vm, ctx) {
|
|
var footer = vm.footer;
|
|
var length = footer.length;
|
|
var footerFontSize, i;
|
|
|
|
if (length) {
|
|
var rtlHelper = getRtlHelper(vm.rtl, vm.x, vm.width);
|
|
|
|
pt.x = getAlignedX(vm, vm._footerAlign);
|
|
pt.y += vm.footerMarginTop;
|
|
|
|
ctx.textAlign = rtlHelper.textAlign(vm._footerAlign);
|
|
ctx.textBaseline = 'middle';
|
|
|
|
footerFontSize = vm.footerFontSize;
|
|
|
|
ctx.fillStyle = vm.footerFontColor;
|
|
ctx.font = helpers$1.fontString(footerFontSize, vm._footerFontStyle, vm._footerFontFamily);
|
|
|
|
for (i = 0; i < length; ++i) {
|
|
ctx.fillText(footer[i], rtlHelper.x(pt.x), pt.y + footerFontSize / 2);
|
|
pt.y += footerFontSize + vm.footerSpacing;
|
|
}
|
|
}
|
|
},
|
|
|
|
drawBackground: function(pt, vm, ctx, tooltipSize) {
|
|
ctx.fillStyle = vm.backgroundColor;
|
|
ctx.strokeStyle = vm.borderColor;
|
|
ctx.lineWidth = vm.borderWidth;
|
|
var xAlign = vm.xAlign;
|
|
var yAlign = vm.yAlign;
|
|
var x = pt.x;
|
|
var y = pt.y;
|
|
var width = tooltipSize.width;
|
|
var height = tooltipSize.height;
|
|
var radius = vm.cornerRadius;
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(x + radius, y);
|
|
if (yAlign === 'top') {
|
|
this.drawCaret(pt, tooltipSize);
|
|
}
|
|
ctx.lineTo(x + width - radius, y);
|
|
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
|
|
if (yAlign === 'center' && xAlign === 'right') {
|
|
this.drawCaret(pt, tooltipSize);
|
|
}
|
|
ctx.lineTo(x + width, y + height - radius);
|
|
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
|
|
if (yAlign === 'bottom') {
|
|
this.drawCaret(pt, tooltipSize);
|
|
}
|
|
ctx.lineTo(x + radius, y + height);
|
|
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
|
|
if (yAlign === 'center' && xAlign === 'left') {
|
|
this.drawCaret(pt, tooltipSize);
|
|
}
|
|
ctx.lineTo(x, y + radius);
|
|
ctx.quadraticCurveTo(x, y, x + radius, y);
|
|
ctx.closePath();
|
|
|
|
ctx.fill();
|
|
|
|
if (vm.borderWidth > 0) {
|
|
ctx.stroke();
|
|
}
|
|
},
|
|
|
|
draw: function() {
|
|
var ctx = this._chart.ctx;
|
|
var vm = this._view;
|
|
|
|
if (vm.opacity === 0) {
|
|
return;
|
|
}
|
|
|
|
var tooltipSize = {
|
|
width: vm.width,
|
|
height: vm.height
|
|
};
|
|
var pt = {
|
|
x: vm.x,
|
|
y: vm.y
|
|
};
|
|
|
|
// IE11/Edge does not like very small opacities, so snap to 0
|
|
var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity;
|
|
|
|
// Truthy/falsey value for empty tooltip
|
|
var hasTooltipContent = vm.title.length || vm.beforeBody.length || vm.body.length || vm.afterBody.length || vm.footer.length;
|
|
|
|
if (this._options.enabled && hasTooltipContent) {
|
|
ctx.save();
|
|
ctx.globalAlpha = opacity;
|
|
|
|
// Draw Background
|
|
this.drawBackground(pt, vm, ctx, tooltipSize);
|
|
|
|
// Draw Title, Body, and Footer
|
|
pt.y += vm.yPadding;
|
|
|
|
helpers$1.rtl.overrideTextDirection(ctx, vm.textDirection);
|
|
|
|
// Titles
|
|
this.drawTitle(pt, vm, ctx);
|
|
|
|
// Body
|
|
this.drawBody(pt, vm, ctx);
|
|
|
|
// Footer
|
|
this.drawFooter(pt, vm, ctx);
|
|
|
|
helpers$1.rtl.restoreTextDirection(ctx, vm.textDirection);
|
|
|
|
ctx.restore();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Handle an event
|
|
* @private
|
|
* @param {IEvent} event - The event to handle
|
|
* @returns {boolean} true if the tooltip changed
|
|
*/
|
|
handleEvent: function(e) {
|
|
var me = this;
|
|
var options = me._options;
|
|
var changed = false;
|
|
|
|
me._lastActive = me._lastActive || [];
|
|
|
|
// Find Active Elements for tooltips
|
|
if (e.type === 'mouseout') {
|
|
me._active = [];
|
|
} else {
|
|
me._active = me._chart.getElementsAtEventForMode(e, options.mode, options);
|
|
if (options.reverse) {
|
|
me._active.reverse();
|
|
}
|
|
}
|
|
|
|
// Remember Last Actives
|
|
changed = !helpers$1.arrayEquals(me._active, me._lastActive);
|
|
|
|
// Only handle target event on tooltip change
|
|
if (changed) {
|
|
me._lastActive = me._active;
|
|
|
|
if (options.enabled || options.custom) {
|
|
me._eventPosition = {
|
|
x: e.x,
|
|
y: e.y
|
|
};
|
|
|
|
me.update(true);
|
|
me.pivot();
|
|
}
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @namespace Chart.Tooltip.positioners
|
|
*/
|
|
var positioners_1 = positioners;
|
|
|
|
var core_tooltip = exports$4;
|
|
core_tooltip.positioners = positioners_1;
|
|
|
|
var valueOrDefault$9 = helpers$1.valueOrDefault;
|
|
|
|
core_defaults._set('global', {
|
|
elements: {},
|
|
events: [
|
|
'mousemove',
|
|
'mouseout',
|
|
'click',
|
|
'touchstart',
|
|
'touchmove'
|
|
],
|
|
hover: {
|
|
onHover: null,
|
|
mode: 'nearest',
|
|
intersect: true,
|
|
animationDuration: 400
|
|
},
|
|
onClick: null,
|
|
maintainAspectRatio: true,
|
|
responsive: true,
|
|
responsiveAnimationDuration: 0
|
|
});
|
|
|
|
/**
|
|
* Recursively merge the given config objects representing the `scales` option
|
|
* by incorporating scale defaults in `xAxes` and `yAxes` array items, then
|
|
* returns a deep copy of the result, thus doesn't alter inputs.
|
|
*/
|
|
function mergeScaleConfig(/* config objects ... */) {
|
|
return helpers$1.merge({}, [].slice.call(arguments), {
|
|
merger: function(key, target, source, options) {
|
|
if (key === 'xAxes' || key === 'yAxes') {
|
|
var slen = source[key].length;
|
|
var i, type, scale;
|
|
|
|
if (!target[key]) {
|
|
target[key] = [];
|
|
}
|
|
|
|
for (i = 0; i < slen; ++i) {
|
|
scale = source[key][i];
|
|
type = valueOrDefault$9(scale.type, key === 'xAxes' ? 'category' : 'linear');
|
|
|
|
if (i >= target[key].length) {
|
|
target[key].push({});
|
|
}
|
|
|
|
if (!target[key][i].type || (scale.type && scale.type !== target[key][i].type)) {
|
|
// new/untyped scale or type changed: let's apply the new defaults
|
|
// then merge source scale to correctly overwrite the defaults.
|
|
helpers$1.merge(target[key][i], [core_scaleService.getScaleDefaults(type), scale]);
|
|
} else {
|
|
// scales type are the same
|
|
helpers$1.merge(target[key][i], scale);
|
|
}
|
|
}
|
|
} else {
|
|
helpers$1._merger(key, target, source, options);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Recursively merge the given config objects as the root options by handling
|
|
* default scale options for the `scales` and `scale` properties, then returns
|
|
* a deep copy of the result, thus doesn't alter inputs.
|
|
*/
|
|
function mergeConfig(/* config objects ... */) {
|
|
return helpers$1.merge({}, [].slice.call(arguments), {
|
|
merger: function(key, target, source, options) {
|
|
var tval = target[key] || {};
|
|
var sval = source[key];
|
|
|
|
if (key === 'scales') {
|
|
// scale config merging is complex. Add our own function here for that
|
|
target[key] = mergeScaleConfig(tval, sval);
|
|
} else if (key === 'scale') {
|
|
// used in polar area & radar charts since there is only one scale
|
|
target[key] = helpers$1.merge(tval, [core_scaleService.getScaleDefaults(sval.type), sval]);
|
|
} else {
|
|
helpers$1._merger(key, target, source, options);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function initConfig(config) {
|
|
config = config || {};
|
|
|
|
// Do NOT use mergeConfig for the data object because this method merges arrays
|
|
// and so would change references to labels and datasets, preventing data updates.
|
|
var data = config.data = config.data || {};
|
|
data.datasets = data.datasets || [];
|
|
data.labels = data.labels || [];
|
|
|
|
config.options = mergeConfig(
|
|
core_defaults.global,
|
|
core_defaults[config.type],
|
|
config.options || {});
|
|
|
|
return config;
|
|
}
|
|
|
|
function updateConfig(chart) {
|
|
var newOptions = chart.options;
|
|
|
|
helpers$1.each(chart.scales, function(scale) {
|
|
core_layouts.removeBox(chart, scale);
|
|
});
|
|
|
|
newOptions = mergeConfig(
|
|
core_defaults.global,
|
|
core_defaults[chart.config.type],
|
|
newOptions);
|
|
|
|
chart.options = chart.config.options = newOptions;
|
|
chart.ensureScalesHaveIDs();
|
|
chart.buildOrUpdateScales();
|
|
|
|
// Tooltip
|
|
chart.tooltip._options = newOptions.tooltips;
|
|
chart.tooltip.initialize();
|
|
}
|
|
|
|
function nextAvailableScaleId(axesOpts, prefix, index) {
|
|
var id;
|
|
var hasId = function(obj) {
|
|
return obj.id === id;
|
|
};
|
|
|
|
do {
|
|
id = prefix + index++;
|
|
} while (helpers$1.findIndex(axesOpts, hasId) >= 0);
|
|
|
|
return id;
|
|
}
|
|
|
|
function positionIsHorizontal(position) {
|
|
return position === 'top' || position === 'bottom';
|
|
}
|
|
|
|
function compare2Level(l1, l2) {
|
|
return function(a, b) {
|
|
return a[l1] === b[l1]
|
|
? a[l2] - b[l2]
|
|
: a[l1] - b[l1];
|
|
};
|
|
}
|
|
|
|
var Chart = function(item, config) {
|
|
this.construct(item, config);
|
|
return this;
|
|
};
|
|
|
|
helpers$1.extend(Chart.prototype, /** @lends Chart */ {
|
|
/**
|
|
* @private
|
|
*/
|
|
construct: function(item, config) {
|
|
var me = this;
|
|
|
|
config = initConfig(config);
|
|
|
|
var context = platform.acquireContext(item, config);
|
|
var canvas = context && context.canvas;
|
|
var height = canvas && canvas.height;
|
|
var width = canvas && canvas.width;
|
|
|
|
me.id = helpers$1.uid();
|
|
me.ctx = context;
|
|
me.canvas = canvas;
|
|
me.config = config;
|
|
me.width = width;
|
|
me.height = height;
|
|
me.aspectRatio = height ? width / height : null;
|
|
me.options = config.options;
|
|
me._bufferedRender = false;
|
|
me._layers = [];
|
|
|
|
/**
|
|
* Provided for backward compatibility, Chart and Chart.Controller have been merged,
|
|
* the "instance" still need to be defined since it might be called from plugins.
|
|
* @prop Chart#chart
|
|
* @deprecated since version 2.6.0
|
|
* @todo remove at version 3
|
|
* @private
|
|
*/
|
|
me.chart = me;
|
|
me.controller = me; // chart.chart.controller #inception
|
|
|
|
// Add the chart instance to the global namespace
|
|
Chart.instances[me.id] = me;
|
|
|
|
// Define alias to the config data: `chart.data === chart.config.data`
|
|
Object.defineProperty(me, 'data', {
|
|
get: function() {
|
|
return me.config.data;
|
|
},
|
|
set: function(value) {
|
|
me.config.data = value;
|
|
}
|
|
});
|
|
|
|
if (!context || !canvas) {
|
|
// The given item is not a compatible context2d element, let's return before finalizing
|
|
// the chart initialization but after setting basic chart / controller properties that
|
|
// can help to figure out that the chart is not valid (e.g chart.canvas !== null);
|
|
// https://github.com/chartjs/Chart.js/issues/2807
|
|
console.error("Failed to create chart: can't acquire context from the given item");
|
|
return;
|
|
}
|
|
|
|
me.initialize();
|
|
me.update();
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
initialize: function() {
|
|
var me = this;
|
|
|
|
// Before init plugin notification
|
|
core_plugins.notify(me, 'beforeInit');
|
|
|
|
helpers$1.retinaScale(me, me.options.devicePixelRatio);
|
|
|
|
me.bindEvents();
|
|
|
|
if (me.options.responsive) {
|
|
// Initial resize before chart draws (must be silent to preserve initial animations).
|
|
me.resize(true);
|
|
}
|
|
|
|
me.initToolTip();
|
|
|
|
// After init plugin notification
|
|
core_plugins.notify(me, 'afterInit');
|
|
|
|
return me;
|
|
},
|
|
|
|
clear: function() {
|
|
helpers$1.canvas.clear(this);
|
|
return this;
|
|
},
|
|
|
|
stop: function() {
|
|
// Stops any current animation loop occurring
|
|
core_animations.cancelAnimation(this);
|
|
return this;
|
|
},
|
|
|
|
resize: function(silent) {
|
|
var me = this;
|
|
var options = me.options;
|
|
var canvas = me.canvas;
|
|
var aspectRatio = (options.maintainAspectRatio && me.aspectRatio) || null;
|
|
|
|
// the canvas render width and height will be casted to integers so make sure that
|
|
// the canvas display style uses the same integer values to avoid blurring effect.
|
|
|
|
// Set to 0 instead of canvas.size because the size defaults to 300x150 if the element is collapsed
|
|
var newWidth = Math.max(0, Math.floor(helpers$1.getMaximumWidth(canvas)));
|
|
var newHeight = Math.max(0, Math.floor(aspectRatio ? newWidth / aspectRatio : helpers$1.getMaximumHeight(canvas)));
|
|
|
|
if (me.width === newWidth && me.height === newHeight) {
|
|
return;
|
|
}
|
|
|
|
canvas.width = me.width = newWidth;
|
|
canvas.height = me.height = newHeight;
|
|
canvas.style.width = newWidth + 'px';
|
|
canvas.style.height = newHeight + 'px';
|
|
|
|
helpers$1.retinaScale(me, options.devicePixelRatio);
|
|
|
|
if (!silent) {
|
|
// Notify any plugins about the resize
|
|
var newSize = {width: newWidth, height: newHeight};
|
|
core_plugins.notify(me, 'resize', [newSize]);
|
|
|
|
// Notify of resize
|
|
if (options.onResize) {
|
|
options.onResize(me, newSize);
|
|
}
|
|
|
|
me.stop();
|
|
me.update({
|
|
duration: options.responsiveAnimationDuration
|
|
});
|
|
}
|
|
},
|
|
|
|
ensureScalesHaveIDs: function() {
|
|
var options = this.options;
|
|
var scalesOptions = options.scales || {};
|
|
var scaleOptions = options.scale;
|
|
|
|
helpers$1.each(scalesOptions.xAxes, function(xAxisOptions, index) {
|
|
if (!xAxisOptions.id) {
|
|
xAxisOptions.id = nextAvailableScaleId(scalesOptions.xAxes, 'x-axis-', index);
|
|
}
|
|
});
|
|
|
|
helpers$1.each(scalesOptions.yAxes, function(yAxisOptions, index) {
|
|
if (!yAxisOptions.id) {
|
|
yAxisOptions.id = nextAvailableScaleId(scalesOptions.yAxes, 'y-axis-', index);
|
|
}
|
|
});
|
|
|
|
if (scaleOptions) {
|
|
scaleOptions.id = scaleOptions.id || 'scale';
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Builds a map of scale ID to scale object for future lookup.
|
|
*/
|
|
buildOrUpdateScales: function() {
|
|
var me = this;
|
|
var options = me.options;
|
|
var scales = me.scales || {};
|
|
var items = [];
|
|
var updated = Object.keys(scales).reduce(function(obj, id) {
|
|
obj[id] = false;
|
|
return obj;
|
|
}, {});
|
|
|
|
if (options.scales) {
|
|
items = items.concat(
|
|
(options.scales.xAxes || []).map(function(xAxisOptions) {
|
|
return {options: xAxisOptions, dtype: 'category', dposition: 'bottom'};
|
|
}),
|
|
(options.scales.yAxes || []).map(function(yAxisOptions) {
|
|
return {options: yAxisOptions, dtype: 'linear', dposition: 'left'};
|
|
})
|
|
);
|
|
}
|
|
|
|
if (options.scale) {
|
|
items.push({
|
|
options: options.scale,
|
|
dtype: 'radialLinear',
|
|
isDefault: true,
|
|
dposition: 'chartArea'
|
|
});
|
|
}
|
|
|
|
helpers$1.each(items, function(item) {
|
|
var scaleOptions = item.options;
|
|
var id = scaleOptions.id;
|
|
var scaleType = valueOrDefault$9(scaleOptions.type, item.dtype);
|
|
|
|
if (positionIsHorizontal(scaleOptions.position) !== positionIsHorizontal(item.dposition)) {
|
|
scaleOptions.position = item.dposition;
|
|
}
|
|
|
|
updated[id] = true;
|
|
var scale = null;
|
|
if (id in scales && scales[id].type === scaleType) {
|
|
scale = scales[id];
|
|
scale.options = scaleOptions;
|
|
scale.ctx = me.ctx;
|
|
scale.chart = me;
|
|
} else {
|
|
var scaleClass = core_scaleService.getScaleConstructor(scaleType);
|
|
if (!scaleClass) {
|
|
return;
|
|
}
|
|
scale = new scaleClass({
|
|
id: id,
|
|
type: scaleType,
|
|
options: scaleOptions,
|
|
ctx: me.ctx,
|
|
chart: me
|
|
});
|
|
scales[scale.id] = scale;
|
|
}
|
|
|
|
scale.mergeTicksOptions();
|
|
|
|
// TODO(SB): I think we should be able to remove this custom case (options.scale)
|
|
// and consider it as a regular scale part of the "scales"" map only! This would
|
|
// make the logic easier and remove some useless? custom code.
|
|
if (item.isDefault) {
|
|
me.scale = scale;
|
|
}
|
|
});
|
|
// clear up discarded scales
|
|
helpers$1.each(updated, function(hasUpdated, id) {
|
|
if (!hasUpdated) {
|
|
delete scales[id];
|
|
}
|
|
});
|
|
|
|
me.scales = scales;
|
|
|
|
core_scaleService.addScalesToLayout(this);
|
|
},
|
|
|
|
buildOrUpdateControllers: function() {
|
|
var me = this;
|
|
var newControllers = [];
|
|
var datasets = me.data.datasets;
|
|
var i, ilen;
|
|
|
|
for (i = 0, ilen = datasets.length; i < ilen; i++) {
|
|
var dataset = datasets[i];
|
|
var meta = me.getDatasetMeta(i);
|
|
var type = dataset.type || me.config.type;
|
|
|
|
if (meta.type && meta.type !== type) {
|
|
me.destroyDatasetMeta(i);
|
|
meta = me.getDatasetMeta(i);
|
|
}
|
|
meta.type = type;
|
|
meta.order = dataset.order || 0;
|
|
meta.index = i;
|
|
|
|
if (meta.controller) {
|
|
meta.controller.updateIndex(i);
|
|
meta.controller.linkScales();
|
|
} else {
|
|
var ControllerClass = controllers[meta.type];
|
|
if (ControllerClass === undefined) {
|
|
throw new Error('"' + meta.type + '" is not a chart type.');
|
|
}
|
|
|
|
meta.controller = new ControllerClass(me, i);
|
|
newControllers.push(meta.controller);
|
|
}
|
|
}
|
|
|
|
return newControllers;
|
|
},
|
|
|
|
/**
|
|
* Reset the elements of all datasets
|
|
* @private
|
|
*/
|
|
resetElements: function() {
|
|
var me = this;
|
|
helpers$1.each(me.data.datasets, function(dataset, datasetIndex) {
|
|
me.getDatasetMeta(datasetIndex).controller.reset();
|
|
}, me);
|
|
},
|
|
|
|
/**
|
|
* Resets the chart back to it's state before the initial animation
|
|
*/
|
|
reset: function() {
|
|
this.resetElements();
|
|
this.tooltip.initialize();
|
|
},
|
|
|
|
update: function(config) {
|
|
var me = this;
|
|
var i, ilen;
|
|
|
|
if (!config || typeof config !== 'object') {
|
|
// backwards compatibility
|
|
config = {
|
|
duration: config,
|
|
lazy: arguments[1]
|
|
};
|
|
}
|
|
|
|
updateConfig(me);
|
|
|
|
// plugins options references might have change, let's invalidate the cache
|
|
// https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167
|
|
core_plugins._invalidate(me);
|
|
|
|
if (core_plugins.notify(me, 'beforeUpdate') === false) {
|
|
return;
|
|
}
|
|
|
|
// In case the entire data object changed
|
|
me.tooltip._data = me.data;
|
|
|
|
// Make sure dataset controllers are updated and new controllers are reset
|
|
var newControllers = me.buildOrUpdateControllers();
|
|
|
|
// Make sure all dataset controllers have correct meta data counts
|
|
for (i = 0, ilen = me.data.datasets.length; i < ilen; i++) {
|
|
me.getDatasetMeta(i).controller.buildOrUpdateElements();
|
|
}
|
|
|
|
me.updateLayout();
|
|
|
|
// Can only reset the new controllers after the scales have been updated
|
|
if (me.options.animation && me.options.animation.duration) {
|
|
helpers$1.each(newControllers, function(controller) {
|
|
controller.reset();
|
|
});
|
|
}
|
|
|
|
me.updateDatasets();
|
|
|
|
// Need to reset tooltip in case it is displayed with elements that are removed
|
|
// after update.
|
|
me.tooltip.initialize();
|
|
|
|
// Last active contains items that were previously in the tooltip.
|
|
// When we reset the tooltip, we need to clear it
|
|
me.lastActive = [];
|
|
|
|
// Do this before render so that any plugins that need final scale updates can use it
|
|
core_plugins.notify(me, 'afterUpdate');
|
|
|
|
me._layers.sort(compare2Level('z', '_idx'));
|
|
|
|
if (me._bufferedRender) {
|
|
me._bufferedRequest = {
|
|
duration: config.duration,
|
|
easing: config.easing,
|
|
lazy: config.lazy
|
|
};
|
|
} else {
|
|
me.render(config);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Updates the chart layout unless a plugin returns `false` to the `beforeLayout`
|
|
* hook, in which case, plugins will not be called on `afterLayout`.
|
|
* @private
|
|
*/
|
|
updateLayout: function() {
|
|
var me = this;
|
|
|
|
if (core_plugins.notify(me, 'beforeLayout') === false) {
|
|
return;
|
|
}
|
|
|
|
core_layouts.update(this, this.width, this.height);
|
|
|
|
me._layers = [];
|
|
helpers$1.each(me.boxes, function(box) {
|
|
// _configure is called twice, once in core.scale.update and once here.
|
|
// Here the boxes are fully updated and at their final positions.
|
|
if (box._configure) {
|
|
box._configure();
|
|
}
|
|
me._layers.push.apply(me._layers, box._layers());
|
|
}, me);
|
|
|
|
me._layers.forEach(function(item, index) {
|
|
item._idx = index;
|
|
});
|
|
|
|
/**
|
|
* Provided for backward compatibility, use `afterLayout` instead.
|
|
* @method IPlugin#afterScaleUpdate
|
|
* @deprecated since version 2.5.0
|
|
* @todo remove at version 3
|
|
* @private
|
|
*/
|
|
core_plugins.notify(me, 'afterScaleUpdate');
|
|
core_plugins.notify(me, 'afterLayout');
|
|
},
|
|
|
|
/**
|
|
* Updates all datasets unless a plugin returns `false` to the `beforeDatasetsUpdate`
|
|
* hook, in which case, plugins will not be called on `afterDatasetsUpdate`.
|
|
* @private
|
|
*/
|
|
updateDatasets: function() {
|
|
var me = this;
|
|
|
|
if (core_plugins.notify(me, 'beforeDatasetsUpdate') === false) {
|
|
return;
|
|
}
|
|
|
|
for (var i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
|
|
me.updateDataset(i);
|
|
}
|
|
|
|
core_plugins.notify(me, 'afterDatasetsUpdate');
|
|
},
|
|
|
|
/**
|
|
* Updates dataset at index unless a plugin returns `false` to the `beforeDatasetUpdate`
|
|
* hook, in which case, plugins will not be called on `afterDatasetUpdate`.
|
|
* @private
|
|
*/
|
|
updateDataset: function(index) {
|
|
var me = this;
|
|
var meta = me.getDatasetMeta(index);
|
|
var args = {
|
|
meta: meta,
|
|
index: index
|
|
};
|
|
|
|
if (core_plugins.notify(me, 'beforeDatasetUpdate', [args]) === false) {
|
|
return;
|
|
}
|
|
|
|
meta.controller._update();
|
|
|
|
core_plugins.notify(me, 'afterDatasetUpdate', [args]);
|
|
},
|
|
|
|
render: function(config) {
|
|
var me = this;
|
|
|
|
if (!config || typeof config !== 'object') {
|
|
// backwards compatibility
|
|
config = {
|
|
duration: config,
|
|
lazy: arguments[1]
|
|
};
|
|
}
|
|
|
|
var animationOptions = me.options.animation;
|
|
var duration = valueOrDefault$9(config.duration, animationOptions && animationOptions.duration);
|
|
var lazy = config.lazy;
|
|
|
|
if (core_plugins.notify(me, 'beforeRender') === false) {
|
|
return;
|
|
}
|
|
|
|
var onComplete = function(animation) {
|
|
core_plugins.notify(me, 'afterRender');
|
|
helpers$1.callback(animationOptions && animationOptions.onComplete, [animation], me);
|
|
};
|
|
|
|
if (animationOptions && duration) {
|
|
var animation = new core_animation({
|
|
numSteps: duration / 16.66, // 60 fps
|
|
easing: config.easing || animationOptions.easing,
|
|
|
|
render: function(chart, animationObject) {
|
|
var easingFunction = helpers$1.easing.effects[animationObject.easing];
|
|
var currentStep = animationObject.currentStep;
|
|
var stepDecimal = currentStep / animationObject.numSteps;
|
|
|
|
chart.draw(easingFunction(stepDecimal), stepDecimal, currentStep);
|
|
},
|
|
|
|
onAnimationProgress: animationOptions.onProgress,
|
|
onAnimationComplete: onComplete
|
|
});
|
|
|
|
core_animations.addAnimation(me, animation, duration, lazy);
|
|
} else {
|
|
me.draw();
|
|
|
|
// See https://github.com/chartjs/Chart.js/issues/3781
|
|
onComplete(new core_animation({numSteps: 0, chart: me}));
|
|
}
|
|
|
|
return me;
|
|
},
|
|
|
|
draw: function(easingValue) {
|
|
var me = this;
|
|
var i, layers;
|
|
|
|
me.clear();
|
|
|
|
if (helpers$1.isNullOrUndef(easingValue)) {
|
|
easingValue = 1;
|
|
}
|
|
|
|
me.transition(easingValue);
|
|
|
|
if (me.width <= 0 || me.height <= 0) {
|
|
return;
|
|
}
|
|
|
|
if (core_plugins.notify(me, 'beforeDraw', [easingValue]) === false) {
|
|
return;
|
|
}
|
|
|
|
// Because of plugin hooks (before/afterDatasetsDraw), datasets can't
|
|
// currently be part of layers. Instead, we draw
|
|
// layers <= 0 before(default, backward compat), and the rest after
|
|
layers = me._layers;
|
|
for (i = 0; i < layers.length && layers[i].z <= 0; ++i) {
|
|
layers[i].draw(me.chartArea);
|
|
}
|
|
|
|
me.drawDatasets(easingValue);
|
|
|
|
// Rest of layers
|
|
for (; i < layers.length; ++i) {
|
|
layers[i].draw(me.chartArea);
|
|
}
|
|
|
|
me._drawTooltip(easingValue);
|
|
|
|
core_plugins.notify(me, 'afterDraw', [easingValue]);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
transition: function(easingValue) {
|
|
var me = this;
|
|
|
|
for (var i = 0, ilen = (me.data.datasets || []).length; i < ilen; ++i) {
|
|
if (me.isDatasetVisible(i)) {
|
|
me.getDatasetMeta(i).controller.transition(easingValue);
|
|
}
|
|
}
|
|
|
|
me.tooltip.transition(easingValue);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_getSortedDatasetMetas: function(filterVisible) {
|
|
var me = this;
|
|
var datasets = me.data.datasets || [];
|
|
var result = [];
|
|
var i, ilen;
|
|
|
|
for (i = 0, ilen = datasets.length; i < ilen; ++i) {
|
|
if (!filterVisible || me.isDatasetVisible(i)) {
|
|
result.push(me.getDatasetMeta(i));
|
|
}
|
|
}
|
|
|
|
result.sort(compare2Level('order', 'index'));
|
|
|
|
return result;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_getSortedVisibleDatasetMetas: function() {
|
|
return this._getSortedDatasetMetas(true);
|
|
},
|
|
|
|
/**
|
|
* Draws all datasets unless a plugin returns `false` to the `beforeDatasetsDraw`
|
|
* hook, in which case, plugins will not be called on `afterDatasetsDraw`.
|
|
* @private
|
|
*/
|
|
drawDatasets: function(easingValue) {
|
|
var me = this;
|
|
var metasets, i;
|
|
|
|
if (core_plugins.notify(me, 'beforeDatasetsDraw', [easingValue]) === false) {
|
|
return;
|
|
}
|
|
|
|
metasets = me._getSortedVisibleDatasetMetas();
|
|
for (i = metasets.length - 1; i >= 0; --i) {
|
|
me.drawDataset(metasets[i], easingValue);
|
|
}
|
|
|
|
core_plugins.notify(me, 'afterDatasetsDraw', [easingValue]);
|
|
},
|
|
|
|
/**
|
|
* Draws dataset at index unless a plugin returns `false` to the `beforeDatasetDraw`
|
|
* hook, in which case, plugins will not be called on `afterDatasetDraw`.
|
|
* @private
|
|
*/
|
|
drawDataset: function(meta, easingValue) {
|
|
var me = this;
|
|
var args = {
|
|
meta: meta,
|
|
index: meta.index,
|
|
easingValue: easingValue
|
|
};
|
|
|
|
if (core_plugins.notify(me, 'beforeDatasetDraw', [args]) === false) {
|
|
return;
|
|
}
|
|
|
|
meta.controller.draw(easingValue);
|
|
|
|
core_plugins.notify(me, 'afterDatasetDraw', [args]);
|
|
},
|
|
|
|
/**
|
|
* Draws tooltip unless a plugin returns `false` to the `beforeTooltipDraw`
|
|
* hook, in which case, plugins will not be called on `afterTooltipDraw`.
|
|
* @private
|
|
*/
|
|
_drawTooltip: function(easingValue) {
|
|
var me = this;
|
|
var tooltip = me.tooltip;
|
|
var args = {
|
|
tooltip: tooltip,
|
|
easingValue: easingValue
|
|
};
|
|
|
|
if (core_plugins.notify(me, 'beforeTooltipDraw', [args]) === false) {
|
|
return;
|
|
}
|
|
|
|
tooltip.draw();
|
|
|
|
core_plugins.notify(me, 'afterTooltipDraw', [args]);
|
|
},
|
|
|
|
/**
|
|
* Get the single element that was clicked on
|
|
* @return An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw
|
|
*/
|
|
getElementAtEvent: function(e) {
|
|
return core_interaction.modes.single(this, e);
|
|
},
|
|
|
|
getElementsAtEvent: function(e) {
|
|
return core_interaction.modes.label(this, e, {intersect: true});
|
|
},
|
|
|
|
getElementsAtXAxis: function(e) {
|
|
return core_interaction.modes['x-axis'](this, e, {intersect: true});
|
|
},
|
|
|
|
getElementsAtEventForMode: function(e, mode, options) {
|
|
var method = core_interaction.modes[mode];
|
|
if (typeof method === 'function') {
|
|
return method(this, e, options);
|
|
}
|
|
|
|
return [];
|
|
},
|
|
|
|
getDatasetAtEvent: function(e) {
|
|
return core_interaction.modes.dataset(this, e, {intersect: true});
|
|
},
|
|
|
|
getDatasetMeta: function(datasetIndex) {
|
|
var me = this;
|
|
var dataset = me.data.datasets[datasetIndex];
|
|
if (!dataset._meta) {
|
|
dataset._meta = {};
|
|
}
|
|
|
|
var meta = dataset._meta[me.id];
|
|
if (!meta) {
|
|
meta = dataset._meta[me.id] = {
|
|
type: null,
|
|
data: [],
|
|
dataset: null,
|
|
controller: null,
|
|
hidden: null, // See isDatasetVisible() comment
|
|
xAxisID: null,
|
|
yAxisID: null,
|
|
order: dataset.order || 0,
|
|
index: datasetIndex
|
|
};
|
|
}
|
|
|
|
return meta;
|
|
},
|
|
|
|
getVisibleDatasetCount: function() {
|
|
var count = 0;
|
|
for (var i = 0, ilen = this.data.datasets.length; i < ilen; ++i) {
|
|
if (this.isDatasetVisible(i)) {
|
|
count++;
|
|
}
|
|
}
|
|
return count;
|
|
},
|
|
|
|
isDatasetVisible: function(datasetIndex) {
|
|
var meta = this.getDatasetMeta(datasetIndex);
|
|
|
|
// meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false,
|
|
// the dataset.hidden value is ignored, else if null, the dataset hidden state is returned.
|
|
return typeof meta.hidden === 'boolean' ? !meta.hidden : !this.data.datasets[datasetIndex].hidden;
|
|
},
|
|
|
|
generateLegend: function() {
|
|
return this.options.legendCallback(this);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
destroyDatasetMeta: function(datasetIndex) {
|
|
var id = this.id;
|
|
var dataset = this.data.datasets[datasetIndex];
|
|
var meta = dataset._meta && dataset._meta[id];
|
|
|
|
if (meta) {
|
|
meta.controller.destroy();
|
|
delete dataset._meta[id];
|
|
}
|
|
},
|
|
|
|
destroy: function() {
|
|
var me = this;
|
|
var canvas = me.canvas;
|
|
var i, ilen;
|
|
|
|
me.stop();
|
|
|
|
// dataset controllers need to cleanup associated data
|
|
for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
|
|
me.destroyDatasetMeta(i);
|
|
}
|
|
|
|
if (canvas) {
|
|
me.unbindEvents();
|
|
helpers$1.canvas.clear(me);
|
|
platform.releaseContext(me.ctx);
|
|
me.canvas = null;
|
|
me.ctx = null;
|
|
}
|
|
|
|
core_plugins.notify(me, 'destroy');
|
|
|
|
delete Chart.instances[me.id];
|
|
},
|
|
|
|
toBase64Image: function() {
|
|
return this.canvas.toDataURL.apply(this.canvas, arguments);
|
|
},
|
|
|
|
initToolTip: function() {
|
|
var me = this;
|
|
me.tooltip = new core_tooltip({
|
|
_chart: me,
|
|
_chartInstance: me, // deprecated, backward compatibility
|
|
_data: me.data,
|
|
_options: me.options.tooltips
|
|
}, me);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
bindEvents: function() {
|
|
var me = this;
|
|
var listeners = me._listeners = {};
|
|
var listener = function() {
|
|
me.eventHandler.apply(me, arguments);
|
|
};
|
|
|
|
helpers$1.each(me.options.events, function(type) {
|
|
platform.addEventListener(me, type, listener);
|
|
listeners[type] = listener;
|
|
});
|
|
|
|
// Elements used to detect size change should not be injected for non responsive charts.
|
|
// See https://github.com/chartjs/Chart.js/issues/2210
|
|
if (me.options.responsive) {
|
|
listener = function() {
|
|
me.resize();
|
|
};
|
|
|
|
platform.addEventListener(me, 'resize', listener);
|
|
listeners.resize = listener;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
unbindEvents: function() {
|
|
var me = this;
|
|
var listeners = me._listeners;
|
|
if (!listeners) {
|
|
return;
|
|
}
|
|
|
|
delete me._listeners;
|
|
helpers$1.each(listeners, function(listener, type) {
|
|
platform.removeEventListener(me, type, listener);
|
|
});
|
|
},
|
|
|
|
updateHoverStyle: function(elements, mode, enabled) {
|
|
var prefix = enabled ? 'set' : 'remove';
|
|
var element, i, ilen;
|
|
|
|
for (i = 0, ilen = elements.length; i < ilen; ++i) {
|
|
element = elements[i];
|
|
if (element) {
|
|
this.getDatasetMeta(element._datasetIndex).controller[prefix + 'HoverStyle'](element);
|
|
}
|
|
}
|
|
|
|
if (mode === 'dataset') {
|
|
this.getDatasetMeta(elements[0]._datasetIndex).controller['_' + prefix + 'DatasetHoverStyle']();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
eventHandler: function(e) {
|
|
var me = this;
|
|
var tooltip = me.tooltip;
|
|
|
|
if (core_plugins.notify(me, 'beforeEvent', [e]) === false) {
|
|
return;
|
|
}
|
|
|
|
// Buffer any update calls so that renders do not occur
|
|
me._bufferedRender = true;
|
|
me._bufferedRequest = null;
|
|
|
|
var changed = me.handleEvent(e);
|
|
// for smooth tooltip animations issue #4989
|
|
// the tooltip should be the source of change
|
|
// Animation check workaround:
|
|
// tooltip._start will be null when tooltip isn't animating
|
|
if (tooltip) {
|
|
changed = tooltip._start
|
|
? tooltip.handleEvent(e)
|
|
: changed | tooltip.handleEvent(e);
|
|
}
|
|
|
|
core_plugins.notify(me, 'afterEvent', [e]);
|
|
|
|
var bufferedRequest = me._bufferedRequest;
|
|
if (bufferedRequest) {
|
|
// If we have an update that was triggered, we need to do a normal render
|
|
me.render(bufferedRequest);
|
|
} else if (changed && !me.animating) {
|
|
// If entering, leaving, or changing elements, animate the change via pivot
|
|
me.stop();
|
|
|
|
// We only need to render at this point. Updating will cause scales to be
|
|
// recomputed generating flicker & using more memory than necessary.
|
|
me.render({
|
|
duration: me.options.hover.animationDuration,
|
|
lazy: true
|
|
});
|
|
}
|
|
|
|
me._bufferedRender = false;
|
|
me._bufferedRequest = null;
|
|
|
|
return me;
|
|
},
|
|
|
|
/**
|
|
* Handle an event
|
|
* @private
|
|
* @param {IEvent} event the event to handle
|
|
* @return {boolean} true if the chart needs to re-render
|
|
*/
|
|
handleEvent: function(e) {
|
|
var me = this;
|
|
var options = me.options || {};
|
|
var hoverOptions = options.hover;
|
|
var changed = false;
|
|
|
|
me.lastActive = me.lastActive || [];
|
|
|
|
// Find Active Elements for hover and tooltips
|
|
if (e.type === 'mouseout') {
|
|
me.active = [];
|
|
} else {
|
|
me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions);
|
|
}
|
|
|
|
// Invoke onHover hook
|
|
// Need to call with native event here to not break backwards compatibility
|
|
helpers$1.callback(options.onHover || options.hover.onHover, [e.native, me.active], me);
|
|
|
|
if (e.type === 'mouseup' || e.type === 'click') {
|
|
if (options.onClick) {
|
|
// Use e.native here for backwards compatibility
|
|
options.onClick.call(me, e.native, me.active);
|
|
}
|
|
}
|
|
|
|
// Remove styling for last active (even if it may still be active)
|
|
if (me.lastActive.length) {
|
|
me.updateHoverStyle(me.lastActive, hoverOptions.mode, false);
|
|
}
|
|
|
|
// Built in hover styling
|
|
if (me.active.length && hoverOptions.mode) {
|
|
me.updateHoverStyle(me.active, hoverOptions.mode, true);
|
|
}
|
|
|
|
changed = !helpers$1.arrayEquals(me.active, me.lastActive);
|
|
|
|
// Remember Last Actives
|
|
me.lastActive = me.active;
|
|
|
|
return changed;
|
|
}
|
|
});
|
|
|
|
/**
|
|
* NOTE(SB) We actually don't use this container anymore but we need to keep it
|
|
* for backward compatibility. Though, it can still be useful for plugins that
|
|
* would need to work on multiple charts?!
|
|
*/
|
|
Chart.instances = {};
|
|
|
|
var core_controller = Chart;
|
|
|
|
// DEPRECATIONS
|
|
|
|
/**
|
|
* Provided for backward compatibility, use Chart instead.
|
|
* @class Chart.Controller
|
|
* @deprecated since version 2.6
|
|
* @todo remove at version 3
|
|
* @private
|
|
*/
|
|
Chart.Controller = Chart;
|
|
|
|
/**
|
|
* Provided for backward compatibility, not available anymore.
|
|
* @namespace Chart
|
|
* @deprecated since version 2.8
|
|
* @todo remove at version 3
|
|
* @private
|
|
*/
|
|
Chart.types = {};
|
|
|
|
/**
|
|
* Provided for backward compatibility, not available anymore.
|
|
* @namespace Chart.helpers.configMerge
|
|
* @deprecated since version 2.8.0
|
|
* @todo remove at version 3
|
|
* @private
|
|
*/
|
|
helpers$1.configMerge = mergeConfig;
|
|
|
|
/**
|
|
* Provided for backward compatibility, not available anymore.
|
|
* @namespace Chart.helpers.scaleMerge
|
|
* @deprecated since version 2.8.0
|
|
* @todo remove at version 3
|
|
* @private
|
|
*/
|
|
helpers$1.scaleMerge = mergeScaleConfig;
|
|
|
|
var core_helpers = function() {
|
|
|
|
// -- Basic js utility methods
|
|
|
|
helpers$1.where = function(collection, filterCallback) {
|
|
if (helpers$1.isArray(collection) && Array.prototype.filter) {
|
|
return collection.filter(filterCallback);
|
|
}
|
|
var filtered = [];
|
|
|
|
helpers$1.each(collection, function(item) {
|
|
if (filterCallback(item)) {
|
|
filtered.push(item);
|
|
}
|
|
});
|
|
|
|
return filtered;
|
|
};
|
|
helpers$1.findIndex = Array.prototype.findIndex ?
|
|
function(array, callback, scope) {
|
|
return array.findIndex(callback, scope);
|
|
} :
|
|
function(array, callback, scope) {
|
|
scope = scope === undefined ? array : scope;
|
|
for (var i = 0, ilen = array.length; i < ilen; ++i) {
|
|
if (callback.call(scope, array[i], i, array)) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
};
|
|
helpers$1.findNextWhere = function(arrayToSearch, filterCallback, startIndex) {
|
|
// Default to start of the array
|
|
if (helpers$1.isNullOrUndef(startIndex)) {
|
|
startIndex = -1;
|
|
}
|
|
for (var i = startIndex + 1; i < arrayToSearch.length; i++) {
|
|
var currentItem = arrayToSearch[i];
|
|
if (filterCallback(currentItem)) {
|
|
return currentItem;
|
|
}
|
|
}
|
|
};
|
|
helpers$1.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) {
|
|
// Default to end of the array
|
|
if (helpers$1.isNullOrUndef(startIndex)) {
|
|
startIndex = arrayToSearch.length;
|
|
}
|
|
for (var i = startIndex - 1; i >= 0; i--) {
|
|
var currentItem = arrayToSearch[i];
|
|
if (filterCallback(currentItem)) {
|
|
return currentItem;
|
|
}
|
|
}
|
|
};
|
|
|
|
// -- Math methods
|
|
helpers$1.isNumber = function(n) {
|
|
return !isNaN(parseFloat(n)) && isFinite(n);
|
|
};
|
|
helpers$1.almostEquals = function(x, y, epsilon) {
|
|
return Math.abs(x - y) < epsilon;
|
|
};
|
|
helpers$1.almostWhole = function(x, epsilon) {
|
|
var rounded = Math.round(x);
|
|
return ((rounded - epsilon) <= x) && ((rounded + epsilon) >= x);
|
|
};
|
|
helpers$1.max = function(array) {
|
|
return array.reduce(function(max, value) {
|
|
if (!isNaN(value)) {
|
|
return Math.max(max, value);
|
|
}
|
|
return max;
|
|
}, Number.NEGATIVE_INFINITY);
|
|
};
|
|
helpers$1.min = function(array) {
|
|
return array.reduce(function(min, value) {
|
|
if (!isNaN(value)) {
|
|
return Math.min(min, value);
|
|
}
|
|
return min;
|
|
}, Number.POSITIVE_INFINITY);
|
|
};
|
|
helpers$1.sign = Math.sign ?
|
|
function(x) {
|
|
return Math.sign(x);
|
|
} :
|
|
function(x) {
|
|
x = +x; // convert to a number
|
|
if (x === 0 || isNaN(x)) {
|
|
return x;
|
|
}
|
|
return x > 0 ? 1 : -1;
|
|
};
|
|
helpers$1.toRadians = function(degrees) {
|
|
return degrees * (Math.PI / 180);
|
|
};
|
|
helpers$1.toDegrees = function(radians) {
|
|
return radians * (180 / Math.PI);
|
|
};
|
|
|
|
/**
|
|
* Returns the number of decimal places
|
|
* i.e. the number of digits after the decimal point, of the value of this Number.
|
|
* @param {number} x - A number.
|
|
* @returns {number} The number of decimal places.
|
|
* @private
|
|
*/
|
|
helpers$1._decimalPlaces = function(x) {
|
|
if (!helpers$1.isFinite(x)) {
|
|
return;
|
|
}
|
|
var e = 1;
|
|
var p = 0;
|
|
while (Math.round(x * e) / e !== x) {
|
|
e *= 10;
|
|
p++;
|
|
}
|
|
return p;
|
|
};
|
|
|
|
// Gets the angle from vertical upright to the point about a centre.
|
|
helpers$1.getAngleFromPoint = function(centrePoint, anglePoint) {
|
|
var distanceFromXCenter = anglePoint.x - centrePoint.x;
|
|
var distanceFromYCenter = anglePoint.y - centrePoint.y;
|
|
var radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
|
|
|
|
var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter);
|
|
|
|
if (angle < (-0.5 * Math.PI)) {
|
|
angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2]
|
|
}
|
|
|
|
return {
|
|
angle: angle,
|
|
distance: radialDistanceFromCenter
|
|
};
|
|
};
|
|
helpers$1.distanceBetweenPoints = function(pt1, pt2) {
|
|
return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2));
|
|
};
|
|
|
|
/**
|
|
* Provided for backward compatibility, not available anymore
|
|
* @function Chart.helpers.aliasPixel
|
|
* @deprecated since version 2.8.0
|
|
* @todo remove at version 3
|
|
*/
|
|
helpers$1.aliasPixel = function(pixelWidth) {
|
|
return (pixelWidth % 2 === 0) ? 0 : 0.5;
|
|
};
|
|
|
|
/**
|
|
* Returns the aligned pixel value to avoid anti-aliasing blur
|
|
* @param {Chart} chart - The chart instance.
|
|
* @param {number} pixel - A pixel value.
|
|
* @param {number} width - The width of the element.
|
|
* @returns {number} The aligned pixel value.
|
|
* @private
|
|
*/
|
|
helpers$1._alignPixel = function(chart, pixel, width) {
|
|
var devicePixelRatio = chart.currentDevicePixelRatio;
|
|
var halfWidth = width / 2;
|
|
return Math.round((pixel - halfWidth) * devicePixelRatio) / devicePixelRatio + halfWidth;
|
|
};
|
|
|
|
helpers$1.splineCurve = function(firstPoint, middlePoint, afterPoint, t) {
|
|
// Props to Rob Spencer at scaled innovation for his post on splining between points
|
|
// http://scaledinnovation.com/analytics/splines/aboutSplines.html
|
|
|
|
// This function must also respect "skipped" points
|
|
|
|
var previous = firstPoint.skip ? middlePoint : firstPoint;
|
|
var current = middlePoint;
|
|
var next = afterPoint.skip ? middlePoint : afterPoint;
|
|
|
|
var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2));
|
|
var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2));
|
|
|
|
var s01 = d01 / (d01 + d12);
|
|
var s12 = d12 / (d01 + d12);
|
|
|
|
// If all points are the same, s01 & s02 will be inf
|
|
s01 = isNaN(s01) ? 0 : s01;
|
|
s12 = isNaN(s12) ? 0 : s12;
|
|
|
|
var fa = t * s01; // scaling factor for triangle Ta
|
|
var fb = t * s12;
|
|
|
|
return {
|
|
previous: {
|
|
x: current.x - fa * (next.x - previous.x),
|
|
y: current.y - fa * (next.y - previous.y)
|
|
},
|
|
next: {
|
|
x: current.x + fb * (next.x - previous.x),
|
|
y: current.y + fb * (next.y - previous.y)
|
|
}
|
|
};
|
|
};
|
|
helpers$1.EPSILON = Number.EPSILON || 1e-14;
|
|
helpers$1.splineCurveMonotone = function(points) {
|
|
// This function calculates Bézier control points in a similar way than |splineCurve|,
|
|
// but preserves monotonicity of the provided data and ensures no local extremums are added
|
|
// between the dataset discrete points due to the interpolation.
|
|
// See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation
|
|
|
|
var pointsWithTangents = (points || []).map(function(point) {
|
|
return {
|
|
model: point._model,
|
|
deltaK: 0,
|
|
mK: 0
|
|
};
|
|
});
|
|
|
|
// Calculate slopes (deltaK) and initialize tangents (mK)
|
|
var pointsLen = pointsWithTangents.length;
|
|
var i, pointBefore, pointCurrent, pointAfter;
|
|
for (i = 0; i < pointsLen; ++i) {
|
|
pointCurrent = pointsWithTangents[i];
|
|
if (pointCurrent.model.skip) {
|
|
continue;
|
|
}
|
|
|
|
pointBefore = i > 0 ? pointsWithTangents[i - 1] : null;
|
|
pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null;
|
|
if (pointAfter && !pointAfter.model.skip) {
|
|
var slopeDeltaX = (pointAfter.model.x - pointCurrent.model.x);
|
|
|
|
// In the case of two points that appear at the same x pixel, slopeDeltaX is 0
|
|
pointCurrent.deltaK = slopeDeltaX !== 0 ? (pointAfter.model.y - pointCurrent.model.y) / slopeDeltaX : 0;
|
|
}
|
|
|
|
if (!pointBefore || pointBefore.model.skip) {
|
|
pointCurrent.mK = pointCurrent.deltaK;
|
|
} else if (!pointAfter || pointAfter.model.skip) {
|
|
pointCurrent.mK = pointBefore.deltaK;
|
|
} else if (this.sign(pointBefore.deltaK) !== this.sign(pointCurrent.deltaK)) {
|
|
pointCurrent.mK = 0;
|
|
} else {
|
|
pointCurrent.mK = (pointBefore.deltaK + pointCurrent.deltaK) / 2;
|
|
}
|
|
}
|
|
|
|
// Adjust tangents to ensure monotonic properties
|
|
var alphaK, betaK, tauK, squaredMagnitude;
|
|
for (i = 0; i < pointsLen - 1; ++i) {
|
|
pointCurrent = pointsWithTangents[i];
|
|
pointAfter = pointsWithTangents[i + 1];
|
|
if (pointCurrent.model.skip || pointAfter.model.skip) {
|
|
continue;
|
|
}
|
|
|
|
if (helpers$1.almostEquals(pointCurrent.deltaK, 0, this.EPSILON)) {
|
|
pointCurrent.mK = pointAfter.mK = 0;
|
|
continue;
|
|
}
|
|
|
|
alphaK = pointCurrent.mK / pointCurrent.deltaK;
|
|
betaK = pointAfter.mK / pointCurrent.deltaK;
|
|
squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2);
|
|
if (squaredMagnitude <= 9) {
|
|
continue;
|
|
}
|
|
|
|
tauK = 3 / Math.sqrt(squaredMagnitude);
|
|
pointCurrent.mK = alphaK * tauK * pointCurrent.deltaK;
|
|
pointAfter.mK = betaK * tauK * pointCurrent.deltaK;
|
|
}
|
|
|
|
// Compute control points
|
|
var deltaX;
|
|
for (i = 0; i < pointsLen; ++i) {
|
|
pointCurrent = pointsWithTangents[i];
|
|
if (pointCurrent.model.skip) {
|
|
continue;
|
|
}
|
|
|
|
pointBefore = i > 0 ? pointsWithTangents[i - 1] : null;
|
|
pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null;
|
|
if (pointBefore && !pointBefore.model.skip) {
|
|
deltaX = (pointCurrent.model.x - pointBefore.model.x) / 3;
|
|
pointCurrent.model.controlPointPreviousX = pointCurrent.model.x - deltaX;
|
|
pointCurrent.model.controlPointPreviousY = pointCurrent.model.y - deltaX * pointCurrent.mK;
|
|
}
|
|
if (pointAfter && !pointAfter.model.skip) {
|
|
deltaX = (pointAfter.model.x - pointCurrent.model.x) / 3;
|
|
pointCurrent.model.controlPointNextX = pointCurrent.model.x + deltaX;
|
|
pointCurrent.model.controlPointNextY = pointCurrent.model.y + deltaX * pointCurrent.mK;
|
|
}
|
|
}
|
|
};
|
|
helpers$1.nextItem = function(collection, index, loop) {
|
|
if (loop) {
|
|
return index >= collection.length - 1 ? collection[0] : collection[index + 1];
|
|
}
|
|
return index >= collection.length - 1 ? collection[collection.length - 1] : collection[index + 1];
|
|
};
|
|
helpers$1.previousItem = function(collection, index, loop) {
|
|
if (loop) {
|
|
return index <= 0 ? collection[collection.length - 1] : collection[index - 1];
|
|
}
|
|
return index <= 0 ? collection[0] : collection[index - 1];
|
|
};
|
|
// Implementation of the nice number algorithm used in determining where axis labels will go
|
|
helpers$1.niceNum = function(range, round) {
|
|
var exponent = Math.floor(helpers$1.log10(range));
|
|
var fraction = range / Math.pow(10, exponent);
|
|
var niceFraction;
|
|
|
|
if (round) {
|
|
if (fraction < 1.5) {
|
|
niceFraction = 1;
|
|
} else if (fraction < 3) {
|
|
niceFraction = 2;
|
|
} else if (fraction < 7) {
|
|
niceFraction = 5;
|
|
} else {
|
|
niceFraction = 10;
|
|
}
|
|
} else if (fraction <= 1.0) {
|
|
niceFraction = 1;
|
|
} else if (fraction <= 2) {
|
|
niceFraction = 2;
|
|
} else if (fraction <= 5) {
|
|
niceFraction = 5;
|
|
} else {
|
|
niceFraction = 10;
|
|
}
|
|
|
|
return niceFraction * Math.pow(10, exponent);
|
|
};
|
|
// Request animation polyfill - https://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
|
|
helpers$1.requestAnimFrame = (function() {
|
|
if (typeof window === 'undefined') {
|
|
return function(callback) {
|
|
callback();
|
|
};
|
|
}
|
|
return window.requestAnimationFrame ||
|
|
window.webkitRequestAnimationFrame ||
|
|
window.mozRequestAnimationFrame ||
|
|
window.oRequestAnimationFrame ||
|
|
window.msRequestAnimationFrame ||
|
|
function(callback) {
|
|
return window.setTimeout(callback, 1000 / 60);
|
|
};
|
|
}());
|
|
// -- DOM methods
|
|
helpers$1.getRelativePosition = function(evt, chart) {
|
|
var mouseX, mouseY;
|
|
var e = evt.originalEvent || evt;
|
|
var canvas = evt.target || evt.srcElement;
|
|
var boundingRect = canvas.getBoundingClientRect();
|
|
|
|
var touches = e.touches;
|
|
if (touches && touches.length > 0) {
|
|
mouseX = touches[0].clientX;
|
|
mouseY = touches[0].clientY;
|
|
|
|
} else {
|
|
mouseX = e.clientX;
|
|
mouseY = e.clientY;
|
|
}
|
|
|
|
// Scale mouse coordinates into canvas coordinates
|
|
// by following the pattern laid out by 'jerryj' in the comments of
|
|
// https://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/
|
|
var paddingLeft = parseFloat(helpers$1.getStyle(canvas, 'padding-left'));
|
|
var paddingTop = parseFloat(helpers$1.getStyle(canvas, 'padding-top'));
|
|
var paddingRight = parseFloat(helpers$1.getStyle(canvas, 'padding-right'));
|
|
var paddingBottom = parseFloat(helpers$1.getStyle(canvas, 'padding-bottom'));
|
|
var width = boundingRect.right - boundingRect.left - paddingLeft - paddingRight;
|
|
var height = boundingRect.bottom - boundingRect.top - paddingTop - paddingBottom;
|
|
|
|
// We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However
|
|
// the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here
|
|
mouseX = Math.round((mouseX - boundingRect.left - paddingLeft) / (width) * canvas.width / chart.currentDevicePixelRatio);
|
|
mouseY = Math.round((mouseY - boundingRect.top - paddingTop) / (height) * canvas.height / chart.currentDevicePixelRatio);
|
|
|
|
return {
|
|
x: mouseX,
|
|
y: mouseY
|
|
};
|
|
|
|
};
|
|
|
|
// Private helper function to convert max-width/max-height values that may be percentages into a number
|
|
function parseMaxStyle(styleValue, node, parentProperty) {
|
|
var valueInPixels;
|
|
if (typeof styleValue === 'string') {
|
|
valueInPixels = parseInt(styleValue, 10);
|
|
|
|
if (styleValue.indexOf('%') !== -1) {
|
|
// percentage * size in dimension
|
|
valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty];
|
|
}
|
|
} else {
|
|
valueInPixels = styleValue;
|
|
}
|
|
|
|
return valueInPixels;
|
|
}
|
|
|
|
/**
|
|
* Returns if the given value contains an effective constraint.
|
|
* @private
|
|
*/
|
|
function isConstrainedValue(value) {
|
|
return value !== undefined && value !== null && value !== 'none';
|
|
}
|
|
|
|
/**
|
|
* Returns the max width or height of the given DOM node in a cross-browser compatible fashion
|
|
* @param {HTMLElement} domNode - the node to check the constraint on
|
|
* @param {string} maxStyle - the style that defines the maximum for the direction we are using ('max-width' / 'max-height')
|
|
* @param {string} percentageProperty - property of parent to use when calculating width as a percentage
|
|
* @see {@link https://www.nathanaeljones.com/blog/2013/reading-max-width-cross-browser}
|
|
*/
|
|
function getConstraintDimension(domNode, maxStyle, percentageProperty) {
|
|
var view = document.defaultView;
|
|
var parentNode = helpers$1._getParentNode(domNode);
|
|
var constrainedNode = view.getComputedStyle(domNode)[maxStyle];
|
|
var constrainedContainer = view.getComputedStyle(parentNode)[maxStyle];
|
|
var hasCNode = isConstrainedValue(constrainedNode);
|
|
var hasCContainer = isConstrainedValue(constrainedContainer);
|
|
var infinity = Number.POSITIVE_INFINITY;
|
|
|
|
if (hasCNode || hasCContainer) {
|
|
return Math.min(
|
|
hasCNode ? parseMaxStyle(constrainedNode, domNode, percentageProperty) : infinity,
|
|
hasCContainer ? parseMaxStyle(constrainedContainer, parentNode, percentageProperty) : infinity);
|
|
}
|
|
|
|
return 'none';
|
|
}
|
|
// returns Number or undefined if no constraint
|
|
helpers$1.getConstraintWidth = function(domNode) {
|
|
return getConstraintDimension(domNode, 'max-width', 'clientWidth');
|
|
};
|
|
// returns Number or undefined if no constraint
|
|
helpers$1.getConstraintHeight = function(domNode) {
|
|
return getConstraintDimension(domNode, 'max-height', 'clientHeight');
|
|
};
|
|
/**
|
|
* @private
|
|
*/
|
|
helpers$1._calculatePadding = function(container, padding, parentDimension) {
|
|
padding = helpers$1.getStyle(container, padding);
|
|
|
|
return padding.indexOf('%') > -1 ? parentDimension * parseInt(padding, 10) / 100 : parseInt(padding, 10);
|
|
};
|
|
/**
|
|
* @private
|
|
*/
|
|
helpers$1._getParentNode = function(domNode) {
|
|
var parent = domNode.parentNode;
|
|
if (parent && parent.toString() === '[object ShadowRoot]') {
|
|
parent = parent.host;
|
|
}
|
|
return parent;
|
|
};
|
|
helpers$1.getMaximumWidth = function(domNode) {
|
|
var container = helpers$1._getParentNode(domNode);
|
|
if (!container) {
|
|
return domNode.clientWidth;
|
|
}
|
|
|
|
var clientWidth = container.clientWidth;
|
|
var paddingLeft = helpers$1._calculatePadding(container, 'padding-left', clientWidth);
|
|
var paddingRight = helpers$1._calculatePadding(container, 'padding-right', clientWidth);
|
|
|
|
var w = clientWidth - paddingLeft - paddingRight;
|
|
var cw = helpers$1.getConstraintWidth(domNode);
|
|
return isNaN(cw) ? w : Math.min(w, cw);
|
|
};
|
|
helpers$1.getMaximumHeight = function(domNode) {
|
|
var container = helpers$1._getParentNode(domNode);
|
|
if (!container) {
|
|
return domNode.clientHeight;
|
|
}
|
|
|
|
var clientHeight = container.clientHeight;
|
|
var paddingTop = helpers$1._calculatePadding(container, 'padding-top', clientHeight);
|
|
var paddingBottom = helpers$1._calculatePadding(container, 'padding-bottom', clientHeight);
|
|
|
|
var h = clientHeight - paddingTop - paddingBottom;
|
|
var ch = helpers$1.getConstraintHeight(domNode);
|
|
return isNaN(ch) ? h : Math.min(h, ch);
|
|
};
|
|
helpers$1.getStyle = function(el, property) {
|
|
return el.currentStyle ?
|
|
el.currentStyle[property] :
|
|
document.defaultView.getComputedStyle(el, null).getPropertyValue(property);
|
|
};
|
|
helpers$1.retinaScale = function(chart, forceRatio) {
|
|
var pixelRatio = chart.currentDevicePixelRatio = forceRatio || (typeof window !== 'undefined' && window.devicePixelRatio) || 1;
|
|
if (pixelRatio === 1) {
|
|
return;
|
|
}
|
|
|
|
var canvas = chart.canvas;
|
|
var height = chart.height;
|
|
var width = chart.width;
|
|
|
|
canvas.height = height * pixelRatio;
|
|
canvas.width = width * pixelRatio;
|
|
chart.ctx.scale(pixelRatio, pixelRatio);
|
|
|
|
// If no style has been set on the canvas, the render size is used as display size,
|
|
// making the chart visually bigger, so let's enforce it to the "correct" values.
|
|
// See https://github.com/chartjs/Chart.js/issues/3575
|
|
if (!canvas.style.height && !canvas.style.width) {
|
|
canvas.style.height = height + 'px';
|
|
canvas.style.width = width + 'px';
|
|
}
|
|
};
|
|
// -- Canvas methods
|
|
helpers$1.fontString = function(pixelSize, fontStyle, fontFamily) {
|
|
return fontStyle + ' ' + pixelSize + 'px ' + fontFamily;
|
|
};
|
|
helpers$1.longestText = function(ctx, font, arrayOfThings, cache) {
|
|
cache = cache || {};
|
|
var data = cache.data = cache.data || {};
|
|
var gc = cache.garbageCollect = cache.garbageCollect || [];
|
|
|
|
if (cache.font !== font) {
|
|
data = cache.data = {};
|
|
gc = cache.garbageCollect = [];
|
|
cache.font = font;
|
|
}
|
|
|
|
ctx.font = font;
|
|
var longest = 0;
|
|
var ilen = arrayOfThings.length;
|
|
var i, j, jlen, thing, nestedThing;
|
|
for (i = 0; i < ilen; i++) {
|
|
thing = arrayOfThings[i];
|
|
|
|
// Undefined strings and arrays should not be measured
|
|
if (thing !== undefined && thing !== null && helpers$1.isArray(thing) !== true) {
|
|
longest = helpers$1.measureText(ctx, data, gc, longest, thing);
|
|
} else if (helpers$1.isArray(thing)) {
|
|
// if it is an array lets measure each element
|
|
// to do maybe simplify this function a bit so we can do this more recursively?
|
|
for (j = 0, jlen = thing.length; j < jlen; j++) {
|
|
nestedThing = thing[j];
|
|
// Undefined strings and arrays should not be measured
|
|
if (nestedThing !== undefined && nestedThing !== null && !helpers$1.isArray(nestedThing)) {
|
|
longest = helpers$1.measureText(ctx, data, gc, longest, nestedThing);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var gcLen = gc.length / 2;
|
|
if (gcLen > arrayOfThings.length) {
|
|
for (i = 0; i < gcLen; i++) {
|
|
delete data[gc[i]];
|
|
}
|
|
gc.splice(0, gcLen);
|
|
}
|
|
return longest;
|
|
};
|
|
helpers$1.measureText = function(ctx, data, gc, longest, string) {
|
|
var textWidth = data[string];
|
|
if (!textWidth) {
|
|
textWidth = data[string] = ctx.measureText(string).width;
|
|
gc.push(string);
|
|
}
|
|
if (textWidth > longest) {
|
|
longest = textWidth;
|
|
}
|
|
return longest;
|
|
};
|
|
|
|
/**
|
|
* @deprecated
|
|
*/
|
|
helpers$1.numberOfLabelLines = function(arrayOfThings) {
|
|
var numberOfLines = 1;
|
|
helpers$1.each(arrayOfThings, function(thing) {
|
|
if (helpers$1.isArray(thing)) {
|
|
if (thing.length > numberOfLines) {
|
|
numberOfLines = thing.length;
|
|
}
|
|
}
|
|
});
|
|
return numberOfLines;
|
|
};
|
|
|
|
helpers$1.color = !chartjsColor ?
|
|
function(value) {
|
|
console.error('Color.js not found!');
|
|
return value;
|
|
} :
|
|
function(value) {
|
|
/* global CanvasGradient */
|
|
if (value instanceof CanvasGradient) {
|
|
value = core_defaults.global.defaultColor;
|
|
}
|
|
|
|
return chartjsColor(value);
|
|
};
|
|
|
|
helpers$1.getHoverColor = function(colorValue) {
|
|
/* global CanvasPattern */
|
|
return (colorValue instanceof CanvasPattern || colorValue instanceof CanvasGradient) ?
|
|
colorValue :
|
|
helpers$1.color(colorValue).saturate(0.5).darken(0.1).rgbString();
|
|
};
|
|
};
|
|
|
|
function abstract() {
|
|
throw new Error(
|
|
'This method is not implemented: either no adapter can ' +
|
|
'be found or an incomplete integration was provided.'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Date adapter (current used by the time scale)
|
|
* @namespace Chart._adapters._date
|
|
* @memberof Chart._adapters
|
|
* @private
|
|
*/
|
|
|
|
/**
|
|
* Currently supported unit string values.
|
|
* @typedef {('millisecond'|'second'|'minute'|'hour'|'day'|'week'|'month'|'quarter'|'year')}
|
|
* @memberof Chart._adapters._date
|
|
* @name Unit
|
|
*/
|
|
|
|
/**
|
|
* @class
|
|
*/
|
|
function DateAdapter(options) {
|
|
this.options = options || {};
|
|
}
|
|
|
|
helpers$1.extend(DateAdapter.prototype, /** @lends DateAdapter */ {
|
|
/**
|
|
* Returns a map of time formats for the supported formatting units defined
|
|
* in Unit as well as 'datetime' representing a detailed date/time string.
|
|
* @returns {{string: string}}
|
|
*/
|
|
formats: abstract,
|
|
|
|
/**
|
|
* Parses the given `value` and return the associated timestamp.
|
|
* @param {any} value - the value to parse (usually comes from the data)
|
|
* @param {string} [format] - the expected data format
|
|
* @returns {(number|null)}
|
|
* @function
|
|
*/
|
|
parse: abstract,
|
|
|
|
/**
|
|
* Returns the formatted date in the specified `format` for a given `timestamp`.
|
|
* @param {number} timestamp - the timestamp to format
|
|
* @param {string} format - the date/time token
|
|
* @return {string}
|
|
* @function
|
|
*/
|
|
format: abstract,
|
|
|
|
/**
|
|
* Adds the specified `amount` of `unit` to the given `timestamp`.
|
|
* @param {number} timestamp - the input timestamp
|
|
* @param {number} amount - the amount to add
|
|
* @param {Unit} unit - the unit as string
|
|
* @return {number}
|
|
* @function
|
|
*/
|
|
add: abstract,
|
|
|
|
/**
|
|
* Returns the number of `unit` between the given timestamps.
|
|
* @param {number} max - the input timestamp (reference)
|
|
* @param {number} min - the timestamp to substract
|
|
* @param {Unit} unit - the unit as string
|
|
* @return {number}
|
|
* @function
|
|
*/
|
|
diff: abstract,
|
|
|
|
/**
|
|
* Returns start of `unit` for the given `timestamp`.
|
|
* @param {number} timestamp - the input timestamp
|
|
* @param {Unit} unit - the unit as string
|
|
* @param {number} [weekday] - the ISO day of the week with 1 being Monday
|
|
* and 7 being Sunday (only needed if param *unit* is `isoWeek`).
|
|
* @function
|
|
*/
|
|
startOf: abstract,
|
|
|
|
/**
|
|
* Returns end of `unit` for the given `timestamp`.
|
|
* @param {number} timestamp - the input timestamp
|
|
* @param {Unit} unit - the unit as string
|
|
* @function
|
|
*/
|
|
endOf: abstract,
|
|
|
|
// DEPRECATIONS
|
|
|
|
/**
|
|
* Provided for backward compatibility for scale.getValueForPixel(),
|
|
* this method should be overridden only by the moment adapter.
|
|
* @deprecated since version 2.8.0
|
|
* @todo remove at version 3
|
|
* @private
|
|
*/
|
|
_create: function(value) {
|
|
return value;
|
|
}
|
|
});
|
|
|
|
DateAdapter.override = function(members) {
|
|
helpers$1.extend(DateAdapter.prototype, members);
|
|
};
|
|
|
|
var _date = DateAdapter;
|
|
|
|
var core_adapters = {
|
|
_date: _date
|
|
};
|
|
|
|
/**
|
|
* Namespace to hold static tick generation functions
|
|
* @namespace Chart.Ticks
|
|
*/
|
|
var core_ticks = {
|
|
/**
|
|
* Namespace to hold formatters for different types of ticks
|
|
* @namespace Chart.Ticks.formatters
|
|
*/
|
|
formatters: {
|
|
/**
|
|
* Formatter for value labels
|
|
* @method Chart.Ticks.formatters.values
|
|
* @param value the value to display
|
|
* @return {string|string[]} the label to display
|
|
*/
|
|
values: function(value) {
|
|
return helpers$1.isArray(value) ? value : '' + value;
|
|
},
|
|
|
|
/**
|
|
* Formatter for linear numeric ticks
|
|
* @method Chart.Ticks.formatters.linear
|
|
* @param tickValue {number} the value to be formatted
|
|
* @param index {number} the position of the tickValue parameter in the ticks array
|
|
* @param ticks {number[]} the list of ticks being converted
|
|
* @return {string} string representation of the tickValue parameter
|
|
*/
|
|
linear: function(tickValue, index, ticks) {
|
|
// If we have lots of ticks, don't use the ones
|
|
var delta = ticks.length > 3 ? ticks[2] - ticks[1] : ticks[1] - ticks[0];
|
|
|
|
// If we have a number like 2.5 as the delta, figure out how many decimal places we need
|
|
if (Math.abs(delta) > 1) {
|
|
if (tickValue !== Math.floor(tickValue)) {
|
|
// not an integer
|
|
delta = tickValue - Math.floor(tickValue);
|
|
}
|
|
}
|
|
|
|
var logDelta = helpers$1.log10(Math.abs(delta));
|
|
var tickString = '';
|
|
|
|
if (tickValue !== 0) {
|
|
var maxTick = Math.max(Math.abs(ticks[0]), Math.abs(ticks[ticks.length - 1]));
|
|
if (maxTick < 1e-4) { // all ticks are small numbers; use scientific notation
|
|
var logTick = helpers$1.log10(Math.abs(tickValue));
|
|
var numExponential = Math.floor(logTick) - Math.floor(logDelta);
|
|
numExponential = Math.max(Math.min(numExponential, 20), 0);
|
|
tickString = tickValue.toExponential(numExponential);
|
|
} else {
|
|
var numDecimal = -1 * Math.floor(logDelta);
|
|
numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places
|
|
tickString = tickValue.toFixed(numDecimal);
|
|
}
|
|
} else {
|
|
tickString = '0'; // never show decimal places for 0
|
|
}
|
|
|
|
return tickString;
|
|
},
|
|
|
|
logarithmic: function(tickValue, index, ticks) {
|
|
var remain = tickValue / (Math.pow(10, Math.floor(helpers$1.log10(tickValue))));
|
|
|
|
if (tickValue === 0) {
|
|
return '0';
|
|
} else if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === ticks.length - 1) {
|
|
return tickValue.toExponential();
|
|
}
|
|
return '';
|
|
}
|
|
}
|
|
};
|
|
|
|
var isArray = helpers$1.isArray;
|
|
var isNullOrUndef = helpers$1.isNullOrUndef;
|
|
var valueOrDefault$a = helpers$1.valueOrDefault;
|
|
var valueAtIndexOrDefault = helpers$1.valueAtIndexOrDefault;
|
|
|
|
core_defaults._set('scale', {
|
|
display: true,
|
|
position: 'left',
|
|
offset: false,
|
|
|
|
// grid line settings
|
|
gridLines: {
|
|
display: true,
|
|
color: 'rgba(0,0,0,0.1)',
|
|
lineWidth: 1,
|
|
drawBorder: true,
|
|
drawOnChartArea: true,
|
|
drawTicks: true,
|
|
tickMarkLength: 10,
|
|
zeroLineWidth: 1,
|
|
zeroLineColor: 'rgba(0,0,0,0.25)',
|
|
zeroLineBorderDash: [],
|
|
zeroLineBorderDashOffset: 0.0,
|
|
offsetGridLines: false,
|
|
borderDash: [],
|
|
borderDashOffset: 0.0
|
|
},
|
|
|
|
// scale label
|
|
scaleLabel: {
|
|
// display property
|
|
display: false,
|
|
|
|
// actual label
|
|
labelString: '',
|
|
|
|
// top/bottom padding
|
|
padding: {
|
|
top: 4,
|
|
bottom: 4
|
|
}
|
|
},
|
|
|
|
// label settings
|
|
ticks: {
|
|
beginAtZero: false,
|
|
minRotation: 0,
|
|
maxRotation: 50,
|
|
mirror: false,
|
|
padding: 0,
|
|
reverse: false,
|
|
display: true,
|
|
autoSkip: true,
|
|
autoSkipPadding: 0,
|
|
labelOffset: 0,
|
|
// We pass through arrays to be rendered as multiline labels, we convert Others to strings here.
|
|
callback: core_ticks.formatters.values,
|
|
minor: {},
|
|
major: {}
|
|
}
|
|
});
|
|
|
|
/** Returns a new array containing numItems from arr */
|
|
function sample(arr, numItems) {
|
|
var result = [];
|
|
var increment = arr.length / numItems;
|
|
var i = 0;
|
|
var len = arr.length;
|
|
|
|
for (; i < len; i += increment) {
|
|
result.push(arr[Math.floor(i)]);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function getPixelForGridLine(scale, index, offsetGridLines) {
|
|
var length = scale.getTicks().length;
|
|
var validIndex = Math.min(index, length - 1);
|
|
var lineValue = scale.getPixelForTick(validIndex);
|
|
var start = scale._startPixel;
|
|
var end = scale._endPixel;
|
|
var epsilon = 1e-6; // 1e-6 is margin in pixels for accumulated error.
|
|
var offset;
|
|
|
|
if (offsetGridLines) {
|
|
if (length === 1) {
|
|
offset = Math.max(lineValue - start, end - lineValue);
|
|
} else if (index === 0) {
|
|
offset = (scale.getPixelForTick(1) - lineValue) / 2;
|
|
} else {
|
|
offset = (lineValue - scale.getPixelForTick(validIndex - 1)) / 2;
|
|
}
|
|
lineValue += validIndex < index ? offset : -offset;
|
|
|
|
// Return undefined if the pixel is out of the range
|
|
if (lineValue < start - epsilon || lineValue > end + epsilon) {
|
|
return;
|
|
}
|
|
}
|
|
return lineValue;
|
|
}
|
|
|
|
function garbageCollect(caches, length) {
|
|
helpers$1.each(caches, function(cache) {
|
|
var gc = cache.gc;
|
|
var gcLen = gc.length / 2;
|
|
var i;
|
|
if (gcLen > length) {
|
|
for (i = 0; i < gcLen; ++i) {
|
|
delete cache.data[gc[i]];
|
|
}
|
|
gc.splice(0, gcLen);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Returns {width, height, offset} objects for the first, last, widest, highest tick
|
|
* labels where offset indicates the anchor point offset from the top in pixels.
|
|
*/
|
|
function computeLabelSizes(ctx, tickFonts, ticks, caches) {
|
|
var length = ticks.length;
|
|
var widths = [];
|
|
var heights = [];
|
|
var offsets = [];
|
|
var i, j, jlen, label, tickFont, fontString, cache, lineHeight, width, height, nestedLabel, widest, highest;
|
|
|
|
for (i = 0; i < length; ++i) {
|
|
label = ticks[i].label;
|
|
tickFont = ticks[i].major ? tickFonts.major : tickFonts.minor;
|
|
ctx.font = fontString = tickFont.string;
|
|
cache = caches[fontString] = caches[fontString] || {data: {}, gc: []};
|
|
lineHeight = tickFont.lineHeight;
|
|
width = height = 0;
|
|
// Undefined labels and arrays should not be measured
|
|
if (!isNullOrUndef(label) && !isArray(label)) {
|
|
width = helpers$1.measureText(ctx, cache.data, cache.gc, width, label);
|
|
height = lineHeight;
|
|
} else if (isArray(label)) {
|
|
// if it is an array let's measure each element
|
|
for (j = 0, jlen = label.length; j < jlen; ++j) {
|
|
nestedLabel = label[j];
|
|
// Undefined labels and arrays should not be measured
|
|
if (!isNullOrUndef(nestedLabel) && !isArray(nestedLabel)) {
|
|
width = helpers$1.measureText(ctx, cache.data, cache.gc, width, nestedLabel);
|
|
height += lineHeight;
|
|
}
|
|
}
|
|
}
|
|
widths.push(width);
|
|
heights.push(height);
|
|
offsets.push(lineHeight / 2);
|
|
}
|
|
garbageCollect(caches, length);
|
|
|
|
widest = widths.indexOf(Math.max.apply(null, widths));
|
|
highest = heights.indexOf(Math.max.apply(null, heights));
|
|
|
|
function valueAt(idx) {
|
|
return {
|
|
width: widths[idx] || 0,
|
|
height: heights[idx] || 0,
|
|
offset: offsets[idx] || 0
|
|
};
|
|
}
|
|
|
|
return {
|
|
first: valueAt(0),
|
|
last: valueAt(length - 1),
|
|
widest: valueAt(widest),
|
|
highest: valueAt(highest)
|
|
};
|
|
}
|
|
|
|
function getTickMarkLength(options) {
|
|
return options.drawTicks ? options.tickMarkLength : 0;
|
|
}
|
|
|
|
function getScaleLabelHeight(options) {
|
|
var font, padding;
|
|
|
|
if (!options.display) {
|
|
return 0;
|
|
}
|
|
|
|
font = helpers$1.options._parseFont(options);
|
|
padding = helpers$1.options.toPadding(options.padding);
|
|
|
|
return font.lineHeight + padding.height;
|
|
}
|
|
|
|
function parseFontOptions(options, nestedOpts) {
|
|
return helpers$1.extend(helpers$1.options._parseFont({
|
|
fontFamily: valueOrDefault$a(nestedOpts.fontFamily, options.fontFamily),
|
|
fontSize: valueOrDefault$a(nestedOpts.fontSize, options.fontSize),
|
|
fontStyle: valueOrDefault$a(nestedOpts.fontStyle, options.fontStyle),
|
|
lineHeight: valueOrDefault$a(nestedOpts.lineHeight, options.lineHeight)
|
|
}), {
|
|
color: helpers$1.options.resolve([nestedOpts.fontColor, options.fontColor, core_defaults.global.defaultFontColor])
|
|
});
|
|
}
|
|
|
|
function parseTickFontOptions(options) {
|
|
var minor = parseFontOptions(options, options.minor);
|
|
var major = options.major.enabled ? parseFontOptions(options, options.major) : minor;
|
|
|
|
return {minor: minor, major: major};
|
|
}
|
|
|
|
function nonSkipped(ticksToFilter) {
|
|
var filtered = [];
|
|
var item, index, len;
|
|
for (index = 0, len = ticksToFilter.length; index < len; ++index) {
|
|
item = ticksToFilter[index];
|
|
if (typeof item._index !== 'undefined') {
|
|
filtered.push(item);
|
|
}
|
|
}
|
|
return filtered;
|
|
}
|
|
|
|
function getEvenSpacing(arr) {
|
|
var len = arr.length;
|
|
var i, diff;
|
|
|
|
if (len < 2) {
|
|
return false;
|
|
}
|
|
|
|
for (diff = arr[0], i = 1; i < len; ++i) {
|
|
if (arr[i] - arr[i - 1] !== diff) {
|
|
return false;
|
|
}
|
|
}
|
|
return diff;
|
|
}
|
|
|
|
function calculateSpacing(majorIndices, ticks, axisLength, ticksLimit) {
|
|
var evenMajorSpacing = getEvenSpacing(majorIndices);
|
|
var spacing = (ticks.length - 1) / ticksLimit;
|
|
var factors, factor, i, ilen;
|
|
|
|
// If the major ticks are evenly spaced apart, place the minor ticks
|
|
// so that they divide the major ticks into even chunks
|
|
if (!evenMajorSpacing) {
|
|
return Math.max(spacing, 1);
|
|
}
|
|
|
|
factors = helpers$1.math._factorize(evenMajorSpacing);
|
|
for (i = 0, ilen = factors.length - 1; i < ilen; i++) {
|
|
factor = factors[i];
|
|
if (factor > spacing) {
|
|
return factor;
|
|
}
|
|
}
|
|
return Math.max(spacing, 1);
|
|
}
|
|
|
|
function getMajorIndices(ticks) {
|
|
var result = [];
|
|
var i, ilen;
|
|
for (i = 0, ilen = ticks.length; i < ilen; i++) {
|
|
if (ticks[i].major) {
|
|
result.push(i);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function skipMajors(ticks, majorIndices, spacing) {
|
|
var count = 0;
|
|
var next = majorIndices[0];
|
|
var i, tick;
|
|
|
|
spacing = Math.ceil(spacing);
|
|
for (i = 0; i < ticks.length; i++) {
|
|
tick = ticks[i];
|
|
if (i === next) {
|
|
tick._index = i;
|
|
count++;
|
|
next = majorIndices[count * spacing];
|
|
} else {
|
|
delete tick.label;
|
|
}
|
|
}
|
|
}
|
|
|
|
function skip(ticks, spacing, majorStart, majorEnd) {
|
|
var start = valueOrDefault$a(majorStart, 0);
|
|
var end = Math.min(valueOrDefault$a(majorEnd, ticks.length), ticks.length);
|
|
var count = 0;
|
|
var length, i, tick, next;
|
|
|
|
spacing = Math.ceil(spacing);
|
|
if (majorEnd) {
|
|
length = majorEnd - majorStart;
|
|
spacing = length / Math.floor(length / spacing);
|
|
}
|
|
|
|
next = start;
|
|
|
|
while (next < 0) {
|
|
count++;
|
|
next = Math.round(start + count * spacing);
|
|
}
|
|
|
|
for (i = Math.max(start, 0); i < end; i++) {
|
|
tick = ticks[i];
|
|
if (i === next) {
|
|
tick._index = i;
|
|
count++;
|
|
next = Math.round(start + count * spacing);
|
|
} else {
|
|
delete tick.label;
|
|
}
|
|
}
|
|
}
|
|
|
|
var Scale = core_element.extend({
|
|
|
|
zeroLineIndex: 0,
|
|
|
|
/**
|
|
* Get the padding needed for the scale
|
|
* @method getPadding
|
|
* @private
|
|
* @returns {Padding} the necessary padding
|
|
*/
|
|
getPadding: function() {
|
|
var me = this;
|
|
return {
|
|
left: me.paddingLeft || 0,
|
|
top: me.paddingTop || 0,
|
|
right: me.paddingRight || 0,
|
|
bottom: me.paddingBottom || 0
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Returns the scale tick objects ({label, major})
|
|
* @since 2.7
|
|
*/
|
|
getTicks: function() {
|
|
return this._ticks;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_getLabels: function() {
|
|
var data = this.chart.data;
|
|
return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels || [];
|
|
},
|
|
|
|
// These methods are ordered by lifecyle. Utilities then follow.
|
|
// Any function defined here is inherited by all scale types.
|
|
// Any function can be extended by the scale type
|
|
|
|
/**
|
|
* Provided for backward compatibility, not available anymore
|
|
* @function Chart.Scale.mergeTicksOptions
|
|
* @deprecated since version 2.8.0
|
|
* @todo remove at version 3
|
|
*/
|
|
mergeTicksOptions: function() {
|
|
// noop
|
|
},
|
|
|
|
beforeUpdate: function() {
|
|
helpers$1.callback(this.options.beforeUpdate, [this]);
|
|
},
|
|
|
|
/**
|
|
* @param {number} maxWidth - the max width in pixels
|
|
* @param {number} maxHeight - the max height in pixels
|
|
* @param {object} margins - the space between the edge of the other scales and edge of the chart
|
|
* This space comes from two sources:
|
|
* - padding - space that's required to show the labels at the edges of the scale
|
|
* - thickness of scales or legends in another orientation
|
|
*/
|
|
update: function(maxWidth, maxHeight, margins) {
|
|
var me = this;
|
|
var tickOpts = me.options.ticks;
|
|
var sampleSize = tickOpts.sampleSize;
|
|
var i, ilen, labels, ticks, samplingEnabled;
|
|
|
|
// Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
|
|
me.beforeUpdate();
|
|
|
|
// Absorb the master measurements
|
|
me.maxWidth = maxWidth;
|
|
me.maxHeight = maxHeight;
|
|
me.margins = helpers$1.extend({
|
|
left: 0,
|
|
right: 0,
|
|
top: 0,
|
|
bottom: 0
|
|
}, margins);
|
|
|
|
me._ticks = null;
|
|
me.ticks = null;
|
|
me._labelSizes = null;
|
|
me._maxLabelLines = 0;
|
|
me.longestLabelWidth = 0;
|
|
me.longestTextCache = me.longestTextCache || {};
|
|
me._gridLineItems = null;
|
|
me._labelItems = null;
|
|
|
|
// Dimensions
|
|
me.beforeSetDimensions();
|
|
me.setDimensions();
|
|
me.afterSetDimensions();
|
|
|
|
// Data min/max
|
|
me.beforeDataLimits();
|
|
me.determineDataLimits();
|
|
me.afterDataLimits();
|
|
|
|
// Ticks - `this.ticks` is now DEPRECATED!
|
|
// Internal ticks are now stored as objects in the PRIVATE `this._ticks` member
|
|
// and must not be accessed directly from outside this class. `this.ticks` being
|
|
// around for long time and not marked as private, we can't change its structure
|
|
// without unexpected breaking changes. If you need to access the scale ticks,
|
|
// use scale.getTicks() instead.
|
|
|
|
me.beforeBuildTicks();
|
|
|
|
// New implementations should return an array of objects but for BACKWARD COMPAT,
|
|
// we still support no return (`this.ticks` internally set by calling this method).
|
|
ticks = me.buildTicks() || [];
|
|
|
|
// Allow modification of ticks in callback.
|
|
ticks = me.afterBuildTicks(ticks) || ticks;
|
|
|
|
// Ensure ticks contains ticks in new tick format
|
|
if ((!ticks || !ticks.length) && me.ticks) {
|
|
ticks = [];
|
|
for (i = 0, ilen = me.ticks.length; i < ilen; ++i) {
|
|
ticks.push({
|
|
value: me.ticks[i],
|
|
major: false
|
|
});
|
|
}
|
|
}
|
|
|
|
me._ticks = ticks;
|
|
|
|
// Compute tick rotation and fit using a sampled subset of labels
|
|
// We generally don't need to compute the size of every single label for determining scale size
|
|
samplingEnabled = sampleSize < ticks.length;
|
|
labels = me._convertTicksToLabels(samplingEnabled ? sample(ticks, sampleSize) : ticks);
|
|
|
|
// _configure is called twice, once here, once from core.controller.updateLayout.
|
|
// Here we haven't been positioned yet, but dimensions are correct.
|
|
// Variables set in _configure are needed for calculateTickRotation, and
|
|
// it's ok that coordinates are not correct there, only dimensions matter.
|
|
me._configure();
|
|
|
|
// Tick Rotation
|
|
me.beforeCalculateTickRotation();
|
|
me.calculateTickRotation();
|
|
me.afterCalculateTickRotation();
|
|
|
|
me.beforeFit();
|
|
me.fit();
|
|
me.afterFit();
|
|
|
|
// Auto-skip
|
|
me._ticksToDraw = tickOpts.display && (tickOpts.autoSkip || tickOpts.source === 'auto') ? me._autoSkip(ticks) : ticks;
|
|
|
|
if (samplingEnabled) {
|
|
// Generate labels using all non-skipped ticks
|
|
labels = me._convertTicksToLabels(me._ticksToDraw);
|
|
}
|
|
|
|
me.ticks = labels; // BACKWARD COMPATIBILITY
|
|
|
|
// IMPORTANT: after this point, we consider that `this.ticks` will NEVER change!
|
|
|
|
me.afterUpdate();
|
|
|
|
// TODO(v3): remove minSize as a public property and return value from all layout boxes. It is unused
|
|
// make maxWidth and maxHeight private
|
|
return me.minSize;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_configure: function() {
|
|
var me = this;
|
|
var reversePixels = me.options.ticks.reverse;
|
|
var startPixel, endPixel;
|
|
|
|
if (me.isHorizontal()) {
|
|
startPixel = me.left;
|
|
endPixel = me.right;
|
|
} else {
|
|
startPixel = me.top;
|
|
endPixel = me.bottom;
|
|
// by default vertical scales are from bottom to top, so pixels are reversed
|
|
reversePixels = !reversePixels;
|
|
}
|
|
me._startPixel = startPixel;
|
|
me._endPixel = endPixel;
|
|
me._reversePixels = reversePixels;
|
|
me._length = endPixel - startPixel;
|
|
},
|
|
|
|
afterUpdate: function() {
|
|
helpers$1.callback(this.options.afterUpdate, [this]);
|
|
},
|
|
|
|
//
|
|
|
|
beforeSetDimensions: function() {
|
|
helpers$1.callback(this.options.beforeSetDimensions, [this]);
|
|
},
|
|
setDimensions: function() {
|
|
var me = this;
|
|
// Set the unconstrained dimension before label rotation
|
|
if (me.isHorizontal()) {
|
|
// Reset position before calculating rotation
|
|
me.width = me.maxWidth;
|
|
me.left = 0;
|
|
me.right = me.width;
|
|
} else {
|
|
me.height = me.maxHeight;
|
|
|
|
// Reset position before calculating rotation
|
|
me.top = 0;
|
|
me.bottom = me.height;
|
|
}
|
|
|
|
// Reset padding
|
|
me.paddingLeft = 0;
|
|
me.paddingTop = 0;
|
|
me.paddingRight = 0;
|
|
me.paddingBottom = 0;
|
|
},
|
|
afterSetDimensions: function() {
|
|
helpers$1.callback(this.options.afterSetDimensions, [this]);
|
|
},
|
|
|
|
// Data limits
|
|
beforeDataLimits: function() {
|
|
helpers$1.callback(this.options.beforeDataLimits, [this]);
|
|
},
|
|
determineDataLimits: helpers$1.noop,
|
|
afterDataLimits: function() {
|
|
helpers$1.callback(this.options.afterDataLimits, [this]);
|
|
},
|
|
|
|
//
|
|
beforeBuildTicks: function() {
|
|
helpers$1.callback(this.options.beforeBuildTicks, [this]);
|
|
},
|
|
buildTicks: helpers$1.noop,
|
|
afterBuildTicks: function(ticks) {
|
|
var me = this;
|
|
// ticks is empty for old axis implementations here
|
|
if (isArray(ticks) && ticks.length) {
|
|
return helpers$1.callback(me.options.afterBuildTicks, [me, ticks]);
|
|
}
|
|
// Support old implementations (that modified `this.ticks` directly in buildTicks)
|
|
me.ticks = helpers$1.callback(me.options.afterBuildTicks, [me, me.ticks]) || me.ticks;
|
|
return ticks;
|
|
},
|
|
|
|
beforeTickToLabelConversion: function() {
|
|
helpers$1.callback(this.options.beforeTickToLabelConversion, [this]);
|
|
},
|
|
convertTicksToLabels: function() {
|
|
var me = this;
|
|
// Convert ticks to strings
|
|
var tickOpts = me.options.ticks;
|
|
me.ticks = me.ticks.map(tickOpts.userCallback || tickOpts.callback, this);
|
|
},
|
|
afterTickToLabelConversion: function() {
|
|
helpers$1.callback(this.options.afterTickToLabelConversion, [this]);
|
|
},
|
|
|
|
//
|
|
|
|
beforeCalculateTickRotation: function() {
|
|
helpers$1.callback(this.options.beforeCalculateTickRotation, [this]);
|
|
},
|
|
calculateTickRotation: function() {
|
|
var me = this;
|
|
var options = me.options;
|
|
var tickOpts = options.ticks;
|
|
var numTicks = me.getTicks().length;
|
|
var minRotation = tickOpts.minRotation || 0;
|
|
var maxRotation = tickOpts.maxRotation;
|
|
var labelRotation = minRotation;
|
|
var labelSizes, maxLabelWidth, maxLabelHeight, maxWidth, tickWidth, maxHeight, maxLabelDiagonal;
|
|
|
|
if (!me._isVisible() || !tickOpts.display || minRotation >= maxRotation || numTicks <= 1 || !me.isHorizontal()) {
|
|
me.labelRotation = minRotation;
|
|
return;
|
|
}
|
|
|
|
labelSizes = me._getLabelSizes();
|
|
maxLabelWidth = labelSizes.widest.width;
|
|
maxLabelHeight = labelSizes.highest.height - labelSizes.highest.offset;
|
|
|
|
// Estimate the width of each grid based on the canvas width, the maximum
|
|
// label width and the number of tick intervals
|
|
maxWidth = Math.min(me.maxWidth, me.chart.width - maxLabelWidth);
|
|
tickWidth = options.offset ? me.maxWidth / numTicks : maxWidth / (numTicks - 1);
|
|
|
|
// Allow 3 pixels x2 padding either side for label readability
|
|
if (maxLabelWidth + 6 > tickWidth) {
|
|
tickWidth = maxWidth / (numTicks - (options.offset ? 0.5 : 1));
|
|
maxHeight = me.maxHeight - getTickMarkLength(options.gridLines)
|
|
- tickOpts.padding - getScaleLabelHeight(options.scaleLabel);
|
|
maxLabelDiagonal = Math.sqrt(maxLabelWidth * maxLabelWidth + maxLabelHeight * maxLabelHeight);
|
|
labelRotation = helpers$1.toDegrees(Math.min(
|
|
Math.asin(Math.min((labelSizes.highest.height + 6) / tickWidth, 1)),
|
|
Math.asin(Math.min(maxHeight / maxLabelDiagonal, 1)) - Math.asin(maxLabelHeight / maxLabelDiagonal)
|
|
));
|
|
labelRotation = Math.max(minRotation, Math.min(maxRotation, labelRotation));
|
|
}
|
|
|
|
me.labelRotation = labelRotation;
|
|
},
|
|
afterCalculateTickRotation: function() {
|
|
helpers$1.callback(this.options.afterCalculateTickRotation, [this]);
|
|
},
|
|
|
|
//
|
|
|
|
beforeFit: function() {
|
|
helpers$1.callback(this.options.beforeFit, [this]);
|
|
},
|
|
fit: function() {
|
|
var me = this;
|
|
// Reset
|
|
var minSize = me.minSize = {
|
|
width: 0,
|
|
height: 0
|
|
};
|
|
|
|
var chart = me.chart;
|
|
var opts = me.options;
|
|
var tickOpts = opts.ticks;
|
|
var scaleLabelOpts = opts.scaleLabel;
|
|
var gridLineOpts = opts.gridLines;
|
|
var display = me._isVisible();
|
|
var isBottom = opts.position === 'bottom';
|
|
var isHorizontal = me.isHorizontal();
|
|
|
|
// Width
|
|
if (isHorizontal) {
|
|
minSize.width = me.maxWidth;
|
|
} else if (display) {
|
|
minSize.width = getTickMarkLength(gridLineOpts) + getScaleLabelHeight(scaleLabelOpts);
|
|
}
|
|
|
|
// height
|
|
if (!isHorizontal) {
|
|
minSize.height = me.maxHeight; // fill all the height
|
|
} else if (display) {
|
|
minSize.height = getTickMarkLength(gridLineOpts) + getScaleLabelHeight(scaleLabelOpts);
|
|
}
|
|
|
|
// Don't bother fitting the ticks if we are not showing the labels
|
|
if (tickOpts.display && display) {
|
|
var tickFonts = parseTickFontOptions(tickOpts);
|
|
var labelSizes = me._getLabelSizes();
|
|
var firstLabelSize = labelSizes.first;
|
|
var lastLabelSize = labelSizes.last;
|
|
var widestLabelSize = labelSizes.widest;
|
|
var highestLabelSize = labelSizes.highest;
|
|
var lineSpace = tickFonts.minor.lineHeight * 0.4;
|
|
var tickPadding = tickOpts.padding;
|
|
|
|
if (isHorizontal) {
|
|
// A horizontal axis is more constrained by the height.
|
|
var isRotated = me.labelRotation !== 0;
|
|
var angleRadians = helpers$1.toRadians(me.labelRotation);
|
|
var cosRotation = Math.cos(angleRadians);
|
|
var sinRotation = Math.sin(angleRadians);
|
|
|
|
var labelHeight = sinRotation * widestLabelSize.width
|
|
+ cosRotation * (highestLabelSize.height - (isRotated ? highestLabelSize.offset : 0))
|
|
+ (isRotated ? 0 : lineSpace); // padding
|
|
|
|
minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight + tickPadding);
|
|
|
|
var offsetLeft = me.getPixelForTick(0) - me.left;
|
|
var offsetRight = me.right - me.getPixelForTick(me.getTicks().length - 1);
|
|
var paddingLeft, paddingRight;
|
|
|
|
// Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned
|
|
// which means that the right padding is dominated by the font height
|
|
if (isRotated) {
|
|
paddingLeft = isBottom ?
|
|
cosRotation * firstLabelSize.width + sinRotation * firstLabelSize.offset :
|
|
sinRotation * (firstLabelSize.height - firstLabelSize.offset);
|
|
paddingRight = isBottom ?
|
|
sinRotation * (lastLabelSize.height - lastLabelSize.offset) :
|
|
cosRotation * lastLabelSize.width + sinRotation * lastLabelSize.offset;
|
|
} else {
|
|
paddingLeft = firstLabelSize.width / 2;
|
|
paddingRight = lastLabelSize.width / 2;
|
|
}
|
|
|
|
// Adjust padding taking into account changes in offsets
|
|
// and add 3 px to move away from canvas edges
|
|
me.paddingLeft = Math.max((paddingLeft - offsetLeft) * me.width / (me.width - offsetLeft), 0) + 3;
|
|
me.paddingRight = Math.max((paddingRight - offsetRight) * me.width / (me.width - offsetRight), 0) + 3;
|
|
} else {
|
|
// A vertical axis is more constrained by the width. Labels are the
|
|
// dominant factor here, so get that length first and account for padding
|
|
var labelWidth = tickOpts.mirror ? 0 :
|
|
// use lineSpace for consistency with horizontal axis
|
|
// tickPadding is not implemented for horizontal
|
|
widestLabelSize.width + tickPadding + lineSpace;
|
|
|
|
minSize.width = Math.min(me.maxWidth, minSize.width + labelWidth);
|
|
|
|
me.paddingTop = firstLabelSize.height / 2;
|
|
me.paddingBottom = lastLabelSize.height / 2;
|
|
}
|
|
}
|
|
|
|
me.handleMargins();
|
|
|
|
if (isHorizontal) {
|
|
me.width = me._length = chart.width - me.margins.left - me.margins.right;
|
|
me.height = minSize.height;
|
|
} else {
|
|
me.width = minSize.width;
|
|
me.height = me._length = chart.height - me.margins.top - me.margins.bottom;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Handle margins and padding interactions
|
|
* @private
|
|
*/
|
|
handleMargins: function() {
|
|
var me = this;
|
|
if (me.margins) {
|
|
me.margins.left = Math.max(me.paddingLeft, me.margins.left);
|
|
me.margins.top = Math.max(me.paddingTop, me.margins.top);
|
|
me.margins.right = Math.max(me.paddingRight, me.margins.right);
|
|
me.margins.bottom = Math.max(me.paddingBottom, me.margins.bottom);
|
|
}
|
|
},
|
|
|
|
afterFit: function() {
|
|
helpers$1.callback(this.options.afterFit, [this]);
|
|
},
|
|
|
|
// Shared Methods
|
|
isHorizontal: function() {
|
|
var pos = this.options.position;
|
|
return pos === 'top' || pos === 'bottom';
|
|
},
|
|
isFullWidth: function() {
|
|
return this.options.fullWidth;
|
|
},
|
|
|
|
// Get the correct value. NaN bad inputs, If the value type is object get the x or y based on whether we are horizontal or not
|
|
getRightValue: function(rawValue) {
|
|
// Null and undefined values first
|
|
if (isNullOrUndef(rawValue)) {
|
|
return NaN;
|
|
}
|
|
// isNaN(object) returns true, so make sure NaN is checking for a number; Discard Infinite values
|
|
if ((typeof rawValue === 'number' || rawValue instanceof Number) && !isFinite(rawValue)) {
|
|
return NaN;
|
|
}
|
|
|
|
// If it is in fact an object, dive in one more level
|
|
if (rawValue) {
|
|
if (this.isHorizontal()) {
|
|
if (rawValue.x !== undefined) {
|
|
return this.getRightValue(rawValue.x);
|
|
}
|
|
} else if (rawValue.y !== undefined) {
|
|
return this.getRightValue(rawValue.y);
|
|
}
|
|
}
|
|
|
|
// Value is good, return it
|
|
return rawValue;
|
|
},
|
|
|
|
_convertTicksToLabels: function(ticks) {
|
|
var me = this;
|
|
var labels, i, ilen;
|
|
|
|
me.ticks = ticks.map(function(tick) {
|
|
return tick.value;
|
|
});
|
|
|
|
me.beforeTickToLabelConversion();
|
|
|
|
// New implementations should return the formatted tick labels but for BACKWARD
|
|
// COMPAT, we still support no return (`this.ticks` internally changed by calling
|
|
// this method and supposed to contain only string values).
|
|
labels = me.convertTicksToLabels(ticks) || me.ticks;
|
|
|
|
me.afterTickToLabelConversion();
|
|
|
|
// BACKWARD COMPAT: synchronize `_ticks` with labels (so potentially `this.ticks`)
|
|
for (i = 0, ilen = ticks.length; i < ilen; ++i) {
|
|
ticks[i].label = labels[i];
|
|
}
|
|
|
|
return labels;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_getLabelSizes: function() {
|
|
var me = this;
|
|
var labelSizes = me._labelSizes;
|
|
|
|
if (!labelSizes) {
|
|
me._labelSizes = labelSizes = computeLabelSizes(me.ctx, parseTickFontOptions(me.options.ticks), me.getTicks(), me.longestTextCache);
|
|
me.longestLabelWidth = labelSizes.widest.width;
|
|
}
|
|
|
|
return labelSizes;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_parseValue: function(value) {
|
|
var start, end, min, max;
|
|
|
|
if (isArray(value)) {
|
|
start = +this.getRightValue(value[0]);
|
|
end = +this.getRightValue(value[1]);
|
|
min = Math.min(start, end);
|
|
max = Math.max(start, end);
|
|
} else {
|
|
value = +this.getRightValue(value);
|
|
start = undefined;
|
|
end = value;
|
|
min = value;
|
|
max = value;
|
|
}
|
|
|
|
return {
|
|
min: min,
|
|
max: max,
|
|
start: start,
|
|
end: end
|
|
};
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_getScaleLabel: function(rawValue) {
|
|
var v = this._parseValue(rawValue);
|
|
if (v.start !== undefined) {
|
|
return '[' + v.start + ', ' + v.end + ']';
|
|
}
|
|
|
|
return +this.getRightValue(rawValue);
|
|
},
|
|
|
|
/**
|
|
* Used to get the value to display in the tooltip for the data at the given index
|
|
* @param index
|
|
* @param datasetIndex
|
|
*/
|
|
getLabelForIndex: helpers$1.noop,
|
|
|
|
/**
|
|
* Returns the location of the given data point. Value can either be an index or a numerical value
|
|
* The coordinate (0, 0) is at the upper-left corner of the canvas
|
|
* @param value
|
|
* @param index
|
|
* @param datasetIndex
|
|
*/
|
|
getPixelForValue: helpers$1.noop,
|
|
|
|
/**
|
|
* Used to get the data value from a given pixel. This is the inverse of getPixelForValue
|
|
* The coordinate (0, 0) is at the upper-left corner of the canvas
|
|
* @param pixel
|
|
*/
|
|
getValueForPixel: helpers$1.noop,
|
|
|
|
/**
|
|
* Returns the location of the tick at the given index
|
|
* The coordinate (0, 0) is at the upper-left corner of the canvas
|
|
*/
|
|
getPixelForTick: function(index) {
|
|
var me = this;
|
|
var offset = me.options.offset;
|
|
var numTicks = me._ticks.length;
|
|
var tickWidth = 1 / Math.max(numTicks - (offset ? 0 : 1), 1);
|
|
|
|
return index < 0 || index > numTicks - 1
|
|
? null
|
|
: me.getPixelForDecimal(index * tickWidth + (offset ? tickWidth / 2 : 0));
|
|
},
|
|
|
|
/**
|
|
* Utility for getting the pixel location of a percentage of scale
|
|
* The coordinate (0, 0) is at the upper-left corner of the canvas
|
|
*/
|
|
getPixelForDecimal: function(decimal) {
|
|
var me = this;
|
|
|
|
if (me._reversePixels) {
|
|
decimal = 1 - decimal;
|
|
}
|
|
|
|
return me._startPixel + decimal * me._length;
|
|
},
|
|
|
|
getDecimalForPixel: function(pixel) {
|
|
var decimal = (pixel - this._startPixel) / this._length;
|
|
return this._reversePixels ? 1 - decimal : decimal;
|
|
},
|
|
|
|
/**
|
|
* Returns the pixel for the minimum chart value
|
|
* The coordinate (0, 0) is at the upper-left corner of the canvas
|
|
*/
|
|
getBasePixel: function() {
|
|
return this.getPixelForValue(this.getBaseValue());
|
|
},
|
|
|
|
getBaseValue: function() {
|
|
var me = this;
|
|
var min = me.min;
|
|
var max = me.max;
|
|
|
|
return me.beginAtZero ? 0 :
|
|
min < 0 && max < 0 ? max :
|
|
min > 0 && max > 0 ? min :
|
|
0;
|
|
},
|
|
|
|
/**
|
|
* Returns a subset of ticks to be plotted to avoid overlapping labels.
|
|
* @private
|
|
*/
|
|
_autoSkip: function(ticks) {
|
|
var me = this;
|
|
var tickOpts = me.options.ticks;
|
|
var axisLength = me._length;
|
|
var ticksLimit = tickOpts.maxTicksLimit || axisLength / me._tickSize() + 1;
|
|
var majorIndices = tickOpts.major.enabled ? getMajorIndices(ticks) : [];
|
|
var numMajorIndices = majorIndices.length;
|
|
var first = majorIndices[0];
|
|
var last = majorIndices[numMajorIndices - 1];
|
|
var i, ilen, spacing, avgMajorSpacing;
|
|
|
|
// If there are too many major ticks to display them all
|
|
if (numMajorIndices > ticksLimit) {
|
|
skipMajors(ticks, majorIndices, numMajorIndices / ticksLimit);
|
|
return nonSkipped(ticks);
|
|
}
|
|
|
|
spacing = calculateSpacing(majorIndices, ticks, axisLength, ticksLimit);
|
|
|
|
if (numMajorIndices > 0) {
|
|
for (i = 0, ilen = numMajorIndices - 1; i < ilen; i++) {
|
|
skip(ticks, spacing, majorIndices[i], majorIndices[i + 1]);
|
|
}
|
|
avgMajorSpacing = numMajorIndices > 1 ? (last - first) / (numMajorIndices - 1) : null;
|
|
skip(ticks, spacing, helpers$1.isNullOrUndef(avgMajorSpacing) ? 0 : first - avgMajorSpacing, first);
|
|
skip(ticks, spacing, last, helpers$1.isNullOrUndef(avgMajorSpacing) ? ticks.length : last + avgMajorSpacing);
|
|
return nonSkipped(ticks);
|
|
}
|
|
skip(ticks, spacing);
|
|
return nonSkipped(ticks);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_tickSize: function() {
|
|
var me = this;
|
|
var optionTicks = me.options.ticks;
|
|
|
|
// Calculate space needed by label in axis direction.
|
|
var rot = helpers$1.toRadians(me.labelRotation);
|
|
var cos = Math.abs(Math.cos(rot));
|
|
var sin = Math.abs(Math.sin(rot));
|
|
|
|
var labelSizes = me._getLabelSizes();
|
|
var padding = optionTicks.autoSkipPadding || 0;
|
|
var w = labelSizes ? labelSizes.widest.width + padding : 0;
|
|
var h = labelSizes ? labelSizes.highest.height + padding : 0;
|
|
|
|
// Calculate space needed for 1 tick in axis direction.
|
|
return me.isHorizontal()
|
|
? h * cos > w * sin ? w / cos : h / sin
|
|
: h * sin < w * cos ? h / cos : w / sin;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_isVisible: function() {
|
|
var me = this;
|
|
var chart = me.chart;
|
|
var display = me.options.display;
|
|
var i, ilen, meta;
|
|
|
|
if (display !== 'auto') {
|
|
return !!display;
|
|
}
|
|
|
|
// When 'auto', the scale is visible if at least one associated dataset is visible.
|
|
for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) {
|
|
if (chart.isDatasetVisible(i)) {
|
|
meta = chart.getDatasetMeta(i);
|
|
if (meta.xAxisID === me.id || meta.yAxisID === me.id) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_computeGridLineItems: function(chartArea) {
|
|
var me = this;
|
|
var chart = me.chart;
|
|
var options = me.options;
|
|
var gridLines = options.gridLines;
|
|
var position = options.position;
|
|
var offsetGridLines = gridLines.offsetGridLines;
|
|
var isHorizontal = me.isHorizontal();
|
|
var ticks = me._ticksToDraw;
|
|
var ticksLength = ticks.length + (offsetGridLines ? 1 : 0);
|
|
|
|
var tl = getTickMarkLength(gridLines);
|
|
var items = [];
|
|
var axisWidth = gridLines.drawBorder ? valueAtIndexOrDefault(gridLines.lineWidth, 0, 0) : 0;
|
|
var axisHalfWidth = axisWidth / 2;
|
|
var alignPixel = helpers$1._alignPixel;
|
|
var alignBorderValue = function(pixel) {
|
|
return alignPixel(chart, pixel, axisWidth);
|
|
};
|
|
var borderValue, i, tick, lineValue, alignedLineValue;
|
|
var tx1, ty1, tx2, ty2, x1, y1, x2, y2, lineWidth, lineColor, borderDash, borderDashOffset;
|
|
|
|
if (position === 'top') {
|
|
borderValue = alignBorderValue(me.bottom);
|
|
ty1 = me.bottom - tl;
|
|
ty2 = borderValue - axisHalfWidth;
|
|
y1 = alignBorderValue(chartArea.top) + axisHalfWidth;
|
|
y2 = chartArea.bottom;
|
|
} else if (position === 'bottom') {
|
|
borderValue = alignBorderValue(me.top);
|
|
y1 = chartArea.top;
|
|
y2 = alignBorderValue(chartArea.bottom) - axisHalfWidth;
|
|
ty1 = borderValue + axisHalfWidth;
|
|
ty2 = me.top + tl;
|
|
} else if (position === 'left') {
|
|
borderValue = alignBorderValue(me.right);
|
|
tx1 = me.right - tl;
|
|
tx2 = borderValue - axisHalfWidth;
|
|
x1 = alignBorderValue(chartArea.left) + axisHalfWidth;
|
|
x2 = chartArea.right;
|
|
} else {
|
|
borderValue = alignBorderValue(me.left);
|
|
x1 = chartArea.left;
|
|
x2 = alignBorderValue(chartArea.right) - axisHalfWidth;
|
|
tx1 = borderValue + axisHalfWidth;
|
|
tx2 = me.left + tl;
|
|
}
|
|
|
|
for (i = 0; i < ticksLength; ++i) {
|
|
tick = ticks[i] || {};
|
|
|
|
// autoskipper skipped this tick (#4635)
|
|
if (isNullOrUndef(tick.label) && i < ticks.length) {
|
|
continue;
|
|
}
|
|
|
|
if (i === me.zeroLineIndex && options.offset === offsetGridLines) {
|
|
// Draw the first index specially
|
|
lineWidth = gridLines.zeroLineWidth;
|
|
lineColor = gridLines.zeroLineColor;
|
|
borderDash = gridLines.zeroLineBorderDash || [];
|
|
borderDashOffset = gridLines.zeroLineBorderDashOffset || 0.0;
|
|
} else {
|
|
lineWidth = valueAtIndexOrDefault(gridLines.lineWidth, i, 1);
|
|
lineColor = valueAtIndexOrDefault(gridLines.color, i, 'rgba(0,0,0,0.1)');
|
|
borderDash = gridLines.borderDash || [];
|
|
borderDashOffset = gridLines.borderDashOffset || 0.0;
|
|
}
|
|
|
|
lineValue = getPixelForGridLine(me, tick._index || i, offsetGridLines);
|
|
|
|
// Skip if the pixel is out of the range
|
|
if (lineValue === undefined) {
|
|
continue;
|
|
}
|
|
|
|
alignedLineValue = alignPixel(chart, lineValue, lineWidth);
|
|
|
|
if (isHorizontal) {
|
|
tx1 = tx2 = x1 = x2 = alignedLineValue;
|
|
} else {
|
|
ty1 = ty2 = y1 = y2 = alignedLineValue;
|
|
}
|
|
|
|
items.push({
|
|
tx1: tx1,
|
|
ty1: ty1,
|
|
tx2: tx2,
|
|
ty2: ty2,
|
|
x1: x1,
|
|
y1: y1,
|
|
x2: x2,
|
|
y2: y2,
|
|
width: lineWidth,
|
|
color: lineColor,
|
|
borderDash: borderDash,
|
|
borderDashOffset: borderDashOffset,
|
|
});
|
|
}
|
|
|
|
items.ticksLength = ticksLength;
|
|
items.borderValue = borderValue;
|
|
|
|
return items;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_computeLabelItems: function() {
|
|
var me = this;
|
|
var options = me.options;
|
|
var optionTicks = options.ticks;
|
|
var position = options.position;
|
|
var isMirrored = optionTicks.mirror;
|
|
var isHorizontal = me.isHorizontal();
|
|
var ticks = me._ticksToDraw;
|
|
var fonts = parseTickFontOptions(optionTicks);
|
|
var tickPadding = optionTicks.padding;
|
|
var tl = getTickMarkLength(options.gridLines);
|
|
var rotation = -helpers$1.toRadians(me.labelRotation);
|
|
var items = [];
|
|
var i, ilen, tick, label, x, y, textAlign, pixel, font, lineHeight, lineCount, textOffset;
|
|
|
|
if (position === 'top') {
|
|
y = me.bottom - tl - tickPadding;
|
|
textAlign = !rotation ? 'center' : 'left';
|
|
} else if (position === 'bottom') {
|
|
y = me.top + tl + tickPadding;
|
|
textAlign = !rotation ? 'center' : 'right';
|
|
} else if (position === 'left') {
|
|
x = me.right - (isMirrored ? 0 : tl) - tickPadding;
|
|
textAlign = isMirrored ? 'left' : 'right';
|
|
} else {
|
|
x = me.left + (isMirrored ? 0 : tl) + tickPadding;
|
|
textAlign = isMirrored ? 'right' : 'left';
|
|
}
|
|
|
|
for (i = 0, ilen = ticks.length; i < ilen; ++i) {
|
|
tick = ticks[i];
|
|
label = tick.label;
|
|
|
|
// autoskipper skipped this tick (#4635)
|
|
if (isNullOrUndef(label)) {
|
|
continue;
|
|
}
|
|
|
|
pixel = me.getPixelForTick(tick._index || i) + optionTicks.labelOffset;
|
|
font = tick.major ? fonts.major : fonts.minor;
|
|
lineHeight = font.lineHeight;
|
|
lineCount = isArray(label) ? label.length : 1;
|
|
|
|
if (isHorizontal) {
|
|
x = pixel;
|
|
textOffset = position === 'top'
|
|
? ((!rotation ? 0.5 : 1) - lineCount) * lineHeight
|
|
: (!rotation ? 0.5 : 0) * lineHeight;
|
|
} else {
|
|
y = pixel;
|
|
textOffset = (1 - lineCount) * lineHeight / 2;
|
|
}
|
|
|
|
items.push({
|
|
x: x,
|
|
y: y,
|
|
rotation: rotation,
|
|
label: label,
|
|
font: font,
|
|
textOffset: textOffset,
|
|
textAlign: textAlign
|
|
});
|
|
}
|
|
|
|
return items;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_drawGrid: function(chartArea) {
|
|
var me = this;
|
|
var gridLines = me.options.gridLines;
|
|
|
|
if (!gridLines.display) {
|
|
return;
|
|
}
|
|
|
|
var ctx = me.ctx;
|
|
var chart = me.chart;
|
|
var alignPixel = helpers$1._alignPixel;
|
|
var axisWidth = gridLines.drawBorder ? valueAtIndexOrDefault(gridLines.lineWidth, 0, 0) : 0;
|
|
var items = me._gridLineItems || (me._gridLineItems = me._computeGridLineItems(chartArea));
|
|
var width, color, i, ilen, item;
|
|
|
|
for (i = 0, ilen = items.length; i < ilen; ++i) {
|
|
item = items[i];
|
|
width = item.width;
|
|
color = item.color;
|
|
|
|
if (width && color) {
|
|
ctx.save();
|
|
ctx.lineWidth = width;
|
|
ctx.strokeStyle = color;
|
|
if (ctx.setLineDash) {
|
|
ctx.setLineDash(item.borderDash);
|
|
ctx.lineDashOffset = item.borderDashOffset;
|
|
}
|
|
|
|
ctx.beginPath();
|
|
|
|
if (gridLines.drawTicks) {
|
|
ctx.moveTo(item.tx1, item.ty1);
|
|
ctx.lineTo(item.tx2, item.ty2);
|
|
}
|
|
|
|
if (gridLines.drawOnChartArea) {
|
|
ctx.moveTo(item.x1, item.y1);
|
|
ctx.lineTo(item.x2, item.y2);
|
|
}
|
|
|
|
ctx.stroke();
|
|
ctx.restore();
|
|
}
|
|
}
|
|
|
|
if (axisWidth) {
|
|
// Draw the line at the edge of the axis
|
|
var firstLineWidth = axisWidth;
|
|
var lastLineWidth = valueAtIndexOrDefault(gridLines.lineWidth, items.ticksLength - 1, 1);
|
|
var borderValue = items.borderValue;
|
|
var x1, x2, y1, y2;
|
|
|
|
if (me.isHorizontal()) {
|
|
x1 = alignPixel(chart, me.left, firstLineWidth) - firstLineWidth / 2;
|
|
x2 = alignPixel(chart, me.right, lastLineWidth) + lastLineWidth / 2;
|
|
y1 = y2 = borderValue;
|
|
} else {
|
|
y1 = alignPixel(chart, me.top, firstLineWidth) - firstLineWidth / 2;
|
|
y2 = alignPixel(chart, me.bottom, lastLineWidth) + lastLineWidth / 2;
|
|
x1 = x2 = borderValue;
|
|
}
|
|
|
|
ctx.lineWidth = axisWidth;
|
|
ctx.strokeStyle = valueAtIndexOrDefault(gridLines.color, 0);
|
|
ctx.beginPath();
|
|
ctx.moveTo(x1, y1);
|
|
ctx.lineTo(x2, y2);
|
|
ctx.stroke();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_drawLabels: function() {
|
|
var me = this;
|
|
var optionTicks = me.options.ticks;
|
|
|
|
if (!optionTicks.display) {
|
|
return;
|
|
}
|
|
|
|
var ctx = me.ctx;
|
|
var items = me._labelItems || (me._labelItems = me._computeLabelItems());
|
|
var i, j, ilen, jlen, item, tickFont, label, y;
|
|
|
|
for (i = 0, ilen = items.length; i < ilen; ++i) {
|
|
item = items[i];
|
|
tickFont = item.font;
|
|
|
|
// Make sure we draw text in the correct color and font
|
|
ctx.save();
|
|
ctx.translate(item.x, item.y);
|
|
ctx.rotate(item.rotation);
|
|
ctx.font = tickFont.string;
|
|
ctx.fillStyle = tickFont.color;
|
|
ctx.textBaseline = 'middle';
|
|
ctx.textAlign = item.textAlign;
|
|
|
|
label = item.label;
|
|
y = item.textOffset;
|
|
if (isArray(label)) {
|
|
for (j = 0, jlen = label.length; j < jlen; ++j) {
|
|
// We just make sure the multiline element is a string here..
|
|
ctx.fillText('' + label[j], 0, y);
|
|
y += tickFont.lineHeight;
|
|
}
|
|
} else {
|
|
ctx.fillText(label, 0, y);
|
|
}
|
|
ctx.restore();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_drawTitle: function() {
|
|
var me = this;
|
|
var ctx = me.ctx;
|
|
var options = me.options;
|
|
var scaleLabel = options.scaleLabel;
|
|
|
|
if (!scaleLabel.display) {
|
|
return;
|
|
}
|
|
|
|
var scaleLabelFontColor = valueOrDefault$a(scaleLabel.fontColor, core_defaults.global.defaultFontColor);
|
|
var scaleLabelFont = helpers$1.options._parseFont(scaleLabel);
|
|
var scaleLabelPadding = helpers$1.options.toPadding(scaleLabel.padding);
|
|
var halfLineHeight = scaleLabelFont.lineHeight / 2;
|
|
var position = options.position;
|
|
var rotation = 0;
|
|
var scaleLabelX, scaleLabelY;
|
|
|
|
if (me.isHorizontal()) {
|
|
scaleLabelX = me.left + me.width / 2; // midpoint of the width
|
|
scaleLabelY = position === 'bottom'
|
|
? me.bottom - halfLineHeight - scaleLabelPadding.bottom
|
|
: me.top + halfLineHeight + scaleLabelPadding.top;
|
|
} else {
|
|
var isLeft = position === 'left';
|
|
scaleLabelX = isLeft
|
|
? me.left + halfLineHeight + scaleLabelPadding.top
|
|
: me.right - halfLineHeight - scaleLabelPadding.top;
|
|
scaleLabelY = me.top + me.height / 2;
|
|
rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI;
|
|
}
|
|
|
|
ctx.save();
|
|
ctx.translate(scaleLabelX, scaleLabelY);
|
|
ctx.rotate(rotation);
|
|
ctx.textAlign = 'center';
|
|
ctx.textBaseline = 'middle';
|
|
ctx.fillStyle = scaleLabelFontColor; // render in correct colour
|
|
ctx.font = scaleLabelFont.string;
|
|
ctx.fillText(scaleLabel.labelString, 0, 0);
|
|
ctx.restore();
|
|
},
|
|
|
|
draw: function(chartArea) {
|
|
var me = this;
|
|
|
|
if (!me._isVisible()) {
|
|
return;
|
|
}
|
|
|
|
me._drawGrid(chartArea);
|
|
me._drawTitle();
|
|
me._drawLabels();
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_layers: function() {
|
|
var me = this;
|
|
var opts = me.options;
|
|
var tz = opts.ticks && opts.ticks.z || 0;
|
|
var gz = opts.gridLines && opts.gridLines.z || 0;
|
|
|
|
if (!me._isVisible() || tz === gz || me.draw !== me._draw) {
|
|
// backward compatibility: draw has been overridden by custom scale
|
|
return [{
|
|
z: tz,
|
|
draw: function() {
|
|
me.draw.apply(me, arguments);
|
|
}
|
|
}];
|
|
}
|
|
|
|
return [{
|
|
z: gz,
|
|
draw: function() {
|
|
me._drawGrid.apply(me, arguments);
|
|
me._drawTitle.apply(me, arguments);
|
|
}
|
|
}, {
|
|
z: tz,
|
|
draw: function() {
|
|
me._drawLabels.apply(me, arguments);
|
|
}
|
|
}];
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_getMatchingVisibleMetas: function(type) {
|
|
var me = this;
|
|
var isHorizontal = me.isHorizontal();
|
|
return me.chart._getSortedVisibleDatasetMetas()
|
|
.filter(function(meta) {
|
|
return (!type || meta.type === type)
|
|
&& (isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id);
|
|
});
|
|
}
|
|
});
|
|
|
|
Scale.prototype._draw = Scale.prototype.draw;
|
|
|
|
var core_scale = Scale;
|
|
|
|
var isNullOrUndef$1 = helpers$1.isNullOrUndef;
|
|
|
|
var defaultConfig = {
|
|
position: 'bottom'
|
|
};
|
|
|
|
var scale_category = core_scale.extend({
|
|
determineDataLimits: function() {
|
|
var me = this;
|
|
var labels = me._getLabels();
|
|
var ticksOpts = me.options.ticks;
|
|
var min = ticksOpts.min;
|
|
var max = ticksOpts.max;
|
|
var minIndex = 0;
|
|
var maxIndex = labels.length - 1;
|
|
var findIndex;
|
|
|
|
if (min !== undefined) {
|
|
// user specified min value
|
|
findIndex = labels.indexOf(min);
|
|
if (findIndex >= 0) {
|
|
minIndex = findIndex;
|
|
}
|
|
}
|
|
|
|
if (max !== undefined) {
|
|
// user specified max value
|
|
findIndex = labels.indexOf(max);
|
|
if (findIndex >= 0) {
|
|
maxIndex = findIndex;
|
|
}
|
|
}
|
|
|
|
me.minIndex = minIndex;
|
|
me.maxIndex = maxIndex;
|
|
me.min = labels[minIndex];
|
|
me.max = labels[maxIndex];
|
|
},
|
|
|
|
buildTicks: function() {
|
|
var me = this;
|
|
var labels = me._getLabels();
|
|
var minIndex = me.minIndex;
|
|
var maxIndex = me.maxIndex;
|
|
|
|
// If we are viewing some subset of labels, slice the original array
|
|
me.ticks = (minIndex === 0 && maxIndex === labels.length - 1) ? labels : labels.slice(minIndex, maxIndex + 1);
|
|
},
|
|
|
|
getLabelForIndex: function(index, datasetIndex) {
|
|
var me = this;
|
|
var chart = me.chart;
|
|
|
|
if (chart.getDatasetMeta(datasetIndex).controller._getValueScaleId() === me.id) {
|
|
return me.getRightValue(chart.data.datasets[datasetIndex].data[index]);
|
|
}
|
|
|
|
return me._getLabels()[index];
|
|
},
|
|
|
|
_configure: function() {
|
|
var me = this;
|
|
var offset = me.options.offset;
|
|
var ticks = me.ticks;
|
|
|
|
core_scale.prototype._configure.call(me);
|
|
|
|
if (!me.isHorizontal()) {
|
|
// For backward compatibility, vertical category scale reverse is inverted.
|
|
me._reversePixels = !me._reversePixels;
|
|
}
|
|
|
|
if (!ticks) {
|
|
return;
|
|
}
|
|
|
|
me._startValue = me.minIndex - (offset ? 0.5 : 0);
|
|
me._valueRange = Math.max(ticks.length - (offset ? 0 : 1), 1);
|
|
},
|
|
|
|
// Used to get data value locations. Value can either be an index or a numerical value
|
|
getPixelForValue: function(value, index, datasetIndex) {
|
|
var me = this;
|
|
var valueCategory, labels, idx;
|
|
|
|
if (!isNullOrUndef$1(index) && !isNullOrUndef$1(datasetIndex)) {
|
|
value = me.chart.data.datasets[datasetIndex].data[index];
|
|
}
|
|
|
|
// If value is a data object, then index is the index in the data array,
|
|
// not the index of the scale. We need to change that.
|
|
if (!isNullOrUndef$1(value)) {
|
|
valueCategory = me.isHorizontal() ? value.x : value.y;
|
|
}
|
|
if (valueCategory !== undefined || (value !== undefined && isNaN(index))) {
|
|
labels = me._getLabels();
|
|
value = helpers$1.valueOrDefault(valueCategory, value);
|
|
idx = labels.indexOf(value);
|
|
index = idx !== -1 ? idx : index;
|
|
if (isNaN(index)) {
|
|
index = value;
|
|
}
|
|
}
|
|
return me.getPixelForDecimal((index - me._startValue) / me._valueRange);
|
|
},
|
|
|
|
getPixelForTick: function(index) {
|
|
var ticks = this.ticks;
|
|
return index < 0 || index > ticks.length - 1
|
|
? null
|
|
: this.getPixelForValue(ticks[index], index + this.minIndex);
|
|
},
|
|
|
|
getValueForPixel: function(pixel) {
|
|
var me = this;
|
|
var value = Math.round(me._startValue + me.getDecimalForPixel(pixel) * me._valueRange);
|
|
return Math.min(Math.max(value, 0), me.ticks.length - 1);
|
|
},
|
|
|
|
getBasePixel: function() {
|
|
return this.bottom;
|
|
}
|
|
});
|
|
|
|
// INTERNAL: static default options, registered in src/index.js
|
|
var _defaults = defaultConfig;
|
|
scale_category._defaults = _defaults;
|
|
|
|
var noop = helpers$1.noop;
|
|
var isNullOrUndef$2 = helpers$1.isNullOrUndef;
|
|
|
|
/**
|
|
* Generate a set of linear ticks
|
|
* @param generationOptions the options used to generate the ticks
|
|
* @param dataRange the range of the data
|
|
* @returns {number[]} array of tick values
|
|
*/
|
|
function generateTicks(generationOptions, dataRange) {
|
|
var ticks = [];
|
|
// To get a "nice" value for the tick spacing, we will use the appropriately named
|
|
// "nice number" algorithm. See https://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks
|
|
// for details.
|
|
|
|
var MIN_SPACING = 1e-14;
|
|
var stepSize = generationOptions.stepSize;
|
|
var unit = stepSize || 1;
|
|
var maxNumSpaces = generationOptions.maxTicks - 1;
|
|
var min = generationOptions.min;
|
|
var max = generationOptions.max;
|
|
var precision = generationOptions.precision;
|
|
var rmin = dataRange.min;
|
|
var rmax = dataRange.max;
|
|
var spacing = helpers$1.niceNum((rmax - rmin) / maxNumSpaces / unit) * unit;
|
|
var factor, niceMin, niceMax, numSpaces;
|
|
|
|
// Beyond MIN_SPACING floating point numbers being to lose precision
|
|
// such that we can't do the math necessary to generate ticks
|
|
if (spacing < MIN_SPACING && isNullOrUndef$2(min) && isNullOrUndef$2(max)) {
|
|
return [rmin, rmax];
|
|
}
|
|
|
|
numSpaces = Math.ceil(rmax / spacing) - Math.floor(rmin / spacing);
|
|
if (numSpaces > maxNumSpaces) {
|
|
// If the calculated num of spaces exceeds maxNumSpaces, recalculate it
|
|
spacing = helpers$1.niceNum(numSpaces * spacing / maxNumSpaces / unit) * unit;
|
|
}
|
|
|
|
if (stepSize || isNullOrUndef$2(precision)) {
|
|
// If a precision is not specified, calculate factor based on spacing
|
|
factor = Math.pow(10, helpers$1._decimalPlaces(spacing));
|
|
} else {
|
|
// If the user specified a precision, round to that number of decimal places
|
|
factor = Math.pow(10, precision);
|
|
spacing = Math.ceil(spacing * factor) / factor;
|
|
}
|
|
|
|
niceMin = Math.floor(rmin / spacing) * spacing;
|
|
niceMax = Math.ceil(rmax / spacing) * spacing;
|
|
|
|
// If min, max and stepSize is set and they make an evenly spaced scale use it.
|
|
if (stepSize) {
|
|
// If very close to our whole number, use it.
|
|
if (!isNullOrUndef$2(min) && helpers$1.almostWhole(min / spacing, spacing / 1000)) {
|
|
niceMin = min;
|
|
}
|
|
if (!isNullOrUndef$2(max) && helpers$1.almostWhole(max / spacing, spacing / 1000)) {
|
|
niceMax = max;
|
|
}
|
|
}
|
|
|
|
numSpaces = (niceMax - niceMin) / spacing;
|
|
// If very close to our rounded value, use it.
|
|
if (helpers$1.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {
|
|
numSpaces = Math.round(numSpaces);
|
|
} else {
|
|
numSpaces = Math.ceil(numSpaces);
|
|
}
|
|
|
|
niceMin = Math.round(niceMin * factor) / factor;
|
|
niceMax = Math.round(niceMax * factor) / factor;
|
|
ticks.push(isNullOrUndef$2(min) ? niceMin : min);
|
|
for (var j = 1; j < numSpaces; ++j) {
|
|
ticks.push(Math.round((niceMin + j * spacing) * factor) / factor);
|
|
}
|
|
ticks.push(isNullOrUndef$2(max) ? niceMax : max);
|
|
|
|
return ticks;
|
|
}
|
|
|
|
var scale_linearbase = core_scale.extend({
|
|
getRightValue: function(value) {
|
|
if (typeof value === 'string') {
|
|
return +value;
|
|
}
|
|
return core_scale.prototype.getRightValue.call(this, value);
|
|
},
|
|
|
|
handleTickRangeOptions: function() {
|
|
var me = this;
|
|
var opts = me.options;
|
|
var tickOpts = opts.ticks;
|
|
|
|
// If we are forcing it to begin at 0, but 0 will already be rendered on the chart,
|
|
// do nothing since that would make the chart weird. If the user really wants a weird chart
|
|
// axis, they can manually override it
|
|
if (tickOpts.beginAtZero) {
|
|
var minSign = helpers$1.sign(me.min);
|
|
var maxSign = helpers$1.sign(me.max);
|
|
|
|
if (minSign < 0 && maxSign < 0) {
|
|
// move the top up to 0
|
|
me.max = 0;
|
|
} else if (minSign > 0 && maxSign > 0) {
|
|
// move the bottom down to 0
|
|
me.min = 0;
|
|
}
|
|
}
|
|
|
|
var setMin = tickOpts.min !== undefined || tickOpts.suggestedMin !== undefined;
|
|
var setMax = tickOpts.max !== undefined || tickOpts.suggestedMax !== undefined;
|
|
|
|
if (tickOpts.min !== undefined) {
|
|
me.min = tickOpts.min;
|
|
} else if (tickOpts.suggestedMin !== undefined) {
|
|
if (me.min === null) {
|
|
me.min = tickOpts.suggestedMin;
|
|
} else {
|
|
me.min = Math.min(me.min, tickOpts.suggestedMin);
|
|
}
|
|
}
|
|
|
|
if (tickOpts.max !== undefined) {
|
|
me.max = tickOpts.max;
|
|
} else if (tickOpts.suggestedMax !== undefined) {
|
|
if (me.max === null) {
|
|
me.max = tickOpts.suggestedMax;
|
|
} else {
|
|
me.max = Math.max(me.max, tickOpts.suggestedMax);
|
|
}
|
|
}
|
|
|
|
if (setMin !== setMax) {
|
|
// We set the min or the max but not both.
|
|
// So ensure that our range is good
|
|
// Inverted or 0 length range can happen when
|
|
// ticks.min is set, and no datasets are visible
|
|
if (me.min >= me.max) {
|
|
if (setMin) {
|
|
me.max = me.min + 1;
|
|
} else {
|
|
me.min = me.max - 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (me.min === me.max) {
|
|
me.max++;
|
|
|
|
if (!tickOpts.beginAtZero) {
|
|
me.min--;
|
|
}
|
|
}
|
|
},
|
|
|
|
getTickLimit: function() {
|
|
var me = this;
|
|
var tickOpts = me.options.ticks;
|
|
var stepSize = tickOpts.stepSize;
|
|
var maxTicksLimit = tickOpts.maxTicksLimit;
|
|
var maxTicks;
|
|
|
|
if (stepSize) {
|
|
maxTicks = Math.ceil(me.max / stepSize) - Math.floor(me.min / stepSize) + 1;
|
|
} else {
|
|
maxTicks = me._computeTickLimit();
|
|
maxTicksLimit = maxTicksLimit || 11;
|
|
}
|
|
|
|
if (maxTicksLimit) {
|
|
maxTicks = Math.min(maxTicksLimit, maxTicks);
|
|
}
|
|
|
|
return maxTicks;
|
|
},
|
|
|
|
_computeTickLimit: function() {
|
|
return Number.POSITIVE_INFINITY;
|
|
},
|
|
|
|
handleDirectionalChanges: noop,
|
|
|
|
buildTicks: function() {
|
|
var me = this;
|
|
var opts = me.options;
|
|
var tickOpts = opts.ticks;
|
|
|
|
// Figure out what the max number of ticks we can support it is based on the size of
|
|
// the axis area. For now, we say that the minimum tick spacing in pixels must be 40
|
|
// We also limit the maximum number of ticks to 11 which gives a nice 10 squares on
|
|
// the graph. Make sure we always have at least 2 ticks
|
|
var maxTicks = me.getTickLimit();
|
|
maxTicks = Math.max(2, maxTicks);
|
|
|
|
var numericGeneratorOptions = {
|
|
maxTicks: maxTicks,
|
|
min: tickOpts.min,
|
|
max: tickOpts.max,
|
|
precision: tickOpts.precision,
|
|
stepSize: helpers$1.valueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize)
|
|
};
|
|
var ticks = me.ticks = generateTicks(numericGeneratorOptions, me);
|
|
|
|
me.handleDirectionalChanges();
|
|
|
|
// At this point, we need to update our max and min given the tick values since we have expanded the
|
|
// range of the scale
|
|
me.max = helpers$1.max(ticks);
|
|
me.min = helpers$1.min(ticks);
|
|
|
|
if (tickOpts.reverse) {
|
|
ticks.reverse();
|
|
|
|
me.start = me.max;
|
|
me.end = me.min;
|
|
} else {
|
|
me.start = me.min;
|
|
me.end = me.max;
|
|
}
|
|
},
|
|
|
|
convertTicksToLabels: function() {
|
|
var me = this;
|
|
me.ticksAsNumbers = me.ticks.slice();
|
|
me.zeroLineIndex = me.ticks.indexOf(0);
|
|
|
|
core_scale.prototype.convertTicksToLabels.call(me);
|
|
},
|
|
|
|
_configure: function() {
|
|
var me = this;
|
|
var ticks = me.getTicks();
|
|
var start = me.min;
|
|
var end = me.max;
|
|
var offset;
|
|
|
|
core_scale.prototype._configure.call(me);
|
|
|
|
if (me.options.offset && ticks.length) {
|
|
offset = (end - start) / Math.max(ticks.length - 1, 1) / 2;
|
|
start -= offset;
|
|
end += offset;
|
|
}
|
|
me._startValue = start;
|
|
me._endValue = end;
|
|
me._valueRange = end - start;
|
|
}
|
|
});
|
|
|
|
var defaultConfig$1 = {
|
|
position: 'left',
|
|
ticks: {
|
|
callback: core_ticks.formatters.linear
|
|
}
|
|
};
|
|
|
|
var DEFAULT_MIN = 0;
|
|
var DEFAULT_MAX = 1;
|
|
|
|
function getOrCreateStack(stacks, stacked, meta) {
|
|
var key = [
|
|
meta.type,
|
|
// we have a separate stack for stack=undefined datasets when the opts.stacked is undefined
|
|
stacked === undefined && meta.stack === undefined ? meta.index : '',
|
|
meta.stack
|
|
].join('.');
|
|
|
|
if (stacks[key] === undefined) {
|
|
stacks[key] = {
|
|
pos: [],
|
|
neg: []
|
|
};
|
|
}
|
|
|
|
return stacks[key];
|
|
}
|
|
|
|
function stackData(scale, stacks, meta, data) {
|
|
var opts = scale.options;
|
|
var stacked = opts.stacked;
|
|
var stack = getOrCreateStack(stacks, stacked, meta);
|
|
var pos = stack.pos;
|
|
var neg = stack.neg;
|
|
var ilen = data.length;
|
|
var i, value;
|
|
|
|
for (i = 0; i < ilen; ++i) {
|
|
value = scale._parseValue(data[i]);
|
|
if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden) {
|
|
continue;
|
|
}
|
|
|
|
pos[i] = pos[i] || 0;
|
|
neg[i] = neg[i] || 0;
|
|
|
|
if (opts.relativePoints) {
|
|
pos[i] = 100;
|
|
} else if (value.min < 0 || value.max < 0) {
|
|
neg[i] += value.min;
|
|
} else {
|
|
pos[i] += value.max;
|
|
}
|
|
}
|
|
}
|
|
|
|
function updateMinMax(scale, meta, data) {
|
|
var ilen = data.length;
|
|
var i, value;
|
|
|
|
for (i = 0; i < ilen; ++i) {
|
|
value = scale._parseValue(data[i]);
|
|
if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden) {
|
|
continue;
|
|
}
|
|
|
|
scale.min = Math.min(scale.min, value.min);
|
|
scale.max = Math.max(scale.max, value.max);
|
|
}
|
|
}
|
|
|
|
var scale_linear = scale_linearbase.extend({
|
|
determineDataLimits: function() {
|
|
var me = this;
|
|
var opts = me.options;
|
|
var chart = me.chart;
|
|
var datasets = chart.data.datasets;
|
|
var metasets = me._getMatchingVisibleMetas();
|
|
var hasStacks = opts.stacked;
|
|
var stacks = {};
|
|
var ilen = metasets.length;
|
|
var i, meta, data, values;
|
|
|
|
me.min = Number.POSITIVE_INFINITY;
|
|
me.max = Number.NEGATIVE_INFINITY;
|
|
|
|
if (hasStacks === undefined) {
|
|
for (i = 0; !hasStacks && i < ilen; ++i) {
|
|
meta = metasets[i];
|
|
hasStacks = meta.stack !== undefined;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < ilen; ++i) {
|
|
meta = metasets[i];
|
|
data = datasets[meta.index].data;
|
|
if (hasStacks) {
|
|
stackData(me, stacks, meta, data);
|
|
} else {
|
|
updateMinMax(me, meta, data);
|
|
}
|
|
}
|
|
|
|
helpers$1.each(stacks, function(stackValues) {
|
|
values = stackValues.pos.concat(stackValues.neg);
|
|
me.min = Math.min(me.min, helpers$1.min(values));
|
|
me.max = Math.max(me.max, helpers$1.max(values));
|
|
});
|
|
|
|
me.min = helpers$1.isFinite(me.min) && !isNaN(me.min) ? me.min : DEFAULT_MIN;
|
|
me.max = helpers$1.isFinite(me.max) && !isNaN(me.max) ? me.max : DEFAULT_MAX;
|
|
|
|
// Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
|
|
me.handleTickRangeOptions();
|
|
},
|
|
|
|
// Returns the maximum number of ticks based on the scale dimension
|
|
_computeTickLimit: function() {
|
|
var me = this;
|
|
var tickFont;
|
|
|
|
if (me.isHorizontal()) {
|
|
return Math.ceil(me.width / 40);
|
|
}
|
|
tickFont = helpers$1.options._parseFont(me.options.ticks);
|
|
return Math.ceil(me.height / tickFont.lineHeight);
|
|
},
|
|
|
|
// Called after the ticks are built. We need
|
|
handleDirectionalChanges: function() {
|
|
if (!this.isHorizontal()) {
|
|
// We are in a vertical orientation. The top value is the highest. So reverse the array
|
|
this.ticks.reverse();
|
|
}
|
|
},
|
|
|
|
getLabelForIndex: function(index, datasetIndex) {
|
|
return this._getScaleLabel(this.chart.data.datasets[datasetIndex].data[index]);
|
|
},
|
|
|
|
// Utils
|
|
getPixelForValue: function(value) {
|
|
var me = this;
|
|
return me.getPixelForDecimal((+me.getRightValue(value) - me._startValue) / me._valueRange);
|
|
},
|
|
|
|
getValueForPixel: function(pixel) {
|
|
return this._startValue + this.getDecimalForPixel(pixel) * this._valueRange;
|
|
},
|
|
|
|
getPixelForTick: function(index) {
|
|
var ticks = this.ticksAsNumbers;
|
|
if (index < 0 || index > ticks.length - 1) {
|
|
return null;
|
|
}
|
|
return this.getPixelForValue(ticks[index]);
|
|
}
|
|
});
|
|
|
|
// INTERNAL: static default options, registered in src/index.js
|
|
var _defaults$1 = defaultConfig$1;
|
|
scale_linear._defaults = _defaults$1;
|
|
|
|
var valueOrDefault$b = helpers$1.valueOrDefault;
|
|
var log10 = helpers$1.math.log10;
|
|
|
|
/**
|
|
* Generate a set of logarithmic ticks
|
|
* @param generationOptions the options used to generate the ticks
|
|
* @param dataRange the range of the data
|
|
* @returns {number[]} array of tick values
|
|
*/
|
|
function generateTicks$1(generationOptions, dataRange) {
|
|
var ticks = [];
|
|
|
|
var tickVal = valueOrDefault$b(generationOptions.min, Math.pow(10, Math.floor(log10(dataRange.min))));
|
|
|
|
var endExp = Math.floor(log10(dataRange.max));
|
|
var endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp));
|
|
var exp, significand;
|
|
|
|
if (tickVal === 0) {
|
|
exp = Math.floor(log10(dataRange.minNotZero));
|
|
significand = Math.floor(dataRange.minNotZero / Math.pow(10, exp));
|
|
|
|
ticks.push(tickVal);
|
|
tickVal = significand * Math.pow(10, exp);
|
|
} else {
|
|
exp = Math.floor(log10(tickVal));
|
|
significand = Math.floor(tickVal / Math.pow(10, exp));
|
|
}
|
|
var precision = exp < 0 ? Math.pow(10, Math.abs(exp)) : 1;
|
|
|
|
do {
|
|
ticks.push(tickVal);
|
|
|
|
++significand;
|
|
if (significand === 10) {
|
|
significand = 1;
|
|
++exp;
|
|
precision = exp >= 0 ? 1 : precision;
|
|
}
|
|
|
|
tickVal = Math.round(significand * Math.pow(10, exp) * precision) / precision;
|
|
} while (exp < endExp || (exp === endExp && significand < endSignificand));
|
|
|
|
var lastTick = valueOrDefault$b(generationOptions.max, tickVal);
|
|
ticks.push(lastTick);
|
|
|
|
return ticks;
|
|
}
|
|
|
|
var defaultConfig$2 = {
|
|
position: 'left',
|
|
|
|
// label settings
|
|
ticks: {
|
|
callback: core_ticks.formatters.logarithmic
|
|
}
|
|
};
|
|
|
|
// TODO(v3): change this to positiveOrDefault
|
|
function nonNegativeOrDefault(value, defaultValue) {
|
|
return helpers$1.isFinite(value) && value >= 0 ? value : defaultValue;
|
|
}
|
|
|
|
var scale_logarithmic = core_scale.extend({
|
|
determineDataLimits: function() {
|
|
var me = this;
|
|
var opts = me.options;
|
|
var chart = me.chart;
|
|
var datasets = chart.data.datasets;
|
|
var isHorizontal = me.isHorizontal();
|
|
function IDMatches(meta) {
|
|
return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id;
|
|
}
|
|
var datasetIndex, meta, value, data, i, ilen;
|
|
|
|
// Calculate Range
|
|
me.min = Number.POSITIVE_INFINITY;
|
|
me.max = Number.NEGATIVE_INFINITY;
|
|
me.minNotZero = Number.POSITIVE_INFINITY;
|
|
|
|
var hasStacks = opts.stacked;
|
|
if (hasStacks === undefined) {
|
|
for (datasetIndex = 0; datasetIndex < datasets.length; datasetIndex++) {
|
|
meta = chart.getDatasetMeta(datasetIndex);
|
|
if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) &&
|
|
meta.stack !== undefined) {
|
|
hasStacks = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (opts.stacked || hasStacks) {
|
|
var valuesPerStack = {};
|
|
|
|
for (datasetIndex = 0; datasetIndex < datasets.length; datasetIndex++) {
|
|
meta = chart.getDatasetMeta(datasetIndex);
|
|
var key = [
|
|
meta.type,
|
|
// we have a separate stack for stack=undefined datasets when the opts.stacked is undefined
|
|
((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''),
|
|
meta.stack
|
|
].join('.');
|
|
|
|
if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
|
|
if (valuesPerStack[key] === undefined) {
|
|
valuesPerStack[key] = [];
|
|
}
|
|
|
|
data = datasets[datasetIndex].data;
|
|
for (i = 0, ilen = data.length; i < ilen; i++) {
|
|
var values = valuesPerStack[key];
|
|
value = me._parseValue(data[i]);
|
|
// invalid, hidden and negative values are ignored
|
|
if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden || value.min < 0 || value.max < 0) {
|
|
continue;
|
|
}
|
|
values[i] = values[i] || 0;
|
|
values[i] += value.max;
|
|
}
|
|
}
|
|
}
|
|
|
|
helpers$1.each(valuesPerStack, function(valuesForType) {
|
|
if (valuesForType.length > 0) {
|
|
var minVal = helpers$1.min(valuesForType);
|
|
var maxVal = helpers$1.max(valuesForType);
|
|
me.min = Math.min(me.min, minVal);
|
|
me.max = Math.max(me.max, maxVal);
|
|
}
|
|
});
|
|
|
|
} else {
|
|
for (datasetIndex = 0; datasetIndex < datasets.length; datasetIndex++) {
|
|
meta = chart.getDatasetMeta(datasetIndex);
|
|
if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) {
|
|
data = datasets[datasetIndex].data;
|
|
for (i = 0, ilen = data.length; i < ilen; i++) {
|
|
value = me._parseValue(data[i]);
|
|
// invalid, hidden and negative values are ignored
|
|
if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden || value.min < 0 || value.max < 0) {
|
|
continue;
|
|
}
|
|
|
|
me.min = Math.min(value.min, me.min);
|
|
me.max = Math.max(value.max, me.max);
|
|
|
|
if (value.min !== 0) {
|
|
me.minNotZero = Math.min(value.min, me.minNotZero);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
me.min = helpers$1.isFinite(me.min) ? me.min : null;
|
|
me.max = helpers$1.isFinite(me.max) ? me.max : null;
|
|
me.minNotZero = helpers$1.isFinite(me.minNotZero) ? me.minNotZero : null;
|
|
|
|
// Common base implementation to handle ticks.min, ticks.max
|
|
this.handleTickRangeOptions();
|
|
},
|
|
|
|
handleTickRangeOptions: function() {
|
|
var me = this;
|
|
var tickOpts = me.options.ticks;
|
|
var DEFAULT_MIN = 1;
|
|
var DEFAULT_MAX = 10;
|
|
|
|
me.min = nonNegativeOrDefault(tickOpts.min, me.min);
|
|
me.max = nonNegativeOrDefault(tickOpts.max, me.max);
|
|
|
|
if (me.min === me.max) {
|
|
if (me.min !== 0 && me.min !== null) {
|
|
me.min = Math.pow(10, Math.floor(log10(me.min)) - 1);
|
|
me.max = Math.pow(10, Math.floor(log10(me.max)) + 1);
|
|
} else {
|
|
me.min = DEFAULT_MIN;
|
|
me.max = DEFAULT_MAX;
|
|
}
|
|
}
|
|
if (me.min === null) {
|
|
me.min = Math.pow(10, Math.floor(log10(me.max)) - 1);
|
|
}
|
|
if (me.max === null) {
|
|
me.max = me.min !== 0
|
|
? Math.pow(10, Math.floor(log10(me.min)) + 1)
|
|
: DEFAULT_MAX;
|
|
}
|
|
if (me.minNotZero === null) {
|
|
if (me.min > 0) {
|
|
me.minNotZero = me.min;
|
|
} else if (me.max < 1) {
|
|
me.minNotZero = Math.pow(10, Math.floor(log10(me.max)));
|
|
} else {
|
|
me.minNotZero = DEFAULT_MIN;
|
|
}
|
|
}
|
|
},
|
|
|
|
buildTicks: function() {
|
|
var me = this;
|
|
var tickOpts = me.options.ticks;
|
|
var reverse = !me.isHorizontal();
|
|
|
|
var generationOptions = {
|
|
min: nonNegativeOrDefault(tickOpts.min),
|
|
max: nonNegativeOrDefault(tickOpts.max)
|
|
};
|
|
var ticks = me.ticks = generateTicks$1(generationOptions, me);
|
|
|
|
// At this point, we need to update our max and min given the tick values since we have expanded the
|
|
// range of the scale
|
|
me.max = helpers$1.max(ticks);
|
|
me.min = helpers$1.min(ticks);
|
|
|
|
if (tickOpts.reverse) {
|
|
reverse = !reverse;
|
|
me.start = me.max;
|
|
me.end = me.min;
|
|
} else {
|
|
me.start = me.min;
|
|
me.end = me.max;
|
|
}
|
|
if (reverse) {
|
|
ticks.reverse();
|
|
}
|
|
},
|
|
|
|
convertTicksToLabels: function() {
|
|
this.tickValues = this.ticks.slice();
|
|
|
|
core_scale.prototype.convertTicksToLabels.call(this);
|
|
},
|
|
|
|
// Get the correct tooltip label
|
|
getLabelForIndex: function(index, datasetIndex) {
|
|
return this._getScaleLabel(this.chart.data.datasets[datasetIndex].data[index]);
|
|
},
|
|
|
|
getPixelForTick: function(index) {
|
|
var ticks = this.tickValues;
|
|
if (index < 0 || index > ticks.length - 1) {
|
|
return null;
|
|
}
|
|
return this.getPixelForValue(ticks[index]);
|
|
},
|
|
|
|
/**
|
|
* Returns the value of the first tick.
|
|
* @param {number} value - The minimum not zero value.
|
|
* @return {number} The first tick value.
|
|
* @private
|
|
*/
|
|
_getFirstTickValue: function(value) {
|
|
var exp = Math.floor(log10(value));
|
|
var significand = Math.floor(value / Math.pow(10, exp));
|
|
|
|
return significand * Math.pow(10, exp);
|
|
},
|
|
|
|
_configure: function() {
|
|
var me = this;
|
|
var start = me.min;
|
|
var offset = 0;
|
|
|
|
core_scale.prototype._configure.call(me);
|
|
|
|
if (start === 0) {
|
|
start = me._getFirstTickValue(me.minNotZero);
|
|
offset = valueOrDefault$b(me.options.ticks.fontSize, core_defaults.global.defaultFontSize) / me._length;
|
|
}
|
|
|
|
me._startValue = log10(start);
|
|
me._valueOffset = offset;
|
|
me._valueRange = (log10(me.max) - log10(start)) / (1 - offset);
|
|
},
|
|
|
|
getPixelForValue: function(value) {
|
|
var me = this;
|
|
var decimal = 0;
|
|
|
|
value = +me.getRightValue(value);
|
|
|
|
if (value > me.min && value > 0) {
|
|
decimal = (log10(value) - me._startValue) / me._valueRange + me._valueOffset;
|
|
}
|
|
return me.getPixelForDecimal(decimal);
|
|
},
|
|
|
|
getValueForPixel: function(pixel) {
|
|
var me = this;
|
|
var decimal = me.getDecimalForPixel(pixel);
|
|
return decimal === 0 && me.min === 0
|
|
? 0
|
|
: Math.pow(10, me._startValue + (decimal - me._valueOffset) * me._valueRange);
|
|
}
|
|
});
|
|
|
|
// INTERNAL: static default options, registered in src/index.js
|
|
var _defaults$2 = defaultConfig$2;
|
|
scale_logarithmic._defaults = _defaults$2;
|
|
|
|
var valueOrDefault$c = helpers$1.valueOrDefault;
|
|
var valueAtIndexOrDefault$1 = helpers$1.valueAtIndexOrDefault;
|
|
var resolve$4 = helpers$1.options.resolve;
|
|
|
|
var defaultConfig$3 = {
|
|
display: true,
|
|
|
|
// Boolean - Whether to animate scaling the chart from the centre
|
|
animate: true,
|
|
position: 'chartArea',
|
|
|
|
angleLines: {
|
|
display: true,
|
|
color: 'rgba(0,0,0,0.1)',
|
|
lineWidth: 1,
|
|
borderDash: [],
|
|
borderDashOffset: 0.0
|
|
},
|
|
|
|
gridLines: {
|
|
circular: false
|
|
},
|
|
|
|
// label settings
|
|
ticks: {
|
|
// Boolean - Show a backdrop to the scale label
|
|
showLabelBackdrop: true,
|
|
|
|
// String - The colour of the label backdrop
|
|
backdropColor: 'rgba(255,255,255,0.75)',
|
|
|
|
// Number - The backdrop padding above & below the label in pixels
|
|
backdropPaddingY: 2,
|
|
|
|
// Number - The backdrop padding to the side of the label in pixels
|
|
backdropPaddingX: 2,
|
|
|
|
callback: core_ticks.formatters.linear
|
|
},
|
|
|
|
pointLabels: {
|
|
// Boolean - if true, show point labels
|
|
display: true,
|
|
|
|
// Number - Point label font size in pixels
|
|
fontSize: 10,
|
|
|
|
// Function - Used to convert point labels
|
|
callback: function(label) {
|
|
return label;
|
|
}
|
|
}
|
|
};
|
|
|
|
function getTickBackdropHeight(opts) {
|
|
var tickOpts = opts.ticks;
|
|
|
|
if (tickOpts.display && opts.display) {
|
|
return valueOrDefault$c(tickOpts.fontSize, core_defaults.global.defaultFontSize) + tickOpts.backdropPaddingY * 2;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
function measureLabelSize(ctx, lineHeight, label) {
|
|
if (helpers$1.isArray(label)) {
|
|
return {
|
|
w: helpers$1.longestText(ctx, ctx.font, label),
|
|
h: label.length * lineHeight
|
|
};
|
|
}
|
|
|
|
return {
|
|
w: ctx.measureText(label).width,
|
|
h: lineHeight
|
|
};
|
|
}
|
|
|
|
function determineLimits(angle, pos, size, min, max) {
|
|
if (angle === min || angle === max) {
|
|
return {
|
|
start: pos - (size / 2),
|
|
end: pos + (size / 2)
|
|
};
|
|
} else if (angle < min || angle > max) {
|
|
return {
|
|
start: pos - size,
|
|
end: pos
|
|
};
|
|
}
|
|
|
|
return {
|
|
start: pos,
|
|
end: pos + size
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Helper function to fit a radial linear scale with point labels
|
|
*/
|
|
function fitWithPointLabels(scale) {
|
|
|
|
// Right, this is really confusing and there is a lot of maths going on here
|
|
// The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
|
|
//
|
|
// Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
|
|
//
|
|
// Solution:
|
|
//
|
|
// We assume the radius of the polygon is half the size of the canvas at first
|
|
// at each index we check if the text overlaps.
|
|
//
|
|
// Where it does, we store that angle and that index.
|
|
//
|
|
// After finding the largest index and angle we calculate how much we need to remove
|
|
// from the shape radius to move the point inwards by that x.
|
|
//
|
|
// We average the left and right distances to get the maximum shape radius that can fit in the box
|
|
// along with labels.
|
|
//
|
|
// Once we have that, we can find the centre point for the chart, by taking the x text protrusion
|
|
// on each side, removing that from the size, halving it and adding the left x protrusion width.
|
|
//
|
|
// This will mean we have a shape fitted to the canvas, as large as it can be with the labels
|
|
// and position it in the most space efficient manner
|
|
//
|
|
// https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
|
|
|
|
var plFont = helpers$1.options._parseFont(scale.options.pointLabels);
|
|
|
|
// Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
|
|
// Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
|
|
var furthestLimits = {
|
|
l: 0,
|
|
r: scale.width,
|
|
t: 0,
|
|
b: scale.height - scale.paddingTop
|
|
};
|
|
var furthestAngles = {};
|
|
var i, textSize, pointPosition;
|
|
|
|
scale.ctx.font = plFont.string;
|
|
scale._pointLabelSizes = [];
|
|
|
|
var valueCount = scale.chart.data.labels.length;
|
|
for (i = 0; i < valueCount; i++) {
|
|
pointPosition = scale.getPointPosition(i, scale.drawingArea + 5);
|
|
textSize = measureLabelSize(scale.ctx, plFont.lineHeight, scale.pointLabels[i]);
|
|
scale._pointLabelSizes[i] = textSize;
|
|
|
|
// Add quarter circle to make degree 0 mean top of circle
|
|
var angleRadians = scale.getIndexAngle(i);
|
|
var angle = helpers$1.toDegrees(angleRadians) % 360;
|
|
var hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180);
|
|
var vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270);
|
|
|
|
if (hLimits.start < furthestLimits.l) {
|
|
furthestLimits.l = hLimits.start;
|
|
furthestAngles.l = angleRadians;
|
|
}
|
|
|
|
if (hLimits.end > furthestLimits.r) {
|
|
furthestLimits.r = hLimits.end;
|
|
furthestAngles.r = angleRadians;
|
|
}
|
|
|
|
if (vLimits.start < furthestLimits.t) {
|
|
furthestLimits.t = vLimits.start;
|
|
furthestAngles.t = angleRadians;
|
|
}
|
|
|
|
if (vLimits.end > furthestLimits.b) {
|
|
furthestLimits.b = vLimits.end;
|
|
furthestAngles.b = angleRadians;
|
|
}
|
|
}
|
|
|
|
scale.setReductions(scale.drawingArea, furthestLimits, furthestAngles);
|
|
}
|
|
|
|
function getTextAlignForAngle(angle) {
|
|
if (angle === 0 || angle === 180) {
|
|
return 'center';
|
|
} else if (angle < 180) {
|
|
return 'left';
|
|
}
|
|
|
|
return 'right';
|
|
}
|
|
|
|
function fillText(ctx, text, position, lineHeight) {
|
|
var y = position.y + lineHeight / 2;
|
|
var i, ilen;
|
|
|
|
if (helpers$1.isArray(text)) {
|
|
for (i = 0, ilen = text.length; i < ilen; ++i) {
|
|
ctx.fillText(text[i], position.x, y);
|
|
y += lineHeight;
|
|
}
|
|
} else {
|
|
ctx.fillText(text, position.x, y);
|
|
}
|
|
}
|
|
|
|
function adjustPointPositionForLabelHeight(angle, textSize, position) {
|
|
if (angle === 90 || angle === 270) {
|
|
position.y -= (textSize.h / 2);
|
|
} else if (angle > 270 || angle < 90) {
|
|
position.y -= textSize.h;
|
|
}
|
|
}
|
|
|
|
function drawPointLabels(scale) {
|
|
var ctx = scale.ctx;
|
|
var opts = scale.options;
|
|
var pointLabelOpts = opts.pointLabels;
|
|
var tickBackdropHeight = getTickBackdropHeight(opts);
|
|
var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max);
|
|
var plFont = helpers$1.options._parseFont(pointLabelOpts);
|
|
|
|
ctx.save();
|
|
|
|
ctx.font = plFont.string;
|
|
ctx.textBaseline = 'middle';
|
|
|
|
for (var i = scale.chart.data.labels.length - 1; i >= 0; i--) {
|
|
// Extra pixels out for some label spacing
|
|
var extra = (i === 0 ? tickBackdropHeight / 2 : 0);
|
|
var pointLabelPosition = scale.getPointPosition(i, outerDistance + extra + 5);
|
|
|
|
// Keep this in loop since we may support array properties here
|
|
var pointLabelFontColor = valueAtIndexOrDefault$1(pointLabelOpts.fontColor, i, core_defaults.global.defaultFontColor);
|
|
ctx.fillStyle = pointLabelFontColor;
|
|
|
|
var angleRadians = scale.getIndexAngle(i);
|
|
var angle = helpers$1.toDegrees(angleRadians);
|
|
ctx.textAlign = getTextAlignForAngle(angle);
|
|
adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition);
|
|
fillText(ctx, scale.pointLabels[i], pointLabelPosition, plFont.lineHeight);
|
|
}
|
|
ctx.restore();
|
|
}
|
|
|
|
function drawRadiusLine(scale, gridLineOpts, radius, index) {
|
|
var ctx = scale.ctx;
|
|
var circular = gridLineOpts.circular;
|
|
var valueCount = scale.chart.data.labels.length;
|
|
var lineColor = valueAtIndexOrDefault$1(gridLineOpts.color, index - 1);
|
|
var lineWidth = valueAtIndexOrDefault$1(gridLineOpts.lineWidth, index - 1);
|
|
var pointPosition;
|
|
|
|
if ((!circular && !valueCount) || !lineColor || !lineWidth) {
|
|
return;
|
|
}
|
|
|
|
ctx.save();
|
|
ctx.strokeStyle = lineColor;
|
|
ctx.lineWidth = lineWidth;
|
|
if (ctx.setLineDash) {
|
|
ctx.setLineDash(gridLineOpts.borderDash || []);
|
|
ctx.lineDashOffset = gridLineOpts.borderDashOffset || 0.0;
|
|
}
|
|
|
|
ctx.beginPath();
|
|
if (circular) {
|
|
// Draw circular arcs between the points
|
|
ctx.arc(scale.xCenter, scale.yCenter, radius, 0, Math.PI * 2);
|
|
} else {
|
|
// Draw straight lines connecting each index
|
|
pointPosition = scale.getPointPosition(0, radius);
|
|
ctx.moveTo(pointPosition.x, pointPosition.y);
|
|
|
|
for (var i = 1; i < valueCount; i++) {
|
|
pointPosition = scale.getPointPosition(i, radius);
|
|
ctx.lineTo(pointPosition.x, pointPosition.y);
|
|
}
|
|
}
|
|
ctx.closePath();
|
|
ctx.stroke();
|
|
ctx.restore();
|
|
}
|
|
|
|
function numberOrZero(param) {
|
|
return helpers$1.isNumber(param) ? param : 0;
|
|
}
|
|
|
|
var scale_radialLinear = scale_linearbase.extend({
|
|
setDimensions: function() {
|
|
var me = this;
|
|
|
|
// Set the unconstrained dimension before label rotation
|
|
me.width = me.maxWidth;
|
|
me.height = me.maxHeight;
|
|
me.paddingTop = getTickBackdropHeight(me.options) / 2;
|
|
me.xCenter = Math.floor(me.width / 2);
|
|
me.yCenter = Math.floor((me.height - me.paddingTop) / 2);
|
|
me.drawingArea = Math.min(me.height - me.paddingTop, me.width) / 2;
|
|
},
|
|
|
|
determineDataLimits: function() {
|
|
var me = this;
|
|
var chart = me.chart;
|
|
var min = Number.POSITIVE_INFINITY;
|
|
var max = Number.NEGATIVE_INFINITY;
|
|
|
|
helpers$1.each(chart.data.datasets, function(dataset, datasetIndex) {
|
|
if (chart.isDatasetVisible(datasetIndex)) {
|
|
var meta = chart.getDatasetMeta(datasetIndex);
|
|
|
|
helpers$1.each(dataset.data, function(rawValue, index) {
|
|
var value = +me.getRightValue(rawValue);
|
|
if (isNaN(value) || meta.data[index].hidden) {
|
|
return;
|
|
}
|
|
|
|
min = Math.min(value, min);
|
|
max = Math.max(value, max);
|
|
});
|
|
}
|
|
});
|
|
|
|
me.min = (min === Number.POSITIVE_INFINITY ? 0 : min);
|
|
me.max = (max === Number.NEGATIVE_INFINITY ? 0 : max);
|
|
|
|
// Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero
|
|
me.handleTickRangeOptions();
|
|
},
|
|
|
|
// Returns the maximum number of ticks based on the scale dimension
|
|
_computeTickLimit: function() {
|
|
return Math.ceil(this.drawingArea / getTickBackdropHeight(this.options));
|
|
},
|
|
|
|
convertTicksToLabels: function() {
|
|
var me = this;
|
|
|
|
scale_linearbase.prototype.convertTicksToLabels.call(me);
|
|
|
|
// Point labels
|
|
me.pointLabels = me.chart.data.labels.map(function() {
|
|
var label = helpers$1.callback(me.options.pointLabels.callback, arguments, me);
|
|
return label || label === 0 ? label : '';
|
|
});
|
|
},
|
|
|
|
getLabelForIndex: function(index, datasetIndex) {
|
|
return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]);
|
|
},
|
|
|
|
fit: function() {
|
|
var me = this;
|
|
var opts = me.options;
|
|
|
|
if (opts.display && opts.pointLabels.display) {
|
|
fitWithPointLabels(me);
|
|
} else {
|
|
me.setCenterPoint(0, 0, 0, 0);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Set radius reductions and determine new radius and center point
|
|
* @private
|
|
*/
|
|
setReductions: function(largestPossibleRadius, furthestLimits, furthestAngles) {
|
|
var me = this;
|
|
var radiusReductionLeft = furthestLimits.l / Math.sin(furthestAngles.l);
|
|
var radiusReductionRight = Math.max(furthestLimits.r - me.width, 0) / Math.sin(furthestAngles.r);
|
|
var radiusReductionTop = -furthestLimits.t / Math.cos(furthestAngles.t);
|
|
var radiusReductionBottom = -Math.max(furthestLimits.b - (me.height - me.paddingTop), 0) / Math.cos(furthestAngles.b);
|
|
|
|
radiusReductionLeft = numberOrZero(radiusReductionLeft);
|
|
radiusReductionRight = numberOrZero(radiusReductionRight);
|
|
radiusReductionTop = numberOrZero(radiusReductionTop);
|
|
radiusReductionBottom = numberOrZero(radiusReductionBottom);
|
|
|
|
me.drawingArea = Math.min(
|
|
Math.floor(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2),
|
|
Math.floor(largestPossibleRadius - (radiusReductionTop + radiusReductionBottom) / 2));
|
|
me.setCenterPoint(radiusReductionLeft, radiusReductionRight, radiusReductionTop, radiusReductionBottom);
|
|
},
|
|
|
|
setCenterPoint: function(leftMovement, rightMovement, topMovement, bottomMovement) {
|
|
var me = this;
|
|
var maxRight = me.width - rightMovement - me.drawingArea;
|
|
var maxLeft = leftMovement + me.drawingArea;
|
|
var maxTop = topMovement + me.drawingArea;
|
|
var maxBottom = (me.height - me.paddingTop) - bottomMovement - me.drawingArea;
|
|
|
|
me.xCenter = Math.floor(((maxLeft + maxRight) / 2) + me.left);
|
|
me.yCenter = Math.floor(((maxTop + maxBottom) / 2) + me.top + me.paddingTop);
|
|
},
|
|
|
|
getIndexAngle: function(index) {
|
|
var chart = this.chart;
|
|
var angleMultiplier = 360 / chart.data.labels.length;
|
|
var options = chart.options || {};
|
|
var startAngle = options.startAngle || 0;
|
|
|
|
// Start from the top instead of right, so remove a quarter of the circle
|
|
var angle = (index * angleMultiplier + startAngle) % 360;
|
|
|
|
return (angle < 0 ? angle + 360 : angle) * Math.PI * 2 / 360;
|
|
},
|
|
|
|
getDistanceFromCenterForValue: function(value) {
|
|
var me = this;
|
|
|
|
if (helpers$1.isNullOrUndef(value)) {
|
|
return NaN;
|
|
}
|
|
|
|
// Take into account half font size + the yPadding of the top value
|
|
var scalingFactor = me.drawingArea / (me.max - me.min);
|
|
if (me.options.ticks.reverse) {
|
|
return (me.max - value) * scalingFactor;
|
|
}
|
|
return (value - me.min) * scalingFactor;
|
|
},
|
|
|
|
getPointPosition: function(index, distanceFromCenter) {
|
|
var me = this;
|
|
var thisAngle = me.getIndexAngle(index) - (Math.PI / 2);
|
|
return {
|
|
x: Math.cos(thisAngle) * distanceFromCenter + me.xCenter,
|
|
y: Math.sin(thisAngle) * distanceFromCenter + me.yCenter
|
|
};
|
|
},
|
|
|
|
getPointPositionForValue: function(index, value) {
|
|
return this.getPointPosition(index, this.getDistanceFromCenterForValue(value));
|
|
},
|
|
|
|
getBasePosition: function(index) {
|
|
var me = this;
|
|
var min = me.min;
|
|
var max = me.max;
|
|
|
|
return me.getPointPositionForValue(index || 0,
|
|
me.beginAtZero ? 0 :
|
|
min < 0 && max < 0 ? max :
|
|
min > 0 && max > 0 ? min :
|
|
0);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_drawGrid: function() {
|
|
var me = this;
|
|
var ctx = me.ctx;
|
|
var opts = me.options;
|
|
var gridLineOpts = opts.gridLines;
|
|
var angleLineOpts = opts.angleLines;
|
|
var lineWidth = valueOrDefault$c(angleLineOpts.lineWidth, gridLineOpts.lineWidth);
|
|
var lineColor = valueOrDefault$c(angleLineOpts.color, gridLineOpts.color);
|
|
var i, offset, position;
|
|
|
|
if (opts.pointLabels.display) {
|
|
drawPointLabels(me);
|
|
}
|
|
|
|
if (gridLineOpts.display) {
|
|
helpers$1.each(me.ticks, function(label, index) {
|
|
if (index !== 0) {
|
|
offset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]);
|
|
drawRadiusLine(me, gridLineOpts, offset, index);
|
|
}
|
|
});
|
|
}
|
|
|
|
if (angleLineOpts.display && lineWidth && lineColor) {
|
|
ctx.save();
|
|
ctx.lineWidth = lineWidth;
|
|
ctx.strokeStyle = lineColor;
|
|
if (ctx.setLineDash) {
|
|
ctx.setLineDash(resolve$4([angleLineOpts.borderDash, gridLineOpts.borderDash, []]));
|
|
ctx.lineDashOffset = resolve$4([angleLineOpts.borderDashOffset, gridLineOpts.borderDashOffset, 0.0]);
|
|
}
|
|
|
|
for (i = me.chart.data.labels.length - 1; i >= 0; i--) {
|
|
offset = me.getDistanceFromCenterForValue(opts.ticks.reverse ? me.min : me.max);
|
|
position = me.getPointPosition(i, offset);
|
|
ctx.beginPath();
|
|
ctx.moveTo(me.xCenter, me.yCenter);
|
|
ctx.lineTo(position.x, position.y);
|
|
ctx.stroke();
|
|
}
|
|
|
|
ctx.restore();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_drawLabels: function() {
|
|
var me = this;
|
|
var ctx = me.ctx;
|
|
var opts = me.options;
|
|
var tickOpts = opts.ticks;
|
|
|
|
if (!tickOpts.display) {
|
|
return;
|
|
}
|
|
|
|
var startAngle = me.getIndexAngle(0);
|
|
var tickFont = helpers$1.options._parseFont(tickOpts);
|
|
var tickFontColor = valueOrDefault$c(tickOpts.fontColor, core_defaults.global.defaultFontColor);
|
|
var offset, width;
|
|
|
|
ctx.save();
|
|
ctx.font = tickFont.string;
|
|
ctx.translate(me.xCenter, me.yCenter);
|
|
ctx.rotate(startAngle);
|
|
ctx.textAlign = 'center';
|
|
ctx.textBaseline = 'middle';
|
|
|
|
helpers$1.each(me.ticks, function(label, index) {
|
|
if (index === 0 && !tickOpts.reverse) {
|
|
return;
|
|
}
|
|
|
|
offset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]);
|
|
|
|
if (tickOpts.showLabelBackdrop) {
|
|
width = ctx.measureText(label).width;
|
|
ctx.fillStyle = tickOpts.backdropColor;
|
|
|
|
ctx.fillRect(
|
|
-width / 2 - tickOpts.backdropPaddingX,
|
|
-offset - tickFont.size / 2 - tickOpts.backdropPaddingY,
|
|
width + tickOpts.backdropPaddingX * 2,
|
|
tickFont.size + tickOpts.backdropPaddingY * 2
|
|
);
|
|
}
|
|
|
|
ctx.fillStyle = tickFontColor;
|
|
ctx.fillText(label, 0, -offset);
|
|
});
|
|
|
|
ctx.restore();
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_drawTitle: helpers$1.noop
|
|
});
|
|
|
|
// INTERNAL: static default options, registered in src/index.js
|
|
var _defaults$3 = defaultConfig$3;
|
|
scale_radialLinear._defaults = _defaults$3;
|
|
|
|
var deprecated$1 = helpers$1._deprecated;
|
|
var resolve$5 = helpers$1.options.resolve;
|
|
var valueOrDefault$d = helpers$1.valueOrDefault;
|
|
|
|
// Integer constants are from the ES6 spec.
|
|
var MIN_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991;
|
|
var MAX_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991;
|
|
|
|
var INTERVALS = {
|
|
millisecond: {
|
|
common: true,
|
|
size: 1,
|
|
steps: 1000
|
|
},
|
|
second: {
|
|
common: true,
|
|
size: 1000,
|
|
steps: 60
|
|
},
|
|
minute: {
|
|
common: true,
|
|
size: 60000,
|
|
steps: 60
|
|
},
|
|
hour: {
|
|
common: true,
|
|
size: 3600000,
|
|
steps: 24
|
|
},
|
|
day: {
|
|
common: true,
|
|
size: 86400000,
|
|
steps: 30
|
|
},
|
|
week: {
|
|
common: false,
|
|
size: 604800000,
|
|
steps: 4
|
|
},
|
|
month: {
|
|
common: true,
|
|
size: 2.628e9,
|
|
steps: 12
|
|
},
|
|
quarter: {
|
|
common: false,
|
|
size: 7.884e9,
|
|
steps: 4
|
|
},
|
|
year: {
|
|
common: true,
|
|
size: 3.154e10
|
|
}
|
|
};
|
|
|
|
var UNITS = Object.keys(INTERVALS);
|
|
|
|
function sorter(a, b) {
|
|
return a - b;
|
|
}
|
|
|
|
function arrayUnique(items) {
|
|
var hash = {};
|
|
var out = [];
|
|
var i, ilen, item;
|
|
|
|
for (i = 0, ilen = items.length; i < ilen; ++i) {
|
|
item = items[i];
|
|
if (!hash[item]) {
|
|
hash[item] = true;
|
|
out.push(item);
|
|
}
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
function getMin(options) {
|
|
return helpers$1.valueOrDefault(options.time.min, options.ticks.min);
|
|
}
|
|
|
|
function getMax(options) {
|
|
return helpers$1.valueOrDefault(options.time.max, options.ticks.max);
|
|
}
|
|
|
|
/**
|
|
* Returns an array of {time, pos} objects used to interpolate a specific `time` or position
|
|
* (`pos`) on the scale, by searching entries before and after the requested value. `pos` is
|
|
* a decimal between 0 and 1: 0 being the start of the scale (left or top) and 1 the other
|
|
* extremity (left + width or top + height). Note that it would be more optimized to directly
|
|
* store pre-computed pixels, but the scale dimensions are not guaranteed at the time we need
|
|
* to create the lookup table. The table ALWAYS contains at least two items: min and max.
|
|
*
|
|
* @param {number[]} timestamps - timestamps sorted from lowest to highest.
|
|
* @param {string} distribution - If 'linear', timestamps will be spread linearly along the min
|
|
* and max range, so basically, the table will contains only two items: {min, 0} and {max, 1}.
|
|
* If 'series', timestamps will be positioned at the same distance from each other. In this
|
|
* case, only timestamps that break the time linearity are registered, meaning that in the
|
|
* best case, all timestamps are linear, the table contains only min and max.
|
|
*/
|
|
function buildLookupTable(timestamps, min, max, distribution) {
|
|
if (distribution === 'linear' || !timestamps.length) {
|
|
return [
|
|
{time: min, pos: 0},
|
|
{time: max, pos: 1}
|
|
];
|
|
}
|
|
|
|
var table = [];
|
|
var items = [min];
|
|
var i, ilen, prev, curr, next;
|
|
|
|
for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
|
|
curr = timestamps[i];
|
|
if (curr > min && curr < max) {
|
|
items.push(curr);
|
|
}
|
|
}
|
|
|
|
items.push(max);
|
|
|
|
for (i = 0, ilen = items.length; i < ilen; ++i) {
|
|
next = items[i + 1];
|
|
prev = items[i - 1];
|
|
curr = items[i];
|
|
|
|
// only add points that breaks the scale linearity
|
|
if (prev === undefined || next === undefined || Math.round((next + prev) / 2) !== curr) {
|
|
table.push({time: curr, pos: i / (ilen - 1)});
|
|
}
|
|
}
|
|
|
|
return table;
|
|
}
|
|
|
|
// @see adapted from https://www.anujgakhar.com/2014/03/01/binary-search-in-javascript/
|
|
function lookup(table, key, value) {
|
|
var lo = 0;
|
|
var hi = table.length - 1;
|
|
var mid, i0, i1;
|
|
|
|
while (lo >= 0 && lo <= hi) {
|
|
mid = (lo + hi) >> 1;
|
|
i0 = table[mid - 1] || null;
|
|
i1 = table[mid];
|
|
|
|
if (!i0) {
|
|
// given value is outside table (before first item)
|
|
return {lo: null, hi: i1};
|
|
} else if (i1[key] < value) {
|
|
lo = mid + 1;
|
|
} else if (i0[key] > value) {
|
|
hi = mid - 1;
|
|
} else {
|
|
return {lo: i0, hi: i1};
|
|
}
|
|
}
|
|
|
|
// given value is outside table (after last item)
|
|
return {lo: i1, hi: null};
|
|
}
|
|
|
|
/**
|
|
* Linearly interpolates the given source `value` using the table items `skey` values and
|
|
* returns the associated `tkey` value. For example, interpolate(table, 'time', 42, 'pos')
|
|
* returns the position for a timestamp equal to 42. If value is out of bounds, values at
|
|
* index [0, 1] or [n - 1, n] are used for the interpolation.
|
|
*/
|
|
function interpolate$1(table, skey, sval, tkey) {
|
|
var range = lookup(table, skey, sval);
|
|
|
|
// Note: the lookup table ALWAYS contains at least 2 items (min and max)
|
|
var prev = !range.lo ? table[0] : !range.hi ? table[table.length - 2] : range.lo;
|
|
var next = !range.lo ? table[1] : !range.hi ? table[table.length - 1] : range.hi;
|
|
|
|
var span = next[skey] - prev[skey];
|
|
var ratio = span ? (sval - prev[skey]) / span : 0;
|
|
var offset = (next[tkey] - prev[tkey]) * ratio;
|
|
|
|
return prev[tkey] + offset;
|
|
}
|
|
|
|
function toTimestamp(scale, input) {
|
|
var adapter = scale._adapter;
|
|
var options = scale.options.time;
|
|
var parser = options.parser;
|
|
var format = parser || options.format;
|
|
var value = input;
|
|
|
|
if (typeof parser === 'function') {
|
|
value = parser(value);
|
|
}
|
|
|
|
// Only parse if its not a timestamp already
|
|
if (!helpers$1.isFinite(value)) {
|
|
value = typeof format === 'string'
|
|
? adapter.parse(value, format)
|
|
: adapter.parse(value);
|
|
}
|
|
|
|
if (value !== null) {
|
|
return +value;
|
|
}
|
|
|
|
// Labels are in an incompatible format and no `parser` has been provided.
|
|
// The user might still use the deprecated `format` option for parsing.
|
|
if (!parser && typeof format === 'function') {
|
|
value = format(input);
|
|
|
|
// `format` could return something else than a timestamp, if so, parse it
|
|
if (!helpers$1.isFinite(value)) {
|
|
value = adapter.parse(value);
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
function parse(scale, input) {
|
|
if (helpers$1.isNullOrUndef(input)) {
|
|
return null;
|
|
}
|
|
|
|
var options = scale.options.time;
|
|
var value = toTimestamp(scale, scale.getRightValue(input));
|
|
if (value === null) {
|
|
return value;
|
|
}
|
|
|
|
if (options.round) {
|
|
value = +scale._adapter.startOf(value, options.round);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* Figures out what unit results in an appropriate number of auto-generated ticks
|
|
*/
|
|
function determineUnitForAutoTicks(minUnit, min, max, capacity) {
|
|
var ilen = UNITS.length;
|
|
var i, interval, factor;
|
|
|
|
for (i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) {
|
|
interval = INTERVALS[UNITS[i]];
|
|
factor = interval.steps ? interval.steps : MAX_INTEGER;
|
|
|
|
if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) {
|
|
return UNITS[i];
|
|
}
|
|
}
|
|
|
|
return UNITS[ilen - 1];
|
|
}
|
|
|
|
/**
|
|
* Figures out what unit to format a set of ticks with
|
|
*/
|
|
function determineUnitForFormatting(scale, numTicks, minUnit, min, max) {
|
|
var i, unit;
|
|
|
|
for (i = UNITS.length - 1; i >= UNITS.indexOf(minUnit); i--) {
|
|
unit = UNITS[i];
|
|
if (INTERVALS[unit].common && scale._adapter.diff(max, min, unit) >= numTicks - 1) {
|
|
return unit;
|
|
}
|
|
}
|
|
|
|
return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0];
|
|
}
|
|
|
|
function determineMajorUnit(unit) {
|
|
for (var i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i) {
|
|
if (INTERVALS[UNITS[i]].common) {
|
|
return UNITS[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generates a maximum of `capacity` timestamps between min and max, rounded to the
|
|
* `minor` unit using the given scale time `options`.
|
|
* Important: this method can return ticks outside the min and max range, it's the
|
|
* responsibility of the calling code to clamp values if needed.
|
|
*/
|
|
function generate(scale, min, max, capacity) {
|
|
var adapter = scale._adapter;
|
|
var options = scale.options;
|
|
var timeOpts = options.time;
|
|
var minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, capacity);
|
|
var stepSize = resolve$5([timeOpts.stepSize, timeOpts.unitStepSize, 1]);
|
|
var weekday = minor === 'week' ? timeOpts.isoWeekday : false;
|
|
var first = min;
|
|
var ticks = [];
|
|
var time;
|
|
|
|
// For 'week' unit, handle the first day of week option
|
|
if (weekday) {
|
|
first = +adapter.startOf(first, 'isoWeek', weekday);
|
|
}
|
|
|
|
// Align first ticks on unit
|
|
first = +adapter.startOf(first, weekday ? 'day' : minor);
|
|
|
|
// Prevent browser from freezing in case user options request millions of milliseconds
|
|
if (adapter.diff(max, min, minor) > 100000 * stepSize) {
|
|
throw min + ' and ' + max + ' are too far apart with stepSize of ' + stepSize + ' ' + minor;
|
|
}
|
|
|
|
for (time = first; time < max; time = +adapter.add(time, stepSize, minor)) {
|
|
ticks.push(time);
|
|
}
|
|
|
|
if (time === max || options.bounds === 'ticks') {
|
|
ticks.push(time);
|
|
}
|
|
|
|
return ticks;
|
|
}
|
|
|
|
/**
|
|
* Returns the start and end offsets from edges in the form of {start, end}
|
|
* where each value is a relative width to the scale and ranges between 0 and 1.
|
|
* They add extra margins on the both sides by scaling down the original scale.
|
|
* Offsets are added when the `offset` option is true.
|
|
*/
|
|
function computeOffsets(table, ticks, min, max, options) {
|
|
var start = 0;
|
|
var end = 0;
|
|
var first, last;
|
|
|
|
if (options.offset && ticks.length) {
|
|
first = interpolate$1(table, 'time', ticks[0], 'pos');
|
|
if (ticks.length === 1) {
|
|
start = 1 - first;
|
|
} else {
|
|
start = (interpolate$1(table, 'time', ticks[1], 'pos') - first) / 2;
|
|
}
|
|
last = interpolate$1(table, 'time', ticks[ticks.length - 1], 'pos');
|
|
if (ticks.length === 1) {
|
|
end = last;
|
|
} else {
|
|
end = (last - interpolate$1(table, 'time', ticks[ticks.length - 2], 'pos')) / 2;
|
|
}
|
|
}
|
|
|
|
return {start: start, end: end, factor: 1 / (start + 1 + end)};
|
|
}
|
|
|
|
function setMajorTicks(scale, ticks, map, majorUnit) {
|
|
var adapter = scale._adapter;
|
|
var first = +adapter.startOf(ticks[0].value, majorUnit);
|
|
var last = ticks[ticks.length - 1].value;
|
|
var major, index;
|
|
|
|
for (major = first; major <= last; major = +adapter.add(major, 1, majorUnit)) {
|
|
index = map[major];
|
|
if (index >= 0) {
|
|
ticks[index].major = true;
|
|
}
|
|
}
|
|
return ticks;
|
|
}
|
|
|
|
function ticksFromTimestamps(scale, values, majorUnit) {
|
|
var ticks = [];
|
|
var map = {};
|
|
var ilen = values.length;
|
|
var i, value;
|
|
|
|
for (i = 0; i < ilen; ++i) {
|
|
value = values[i];
|
|
map[value] = i;
|
|
|
|
ticks.push({
|
|
value: value,
|
|
major: false
|
|
});
|
|
}
|
|
|
|
// We set the major ticks separately from the above loop because calling startOf for every tick
|
|
// is expensive when there is a large number of ticks
|
|
return (ilen === 0 || !majorUnit) ? ticks : setMajorTicks(scale, ticks, map, majorUnit);
|
|
}
|
|
|
|
var defaultConfig$4 = {
|
|
position: 'bottom',
|
|
|
|
/**
|
|
* Data distribution along the scale:
|
|
* - 'linear': data are spread according to their time (distances can vary),
|
|
* - 'series': data are spread at the same distance from each other.
|
|
* @see https://github.com/chartjs/Chart.js/pull/4507
|
|
* @since 2.7.0
|
|
*/
|
|
distribution: 'linear',
|
|
|
|
/**
|
|
* Scale boundary strategy (bypassed by min/max time options)
|
|
* - `data`: make sure data are fully visible, ticks outside are removed
|
|
* - `ticks`: make sure ticks are fully visible, data outside are truncated
|
|
* @see https://github.com/chartjs/Chart.js/pull/4556
|
|
* @since 2.7.0
|
|
*/
|
|
bounds: 'data',
|
|
|
|
adapters: {},
|
|
time: {
|
|
parser: false, // false == a pattern string from https://momentjs.com/docs/#/parsing/string-format/ or a custom callback that converts its argument to a moment
|
|
unit: false, // false == automatic or override with week, month, year, etc.
|
|
round: false, // none, or override with week, month, year, etc.
|
|
displayFormat: false, // DEPRECATED
|
|
isoWeekday: false, // override week start day - see https://momentjs.com/docs/#/get-set/iso-weekday/
|
|
minUnit: 'millisecond',
|
|
displayFormats: {}
|
|
},
|
|
ticks: {
|
|
autoSkip: false,
|
|
|
|
/**
|
|
* Ticks generation input values:
|
|
* - 'auto': generates "optimal" ticks based on scale size and time options.
|
|
* - 'data': generates ticks from data (including labels from data {t|x|y} objects).
|
|
* - 'labels': generates ticks from user given `data.labels` values ONLY.
|
|
* @see https://github.com/chartjs/Chart.js/pull/4507
|
|
* @since 2.7.0
|
|
*/
|
|
source: 'auto',
|
|
|
|
major: {
|
|
enabled: false
|
|
}
|
|
}
|
|
};
|
|
|
|
var scale_time = core_scale.extend({
|
|
initialize: function() {
|
|
this.mergeTicksOptions();
|
|
core_scale.prototype.initialize.call(this);
|
|
},
|
|
|
|
update: function() {
|
|
var me = this;
|
|
var options = me.options;
|
|
var time = options.time || (options.time = {});
|
|
var adapter = me._adapter = new core_adapters._date(options.adapters.date);
|
|
|
|
// DEPRECATIONS: output a message only one time per update
|
|
deprecated$1('time scale', time.format, 'time.format', 'time.parser');
|
|
deprecated$1('time scale', time.min, 'time.min', 'ticks.min');
|
|
deprecated$1('time scale', time.max, 'time.max', 'ticks.max');
|
|
|
|
// Backward compatibility: before introducing adapter, `displayFormats` was
|
|
// supposed to contain *all* unit/string pairs but this can't be resolved
|
|
// when loading the scale (adapters are loaded afterward), so let's populate
|
|
// missing formats on update
|
|
helpers$1.mergeIf(time.displayFormats, adapter.formats());
|
|
|
|
return core_scale.prototype.update.apply(me, arguments);
|
|
},
|
|
|
|
/**
|
|
* Allows data to be referenced via 't' attribute
|
|
*/
|
|
getRightValue: function(rawValue) {
|
|
if (rawValue && rawValue.t !== undefined) {
|
|
rawValue = rawValue.t;
|
|
}
|
|
return core_scale.prototype.getRightValue.call(this, rawValue);
|
|
},
|
|
|
|
determineDataLimits: function() {
|
|
var me = this;
|
|
var chart = me.chart;
|
|
var adapter = me._adapter;
|
|
var options = me.options;
|
|
var unit = options.time.unit || 'day';
|
|
var min = MAX_INTEGER;
|
|
var max = MIN_INTEGER;
|
|
var timestamps = [];
|
|
var datasets = [];
|
|
var labels = [];
|
|
var i, j, ilen, jlen, data, timestamp, labelsAdded;
|
|
var dataLabels = me._getLabels();
|
|
|
|
for (i = 0, ilen = dataLabels.length; i < ilen; ++i) {
|
|
labels.push(parse(me, dataLabels[i]));
|
|
}
|
|
|
|
for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) {
|
|
if (chart.isDatasetVisible(i)) {
|
|
data = chart.data.datasets[i].data;
|
|
|
|
// Let's consider that all data have the same format.
|
|
if (helpers$1.isObject(data[0])) {
|
|
datasets[i] = [];
|
|
|
|
for (j = 0, jlen = data.length; j < jlen; ++j) {
|
|
timestamp = parse(me, data[j]);
|
|
timestamps.push(timestamp);
|
|
datasets[i][j] = timestamp;
|
|
}
|
|
} else {
|
|
datasets[i] = labels.slice(0);
|
|
if (!labelsAdded) {
|
|
timestamps = timestamps.concat(labels);
|
|
labelsAdded = true;
|
|
}
|
|
}
|
|
} else {
|
|
datasets[i] = [];
|
|
}
|
|
}
|
|
|
|
if (labels.length) {
|
|
min = Math.min(min, labels[0]);
|
|
max = Math.max(max, labels[labels.length - 1]);
|
|
}
|
|
|
|
if (timestamps.length) {
|
|
timestamps = ilen > 1 ? arrayUnique(timestamps).sort(sorter) : timestamps.sort(sorter);
|
|
min = Math.min(min, timestamps[0]);
|
|
max = Math.max(max, timestamps[timestamps.length - 1]);
|
|
}
|
|
|
|
min = parse(me, getMin(options)) || min;
|
|
max = parse(me, getMax(options)) || max;
|
|
|
|
// In case there is no valid min/max, set limits based on unit time option
|
|
min = min === MAX_INTEGER ? +adapter.startOf(Date.now(), unit) : min;
|
|
max = max === MIN_INTEGER ? +adapter.endOf(Date.now(), unit) + 1 : max;
|
|
|
|
// Make sure that max is strictly higher than min (required by the lookup table)
|
|
me.min = Math.min(min, max);
|
|
me.max = Math.max(min + 1, max);
|
|
|
|
// PRIVATE
|
|
me._table = [];
|
|
me._timestamps = {
|
|
data: timestamps,
|
|
datasets: datasets,
|
|
labels: labels
|
|
};
|
|
},
|
|
|
|
buildTicks: function() {
|
|
var me = this;
|
|
var min = me.min;
|
|
var max = me.max;
|
|
var options = me.options;
|
|
var tickOpts = options.ticks;
|
|
var timeOpts = options.time;
|
|
var timestamps = me._timestamps;
|
|
var ticks = [];
|
|
var capacity = me.getLabelCapacity(min);
|
|
var source = tickOpts.source;
|
|
var distribution = options.distribution;
|
|
var i, ilen, timestamp;
|
|
|
|
if (source === 'data' || (source === 'auto' && distribution === 'series')) {
|
|
timestamps = timestamps.data;
|
|
} else if (source === 'labels') {
|
|
timestamps = timestamps.labels;
|
|
} else {
|
|
timestamps = generate(me, min, max, capacity);
|
|
}
|
|
|
|
if (options.bounds === 'ticks' && timestamps.length) {
|
|
min = timestamps[0];
|
|
max = timestamps[timestamps.length - 1];
|
|
}
|
|
|
|
// Enforce limits with user min/max options
|
|
min = parse(me, getMin(options)) || min;
|
|
max = parse(me, getMax(options)) || max;
|
|
|
|
// Remove ticks outside the min/max range
|
|
for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
|
|
timestamp = timestamps[i];
|
|
if (timestamp >= min && timestamp <= max) {
|
|
ticks.push(timestamp);
|
|
}
|
|
}
|
|
|
|
me.min = min;
|
|
me.max = max;
|
|
|
|
// PRIVATE
|
|
// determineUnitForFormatting relies on the number of ticks so we don't use it when
|
|
// autoSkip is enabled because we don't yet know what the final number of ticks will be
|
|
me._unit = timeOpts.unit || (tickOpts.autoSkip
|
|
? determineUnitForAutoTicks(timeOpts.minUnit, me.min, me.max, capacity)
|
|
: determineUnitForFormatting(me, ticks.length, timeOpts.minUnit, me.min, me.max));
|
|
me._majorUnit = !tickOpts.major.enabled || me._unit === 'year' ? undefined
|
|
: determineMajorUnit(me._unit);
|
|
me._table = buildLookupTable(me._timestamps.data, min, max, distribution);
|
|
me._offsets = computeOffsets(me._table, ticks, min, max, options);
|
|
|
|
if (tickOpts.reverse) {
|
|
ticks.reverse();
|
|
}
|
|
|
|
return ticksFromTimestamps(me, ticks, me._majorUnit);
|
|
},
|
|
|
|
getLabelForIndex: function(index, datasetIndex) {
|
|
var me = this;
|
|
var adapter = me._adapter;
|
|
var data = me.chart.data;
|
|
var timeOpts = me.options.time;
|
|
var label = data.labels && index < data.labels.length ? data.labels[index] : '';
|
|
var value = data.datasets[datasetIndex].data[index];
|
|
|
|
if (helpers$1.isObject(value)) {
|
|
label = me.getRightValue(value);
|
|
}
|
|
if (timeOpts.tooltipFormat) {
|
|
return adapter.format(toTimestamp(me, label), timeOpts.tooltipFormat);
|
|
}
|
|
if (typeof label === 'string') {
|
|
return label;
|
|
}
|
|
return adapter.format(toTimestamp(me, label), timeOpts.displayFormats.datetime);
|
|
},
|
|
|
|
/**
|
|
* Function to format an individual tick mark
|
|
* @private
|
|
*/
|
|
tickFormatFunction: function(time, index, ticks, format) {
|
|
var me = this;
|
|
var adapter = me._adapter;
|
|
var options = me.options;
|
|
var formats = options.time.displayFormats;
|
|
var minorFormat = formats[me._unit];
|
|
var majorUnit = me._majorUnit;
|
|
var majorFormat = formats[majorUnit];
|
|
var tick = ticks[index];
|
|
var tickOpts = options.ticks;
|
|
var major = majorUnit && majorFormat && tick && tick.major;
|
|
var label = adapter.format(time, format ? format : major ? majorFormat : minorFormat);
|
|
var nestedTickOpts = major ? tickOpts.major : tickOpts.minor;
|
|
var formatter = resolve$5([
|
|
nestedTickOpts.callback,
|
|
nestedTickOpts.userCallback,
|
|
tickOpts.callback,
|
|
tickOpts.userCallback
|
|
]);
|
|
|
|
return formatter ? formatter(label, index, ticks) : label;
|
|
},
|
|
|
|
convertTicksToLabels: function(ticks) {
|
|
var labels = [];
|
|
var i, ilen;
|
|
|
|
for (i = 0, ilen = ticks.length; i < ilen; ++i) {
|
|
labels.push(this.tickFormatFunction(ticks[i].value, i, ticks));
|
|
}
|
|
|
|
return labels;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
getPixelForOffset: function(time) {
|
|
var me = this;
|
|
var offsets = me._offsets;
|
|
var pos = interpolate$1(me._table, 'time', time, 'pos');
|
|
return me.getPixelForDecimal((offsets.start + pos) * offsets.factor);
|
|
},
|
|
|
|
getPixelForValue: function(value, index, datasetIndex) {
|
|
var me = this;
|
|
var time = null;
|
|
|
|
if (index !== undefined && datasetIndex !== undefined) {
|
|
time = me._timestamps.datasets[datasetIndex][index];
|
|
}
|
|
|
|
if (time === null) {
|
|
time = parse(me, value);
|
|
}
|
|
|
|
if (time !== null) {
|
|
return me.getPixelForOffset(time);
|
|
}
|
|
},
|
|
|
|
getPixelForTick: function(index) {
|
|
var ticks = this.getTicks();
|
|
return index >= 0 && index < ticks.length ?
|
|
this.getPixelForOffset(ticks[index].value) :
|
|
null;
|
|
},
|
|
|
|
getValueForPixel: function(pixel) {
|
|
var me = this;
|
|
var offsets = me._offsets;
|
|
var pos = me.getDecimalForPixel(pixel) / offsets.factor - offsets.end;
|
|
var time = interpolate$1(me._table, 'pos', pos, 'time');
|
|
|
|
// DEPRECATION, we should return time directly
|
|
return me._adapter._create(time);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_getLabelSize: function(label) {
|
|
var me = this;
|
|
var ticksOpts = me.options.ticks;
|
|
var tickLabelWidth = me.ctx.measureText(label).width;
|
|
var angle = helpers$1.toRadians(me.isHorizontal() ? ticksOpts.maxRotation : ticksOpts.minRotation);
|
|
var cosRotation = Math.cos(angle);
|
|
var sinRotation = Math.sin(angle);
|
|
var tickFontSize = valueOrDefault$d(ticksOpts.fontSize, core_defaults.global.defaultFontSize);
|
|
|
|
return {
|
|
w: (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation),
|
|
h: (tickLabelWidth * sinRotation) + (tickFontSize * cosRotation)
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Crude approximation of what the label width might be
|
|
* @private
|
|
*/
|
|
getLabelWidth: function(label) {
|
|
return this._getLabelSize(label).w;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
getLabelCapacity: function(exampleTime) {
|
|
var me = this;
|
|
var timeOpts = me.options.time;
|
|
var displayFormats = timeOpts.displayFormats;
|
|
|
|
// pick the longest format (milliseconds) for guestimation
|
|
var format = displayFormats[timeOpts.unit] || displayFormats.millisecond;
|
|
var exampleLabel = me.tickFormatFunction(exampleTime, 0, ticksFromTimestamps(me, [exampleTime], me._majorUnit), format);
|
|
var size = me._getLabelSize(exampleLabel);
|
|
var capacity = Math.floor(me.isHorizontal() ? me.width / size.w : me.height / size.h);
|
|
|
|
if (me.options.offset) {
|
|
capacity--;
|
|
}
|
|
|
|
return capacity > 0 ? capacity : 1;
|
|
}
|
|
});
|
|
|
|
// INTERNAL: static default options, registered in src/index.js
|
|
var _defaults$4 = defaultConfig$4;
|
|
scale_time._defaults = _defaults$4;
|
|
|
|
var scales = {
|
|
category: scale_category,
|
|
linear: scale_linear,
|
|
logarithmic: scale_logarithmic,
|
|
radialLinear: scale_radialLinear,
|
|
time: scale_time
|
|
};
|
|
|
|
var moment = createCommonjsModule(function (module, exports) {
|
|
(function (global, factory) {
|
|
module.exports = factory() ;
|
|
}(commonjsGlobal, (function () {
|
|
var hookCallback;
|
|
|
|
function hooks () {
|
|
return hookCallback.apply(null, arguments);
|
|
}
|
|
|
|
// This is done to register the method called with moment()
|
|
// without creating circular dependencies.
|
|
function setHookCallback (callback) {
|
|
hookCallback = callback;
|
|
}
|
|
|
|
function isArray(input) {
|
|
return input instanceof Array || Object.prototype.toString.call(input) === '[object Array]';
|
|
}
|
|
|
|
function isObject(input) {
|
|
// IE8 will treat undefined and null as object if it wasn't for
|
|
// input != null
|
|
return input != null && Object.prototype.toString.call(input) === '[object Object]';
|
|
}
|
|
|
|
function isObjectEmpty(obj) {
|
|
if (Object.getOwnPropertyNames) {
|
|
return (Object.getOwnPropertyNames(obj).length === 0);
|
|
} else {
|
|
var k;
|
|
for (k in obj) {
|
|
if (obj.hasOwnProperty(k)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
function isUndefined(input) {
|
|
return input === void 0;
|
|
}
|
|
|
|
function isNumber(input) {
|
|
return typeof input === 'number' || Object.prototype.toString.call(input) === '[object Number]';
|
|
}
|
|
|
|
function isDate(input) {
|
|
return input instanceof Date || Object.prototype.toString.call(input) === '[object Date]';
|
|
}
|
|
|
|
function map(arr, fn) {
|
|
var res = [], i;
|
|
for (i = 0; i < arr.length; ++i) {
|
|
res.push(fn(arr[i], i));
|
|
}
|
|
return res;
|
|
}
|
|
|
|
function hasOwnProp(a, b) {
|
|
return Object.prototype.hasOwnProperty.call(a, b);
|
|
}
|
|
|
|
function extend(a, b) {
|
|
for (var i in b) {
|
|
if (hasOwnProp(b, i)) {
|
|
a[i] = b[i];
|
|
}
|
|
}
|
|
|
|
if (hasOwnProp(b, 'toString')) {
|
|
a.toString = b.toString;
|
|
}
|
|
|
|
if (hasOwnProp(b, 'valueOf')) {
|
|
a.valueOf = b.valueOf;
|
|
}
|
|
|
|
return a;
|
|
}
|
|
|
|
function createUTC (input, format, locale, strict) {
|
|
return createLocalOrUTC(input, format, locale, strict, true).utc();
|
|
}
|
|
|
|
function defaultParsingFlags() {
|
|
// We need to deep clone this object.
|
|
return {
|
|
empty : false,
|
|
unusedTokens : [],
|
|
unusedInput : [],
|
|
overflow : -2,
|
|
charsLeftOver : 0,
|
|
nullInput : false,
|
|
invalidMonth : null,
|
|
invalidFormat : false,
|
|
userInvalidated : false,
|
|
iso : false,
|
|
parsedDateParts : [],
|
|
meridiem : null,
|
|
rfc2822 : false,
|
|
weekdayMismatch : false
|
|
};
|
|
}
|
|
|
|
function getParsingFlags(m) {
|
|
if (m._pf == null) {
|
|
m._pf = defaultParsingFlags();
|
|
}
|
|
return m._pf;
|
|
}
|
|
|
|
var some;
|
|
if (Array.prototype.some) {
|
|
some = Array.prototype.some;
|
|
} else {
|
|
some = function (fun) {
|
|
var t = Object(this);
|
|
var len = t.length >>> 0;
|
|
|
|
for (var i = 0; i < len; i++) {
|
|
if (i in t && fun.call(this, t[i], i, t)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
};
|
|
}
|
|
|
|
function isValid(m) {
|
|
if (m._isValid == null) {
|
|
var flags = getParsingFlags(m);
|
|
var parsedParts = some.call(flags.parsedDateParts, function (i) {
|
|
return i != null;
|
|
});
|
|
var isNowValid = !isNaN(m._d.getTime()) &&
|
|
flags.overflow < 0 &&
|
|
!flags.empty &&
|
|
!flags.invalidMonth &&
|
|
!flags.invalidWeekday &&
|
|
!flags.weekdayMismatch &&
|
|
!flags.nullInput &&
|
|
!flags.invalidFormat &&
|
|
!flags.userInvalidated &&
|
|
(!flags.meridiem || (flags.meridiem && parsedParts));
|
|
|
|
if (m._strict) {
|
|
isNowValid = isNowValid &&
|
|
flags.charsLeftOver === 0 &&
|
|
flags.unusedTokens.length === 0 &&
|
|
flags.bigHour === undefined;
|
|
}
|
|
|
|
if (Object.isFrozen == null || !Object.isFrozen(m)) {
|
|
m._isValid = isNowValid;
|
|
}
|
|
else {
|
|
return isNowValid;
|
|
}
|
|
}
|
|
return m._isValid;
|
|
}
|
|
|
|
function createInvalid (flags) {
|
|
var m = createUTC(NaN);
|
|
if (flags != null) {
|
|
extend(getParsingFlags(m), flags);
|
|
}
|
|
else {
|
|
getParsingFlags(m).userInvalidated = true;
|
|
}
|
|
|
|
return m;
|
|
}
|
|
|
|
// Plugins that add properties should also add the key here (null value),
|
|
// so we can properly clone ourselves.
|
|
var momentProperties = hooks.momentProperties = [];
|
|
|
|
function copyConfig(to, from) {
|
|
var i, prop, val;
|
|
|
|
if (!isUndefined(from._isAMomentObject)) {
|
|
to._isAMomentObject = from._isAMomentObject;
|
|
}
|
|
if (!isUndefined(from._i)) {
|
|
to._i = from._i;
|
|
}
|
|
if (!isUndefined(from._f)) {
|
|
to._f = from._f;
|
|
}
|
|
if (!isUndefined(from._l)) {
|
|
to._l = from._l;
|
|
}
|
|
if (!isUndefined(from._strict)) {
|
|
to._strict = from._strict;
|
|
}
|
|
if (!isUndefined(from._tzm)) {
|
|
to._tzm = from._tzm;
|
|
}
|
|
if (!isUndefined(from._isUTC)) {
|
|
to._isUTC = from._isUTC;
|
|
}
|
|
if (!isUndefined(from._offset)) {
|
|
to._offset = from._offset;
|
|
}
|
|
if (!isUndefined(from._pf)) {
|
|
to._pf = getParsingFlags(from);
|
|
}
|
|
if (!isUndefined(from._locale)) {
|
|
to._locale = from._locale;
|
|
}
|
|
|
|
if (momentProperties.length > 0) {
|
|
for (i = 0; i < momentProperties.length; i++) {
|
|
prop = momentProperties[i];
|
|
val = from[prop];
|
|
if (!isUndefined(val)) {
|
|
to[prop] = val;
|
|
}
|
|
}
|
|
}
|
|
|
|
return to;
|
|
}
|
|
|
|
var updateInProgress = false;
|
|
|
|
// Moment prototype object
|
|
function Moment(config) {
|
|
copyConfig(this, config);
|
|
this._d = new Date(config._d != null ? config._d.getTime() : NaN);
|
|
if (!this.isValid()) {
|
|
this._d = new Date(NaN);
|
|
}
|
|
// Prevent infinite loop in case updateOffset creates new moment
|
|
// objects.
|
|
if (updateInProgress === false) {
|
|
updateInProgress = true;
|
|
hooks.updateOffset(this);
|
|
updateInProgress = false;
|
|
}
|
|
}
|
|
|
|
function isMoment (obj) {
|
|
return obj instanceof Moment || (obj != null && obj._isAMomentObject != null);
|
|
}
|
|
|
|
function absFloor (number) {
|
|
if (number < 0) {
|
|
// -0 -> 0
|
|
return Math.ceil(number) || 0;
|
|
} else {
|
|
return Math.floor(number);
|
|
}
|
|
}
|
|
|
|
function toInt(argumentForCoercion) {
|
|
var coercedNumber = +argumentForCoercion,
|
|
value = 0;
|
|
|
|
if (coercedNumber !== 0 && isFinite(coercedNumber)) {
|
|
value = absFloor(coercedNumber);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
// compare two arrays, return the number of differences
|
|
function compareArrays(array1, array2, dontConvert) {
|
|
var len = Math.min(array1.length, array2.length),
|
|
lengthDiff = Math.abs(array1.length - array2.length),
|
|
diffs = 0,
|
|
i;
|
|
for (i = 0; i < len; i++) {
|
|
if ((dontConvert && array1[i] !== array2[i]) ||
|
|
(!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) {
|
|
diffs++;
|
|
}
|
|
}
|
|
return diffs + lengthDiff;
|
|
}
|
|
|
|
function warn(msg) {
|
|
if (hooks.suppressDeprecationWarnings === false &&
|
|
(typeof console !== 'undefined') && console.warn) {
|
|
console.warn('Deprecation warning: ' + msg);
|
|
}
|
|
}
|
|
|
|
function deprecate(msg, fn) {
|
|
var firstTime = true;
|
|
|
|
return extend(function () {
|
|
if (hooks.deprecationHandler != null) {
|
|
hooks.deprecationHandler(null, msg);
|
|
}
|
|
if (firstTime) {
|
|
var args = [];
|
|
var arg;
|
|
for (var i = 0; i < arguments.length; i++) {
|
|
arg = '';
|
|
if (typeof arguments[i] === 'object') {
|
|
arg += '\n[' + i + '] ';
|
|
for (var key in arguments[0]) {
|
|
arg += key + ': ' + arguments[0][key] + ', ';
|
|
}
|
|
arg = arg.slice(0, -2); // Remove trailing comma and space
|
|
} else {
|
|
arg = arguments[i];
|
|
}
|
|
args.push(arg);
|
|
}
|
|
warn(msg + '\nArguments: ' + Array.prototype.slice.call(args).join('') + '\n' + (new Error()).stack);
|
|
firstTime = false;
|
|
}
|
|
return fn.apply(this, arguments);
|
|
}, fn);
|
|
}
|
|
|
|
var deprecations = {};
|
|
|
|
function deprecateSimple(name, msg) {
|
|
if (hooks.deprecationHandler != null) {
|
|
hooks.deprecationHandler(name, msg);
|
|
}
|
|
if (!deprecations[name]) {
|
|
warn(msg);
|
|
deprecations[name] = true;
|
|
}
|
|
}
|
|
|
|
hooks.suppressDeprecationWarnings = false;
|
|
hooks.deprecationHandler = null;
|
|
|
|
function isFunction(input) {
|
|
return input instanceof Function || Object.prototype.toString.call(input) === '[object Function]';
|
|
}
|
|
|
|
function set (config) {
|
|
var prop, i;
|
|
for (i in config) {
|
|
prop = config[i];
|
|
if (isFunction(prop)) {
|
|
this[i] = prop;
|
|
} else {
|
|
this['_' + i] = prop;
|
|
}
|
|
}
|
|
this._config = config;
|
|
// Lenient ordinal parsing accepts just a number in addition to
|
|
// number + (possibly) stuff coming from _dayOfMonthOrdinalParse.
|
|
// TODO: Remove "ordinalParse" fallback in next major release.
|
|
this._dayOfMonthOrdinalParseLenient = new RegExp(
|
|
(this._dayOfMonthOrdinalParse.source || this._ordinalParse.source) +
|
|
'|' + (/\d{1,2}/).source);
|
|
}
|
|
|
|
function mergeConfigs(parentConfig, childConfig) {
|
|
var res = extend({}, parentConfig), prop;
|
|
for (prop in childConfig) {
|
|
if (hasOwnProp(childConfig, prop)) {
|
|
if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) {
|
|
res[prop] = {};
|
|
extend(res[prop], parentConfig[prop]);
|
|
extend(res[prop], childConfig[prop]);
|
|
} else if (childConfig[prop] != null) {
|
|
res[prop] = childConfig[prop];
|
|
} else {
|
|
delete res[prop];
|
|
}
|
|
}
|
|
}
|
|
for (prop in parentConfig) {
|
|
if (hasOwnProp(parentConfig, prop) &&
|
|
!hasOwnProp(childConfig, prop) &&
|
|
isObject(parentConfig[prop])) {
|
|
// make sure changes to properties don't modify parent config
|
|
res[prop] = extend({}, res[prop]);
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
function Locale(config) {
|
|
if (config != null) {
|
|
this.set(config);
|
|
}
|
|
}
|
|
|
|
var keys;
|
|
|
|
if (Object.keys) {
|
|
keys = Object.keys;
|
|
} else {
|
|
keys = function (obj) {
|
|
var i, res = [];
|
|
for (i in obj) {
|
|
if (hasOwnProp(obj, i)) {
|
|
res.push(i);
|
|
}
|
|
}
|
|
return res;
|
|
};
|
|
}
|
|
|
|
var defaultCalendar = {
|
|
sameDay : '[Today at] LT',
|
|
nextDay : '[Tomorrow at] LT',
|
|
nextWeek : 'dddd [at] LT',
|
|
lastDay : '[Yesterday at] LT',
|
|
lastWeek : '[Last] dddd [at] LT',
|
|
sameElse : 'L'
|
|
};
|
|
|
|
function calendar (key, mom, now) {
|
|
var output = this._calendar[key] || this._calendar['sameElse'];
|
|
return isFunction(output) ? output.call(mom, now) : output;
|
|
}
|
|
|
|
var defaultLongDateFormat = {
|
|
LTS : 'h:mm:ss A',
|
|
LT : 'h:mm A',
|
|
L : 'MM/DD/YYYY',
|
|
LL : 'MMMM D, YYYY',
|
|
LLL : 'MMMM D, YYYY h:mm A',
|
|
LLLL : 'dddd, MMMM D, YYYY h:mm A'
|
|
};
|
|
|
|
function longDateFormat (key) {
|
|
var format = this._longDateFormat[key],
|
|
formatUpper = this._longDateFormat[key.toUpperCase()];
|
|
|
|
if (format || !formatUpper) {
|
|
return format;
|
|
}
|
|
|
|
this._longDateFormat[key] = formatUpper.replace(/MMMM|MM|DD|dddd/g, function (val) {
|
|
return val.slice(1);
|
|
});
|
|
|
|
return this._longDateFormat[key];
|
|
}
|
|
|
|
var defaultInvalidDate = 'Invalid date';
|
|
|
|
function invalidDate () {
|
|
return this._invalidDate;
|
|
}
|
|
|
|
var defaultOrdinal = '%d';
|
|
var defaultDayOfMonthOrdinalParse = /\d{1,2}/;
|
|
|
|
function ordinal (number) {
|
|
return this._ordinal.replace('%d', number);
|
|
}
|
|
|
|
var defaultRelativeTime = {
|
|
future : 'in %s',
|
|
past : '%s ago',
|
|
s : 'a few seconds',
|
|
ss : '%d seconds',
|
|
m : 'a minute',
|
|
mm : '%d minutes',
|
|
h : 'an hour',
|
|
hh : '%d hours',
|
|
d : 'a day',
|
|
dd : '%d days',
|
|
M : 'a month',
|
|
MM : '%d months',
|
|
y : 'a year',
|
|
yy : '%d years'
|
|
};
|
|
|
|
function relativeTime (number, withoutSuffix, string, isFuture) {
|
|
var output = this._relativeTime[string];
|
|
return (isFunction(output)) ?
|
|
output(number, withoutSuffix, string, isFuture) :
|
|
output.replace(/%d/i, number);
|
|
}
|
|
|
|
function pastFuture (diff, output) {
|
|
var format = this._relativeTime[diff > 0 ? 'future' : 'past'];
|
|
return isFunction(format) ? format(output) : format.replace(/%s/i, output);
|
|
}
|
|
|
|
var aliases = {};
|
|
|
|
function addUnitAlias (unit, shorthand) {
|
|
var lowerCase = unit.toLowerCase();
|
|
aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit;
|
|
}
|
|
|
|
function normalizeUnits(units) {
|
|
return typeof units === 'string' ? aliases[units] || aliases[units.toLowerCase()] : undefined;
|
|
}
|
|
|
|
function normalizeObjectUnits(inputObject) {
|
|
var normalizedInput = {},
|
|
normalizedProp,
|
|
prop;
|
|
|
|
for (prop in inputObject) {
|
|
if (hasOwnProp(inputObject, prop)) {
|
|
normalizedProp = normalizeUnits(prop);
|
|
if (normalizedProp) {
|
|
normalizedInput[normalizedProp] = inputObject[prop];
|
|
}
|
|
}
|
|
}
|
|
|
|
return normalizedInput;
|
|
}
|
|
|
|
var priorities = {};
|
|
|
|
function addUnitPriority(unit, priority) {
|
|
priorities[unit] = priority;
|
|
}
|
|
|
|
function getPrioritizedUnits(unitsObj) {
|
|
var units = [];
|
|
for (var u in unitsObj) {
|
|
units.push({unit: u, priority: priorities[u]});
|
|
}
|
|
units.sort(function (a, b) {
|
|
return a.priority - b.priority;
|
|
});
|
|
return units;
|
|
}
|
|
|
|
function zeroFill(number, targetLength, forceSign) {
|
|
var absNumber = '' + Math.abs(number),
|
|
zerosToFill = targetLength - absNumber.length,
|
|
sign = number >= 0;
|
|
return (sign ? (forceSign ? '+' : '') : '-') +
|
|
Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) + absNumber;
|
|
}
|
|
|
|
var formattingTokens = /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g;
|
|
|
|
var localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g;
|
|
|
|
var formatFunctions = {};
|
|
|
|
var formatTokenFunctions = {};
|
|
|
|
// token: 'M'
|
|
// padded: ['MM', 2]
|
|
// ordinal: 'Mo'
|
|
// callback: function () { this.month() + 1 }
|
|
function addFormatToken (token, padded, ordinal, callback) {
|
|
var func = callback;
|
|
if (typeof callback === 'string') {
|
|
func = function () {
|
|
return this[callback]();
|
|
};
|
|
}
|
|
if (token) {
|
|
formatTokenFunctions[token] = func;
|
|
}
|
|
if (padded) {
|
|
formatTokenFunctions[padded[0]] = function () {
|
|
return zeroFill(func.apply(this, arguments), padded[1], padded[2]);
|
|
};
|
|
}
|
|
if (ordinal) {
|
|
formatTokenFunctions[ordinal] = function () {
|
|
return this.localeData().ordinal(func.apply(this, arguments), token);
|
|
};
|
|
}
|
|
}
|
|
|
|
function removeFormattingTokens(input) {
|
|
if (input.match(/\[[\s\S]/)) {
|
|
return input.replace(/^\[|\]$/g, '');
|
|
}
|
|
return input.replace(/\\/g, '');
|
|
}
|
|
|
|
function makeFormatFunction(format) {
|
|
var array = format.match(formattingTokens), i, length;
|
|
|
|
for (i = 0, length = array.length; i < length; i++) {
|
|
if (formatTokenFunctions[array[i]]) {
|
|
array[i] = formatTokenFunctions[array[i]];
|
|
} else {
|
|
array[i] = removeFormattingTokens(array[i]);
|
|
}
|
|
}
|
|
|
|
return function (mom) {
|
|
var output = '', i;
|
|
for (i = 0; i < length; i++) {
|
|
output += isFunction(array[i]) ? array[i].call(mom, format) : array[i];
|
|
}
|
|
return output;
|
|
};
|
|
}
|
|
|
|
// format date using native date object
|
|
function formatMoment(m, format) {
|
|
if (!m.isValid()) {
|
|
return m.localeData().invalidDate();
|
|
}
|
|
|
|
format = expandFormat(format, m.localeData());
|
|
formatFunctions[format] = formatFunctions[format] || makeFormatFunction(format);
|
|
|
|
return formatFunctions[format](m);
|
|
}
|
|
|
|
function expandFormat(format, locale) {
|
|
var i = 5;
|
|
|
|
function replaceLongDateFormatTokens(input) {
|
|
return locale.longDateFormat(input) || input;
|
|
}
|
|
|
|
localFormattingTokens.lastIndex = 0;
|
|
while (i >= 0 && localFormattingTokens.test(format)) {
|
|
format = format.replace(localFormattingTokens, replaceLongDateFormatTokens);
|
|
localFormattingTokens.lastIndex = 0;
|
|
i -= 1;
|
|
}
|
|
|
|
return format;
|
|
}
|
|
|
|
var match1 = /\d/; // 0 - 9
|
|
var match2 = /\d\d/; // 00 - 99
|
|
var match3 = /\d{3}/; // 000 - 999
|
|
var match4 = /\d{4}/; // 0000 - 9999
|
|
var match6 = /[+-]?\d{6}/; // -999999 - 999999
|
|
var match1to2 = /\d\d?/; // 0 - 99
|
|
var match3to4 = /\d\d\d\d?/; // 999 - 9999
|
|
var match5to6 = /\d\d\d\d\d\d?/; // 99999 - 999999
|
|
var match1to3 = /\d{1,3}/; // 0 - 999
|
|
var match1to4 = /\d{1,4}/; // 0 - 9999
|
|
var match1to6 = /[+-]?\d{1,6}/; // -999999 - 999999
|
|
|
|
var matchUnsigned = /\d+/; // 0 - inf
|
|
var matchSigned = /[+-]?\d+/; // -inf - inf
|
|
|
|
var matchOffset = /Z|[+-]\d\d:?\d\d/gi; // +00:00 -00:00 +0000 -0000 or Z
|
|
var matchShortOffset = /Z|[+-]\d\d(?::?\d\d)?/gi; // +00 -00 +00:00 -00:00 +0000 -0000 or Z
|
|
|
|
var matchTimestamp = /[+-]?\d+(\.\d{1,3})?/; // 123456789 123456789.123
|
|
|
|
// any word (or two) characters or numbers including two/three word month in arabic.
|
|
// includes scottish gaelic two word and hyphenated months
|
|
var matchWord = /[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i;
|
|
|
|
var regexes = {};
|
|
|
|
function addRegexToken (token, regex, strictRegex) {
|
|
regexes[token] = isFunction(regex) ? regex : function (isStrict, localeData) {
|
|
return (isStrict && strictRegex) ? strictRegex : regex;
|
|
};
|
|
}
|
|
|
|
function getParseRegexForToken (token, config) {
|
|
if (!hasOwnProp(regexes, token)) {
|
|
return new RegExp(unescapeFormat(token));
|
|
}
|
|
|
|
return regexes[token](config._strict, config._locale);
|
|
}
|
|
|
|
// Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
|
|
function unescapeFormat(s) {
|
|
return regexEscape(s.replace('\\', '').replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) {
|
|
return p1 || p2 || p3 || p4;
|
|
}));
|
|
}
|
|
|
|
function regexEscape(s) {
|
|
return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
}
|
|
|
|
var tokens = {};
|
|
|
|
function addParseToken (token, callback) {
|
|
var i, func = callback;
|
|
if (typeof token === 'string') {
|
|
token = [token];
|
|
}
|
|
if (isNumber(callback)) {
|
|
func = function (input, array) {
|
|
array[callback] = toInt(input);
|
|
};
|
|
}
|
|
for (i = 0; i < token.length; i++) {
|
|
tokens[token[i]] = func;
|
|
}
|
|
}
|
|
|
|
function addWeekParseToken (token, callback) {
|
|
addParseToken(token, function (input, array, config, token) {
|
|
config._w = config._w || {};
|
|
callback(input, config._w, config, token);
|
|
});
|
|
}
|
|
|
|
function addTimeToArrayFromToken(token, input, config) {
|
|
if (input != null && hasOwnProp(tokens, token)) {
|
|
tokens[token](input, config._a, config, token);
|
|
}
|
|
}
|
|
|
|
var YEAR = 0;
|
|
var MONTH = 1;
|
|
var DATE = 2;
|
|
var HOUR = 3;
|
|
var MINUTE = 4;
|
|
var SECOND = 5;
|
|
var MILLISECOND = 6;
|
|
var WEEK = 7;
|
|
var WEEKDAY = 8;
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken('Y', 0, 0, function () {
|
|
var y = this.year();
|
|
return y <= 9999 ? '' + y : '+' + y;
|
|
});
|
|
|
|
addFormatToken(0, ['YY', 2], 0, function () {
|
|
return this.year() % 100;
|
|
});
|
|
|
|
addFormatToken(0, ['YYYY', 4], 0, 'year');
|
|
addFormatToken(0, ['YYYYY', 5], 0, 'year');
|
|
addFormatToken(0, ['YYYYYY', 6, true], 0, 'year');
|
|
|
|
// ALIASES
|
|
|
|
addUnitAlias('year', 'y');
|
|
|
|
// PRIORITIES
|
|
|
|
addUnitPriority('year', 1);
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('Y', matchSigned);
|
|
addRegexToken('YY', match1to2, match2);
|
|
addRegexToken('YYYY', match1to4, match4);
|
|
addRegexToken('YYYYY', match1to6, match6);
|
|
addRegexToken('YYYYYY', match1to6, match6);
|
|
|
|
addParseToken(['YYYYY', 'YYYYYY'], YEAR);
|
|
addParseToken('YYYY', function (input, array) {
|
|
array[YEAR] = input.length === 2 ? hooks.parseTwoDigitYear(input) : toInt(input);
|
|
});
|
|
addParseToken('YY', function (input, array) {
|
|
array[YEAR] = hooks.parseTwoDigitYear(input);
|
|
});
|
|
addParseToken('Y', function (input, array) {
|
|
array[YEAR] = parseInt(input, 10);
|
|
});
|
|
|
|
// HELPERS
|
|
|
|
function daysInYear(year) {
|
|
return isLeapYear(year) ? 366 : 365;
|
|
}
|
|
|
|
function isLeapYear(year) {
|
|
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
|
|
}
|
|
|
|
// HOOKS
|
|
|
|
hooks.parseTwoDigitYear = function (input) {
|
|
return toInt(input) + (toInt(input) > 68 ? 1900 : 2000);
|
|
};
|
|
|
|
// MOMENTS
|
|
|
|
var getSetYear = makeGetSet('FullYear', true);
|
|
|
|
function getIsLeapYear () {
|
|
return isLeapYear(this.year());
|
|
}
|
|
|
|
function makeGetSet (unit, keepTime) {
|
|
return function (value) {
|
|
if (value != null) {
|
|
set$1(this, unit, value);
|
|
hooks.updateOffset(this, keepTime);
|
|
return this;
|
|
} else {
|
|
return get(this, unit);
|
|
}
|
|
};
|
|
}
|
|
|
|
function get (mom, unit) {
|
|
return mom.isValid() ?
|
|
mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]() : NaN;
|
|
}
|
|
|
|
function set$1 (mom, unit, value) {
|
|
if (mom.isValid() && !isNaN(value)) {
|
|
if (unit === 'FullYear' && isLeapYear(mom.year()) && mom.month() === 1 && mom.date() === 29) {
|
|
mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value, mom.month(), daysInMonth(value, mom.month()));
|
|
}
|
|
else {
|
|
mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value);
|
|
}
|
|
}
|
|
}
|
|
|
|
// MOMENTS
|
|
|
|
function stringGet (units) {
|
|
units = normalizeUnits(units);
|
|
if (isFunction(this[units])) {
|
|
return this[units]();
|
|
}
|
|
return this;
|
|
}
|
|
|
|
|
|
function stringSet (units, value) {
|
|
if (typeof units === 'object') {
|
|
units = normalizeObjectUnits(units);
|
|
var prioritized = getPrioritizedUnits(units);
|
|
for (var i = 0; i < prioritized.length; i++) {
|
|
this[prioritized[i].unit](units[prioritized[i].unit]);
|
|
}
|
|
} else {
|
|
units = normalizeUnits(units);
|
|
if (isFunction(this[units])) {
|
|
return this[units](value);
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
|
|
function mod(n, x) {
|
|
return ((n % x) + x) % x;
|
|
}
|
|
|
|
var indexOf;
|
|
|
|
if (Array.prototype.indexOf) {
|
|
indexOf = Array.prototype.indexOf;
|
|
} else {
|
|
indexOf = function (o) {
|
|
// I know
|
|
var i;
|
|
for (i = 0; i < this.length; ++i) {
|
|
if (this[i] === o) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
};
|
|
}
|
|
|
|
function daysInMonth(year, month) {
|
|
if (isNaN(year) || isNaN(month)) {
|
|
return NaN;
|
|
}
|
|
var modMonth = mod(month, 12);
|
|
year += (month - modMonth) / 12;
|
|
return modMonth === 1 ? (isLeapYear(year) ? 29 : 28) : (31 - modMonth % 7 % 2);
|
|
}
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken('M', ['MM', 2], 'Mo', function () {
|
|
return this.month() + 1;
|
|
});
|
|
|
|
addFormatToken('MMM', 0, 0, function (format) {
|
|
return this.localeData().monthsShort(this, format);
|
|
});
|
|
|
|
addFormatToken('MMMM', 0, 0, function (format) {
|
|
return this.localeData().months(this, format);
|
|
});
|
|
|
|
// ALIASES
|
|
|
|
addUnitAlias('month', 'M');
|
|
|
|
// PRIORITY
|
|
|
|
addUnitPriority('month', 8);
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('M', match1to2);
|
|
addRegexToken('MM', match1to2, match2);
|
|
addRegexToken('MMM', function (isStrict, locale) {
|
|
return locale.monthsShortRegex(isStrict);
|
|
});
|
|
addRegexToken('MMMM', function (isStrict, locale) {
|
|
return locale.monthsRegex(isStrict);
|
|
});
|
|
|
|
addParseToken(['M', 'MM'], function (input, array) {
|
|
array[MONTH] = toInt(input) - 1;
|
|
});
|
|
|
|
addParseToken(['MMM', 'MMMM'], function (input, array, config, token) {
|
|
var month = config._locale.monthsParse(input, token, config._strict);
|
|
// if we didn't find a month name, mark the date as invalid.
|
|
if (month != null) {
|
|
array[MONTH] = month;
|
|
} else {
|
|
getParsingFlags(config).invalidMonth = input;
|
|
}
|
|
});
|
|
|
|
// LOCALES
|
|
|
|
var MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/;
|
|
var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_');
|
|
function localeMonths (m, format) {
|
|
if (!m) {
|
|
return isArray(this._months) ? this._months :
|
|
this._months['standalone'];
|
|
}
|
|
return isArray(this._months) ? this._months[m.month()] :
|
|
this._months[(this._months.isFormat || MONTHS_IN_FORMAT).test(format) ? 'format' : 'standalone'][m.month()];
|
|
}
|
|
|
|
var defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_');
|
|
function localeMonthsShort (m, format) {
|
|
if (!m) {
|
|
return isArray(this._monthsShort) ? this._monthsShort :
|
|
this._monthsShort['standalone'];
|
|
}
|
|
return isArray(this._monthsShort) ? this._monthsShort[m.month()] :
|
|
this._monthsShort[MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'][m.month()];
|
|
}
|
|
|
|
function handleStrictParse(monthName, format, strict) {
|
|
var i, ii, mom, llc = monthName.toLocaleLowerCase();
|
|
if (!this._monthsParse) {
|
|
// this is not used
|
|
this._monthsParse = [];
|
|
this._longMonthsParse = [];
|
|
this._shortMonthsParse = [];
|
|
for (i = 0; i < 12; ++i) {
|
|
mom = createUTC([2000, i]);
|
|
this._shortMonthsParse[i] = this.monthsShort(mom, '').toLocaleLowerCase();
|
|
this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase();
|
|
}
|
|
}
|
|
|
|
if (strict) {
|
|
if (format === 'MMM') {
|
|
ii = indexOf.call(this._shortMonthsParse, llc);
|
|
return ii !== -1 ? ii : null;
|
|
} else {
|
|
ii = indexOf.call(this._longMonthsParse, llc);
|
|
return ii !== -1 ? ii : null;
|
|
}
|
|
} else {
|
|
if (format === 'MMM') {
|
|
ii = indexOf.call(this._shortMonthsParse, llc);
|
|
if (ii !== -1) {
|
|
return ii;
|
|
}
|
|
ii = indexOf.call(this._longMonthsParse, llc);
|
|
return ii !== -1 ? ii : null;
|
|
} else {
|
|
ii = indexOf.call(this._longMonthsParse, llc);
|
|
if (ii !== -1) {
|
|
return ii;
|
|
}
|
|
ii = indexOf.call(this._shortMonthsParse, llc);
|
|
return ii !== -1 ? ii : null;
|
|
}
|
|
}
|
|
}
|
|
|
|
function localeMonthsParse (monthName, format, strict) {
|
|
var i, mom, regex;
|
|
|
|
if (this._monthsParseExact) {
|
|
return handleStrictParse.call(this, monthName, format, strict);
|
|
}
|
|
|
|
if (!this._monthsParse) {
|
|
this._monthsParse = [];
|
|
this._longMonthsParse = [];
|
|
this._shortMonthsParse = [];
|
|
}
|
|
|
|
// TODO: add sorting
|
|
// Sorting makes sure if one month (or abbr) is a prefix of another
|
|
// see sorting in computeMonthsParse
|
|
for (i = 0; i < 12; i++) {
|
|
// make the regex if we don't have it already
|
|
mom = createUTC([2000, i]);
|
|
if (strict && !this._longMonthsParse[i]) {
|
|
this._longMonthsParse[i] = new RegExp('^' + this.months(mom, '').replace('.', '') + '$', 'i');
|
|
this._shortMonthsParse[i] = new RegExp('^' + this.monthsShort(mom, '').replace('.', '') + '$', 'i');
|
|
}
|
|
if (!strict && !this._monthsParse[i]) {
|
|
regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, '');
|
|
this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i');
|
|
}
|
|
// test the regex
|
|
if (strict && format === 'MMMM' && this._longMonthsParse[i].test(monthName)) {
|
|
return i;
|
|
} else if (strict && format === 'MMM' && this._shortMonthsParse[i].test(monthName)) {
|
|
return i;
|
|
} else if (!strict && this._monthsParse[i].test(monthName)) {
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
|
|
// MOMENTS
|
|
|
|
function setMonth (mom, value) {
|
|
var dayOfMonth;
|
|
|
|
if (!mom.isValid()) {
|
|
// No op
|
|
return mom;
|
|
}
|
|
|
|
if (typeof value === 'string') {
|
|
if (/^\d+$/.test(value)) {
|
|
value = toInt(value);
|
|
} else {
|
|
value = mom.localeData().monthsParse(value);
|
|
// TODO: Another silent failure?
|
|
if (!isNumber(value)) {
|
|
return mom;
|
|
}
|
|
}
|
|
}
|
|
|
|
dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value));
|
|
mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth);
|
|
return mom;
|
|
}
|
|
|
|
function getSetMonth (value) {
|
|
if (value != null) {
|
|
setMonth(this, value);
|
|
hooks.updateOffset(this, true);
|
|
return this;
|
|
} else {
|
|
return get(this, 'Month');
|
|
}
|
|
}
|
|
|
|
function getDaysInMonth () {
|
|
return daysInMonth(this.year(), this.month());
|
|
}
|
|
|
|
var defaultMonthsShortRegex = matchWord;
|
|
function monthsShortRegex (isStrict) {
|
|
if (this._monthsParseExact) {
|
|
if (!hasOwnProp(this, '_monthsRegex')) {
|
|
computeMonthsParse.call(this);
|
|
}
|
|
if (isStrict) {
|
|
return this._monthsShortStrictRegex;
|
|
} else {
|
|
return this._monthsShortRegex;
|
|
}
|
|
} else {
|
|
if (!hasOwnProp(this, '_monthsShortRegex')) {
|
|
this._monthsShortRegex = defaultMonthsShortRegex;
|
|
}
|
|
return this._monthsShortStrictRegex && isStrict ?
|
|
this._monthsShortStrictRegex : this._monthsShortRegex;
|
|
}
|
|
}
|
|
|
|
var defaultMonthsRegex = matchWord;
|
|
function monthsRegex (isStrict) {
|
|
if (this._monthsParseExact) {
|
|
if (!hasOwnProp(this, '_monthsRegex')) {
|
|
computeMonthsParse.call(this);
|
|
}
|
|
if (isStrict) {
|
|
return this._monthsStrictRegex;
|
|
} else {
|
|
return this._monthsRegex;
|
|
}
|
|
} else {
|
|
if (!hasOwnProp(this, '_monthsRegex')) {
|
|
this._monthsRegex = defaultMonthsRegex;
|
|
}
|
|
return this._monthsStrictRegex && isStrict ?
|
|
this._monthsStrictRegex : this._monthsRegex;
|
|
}
|
|
}
|
|
|
|
function computeMonthsParse () {
|
|
function cmpLenRev(a, b) {
|
|
return b.length - a.length;
|
|
}
|
|
|
|
var shortPieces = [], longPieces = [], mixedPieces = [],
|
|
i, mom;
|
|
for (i = 0; i < 12; i++) {
|
|
// make the regex if we don't have it already
|
|
mom = createUTC([2000, i]);
|
|
shortPieces.push(this.monthsShort(mom, ''));
|
|
longPieces.push(this.months(mom, ''));
|
|
mixedPieces.push(this.months(mom, ''));
|
|
mixedPieces.push(this.monthsShort(mom, ''));
|
|
}
|
|
// Sorting makes sure if one month (or abbr) is a prefix of another it
|
|
// will match the longer piece.
|
|
shortPieces.sort(cmpLenRev);
|
|
longPieces.sort(cmpLenRev);
|
|
mixedPieces.sort(cmpLenRev);
|
|
for (i = 0; i < 12; i++) {
|
|
shortPieces[i] = regexEscape(shortPieces[i]);
|
|
longPieces[i] = regexEscape(longPieces[i]);
|
|
}
|
|
for (i = 0; i < 24; i++) {
|
|
mixedPieces[i] = regexEscape(mixedPieces[i]);
|
|
}
|
|
|
|
this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
|
|
this._monthsShortRegex = this._monthsRegex;
|
|
this._monthsStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i');
|
|
this._monthsShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i');
|
|
}
|
|
|
|
function createDate (y, m, d, h, M, s, ms) {
|
|
// can't just apply() to create a date:
|
|
// https://stackoverflow.com/q/181348
|
|
var date;
|
|
// the date constructor remaps years 0-99 to 1900-1999
|
|
if (y < 100 && y >= 0) {
|
|
// preserve leap years using a full 400 year cycle, then reset
|
|
date = new Date(y + 400, m, d, h, M, s, ms);
|
|
if (isFinite(date.getFullYear())) {
|
|
date.setFullYear(y);
|
|
}
|
|
} else {
|
|
date = new Date(y, m, d, h, M, s, ms);
|
|
}
|
|
|
|
return date;
|
|
}
|
|
|
|
function createUTCDate (y) {
|
|
var date;
|
|
// the Date.UTC function remaps years 0-99 to 1900-1999
|
|
if (y < 100 && y >= 0) {
|
|
var args = Array.prototype.slice.call(arguments);
|
|
// preserve leap years using a full 400 year cycle, then reset
|
|
args[0] = y + 400;
|
|
date = new Date(Date.UTC.apply(null, args));
|
|
if (isFinite(date.getUTCFullYear())) {
|
|
date.setUTCFullYear(y);
|
|
}
|
|
} else {
|
|
date = new Date(Date.UTC.apply(null, arguments));
|
|
}
|
|
|
|
return date;
|
|
}
|
|
|
|
// start-of-first-week - start-of-year
|
|
function firstWeekOffset(year, dow, doy) {
|
|
var // first-week day -- which january is always in the first week (4 for iso, 1 for other)
|
|
fwd = 7 + dow - doy,
|
|
// first-week day local weekday -- which local weekday is fwd
|
|
fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7;
|
|
|
|
return -fwdlw + fwd - 1;
|
|
}
|
|
|
|
// https://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday
|
|
function dayOfYearFromWeeks(year, week, weekday, dow, doy) {
|
|
var localWeekday = (7 + weekday - dow) % 7,
|
|
weekOffset = firstWeekOffset(year, dow, doy),
|
|
dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset,
|
|
resYear, resDayOfYear;
|
|
|
|
if (dayOfYear <= 0) {
|
|
resYear = year - 1;
|
|
resDayOfYear = daysInYear(resYear) + dayOfYear;
|
|
} else if (dayOfYear > daysInYear(year)) {
|
|
resYear = year + 1;
|
|
resDayOfYear = dayOfYear - daysInYear(year);
|
|
} else {
|
|
resYear = year;
|
|
resDayOfYear = dayOfYear;
|
|
}
|
|
|
|
return {
|
|
year: resYear,
|
|
dayOfYear: resDayOfYear
|
|
};
|
|
}
|
|
|
|
function weekOfYear(mom, dow, doy) {
|
|
var weekOffset = firstWeekOffset(mom.year(), dow, doy),
|
|
week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1,
|
|
resWeek, resYear;
|
|
|
|
if (week < 1) {
|
|
resYear = mom.year() - 1;
|
|
resWeek = week + weeksInYear(resYear, dow, doy);
|
|
} else if (week > weeksInYear(mom.year(), dow, doy)) {
|
|
resWeek = week - weeksInYear(mom.year(), dow, doy);
|
|
resYear = mom.year() + 1;
|
|
} else {
|
|
resYear = mom.year();
|
|
resWeek = week;
|
|
}
|
|
|
|
return {
|
|
week: resWeek,
|
|
year: resYear
|
|
};
|
|
}
|
|
|
|
function weeksInYear(year, dow, doy) {
|
|
var weekOffset = firstWeekOffset(year, dow, doy),
|
|
weekOffsetNext = firstWeekOffset(year + 1, dow, doy);
|
|
return (daysInYear(year) - weekOffset + weekOffsetNext) / 7;
|
|
}
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken('w', ['ww', 2], 'wo', 'week');
|
|
addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek');
|
|
|
|
// ALIASES
|
|
|
|
addUnitAlias('week', 'w');
|
|
addUnitAlias('isoWeek', 'W');
|
|
|
|
// PRIORITIES
|
|
|
|
addUnitPriority('week', 5);
|
|
addUnitPriority('isoWeek', 5);
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('w', match1to2);
|
|
addRegexToken('ww', match1to2, match2);
|
|
addRegexToken('W', match1to2);
|
|
addRegexToken('WW', match1to2, match2);
|
|
|
|
addWeekParseToken(['w', 'ww', 'W', 'WW'], function (input, week, config, token) {
|
|
week[token.substr(0, 1)] = toInt(input);
|
|
});
|
|
|
|
// HELPERS
|
|
|
|
// LOCALES
|
|
|
|
function localeWeek (mom) {
|
|
return weekOfYear(mom, this._week.dow, this._week.doy).week;
|
|
}
|
|
|
|
var defaultLocaleWeek = {
|
|
dow : 0, // Sunday is the first day of the week.
|
|
doy : 6 // The week that contains Jan 6th is the first week of the year.
|
|
};
|
|
|
|
function localeFirstDayOfWeek () {
|
|
return this._week.dow;
|
|
}
|
|
|
|
function localeFirstDayOfYear () {
|
|
return this._week.doy;
|
|
}
|
|
|
|
// MOMENTS
|
|
|
|
function getSetWeek (input) {
|
|
var week = this.localeData().week(this);
|
|
return input == null ? week : this.add((input - week) * 7, 'd');
|
|
}
|
|
|
|
function getSetISOWeek (input) {
|
|
var week = weekOfYear(this, 1, 4).week;
|
|
return input == null ? week : this.add((input - week) * 7, 'd');
|
|
}
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken('d', 0, 'do', 'day');
|
|
|
|
addFormatToken('dd', 0, 0, function (format) {
|
|
return this.localeData().weekdaysMin(this, format);
|
|
});
|
|
|
|
addFormatToken('ddd', 0, 0, function (format) {
|
|
return this.localeData().weekdaysShort(this, format);
|
|
});
|
|
|
|
addFormatToken('dddd', 0, 0, function (format) {
|
|
return this.localeData().weekdays(this, format);
|
|
});
|
|
|
|
addFormatToken('e', 0, 0, 'weekday');
|
|
addFormatToken('E', 0, 0, 'isoWeekday');
|
|
|
|
// ALIASES
|
|
|
|
addUnitAlias('day', 'd');
|
|
addUnitAlias('weekday', 'e');
|
|
addUnitAlias('isoWeekday', 'E');
|
|
|
|
// PRIORITY
|
|
addUnitPriority('day', 11);
|
|
addUnitPriority('weekday', 11);
|
|
addUnitPriority('isoWeekday', 11);
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('d', match1to2);
|
|
addRegexToken('e', match1to2);
|
|
addRegexToken('E', match1to2);
|
|
addRegexToken('dd', function (isStrict, locale) {
|
|
return locale.weekdaysMinRegex(isStrict);
|
|
});
|
|
addRegexToken('ddd', function (isStrict, locale) {
|
|
return locale.weekdaysShortRegex(isStrict);
|
|
});
|
|
addRegexToken('dddd', function (isStrict, locale) {
|
|
return locale.weekdaysRegex(isStrict);
|
|
});
|
|
|
|
addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) {
|
|
var weekday = config._locale.weekdaysParse(input, token, config._strict);
|
|
// if we didn't get a weekday name, mark the date as invalid
|
|
if (weekday != null) {
|
|
week.d = weekday;
|
|
} else {
|
|
getParsingFlags(config).invalidWeekday = input;
|
|
}
|
|
});
|
|
|
|
addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) {
|
|
week[token] = toInt(input);
|
|
});
|
|
|
|
// HELPERS
|
|
|
|
function parseWeekday(input, locale) {
|
|
if (typeof input !== 'string') {
|
|
return input;
|
|
}
|
|
|
|
if (!isNaN(input)) {
|
|
return parseInt(input, 10);
|
|
}
|
|
|
|
input = locale.weekdaysParse(input);
|
|
if (typeof input === 'number') {
|
|
return input;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function parseIsoWeekday(input, locale) {
|
|
if (typeof input === 'string') {
|
|
return locale.weekdaysParse(input) % 7 || 7;
|
|
}
|
|
return isNaN(input) ? null : input;
|
|
}
|
|
|
|
// LOCALES
|
|
function shiftWeekdays (ws, n) {
|
|
return ws.slice(n, 7).concat(ws.slice(0, n));
|
|
}
|
|
|
|
var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_');
|
|
function localeWeekdays (m, format) {
|
|
var weekdays = isArray(this._weekdays) ? this._weekdays :
|
|
this._weekdays[(m && m !== true && this._weekdays.isFormat.test(format)) ? 'format' : 'standalone'];
|
|
return (m === true) ? shiftWeekdays(weekdays, this._week.dow)
|
|
: (m) ? weekdays[m.day()] : weekdays;
|
|
}
|
|
|
|
var defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_');
|
|
function localeWeekdaysShort (m) {
|
|
return (m === true) ? shiftWeekdays(this._weekdaysShort, this._week.dow)
|
|
: (m) ? this._weekdaysShort[m.day()] : this._weekdaysShort;
|
|
}
|
|
|
|
var defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_');
|
|
function localeWeekdaysMin (m) {
|
|
return (m === true) ? shiftWeekdays(this._weekdaysMin, this._week.dow)
|
|
: (m) ? this._weekdaysMin[m.day()] : this._weekdaysMin;
|
|
}
|
|
|
|
function handleStrictParse$1(weekdayName, format, strict) {
|
|
var i, ii, mom, llc = weekdayName.toLocaleLowerCase();
|
|
if (!this._weekdaysParse) {
|
|
this._weekdaysParse = [];
|
|
this._shortWeekdaysParse = [];
|
|
this._minWeekdaysParse = [];
|
|
|
|
for (i = 0; i < 7; ++i) {
|
|
mom = createUTC([2000, 1]).day(i);
|
|
this._minWeekdaysParse[i] = this.weekdaysMin(mom, '').toLocaleLowerCase();
|
|
this._shortWeekdaysParse[i] = this.weekdaysShort(mom, '').toLocaleLowerCase();
|
|
this._weekdaysParse[i] = this.weekdays(mom, '').toLocaleLowerCase();
|
|
}
|
|
}
|
|
|
|
if (strict) {
|
|
if (format === 'dddd') {
|
|
ii = indexOf.call(this._weekdaysParse, llc);
|
|
return ii !== -1 ? ii : null;
|
|
} else if (format === 'ddd') {
|
|
ii = indexOf.call(this._shortWeekdaysParse, llc);
|
|
return ii !== -1 ? ii : null;
|
|
} else {
|
|
ii = indexOf.call(this._minWeekdaysParse, llc);
|
|
return ii !== -1 ? ii : null;
|
|
}
|
|
} else {
|
|
if (format === 'dddd') {
|
|
ii = indexOf.call(this._weekdaysParse, llc);
|
|
if (ii !== -1) {
|
|
return ii;
|
|
}
|
|
ii = indexOf.call(this._shortWeekdaysParse, llc);
|
|
if (ii !== -1) {
|
|
return ii;
|
|
}
|
|
ii = indexOf.call(this._minWeekdaysParse, llc);
|
|
return ii !== -1 ? ii : null;
|
|
} else if (format === 'ddd') {
|
|
ii = indexOf.call(this._shortWeekdaysParse, llc);
|
|
if (ii !== -1) {
|
|
return ii;
|
|
}
|
|
ii = indexOf.call(this._weekdaysParse, llc);
|
|
if (ii !== -1) {
|
|
return ii;
|
|
}
|
|
ii = indexOf.call(this._minWeekdaysParse, llc);
|
|
return ii !== -1 ? ii : null;
|
|
} else {
|
|
ii = indexOf.call(this._minWeekdaysParse, llc);
|
|
if (ii !== -1) {
|
|
return ii;
|
|
}
|
|
ii = indexOf.call(this._weekdaysParse, llc);
|
|
if (ii !== -1) {
|
|
return ii;
|
|
}
|
|
ii = indexOf.call(this._shortWeekdaysParse, llc);
|
|
return ii !== -1 ? ii : null;
|
|
}
|
|
}
|
|
}
|
|
|
|
function localeWeekdaysParse (weekdayName, format, strict) {
|
|
var i, mom, regex;
|
|
|
|
if (this._weekdaysParseExact) {
|
|
return handleStrictParse$1.call(this, weekdayName, format, strict);
|
|
}
|
|
|
|
if (!this._weekdaysParse) {
|
|
this._weekdaysParse = [];
|
|
this._minWeekdaysParse = [];
|
|
this._shortWeekdaysParse = [];
|
|
this._fullWeekdaysParse = [];
|
|
}
|
|
|
|
for (i = 0; i < 7; i++) {
|
|
// make the regex if we don't have it already
|
|
|
|
mom = createUTC([2000, 1]).day(i);
|
|
if (strict && !this._fullWeekdaysParse[i]) {
|
|
this._fullWeekdaysParse[i] = new RegExp('^' + this.weekdays(mom, '').replace('.', '\\.?') + '$', 'i');
|
|
this._shortWeekdaysParse[i] = new RegExp('^' + this.weekdaysShort(mom, '').replace('.', '\\.?') + '$', 'i');
|
|
this._minWeekdaysParse[i] = new RegExp('^' + this.weekdaysMin(mom, '').replace('.', '\\.?') + '$', 'i');
|
|
}
|
|
if (!this._weekdaysParse[i]) {
|
|
regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, '');
|
|
this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i');
|
|
}
|
|
// test the regex
|
|
if (strict && format === 'dddd' && this._fullWeekdaysParse[i].test(weekdayName)) {
|
|
return i;
|
|
} else if (strict && format === 'ddd' && this._shortWeekdaysParse[i].test(weekdayName)) {
|
|
return i;
|
|
} else if (strict && format === 'dd' && this._minWeekdaysParse[i].test(weekdayName)) {
|
|
return i;
|
|
} else if (!strict && this._weekdaysParse[i].test(weekdayName)) {
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
|
|
// MOMENTS
|
|
|
|
function getSetDayOfWeek (input) {
|
|
if (!this.isValid()) {
|
|
return input != null ? this : NaN;
|
|
}
|
|
var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();
|
|
if (input != null) {
|
|
input = parseWeekday(input, this.localeData());
|
|
return this.add(input - day, 'd');
|
|
} else {
|
|
return day;
|
|
}
|
|
}
|
|
|
|
function getSetLocaleDayOfWeek (input) {
|
|
if (!this.isValid()) {
|
|
return input != null ? this : NaN;
|
|
}
|
|
var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7;
|
|
return input == null ? weekday : this.add(input - weekday, 'd');
|
|
}
|
|
|
|
function getSetISODayOfWeek (input) {
|
|
if (!this.isValid()) {
|
|
return input != null ? this : NaN;
|
|
}
|
|
|
|
// behaves the same as moment#day except
|
|
// as a getter, returns 7 instead of 0 (1-7 range instead of 0-6)
|
|
// as a setter, sunday should belong to the previous week.
|
|
|
|
if (input != null) {
|
|
var weekday = parseIsoWeekday(input, this.localeData());
|
|
return this.day(this.day() % 7 ? weekday : weekday - 7);
|
|
} else {
|
|
return this.day() || 7;
|
|
}
|
|
}
|
|
|
|
var defaultWeekdaysRegex = matchWord;
|
|
function weekdaysRegex (isStrict) {
|
|
if (this._weekdaysParseExact) {
|
|
if (!hasOwnProp(this, '_weekdaysRegex')) {
|
|
computeWeekdaysParse.call(this);
|
|
}
|
|
if (isStrict) {
|
|
return this._weekdaysStrictRegex;
|
|
} else {
|
|
return this._weekdaysRegex;
|
|
}
|
|
} else {
|
|
if (!hasOwnProp(this, '_weekdaysRegex')) {
|
|
this._weekdaysRegex = defaultWeekdaysRegex;
|
|
}
|
|
return this._weekdaysStrictRegex && isStrict ?
|
|
this._weekdaysStrictRegex : this._weekdaysRegex;
|
|
}
|
|
}
|
|
|
|
var defaultWeekdaysShortRegex = matchWord;
|
|
function weekdaysShortRegex (isStrict) {
|
|
if (this._weekdaysParseExact) {
|
|
if (!hasOwnProp(this, '_weekdaysRegex')) {
|
|
computeWeekdaysParse.call(this);
|
|
}
|
|
if (isStrict) {
|
|
return this._weekdaysShortStrictRegex;
|
|
} else {
|
|
return this._weekdaysShortRegex;
|
|
}
|
|
} else {
|
|
if (!hasOwnProp(this, '_weekdaysShortRegex')) {
|
|
this._weekdaysShortRegex = defaultWeekdaysShortRegex;
|
|
}
|
|
return this._weekdaysShortStrictRegex && isStrict ?
|
|
this._weekdaysShortStrictRegex : this._weekdaysShortRegex;
|
|
}
|
|
}
|
|
|
|
var defaultWeekdaysMinRegex = matchWord;
|
|
function weekdaysMinRegex (isStrict) {
|
|
if (this._weekdaysParseExact) {
|
|
if (!hasOwnProp(this, '_weekdaysRegex')) {
|
|
computeWeekdaysParse.call(this);
|
|
}
|
|
if (isStrict) {
|
|
return this._weekdaysMinStrictRegex;
|
|
} else {
|
|
return this._weekdaysMinRegex;
|
|
}
|
|
} else {
|
|
if (!hasOwnProp(this, '_weekdaysMinRegex')) {
|
|
this._weekdaysMinRegex = defaultWeekdaysMinRegex;
|
|
}
|
|
return this._weekdaysMinStrictRegex && isStrict ?
|
|
this._weekdaysMinStrictRegex : this._weekdaysMinRegex;
|
|
}
|
|
}
|
|
|
|
|
|
function computeWeekdaysParse () {
|
|
function cmpLenRev(a, b) {
|
|
return b.length - a.length;
|
|
}
|
|
|
|
var minPieces = [], shortPieces = [], longPieces = [], mixedPieces = [],
|
|
i, mom, minp, shortp, longp;
|
|
for (i = 0; i < 7; i++) {
|
|
// make the regex if we don't have it already
|
|
mom = createUTC([2000, 1]).day(i);
|
|
minp = this.weekdaysMin(mom, '');
|
|
shortp = this.weekdaysShort(mom, '');
|
|
longp = this.weekdays(mom, '');
|
|
minPieces.push(minp);
|
|
shortPieces.push(shortp);
|
|
longPieces.push(longp);
|
|
mixedPieces.push(minp);
|
|
mixedPieces.push(shortp);
|
|
mixedPieces.push(longp);
|
|
}
|
|
// Sorting makes sure if one weekday (or abbr) is a prefix of another it
|
|
// will match the longer piece.
|
|
minPieces.sort(cmpLenRev);
|
|
shortPieces.sort(cmpLenRev);
|
|
longPieces.sort(cmpLenRev);
|
|
mixedPieces.sort(cmpLenRev);
|
|
for (i = 0; i < 7; i++) {
|
|
shortPieces[i] = regexEscape(shortPieces[i]);
|
|
longPieces[i] = regexEscape(longPieces[i]);
|
|
mixedPieces[i] = regexEscape(mixedPieces[i]);
|
|
}
|
|
|
|
this._weekdaysRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
|
|
this._weekdaysShortRegex = this._weekdaysRegex;
|
|
this._weekdaysMinRegex = this._weekdaysRegex;
|
|
|
|
this._weekdaysStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i');
|
|
this._weekdaysShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i');
|
|
this._weekdaysMinStrictRegex = new RegExp('^(' + minPieces.join('|') + ')', 'i');
|
|
}
|
|
|
|
// FORMATTING
|
|
|
|
function hFormat() {
|
|
return this.hours() % 12 || 12;
|
|
}
|
|
|
|
function kFormat() {
|
|
return this.hours() || 24;
|
|
}
|
|
|
|
addFormatToken('H', ['HH', 2], 0, 'hour');
|
|
addFormatToken('h', ['hh', 2], 0, hFormat);
|
|
addFormatToken('k', ['kk', 2], 0, kFormat);
|
|
|
|
addFormatToken('hmm', 0, 0, function () {
|
|
return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2);
|
|
});
|
|
|
|
addFormatToken('hmmss', 0, 0, function () {
|
|
return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2) +
|
|
zeroFill(this.seconds(), 2);
|
|
});
|
|
|
|
addFormatToken('Hmm', 0, 0, function () {
|
|
return '' + this.hours() + zeroFill(this.minutes(), 2);
|
|
});
|
|
|
|
addFormatToken('Hmmss', 0, 0, function () {
|
|
return '' + this.hours() + zeroFill(this.minutes(), 2) +
|
|
zeroFill(this.seconds(), 2);
|
|
});
|
|
|
|
function meridiem (token, lowercase) {
|
|
addFormatToken(token, 0, 0, function () {
|
|
return this.localeData().meridiem(this.hours(), this.minutes(), lowercase);
|
|
});
|
|
}
|
|
|
|
meridiem('a', true);
|
|
meridiem('A', false);
|
|
|
|
// ALIASES
|
|
|
|
addUnitAlias('hour', 'h');
|
|
|
|
// PRIORITY
|
|
addUnitPriority('hour', 13);
|
|
|
|
// PARSING
|
|
|
|
function matchMeridiem (isStrict, locale) {
|
|
return locale._meridiemParse;
|
|
}
|
|
|
|
addRegexToken('a', matchMeridiem);
|
|
addRegexToken('A', matchMeridiem);
|
|
addRegexToken('H', match1to2);
|
|
addRegexToken('h', match1to2);
|
|
addRegexToken('k', match1to2);
|
|
addRegexToken('HH', match1to2, match2);
|
|
addRegexToken('hh', match1to2, match2);
|
|
addRegexToken('kk', match1to2, match2);
|
|
|
|
addRegexToken('hmm', match3to4);
|
|
addRegexToken('hmmss', match5to6);
|
|
addRegexToken('Hmm', match3to4);
|
|
addRegexToken('Hmmss', match5to6);
|
|
|
|
addParseToken(['H', 'HH'], HOUR);
|
|
addParseToken(['k', 'kk'], function (input, array, config) {
|
|
var kInput = toInt(input);
|
|
array[HOUR] = kInput === 24 ? 0 : kInput;
|
|
});
|
|
addParseToken(['a', 'A'], function (input, array, config) {
|
|
config._isPm = config._locale.isPM(input);
|
|
config._meridiem = input;
|
|
});
|
|
addParseToken(['h', 'hh'], function (input, array, config) {
|
|
array[HOUR] = toInt(input);
|
|
getParsingFlags(config).bigHour = true;
|
|
});
|
|
addParseToken('hmm', function (input, array, config) {
|
|
var pos = input.length - 2;
|
|
array[HOUR] = toInt(input.substr(0, pos));
|
|
array[MINUTE] = toInt(input.substr(pos));
|
|
getParsingFlags(config).bigHour = true;
|
|
});
|
|
addParseToken('hmmss', function (input, array, config) {
|
|
var pos1 = input.length - 4;
|
|
var pos2 = input.length - 2;
|
|
array[HOUR] = toInt(input.substr(0, pos1));
|
|
array[MINUTE] = toInt(input.substr(pos1, 2));
|
|
array[SECOND] = toInt(input.substr(pos2));
|
|
getParsingFlags(config).bigHour = true;
|
|
});
|
|
addParseToken('Hmm', function (input, array, config) {
|
|
var pos = input.length - 2;
|
|
array[HOUR] = toInt(input.substr(0, pos));
|
|
array[MINUTE] = toInt(input.substr(pos));
|
|
});
|
|
addParseToken('Hmmss', function (input, array, config) {
|
|
var pos1 = input.length - 4;
|
|
var pos2 = input.length - 2;
|
|
array[HOUR] = toInt(input.substr(0, pos1));
|
|
array[MINUTE] = toInt(input.substr(pos1, 2));
|
|
array[SECOND] = toInt(input.substr(pos2));
|
|
});
|
|
|
|
// LOCALES
|
|
|
|
function localeIsPM (input) {
|
|
// IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays
|
|
// Using charAt should be more compatible.
|
|
return ((input + '').toLowerCase().charAt(0) === 'p');
|
|
}
|
|
|
|
var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i;
|
|
function localeMeridiem (hours, minutes, isLower) {
|
|
if (hours > 11) {
|
|
return isLower ? 'pm' : 'PM';
|
|
} else {
|
|
return isLower ? 'am' : 'AM';
|
|
}
|
|
}
|
|
|
|
|
|
// MOMENTS
|
|
|
|
// Setting the hour should keep the time, because the user explicitly
|
|
// specified which hour they want. So trying to maintain the same hour (in
|
|
// a new timezone) makes sense. Adding/subtracting hours does not follow
|
|
// this rule.
|
|
var getSetHour = makeGetSet('Hours', true);
|
|
|
|
var baseConfig = {
|
|
calendar: defaultCalendar,
|
|
longDateFormat: defaultLongDateFormat,
|
|
invalidDate: defaultInvalidDate,
|
|
ordinal: defaultOrdinal,
|
|
dayOfMonthOrdinalParse: defaultDayOfMonthOrdinalParse,
|
|
relativeTime: defaultRelativeTime,
|
|
|
|
months: defaultLocaleMonths,
|
|
monthsShort: defaultLocaleMonthsShort,
|
|
|
|
week: defaultLocaleWeek,
|
|
|
|
weekdays: defaultLocaleWeekdays,
|
|
weekdaysMin: defaultLocaleWeekdaysMin,
|
|
weekdaysShort: defaultLocaleWeekdaysShort,
|
|
|
|
meridiemParse: defaultLocaleMeridiemParse
|
|
};
|
|
|
|
// internal storage for locale config files
|
|
var locales = {};
|
|
var localeFamilies = {};
|
|
var globalLocale;
|
|
|
|
function normalizeLocale(key) {
|
|
return key ? key.toLowerCase().replace('_', '-') : key;
|
|
}
|
|
|
|
// pick the locale from the array
|
|
// try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each
|
|
// substring from most specific to least, but move to the next array item if it's a more specific variant than the current root
|
|
function chooseLocale(names) {
|
|
var i = 0, j, next, locale, split;
|
|
|
|
while (i < names.length) {
|
|
split = normalizeLocale(names[i]).split('-');
|
|
j = split.length;
|
|
next = normalizeLocale(names[i + 1]);
|
|
next = next ? next.split('-') : null;
|
|
while (j > 0) {
|
|
locale = loadLocale(split.slice(0, j).join('-'));
|
|
if (locale) {
|
|
return locale;
|
|
}
|
|
if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) {
|
|
//the next array item is better than a shallower substring of this one
|
|
break;
|
|
}
|
|
j--;
|
|
}
|
|
i++;
|
|
}
|
|
return globalLocale;
|
|
}
|
|
|
|
function loadLocale(name) {
|
|
var oldLocale = null;
|
|
// TODO: Find a better way to register and load all the locales in Node
|
|
if (!locales[name] && ('object' !== 'undefined') &&
|
|
module && module.exports) {
|
|
try {
|
|
oldLocale = globalLocale._abbr;
|
|
var aliasedRequire = commonjsRequire;
|
|
aliasedRequire('./locale/' + name);
|
|
getSetGlobalLocale(oldLocale);
|
|
} catch (e) {}
|
|
}
|
|
return locales[name];
|
|
}
|
|
|
|
// This function will load locale and then set the global locale. If
|
|
// no arguments are passed in, it will simply return the current global
|
|
// locale key.
|
|
function getSetGlobalLocale (key, values) {
|
|
var data;
|
|
if (key) {
|
|
if (isUndefined(values)) {
|
|
data = getLocale(key);
|
|
}
|
|
else {
|
|
data = defineLocale(key, values);
|
|
}
|
|
|
|
if (data) {
|
|
// moment.duration._locale = moment._locale = data;
|
|
globalLocale = data;
|
|
}
|
|
else {
|
|
if ((typeof console !== 'undefined') && console.warn) {
|
|
//warn user if arguments are passed but the locale could not be set
|
|
console.warn('Locale ' + key + ' not found. Did you forget to load it?');
|
|
}
|
|
}
|
|
}
|
|
|
|
return globalLocale._abbr;
|
|
}
|
|
|
|
function defineLocale (name, config) {
|
|
if (config !== null) {
|
|
var locale, parentConfig = baseConfig;
|
|
config.abbr = name;
|
|
if (locales[name] != null) {
|
|
deprecateSimple('defineLocaleOverride',
|
|
'use moment.updateLocale(localeName, config) to change ' +
|
|
'an existing locale. moment.defineLocale(localeName, ' +
|
|
'config) should only be used for creating a new locale ' +
|
|
'See http://momentjs.com/guides/#/warnings/define-locale/ for more info.');
|
|
parentConfig = locales[name]._config;
|
|
} else if (config.parentLocale != null) {
|
|
if (locales[config.parentLocale] != null) {
|
|
parentConfig = locales[config.parentLocale]._config;
|
|
} else {
|
|
locale = loadLocale(config.parentLocale);
|
|
if (locale != null) {
|
|
parentConfig = locale._config;
|
|
} else {
|
|
if (!localeFamilies[config.parentLocale]) {
|
|
localeFamilies[config.parentLocale] = [];
|
|
}
|
|
localeFamilies[config.parentLocale].push({
|
|
name: name,
|
|
config: config
|
|
});
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
locales[name] = new Locale(mergeConfigs(parentConfig, config));
|
|
|
|
if (localeFamilies[name]) {
|
|
localeFamilies[name].forEach(function (x) {
|
|
defineLocale(x.name, x.config);
|
|
});
|
|
}
|
|
|
|
// backwards compat for now: also set the locale
|
|
// make sure we set the locale AFTER all child locales have been
|
|
// created, so we won't end up with the child locale set.
|
|
getSetGlobalLocale(name);
|
|
|
|
|
|
return locales[name];
|
|
} else {
|
|
// useful for testing
|
|
delete locales[name];
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function updateLocale(name, config) {
|
|
if (config != null) {
|
|
var locale, tmpLocale, parentConfig = baseConfig;
|
|
// MERGE
|
|
tmpLocale = loadLocale(name);
|
|
if (tmpLocale != null) {
|
|
parentConfig = tmpLocale._config;
|
|
}
|
|
config = mergeConfigs(parentConfig, config);
|
|
locale = new Locale(config);
|
|
locale.parentLocale = locales[name];
|
|
locales[name] = locale;
|
|
|
|
// backwards compat for now: also set the locale
|
|
getSetGlobalLocale(name);
|
|
} else {
|
|
// pass null for config to unupdate, useful for tests
|
|
if (locales[name] != null) {
|
|
if (locales[name].parentLocale != null) {
|
|
locales[name] = locales[name].parentLocale;
|
|
} else if (locales[name] != null) {
|
|
delete locales[name];
|
|
}
|
|
}
|
|
}
|
|
return locales[name];
|
|
}
|
|
|
|
// returns locale data
|
|
function getLocale (key) {
|
|
var locale;
|
|
|
|
if (key && key._locale && key._locale._abbr) {
|
|
key = key._locale._abbr;
|
|
}
|
|
|
|
if (!key) {
|
|
return globalLocale;
|
|
}
|
|
|
|
if (!isArray(key)) {
|
|
//short-circuit everything else
|
|
locale = loadLocale(key);
|
|
if (locale) {
|
|
return locale;
|
|
}
|
|
key = [key];
|
|
}
|
|
|
|
return chooseLocale(key);
|
|
}
|
|
|
|
function listLocales() {
|
|
return keys(locales);
|
|
}
|
|
|
|
function checkOverflow (m) {
|
|
var overflow;
|
|
var a = m._a;
|
|
|
|
if (a && getParsingFlags(m).overflow === -2) {
|
|
overflow =
|
|
a[MONTH] < 0 || a[MONTH] > 11 ? MONTH :
|
|
a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH]) ? DATE :
|
|
a[HOUR] < 0 || a[HOUR] > 24 || (a[HOUR] === 24 && (a[MINUTE] !== 0 || a[SECOND] !== 0 || a[MILLISECOND] !== 0)) ? HOUR :
|
|
a[MINUTE] < 0 || a[MINUTE] > 59 ? MINUTE :
|
|
a[SECOND] < 0 || a[SECOND] > 59 ? SECOND :
|
|
a[MILLISECOND] < 0 || a[MILLISECOND] > 999 ? MILLISECOND :
|
|
-1;
|
|
|
|
if (getParsingFlags(m)._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) {
|
|
overflow = DATE;
|
|
}
|
|
if (getParsingFlags(m)._overflowWeeks && overflow === -1) {
|
|
overflow = WEEK;
|
|
}
|
|
if (getParsingFlags(m)._overflowWeekday && overflow === -1) {
|
|
overflow = WEEKDAY;
|
|
}
|
|
|
|
getParsingFlags(m).overflow = overflow;
|
|
}
|
|
|
|
return m;
|
|
}
|
|
|
|
// Pick the first defined of two or three arguments.
|
|
function defaults(a, b, c) {
|
|
if (a != null) {
|
|
return a;
|
|
}
|
|
if (b != null) {
|
|
return b;
|
|
}
|
|
return c;
|
|
}
|
|
|
|
function currentDateArray(config) {
|
|
// hooks is actually the exported moment object
|
|
var nowValue = new Date(hooks.now());
|
|
if (config._useUTC) {
|
|
return [nowValue.getUTCFullYear(), nowValue.getUTCMonth(), nowValue.getUTCDate()];
|
|
}
|
|
return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()];
|
|
}
|
|
|
|
// convert an array to a date.
|
|
// the array should mirror the parameters below
|
|
// note: all values past the year are optional and will default to the lowest possible value.
|
|
// [year, month, day , hour, minute, second, millisecond]
|
|
function configFromArray (config) {
|
|
var i, date, input = [], currentDate, expectedWeekday, yearToUse;
|
|
|
|
if (config._d) {
|
|
return;
|
|
}
|
|
|
|
currentDate = currentDateArray(config);
|
|
|
|
//compute day of the year from weeks and weekdays
|
|
if (config._w && config._a[DATE] == null && config._a[MONTH] == null) {
|
|
dayOfYearFromWeekInfo(config);
|
|
}
|
|
|
|
//if the day of the year is set, figure out what it is
|
|
if (config._dayOfYear != null) {
|
|
yearToUse = defaults(config._a[YEAR], currentDate[YEAR]);
|
|
|
|
if (config._dayOfYear > daysInYear(yearToUse) || config._dayOfYear === 0) {
|
|
getParsingFlags(config)._overflowDayOfYear = true;
|
|
}
|
|
|
|
date = createUTCDate(yearToUse, 0, config._dayOfYear);
|
|
config._a[MONTH] = date.getUTCMonth();
|
|
config._a[DATE] = date.getUTCDate();
|
|
}
|
|
|
|
// Default to current date.
|
|
// * if no year, month, day of month are given, default to today
|
|
// * if day of month is given, default month and year
|
|
// * if month is given, default only year
|
|
// * if year is given, don't default anything
|
|
for (i = 0; i < 3 && config._a[i] == null; ++i) {
|
|
config._a[i] = input[i] = currentDate[i];
|
|
}
|
|
|
|
// Zero out whatever was not defaulted, including time
|
|
for (; i < 7; i++) {
|
|
config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i];
|
|
}
|
|
|
|
// Check for 24:00:00.000
|
|
if (config._a[HOUR] === 24 &&
|
|
config._a[MINUTE] === 0 &&
|
|
config._a[SECOND] === 0 &&
|
|
config._a[MILLISECOND] === 0) {
|
|
config._nextDay = true;
|
|
config._a[HOUR] = 0;
|
|
}
|
|
|
|
config._d = (config._useUTC ? createUTCDate : createDate).apply(null, input);
|
|
expectedWeekday = config._useUTC ? config._d.getUTCDay() : config._d.getDay();
|
|
|
|
// Apply timezone offset from input. The actual utcOffset can be changed
|
|
// with parseZone.
|
|
if (config._tzm != null) {
|
|
config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);
|
|
}
|
|
|
|
if (config._nextDay) {
|
|
config._a[HOUR] = 24;
|
|
}
|
|
|
|
// check for mismatching day of week
|
|
if (config._w && typeof config._w.d !== 'undefined' && config._w.d !== expectedWeekday) {
|
|
getParsingFlags(config).weekdayMismatch = true;
|
|
}
|
|
}
|
|
|
|
function dayOfYearFromWeekInfo(config) {
|
|
var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow;
|
|
|
|
w = config._w;
|
|
if (w.GG != null || w.W != null || w.E != null) {
|
|
dow = 1;
|
|
doy = 4;
|
|
|
|
// TODO: We need to take the current isoWeekYear, but that depends on
|
|
// how we interpret now (local, utc, fixed offset). So create
|
|
// a now version of current config (take local/utc/offset flags, and
|
|
// create now).
|
|
weekYear = defaults(w.GG, config._a[YEAR], weekOfYear(createLocal(), 1, 4).year);
|
|
week = defaults(w.W, 1);
|
|
weekday = defaults(w.E, 1);
|
|
if (weekday < 1 || weekday > 7) {
|
|
weekdayOverflow = true;
|
|
}
|
|
} else {
|
|
dow = config._locale._week.dow;
|
|
doy = config._locale._week.doy;
|
|
|
|
var curWeek = weekOfYear(createLocal(), dow, doy);
|
|
|
|
weekYear = defaults(w.gg, config._a[YEAR], curWeek.year);
|
|
|
|
// Default to current week.
|
|
week = defaults(w.w, curWeek.week);
|
|
|
|
if (w.d != null) {
|
|
// weekday -- low day numbers are considered next week
|
|
weekday = w.d;
|
|
if (weekday < 0 || weekday > 6) {
|
|
weekdayOverflow = true;
|
|
}
|
|
} else if (w.e != null) {
|
|
// local weekday -- counting starts from beginning of week
|
|
weekday = w.e + dow;
|
|
if (w.e < 0 || w.e > 6) {
|
|
weekdayOverflow = true;
|
|
}
|
|
} else {
|
|
// default to beginning of week
|
|
weekday = dow;
|
|
}
|
|
}
|
|
if (week < 1 || week > weeksInYear(weekYear, dow, doy)) {
|
|
getParsingFlags(config)._overflowWeeks = true;
|
|
} else if (weekdayOverflow != null) {
|
|
getParsingFlags(config)._overflowWeekday = true;
|
|
} else {
|
|
temp = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy);
|
|
config._a[YEAR] = temp.year;
|
|
config._dayOfYear = temp.dayOfYear;
|
|
}
|
|
}
|
|
|
|
// iso 8601 regex
|
|
// 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00)
|
|
var extendedIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/;
|
|
var basicIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/;
|
|
|
|
var tzRegex = /Z|[+-]\d\d(?::?\d\d)?/;
|
|
|
|
var isoDates = [
|
|
['YYYYYY-MM-DD', /[+-]\d{6}-\d\d-\d\d/],
|
|
['YYYY-MM-DD', /\d{4}-\d\d-\d\d/],
|
|
['GGGG-[W]WW-E', /\d{4}-W\d\d-\d/],
|
|
['GGGG-[W]WW', /\d{4}-W\d\d/, false],
|
|
['YYYY-DDD', /\d{4}-\d{3}/],
|
|
['YYYY-MM', /\d{4}-\d\d/, false],
|
|
['YYYYYYMMDD', /[+-]\d{10}/],
|
|
['YYYYMMDD', /\d{8}/],
|
|
// YYYYMM is NOT allowed by the standard
|
|
['GGGG[W]WWE', /\d{4}W\d{3}/],
|
|
['GGGG[W]WW', /\d{4}W\d{2}/, false],
|
|
['YYYYDDD', /\d{7}/]
|
|
];
|
|
|
|
// iso time formats and regexes
|
|
var isoTimes = [
|
|
['HH:mm:ss.SSSS', /\d\d:\d\d:\d\d\.\d+/],
|
|
['HH:mm:ss,SSSS', /\d\d:\d\d:\d\d,\d+/],
|
|
['HH:mm:ss', /\d\d:\d\d:\d\d/],
|
|
['HH:mm', /\d\d:\d\d/],
|
|
['HHmmss.SSSS', /\d\d\d\d\d\d\.\d+/],
|
|
['HHmmss,SSSS', /\d\d\d\d\d\d,\d+/],
|
|
['HHmmss', /\d\d\d\d\d\d/],
|
|
['HHmm', /\d\d\d\d/],
|
|
['HH', /\d\d/]
|
|
];
|
|
|
|
var aspNetJsonRegex = /^\/?Date\((\-?\d+)/i;
|
|
|
|
// date from iso format
|
|
function configFromISO(config) {
|
|
var i, l,
|
|
string = config._i,
|
|
match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string),
|
|
allowTime, dateFormat, timeFormat, tzFormat;
|
|
|
|
if (match) {
|
|
getParsingFlags(config).iso = true;
|
|
|
|
for (i = 0, l = isoDates.length; i < l; i++) {
|
|
if (isoDates[i][1].exec(match[1])) {
|
|
dateFormat = isoDates[i][0];
|
|
allowTime = isoDates[i][2] !== false;
|
|
break;
|
|
}
|
|
}
|
|
if (dateFormat == null) {
|
|
config._isValid = false;
|
|
return;
|
|
}
|
|
if (match[3]) {
|
|
for (i = 0, l = isoTimes.length; i < l; i++) {
|
|
if (isoTimes[i][1].exec(match[3])) {
|
|
// match[2] should be 'T' or space
|
|
timeFormat = (match[2] || ' ') + isoTimes[i][0];
|
|
break;
|
|
}
|
|
}
|
|
if (timeFormat == null) {
|
|
config._isValid = false;
|
|
return;
|
|
}
|
|
}
|
|
if (!allowTime && timeFormat != null) {
|
|
config._isValid = false;
|
|
return;
|
|
}
|
|
if (match[4]) {
|
|
if (tzRegex.exec(match[4])) {
|
|
tzFormat = 'Z';
|
|
} else {
|
|
config._isValid = false;
|
|
return;
|
|
}
|
|
}
|
|
config._f = dateFormat + (timeFormat || '') + (tzFormat || '');
|
|
configFromStringAndFormat(config);
|
|
} else {
|
|
config._isValid = false;
|
|
}
|
|
}
|
|
|
|
// RFC 2822 regex: For details see https://tools.ietf.org/html/rfc2822#section-3.3
|
|
var rfc2822 = /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/;
|
|
|
|
function extractFromRFC2822Strings(yearStr, monthStr, dayStr, hourStr, minuteStr, secondStr) {
|
|
var result = [
|
|
untruncateYear(yearStr),
|
|
defaultLocaleMonthsShort.indexOf(monthStr),
|
|
parseInt(dayStr, 10),
|
|
parseInt(hourStr, 10),
|
|
parseInt(minuteStr, 10)
|
|
];
|
|
|
|
if (secondStr) {
|
|
result.push(parseInt(secondStr, 10));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function untruncateYear(yearStr) {
|
|
var year = parseInt(yearStr, 10);
|
|
if (year <= 49) {
|
|
return 2000 + year;
|
|
} else if (year <= 999) {
|
|
return 1900 + year;
|
|
}
|
|
return year;
|
|
}
|
|
|
|
function preprocessRFC2822(s) {
|
|
// Remove comments and folding whitespace and replace multiple-spaces with a single space
|
|
return s.replace(/\([^)]*\)|[\n\t]/g, ' ').replace(/(\s\s+)/g, ' ').replace(/^\s\s*/, '').replace(/\s\s*$/, '');
|
|
}
|
|
|
|
function checkWeekday(weekdayStr, parsedInput, config) {
|
|
if (weekdayStr) {
|
|
// TODO: Replace the vanilla JS Date object with an indepentent day-of-week check.
|
|
var weekdayProvided = defaultLocaleWeekdaysShort.indexOf(weekdayStr),
|
|
weekdayActual = new Date(parsedInput[0], parsedInput[1], parsedInput[2]).getDay();
|
|
if (weekdayProvided !== weekdayActual) {
|
|
getParsingFlags(config).weekdayMismatch = true;
|
|
config._isValid = false;
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
var obsOffsets = {
|
|
UT: 0,
|
|
GMT: 0,
|
|
EDT: -4 * 60,
|
|
EST: -5 * 60,
|
|
CDT: -5 * 60,
|
|
CST: -6 * 60,
|
|
MDT: -6 * 60,
|
|
MST: -7 * 60,
|
|
PDT: -7 * 60,
|
|
PST: -8 * 60
|
|
};
|
|
|
|
function calculateOffset(obsOffset, militaryOffset, numOffset) {
|
|
if (obsOffset) {
|
|
return obsOffsets[obsOffset];
|
|
} else if (militaryOffset) {
|
|
// the only allowed military tz is Z
|
|
return 0;
|
|
} else {
|
|
var hm = parseInt(numOffset, 10);
|
|
var m = hm % 100, h = (hm - m) / 100;
|
|
return h * 60 + m;
|
|
}
|
|
}
|
|
|
|
// date and time from ref 2822 format
|
|
function configFromRFC2822(config) {
|
|
var match = rfc2822.exec(preprocessRFC2822(config._i));
|
|
if (match) {
|
|
var parsedArray = extractFromRFC2822Strings(match[4], match[3], match[2], match[5], match[6], match[7]);
|
|
if (!checkWeekday(match[1], parsedArray, config)) {
|
|
return;
|
|
}
|
|
|
|
config._a = parsedArray;
|
|
config._tzm = calculateOffset(match[8], match[9], match[10]);
|
|
|
|
config._d = createUTCDate.apply(null, config._a);
|
|
config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);
|
|
|
|
getParsingFlags(config).rfc2822 = true;
|
|
} else {
|
|
config._isValid = false;
|
|
}
|
|
}
|
|
|
|
// date from iso format or fallback
|
|
function configFromString(config) {
|
|
var matched = aspNetJsonRegex.exec(config._i);
|
|
|
|
if (matched !== null) {
|
|
config._d = new Date(+matched[1]);
|
|
return;
|
|
}
|
|
|
|
configFromISO(config);
|
|
if (config._isValid === false) {
|
|
delete config._isValid;
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
configFromRFC2822(config);
|
|
if (config._isValid === false) {
|
|
delete config._isValid;
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
// Final attempt, use Input Fallback
|
|
hooks.createFromInputFallback(config);
|
|
}
|
|
|
|
hooks.createFromInputFallback = deprecate(
|
|
'value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), ' +
|
|
'which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are ' +
|
|
'discouraged and will be removed in an upcoming major release. Please refer to ' +
|
|
'http://momentjs.com/guides/#/warnings/js-date/ for more info.',
|
|
function (config) {
|
|
config._d = new Date(config._i + (config._useUTC ? ' UTC' : ''));
|
|
}
|
|
);
|
|
|
|
// constant that refers to the ISO standard
|
|
hooks.ISO_8601 = function () {};
|
|
|
|
// constant that refers to the RFC 2822 form
|
|
hooks.RFC_2822 = function () {};
|
|
|
|
// date from string and format string
|
|
function configFromStringAndFormat(config) {
|
|
// TODO: Move this to another part of the creation flow to prevent circular deps
|
|
if (config._f === hooks.ISO_8601) {
|
|
configFromISO(config);
|
|
return;
|
|
}
|
|
if (config._f === hooks.RFC_2822) {
|
|
configFromRFC2822(config);
|
|
return;
|
|
}
|
|
config._a = [];
|
|
getParsingFlags(config).empty = true;
|
|
|
|
// This array is used to make a Date, either with `new Date` or `Date.UTC`
|
|
var string = '' + config._i,
|
|
i, parsedInput, tokens, token, skipped,
|
|
stringLength = string.length,
|
|
totalParsedInputLength = 0;
|
|
|
|
tokens = expandFormat(config._f, config._locale).match(formattingTokens) || [];
|
|
|
|
for (i = 0; i < tokens.length; i++) {
|
|
token = tokens[i];
|
|
parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0];
|
|
// console.log('token', token, 'parsedInput', parsedInput,
|
|
// 'regex', getParseRegexForToken(token, config));
|
|
if (parsedInput) {
|
|
skipped = string.substr(0, string.indexOf(parsedInput));
|
|
if (skipped.length > 0) {
|
|
getParsingFlags(config).unusedInput.push(skipped);
|
|
}
|
|
string = string.slice(string.indexOf(parsedInput) + parsedInput.length);
|
|
totalParsedInputLength += parsedInput.length;
|
|
}
|
|
// don't parse if it's not a known token
|
|
if (formatTokenFunctions[token]) {
|
|
if (parsedInput) {
|
|
getParsingFlags(config).empty = false;
|
|
}
|
|
else {
|
|
getParsingFlags(config).unusedTokens.push(token);
|
|
}
|
|
addTimeToArrayFromToken(token, parsedInput, config);
|
|
}
|
|
else if (config._strict && !parsedInput) {
|
|
getParsingFlags(config).unusedTokens.push(token);
|
|
}
|
|
}
|
|
|
|
// add remaining unparsed input length to the string
|
|
getParsingFlags(config).charsLeftOver = stringLength - totalParsedInputLength;
|
|
if (string.length > 0) {
|
|
getParsingFlags(config).unusedInput.push(string);
|
|
}
|
|
|
|
// clear _12h flag if hour is <= 12
|
|
if (config._a[HOUR] <= 12 &&
|
|
getParsingFlags(config).bigHour === true &&
|
|
config._a[HOUR] > 0) {
|
|
getParsingFlags(config).bigHour = undefined;
|
|
}
|
|
|
|
getParsingFlags(config).parsedDateParts = config._a.slice(0);
|
|
getParsingFlags(config).meridiem = config._meridiem;
|
|
// handle meridiem
|
|
config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR], config._meridiem);
|
|
|
|
configFromArray(config);
|
|
checkOverflow(config);
|
|
}
|
|
|
|
|
|
function meridiemFixWrap (locale, hour, meridiem) {
|
|
var isPm;
|
|
|
|
if (meridiem == null) {
|
|
// nothing to do
|
|
return hour;
|
|
}
|
|
if (locale.meridiemHour != null) {
|
|
return locale.meridiemHour(hour, meridiem);
|
|
} else if (locale.isPM != null) {
|
|
// Fallback
|
|
isPm = locale.isPM(meridiem);
|
|
if (isPm && hour < 12) {
|
|
hour += 12;
|
|
}
|
|
if (!isPm && hour === 12) {
|
|
hour = 0;
|
|
}
|
|
return hour;
|
|
} else {
|
|
// this is not supposed to happen
|
|
return hour;
|
|
}
|
|
}
|
|
|
|
// date from string and array of format strings
|
|
function configFromStringAndArray(config) {
|
|
var tempConfig,
|
|
bestMoment,
|
|
|
|
scoreToBeat,
|
|
i,
|
|
currentScore;
|
|
|
|
if (config._f.length === 0) {
|
|
getParsingFlags(config).invalidFormat = true;
|
|
config._d = new Date(NaN);
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < config._f.length; i++) {
|
|
currentScore = 0;
|
|
tempConfig = copyConfig({}, config);
|
|
if (config._useUTC != null) {
|
|
tempConfig._useUTC = config._useUTC;
|
|
}
|
|
tempConfig._f = config._f[i];
|
|
configFromStringAndFormat(tempConfig);
|
|
|
|
if (!isValid(tempConfig)) {
|
|
continue;
|
|
}
|
|
|
|
// if there is any input that was not parsed add a penalty for that format
|
|
currentScore += getParsingFlags(tempConfig).charsLeftOver;
|
|
|
|
//or tokens
|
|
currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10;
|
|
|
|
getParsingFlags(tempConfig).score = currentScore;
|
|
|
|
if (scoreToBeat == null || currentScore < scoreToBeat) {
|
|
scoreToBeat = currentScore;
|
|
bestMoment = tempConfig;
|
|
}
|
|
}
|
|
|
|
extend(config, bestMoment || tempConfig);
|
|
}
|
|
|
|
function configFromObject(config) {
|
|
if (config._d) {
|
|
return;
|
|
}
|
|
|
|
var i = normalizeObjectUnits(config._i);
|
|
config._a = map([i.year, i.month, i.day || i.date, i.hour, i.minute, i.second, i.millisecond], function (obj) {
|
|
return obj && parseInt(obj, 10);
|
|
});
|
|
|
|
configFromArray(config);
|
|
}
|
|
|
|
function createFromConfig (config) {
|
|
var res = new Moment(checkOverflow(prepareConfig(config)));
|
|
if (res._nextDay) {
|
|
// Adding is smart enough around DST
|
|
res.add(1, 'd');
|
|
res._nextDay = undefined;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
function prepareConfig (config) {
|
|
var input = config._i,
|
|
format = config._f;
|
|
|
|
config._locale = config._locale || getLocale(config._l);
|
|
|
|
if (input === null || (format === undefined && input === '')) {
|
|
return createInvalid({nullInput: true});
|
|
}
|
|
|
|
if (typeof input === 'string') {
|
|
config._i = input = config._locale.preparse(input);
|
|
}
|
|
|
|
if (isMoment(input)) {
|
|
return new Moment(checkOverflow(input));
|
|
} else if (isDate(input)) {
|
|
config._d = input;
|
|
} else if (isArray(format)) {
|
|
configFromStringAndArray(config);
|
|
} else if (format) {
|
|
configFromStringAndFormat(config);
|
|
} else {
|
|
configFromInput(config);
|
|
}
|
|
|
|
if (!isValid(config)) {
|
|
config._d = null;
|
|
}
|
|
|
|
return config;
|
|
}
|
|
|
|
function configFromInput(config) {
|
|
var input = config._i;
|
|
if (isUndefined(input)) {
|
|
config._d = new Date(hooks.now());
|
|
} else if (isDate(input)) {
|
|
config._d = new Date(input.valueOf());
|
|
} else if (typeof input === 'string') {
|
|
configFromString(config);
|
|
} else if (isArray(input)) {
|
|
config._a = map(input.slice(0), function (obj) {
|
|
return parseInt(obj, 10);
|
|
});
|
|
configFromArray(config);
|
|
} else if (isObject(input)) {
|
|
configFromObject(config);
|
|
} else if (isNumber(input)) {
|
|
// from milliseconds
|
|
config._d = new Date(input);
|
|
} else {
|
|
hooks.createFromInputFallback(config);
|
|
}
|
|
}
|
|
|
|
function createLocalOrUTC (input, format, locale, strict, isUTC) {
|
|
var c = {};
|
|
|
|
if (locale === true || locale === false) {
|
|
strict = locale;
|
|
locale = undefined;
|
|
}
|
|
|
|
if ((isObject(input) && isObjectEmpty(input)) ||
|
|
(isArray(input) && input.length === 0)) {
|
|
input = undefined;
|
|
}
|
|
// object construction must be done this way.
|
|
// https://github.com/moment/moment/issues/1423
|
|
c._isAMomentObject = true;
|
|
c._useUTC = c._isUTC = isUTC;
|
|
c._l = locale;
|
|
c._i = input;
|
|
c._f = format;
|
|
c._strict = strict;
|
|
|
|
return createFromConfig(c);
|
|
}
|
|
|
|
function createLocal (input, format, locale, strict) {
|
|
return createLocalOrUTC(input, format, locale, strict, false);
|
|
}
|
|
|
|
var prototypeMin = deprecate(
|
|
'moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/',
|
|
function () {
|
|
var other = createLocal.apply(null, arguments);
|
|
if (this.isValid() && other.isValid()) {
|
|
return other < this ? this : other;
|
|
} else {
|
|
return createInvalid();
|
|
}
|
|
}
|
|
);
|
|
|
|
var prototypeMax = deprecate(
|
|
'moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/',
|
|
function () {
|
|
var other = createLocal.apply(null, arguments);
|
|
if (this.isValid() && other.isValid()) {
|
|
return other > this ? this : other;
|
|
} else {
|
|
return createInvalid();
|
|
}
|
|
}
|
|
);
|
|
|
|
// Pick a moment m from moments so that m[fn](other) is true for all
|
|
// other. This relies on the function fn to be transitive.
|
|
//
|
|
// moments should either be an array of moment objects or an array, whose
|
|
// first element is an array of moment objects.
|
|
function pickBy(fn, moments) {
|
|
var res, i;
|
|
if (moments.length === 1 && isArray(moments[0])) {
|
|
moments = moments[0];
|
|
}
|
|
if (!moments.length) {
|
|
return createLocal();
|
|
}
|
|
res = moments[0];
|
|
for (i = 1; i < moments.length; ++i) {
|
|
if (!moments[i].isValid() || moments[i][fn](res)) {
|
|
res = moments[i];
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
// TODO: Use [].sort instead?
|
|
function min () {
|
|
var args = [].slice.call(arguments, 0);
|
|
|
|
return pickBy('isBefore', args);
|
|
}
|
|
|
|
function max () {
|
|
var args = [].slice.call(arguments, 0);
|
|
|
|
return pickBy('isAfter', args);
|
|
}
|
|
|
|
var now = function () {
|
|
return Date.now ? Date.now() : +(new Date());
|
|
};
|
|
|
|
var ordering = ['year', 'quarter', 'month', 'week', 'day', 'hour', 'minute', 'second', 'millisecond'];
|
|
|
|
function isDurationValid(m) {
|
|
for (var key in m) {
|
|
if (!(indexOf.call(ordering, key) !== -1 && (m[key] == null || !isNaN(m[key])))) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
var unitHasDecimal = false;
|
|
for (var i = 0; i < ordering.length; ++i) {
|
|
if (m[ordering[i]]) {
|
|
if (unitHasDecimal) {
|
|
return false; // only allow non-integers for smallest unit
|
|
}
|
|
if (parseFloat(m[ordering[i]]) !== toInt(m[ordering[i]])) {
|
|
unitHasDecimal = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function isValid$1() {
|
|
return this._isValid;
|
|
}
|
|
|
|
function createInvalid$1() {
|
|
return createDuration(NaN);
|
|
}
|
|
|
|
function Duration (duration) {
|
|
var normalizedInput = normalizeObjectUnits(duration),
|
|
years = normalizedInput.year || 0,
|
|
quarters = normalizedInput.quarter || 0,
|
|
months = normalizedInput.month || 0,
|
|
weeks = normalizedInput.week || normalizedInput.isoWeek || 0,
|
|
days = normalizedInput.day || 0,
|
|
hours = normalizedInput.hour || 0,
|
|
minutes = normalizedInput.minute || 0,
|
|
seconds = normalizedInput.second || 0,
|
|
milliseconds = normalizedInput.millisecond || 0;
|
|
|
|
this._isValid = isDurationValid(normalizedInput);
|
|
|
|
// representation for dateAddRemove
|
|
this._milliseconds = +milliseconds +
|
|
seconds * 1e3 + // 1000
|
|
minutes * 6e4 + // 1000 * 60
|
|
hours * 1000 * 60 * 60; //using 1000 * 60 * 60 instead of 36e5 to avoid floating point rounding errors https://github.com/moment/moment/issues/2978
|
|
// Because of dateAddRemove treats 24 hours as different from a
|
|
// day when working around DST, we need to store them separately
|
|
this._days = +days +
|
|
weeks * 7;
|
|
// It is impossible to translate months into days without knowing
|
|
// which months you are are talking about, so we have to store
|
|
// it separately.
|
|
this._months = +months +
|
|
quarters * 3 +
|
|
years * 12;
|
|
|
|
this._data = {};
|
|
|
|
this._locale = getLocale();
|
|
|
|
this._bubble();
|
|
}
|
|
|
|
function isDuration (obj) {
|
|
return obj instanceof Duration;
|
|
}
|
|
|
|
function absRound (number) {
|
|
if (number < 0) {
|
|
return Math.round(-1 * number) * -1;
|
|
} else {
|
|
return Math.round(number);
|
|
}
|
|
}
|
|
|
|
// FORMATTING
|
|
|
|
function offset (token, separator) {
|
|
addFormatToken(token, 0, 0, function () {
|
|
var offset = this.utcOffset();
|
|
var sign = '+';
|
|
if (offset < 0) {
|
|
offset = -offset;
|
|
sign = '-';
|
|
}
|
|
return sign + zeroFill(~~(offset / 60), 2) + separator + zeroFill(~~(offset) % 60, 2);
|
|
});
|
|
}
|
|
|
|
offset('Z', ':');
|
|
offset('ZZ', '');
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('Z', matchShortOffset);
|
|
addRegexToken('ZZ', matchShortOffset);
|
|
addParseToken(['Z', 'ZZ'], function (input, array, config) {
|
|
config._useUTC = true;
|
|
config._tzm = offsetFromString(matchShortOffset, input);
|
|
});
|
|
|
|
// HELPERS
|
|
|
|
// timezone chunker
|
|
// '+10:00' > ['10', '00']
|
|
// '-1530' > ['-15', '30']
|
|
var chunkOffset = /([\+\-]|\d\d)/gi;
|
|
|
|
function offsetFromString(matcher, string) {
|
|
var matches = (string || '').match(matcher);
|
|
|
|
if (matches === null) {
|
|
return null;
|
|
}
|
|
|
|
var chunk = matches[matches.length - 1] || [];
|
|
var parts = (chunk + '').match(chunkOffset) || ['-', 0, 0];
|
|
var minutes = +(parts[1] * 60) + toInt(parts[2]);
|
|
|
|
return minutes === 0 ?
|
|
0 :
|
|
parts[0] === '+' ? minutes : -minutes;
|
|
}
|
|
|
|
// Return a moment from input, that is local/utc/zone equivalent to model.
|
|
function cloneWithOffset(input, model) {
|
|
var res, diff;
|
|
if (model._isUTC) {
|
|
res = model.clone();
|
|
diff = (isMoment(input) || isDate(input) ? input.valueOf() : createLocal(input).valueOf()) - res.valueOf();
|
|
// Use low-level api, because this fn is low-level api.
|
|
res._d.setTime(res._d.valueOf() + diff);
|
|
hooks.updateOffset(res, false);
|
|
return res;
|
|
} else {
|
|
return createLocal(input).local();
|
|
}
|
|
}
|
|
|
|
function getDateOffset (m) {
|
|
// On Firefox.24 Date#getTimezoneOffset returns a floating point.
|
|
// https://github.com/moment/moment/pull/1871
|
|
return -Math.round(m._d.getTimezoneOffset() / 15) * 15;
|
|
}
|
|
|
|
// HOOKS
|
|
|
|
// This function will be called whenever a moment is mutated.
|
|
// It is intended to keep the offset in sync with the timezone.
|
|
hooks.updateOffset = function () {};
|
|
|
|
// MOMENTS
|
|
|
|
// keepLocalTime = true means only change the timezone, without
|
|
// affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]-->
|
|
// 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset
|
|
// +0200, so we adjust the time as needed, to be valid.
|
|
//
|
|
// Keeping the time actually adds/subtracts (one hour)
|
|
// from the actual represented time. That is why we call updateOffset
|
|
// a second time. In case it wants us to change the offset again
|
|
// _changeInProgress == true case, then we have to adjust, because
|
|
// there is no such time in the given timezone.
|
|
function getSetOffset (input, keepLocalTime, keepMinutes) {
|
|
var offset = this._offset || 0,
|
|
localAdjust;
|
|
if (!this.isValid()) {
|
|
return input != null ? this : NaN;
|
|
}
|
|
if (input != null) {
|
|
if (typeof input === 'string') {
|
|
input = offsetFromString(matchShortOffset, input);
|
|
if (input === null) {
|
|
return this;
|
|
}
|
|
} else if (Math.abs(input) < 16 && !keepMinutes) {
|
|
input = input * 60;
|
|
}
|
|
if (!this._isUTC && keepLocalTime) {
|
|
localAdjust = getDateOffset(this);
|
|
}
|
|
this._offset = input;
|
|
this._isUTC = true;
|
|
if (localAdjust != null) {
|
|
this.add(localAdjust, 'm');
|
|
}
|
|
if (offset !== input) {
|
|
if (!keepLocalTime || this._changeInProgress) {
|
|
addSubtract(this, createDuration(input - offset, 'm'), 1, false);
|
|
} else if (!this._changeInProgress) {
|
|
this._changeInProgress = true;
|
|
hooks.updateOffset(this, true);
|
|
this._changeInProgress = null;
|
|
}
|
|
}
|
|
return this;
|
|
} else {
|
|
return this._isUTC ? offset : getDateOffset(this);
|
|
}
|
|
}
|
|
|
|
function getSetZone (input, keepLocalTime) {
|
|
if (input != null) {
|
|
if (typeof input !== 'string') {
|
|
input = -input;
|
|
}
|
|
|
|
this.utcOffset(input, keepLocalTime);
|
|
|
|
return this;
|
|
} else {
|
|
return -this.utcOffset();
|
|
}
|
|
}
|
|
|
|
function setOffsetToUTC (keepLocalTime) {
|
|
return this.utcOffset(0, keepLocalTime);
|
|
}
|
|
|
|
function setOffsetToLocal (keepLocalTime) {
|
|
if (this._isUTC) {
|
|
this.utcOffset(0, keepLocalTime);
|
|
this._isUTC = false;
|
|
|
|
if (keepLocalTime) {
|
|
this.subtract(getDateOffset(this), 'm');
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
|
|
function setOffsetToParsedOffset () {
|
|
if (this._tzm != null) {
|
|
this.utcOffset(this._tzm, false, true);
|
|
} else if (typeof this._i === 'string') {
|
|
var tZone = offsetFromString(matchOffset, this._i);
|
|
if (tZone != null) {
|
|
this.utcOffset(tZone);
|
|
}
|
|
else {
|
|
this.utcOffset(0, true);
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
|
|
function hasAlignedHourOffset (input) {
|
|
if (!this.isValid()) {
|
|
return false;
|
|
}
|
|
input = input ? createLocal(input).utcOffset() : 0;
|
|
|
|
return (this.utcOffset() - input) % 60 === 0;
|
|
}
|
|
|
|
function isDaylightSavingTime () {
|
|
return (
|
|
this.utcOffset() > this.clone().month(0).utcOffset() ||
|
|
this.utcOffset() > this.clone().month(5).utcOffset()
|
|
);
|
|
}
|
|
|
|
function isDaylightSavingTimeShifted () {
|
|
if (!isUndefined(this._isDSTShifted)) {
|
|
return this._isDSTShifted;
|
|
}
|
|
|
|
var c = {};
|
|
|
|
copyConfig(c, this);
|
|
c = prepareConfig(c);
|
|
|
|
if (c._a) {
|
|
var other = c._isUTC ? createUTC(c._a) : createLocal(c._a);
|
|
this._isDSTShifted = this.isValid() &&
|
|
compareArrays(c._a, other.toArray()) > 0;
|
|
} else {
|
|
this._isDSTShifted = false;
|
|
}
|
|
|
|
return this._isDSTShifted;
|
|
}
|
|
|
|
function isLocal () {
|
|
return this.isValid() ? !this._isUTC : false;
|
|
}
|
|
|
|
function isUtcOffset () {
|
|
return this.isValid() ? this._isUTC : false;
|
|
}
|
|
|
|
function isUtc () {
|
|
return this.isValid() ? this._isUTC && this._offset === 0 : false;
|
|
}
|
|
|
|
// ASP.NET json date format regex
|
|
var aspNetRegex = /^(\-|\+)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/;
|
|
|
|
// from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html
|
|
// somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere
|
|
// and further modified to allow for strings containing both week and day
|
|
var isoRegex = /^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;
|
|
|
|
function createDuration (input, key) {
|
|
var duration = input,
|
|
// matching against regexp is expensive, do it on demand
|
|
match = null,
|
|
sign,
|
|
ret,
|
|
diffRes;
|
|
|
|
if (isDuration(input)) {
|
|
duration = {
|
|
ms : input._milliseconds,
|
|
d : input._days,
|
|
M : input._months
|
|
};
|
|
} else if (isNumber(input)) {
|
|
duration = {};
|
|
if (key) {
|
|
duration[key] = input;
|
|
} else {
|
|
duration.milliseconds = input;
|
|
}
|
|
} else if (!!(match = aspNetRegex.exec(input))) {
|
|
sign = (match[1] === '-') ? -1 : 1;
|
|
duration = {
|
|
y : 0,
|
|
d : toInt(match[DATE]) * sign,
|
|
h : toInt(match[HOUR]) * sign,
|
|
m : toInt(match[MINUTE]) * sign,
|
|
s : toInt(match[SECOND]) * sign,
|
|
ms : toInt(absRound(match[MILLISECOND] * 1000)) * sign // the millisecond decimal point is included in the match
|
|
};
|
|
} else if (!!(match = isoRegex.exec(input))) {
|
|
sign = (match[1] === '-') ? -1 : 1;
|
|
duration = {
|
|
y : parseIso(match[2], sign),
|
|
M : parseIso(match[3], sign),
|
|
w : parseIso(match[4], sign),
|
|
d : parseIso(match[5], sign),
|
|
h : parseIso(match[6], sign),
|
|
m : parseIso(match[7], sign),
|
|
s : parseIso(match[8], sign)
|
|
};
|
|
} else if (duration == null) {// checks for null or undefined
|
|
duration = {};
|
|
} else if (typeof duration === 'object' && ('from' in duration || 'to' in duration)) {
|
|
diffRes = momentsDifference(createLocal(duration.from), createLocal(duration.to));
|
|
|
|
duration = {};
|
|
duration.ms = diffRes.milliseconds;
|
|
duration.M = diffRes.months;
|
|
}
|
|
|
|
ret = new Duration(duration);
|
|
|
|
if (isDuration(input) && hasOwnProp(input, '_locale')) {
|
|
ret._locale = input._locale;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
createDuration.fn = Duration.prototype;
|
|
createDuration.invalid = createInvalid$1;
|
|
|
|
function parseIso (inp, sign) {
|
|
// We'd normally use ~~inp for this, but unfortunately it also
|
|
// converts floats to ints.
|
|
// inp may be undefined, so careful calling replace on it.
|
|
var res = inp && parseFloat(inp.replace(',', '.'));
|
|
// apply sign while we're at it
|
|
return (isNaN(res) ? 0 : res) * sign;
|
|
}
|
|
|
|
function positiveMomentsDifference(base, other) {
|
|
var res = {};
|
|
|
|
res.months = other.month() - base.month() +
|
|
(other.year() - base.year()) * 12;
|
|
if (base.clone().add(res.months, 'M').isAfter(other)) {
|
|
--res.months;
|
|
}
|
|
|
|
res.milliseconds = +other - +(base.clone().add(res.months, 'M'));
|
|
|
|
return res;
|
|
}
|
|
|
|
function momentsDifference(base, other) {
|
|
var res;
|
|
if (!(base.isValid() && other.isValid())) {
|
|
return {milliseconds: 0, months: 0};
|
|
}
|
|
|
|
other = cloneWithOffset(other, base);
|
|
if (base.isBefore(other)) {
|
|
res = positiveMomentsDifference(base, other);
|
|
} else {
|
|
res = positiveMomentsDifference(other, base);
|
|
res.milliseconds = -res.milliseconds;
|
|
res.months = -res.months;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
// TODO: remove 'name' arg after deprecation is removed
|
|
function createAdder(direction, name) {
|
|
return function (val, period) {
|
|
var dur, tmp;
|
|
//invert the arguments, but complain about it
|
|
if (period !== null && !isNaN(+period)) {
|
|
deprecateSimple(name, 'moment().' + name + '(period, number) is deprecated. Please use moment().' + name + '(number, period). ' +
|
|
'See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info.');
|
|
tmp = val; val = period; period = tmp;
|
|
}
|
|
|
|
val = typeof val === 'string' ? +val : val;
|
|
dur = createDuration(val, period);
|
|
addSubtract(this, dur, direction);
|
|
return this;
|
|
};
|
|
}
|
|
|
|
function addSubtract (mom, duration, isAdding, updateOffset) {
|
|
var milliseconds = duration._milliseconds,
|
|
days = absRound(duration._days),
|
|
months = absRound(duration._months);
|
|
|
|
if (!mom.isValid()) {
|
|
// No op
|
|
return;
|
|
}
|
|
|
|
updateOffset = updateOffset == null ? true : updateOffset;
|
|
|
|
if (months) {
|
|
setMonth(mom, get(mom, 'Month') + months * isAdding);
|
|
}
|
|
if (days) {
|
|
set$1(mom, 'Date', get(mom, 'Date') + days * isAdding);
|
|
}
|
|
if (milliseconds) {
|
|
mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding);
|
|
}
|
|
if (updateOffset) {
|
|
hooks.updateOffset(mom, days || months);
|
|
}
|
|
}
|
|
|
|
var add = createAdder(1, 'add');
|
|
var subtract = createAdder(-1, 'subtract');
|
|
|
|
function getCalendarFormat(myMoment, now) {
|
|
var diff = myMoment.diff(now, 'days', true);
|
|
return diff < -6 ? 'sameElse' :
|
|
diff < -1 ? 'lastWeek' :
|
|
diff < 0 ? 'lastDay' :
|
|
diff < 1 ? 'sameDay' :
|
|
diff < 2 ? 'nextDay' :
|
|
diff < 7 ? 'nextWeek' : 'sameElse';
|
|
}
|
|
|
|
function calendar$1 (time, formats) {
|
|
// We want to compare the start of today, vs this.
|
|
// Getting start-of-today depends on whether we're local/utc/offset or not.
|
|
var now = time || createLocal(),
|
|
sod = cloneWithOffset(now, this).startOf('day'),
|
|
format = hooks.calendarFormat(this, sod) || 'sameElse';
|
|
|
|
var output = formats && (isFunction(formats[format]) ? formats[format].call(this, now) : formats[format]);
|
|
|
|
return this.format(output || this.localeData().calendar(format, this, createLocal(now)));
|
|
}
|
|
|
|
function clone () {
|
|
return new Moment(this);
|
|
}
|
|
|
|
function isAfter (input, units) {
|
|
var localInput = isMoment(input) ? input : createLocal(input);
|
|
if (!(this.isValid() && localInput.isValid())) {
|
|
return false;
|
|
}
|
|
units = normalizeUnits(units) || 'millisecond';
|
|
if (units === 'millisecond') {
|
|
return this.valueOf() > localInput.valueOf();
|
|
} else {
|
|
return localInput.valueOf() < this.clone().startOf(units).valueOf();
|
|
}
|
|
}
|
|
|
|
function isBefore (input, units) {
|
|
var localInput = isMoment(input) ? input : createLocal(input);
|
|
if (!(this.isValid() && localInput.isValid())) {
|
|
return false;
|
|
}
|
|
units = normalizeUnits(units) || 'millisecond';
|
|
if (units === 'millisecond') {
|
|
return this.valueOf() < localInput.valueOf();
|
|
} else {
|
|
return this.clone().endOf(units).valueOf() < localInput.valueOf();
|
|
}
|
|
}
|
|
|
|
function isBetween (from, to, units, inclusivity) {
|
|
var localFrom = isMoment(from) ? from : createLocal(from),
|
|
localTo = isMoment(to) ? to : createLocal(to);
|
|
if (!(this.isValid() && localFrom.isValid() && localTo.isValid())) {
|
|
return false;
|
|
}
|
|
inclusivity = inclusivity || '()';
|
|
return (inclusivity[0] === '(' ? this.isAfter(localFrom, units) : !this.isBefore(localFrom, units)) &&
|
|
(inclusivity[1] === ')' ? this.isBefore(localTo, units) : !this.isAfter(localTo, units));
|
|
}
|
|
|
|
function isSame (input, units) {
|
|
var localInput = isMoment(input) ? input : createLocal(input),
|
|
inputMs;
|
|
if (!(this.isValid() && localInput.isValid())) {
|
|
return false;
|
|
}
|
|
units = normalizeUnits(units) || 'millisecond';
|
|
if (units === 'millisecond') {
|
|
return this.valueOf() === localInput.valueOf();
|
|
} else {
|
|
inputMs = localInput.valueOf();
|
|
return this.clone().startOf(units).valueOf() <= inputMs && inputMs <= this.clone().endOf(units).valueOf();
|
|
}
|
|
}
|
|
|
|
function isSameOrAfter (input, units) {
|
|
return this.isSame(input, units) || this.isAfter(input, units);
|
|
}
|
|
|
|
function isSameOrBefore (input, units) {
|
|
return this.isSame(input, units) || this.isBefore(input, units);
|
|
}
|
|
|
|
function diff (input, units, asFloat) {
|
|
var that,
|
|
zoneDelta,
|
|
output;
|
|
|
|
if (!this.isValid()) {
|
|
return NaN;
|
|
}
|
|
|
|
that = cloneWithOffset(input, this);
|
|
|
|
if (!that.isValid()) {
|
|
return NaN;
|
|
}
|
|
|
|
zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4;
|
|
|
|
units = normalizeUnits(units);
|
|
|
|
switch (units) {
|
|
case 'year': output = monthDiff(this, that) / 12; break;
|
|
case 'month': output = monthDiff(this, that); break;
|
|
case 'quarter': output = monthDiff(this, that) / 3; break;
|
|
case 'second': output = (this - that) / 1e3; break; // 1000
|
|
case 'minute': output = (this - that) / 6e4; break; // 1000 * 60
|
|
case 'hour': output = (this - that) / 36e5; break; // 1000 * 60 * 60
|
|
case 'day': output = (this - that - zoneDelta) / 864e5; break; // 1000 * 60 * 60 * 24, negate dst
|
|
case 'week': output = (this - that - zoneDelta) / 6048e5; break; // 1000 * 60 * 60 * 24 * 7, negate dst
|
|
default: output = this - that;
|
|
}
|
|
|
|
return asFloat ? output : absFloor(output);
|
|
}
|
|
|
|
function monthDiff (a, b) {
|
|
// difference in months
|
|
var wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month()),
|
|
// b is in (anchor - 1 month, anchor + 1 month)
|
|
anchor = a.clone().add(wholeMonthDiff, 'months'),
|
|
anchor2, adjust;
|
|
|
|
if (b - anchor < 0) {
|
|
anchor2 = a.clone().add(wholeMonthDiff - 1, 'months');
|
|
// linear across the month
|
|
adjust = (b - anchor) / (anchor - anchor2);
|
|
} else {
|
|
anchor2 = a.clone().add(wholeMonthDiff + 1, 'months');
|
|
// linear across the month
|
|
adjust = (b - anchor) / (anchor2 - anchor);
|
|
}
|
|
|
|
//check for negative zero, return zero if negative zero
|
|
return -(wholeMonthDiff + adjust) || 0;
|
|
}
|
|
|
|
hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ';
|
|
hooks.defaultFormatUtc = 'YYYY-MM-DDTHH:mm:ss[Z]';
|
|
|
|
function toString () {
|
|
return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ');
|
|
}
|
|
|
|
function toISOString(keepOffset) {
|
|
if (!this.isValid()) {
|
|
return null;
|
|
}
|
|
var utc = keepOffset !== true;
|
|
var m = utc ? this.clone().utc() : this;
|
|
if (m.year() < 0 || m.year() > 9999) {
|
|
return formatMoment(m, utc ? 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]' : 'YYYYYY-MM-DD[T]HH:mm:ss.SSSZ');
|
|
}
|
|
if (isFunction(Date.prototype.toISOString)) {
|
|
// native implementation is ~50x faster, use it when we can
|
|
if (utc) {
|
|
return this.toDate().toISOString();
|
|
} else {
|
|
return new Date(this.valueOf() + this.utcOffset() * 60 * 1000).toISOString().replace('Z', formatMoment(m, 'Z'));
|
|
}
|
|
}
|
|
return formatMoment(m, utc ? 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]' : 'YYYY-MM-DD[T]HH:mm:ss.SSSZ');
|
|
}
|
|
|
|
/**
|
|
* Return a human readable representation of a moment that can
|
|
* also be evaluated to get a new moment which is the same
|
|
*
|
|
* @link https://nodejs.org/dist/latest/docs/api/util.html#util_custom_inspect_function_on_objects
|
|
*/
|
|
function inspect () {
|
|
if (!this.isValid()) {
|
|
return 'moment.invalid(/* ' + this._i + ' */)';
|
|
}
|
|
var func = 'moment';
|
|
var zone = '';
|
|
if (!this.isLocal()) {
|
|
func = this.utcOffset() === 0 ? 'moment.utc' : 'moment.parseZone';
|
|
zone = 'Z';
|
|
}
|
|
var prefix = '[' + func + '("]';
|
|
var year = (0 <= this.year() && this.year() <= 9999) ? 'YYYY' : 'YYYYYY';
|
|
var datetime = '-MM-DD[T]HH:mm:ss.SSS';
|
|
var suffix = zone + '[")]';
|
|
|
|
return this.format(prefix + year + datetime + suffix);
|
|
}
|
|
|
|
function format (inputString) {
|
|
if (!inputString) {
|
|
inputString = this.isUtc() ? hooks.defaultFormatUtc : hooks.defaultFormat;
|
|
}
|
|
var output = formatMoment(this, inputString);
|
|
return this.localeData().postformat(output);
|
|
}
|
|
|
|
function from (time, withoutSuffix) {
|
|
if (this.isValid() &&
|
|
((isMoment(time) && time.isValid()) ||
|
|
createLocal(time).isValid())) {
|
|
return createDuration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix);
|
|
} else {
|
|
return this.localeData().invalidDate();
|
|
}
|
|
}
|
|
|
|
function fromNow (withoutSuffix) {
|
|
return this.from(createLocal(), withoutSuffix);
|
|
}
|
|
|
|
function to (time, withoutSuffix) {
|
|
if (this.isValid() &&
|
|
((isMoment(time) && time.isValid()) ||
|
|
createLocal(time).isValid())) {
|
|
return createDuration({from: this, to: time}).locale(this.locale()).humanize(!withoutSuffix);
|
|
} else {
|
|
return this.localeData().invalidDate();
|
|
}
|
|
}
|
|
|
|
function toNow (withoutSuffix) {
|
|
return this.to(createLocal(), withoutSuffix);
|
|
}
|
|
|
|
// If passed a locale key, it will set the locale for this
|
|
// instance. Otherwise, it will return the locale configuration
|
|
// variables for this instance.
|
|
function locale (key) {
|
|
var newLocaleData;
|
|
|
|
if (key === undefined) {
|
|
return this._locale._abbr;
|
|
} else {
|
|
newLocaleData = getLocale(key);
|
|
if (newLocaleData != null) {
|
|
this._locale = newLocaleData;
|
|
}
|
|
return this;
|
|
}
|
|
}
|
|
|
|
var lang = deprecate(
|
|
'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.',
|
|
function (key) {
|
|
if (key === undefined) {
|
|
return this.localeData();
|
|
} else {
|
|
return this.locale(key);
|
|
}
|
|
}
|
|
);
|
|
|
|
function localeData () {
|
|
return this._locale;
|
|
}
|
|
|
|
var MS_PER_SECOND = 1000;
|
|
var MS_PER_MINUTE = 60 * MS_PER_SECOND;
|
|
var MS_PER_HOUR = 60 * MS_PER_MINUTE;
|
|
var MS_PER_400_YEARS = (365 * 400 + 97) * 24 * MS_PER_HOUR;
|
|
|
|
// actual modulo - handles negative numbers (for dates before 1970):
|
|
function mod$1(dividend, divisor) {
|
|
return (dividend % divisor + divisor) % divisor;
|
|
}
|
|
|
|
function localStartOfDate(y, m, d) {
|
|
// the date constructor remaps years 0-99 to 1900-1999
|
|
if (y < 100 && y >= 0) {
|
|
// preserve leap years using a full 400 year cycle, then reset
|
|
return new Date(y + 400, m, d) - MS_PER_400_YEARS;
|
|
} else {
|
|
return new Date(y, m, d).valueOf();
|
|
}
|
|
}
|
|
|
|
function utcStartOfDate(y, m, d) {
|
|
// Date.UTC remaps years 0-99 to 1900-1999
|
|
if (y < 100 && y >= 0) {
|
|
// preserve leap years using a full 400 year cycle, then reset
|
|
return Date.UTC(y + 400, m, d) - MS_PER_400_YEARS;
|
|
} else {
|
|
return Date.UTC(y, m, d);
|
|
}
|
|
}
|
|
|
|
function startOf (units) {
|
|
var time;
|
|
units = normalizeUnits(units);
|
|
if (units === undefined || units === 'millisecond' || !this.isValid()) {
|
|
return this;
|
|
}
|
|
|
|
var startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate;
|
|
|
|
switch (units) {
|
|
case 'year':
|
|
time = startOfDate(this.year(), 0, 1);
|
|
break;
|
|
case 'quarter':
|
|
time = startOfDate(this.year(), this.month() - this.month() % 3, 1);
|
|
break;
|
|
case 'month':
|
|
time = startOfDate(this.year(), this.month(), 1);
|
|
break;
|
|
case 'week':
|
|
time = startOfDate(this.year(), this.month(), this.date() - this.weekday());
|
|
break;
|
|
case 'isoWeek':
|
|
time = startOfDate(this.year(), this.month(), this.date() - (this.isoWeekday() - 1));
|
|
break;
|
|
case 'day':
|
|
case 'date':
|
|
time = startOfDate(this.year(), this.month(), this.date());
|
|
break;
|
|
case 'hour':
|
|
time = this._d.valueOf();
|
|
time -= mod$1(time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE), MS_PER_HOUR);
|
|
break;
|
|
case 'minute':
|
|
time = this._d.valueOf();
|
|
time -= mod$1(time, MS_PER_MINUTE);
|
|
break;
|
|
case 'second':
|
|
time = this._d.valueOf();
|
|
time -= mod$1(time, MS_PER_SECOND);
|
|
break;
|
|
}
|
|
|
|
this._d.setTime(time);
|
|
hooks.updateOffset(this, true);
|
|
return this;
|
|
}
|
|
|
|
function endOf (units) {
|
|
var time;
|
|
units = normalizeUnits(units);
|
|
if (units === undefined || units === 'millisecond' || !this.isValid()) {
|
|
return this;
|
|
}
|
|
|
|
var startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate;
|
|
|
|
switch (units) {
|
|
case 'year':
|
|
time = startOfDate(this.year() + 1, 0, 1) - 1;
|
|
break;
|
|
case 'quarter':
|
|
time = startOfDate(this.year(), this.month() - this.month() % 3 + 3, 1) - 1;
|
|
break;
|
|
case 'month':
|
|
time = startOfDate(this.year(), this.month() + 1, 1) - 1;
|
|
break;
|
|
case 'week':
|
|
time = startOfDate(this.year(), this.month(), this.date() - this.weekday() + 7) - 1;
|
|
break;
|
|
case 'isoWeek':
|
|
time = startOfDate(this.year(), this.month(), this.date() - (this.isoWeekday() - 1) + 7) - 1;
|
|
break;
|
|
case 'day':
|
|
case 'date':
|
|
time = startOfDate(this.year(), this.month(), this.date() + 1) - 1;
|
|
break;
|
|
case 'hour':
|
|
time = this._d.valueOf();
|
|
time += MS_PER_HOUR - mod$1(time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE), MS_PER_HOUR) - 1;
|
|
break;
|
|
case 'minute':
|
|
time = this._d.valueOf();
|
|
time += MS_PER_MINUTE - mod$1(time, MS_PER_MINUTE) - 1;
|
|
break;
|
|
case 'second':
|
|
time = this._d.valueOf();
|
|
time += MS_PER_SECOND - mod$1(time, MS_PER_SECOND) - 1;
|
|
break;
|
|
}
|
|
|
|
this._d.setTime(time);
|
|
hooks.updateOffset(this, true);
|
|
return this;
|
|
}
|
|
|
|
function valueOf () {
|
|
return this._d.valueOf() - ((this._offset || 0) * 60000);
|
|
}
|
|
|
|
function unix () {
|
|
return Math.floor(this.valueOf() / 1000);
|
|
}
|
|
|
|
function toDate () {
|
|
return new Date(this.valueOf());
|
|
}
|
|
|
|
function toArray () {
|
|
var m = this;
|
|
return [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second(), m.millisecond()];
|
|
}
|
|
|
|
function toObject () {
|
|
var m = this;
|
|
return {
|
|
years: m.year(),
|
|
months: m.month(),
|
|
date: m.date(),
|
|
hours: m.hours(),
|
|
minutes: m.minutes(),
|
|
seconds: m.seconds(),
|
|
milliseconds: m.milliseconds()
|
|
};
|
|
}
|
|
|
|
function toJSON () {
|
|
// new Date(NaN).toJSON() === null
|
|
return this.isValid() ? this.toISOString() : null;
|
|
}
|
|
|
|
function isValid$2 () {
|
|
return isValid(this);
|
|
}
|
|
|
|
function parsingFlags () {
|
|
return extend({}, getParsingFlags(this));
|
|
}
|
|
|
|
function invalidAt () {
|
|
return getParsingFlags(this).overflow;
|
|
}
|
|
|
|
function creationData() {
|
|
return {
|
|
input: this._i,
|
|
format: this._f,
|
|
locale: this._locale,
|
|
isUTC: this._isUTC,
|
|
strict: this._strict
|
|
};
|
|
}
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken(0, ['gg', 2], 0, function () {
|
|
return this.weekYear() % 100;
|
|
});
|
|
|
|
addFormatToken(0, ['GG', 2], 0, function () {
|
|
return this.isoWeekYear() % 100;
|
|
});
|
|
|
|
function addWeekYearFormatToken (token, getter) {
|
|
addFormatToken(0, [token, token.length], 0, getter);
|
|
}
|
|
|
|
addWeekYearFormatToken('gggg', 'weekYear');
|
|
addWeekYearFormatToken('ggggg', 'weekYear');
|
|
addWeekYearFormatToken('GGGG', 'isoWeekYear');
|
|
addWeekYearFormatToken('GGGGG', 'isoWeekYear');
|
|
|
|
// ALIASES
|
|
|
|
addUnitAlias('weekYear', 'gg');
|
|
addUnitAlias('isoWeekYear', 'GG');
|
|
|
|
// PRIORITY
|
|
|
|
addUnitPriority('weekYear', 1);
|
|
addUnitPriority('isoWeekYear', 1);
|
|
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('G', matchSigned);
|
|
addRegexToken('g', matchSigned);
|
|
addRegexToken('GG', match1to2, match2);
|
|
addRegexToken('gg', match1to2, match2);
|
|
addRegexToken('GGGG', match1to4, match4);
|
|
addRegexToken('gggg', match1to4, match4);
|
|
addRegexToken('GGGGG', match1to6, match6);
|
|
addRegexToken('ggggg', match1to6, match6);
|
|
|
|
addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function (input, week, config, token) {
|
|
week[token.substr(0, 2)] = toInt(input);
|
|
});
|
|
|
|
addWeekParseToken(['gg', 'GG'], function (input, week, config, token) {
|
|
week[token] = hooks.parseTwoDigitYear(input);
|
|
});
|
|
|
|
// MOMENTS
|
|
|
|
function getSetWeekYear (input) {
|
|
return getSetWeekYearHelper.call(this,
|
|
input,
|
|
this.week(),
|
|
this.weekday(),
|
|
this.localeData()._week.dow,
|
|
this.localeData()._week.doy);
|
|
}
|
|
|
|
function getSetISOWeekYear (input) {
|
|
return getSetWeekYearHelper.call(this,
|
|
input, this.isoWeek(), this.isoWeekday(), 1, 4);
|
|
}
|
|
|
|
function getISOWeeksInYear () {
|
|
return weeksInYear(this.year(), 1, 4);
|
|
}
|
|
|
|
function getWeeksInYear () {
|
|
var weekInfo = this.localeData()._week;
|
|
return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy);
|
|
}
|
|
|
|
function getSetWeekYearHelper(input, week, weekday, dow, doy) {
|
|
var weeksTarget;
|
|
if (input == null) {
|
|
return weekOfYear(this, dow, doy).year;
|
|
} else {
|
|
weeksTarget = weeksInYear(input, dow, doy);
|
|
if (week > weeksTarget) {
|
|
week = weeksTarget;
|
|
}
|
|
return setWeekAll.call(this, input, week, weekday, dow, doy);
|
|
}
|
|
}
|
|
|
|
function setWeekAll(weekYear, week, weekday, dow, doy) {
|
|
var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy),
|
|
date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear);
|
|
|
|
this.year(date.getUTCFullYear());
|
|
this.month(date.getUTCMonth());
|
|
this.date(date.getUTCDate());
|
|
return this;
|
|
}
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken('Q', 0, 'Qo', 'quarter');
|
|
|
|
// ALIASES
|
|
|
|
addUnitAlias('quarter', 'Q');
|
|
|
|
// PRIORITY
|
|
|
|
addUnitPriority('quarter', 7);
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('Q', match1);
|
|
addParseToken('Q', function (input, array) {
|
|
array[MONTH] = (toInt(input) - 1) * 3;
|
|
});
|
|
|
|
// MOMENTS
|
|
|
|
function getSetQuarter (input) {
|
|
return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3);
|
|
}
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken('D', ['DD', 2], 'Do', 'date');
|
|
|
|
// ALIASES
|
|
|
|
addUnitAlias('date', 'D');
|
|
|
|
// PRIORITY
|
|
addUnitPriority('date', 9);
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('D', match1to2);
|
|
addRegexToken('DD', match1to2, match2);
|
|
addRegexToken('Do', function (isStrict, locale) {
|
|
// TODO: Remove "ordinalParse" fallback in next major release.
|
|
return isStrict ?
|
|
(locale._dayOfMonthOrdinalParse || locale._ordinalParse) :
|
|
locale._dayOfMonthOrdinalParseLenient;
|
|
});
|
|
|
|
addParseToken(['D', 'DD'], DATE);
|
|
addParseToken('Do', function (input, array) {
|
|
array[DATE] = toInt(input.match(match1to2)[0]);
|
|
});
|
|
|
|
// MOMENTS
|
|
|
|
var getSetDayOfMonth = makeGetSet('Date', true);
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear');
|
|
|
|
// ALIASES
|
|
|
|
addUnitAlias('dayOfYear', 'DDD');
|
|
|
|
// PRIORITY
|
|
addUnitPriority('dayOfYear', 4);
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('DDD', match1to3);
|
|
addRegexToken('DDDD', match3);
|
|
addParseToken(['DDD', 'DDDD'], function (input, array, config) {
|
|
config._dayOfYear = toInt(input);
|
|
});
|
|
|
|
// HELPERS
|
|
|
|
// MOMENTS
|
|
|
|
function getSetDayOfYear (input) {
|
|
var dayOfYear = Math.round((this.clone().startOf('day') - this.clone().startOf('year')) / 864e5) + 1;
|
|
return input == null ? dayOfYear : this.add((input - dayOfYear), 'd');
|
|
}
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken('m', ['mm', 2], 0, 'minute');
|
|
|
|
// ALIASES
|
|
|
|
addUnitAlias('minute', 'm');
|
|
|
|
// PRIORITY
|
|
|
|
addUnitPriority('minute', 14);
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('m', match1to2);
|
|
addRegexToken('mm', match1to2, match2);
|
|
addParseToken(['m', 'mm'], MINUTE);
|
|
|
|
// MOMENTS
|
|
|
|
var getSetMinute = makeGetSet('Minutes', false);
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken('s', ['ss', 2], 0, 'second');
|
|
|
|
// ALIASES
|
|
|
|
addUnitAlias('second', 's');
|
|
|
|
// PRIORITY
|
|
|
|
addUnitPriority('second', 15);
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('s', match1to2);
|
|
addRegexToken('ss', match1to2, match2);
|
|
addParseToken(['s', 'ss'], SECOND);
|
|
|
|
// MOMENTS
|
|
|
|
var getSetSecond = makeGetSet('Seconds', false);
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken('S', 0, 0, function () {
|
|
return ~~(this.millisecond() / 100);
|
|
});
|
|
|
|
addFormatToken(0, ['SS', 2], 0, function () {
|
|
return ~~(this.millisecond() / 10);
|
|
});
|
|
|
|
addFormatToken(0, ['SSS', 3], 0, 'millisecond');
|
|
addFormatToken(0, ['SSSS', 4], 0, function () {
|
|
return this.millisecond() * 10;
|
|
});
|
|
addFormatToken(0, ['SSSSS', 5], 0, function () {
|
|
return this.millisecond() * 100;
|
|
});
|
|
addFormatToken(0, ['SSSSSS', 6], 0, function () {
|
|
return this.millisecond() * 1000;
|
|
});
|
|
addFormatToken(0, ['SSSSSSS', 7], 0, function () {
|
|
return this.millisecond() * 10000;
|
|
});
|
|
addFormatToken(0, ['SSSSSSSS', 8], 0, function () {
|
|
return this.millisecond() * 100000;
|
|
});
|
|
addFormatToken(0, ['SSSSSSSSS', 9], 0, function () {
|
|
return this.millisecond() * 1000000;
|
|
});
|
|
|
|
|
|
// ALIASES
|
|
|
|
addUnitAlias('millisecond', 'ms');
|
|
|
|
// PRIORITY
|
|
|
|
addUnitPriority('millisecond', 16);
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('S', match1to3, match1);
|
|
addRegexToken('SS', match1to3, match2);
|
|
addRegexToken('SSS', match1to3, match3);
|
|
|
|
var token;
|
|
for (token = 'SSSS'; token.length <= 9; token += 'S') {
|
|
addRegexToken(token, matchUnsigned);
|
|
}
|
|
|
|
function parseMs(input, array) {
|
|
array[MILLISECOND] = toInt(('0.' + input) * 1000);
|
|
}
|
|
|
|
for (token = 'S'; token.length <= 9; token += 'S') {
|
|
addParseToken(token, parseMs);
|
|
}
|
|
// MOMENTS
|
|
|
|
var getSetMillisecond = makeGetSet('Milliseconds', false);
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken('z', 0, 0, 'zoneAbbr');
|
|
addFormatToken('zz', 0, 0, 'zoneName');
|
|
|
|
// MOMENTS
|
|
|
|
function getZoneAbbr () {
|
|
return this._isUTC ? 'UTC' : '';
|
|
}
|
|
|
|
function getZoneName () {
|
|
return this._isUTC ? 'Coordinated Universal Time' : '';
|
|
}
|
|
|
|
var proto = Moment.prototype;
|
|
|
|
proto.add = add;
|
|
proto.calendar = calendar$1;
|
|
proto.clone = clone;
|
|
proto.diff = diff;
|
|
proto.endOf = endOf;
|
|
proto.format = format;
|
|
proto.from = from;
|
|
proto.fromNow = fromNow;
|
|
proto.to = to;
|
|
proto.toNow = toNow;
|
|
proto.get = stringGet;
|
|
proto.invalidAt = invalidAt;
|
|
proto.isAfter = isAfter;
|
|
proto.isBefore = isBefore;
|
|
proto.isBetween = isBetween;
|
|
proto.isSame = isSame;
|
|
proto.isSameOrAfter = isSameOrAfter;
|
|
proto.isSameOrBefore = isSameOrBefore;
|
|
proto.isValid = isValid$2;
|
|
proto.lang = lang;
|
|
proto.locale = locale;
|
|
proto.localeData = localeData;
|
|
proto.max = prototypeMax;
|
|
proto.min = prototypeMin;
|
|
proto.parsingFlags = parsingFlags;
|
|
proto.set = stringSet;
|
|
proto.startOf = startOf;
|
|
proto.subtract = subtract;
|
|
proto.toArray = toArray;
|
|
proto.toObject = toObject;
|
|
proto.toDate = toDate;
|
|
proto.toISOString = toISOString;
|
|
proto.inspect = inspect;
|
|
proto.toJSON = toJSON;
|
|
proto.toString = toString;
|
|
proto.unix = unix;
|
|
proto.valueOf = valueOf;
|
|
proto.creationData = creationData;
|
|
proto.year = getSetYear;
|
|
proto.isLeapYear = getIsLeapYear;
|
|
proto.weekYear = getSetWeekYear;
|
|
proto.isoWeekYear = getSetISOWeekYear;
|
|
proto.quarter = proto.quarters = getSetQuarter;
|
|
proto.month = getSetMonth;
|
|
proto.daysInMonth = getDaysInMonth;
|
|
proto.week = proto.weeks = getSetWeek;
|
|
proto.isoWeek = proto.isoWeeks = getSetISOWeek;
|
|
proto.weeksInYear = getWeeksInYear;
|
|
proto.isoWeeksInYear = getISOWeeksInYear;
|
|
proto.date = getSetDayOfMonth;
|
|
proto.day = proto.days = getSetDayOfWeek;
|
|
proto.weekday = getSetLocaleDayOfWeek;
|
|
proto.isoWeekday = getSetISODayOfWeek;
|
|
proto.dayOfYear = getSetDayOfYear;
|
|
proto.hour = proto.hours = getSetHour;
|
|
proto.minute = proto.minutes = getSetMinute;
|
|
proto.second = proto.seconds = getSetSecond;
|
|
proto.millisecond = proto.milliseconds = getSetMillisecond;
|
|
proto.utcOffset = getSetOffset;
|
|
proto.utc = setOffsetToUTC;
|
|
proto.local = setOffsetToLocal;
|
|
proto.parseZone = setOffsetToParsedOffset;
|
|
proto.hasAlignedHourOffset = hasAlignedHourOffset;
|
|
proto.isDST = isDaylightSavingTime;
|
|
proto.isLocal = isLocal;
|
|
proto.isUtcOffset = isUtcOffset;
|
|
proto.isUtc = isUtc;
|
|
proto.isUTC = isUtc;
|
|
proto.zoneAbbr = getZoneAbbr;
|
|
proto.zoneName = getZoneName;
|
|
proto.dates = deprecate('dates accessor is deprecated. Use date instead.', getSetDayOfMonth);
|
|
proto.months = deprecate('months accessor is deprecated. Use month instead', getSetMonth);
|
|
proto.years = deprecate('years accessor is deprecated. Use year instead', getSetYear);
|
|
proto.zone = deprecate('moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/', getSetZone);
|
|
proto.isDSTShifted = deprecate('isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information', isDaylightSavingTimeShifted);
|
|
|
|
function createUnix (input) {
|
|
return createLocal(input * 1000);
|
|
}
|
|
|
|
function createInZone () {
|
|
return createLocal.apply(null, arguments).parseZone();
|
|
}
|
|
|
|
function preParsePostFormat (string) {
|
|
return string;
|
|
}
|
|
|
|
var proto$1 = Locale.prototype;
|
|
|
|
proto$1.calendar = calendar;
|
|
proto$1.longDateFormat = longDateFormat;
|
|
proto$1.invalidDate = invalidDate;
|
|
proto$1.ordinal = ordinal;
|
|
proto$1.preparse = preParsePostFormat;
|
|
proto$1.postformat = preParsePostFormat;
|
|
proto$1.relativeTime = relativeTime;
|
|
proto$1.pastFuture = pastFuture;
|
|
proto$1.set = set;
|
|
|
|
proto$1.months = localeMonths;
|
|
proto$1.monthsShort = localeMonthsShort;
|
|
proto$1.monthsParse = localeMonthsParse;
|
|
proto$1.monthsRegex = monthsRegex;
|
|
proto$1.monthsShortRegex = monthsShortRegex;
|
|
proto$1.week = localeWeek;
|
|
proto$1.firstDayOfYear = localeFirstDayOfYear;
|
|
proto$1.firstDayOfWeek = localeFirstDayOfWeek;
|
|
|
|
proto$1.weekdays = localeWeekdays;
|
|
proto$1.weekdaysMin = localeWeekdaysMin;
|
|
proto$1.weekdaysShort = localeWeekdaysShort;
|
|
proto$1.weekdaysParse = localeWeekdaysParse;
|
|
|
|
proto$1.weekdaysRegex = weekdaysRegex;
|
|
proto$1.weekdaysShortRegex = weekdaysShortRegex;
|
|
proto$1.weekdaysMinRegex = weekdaysMinRegex;
|
|
|
|
proto$1.isPM = localeIsPM;
|
|
proto$1.meridiem = localeMeridiem;
|
|
|
|
function get$1 (format, index, field, setter) {
|
|
var locale = getLocale();
|
|
var utc = createUTC().set(setter, index);
|
|
return locale[field](utc, format);
|
|
}
|
|
|
|
function listMonthsImpl (format, index, field) {
|
|
if (isNumber(format)) {
|
|
index = format;
|
|
format = undefined;
|
|
}
|
|
|
|
format = format || '';
|
|
|
|
if (index != null) {
|
|
return get$1(format, index, field, 'month');
|
|
}
|
|
|
|
var i;
|
|
var out = [];
|
|
for (i = 0; i < 12; i++) {
|
|
out[i] = get$1(format, i, field, 'month');
|
|
}
|
|
return out;
|
|
}
|
|
|
|
// ()
|
|
// (5)
|
|
// (fmt, 5)
|
|
// (fmt)
|
|
// (true)
|
|
// (true, 5)
|
|
// (true, fmt, 5)
|
|
// (true, fmt)
|
|
function listWeekdaysImpl (localeSorted, format, index, field) {
|
|
if (typeof localeSorted === 'boolean') {
|
|
if (isNumber(format)) {
|
|
index = format;
|
|
format = undefined;
|
|
}
|
|
|
|
format = format || '';
|
|
} else {
|
|
format = localeSorted;
|
|
index = format;
|
|
localeSorted = false;
|
|
|
|
if (isNumber(format)) {
|
|
index = format;
|
|
format = undefined;
|
|
}
|
|
|
|
format = format || '';
|
|
}
|
|
|
|
var locale = getLocale(),
|
|
shift = localeSorted ? locale._week.dow : 0;
|
|
|
|
if (index != null) {
|
|
return get$1(format, (index + shift) % 7, field, 'day');
|
|
}
|
|
|
|
var i;
|
|
var out = [];
|
|
for (i = 0; i < 7; i++) {
|
|
out[i] = get$1(format, (i + shift) % 7, field, 'day');
|
|
}
|
|
return out;
|
|
}
|
|
|
|
function listMonths (format, index) {
|
|
return listMonthsImpl(format, index, 'months');
|
|
}
|
|
|
|
function listMonthsShort (format, index) {
|
|
return listMonthsImpl(format, index, 'monthsShort');
|
|
}
|
|
|
|
function listWeekdays (localeSorted, format, index) {
|
|
return listWeekdaysImpl(localeSorted, format, index, 'weekdays');
|
|
}
|
|
|
|
function listWeekdaysShort (localeSorted, format, index) {
|
|
return listWeekdaysImpl(localeSorted, format, index, 'weekdaysShort');
|
|
}
|
|
|
|
function listWeekdaysMin (localeSorted, format, index) {
|
|
return listWeekdaysImpl(localeSorted, format, index, 'weekdaysMin');
|
|
}
|
|
|
|
getSetGlobalLocale('en', {
|
|
dayOfMonthOrdinalParse: /\d{1,2}(th|st|nd|rd)/,
|
|
ordinal : function (number) {
|
|
var b = number % 10,
|
|
output = (toInt(number % 100 / 10) === 1) ? 'th' :
|
|
(b === 1) ? 'st' :
|
|
(b === 2) ? 'nd' :
|
|
(b === 3) ? 'rd' : 'th';
|
|
return number + output;
|
|
}
|
|
});
|
|
|
|
// Side effect imports
|
|
|
|
hooks.lang = deprecate('moment.lang is deprecated. Use moment.locale instead.', getSetGlobalLocale);
|
|
hooks.langData = deprecate('moment.langData is deprecated. Use moment.localeData instead.', getLocale);
|
|
|
|
var mathAbs = Math.abs;
|
|
|
|
function abs () {
|
|
var data = this._data;
|
|
|
|
this._milliseconds = mathAbs(this._milliseconds);
|
|
this._days = mathAbs(this._days);
|
|
this._months = mathAbs(this._months);
|
|
|
|
data.milliseconds = mathAbs(data.milliseconds);
|
|
data.seconds = mathAbs(data.seconds);
|
|
data.minutes = mathAbs(data.minutes);
|
|
data.hours = mathAbs(data.hours);
|
|
data.months = mathAbs(data.months);
|
|
data.years = mathAbs(data.years);
|
|
|
|
return this;
|
|
}
|
|
|
|
function addSubtract$1 (duration, input, value, direction) {
|
|
var other = createDuration(input, value);
|
|
|
|
duration._milliseconds += direction * other._milliseconds;
|
|
duration._days += direction * other._days;
|
|
duration._months += direction * other._months;
|
|
|
|
return duration._bubble();
|
|
}
|
|
|
|
// supports only 2.0-style add(1, 's') or add(duration)
|
|
function add$1 (input, value) {
|
|
return addSubtract$1(this, input, value, 1);
|
|
}
|
|
|
|
// supports only 2.0-style subtract(1, 's') or subtract(duration)
|
|
function subtract$1 (input, value) {
|
|
return addSubtract$1(this, input, value, -1);
|
|
}
|
|
|
|
function absCeil (number) {
|
|
if (number < 0) {
|
|
return Math.floor(number);
|
|
} else {
|
|
return Math.ceil(number);
|
|
}
|
|
}
|
|
|
|
function bubble () {
|
|
var milliseconds = this._milliseconds;
|
|
var days = this._days;
|
|
var months = this._months;
|
|
var data = this._data;
|
|
var seconds, minutes, hours, years, monthsFromDays;
|
|
|
|
// if we have a mix of positive and negative values, bubble down first
|
|
// check: https://github.com/moment/moment/issues/2166
|
|
if (!((milliseconds >= 0 && days >= 0 && months >= 0) ||
|
|
(milliseconds <= 0 && days <= 0 && months <= 0))) {
|
|
milliseconds += absCeil(monthsToDays(months) + days) * 864e5;
|
|
days = 0;
|
|
months = 0;
|
|
}
|
|
|
|
// The following code bubbles up values, see the tests for
|
|
// examples of what that means.
|
|
data.milliseconds = milliseconds % 1000;
|
|
|
|
seconds = absFloor(milliseconds / 1000);
|
|
data.seconds = seconds % 60;
|
|
|
|
minutes = absFloor(seconds / 60);
|
|
data.minutes = minutes % 60;
|
|
|
|
hours = absFloor(minutes / 60);
|
|
data.hours = hours % 24;
|
|
|
|
days += absFloor(hours / 24);
|
|
|
|
// convert days to months
|
|
monthsFromDays = absFloor(daysToMonths(days));
|
|
months += monthsFromDays;
|
|
days -= absCeil(monthsToDays(monthsFromDays));
|
|
|
|
// 12 months -> 1 year
|
|
years = absFloor(months / 12);
|
|
months %= 12;
|
|
|
|
data.days = days;
|
|
data.months = months;
|
|
data.years = years;
|
|
|
|
return this;
|
|
}
|
|
|
|
function daysToMonths (days) {
|
|
// 400 years have 146097 days (taking into account leap year rules)
|
|
// 400 years have 12 months === 4800
|
|
return days * 4800 / 146097;
|
|
}
|
|
|
|
function monthsToDays (months) {
|
|
// the reverse of daysToMonths
|
|
return months * 146097 / 4800;
|
|
}
|
|
|
|
function as (units) {
|
|
if (!this.isValid()) {
|
|
return NaN;
|
|
}
|
|
var days;
|
|
var months;
|
|
var milliseconds = this._milliseconds;
|
|
|
|
units = normalizeUnits(units);
|
|
|
|
if (units === 'month' || units === 'quarter' || units === 'year') {
|
|
days = this._days + milliseconds / 864e5;
|
|
months = this._months + daysToMonths(days);
|
|
switch (units) {
|
|
case 'month': return months;
|
|
case 'quarter': return months / 3;
|
|
case 'year': return months / 12;
|
|
}
|
|
} else {
|
|
// handle milliseconds separately because of floating point math errors (issue #1867)
|
|
days = this._days + Math.round(monthsToDays(this._months));
|
|
switch (units) {
|
|
case 'week' : return days / 7 + milliseconds / 6048e5;
|
|
case 'day' : return days + milliseconds / 864e5;
|
|
case 'hour' : return days * 24 + milliseconds / 36e5;
|
|
case 'minute' : return days * 1440 + milliseconds / 6e4;
|
|
case 'second' : return days * 86400 + milliseconds / 1000;
|
|
// Math.floor prevents floating point math errors here
|
|
case 'millisecond': return Math.floor(days * 864e5) + milliseconds;
|
|
default: throw new Error('Unknown unit ' + units);
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: Use this.as('ms')?
|
|
function valueOf$1 () {
|
|
if (!this.isValid()) {
|
|
return NaN;
|
|
}
|
|
return (
|
|
this._milliseconds +
|
|
this._days * 864e5 +
|
|
(this._months % 12) * 2592e6 +
|
|
toInt(this._months / 12) * 31536e6
|
|
);
|
|
}
|
|
|
|
function makeAs (alias) {
|
|
return function () {
|
|
return this.as(alias);
|
|
};
|
|
}
|
|
|
|
var asMilliseconds = makeAs('ms');
|
|
var asSeconds = makeAs('s');
|
|
var asMinutes = makeAs('m');
|
|
var asHours = makeAs('h');
|
|
var asDays = makeAs('d');
|
|
var asWeeks = makeAs('w');
|
|
var asMonths = makeAs('M');
|
|
var asQuarters = makeAs('Q');
|
|
var asYears = makeAs('y');
|
|
|
|
function clone$1 () {
|
|
return createDuration(this);
|
|
}
|
|
|
|
function get$2 (units) {
|
|
units = normalizeUnits(units);
|
|
return this.isValid() ? this[units + 's']() : NaN;
|
|
}
|
|
|
|
function makeGetter(name) {
|
|
return function () {
|
|
return this.isValid() ? this._data[name] : NaN;
|
|
};
|
|
}
|
|
|
|
var milliseconds = makeGetter('milliseconds');
|
|
var seconds = makeGetter('seconds');
|
|
var minutes = makeGetter('minutes');
|
|
var hours = makeGetter('hours');
|
|
var days = makeGetter('days');
|
|
var months = makeGetter('months');
|
|
var years = makeGetter('years');
|
|
|
|
function weeks () {
|
|
return absFloor(this.days() / 7);
|
|
}
|
|
|
|
var round = Math.round;
|
|
var thresholds = {
|
|
ss: 44, // a few seconds to seconds
|
|
s : 45, // seconds to minute
|
|
m : 45, // minutes to hour
|
|
h : 22, // hours to day
|
|
d : 26, // days to month
|
|
M : 11 // months to year
|
|
};
|
|
|
|
// helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize
|
|
function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) {
|
|
return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture);
|
|
}
|
|
|
|
function relativeTime$1 (posNegDuration, withoutSuffix, locale) {
|
|
var duration = createDuration(posNegDuration).abs();
|
|
var seconds = round(duration.as('s'));
|
|
var minutes = round(duration.as('m'));
|
|
var hours = round(duration.as('h'));
|
|
var days = round(duration.as('d'));
|
|
var months = round(duration.as('M'));
|
|
var years = round(duration.as('y'));
|
|
|
|
var a = seconds <= thresholds.ss && ['s', seconds] ||
|
|
seconds < thresholds.s && ['ss', seconds] ||
|
|
minutes <= 1 && ['m'] ||
|
|
minutes < thresholds.m && ['mm', minutes] ||
|
|
hours <= 1 && ['h'] ||
|
|
hours < thresholds.h && ['hh', hours] ||
|
|
days <= 1 && ['d'] ||
|
|
days < thresholds.d && ['dd', days] ||
|
|
months <= 1 && ['M'] ||
|
|
months < thresholds.M && ['MM', months] ||
|
|
years <= 1 && ['y'] || ['yy', years];
|
|
|
|
a[2] = withoutSuffix;
|
|
a[3] = +posNegDuration > 0;
|
|
a[4] = locale;
|
|
return substituteTimeAgo.apply(null, a);
|
|
}
|
|
|
|
// This function allows you to set the rounding function for relative time strings
|
|
function getSetRelativeTimeRounding (roundingFunction) {
|
|
if (roundingFunction === undefined) {
|
|
return round;
|
|
}
|
|
if (typeof(roundingFunction) === 'function') {
|
|
round = roundingFunction;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// This function allows you to set a threshold for relative time strings
|
|
function getSetRelativeTimeThreshold (threshold, limit) {
|
|
if (thresholds[threshold] === undefined) {
|
|
return false;
|
|
}
|
|
if (limit === undefined) {
|
|
return thresholds[threshold];
|
|
}
|
|
thresholds[threshold] = limit;
|
|
if (threshold === 's') {
|
|
thresholds.ss = limit - 1;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function humanize (withSuffix) {
|
|
if (!this.isValid()) {
|
|
return this.localeData().invalidDate();
|
|
}
|
|
|
|
var locale = this.localeData();
|
|
var output = relativeTime$1(this, !withSuffix, locale);
|
|
|
|
if (withSuffix) {
|
|
output = locale.pastFuture(+this, output);
|
|
}
|
|
|
|
return locale.postformat(output);
|
|
}
|
|
|
|
var abs$1 = Math.abs;
|
|
|
|
function sign(x) {
|
|
return ((x > 0) - (x < 0)) || +x;
|
|
}
|
|
|
|
function toISOString$1() {
|
|
// for ISO strings we do not use the normal bubbling rules:
|
|
// * milliseconds bubble up until they become hours
|
|
// * days do not bubble at all
|
|
// * months bubble up until they become years
|
|
// This is because there is no context-free conversion between hours and days
|
|
// (think of clock changes)
|
|
// and also not between days and months (28-31 days per month)
|
|
if (!this.isValid()) {
|
|
return this.localeData().invalidDate();
|
|
}
|
|
|
|
var seconds = abs$1(this._milliseconds) / 1000;
|
|
var days = abs$1(this._days);
|
|
var months = abs$1(this._months);
|
|
var minutes, hours, years;
|
|
|
|
// 3600 seconds -> 60 minutes -> 1 hour
|
|
minutes = absFloor(seconds / 60);
|
|
hours = absFloor(minutes / 60);
|
|
seconds %= 60;
|
|
minutes %= 60;
|
|
|
|
// 12 months -> 1 year
|
|
years = absFloor(months / 12);
|
|
months %= 12;
|
|
|
|
|
|
// inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js
|
|
var Y = years;
|
|
var M = months;
|
|
var D = days;
|
|
var h = hours;
|
|
var m = minutes;
|
|
var s = seconds ? seconds.toFixed(3).replace(/\.?0+$/, '') : '';
|
|
var total = this.asSeconds();
|
|
|
|
if (!total) {
|
|
// this is the same as C#'s (Noda) and python (isodate)...
|
|
// but not other JS (goog.date)
|
|
return 'P0D';
|
|
}
|
|
|
|
var totalSign = total < 0 ? '-' : '';
|
|
var ymSign = sign(this._months) !== sign(total) ? '-' : '';
|
|
var daysSign = sign(this._days) !== sign(total) ? '-' : '';
|
|
var hmsSign = sign(this._milliseconds) !== sign(total) ? '-' : '';
|
|
|
|
return totalSign + 'P' +
|
|
(Y ? ymSign + Y + 'Y' : '') +
|
|
(M ? ymSign + M + 'M' : '') +
|
|
(D ? daysSign + D + 'D' : '') +
|
|
((h || m || s) ? 'T' : '') +
|
|
(h ? hmsSign + h + 'H' : '') +
|
|
(m ? hmsSign + m + 'M' : '') +
|
|
(s ? hmsSign + s + 'S' : '');
|
|
}
|
|
|
|
var proto$2 = Duration.prototype;
|
|
|
|
proto$2.isValid = isValid$1;
|
|
proto$2.abs = abs;
|
|
proto$2.add = add$1;
|
|
proto$2.subtract = subtract$1;
|
|
proto$2.as = as;
|
|
proto$2.asMilliseconds = asMilliseconds;
|
|
proto$2.asSeconds = asSeconds;
|
|
proto$2.asMinutes = asMinutes;
|
|
proto$2.asHours = asHours;
|
|
proto$2.asDays = asDays;
|
|
proto$2.asWeeks = asWeeks;
|
|
proto$2.asMonths = asMonths;
|
|
proto$2.asQuarters = asQuarters;
|
|
proto$2.asYears = asYears;
|
|
proto$2.valueOf = valueOf$1;
|
|
proto$2._bubble = bubble;
|
|
proto$2.clone = clone$1;
|
|
proto$2.get = get$2;
|
|
proto$2.milliseconds = milliseconds;
|
|
proto$2.seconds = seconds;
|
|
proto$2.minutes = minutes;
|
|
proto$2.hours = hours;
|
|
proto$2.days = days;
|
|
proto$2.weeks = weeks;
|
|
proto$2.months = months;
|
|
proto$2.years = years;
|
|
proto$2.humanize = humanize;
|
|
proto$2.toISOString = toISOString$1;
|
|
proto$2.toString = toISOString$1;
|
|
proto$2.toJSON = toISOString$1;
|
|
proto$2.locale = locale;
|
|
proto$2.localeData = localeData;
|
|
|
|
proto$2.toIsoString = deprecate('toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)', toISOString$1);
|
|
proto$2.lang = lang;
|
|
|
|
// Side effect imports
|
|
|
|
// FORMATTING
|
|
|
|
addFormatToken('X', 0, 0, 'unix');
|
|
addFormatToken('x', 0, 0, 'valueOf');
|
|
|
|
// PARSING
|
|
|
|
addRegexToken('x', matchSigned);
|
|
addRegexToken('X', matchTimestamp);
|
|
addParseToken('X', function (input, array, config) {
|
|
config._d = new Date(parseFloat(input, 10) * 1000);
|
|
});
|
|
addParseToken('x', function (input, array, config) {
|
|
config._d = new Date(toInt(input));
|
|
});
|
|
|
|
// Side effect imports
|
|
|
|
|
|
hooks.version = '2.24.0';
|
|
|
|
setHookCallback(createLocal);
|
|
|
|
hooks.fn = proto;
|
|
hooks.min = min;
|
|
hooks.max = max;
|
|
hooks.now = now;
|
|
hooks.utc = createUTC;
|
|
hooks.unix = createUnix;
|
|
hooks.months = listMonths;
|
|
hooks.isDate = isDate;
|
|
hooks.locale = getSetGlobalLocale;
|
|
hooks.invalid = createInvalid;
|
|
hooks.duration = createDuration;
|
|
hooks.isMoment = isMoment;
|
|
hooks.weekdays = listWeekdays;
|
|
hooks.parseZone = createInZone;
|
|
hooks.localeData = getLocale;
|
|
hooks.isDuration = isDuration;
|
|
hooks.monthsShort = listMonthsShort;
|
|
hooks.weekdaysMin = listWeekdaysMin;
|
|
hooks.defineLocale = defineLocale;
|
|
hooks.updateLocale = updateLocale;
|
|
hooks.locales = listLocales;
|
|
hooks.weekdaysShort = listWeekdaysShort;
|
|
hooks.normalizeUnits = normalizeUnits;
|
|
hooks.relativeTimeRounding = getSetRelativeTimeRounding;
|
|
hooks.relativeTimeThreshold = getSetRelativeTimeThreshold;
|
|
hooks.calendarFormat = getCalendarFormat;
|
|
hooks.prototype = proto;
|
|
|
|
// currently HTML5 input type only supports 24-hour formats
|
|
hooks.HTML5_FMT = {
|
|
DATETIME_LOCAL: 'YYYY-MM-DDTHH:mm', // <input type="datetime-local" />
|
|
DATETIME_LOCAL_SECONDS: 'YYYY-MM-DDTHH:mm:ss', // <input type="datetime-local" step="1" />
|
|
DATETIME_LOCAL_MS: 'YYYY-MM-DDTHH:mm:ss.SSS', // <input type="datetime-local" step="0.001" />
|
|
DATE: 'YYYY-MM-DD', // <input type="date" />
|
|
TIME: 'HH:mm', // <input type="time" />
|
|
TIME_SECONDS: 'HH:mm:ss', // <input type="time" step="1" />
|
|
TIME_MS: 'HH:mm:ss.SSS', // <input type="time" step="0.001" />
|
|
WEEK: 'GGGG-[W]WW', // <input type="week" />
|
|
MONTH: 'YYYY-MM' // <input type="month" />
|
|
};
|
|
|
|
return hooks;
|
|
|
|
})));
|
|
});
|
|
|
|
var FORMATS = {
|
|
datetime: 'MMM D, YYYY, h:mm:ss a',
|
|
millisecond: 'h:mm:ss.SSS a',
|
|
second: 'h:mm:ss a',
|
|
minute: 'h:mm a',
|
|
hour: 'hA',
|
|
day: 'MMM D',
|
|
week: 'll',
|
|
month: 'MMM YYYY',
|
|
quarter: '[Q]Q - YYYY',
|
|
year: 'YYYY'
|
|
};
|
|
|
|
core_adapters._date.override(typeof moment === 'function' ? {
|
|
_id: 'moment', // DEBUG ONLY
|
|
|
|
formats: function() {
|
|
return FORMATS;
|
|
},
|
|
|
|
parse: function(value, format) {
|
|
if (typeof value === 'string' && typeof format === 'string') {
|
|
value = moment(value, format);
|
|
} else if (!(value instanceof moment)) {
|
|
value = moment(value);
|
|
}
|
|
return value.isValid() ? value.valueOf() : null;
|
|
},
|
|
|
|
format: function(time, format) {
|
|
return moment(time).format(format);
|
|
},
|
|
|
|
add: function(time, amount, unit) {
|
|
return moment(time).add(amount, unit).valueOf();
|
|
},
|
|
|
|
diff: function(max, min, unit) {
|
|
return moment(max).diff(moment(min), unit);
|
|
},
|
|
|
|
startOf: function(time, unit, weekday) {
|
|
time = moment(time);
|
|
if (unit === 'isoWeek') {
|
|
return time.isoWeekday(weekday).valueOf();
|
|
}
|
|
return time.startOf(unit).valueOf();
|
|
},
|
|
|
|
endOf: function(time, unit) {
|
|
return moment(time).endOf(unit).valueOf();
|
|
},
|
|
|
|
// DEPRECATIONS
|
|
|
|
/**
|
|
* Provided for backward compatibility with scale.getValueForPixel().
|
|
* @deprecated since version 2.8.0
|
|
* @todo remove at version 3
|
|
* @private
|
|
*/
|
|
_create: function(time) {
|
|
return moment(time);
|
|
},
|
|
} : {});
|
|
|
|
core_defaults._set('global', {
|
|
plugins: {
|
|
filler: {
|
|
propagate: true
|
|
}
|
|
}
|
|
});
|
|
|
|
var mappers = {
|
|
dataset: function(source) {
|
|
var index = source.fill;
|
|
var chart = source.chart;
|
|
var meta = chart.getDatasetMeta(index);
|
|
var visible = meta && chart.isDatasetVisible(index);
|
|
var points = (visible && meta.dataset._children) || [];
|
|
var length = points.length || 0;
|
|
|
|
return !length ? null : function(point, i) {
|
|
return (i < length && points[i]._view) || null;
|
|
};
|
|
},
|
|
|
|
boundary: function(source) {
|
|
var boundary = source.boundary;
|
|
var x = boundary ? boundary.x : null;
|
|
var y = boundary ? boundary.y : null;
|
|
|
|
if (helpers$1.isArray(boundary)) {
|
|
return function(point, i) {
|
|
return boundary[i];
|
|
};
|
|
}
|
|
|
|
return function(point) {
|
|
return {
|
|
x: x === null ? point.x : x,
|
|
y: y === null ? point.y : y,
|
|
};
|
|
};
|
|
}
|
|
};
|
|
|
|
// @todo if (fill[0] === '#')
|
|
function decodeFill(el, index, count) {
|
|
var model = el._model || {};
|
|
var fill = model.fill;
|
|
var target;
|
|
|
|
if (fill === undefined) {
|
|
fill = !!model.backgroundColor;
|
|
}
|
|
|
|
if (fill === false || fill === null) {
|
|
return false;
|
|
}
|
|
|
|
if (fill === true) {
|
|
return 'origin';
|
|
}
|
|
|
|
target = parseFloat(fill, 10);
|
|
if (isFinite(target) && Math.floor(target) === target) {
|
|
if (fill[0] === '-' || fill[0] === '+') {
|
|
target = index + target;
|
|
}
|
|
|
|
if (target === index || target < 0 || target >= count) {
|
|
return false;
|
|
}
|
|
|
|
return target;
|
|
}
|
|
|
|
switch (fill) {
|
|
// compatibility
|
|
case 'bottom':
|
|
return 'start';
|
|
case 'top':
|
|
return 'end';
|
|
case 'zero':
|
|
return 'origin';
|
|
// supported boundaries
|
|
case 'origin':
|
|
case 'start':
|
|
case 'end':
|
|
return fill;
|
|
// invalid fill values
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function computeLinearBoundary(source) {
|
|
var model = source.el._model || {};
|
|
var scale = source.el._scale || {};
|
|
var fill = source.fill;
|
|
var target = null;
|
|
var horizontal;
|
|
|
|
if (isFinite(fill)) {
|
|
return null;
|
|
}
|
|
|
|
// Backward compatibility: until v3, we still need to support boundary values set on
|
|
// the model (scaleTop, scaleBottom and scaleZero) because some external plugins and
|
|
// controllers might still use it (e.g. the Smith chart).
|
|
|
|
if (fill === 'start') {
|
|
target = model.scaleBottom === undefined ? scale.bottom : model.scaleBottom;
|
|
} else if (fill === 'end') {
|
|
target = model.scaleTop === undefined ? scale.top : model.scaleTop;
|
|
} else if (model.scaleZero !== undefined) {
|
|
target = model.scaleZero;
|
|
} else if (scale.getBasePixel) {
|
|
target = scale.getBasePixel();
|
|
}
|
|
|
|
if (target !== undefined && target !== null) {
|
|
if (target.x !== undefined && target.y !== undefined) {
|
|
return target;
|
|
}
|
|
|
|
if (helpers$1.isFinite(target)) {
|
|
horizontal = scale.isHorizontal();
|
|
return {
|
|
x: horizontal ? target : null,
|
|
y: horizontal ? null : target
|
|
};
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function computeCircularBoundary(source) {
|
|
var scale = source.el._scale;
|
|
var options = scale.options;
|
|
var length = scale.chart.data.labels.length;
|
|
var fill = source.fill;
|
|
var target = [];
|
|
var start, end, center, i, point;
|
|
|
|
if (!length) {
|
|
return null;
|
|
}
|
|
|
|
start = options.ticks.reverse ? scale.max : scale.min;
|
|
end = options.ticks.reverse ? scale.min : scale.max;
|
|
center = scale.getPointPositionForValue(0, start);
|
|
for (i = 0; i < length; ++i) {
|
|
point = fill === 'start' || fill === 'end'
|
|
? scale.getPointPositionForValue(i, fill === 'start' ? start : end)
|
|
: scale.getBasePosition(i);
|
|
if (options.gridLines.circular) {
|
|
point.cx = center.x;
|
|
point.cy = center.y;
|
|
point.angle = scale.getIndexAngle(i) - Math.PI / 2;
|
|
}
|
|
target.push(point);
|
|
}
|
|
return target;
|
|
}
|
|
|
|
function computeBoundary(source) {
|
|
var scale = source.el._scale || {};
|
|
|
|
if (scale.getPointPositionForValue) {
|
|
return computeCircularBoundary(source);
|
|
}
|
|
return computeLinearBoundary(source);
|
|
}
|
|
|
|
function resolveTarget(sources, index, propagate) {
|
|
var source = sources[index];
|
|
var fill = source.fill;
|
|
var visited = [index];
|
|
var target;
|
|
|
|
if (!propagate) {
|
|
return fill;
|
|
}
|
|
|
|
while (fill !== false && visited.indexOf(fill) === -1) {
|
|
if (!isFinite(fill)) {
|
|
return fill;
|
|
}
|
|
|
|
target = sources[fill];
|
|
if (!target) {
|
|
return false;
|
|
}
|
|
|
|
if (target.visible) {
|
|
return fill;
|
|
}
|
|
|
|
visited.push(fill);
|
|
fill = target.fill;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function createMapper(source) {
|
|
var fill = source.fill;
|
|
var type = 'dataset';
|
|
|
|
if (fill === false) {
|
|
return null;
|
|
}
|
|
|
|
if (!isFinite(fill)) {
|
|
type = 'boundary';
|
|
}
|
|
|
|
return mappers[type](source);
|
|
}
|
|
|
|
function isDrawable(point) {
|
|
return point && !point.skip;
|
|
}
|
|
|
|
function drawArea(ctx, curve0, curve1, len0, len1) {
|
|
var i, cx, cy, r;
|
|
|
|
if (!len0 || !len1) {
|
|
return;
|
|
}
|
|
|
|
// building first area curve (normal)
|
|
ctx.moveTo(curve0[0].x, curve0[0].y);
|
|
for (i = 1; i < len0; ++i) {
|
|
helpers$1.canvas.lineTo(ctx, curve0[i - 1], curve0[i]);
|
|
}
|
|
|
|
if (curve1[0].angle !== undefined) {
|
|
cx = curve1[0].cx;
|
|
cy = curve1[0].cy;
|
|
r = Math.sqrt(Math.pow(curve1[0].x - cx, 2) + Math.pow(curve1[0].y - cy, 2));
|
|
for (i = len1 - 1; i > 0; --i) {
|
|
ctx.arc(cx, cy, r, curve1[i].angle, curve1[i - 1].angle, true);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// joining the two area curves
|
|
ctx.lineTo(curve1[len1 - 1].x, curve1[len1 - 1].y);
|
|
|
|
// building opposite area curve (reverse)
|
|
for (i = len1 - 1; i > 0; --i) {
|
|
helpers$1.canvas.lineTo(ctx, curve1[i], curve1[i - 1], true);
|
|
}
|
|
}
|
|
|
|
function doFill(ctx, points, mapper, view, color, loop) {
|
|
var count = points.length;
|
|
var span = view.spanGaps;
|
|
var curve0 = [];
|
|
var curve1 = [];
|
|
var len0 = 0;
|
|
var len1 = 0;
|
|
var i, ilen, index, p0, p1, d0, d1, loopOffset;
|
|
|
|
ctx.beginPath();
|
|
|
|
for (i = 0, ilen = count; i < ilen; ++i) {
|
|
index = i % count;
|
|
p0 = points[index]._view;
|
|
p1 = mapper(p0, index, view);
|
|
d0 = isDrawable(p0);
|
|
d1 = isDrawable(p1);
|
|
|
|
if (loop && loopOffset === undefined && d0) {
|
|
loopOffset = i + 1;
|
|
ilen = count + loopOffset;
|
|
}
|
|
|
|
if (d0 && d1) {
|
|
len0 = curve0.push(p0);
|
|
len1 = curve1.push(p1);
|
|
} else if (len0 && len1) {
|
|
if (!span) {
|
|
drawArea(ctx, curve0, curve1, len0, len1);
|
|
len0 = len1 = 0;
|
|
curve0 = [];
|
|
curve1 = [];
|
|
} else {
|
|
if (d0) {
|
|
curve0.push(p0);
|
|
}
|
|
if (d1) {
|
|
curve1.push(p1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
drawArea(ctx, curve0, curve1, len0, len1);
|
|
|
|
ctx.closePath();
|
|
ctx.fillStyle = color;
|
|
ctx.fill();
|
|
}
|
|
|
|
var plugin_filler = {
|
|
id: 'filler',
|
|
|
|
afterDatasetsUpdate: function(chart, options) {
|
|
var count = (chart.data.datasets || []).length;
|
|
var propagate = options.propagate;
|
|
var sources = [];
|
|
var meta, i, el, source;
|
|
|
|
for (i = 0; i < count; ++i) {
|
|
meta = chart.getDatasetMeta(i);
|
|
el = meta.dataset;
|
|
source = null;
|
|
|
|
if (el && el._model && el instanceof elements.Line) {
|
|
source = {
|
|
visible: chart.isDatasetVisible(i),
|
|
fill: decodeFill(el, i, count),
|
|
chart: chart,
|
|
el: el
|
|
};
|
|
}
|
|
|
|
meta.$filler = source;
|
|
sources.push(source);
|
|
}
|
|
|
|
for (i = 0; i < count; ++i) {
|
|
source = sources[i];
|
|
if (!source) {
|
|
continue;
|
|
}
|
|
|
|
source.fill = resolveTarget(sources, i, propagate);
|
|
source.boundary = computeBoundary(source);
|
|
source.mapper = createMapper(source);
|
|
}
|
|
},
|
|
|
|
beforeDatasetsDraw: function(chart) {
|
|
var metasets = chart._getSortedVisibleDatasetMetas();
|
|
var ctx = chart.ctx;
|
|
var meta, i, el, view, points, mapper, color;
|
|
|
|
for (i = metasets.length - 1; i >= 0; --i) {
|
|
meta = metasets[i].$filler;
|
|
|
|
if (!meta || !meta.visible) {
|
|
continue;
|
|
}
|
|
|
|
el = meta.el;
|
|
view = el._view;
|
|
points = el._children || [];
|
|
mapper = meta.mapper;
|
|
color = view.backgroundColor || core_defaults.global.defaultColor;
|
|
|
|
if (mapper && color && points.length) {
|
|
helpers$1.canvas.clipArea(ctx, chart.chartArea);
|
|
doFill(ctx, points, mapper, view, color, el._loop);
|
|
helpers$1.canvas.unclipArea(ctx);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
var getRtlHelper$1 = helpers$1.rtl.getRtlAdapter;
|
|
var noop$1 = helpers$1.noop;
|
|
var valueOrDefault$e = helpers$1.valueOrDefault;
|
|
|
|
core_defaults._set('global', {
|
|
legend: {
|
|
display: true,
|
|
position: 'top',
|
|
align: 'center',
|
|
fullWidth: true,
|
|
reverse: false,
|
|
weight: 1000,
|
|
|
|
// a callback that will handle
|
|
onClick: function(e, legendItem) {
|
|
var index = legendItem.datasetIndex;
|
|
var ci = this.chart;
|
|
var meta = ci.getDatasetMeta(index);
|
|
|
|
// See controller.isDatasetVisible comment
|
|
meta.hidden = meta.hidden === null ? !ci.data.datasets[index].hidden : null;
|
|
|
|
// We hid a dataset ... rerender the chart
|
|
ci.update();
|
|
},
|
|
|
|
onHover: null,
|
|
onLeave: null,
|
|
|
|
labels: {
|
|
boxWidth: 40,
|
|
padding: 10,
|
|
// Generates labels shown in the legend
|
|
// Valid properties to return:
|
|
// text : text to display
|
|
// fillStyle : fill of coloured box
|
|
// strokeStyle: stroke of coloured box
|
|
// hidden : if this legend item refers to a hidden item
|
|
// lineCap : cap style for line
|
|
// lineDash
|
|
// lineDashOffset :
|
|
// lineJoin :
|
|
// lineWidth :
|
|
generateLabels: function(chart) {
|
|
var datasets = chart.data.datasets;
|
|
var options = chart.options.legend || {};
|
|
var usePointStyle = options.labels && options.labels.usePointStyle;
|
|
|
|
return chart._getSortedDatasetMetas().map(function(meta) {
|
|
var style = meta.controller.getStyle(usePointStyle ? 0 : undefined);
|
|
|
|
return {
|
|
text: datasets[meta.index].label,
|
|
fillStyle: style.backgroundColor,
|
|
hidden: !chart.isDatasetVisible(meta.index),
|
|
lineCap: style.borderCapStyle,
|
|
lineDash: style.borderDash,
|
|
lineDashOffset: style.borderDashOffset,
|
|
lineJoin: style.borderJoinStyle,
|
|
lineWidth: style.borderWidth,
|
|
strokeStyle: style.borderColor,
|
|
pointStyle: style.pointStyle,
|
|
rotation: style.rotation,
|
|
|
|
// Below is extra data used for toggling the datasets
|
|
datasetIndex: meta.index
|
|
};
|
|
}, this);
|
|
}
|
|
}
|
|
},
|
|
|
|
legendCallback: function(chart) {
|
|
var list = document.createElement('ul');
|
|
var datasets = chart.data.datasets;
|
|
var i, ilen, listItem, listItemSpan;
|
|
|
|
list.setAttribute('class', chart.id + '-legend');
|
|
|
|
for (i = 0, ilen = datasets.length; i < ilen; i++) {
|
|
listItem = list.appendChild(document.createElement('li'));
|
|
listItemSpan = listItem.appendChild(document.createElement('span'));
|
|
listItemSpan.style.backgroundColor = datasets[i].backgroundColor;
|
|
if (datasets[i].label) {
|
|
listItem.appendChild(document.createTextNode(datasets[i].label));
|
|
}
|
|
}
|
|
|
|
return list.outerHTML;
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Helper function to get the box width based on the usePointStyle option
|
|
* @param {object} labelopts - the label options on the legend
|
|
* @param {number} fontSize - the label font size
|
|
* @return {number} width of the color box area
|
|
*/
|
|
function getBoxWidth(labelOpts, fontSize) {
|
|
return labelOpts.usePointStyle && labelOpts.boxWidth > fontSize ?
|
|
fontSize :
|
|
labelOpts.boxWidth;
|
|
}
|
|
|
|
/**
|
|
* IMPORTANT: this class is exposed publicly as Chart.Legend, backward compatibility required!
|
|
*/
|
|
var Legend = core_element.extend({
|
|
|
|
initialize: function(config) {
|
|
var me = this;
|
|
helpers$1.extend(me, config);
|
|
|
|
// Contains hit boxes for each dataset (in dataset order)
|
|
me.legendHitBoxes = [];
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
me._hoveredItem = null;
|
|
|
|
// Are we in doughnut mode which has a different data type
|
|
me.doughnutMode = false;
|
|
},
|
|
|
|
// These methods are ordered by lifecycle. Utilities then follow.
|
|
// Any function defined here is inherited by all legend types.
|
|
// Any function can be extended by the legend type
|
|
|
|
beforeUpdate: noop$1,
|
|
update: function(maxWidth, maxHeight, margins) {
|
|
var me = this;
|
|
|
|
// Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
|
|
me.beforeUpdate();
|
|
|
|
// Absorb the master measurements
|
|
me.maxWidth = maxWidth;
|
|
me.maxHeight = maxHeight;
|
|
me.margins = margins;
|
|
|
|
// Dimensions
|
|
me.beforeSetDimensions();
|
|
me.setDimensions();
|
|
me.afterSetDimensions();
|
|
// Labels
|
|
me.beforeBuildLabels();
|
|
me.buildLabels();
|
|
me.afterBuildLabels();
|
|
|
|
// Fit
|
|
me.beforeFit();
|
|
me.fit();
|
|
me.afterFit();
|
|
//
|
|
me.afterUpdate();
|
|
|
|
return me.minSize;
|
|
},
|
|
afterUpdate: noop$1,
|
|
|
|
//
|
|
|
|
beforeSetDimensions: noop$1,
|
|
setDimensions: function() {
|
|
var me = this;
|
|
// Set the unconstrained dimension before label rotation
|
|
if (me.isHorizontal()) {
|
|
// Reset position before calculating rotation
|
|
me.width = me.maxWidth;
|
|
me.left = 0;
|
|
me.right = me.width;
|
|
} else {
|
|
me.height = me.maxHeight;
|
|
|
|
// Reset position before calculating rotation
|
|
me.top = 0;
|
|
me.bottom = me.height;
|
|
}
|
|
|
|
// Reset padding
|
|
me.paddingLeft = 0;
|
|
me.paddingTop = 0;
|
|
me.paddingRight = 0;
|
|
me.paddingBottom = 0;
|
|
|
|
// Reset minSize
|
|
me.minSize = {
|
|
width: 0,
|
|
height: 0
|
|
};
|
|
},
|
|
afterSetDimensions: noop$1,
|
|
|
|
//
|
|
|
|
beforeBuildLabels: noop$1,
|
|
buildLabels: function() {
|
|
var me = this;
|
|
var labelOpts = me.options.labels || {};
|
|
var legendItems = helpers$1.callback(labelOpts.generateLabels, [me.chart], me) || [];
|
|
|
|
if (labelOpts.filter) {
|
|
legendItems = legendItems.filter(function(item) {
|
|
return labelOpts.filter(item, me.chart.data);
|
|
});
|
|
}
|
|
|
|
if (me.options.reverse) {
|
|
legendItems.reverse();
|
|
}
|
|
|
|
me.legendItems = legendItems;
|
|
},
|
|
afterBuildLabels: noop$1,
|
|
|
|
//
|
|
|
|
beforeFit: noop$1,
|
|
fit: function() {
|
|
var me = this;
|
|
var opts = me.options;
|
|
var labelOpts = opts.labels;
|
|
var display = opts.display;
|
|
|
|
var ctx = me.ctx;
|
|
|
|
var labelFont = helpers$1.options._parseFont(labelOpts);
|
|
var fontSize = labelFont.size;
|
|
|
|
// Reset hit boxes
|
|
var hitboxes = me.legendHitBoxes = [];
|
|
|
|
var minSize = me.minSize;
|
|
var isHorizontal = me.isHorizontal();
|
|
|
|
if (isHorizontal) {
|
|
minSize.width = me.maxWidth; // fill all the width
|
|
minSize.height = display ? 10 : 0;
|
|
} else {
|
|
minSize.width = display ? 10 : 0;
|
|
minSize.height = me.maxHeight; // fill all the height
|
|
}
|
|
|
|
// Increase sizes here
|
|
if (!display) {
|
|
me.width = minSize.width = me.height = minSize.height = 0;
|
|
return;
|
|
}
|
|
ctx.font = labelFont.string;
|
|
|
|
if (isHorizontal) {
|
|
// Labels
|
|
|
|
// Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one
|
|
var lineWidths = me.lineWidths = [0];
|
|
var totalHeight = 0;
|
|
|
|
ctx.textAlign = 'left';
|
|
ctx.textBaseline = 'middle';
|
|
|
|
helpers$1.each(me.legendItems, function(legendItem, i) {
|
|
var boxWidth = getBoxWidth(labelOpts, fontSize);
|
|
var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
|
|
|
|
if (i === 0 || lineWidths[lineWidths.length - 1] + width + 2 * labelOpts.padding > minSize.width) {
|
|
totalHeight += fontSize + labelOpts.padding;
|
|
lineWidths[lineWidths.length - (i > 0 ? 0 : 1)] = 0;
|
|
}
|
|
|
|
// Store the hitbox width and height here. Final position will be updated in `draw`
|
|
hitboxes[i] = {
|
|
left: 0,
|
|
top: 0,
|
|
width: width,
|
|
height: fontSize
|
|
};
|
|
|
|
lineWidths[lineWidths.length - 1] += width + labelOpts.padding;
|
|
});
|
|
|
|
minSize.height += totalHeight;
|
|
|
|
} else {
|
|
var vPadding = labelOpts.padding;
|
|
var columnWidths = me.columnWidths = [];
|
|
var columnHeights = me.columnHeights = [];
|
|
var totalWidth = labelOpts.padding;
|
|
var currentColWidth = 0;
|
|
var currentColHeight = 0;
|
|
|
|
helpers$1.each(me.legendItems, function(legendItem, i) {
|
|
var boxWidth = getBoxWidth(labelOpts, fontSize);
|
|
var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
|
|
|
|
// If too tall, go to new column
|
|
if (i > 0 && currentColHeight + fontSize + 2 * vPadding > minSize.height) {
|
|
totalWidth += currentColWidth + labelOpts.padding;
|
|
columnWidths.push(currentColWidth); // previous column width
|
|
columnHeights.push(currentColHeight);
|
|
currentColWidth = 0;
|
|
currentColHeight = 0;
|
|
}
|
|
|
|
// Get max width
|
|
currentColWidth = Math.max(currentColWidth, itemWidth);
|
|
currentColHeight += fontSize + vPadding;
|
|
|
|
// Store the hitbox width and height here. Final position will be updated in `draw`
|
|
hitboxes[i] = {
|
|
left: 0,
|
|
top: 0,
|
|
width: itemWidth,
|
|
height: fontSize
|
|
};
|
|
});
|
|
|
|
totalWidth += currentColWidth;
|
|
columnWidths.push(currentColWidth);
|
|
columnHeights.push(currentColHeight);
|
|
minSize.width += totalWidth;
|
|
}
|
|
|
|
me.width = minSize.width;
|
|
me.height = minSize.height;
|
|
},
|
|
afterFit: noop$1,
|
|
|
|
// Shared Methods
|
|
isHorizontal: function() {
|
|
return this.options.position === 'top' || this.options.position === 'bottom';
|
|
},
|
|
|
|
// Actually draw the legend on the canvas
|
|
draw: function() {
|
|
var me = this;
|
|
var opts = me.options;
|
|
var labelOpts = opts.labels;
|
|
var globalDefaults = core_defaults.global;
|
|
var defaultColor = globalDefaults.defaultColor;
|
|
var lineDefault = globalDefaults.elements.line;
|
|
var legendHeight = me.height;
|
|
var columnHeights = me.columnHeights;
|
|
var legendWidth = me.width;
|
|
var lineWidths = me.lineWidths;
|
|
|
|
if (!opts.display) {
|
|
return;
|
|
}
|
|
|
|
var rtlHelper = getRtlHelper$1(opts.rtl, me.left, me.minSize.width);
|
|
var ctx = me.ctx;
|
|
var fontColor = valueOrDefault$e(labelOpts.fontColor, globalDefaults.defaultFontColor);
|
|
var labelFont = helpers$1.options._parseFont(labelOpts);
|
|
var fontSize = labelFont.size;
|
|
var cursor;
|
|
|
|
// Canvas setup
|
|
ctx.textAlign = rtlHelper.textAlign('left');
|
|
ctx.textBaseline = 'middle';
|
|
ctx.lineWidth = 0.5;
|
|
ctx.strokeStyle = fontColor; // for strikethrough effect
|
|
ctx.fillStyle = fontColor; // render in correct colour
|
|
ctx.font = labelFont.string;
|
|
|
|
var boxWidth = getBoxWidth(labelOpts, fontSize);
|
|
var hitboxes = me.legendHitBoxes;
|
|
|
|
// current position
|
|
var drawLegendBox = function(x, y, legendItem) {
|
|
if (isNaN(boxWidth) || boxWidth <= 0) {
|
|
return;
|
|
}
|
|
|
|
// Set the ctx for the box
|
|
ctx.save();
|
|
|
|
var lineWidth = valueOrDefault$e(legendItem.lineWidth, lineDefault.borderWidth);
|
|
ctx.fillStyle = valueOrDefault$e(legendItem.fillStyle, defaultColor);
|
|
ctx.lineCap = valueOrDefault$e(legendItem.lineCap, lineDefault.borderCapStyle);
|
|
ctx.lineDashOffset = valueOrDefault$e(legendItem.lineDashOffset, lineDefault.borderDashOffset);
|
|
ctx.lineJoin = valueOrDefault$e(legendItem.lineJoin, lineDefault.borderJoinStyle);
|
|
ctx.lineWidth = lineWidth;
|
|
ctx.strokeStyle = valueOrDefault$e(legendItem.strokeStyle, defaultColor);
|
|
|
|
if (ctx.setLineDash) {
|
|
// IE 9 and 10 do not support line dash
|
|
ctx.setLineDash(valueOrDefault$e(legendItem.lineDash, lineDefault.borderDash));
|
|
}
|
|
|
|
if (labelOpts && labelOpts.usePointStyle) {
|
|
// Recalculate x and y for drawPoint() because its expecting
|
|
// x and y to be center of figure (instead of top left)
|
|
var radius = boxWidth * Math.SQRT2 / 2;
|
|
var centerX = rtlHelper.xPlus(x, boxWidth / 2);
|
|
var centerY = y + fontSize / 2;
|
|
|
|
// Draw pointStyle as legend symbol
|
|
helpers$1.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY, legendItem.rotation);
|
|
} else {
|
|
// Draw box as legend symbol
|
|
ctx.fillRect(rtlHelper.leftForLtr(x, boxWidth), y, boxWidth, fontSize);
|
|
if (lineWidth !== 0) {
|
|
ctx.strokeRect(rtlHelper.leftForLtr(x, boxWidth), y, boxWidth, fontSize);
|
|
}
|
|
}
|
|
|
|
ctx.restore();
|
|
};
|
|
|
|
var fillText = function(x, y, legendItem, textWidth) {
|
|
var halfFontSize = fontSize / 2;
|
|
var xLeft = rtlHelper.xPlus(x, boxWidth + halfFontSize);
|
|
var yMiddle = y + halfFontSize;
|
|
|
|
ctx.fillText(legendItem.text, xLeft, yMiddle);
|
|
|
|
if (legendItem.hidden) {
|
|
// Strikethrough the text if hidden
|
|
ctx.beginPath();
|
|
ctx.lineWidth = 2;
|
|
ctx.moveTo(xLeft, yMiddle);
|
|
ctx.lineTo(rtlHelper.xPlus(xLeft, textWidth), yMiddle);
|
|
ctx.stroke();
|
|
}
|
|
};
|
|
|
|
var alignmentOffset = function(dimension, blockSize) {
|
|
switch (opts.align) {
|
|
case 'start':
|
|
return labelOpts.padding;
|
|
case 'end':
|
|
return dimension - blockSize;
|
|
default: // center
|
|
return (dimension - blockSize + labelOpts.padding) / 2;
|
|
}
|
|
};
|
|
|
|
// Horizontal
|
|
var isHorizontal = me.isHorizontal();
|
|
if (isHorizontal) {
|
|
cursor = {
|
|
x: me.left + alignmentOffset(legendWidth, lineWidths[0]),
|
|
y: me.top + labelOpts.padding,
|
|
line: 0
|
|
};
|
|
} else {
|
|
cursor = {
|
|
x: me.left + labelOpts.padding,
|
|
y: me.top + alignmentOffset(legendHeight, columnHeights[0]),
|
|
line: 0
|
|
};
|
|
}
|
|
|
|
helpers$1.rtl.overrideTextDirection(me.ctx, opts.textDirection);
|
|
|
|
var itemHeight = fontSize + labelOpts.padding;
|
|
helpers$1.each(me.legendItems, function(legendItem, i) {
|
|
var textWidth = ctx.measureText(legendItem.text).width;
|
|
var width = boxWidth + (fontSize / 2) + textWidth;
|
|
var x = cursor.x;
|
|
var y = cursor.y;
|
|
|
|
rtlHelper.setWidth(me.minSize.width);
|
|
|
|
// Use (me.left + me.minSize.width) and (me.top + me.minSize.height)
|
|
// instead of me.right and me.bottom because me.width and me.height
|
|
// may have been changed since me.minSize was calculated
|
|
if (isHorizontal) {
|
|
if (i > 0 && x + width + labelOpts.padding > me.left + me.minSize.width) {
|
|
y = cursor.y += itemHeight;
|
|
cursor.line++;
|
|
x = cursor.x = me.left + alignmentOffset(legendWidth, lineWidths[cursor.line]);
|
|
}
|
|
} else if (i > 0 && y + itemHeight > me.top + me.minSize.height) {
|
|
x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding;
|
|
cursor.line++;
|
|
y = cursor.y = me.top + alignmentOffset(legendHeight, columnHeights[cursor.line]);
|
|
}
|
|
|
|
var realX = rtlHelper.x(x);
|
|
|
|
drawLegendBox(realX, y, legendItem);
|
|
|
|
hitboxes[i].left = rtlHelper.leftForLtr(realX, hitboxes[i].width);
|
|
hitboxes[i].top = y;
|
|
|
|
// Fill the actual label
|
|
fillText(realX, y, legendItem, textWidth);
|
|
|
|
if (isHorizontal) {
|
|
cursor.x += width + labelOpts.padding;
|
|
} else {
|
|
cursor.y += itemHeight;
|
|
}
|
|
});
|
|
|
|
helpers$1.rtl.restoreTextDirection(me.ctx, opts.textDirection);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_getLegendItemAt: function(x, y) {
|
|
var me = this;
|
|
var i, hitBox, lh;
|
|
|
|
if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) {
|
|
// See if we are touching one of the dataset boxes
|
|
lh = me.legendHitBoxes;
|
|
for (i = 0; i < lh.length; ++i) {
|
|
hitBox = lh[i];
|
|
|
|
if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) {
|
|
// Touching an element
|
|
return me.legendItems[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
},
|
|
|
|
/**
|
|
* Handle an event
|
|
* @private
|
|
* @param {IEvent} event - The event to handle
|
|
*/
|
|
handleEvent: function(e) {
|
|
var me = this;
|
|
var opts = me.options;
|
|
var type = e.type === 'mouseup' ? 'click' : e.type;
|
|
var hoveredItem;
|
|
|
|
if (type === 'mousemove') {
|
|
if (!opts.onHover && !opts.onLeave) {
|
|
return;
|
|
}
|
|
} else if (type === 'click') {
|
|
if (!opts.onClick) {
|
|
return;
|
|
}
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
// Chart event already has relative position in it
|
|
hoveredItem = me._getLegendItemAt(e.x, e.y);
|
|
|
|
if (type === 'click') {
|
|
if (hoveredItem && opts.onClick) {
|
|
// use e.native for backwards compatibility
|
|
opts.onClick.call(me, e.native, hoveredItem);
|
|
}
|
|
} else {
|
|
if (opts.onLeave && hoveredItem !== me._hoveredItem) {
|
|
if (me._hoveredItem) {
|
|
opts.onLeave.call(me, e.native, me._hoveredItem);
|
|
}
|
|
me._hoveredItem = hoveredItem;
|
|
}
|
|
|
|
if (opts.onHover && hoveredItem) {
|
|
// use e.native for backwards compatibility
|
|
opts.onHover.call(me, e.native, hoveredItem);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
function createNewLegendAndAttach(chart, legendOpts) {
|
|
var legend = new Legend({
|
|
ctx: chart.ctx,
|
|
options: legendOpts,
|
|
chart: chart
|
|
});
|
|
|
|
core_layouts.configure(chart, legend, legendOpts);
|
|
core_layouts.addBox(chart, legend);
|
|
chart.legend = legend;
|
|
}
|
|
|
|
var plugin_legend = {
|
|
id: 'legend',
|
|
|
|
/**
|
|
* Backward compatibility: since 2.1.5, the legend is registered as a plugin, making
|
|
* Chart.Legend obsolete. To avoid a breaking change, we export the Legend as part of
|
|
* the plugin, which one will be re-exposed in the chart.js file.
|
|
* https://github.com/chartjs/Chart.js/pull/2640
|
|
* @private
|
|
*/
|
|
_element: Legend,
|
|
|
|
beforeInit: function(chart) {
|
|
var legendOpts = chart.options.legend;
|
|
|
|
if (legendOpts) {
|
|
createNewLegendAndAttach(chart, legendOpts);
|
|
}
|
|
},
|
|
|
|
beforeUpdate: function(chart) {
|
|
var legendOpts = chart.options.legend;
|
|
var legend = chart.legend;
|
|
|
|
if (legendOpts) {
|
|
helpers$1.mergeIf(legendOpts, core_defaults.global.legend);
|
|
|
|
if (legend) {
|
|
core_layouts.configure(chart, legend, legendOpts);
|
|
legend.options = legendOpts;
|
|
} else {
|
|
createNewLegendAndAttach(chart, legendOpts);
|
|
}
|
|
} else if (legend) {
|
|
core_layouts.removeBox(chart, legend);
|
|
delete chart.legend;
|
|
}
|
|
},
|
|
|
|
afterEvent: function(chart, e) {
|
|
var legend = chart.legend;
|
|
if (legend) {
|
|
legend.handleEvent(e);
|
|
}
|
|
}
|
|
};
|
|
|
|
var noop$2 = helpers$1.noop;
|
|
|
|
core_defaults._set('global', {
|
|
title: {
|
|
display: false,
|
|
fontStyle: 'bold',
|
|
fullWidth: true,
|
|
padding: 10,
|
|
position: 'top',
|
|
text: '',
|
|
weight: 2000 // by default greater than legend (1000) to be above
|
|
}
|
|
});
|
|
|
|
/**
|
|
* IMPORTANT: this class is exposed publicly as Chart.Legend, backward compatibility required!
|
|
*/
|
|
var Title = core_element.extend({
|
|
initialize: function(config) {
|
|
var me = this;
|
|
helpers$1.extend(me, config);
|
|
|
|
// Contains hit boxes for each dataset (in dataset order)
|
|
me.legendHitBoxes = [];
|
|
},
|
|
|
|
// These methods are ordered by lifecycle. Utilities then follow.
|
|
|
|
beforeUpdate: noop$2,
|
|
update: function(maxWidth, maxHeight, margins) {
|
|
var me = this;
|
|
|
|
// Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
|
|
me.beforeUpdate();
|
|
|
|
// Absorb the master measurements
|
|
me.maxWidth = maxWidth;
|
|
me.maxHeight = maxHeight;
|
|
me.margins = margins;
|
|
|
|
// Dimensions
|
|
me.beforeSetDimensions();
|
|
me.setDimensions();
|
|
me.afterSetDimensions();
|
|
// Labels
|
|
me.beforeBuildLabels();
|
|
me.buildLabels();
|
|
me.afterBuildLabels();
|
|
|
|
// Fit
|
|
me.beforeFit();
|
|
me.fit();
|
|
me.afterFit();
|
|
//
|
|
me.afterUpdate();
|
|
|
|
return me.minSize;
|
|
|
|
},
|
|
afterUpdate: noop$2,
|
|
|
|
//
|
|
|
|
beforeSetDimensions: noop$2,
|
|
setDimensions: function() {
|
|
var me = this;
|
|
// Set the unconstrained dimension before label rotation
|
|
if (me.isHorizontal()) {
|
|
// Reset position before calculating rotation
|
|
me.width = me.maxWidth;
|
|
me.left = 0;
|
|
me.right = me.width;
|
|
} else {
|
|
me.height = me.maxHeight;
|
|
|
|
// Reset position before calculating rotation
|
|
me.top = 0;
|
|
me.bottom = me.height;
|
|
}
|
|
|
|
// Reset padding
|
|
me.paddingLeft = 0;
|
|
me.paddingTop = 0;
|
|
me.paddingRight = 0;
|
|
me.paddingBottom = 0;
|
|
|
|
// Reset minSize
|
|
me.minSize = {
|
|
width: 0,
|
|
height: 0
|
|
};
|
|
},
|
|
afterSetDimensions: noop$2,
|
|
|
|
//
|
|
|
|
beforeBuildLabels: noop$2,
|
|
buildLabels: noop$2,
|
|
afterBuildLabels: noop$2,
|
|
|
|
//
|
|
|
|
beforeFit: noop$2,
|
|
fit: function() {
|
|
var me = this;
|
|
var opts = me.options;
|
|
var minSize = me.minSize = {};
|
|
var isHorizontal = me.isHorizontal();
|
|
var lineCount, textSize;
|
|
|
|
if (!opts.display) {
|
|
me.width = minSize.width = me.height = minSize.height = 0;
|
|
return;
|
|
}
|
|
|
|
lineCount = helpers$1.isArray(opts.text) ? opts.text.length : 1;
|
|
textSize = lineCount * helpers$1.options._parseFont(opts).lineHeight + opts.padding * 2;
|
|
|
|
me.width = minSize.width = isHorizontal ? me.maxWidth : textSize;
|
|
me.height = minSize.height = isHorizontal ? textSize : me.maxHeight;
|
|
},
|
|
afterFit: noop$2,
|
|
|
|
// Shared Methods
|
|
isHorizontal: function() {
|
|
var pos = this.options.position;
|
|
return pos === 'top' || pos === 'bottom';
|
|
},
|
|
|
|
// Actually draw the title block on the canvas
|
|
draw: function() {
|
|
var me = this;
|
|
var ctx = me.ctx;
|
|
var opts = me.options;
|
|
|
|
if (!opts.display) {
|
|
return;
|
|
}
|
|
|
|
var fontOpts = helpers$1.options._parseFont(opts);
|
|
var lineHeight = fontOpts.lineHeight;
|
|
var offset = lineHeight / 2 + opts.padding;
|
|
var rotation = 0;
|
|
var top = me.top;
|
|
var left = me.left;
|
|
var bottom = me.bottom;
|
|
var right = me.right;
|
|
var maxWidth, titleX, titleY;
|
|
|
|
ctx.fillStyle = helpers$1.valueOrDefault(opts.fontColor, core_defaults.global.defaultFontColor); // render in correct colour
|
|
ctx.font = fontOpts.string;
|
|
|
|
// Horizontal
|
|
if (me.isHorizontal()) {
|
|
titleX = left + ((right - left) / 2); // midpoint of the width
|
|
titleY = top + offset;
|
|
maxWidth = right - left;
|
|
} else {
|
|
titleX = opts.position === 'left' ? left + offset : right - offset;
|
|
titleY = top + ((bottom - top) / 2);
|
|
maxWidth = bottom - top;
|
|
rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5);
|
|
}
|
|
|
|
ctx.save();
|
|
ctx.translate(titleX, titleY);
|
|
ctx.rotate(rotation);
|
|
ctx.textAlign = 'center';
|
|
ctx.textBaseline = 'middle';
|
|
|
|
var text = opts.text;
|
|
if (helpers$1.isArray(text)) {
|
|
var y = 0;
|
|
for (var i = 0; i < text.length; ++i) {
|
|
ctx.fillText(text[i], 0, y, maxWidth);
|
|
y += lineHeight;
|
|
}
|
|
} else {
|
|
ctx.fillText(text, 0, 0, maxWidth);
|
|
}
|
|
|
|
ctx.restore();
|
|
}
|
|
});
|
|
|
|
function createNewTitleBlockAndAttach(chart, titleOpts) {
|
|
var title = new Title({
|
|
ctx: chart.ctx,
|
|
options: titleOpts,
|
|
chart: chart
|
|
});
|
|
|
|
core_layouts.configure(chart, title, titleOpts);
|
|
core_layouts.addBox(chart, title);
|
|
chart.titleBlock = title;
|
|
}
|
|
|
|
var plugin_title = {
|
|
id: 'title',
|
|
|
|
/**
|
|
* Backward compatibility: since 2.1.5, the title is registered as a plugin, making
|
|
* Chart.Title obsolete. To avoid a breaking change, we export the Title as part of
|
|
* the plugin, which one will be re-exposed in the chart.js file.
|
|
* https://github.com/chartjs/Chart.js/pull/2640
|
|
* @private
|
|
*/
|
|
_element: Title,
|
|
|
|
beforeInit: function(chart) {
|
|
var titleOpts = chart.options.title;
|
|
|
|
if (titleOpts) {
|
|
createNewTitleBlockAndAttach(chart, titleOpts);
|
|
}
|
|
},
|
|
|
|
beforeUpdate: function(chart) {
|
|
var titleOpts = chart.options.title;
|
|
var titleBlock = chart.titleBlock;
|
|
|
|
if (titleOpts) {
|
|
helpers$1.mergeIf(titleOpts, core_defaults.global.title);
|
|
|
|
if (titleBlock) {
|
|
core_layouts.configure(chart, titleBlock, titleOpts);
|
|
titleBlock.options = titleOpts;
|
|
} else {
|
|
createNewTitleBlockAndAttach(chart, titleOpts);
|
|
}
|
|
} else if (titleBlock) {
|
|
core_layouts.removeBox(chart, titleBlock);
|
|
delete chart.titleBlock;
|
|
}
|
|
}
|
|
};
|
|
|
|
var plugins = {};
|
|
var filler = plugin_filler;
|
|
var legend = plugin_legend;
|
|
var title = plugin_title;
|
|
plugins.filler = filler;
|
|
plugins.legend = legend;
|
|
plugins.title = title;
|
|
|
|
/**
|
|
* @namespace Chart
|
|
*/
|
|
|
|
|
|
core_controller.helpers = helpers$1;
|
|
|
|
// @todo dispatch these helpers into appropriated helpers/helpers.* file and write unit tests!
|
|
core_helpers();
|
|
|
|
core_controller._adapters = core_adapters;
|
|
core_controller.Animation = core_animation;
|
|
core_controller.animationService = core_animations;
|
|
core_controller.controllers = controllers;
|
|
core_controller.DatasetController = core_datasetController;
|
|
core_controller.defaults = core_defaults;
|
|
core_controller.Element = core_element;
|
|
core_controller.elements = elements;
|
|
core_controller.Interaction = core_interaction;
|
|
core_controller.layouts = core_layouts;
|
|
core_controller.platform = platform;
|
|
core_controller.plugins = core_plugins;
|
|
core_controller.Scale = core_scale;
|
|
core_controller.scaleService = core_scaleService;
|
|
core_controller.Ticks = core_ticks;
|
|
core_controller.Tooltip = core_tooltip;
|
|
|
|
// Register built-in scales
|
|
|
|
core_controller.helpers.each(scales, function(scale, type) {
|
|
core_controller.scaleService.registerScaleType(type, scale, scale._defaults);
|
|
});
|
|
|
|
// Load to register built-in adapters (as side effects)
|
|
|
|
|
|
// Loading built-in plugins
|
|
|
|
for (var k in plugins) {
|
|
if (plugins.hasOwnProperty(k)) {
|
|
core_controller.plugins.register(plugins[k]);
|
|
}
|
|
}
|
|
|
|
core_controller.platform.initialize();
|
|
|
|
var src = core_controller;
|
|
if (typeof window !== 'undefined') {
|
|
window.Chart = core_controller;
|
|
}
|
|
|
|
// DEPRECATIONS
|
|
|
|
/**
|
|
* Provided for backward compatibility, not available anymore
|
|
* @namespace Chart.Chart
|
|
* @deprecated since version 2.8.0
|
|
* @todo remove at version 3
|
|
* @private
|
|
*/
|
|
core_controller.Chart = core_controller;
|
|
|
|
/**
|
|
* Provided for backward compatibility, not available anymore
|
|
* @namespace Chart.Legend
|
|
* @deprecated since version 2.1.5
|
|
* @todo remove at version 3
|
|
* @private
|
|
*/
|
|
core_controller.Legend = plugins.legend._element;
|
|
|
|
/**
|
|
* Provided for backward compatibility, not available anymore
|
|
* @namespace Chart.Title
|
|
* @deprecated since version 2.1.5
|
|
* @todo remove at version 3
|
|
* @private
|
|
*/
|
|
core_controller.Title = plugins.title._element;
|
|
|
|
/**
|
|
* Provided for backward compatibility, use Chart.plugins instead
|
|
* @namespace Chart.pluginService
|
|
* @deprecated since version 2.1.5
|
|
* @todo remove at version 3
|
|
* @private
|
|
*/
|
|
core_controller.pluginService = core_controller.plugins;
|
|
|
|
/**
|
|
* Provided for backward compatibility, inheriting from Chart.PlugingBase has no
|
|
* effect, instead simply create/register plugins via plain JavaScript objects.
|
|
* @interface Chart.PluginBase
|
|
* @deprecated since version 2.5.0
|
|
* @todo remove at version 3
|
|
* @private
|
|
*/
|
|
core_controller.PluginBase = core_controller.Element.extend({});
|
|
|
|
/**
|
|
* Provided for backward compatibility, use Chart.helpers.canvas instead.
|
|
* @namespace Chart.canvasHelpers
|
|
* @deprecated since version 2.6.0
|
|
* @todo remove at version 3
|
|
* @private
|
|
*/
|
|
core_controller.canvasHelpers = core_controller.helpers.canvas;
|
|
|
|
/**
|
|
* Provided for backward compatibility, use Chart.layouts instead.
|
|
* @namespace Chart.layoutService
|
|
* @deprecated since version 2.7.3
|
|
* @todo remove at version 3
|
|
* @private
|
|
*/
|
|
core_controller.layoutService = core_controller.layouts;
|
|
|
|
/**
|
|
* Provided for backward compatibility, not available anymore.
|
|
* @namespace Chart.LinearScaleBase
|
|
* @deprecated since version 2.8
|
|
* @todo remove at version 3
|
|
* @private
|
|
*/
|
|
core_controller.LinearScaleBase = scale_linearbase;
|
|
|
|
/**
|
|
* Provided for backward compatibility, instead we should create a new Chart
|
|
* by setting the type in the config (`new Chart(id, {type: '{chart-type}'}`).
|
|
* @deprecated since version 2.8.0
|
|
* @todo remove at version 3
|
|
*/
|
|
core_controller.helpers.each(
|
|
[
|
|
'Bar',
|
|
'Bubble',
|
|
'Doughnut',
|
|
'Line',
|
|
'PolarArea',
|
|
'Radar',
|
|
'Scatter'
|
|
],
|
|
function(klass) {
|
|
core_controller[klass] = function(ctx, cfg) {
|
|
return new core_controller(ctx, core_controller.helpers.merge(cfg || {}, {
|
|
type: klass.charAt(0).toLowerCase() + klass.slice(1)
|
|
}));
|
|
};
|
|
}
|
|
);
|
|
|
|
return src;
|
|
|
|
})));
|
|
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
|
// listed below.
|
|
//
|
|
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's
|
|
// vendor/assets/javascripts directory can be referenced here using a relative path.
|
|
//
|
|
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
|
// compiled file. JavaScript code in this file should be added after the last require_* statement.
|
|
//
|
|
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
|
|
// about supported directives.
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;
|