/******/ (() => { // webpackBootstrap /******/ var __webpack_modules__ = ([ /* 0 */, /* 1 */ /***/ (() => { /*** @module up */ window.up = { version: '2.6.0' }; /***/ }), /* 2 */ /***/ (() => { up.mockable = function (originalFn) { let spy; const mockableFn = function () { return (spy || originalFn).apply(null, arguments); }; mockableFn.mock = () => spy = jasmine.createSpy('mockable', originalFn); document.addEventListener('up:framework:reset', () => spy = null); return mockableFn; }; /***/ }), /* 3 */ /***/ (() => { /*- Utility functions ================= The `up.util` module contains functions to facilitate the work with basic JavaScript values like lists, strings or functions. You will recognize many functions form other utility libraries like [Lodash](https://lodash.com/). While feature parity with Lodash is not a goal of `up.util`, you might find it sufficient to not include another library in your asset bundle. @module up.util */ up.util = (function () { /*- A function that does nothing. @function up.util.noop @experimental */ function noop() { } /*- A function that returns a resolved promise. @function up.util.asyncNoop @internal */ function asyncNoop() { return Promise.resolve(); } /*- Ensures that the given function can only be called a single time. Subsequent calls will return the return value of the first call. Note that this is a simple implementation that doesn't distinguish between argument lists. @function up.util.memoize @internal */ function memoize(func) { let cachedValue, cached; return function (...args) { if (cached) { return cachedValue; } else { cached = true; return cachedValue = func.apply(this, args); } }; } /*- Returns if the given port is the default port for the given protocol. @function up.util.isStandardPort @internal */ function isStandardPort(protocol, port) { port = port.toString(); return (((port === "") || (port === "80")) && (protocol === 'http:')) || ((port === "443") && (protocol === 'https:')); } const NORMALIZE_URL_DEFAULTS = { host: 'cross-domain', }; /*- Returns a normalized version of the given URL string. Two URLs that point to the same resource should normalize to the same string. ### Comparing normalized URLs The main purpose of this function is to normalize two URLs for string comparison: ```js up.util.normalizeURL('http://current-host/path') === up.util.normalizeURL('/path') // => true ``` By default the hostname is only included if it points to a different origin: ```js up.util.normalizeURL('http://current-host/path') // => '/path' up.util.normalizeURL('http://other-host/path') // => 'http://other-host/path' ``` Relative paths are normalized to absolute paths: ```js up.util.normalizeURL('index.html') // => '/path/index.html' ``` ### Excluding URL components You may pass options to exclude URL components from the normalized string: ```js up.util.normalizeURL('/foo?query=bar', { query: false }) => '/foo' up.util.normalizeURL('/bar#hash', { hash: false }) => '/bar' ``` ### Limitations - Username and password are always omitted from the normalized URL. - Only `http` and `https` schemes are supported. @function up.util.normalizeURL @param {boolean} [options.host='cross-domain'] Whether to include protocol, hostname and port in the normalized URL. When set to `'cross-domain'` (the default), the host is only included if it differ's from the page's hostname. The port is omitted if the port is the standard port for the given protocol, e.g. `:443` for `https://`. @param {boolean} [options.hash=true] Whether to include an `#hash` anchor in the normalized URL. @param {boolean} [options.search=true] Whether to include a `?query` string in the normalized URL. @param {boolean} [options.trailingSlash=true] Whether to include a trailing slash from the pathname. @return {string} The normalized URL. @experimental */ function normalizeURL(urlOrAnchor, options) { options = newOptions(options, NORMALIZE_URL_DEFAULTS); const parts = parseURL(urlOrAnchor); let normalized = ''; if (options.host === 'cross-domain') { options.host = isCrossOrigin(parts); } if (options.host) { normalized += parts.protocol + "//" + parts.hostname; // Once we drop IE11 we can just use { host }, which contains port and hostname // and also handles standard ports. // See https://developer.mozilla.org/en-US/docs/Web/API/URL/host if (!isStandardPort(parts.protocol, parts.port)) { normalized += `:${parts.port}`; } } let { pathname } = parts; if (options.trailingSlash === false && pathname !== '/') { pathname = pathname.replace(/\/$/, ''); } normalized += pathname; if (options.search !== false) { normalized += parts.search; } if (options.hash !== false) { normalized += parts.hash; } return normalized; } function matchURLs(leftURL, rightURL) { return normalizeURL(leftURL) === normalizeURL(rightURL); } // We're calling isCrossOrigin() a lot. // Accessing location.protocol and location.hostname every time // is much slower than comparing cached strings. // https://jsben.ch/kBATt const APP_PROTOCOL = location.protocol; const APP_HOSTNAME = location.hostname; function isCrossOrigin(urlOrAnchor) { // If the given URL does not contain a hostname we know it cannot be cross-origin. // In that case we don't need to parse the URL. if (isString(urlOrAnchor) && (urlOrAnchor.indexOf('//') === -1)) { return false; } const parts = parseURL(urlOrAnchor); return (APP_HOSTNAME !== parts.hostname) || (APP_PROTOCOL !== parts.protocol); } /*- Parses the given URL into components such as hostname and path. If the given URL is not fully qualified, it is assumed to be relative to the current page. ### Example ```js let parsed = up.util.parseURL('/path?foo=value') parsed.pathname // => '/path' parsed.search // => '/?foo=value' parsed.hash // => '' ``` @function up.util.parseURL @return {Object} The parsed URL as an object with `protocol`, `hostname`, `port`, `pathname`, `search` and `hash` properties. @stable */ function parseURL(urlOrLink) { let link; if (isJQuery(urlOrLink)) { // In case someone passed us a $link, unwrap it link = up.element.get(urlOrLink); } else if (urlOrLink.pathname) { // If we are handed a parsed URL, just return it link = urlOrLink; } else { link = document.createElement('a'); link.href = urlOrLink; } // In IE11 the #hostname and #port properties of unqualified URLs are empty strings. // We can fix this by setting the link's { href } on the link itself. if (!link.hostname) { link.href = link.href; // eslint-disable-line no-self-assign } // Some IEs don't include a leading slash in the #pathname property. // We have confirmed this in IE11 and earlier. if (link.pathname[0] !== '/') { // Only copy the link into an object when we need to (to change a property). // Note that we're parsing a lot of URLs for [up-active]. link = pick(link, ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash']); link.pathname = '/' + link.pathname; } return link; } /*- @function up.util.normalizeMethod @internal */ function normalizeMethod(method) { return method ? method.toUpperCase() : 'GET'; } /*- @function up.util.methodAllowsPayload @internal */ function methodAllowsPayload(method) { return (method !== 'GET') && (method !== 'HEAD'); } // Remove with IE11 function assignPolyfill(target, ...sources) { for (let source of sources) { for (let key in source) { target[key] = source[key]; } } return target; } /*- Merge the own properties of one or more `sources` into the `target` object. @function up.util.assign @param {Object} target @param {Array} sources... @stable */ const assign = Object.assign || assignPolyfill; // Remove with IE11 function valuesPolyfill(object) { return Object.keys(object).map((key) => object[key]); } /*- Returns an array of values of the given object. @function up.util.values @param {Object} object @return {Array} @stable */ const objectValues = Object.values || valuesPolyfill; function iteratee(block) { if (isString(block)) { return item => item[block]; } else { return block; } } /*- Translate all items in an array to new array of items. @function up.util.map @param {Array} array @param {Function(element, index): any|String} block A function that will be called with each element and (optional) iteration index. You can also pass a property name as a String, which will be collected from each item in the array. @return {Array} A new array containing the result of each function call. @stable */ function map(array, block) { if (array.length === 0) { return []; } block = iteratee(block); let mapped = []; for (let i = 0; i < array.length; i++) { let element = array[i]; mapped.push(block(element, i)); } return mapped; } /*- @function up.util.mapObject @internal */ function mapObject(array, pairer) { const merger = function (object, pair) { object[pair[0]] = pair[1]; return object; }; return map(array, pairer).reduce(merger, {}); } /*- Calls the given function for each element (and, optional, index) of the given array. @function up.util.each @param {Array} array @param {Function(element, index)} block A function that will be called with each element and (optional) iteration index. @stable */ function each(array, block) { // note that the native Array.forEach is very slow (https://jsperf.com/fast-array-foreach) for (let i = 0; i < array.length; i++) { block(array[i], i); } } function eachIterator(iterator, callback) { let entry; while ((entry = iterator.next()) && !entry.done) { callback(entry.value); } } /*- Returns whether the given argument is `null`. @function up.util.isNull @param object @return {boolean} @stable */ function isNull(object) { return object === null; } /*- Returns whether the given argument is `undefined`. @function up.util.isUndefined @param object @return {boolean} @stable */ function isUndefined(object) { return object === undefined; } /*- Returns whether the given argument is not `undefined`. @function up.util.isDefined @param object @return {boolean} @stable */ const isDefined = negate(isUndefined); /*- Returns whether the given argument is either `undefined` or `null`. Note that empty strings or zero are *not* considered to be "missing". For the opposite of `up.util.isMissing()` see [`up.util.isGiven()`](/up.util.isGiven). @function up.util.isMissing @param object @return {boolean} @stable */ function isMissing(object) { return isUndefined(object) || isNull(object); } /*- Returns whether the given argument is neither `undefined` nor `null`. Note that empty strings or zero *are* considered to be "given". For the opposite of `up.util.isGiven()` see [`up.util.isMissing()`](/up.util.isMissing). @function up.util.isGiven @param object @return {boolean} @stable */ const isGiven = negate(isMissing); // isNan = (object) -> // isNumber(value) && value != +value /*- Return whether the given argument is considered to be blank. By default, this function returns `true` for: - `undefined` - `null` - Empty strings - Empty arrays - A plain object without own enumerable properties All other arguments return `false`. To check implement blank-ness checks for user-defined classes, see `up.util.isBlank.key`. @function up.util.isBlank @param value The value is to check. @return {boolean} Whether the value is blank. @stable */ function isBlank(value) { if (isMissing(value)) { return true; } if (isObject(value) && value[isBlank.key]) { return value[isBlank.key](); } if (isString(value) || isList(value)) { return value.length === 0; } if (isOptions(value)) { return Object.keys(value).length === 0; } return false; } /*- This property contains the name of a method that user-defined classes may implement to hook into the `up.util.isBlank()` protocol. ### Example We have a user-defined `Account` class that we want to use with `up.util.isBlank()`: ```js class Account { constructor(email) { this.email = email } [up.util.isBlank.key]() { return up.util.isBlank(this.email) } } ``` Note that the protocol method is not actually named `'up.util.isBlank.key'`. Instead it is named after the *value* of the `up.util.isBlank.key` property. To do so, the code sample above is using a [computed property name](https://medium.com/front-end-weekly/javascript-object-creation-356e504173a8) in square brackets. We may now use `Account` instances with `up.util.isBlank()`: ```js let foo = new Account('foo@foo.com') let bar = new Account('') console.log(up.util.isBlank(foo)) // prints false console.log(up.util.isBlank(bar)) // prints true ``` @property up.util.isBlank.key @experimental */ isBlank.key = 'up.util.isBlank'; /*- Returns the given argument if the argument is [present](/up.util.isPresent), otherwise returns `undefined`. @function up.util.presence @param value @param {Function(value): boolean} [tester=up.util.isPresent] The function that will be used to test whether the argument is present. @return {any|undefined} @stable */ function presence(value, tester = isPresent) { if (tester(value)) { return value; } } /*- Returns whether the given argument is not [blank](/up.util.isBlank). @function up.util.isPresent @param object @return {boolean} @stable */ const isPresent = negate(isBlank); /*- Returns whether the given argument is a function. @function up.util.isFunction @param object @return {boolean} @stable */ function isFunction(object) { return typeof (object) === 'function'; } /*- Returns whether the given argument is a string. @function up.util.isString @param object @return {boolean} @stable */ function isString(object) { return (typeof (object) === 'string') || object instanceof String; } /*- Returns whether the given argument is a boolean value. @function up.util.isBoolean @param object @return {boolean} @stable */ function isBoolean(object) { return (typeof (object) === 'boolean') || object instanceof Boolean; } /*- Returns whether the given argument is a number. Note that this will check the argument's *type*. It will return `false` for a string like `"123"`. @function up.util.isNumber @param object @return {boolean} @stable */ function isNumber(object) { return (typeof (object) === 'number') || object instanceof Number; } /*- Returns whether the given argument is an options hash, Differently from [`up.util.isObject()`], this returns false for functions, jQuery collections, promises, `FormData` instances and arrays. @function up.util.isOptions @param object @return {boolean} @internal */ function isOptions(object) { return (typeof (object) === 'object') && !isNull(object) && (isUndefined(object.constructor) || (object.constructor === Object)); } /*- Returns whether the given argument is an object. This also returns `true` for functions, which may behave like objects in JavaScript. @function up.util.isObject @param object @return {boolean} @stable */ function isObject(object) { const typeOfResult = typeof (object); return ((typeOfResult === 'object') && !isNull(object)) || (typeOfResult === 'function'); } /*- Returns whether the given argument is a [DOM element](https://developer.mozilla.org/de/docs/Web/API/Element). @function up.util.isElement @param object @return {boolean} @stable */ function isElement(object) { return object instanceof Element; } /*- Returns whether the given argument is a [regular expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp). @function up.util.isRegExp @param object @return {boolean} @internal */ function isRegExp(object) { return object instanceof RegExp; } /*- Returns whether the given argument is a [jQuery collection](https://learn.jquery.com/using-jquery-core/jquery-object/). @function up.util.isJQuery @param object @return {boolean} @stable */ function isJQuery(object) { return up.browser.canJQuery() && object instanceof jQuery; } /*- @function up.util.isElementish @param object @return {boolean} @internal */ function isElementish(object) { return !!(object && (object.addEventListener || object[0]?.addEventListener)); } /*- Returns whether the given argument is an object with a `then` method. @function up.util.isPromise @param object @return {boolean} @stable */ function isPromise(object) { return isObject(object) && isFunction(object.then); } /*- Returns whether the given argument is an array. @function up.util.isArray @param object @return {boolean} @stable */ // https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray const { isArray } = Array; /*- Returns whether the given argument is a `FormData` instance. Always returns `false` in browsers that don't support `FormData`. @function up.util.isFormData @param object @return {boolean} @internal */ function isFormData(object) { return object instanceof FormData; } /*- Converts the given [array-like value](/up.util.isList) into an array. If the given value is already an array, it is returned unchanged. @function up.util.toArray @param object @return {Array} @stable */ function toArray(value) { return isArray(value) ? value : copyArrayLike(value); } /*- Returns whether the given argument is an array-like value. Return true for `Array`, a [`NodeList`](https://developer.mozilla.org/en-US/docs/Web/API/NodeList), the [arguments object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments) or a jQuery collection. Use [`up.util.isArray()`](/up.util.isArray) to test whether a value is an actual `Array`. @function up.util.isList @param value @return {boolean} @stable */ function isList(value) { return isArray(value) || isNodeList(value) || isArguments(value) || isJQuery(value) || isHTMLCollection(value); } /*- Returns whether the given value is a [`NodeList`](https://developer.mozilla.org/en-US/docs/Web/API/NodeList). `NodeLists` are array-like objects returned by [`document.querySelectorAll()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelectorAll). @function up.util.isNodeList @param value @return {boolean} @internal */ function isNodeList(value) { return value instanceof NodeList; } function isHTMLCollection(value) { return value instanceof HTMLCollection; } /*- Returns whether the given value is an [arguments object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments). @function up.util.isArguments @param value @return {boolean} @internal */ function isArguments(value) { return Object.prototype.toString.call(value) === '[object Arguments]'; } function nullToUndefined(value) { if (!isNull(value)) { return value; } } /*- Returns the given value if it is [array-like](/up.util.isList), otherwise returns an array with the given value as its only element. ### Example ```js up.util.wrapList([1, 2, 3]) // => [1, 2, 3] up.util.wrapList('foo') // => ['foo'] ``` @function up.util.wrapList @param {any} value @return {Array|NodeList|jQuery} @experimental */ function wrapList(value) { if (isList(value)) { return value; } else if (isMissing(value)) { return []; } else { return [value]; } } /*- Returns a shallow copy of the given value. ### Copying protocol - By default `up.util.copy()` can copy [array-like values](/up.util.isList), plain objects and `Date` instances. - Array-like objects are copied into new arrays. - Unsupported types of values are returned unchanged. - To make the copying protocol work with user-defined class, see `up.util.copy.key`. - Immutable objects, like strings or numbers, do not need to be copied. @function up.util.copy @param {any} object @return {any} @stable */ function copy(value) { if (isObject(value) && value[copy.key]) { value = value[copy.key](); } else if (isList(value)) { value = copyArrayLike(value); } else if (isOptions(value)) { value = assign({}, value); } return value; } function copyArrayLike(arrayLike) { return Array.prototype.slice.call(arrayLike); } /*- This property contains the name of a method that user-defined classes may implement to hook into the `up.util.copy()` protocol. ### Example We have a user-defined `Account` class that we want to use with `up.util.copy()`: ```js class Account { constructor(email) { this.email = email } [up.util.copy.key]() { return new Account(this.email) } } ``` Note that the protocol method is not actually named `'up.util.copy.key'`. Instead it is named after the *value* of the `up.util.copy.key` property. To do so, the code sample above is using a [computed property name](https://medium.com/front-end-weekly/javascript-object-creation-356e504173a8) in square brackets. We may now use `Account` instances with `up.util.copy()`: ``` original = new User('foo@foo.com') copy = up.util.copy(original) console.log(copy.email) // prints 'foo@foo.com' original.email = 'bar@bar.com' // change the original console.log(copy.email) // still prints 'foo@foo.com' ``` @property up.util.copy.key @param {string} key @experimental */ copy.key = 'up.util.copy'; // Implement up.util.copy protocol for Date Date.prototype[copy.key] = function () { return new Date(+this); }; // ###** // Returns a deep copy of the given array or object. // // @function up.util.deepCopy // @param {Object|Array} object // @return {Object|Array} // @internal // ### // deepCopy = (object) -> // copy(object, true) /*- Creates a new object by merging together the properties from the given objects. @function up.util.merge @param {Array} sources... @return Object @stable */ function merge(...sources) { return assign({}, ...sources); } /*- @function up.util.mergeDefined @param {Array} sources... @return Object @internal */ function mergeDefined(...sources) { const result = {}; for (let source of sources) { if (source) { for (let key in source) { const value = source[key]; if (isDefined(value)) { result[key] = value; } } } } return result; } /*- Creates an options hash from the given argument and some defaults. The semantics of this function are confusing. We want to get rid of this in the future. @function up.util.options @param {Object} object @param {Object} [defaults] @return {Object} @internal */ function newOptions(object, defaults) { if (defaults) { return merge(defaults, object); } else if (object) { return copy(object); } else { return {}; } } function parseArgIntoOptions(args, argKey) { let options = extractOptions(args); if (isDefined(args[0])) { options = copy(options); options[argKey] = args[0]; } return options; } /*- Passes each element in the given [array-like value](/up.util.isList) to the given function. Returns the first element for which the function returns a truthy value. If no object matches, returns `undefined`. @function up.util.find @param {List} list @param {Function(value): boolean} tester @return {T|undefined} @stable */ function findInList(list, tester) { tester = iteratee(tester); let match; for (let element of list) { if (tester(element)) { match = element; break; } } return match; } /*- Returns whether the given function returns a truthy value for any element in the given [array-like value](/up.util.isList). @function up.util.some @param {List} list @param {Function(value, index): boolean} tester A function that will be called with each element and (optional) iteration index. @return {boolean} @stable */ function some(list, tester) { return !!findResult(list, tester); } /*- Consecutively calls the given function which each element in the given array. Returns the first truthy return value. Returned `undefined` iff the function does not return a truthy value for any element in the array. @function up.util.findResult @param {Array} array @param {Function(element): any} tester A function that will be called with each element and (optional) iteration index. @return {any|undefined} @experimental */ function findResult(array, tester) { tester = iteratee(tester); for (let i = 0; i < array.length; i++) { const result = tester(array[i], i); if (result) { return result; } } } /*- Returns whether the given function returns a truthy value for all elements in the given [array-like value](/up.util.isList). @function up.util.every @param {List} list @param {Function(element, index): boolean} tester A function that will be called with each element and (optional) iteration index. @return {boolean} @experimental */ function every(list, tester) { tester = iteratee(tester); let match = true; for (let i = 0; i < list.length; i++) { if (!tester(list[i], i)) { match = false; break; } } return match; } /*- Returns all elements from the given array that are neither `null` or `undefined`. @function up.util.compact @param {Array} array @return {Array} @stable */ function compact(array) { return filterList(array, isGiven); } function compactObject(object) { return pickBy(object, isGiven); } /*- Returns the given array without duplicates. @function up.util.uniq @param {Array} array @return {Array} @stable */ function uniq(array) { if (array.length < 2) { return array; } return setToArray(arrayToSet(array)); } /*- This function is like [`uniq`](/up.util.uniq), accept that the given function is invoked for each element to generate the value for which uniquness is computed. @function up.util.uniqBy @param {Array} array @param {Function(value): any} array @return {Array} @experimental */ function uniqBy(array, mapper) { if (array.length < 2) { return array; } mapper = iteratee(mapper); const seenElements = new Set(); return filterList(array, function (elem, index) { const mapped = mapper(elem, index); if (seenElements.has(mapped)) { return false; } else { seenElements.add(mapped); return true; } }); } /*- @function up.util.setToArray @internal */ function setToArray(set) { const array = []; set.forEach(elem => array.push(elem)); return array; } /*- @function up.util.arrayToSet @internal */ function arrayToSet(array) { const set = new Set(); array.forEach(elem => set.add(elem)); return set; } /*- Returns all elements from the given [array-like value](/up.util.isList) that return a truthy value when passed to the given function. @function up.util.filter @param {List} list @param {Function(value, index): boolean} tester @return {Array} @stable */ function filterList(list, tester) { tester = iteratee(tester); const matches = []; each(list, function (element, index) { if (tester(element, index)) { return matches.push(element); } }); return matches; } /*- Returns all elements from the given [array-like value](/up.util.isList) that do not return a truthy value when passed to the given function. @function up.util.reject @param {List} list @param {Function(element, index): boolean} tester @return {Array} @stable */ function reject(list, tester) { tester = negate(iteratee(tester)); return filterList(list, tester); } /*- Returns the intersection of the given two arrays. Implementation is not optimized. Don't use it for large arrays. @function up.util.intersect @internal */ function intersect(array1, array2) { return filterList(array1, element => contains(array2, element)); } /*- Waits for the given number of milliseconds, the runs the given callback. Instead of `up.util.timer(0, fn)` you can also use [`up.util.task(fn)`](/up.util.task). @function up.util.timer @param {number} millis @param {Function()} callback @return {number} The ID of the scheduled timeout. You may pass this ID to `clearTimeout()` to un-schedule the timeout. @stable */ function scheduleTimer(millis, callback) { return setTimeout(callback, millis); } /*- Pushes the given function to the [JavaScript task queue](https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/) (also "macrotask queue"). Equivalent to calling `setTimeout(fn, 0)`. Also see `up.util.microtask()`. @function up.util.task @param {Function()} block @stable */ function queueTask(task) { return setTimeout(task); } /*- Pushes the given function to the [JavaScript microtask queue](https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/). @function up.util.microtask @param {Function()} task @return {Promise} A promise that is resolved with the return value of `task`. If `task` throws an error, the promise is rejected with that error. @experimental */ function queueMicrotask(task) { return Promise.resolve().then(task); } function abortableMicrotask(task) { let aborted = false; queueMicrotask(function () { if (!aborted) { return task(); } }); return () => aborted = true; } /*- Returns the last element of the given array. @function up.util.last @param {Array} array @return {T} @stable */ function last(array) { return array[array.length - 1]; } /*- Returns whether the given value contains another value. If `value` is a string, this returns whether `subValue` is a sub-string of `value`. If `value` is an array, this returns whether `subValue` is an element of `value`. @function up.util.contains @param {Array|string} value @param {Array|string} subValue @stable */ function contains(value, subValue) { return value.indexOf(subValue) >= 0; } /*- Returns whether `object`'s entries are a superset of `subObject`'s entries. @function up.util.objectContains @param {Object} object @param {Object} subObject @internal */ function objectContains(object, subObject) { const reducedValue = pick(object, Object.keys(subObject)); return isEqual(subObject, reducedValue); } /*- Returns a copy of the given object that only contains the given keys. @function up.util.pick @param {Object} object @param {Array} keys @return {Object} @stable */ function pick(object, keys) { const filtered = {}; for (let key of keys) { if (key in object) { filtered[key] = object[key]; } } return filtered; } /*- Returns a copy of the given object that only contains properties that pass the given tester function. @function up.util.pickBy @param {Object} object @param {Function(string, string, object): boolean} tester A function that will be called with each property. The arguments are the property value, key and the entire object. @return {Object} @experimental */ function pickBy(object, tester) { tester = iteratee(tester); const filtered = {}; for (let key in object) { const value = object[key]; if (tester(value, key, object)) { filtered[key] = object[key]; } } return filtered; } /*- Returns a copy of the given object that contains all except the given keys. @function up.util.omit @param {Object} object @param {Array} keys @stable */ function omit(object, keys) { return pickBy(object, (_value, key) => !contains(keys, key)); } /*- Returns a promise that will never be resolved. @function up.util.unresolvablePromise @internal */ function unresolvablePromise() { return new Promise(noop); } /*- Removes the given element from the given array. This changes the given array. @function up.util.remove @param {Array} array The array to change. @param {T} element The element to remove. @return {T|undefined} The removed element, or `undefined` if the array didn't contain the element. @stable */ function remove(array, element) { const index = array.indexOf(element); if (index >= 0) { array.splice(index, 1); return element; } } /*- If the given `value` is a function, calls the function with the given `args`. Otherwise it just returns `value`. ### Example ```js up.util.evalOption(5) // => 5 let fn = () => 1 + 2 up.util.evalOption(fn) // => 3 ``` @function up.util.evalOption @param {any} value @param {Array} ...args @return {any} @experimental */ function evalOption(value, ...args) { return isFunction(value) ? value(...args) : value; } const ESCAPE_HTML_ENTITY_MAP = { "&": "&", "<": "<", ">": ">", '"': '"', "'": ''' }; /*- Escapes the given string of HTML by replacing control chars with their HTML entities. @function up.util.escapeHTML @param {string} string The text that should be escaped. @stable */ function escapeHTML(string) { return string.replace(/[&<>"']/g, char => ESCAPE_HTML_ENTITY_MAP[char]); } /*- @function up.util.escapeRegExp @internal */ function escapeRegExp(string) { // From https://github.com/benjamingr/RegExp.escape return string.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&'); } /*- Deletes the property with the given key from the given object and returns its value. @function up.util.pluckKey @param {Object} object @param {string} key @return {any} @experimental */ function pluckKey(object, key) { const value = object[key]; delete object[key]; return value; } function renameKey(object, oldKey, newKey) { return object[newKey] = pluckKey(object, oldKey); } function extractLastArg(args, tester) { if (tester(last(args))) { return args.pop(); } } // extractFirstArg = (args, tester) -> // firstArg = args[0] // if tester(firstArg) // return args.shift() function extractCallback(args) { return extractLastArg(args, isFunction); } function extractOptions(args) { return extractLastArg(args, isOptions) || {}; } // partial = (fn, fixedArgs...) -> // return (callArgs...) -> // fn.apply(this, fixedArgs.concat(callArgs)) // // partialRight = (fn, fixedArgs...) -> // return (callArgs...) -> // fn.apply(this, callArgs.concat(fixedArgs)) //function throttle(callback, limit) { // From https://jsfiddle.net/jonathansampson/m7G64/ // var wait = false // Initially, we're not waiting // return function () { // We return a throttled function // if (!wait) { // If we're not waiting // callback.call() // Execute users function // wait = true // Prevent future invocations // setTimeout(function () { // After a period of time // wait = false // And allow future invocations // }, limit) // } // } //} function identity(arg) { return arg; } // ###** // ### // parsePath = (input) -> // path = [] // pattern = /([^\.\[\]\"\']+)|\[\'([^\']+?)\'\]|\[\"([^\"]+?)\"\]|\[([^\]]+?)\]/g // while match = pattern.exec(input) // path.push(match[1] || match[2] || match[3] || match[4]) // path // ###** // Given an async function that will return a promise, returns a proxy function // with an additional `.promise` attribute. // // When the proxy is called, the inner function is called. // The proxy's `.promise` attribute is available even before the function is called // and will resolve when the inner function's returned promise resolves. // // If the inner function does not return a promise, the proxy's `.promise` attribute // will resolve as soon as the inner function returns. // // @function up.util.previewable // @internal // ### // previewable = (fun) -> // deferred = newDeferred() // preview = (args...) -> // funValue = fun(args...) // # If funValue is again a Promise, it will defer resolution of `deferred` // # until `funValue` is resolved. // deferred.resolve(funValue) // funValue // preview.promise = deferred.promise() // preview /*- @function up.util.sequence @param {Array} functions @return {Function()} A function that will call all `functions` if called. @internal */ function sequence(functions) { // No need for an expensive map() if we're passed a single function. if (functions.length === 1) { return functions[0]; } return () => map(functions, fn => fn()); } // ###** // @function up.util.race // @internal // ### // race = (promises...) -> // raceDone = newDeferred() // each promises, (promise) -> // promise.then -> raceDone.resolve() // raceDone.promise() // ###** // Returns `'left'` if the center of the given element is in the left 50% of the screen. // Otherwise returns `'right'`. // // @function up.util.horizontalScreenHalf // @internal // ### // horizontalScreenHalf = (element) -> // elementDims = element.getBoundingClientRect() // elementMid = elementDims.left + 0.5 * elementDims.width // screenMid = 0.5 * up.viewport.rootWidth() // if elementMid < screenMid // 'left' // else // 'right' /*- Flattens the given `array` a single depth level. ### Example ```js let nested = [1, [2, 3], [4]] up.util.flatten(nested) // => [1, 2, 3, 4] @function up.util.flatten @param {Array} array An array which might contain other arrays @return {Array} The flattened array @experimental */ function flatten(array) { const flattened = []; for (let object of array) { if (isList(object)) { flattened.push(...object); } else { flattened.push(object); } } return flattened; } // flattenObject = (object) -> // result = {} // for key, value of object // result[key] = value // result /*- Maps each element using a mapping function, then [flattens](/up.util.flatten) the result into a new array. @function up.util.flatMap @param {Array} array @param {Function(element)} mapping @return {Array} @experimental */ function flatMap(array, block) { return flatten(map(array, block)); } /*- Returns whether the given value is truthy. @function up.util.isTruthy @internal */ function isTruthy(object) { return !!object; } /*- Sets the given callback as both fulfillment and rejection handler for the given promise. [Unlike `promise#finally()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally#Description), `up.util.always()` may change the settlement value of the given promise. @function up.util.always @internal */ function always(promise, callback) { return promise.then(callback, callback); } // mutedFinally = (promise, callback) -> // # Use finally() instead of always() so we don't accidentally // # register a rejection handler, which would prevent an "Uncaught in Exception" error. // finallyDone = promise.finally(callback) // // # Since finally's return value is itself a promise with the same state // # as `promise`, we don't want to see "Uncaught in Exception". // # If we didn't do this, we couldn't mute rejections in `promise`: // # // # promise = new Promise(...) // # promise.finally(function() { ... }) // # up.util.muteRejection(promise) // has no effect // muteRejection(finallyDone) // // # Return the original promise and *not* finally's return value. // return promise /*- Registers an empty rejection handler with the given promise. This prevents browsers from printing "Uncaught (in promise)" to the error console when the promise is rejected. This is helpful for event handlers where it is clear that no rejection handler will be registered: up.on('submit', 'form[up-target]', (event, $form) => { promise = up.submit($form) up.util.muteRejection(promise) }) Does nothing if passed a missing value. @function up.util.muteRejection @param {Promise|undefined|null} promise @return {Promise} @internal */ function muteRejection(promise) { return promise?.catch(noop); } /*- @function up.util.newDeferred @internal */ function newDeferred() { let resolveFn; let rejectFn; const nativePromise = new Promise(function (givenResolve, givenReject) { resolveFn = givenResolve; rejectFn = givenReject; }); nativePromise.resolve = resolveFn; nativePromise.reject = rejectFn; nativePromise.promise = () => nativePromise; // just return self return nativePromise; } // ###** // Calls the given block. If the block throws an exception, // a rejected promise is returned instead. // // @function up.util.rejectOnError // @internal // ### // rejectOnError = (block) -> // try // block() // catch error // Promise.reject(error) function asyncify(block) { // The side effects of this should be sync, otherwise we could // just do `Promise.resolve().then(block)`. try { return Promise.resolve(block()); } catch (error) { return Promise.reject(error); } } // sum = (list, block) -> // block = iteratee(block) // totalValue = 0 // for entry in list // entryValue = block(entry) // if isGiven(entryValue) # ignore undefined/null, like SQL would do // totalValue += entryValue // totalValue function isBasicObjectProperty(k) { return Object.prototype.hasOwnProperty(k); // eslint-disable-line no-prototype-builtins } /*- Returns whether the two arguments are equal by value. ### Comparison protocol - By default `up.util.isEqual()` can compare strings, numbers, [array-like values](/up.util.isList), plain objects and `Date` objects. - To make the copying protocol work with user-defined classes, see `up.util.isEqual.key`. - Objects without a defined comparison protocol are defined by reference (`===`). @function up.util.isEqual @param {any} a @param {any} b @return {boolean} Whether the arguments are equal by value. @experimental */ function isEqual(a, b) { if (a?.valueOf) { a = a.valueOf(); } // Date, String objects, Number objects if (b?.valueOf) { b = b.valueOf(); } // Date, String objects, Number objects if (typeof (a) !== typeof (b)) { return false; } else if (isList(a) && isList(b)) { return isEqualList(a, b); } else if (isObject(a) && a[isEqual.key]) { return a[isEqual.key](b); } else if (isOptions(a) && isOptions(b)) { const aKeys = Object.keys(a); const bKeys = Object.keys(b); if (isEqualList(aKeys, bKeys)) { return every(aKeys, aKey => isEqual(a[aKey], b[aKey])); } else { return false; } } else { return a === b; } } /*- This property contains the name of a method that user-defined classes may implement to hook into the `up.util.isEqual()` protocol. ### Example We have a user-defined `Account` class that we want to use with `up.util.isEqual()`: ``` class Account { constructor(email) { this.email = email } [up.util.isEqual.key](other) { return this.email === other.email } } ``` Note that the protocol method is not actually named `'up.util.isEqual.key'`. Instead it is named after the *value* of the `up.util.isEqual.key` property. To do so, the code sample above is using a [computed property name](https://medium.com/front-end-weekly/javascript-object-creation-356e504173a8) in square brackets. We may now use `Account` instances with `up.util.isEqual()`: ```js let one = new User('foo@foo.com') let two = new User('foo@foo.com') let three = new User('bar@bar.com') up.util.isEqual(one, two) // returns true up.util.isEqual(one, three) // returns false ``` @property up.util.isEqual.key @param {string} key @experimental */ isEqual.key = 'up.util.isEqual'; function isEqualList(a, b) { return (a.length === b.length) && every(a, (elem, index) => isEqual(elem, b[index])); } function splitValues(value, separator = ' ') { if (isString(value)) { value = value.split(separator); value = map(value, v => v.trim()); value = filterList(value, isPresent); return value; } else { return wrapList(value); } } function endsWith(string, search) { return string.substring(string.length - search.length) === search; } function simpleEase(x) { // easing: http://fooplot.com/?lang=de#W3sidHlwZSI6MCwiZXEiOiJ4PDAuNT8yKngqeDp4Kig0LXgqMiktMSIsImNvbG9yIjoiIzEzRjIxNyJ9LHsidHlwZSI6MCwiZXEiOiJzaW4oKHheMC43LTAuNSkqcGkpKjAuNSswLjUiLCJjb2xvciI6IiMxQTUyRUQifSx7InR5cGUiOjEwMDAsIndpbmRvdyI6WyItMS40NyIsIjEuNzgiLCItMC41NSIsIjEuNDUiXX1d // easing nice: sin((x^0.7-0.5)*pi)*0.5+0.5 // easing performant: x < 0.5 ? 2*x*x : x*(4 - x*2)-1 // https://jsperf.com/easings/1 // Math.sin((Math.pow(x, 0.7) - 0.5) * Math.PI) * 0.5 + 0.5 return x < 0.5 ? 2 * x * x : (x * (4 - (x * 2))) - 1; } function wrapValue(constructor, ...args) { return (args[0] instanceof constructor) ? args[0] : new constructor(...args); } // wrapArray = (objOrArray) -> // if isUndefined(objOrArray) // [] // else if isArray(objOrArray) // objOrArray // else // [objOrArray] let nextUid = 0; function uid() { return nextUid++; } /*- Returns a copy of the given list, in reversed order. @function up.util.reverse @param {List} list @return {Array} @internal */ function reverse(list) { return copy(list).reverse(); } // ###** // Returns a copy of the given `object` with the given `prefix` removed // from its camel-cased keys. // // @function up.util.unprefixKeys // @param {Object} object // @param {string} prefix // @return {Object} // @internal // ### // unprefixKeys = (object, prefix) -> // unprefixed = {} // prefixLength = prefix.length // for key, value of object // if key.indexOf(prefix) == 0 // key = unprefixCamelCase(key, prefixLength) // unprefixed[key] = value // unprefixed // replaceValue = (value, matchValue, replaceValueFn) -> // if value == matchValue // return replaceValueFn() // else // return value function renameKeys(object, renameKeyFn) { const renamed = {}; for (let key in object) { renamed[renameKeyFn(key)] = object[key]; } return renamed; } function camelToKebabCase(str) { return str.replace(/[A-Z]/g, char => '-' + char.toLowerCase()); } function prefixCamelCase(str, prefix) { return prefix + upperCaseFirst(str); } function unprefixCamelCase(str, prefix) { const pattern = new RegExp('^' + prefix + '(.+)$'); let match = str.match(pattern); if (match) { return lowerCaseFirst(match[1]); } } function lowerCaseFirst(str) { return str[0].toLowerCase() + str.slice(1); } function upperCaseFirst(str) { return str[0].toUpperCase() + str.slice(1); } function defineGetter(object, prop, get) { Object.defineProperty(object, prop, { get }); } function defineDelegates(object, props, targetProvider) { wrapList(props).forEach(function (prop) { Object.defineProperty(object, prop, { get() { const target = targetProvider.call(this); let value = target[prop]; if (isFunction(value)) { value = value.bind(target); } return value; }, set(newValue) { const target = targetProvider.call(this); target[prop] = newValue; } }); }); } function stringifyArg(arg) { let string; const maxLength = 200; let closer = ''; if (isString(arg)) { string = arg.replace(/[\n\r\t ]+/g, ' '); string = string.replace(/^[\n\r\t ]+/, ''); string = string.replace(/[\n\r\t ]$/, ''); // string = "\"#{string}\"" // closer = '"' } else if (isUndefined(arg)) { // JSON.stringify(undefined) is actually undefined string = 'undefined'; } else if (isNumber(arg) || isFunction(arg)) { string = arg.toString(); } else if (isArray(arg)) { string = `[${map(arg, stringifyArg).join(', ')}]`; closer = ']'; } else if (isJQuery(arg)) { string = `$(${map(arg, stringifyArg).join(', ')})`; closer = ')'; } else if (isElement(arg)) { string = `<${arg.tagName.toLowerCase()}`; for (let attr of ['id', 'name', 'class']) { let value = arg.getAttribute(attr); if (value) { string += ` ${attr}="${value}"`; } } string += ">"; closer = '>'; } else if (isRegExp(arg)) { string = arg.toString(); } else { // object, array try { string = JSON.stringify(arg); } catch (error) { if (error.name === 'TypeError') { string = '(circular structure)'; } else { throw error; } } } if (string.length > maxLength) { string = `${string.substr(0, maxLength)} …`; string += closer; } return string; } const SPRINTF_PLACEHOLDERS = /%[oOdisf]/g; function secondsSinceEpoch() { return Math.floor(Date.now() * 0.001); } /*- See https://developer.mozilla.org/en-US/docs/Web/API/Console#Using_string_substitutions @function up.util.sprintf @internal */ function sprintf(message, ...args) { return sprintfWithFormattedArgs(identity, message, ...args); } /*- @function up.util.sprintfWithFormattedArgs @internal */ function sprintfWithFormattedArgs(formatter, message, ...args) { if (!message) { return ''; } let i = 0; return message.replace(SPRINTF_PLACEHOLDERS, function () { let arg = args[i]; arg = formatter(stringifyArg(arg)); i += 1; return arg; }); } // Remove with IE11. // When removed we can also remove muteRejection(), as this is the only caller. function allSettled(promises) { return Promise.all(map(promises, muteRejection)); } function negate(fn) { return function (...args) { return !fn(...args); }; } return { parseURL, normalizeURL, matchURLs, normalizeMethod, methodAllowsPayload, assign, assignPolyfill, copy, copyArrayLike, merge, mergeDefined, options: newOptions, parseArgIntoOptions, each, eachIterator, map, flatMap, mapObject, findResult, some, every, find: findInList, filter: filterList, reject, intersect, compact, compactObject, uniq, uniqBy, last, isNull, isDefined, isUndefined, isGiven, isMissing, isPresent, isBlank, presence, isObject, isFunction, isString, isBoolean, isNumber, isElement, isJQuery, isElementish, isPromise, isOptions, isArray, isFormData, isNodeList, isArguments, isList, isRegExp, timer: scheduleTimer, contains, objectContains, toArray, pick, pickBy, omit, unresolvablePromise, remove, memoize, pluckKey, renameKey, extractOptions, extractCallback, noop, asyncNoop, identity, escapeHTML, escapeRegExp, sequence, evalOption, flatten, isTruthy, newDeferred, always, muteRejection, asyncify, isBasicObjectProperty, isCrossOrigin, task: queueTask, microtask: queueMicrotask, abortableMicrotask, isEqual, splitValues, endsWith, wrapList, wrapValue, simpleEase, values: objectValues, arrayToSet, setToArray, uid, upperCaseFirst, lowerCaseFirst, getter: defineGetter, delegate: defineDelegates, reverse, prefixCamelCase, unprefixCamelCase, camelToKebabCase, nullToUndefined, sprintf, sprintfWithFormattedArgs, renameKeys, timestamp: secondsSinceEpoch, allSettled, negate, }; })(); /***/ }), /* 4 */ /***/ (() => { up.error = (function () { const u = up.util; function build(message, props = {}) { if (u.isArray(message)) { message = u.sprintf(...message); } const error = new Error(message); u.assign(error, props); return error; } // Custom error classes is hard when we transpile to ES5. // Hence we create a class-like construct. // See https://webcodr.io/2018/04/why-custom-errors-in-javascript-with-babel-are-broken/ function errorInterface(name, init = build) { const fn = function (...args) { const error = init(...args); error.name = name; return error; }; fn.is = error => error.name === name; fn.async = (...args) => Promise.reject(fn(...args)); return fn; } const failed = errorInterface('up.Failed'); // Emulate the exception that aborted fetch() would throw const aborted = errorInterface('AbortError', (message) => { return build(message || 'Aborted'); }); const notImplemented = errorInterface('up.NotImplemented'); const notApplicable = errorInterface('up.NotApplicable', (change, reason) => { return build(`Cannot apply change: ${change} (${reason})`); }); const invalidSelector = errorInterface('up.InvalidSelector', (selector) => { return build(`Cannot parse selector: ${selector}`); }); function emitGlobal(error) { // Emit an ErrorEvent on window.onerror for exception tracking tools const { message } = error; up.emit(window, 'error', { message, error, log: false }); } /*- Throws a [JavaScript error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) with the given message. The message may contain [substitution marks](https://developer.mozilla.org/en-US/docs/Web/API/console#Using_string_substitutions). ### Examples up.fail('Division by zero') up.fail('Unexpected result %o', result) @function up.fail @param {string} message A message with details about the error. The message can contain [substitution marks](https://developer.mozilla.org/en-US/docs/Web/API/console#Using_string_substitutions) like `%s` or `%o`. @param {Array} vars... A list of variables to replace any substitution marks in the error message. @internal */ function fail(...args) { throw up.error.failed(args); } return { fail, failed, aborted, invalidSelector, notApplicable, notImplemented, emitGlobal }; })(); up.fail = up.error.fail; /***/ }), /* 5 */ /***/ (() => { // This object will gain properties when users load the optional unpoly-migrate.js up.migrate = { config: {} }; /***/ }), /* 6 */ /***/ (() => { /*- Browser interface ================= We tunnel some browser APIs through this module for easier mocking in tests. @module up.browser */ up.browser = (function () { const u = up.util; /*- Submits the given form with a full page load. For mocking in specs. @function up.browser.submitForm @internal */ function submitForm(form) { form.submit(); } function isIE11() { return 'ActiveXObject' in window; // this is undefined, but the key is set } function isEdge18() { // Edge 18: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19582 // Edge 92: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36 Edg/92.0.902.78 return u.contains(navigator.userAgent, ' Edge/'); } /*- Returns whether this browser supports manipulation of the current URL via [`history.pushState`](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState). When `pushState` (e.g. through [`up.follow()`](/up.follow)), it will gracefully fall back to a full page load. Note that Unpoly will not use `pushState` if the initial page was loaded with a request method other than GET. @function up.browser.canPushState @return {boolean} @internal */ function canPushState() { // We cannot use pushState if the initial request method is a POST for two reasons: // // 1. Unpoly replaces the initial state so it can handle the pop event when the // user goes back to the initial URL later. If the initial request was a POST, // Unpoly will wrongly assumed that it can restore the state by reloading with GET. // // 2. Some browsers have a bug where the initial request method is used for all // subsequently pushed states. That means if the user reloads the page on a later // GET state, the browser will wrongly attempt a POST request. // This issue affects Safari 9 and 10 (last tested in 2017-08). // Modern Firefoxes, Chromes and IE10+ don't have this behavior. // // The way that we work around this is that we don't support pushState if the // initial request method was anything other than GET (but allow the rest of the // Unpoly framework to work). This way Unpoly will fall back to full page loads until // the framework was booted from a GET request. return history.pushState && up.protocol.initialRequestMethod() === 'GET'; } /*- Returns whether this browser supports promises. @function up.browser.canPromise @return {boolean} @internal */ function canPromise() { return !!window.Promise; } const canFormatLog = u.negate(isIE11); const canPassiveEventListener = u.negate(isIE11); // Don't memoize so a build may publish window.jQuery after Unpoly was loaded function canJQuery() { return !!window.jQuery; } const canEval = u.memoize(function () { try { // Don't use eval() which would prevent minifiers from compressing local variables. return new Function('return true')(); } catch { // With a strict CSP this will be an error like: // Uncaught EvalError: call to Function() blocked by CSP return false; } }); // IE11: Use the browser.cookies API instead. function popCookie(name) { let value = document.cookie.match(new RegExp(name + "=(\\w+)"))?.[1]; if (value) { document.cookie = name + '=;Max-Age=0;Path=/'; return value; } } const getJQuery = function () { if (!canJQuery()) { up.fail('jQuery must be published as window.jQuery'); } return jQuery; }; /*- @return {boolean} @function up.browser.ensureConfirmed @param {string} options.confirm @param {boolean} options.preload @internal */ function assertConfirmed(options) { const confirmed = !options.confirm || window.confirm(options.confirm); if (!confirmed) { throw up.error.aborted('User canceled action'); } return true; } return { submitForm, canPushState, canFormatLog, canPassiveEventListener, canJQuery, canPromise, canEval, assertConfirmed, popCookie, get jQuery() { return getJQuery(); }, isIE11, isEdge18, }; })(); /***/ }), /* 7 */ /***/ (() => { /*- DOM helpers =========== The `up.element` module offers functions for DOM manipulation and traversal. It complements [native `Element` methods](https://www.w3schools.com/jsref/dom_obj_all.asp) and works across all [supported browsers](/up.browser). @module up.element */ up.element = (function () { const u = up.util; const MATCH_FN_NAME = up.browser.isIE11() ? 'msMatchesSelector' : 'matches'; /*- Returns the first descendant element matching the given selector. @function first @param {Element} [parent=document] The parent element whose descendants to search. If omitted, all elements in the `document` will be searched. @param {string} selector The CSS selector to match. @return {Element|undefined|null} The first element matching the selector. Returns `null` or `undefined` if no element macthes. @internal */ function first(...args) { const selector = args.pop(); const root = args[0] || document; return root.querySelector(selector); } /*- Returns all descendant elements matching the given selector. @function up.element.all @param {Element} [parent=document] The parent element whose descendants to search. If omitted, all elements in the `document` will be searched. @param {string} selector The CSS selector to match. @return {NodeList|Array} A list of all elements matching the selector. Returns an empty list if there are no matches. @stable */ function all(...args) { const selector = args.pop(); const root = args[0] || document; return root.querySelectorAll(selector); } /*- Returns a list of the given parent's descendants matching the given selector. The list will also include the parent element if it matches the selector itself. @function up.element.subtree @param {Element} parent The parent element for the search. @param {string} selector The CSS selector to match. @return {NodeList|Array} A list of all matching elements. @stable */ function subtree(root, selector) { const results = []; if (matches(root, selector)) { results.push(root); } results.push(...all(root, selector)); return results; } /*- Returns whether the given element is either the given root element or its descendants. @function isInSubtree @internal */ function isInSubtree(root, selectorOrElement) { const element = getOne(selectorOrElement); return root.contains(element); } /*- Returns the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree. @function up.element.closest @param {Element} element The element on which to start the search. @param {string} selector The CSS selector to match. @return {Element|null|undefined} element The matching element. Returns `null` or `undefined` if no element matches. @stable */ function closest(element, selector) { if (element.closest) { return element.closest(selector); // If the browser doesn't support Element#closest, we mimic the behavior. } else if (matches(element, selector)) { return element; } else { return ancestor(element, selector); } } /*- Returns whether the given element matches the given CSS selector. To match against a non-standard selector like `:main`, use `up.fragment.matches()` instead. @function up.element.matches @param {Element} element The element to check. @param {string} selector The CSS selector to match. @return {boolean} Whether `element` matches `selector`. @stable */ function matches(element, selector) { return element[MATCH_FN_NAME]?.(selector); } /*- @function up.element.ancestor @internal */ function ancestor(element, selector) { let parentElement = element.parentElement; if (parentElement) { if (matches(parentElement, selector)) { return parentElement; } else { return ancestor(parentElement, selector); } } } function around(element, selector) { return getList(closest(element, selector), subtree(element, selector)); } /*- Returns the native [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element) for the given value. ### Casting rules - If given an element, returns that element. - If given a CSS selector string, returns the first element matching that selector. - If given a jQuery collection , returns the first element in the collection. Throws an error if the collection contains more than one element. - If given any other argument (`undefined`, `null`, `document`, `window`…), returns the argument unchanged. @function up.element.get @param {Element} [parent=document] The parent element whose descendants to search if `value` is a CSS selector string. If omitted, all elements in the `document` will be searched. @param {Element|jQuery|string} value The value to look up. @return {Element} The obtained `Element`. @stable */ function getOne(...args) { const value = args.pop(); if (u.isElement(value)) { // Return an element before we run any other expensive checks return value; } else if (u.isString(value)) { return first(...args, value); } else if (u.isList(value)) { if (value.length > 1) { up.fail('up.element.get(): Cannot cast multiple elements (%o) to a single element', value); } return value[0]; } else { // undefined, null, Window, Document, DocumentFragment, ... return value; } } /*- Composes a list of elements from the given arguments. ### Casting rules - If given a string, returns the all elements matching that string. - If given any other argument, returns the argument [wrapped as a list](/up.util.wrapList). ### Example ```javascript $jquery = $('.jquery') // returns jQuery (2) [div.jquery, div.jquery] nodeList = document.querySelectorAll('.node') // returns NodeList (2) [div.node, div.node] element = document.querySelector('.element') // returns Element div.element selector = '.selector' // returns String '.selector' elements = up.element.list($jquery, nodeList, undefined, element, selector) // returns [div.jquery, div.jquery, div.node, div.node, div.element, div.selector] ``` @function up.element.list @param {Array|String|undefined|null>} ...args @return {Array} @internal */ function getList(...args) { return u.flatMap(args, valueToList); } function valueToList(value) { if (u.isString(value)) { return all(value); } else { return u.wrapList(value); } } // assertIsElement = (element) -> // unless u.isElement(element) // up.fail('Not an element: %o', element) /*- Removes the given element from the DOM tree. If you don't need IE11 support you may also use the built-in [`Element#remove()`](https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/remove) to the same effect. @function up.element.remove @param {Element} element The element to remove. @stable */ function remove(element) { // IE does not support Element#remove() let parent = element.parentNode; if (parent) { parent.removeChild(element); } } /*- Hides the given element. The element is hidden by setting an [inline style](https://www.codecademy.com/articles/html-inline-styles) of `{ display: none }`. Also see `up.element.show()`. @function up.element.hide @param {Element} element @stable */ function hide(element) { element.style.display = 'none'; } /*- Shows the given element. Also see `up.element.hide()`. ### Limitations The element is shown by setting an [inline style](https://www.codecademy.com/articles/html-inline-styles) of `{ display: '' }`. You might have CSS rules causing the element to remain hidden after calling `up.element.show(element)`. Unpoly will not handle such cases in order to keep this function performant. As a workaround, you may manually set the `element.style.display` property. Also see discussion in jQuery issues [#88](https://github.com/jquery/jquery.com/issues/88), [#2057](https://github.com/jquery/jquery/issues/2057) and [this WHATWG mailing list post](http://lists.w3.org/Archives/Public/public-whatwg-archive/2014Apr/0094.html). @function up.element.show @param {Element} element @stable */ function show(element) { element.style.display = ''; } /*- Display or hide the given element, depending on its current visibility. @function up.element.toggle @param {Element} element @param {boolean} [newVisible] Pass `true` to show the element or `false` to hide it. If omitted, the element will be hidden if shown and shown if hidden. @stable */ function toggle(element, newVisible) { if (newVisible == null) { newVisible = !isVisible(element); } (newVisible ? show : hide)(element); } // trace = (fn) -> // (args...) -> // console.debug("Calling %o with %o", fn, args) // fn(args...) /*- Adds or removes the given class from the given element. If you don't need IE11 support you may also use the built-in [`Element#classList.toggle(className)`](https://developer.mozilla.org/en-US/docs/Web/API/Element/classList) to the same effect. @function up.element.toggleClass @param {Element} element The element for which to add or remove the class. @param {string} className The class which should be added or removed. @param {Boolean} [newPresent] Pass `true` to add the class to the element or `false` to remove it. If omitted, the class will be added if missing and removed if present. @stable */ function toggleClass(element, klass, newPresent) { const list = element.classList; if (newPresent == null) { newPresent = !list.contains(klass); } if (newPresent) { return list.add(klass); } else { return list.remove(klass); } } function toggleAttr(element, attr, value, newPresent) { if (newPresent == null) { newPresent = !element.hasAttribute(attr); } if (newPresent) { return element.setAttribute(attr, value); } else { return element.removeAttribute(attr); } } /*- Sets all key/values from the given object as attributes on the given element. ### Example up.element.setAttrs(element, { title: 'Tooltip', tabindex: 1 }) @function up.element.setAttrs @param {Element} element The element on which to set attributes. @param {Object} attributes An object of attributes to set. @stable */ function setAttrs(element, attrs) { for (let key in attrs) { const value = attrs[key]; if (u.isGiven(value)) { element.setAttribute(key, value); } else { element.removeAttribute(key); } } } function setTemporaryAttrs(element, attrs) { const oldAttrs = {}; for (let key of Object.keys(attrs)) { oldAttrs[key] = element.getAttribute(key); } setAttrs(element, attrs); return () => setAttrs(element, oldAttrs); } /*- @function up.element.metaContent @internal */ function metaContent(name) { const selector = "meta" + attributeSelector('name', name); return first(selector)?.getAttribute('content'); } /*- @function up.element.insertBefore @internal */ function insertBefore(existingElement, newElement) { existingElement.insertAdjacentElement('beforebegin', newElement); } // insertAfter = (existingElement, newElement) -> // existingElement.insertAdjacentElement('afterend', newElement) /*- Replaces the given old element with the given new element. The old element will be removed from the DOM tree. If you don't need IE11 support you may also use the built-in [`Element#replaceWith()`](https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/replaceWith) to the same effect. @function up.element.replace @param {Element} oldElement @param {Element} newElement @stable */ function replace(oldElement, newElement) { oldElement.parentElement.replaceChild(newElement, oldElement); } /*- Creates an element matching the given CSS selector. The created element will not yet be attached to the DOM tree. Attach it with [`Element#appendChild()`](https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild) or use `up.element.affix()` to create an attached element. Use `up.hello()` to activate JavaScript behavior within the created element. ### Examples To create an element with a given tag name: element = up.element.createFromSelector('span') // element is To create an element with a given class: element = up.element.createFromSelector('.klass') // element is
To create an element with a given ID: element = up.element.createFromSelector('#foo') // element is
To create an element with a given boolean attribute: element = up.element.createFromSelector('[attr]') // element is
To create an element with a given attribute value: element = up.element.createFromSelector('[attr="value"]') // element is
You may also pass an object of attribute names/values as a second argument: element = up.element.createFromSelector('div', { attr: 'value' }) // element is
You may set the element's inner text by passing a `{ text }` option (HTML control characters will be escaped): element = up.element.createFromSelector('div', { text: 'inner text' }) // element is
inner text
You may set the element's inner HTML by passing a `{ content }` option: element = up.element.createFromSelector('div', { content: 'inner text' }) // element is
inner text
You may set inline styles by passing an object of CSS properties as a second argument: element = up.element.createFromSelector('div', { style: { color: 'red' }}) // element is
@function up.element.createFromSelector @param {string} selector The CSS selector from which to create an element. @param {Object} [attrs] An object of attributes to set on the created element. @param {Object} [attrs.text] The [text content](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent) of the created element. @param {Object} [attrs.content] The [inner HTML](https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML) of the created element. @param {Object|string} [attrs.style] An object of CSS properties that will be set as the inline style of the created element. The given object may use kebab-case or camelCase keys. You may also pass a string with semicolon-separated styles. @return {Element} The created element. @stable */ function createFromSelector(selector, attrs) { // Extract attribute values before we do anything else. // Attribute values might contain spaces, and then we would incorrectly // split depths at that space. const attrValues = []; const selectorWithoutAttrValues = selector.replace(/\[([\w-]+)(?:[~|^$*]?=(["'])?([^\2\]]*?)\2)?\]/g, function (_match, attrName, _quote, attrValue) { attrValues.push(attrValue || ''); return `[${attrName}]`; }); const depths = selectorWithoutAttrValues.split(/[ >]+/); let rootElement; let depthElement; let previousElement; for (let depthSelector of depths) { let tagName; depthSelector = depthSelector.replace(/^[\w-]+/, function (match) { tagName = match; return ''; }); depthElement = document.createElement(tagName || 'div'); if (!rootElement) { rootElement = depthElement; } depthSelector = depthSelector.replace(/#([\w-]+)/, function (_match, id) { depthElement.id = id; return ''; }); depthSelector = depthSelector.replace(/\.([\w-]+)/g, function (_match, className) { depthElement.classList.add(className); return ''; }); // If we have stripped out attrValues at the beginning of the function, // they have been replaced with the attribute name only (as "[name]"). if (attrValues.length) { depthSelector = depthSelector.replace(/\[([\w-]+)\]/g, function (_match, attrName) { depthElement.setAttribute(attrName, attrValues.shift()); return ''; }); } if (depthSelector !== '') { throw up.error.invalidSelector(selector); } previousElement?.appendChild(depthElement); previousElement = depthElement; } if (attrs) { let value; if (value = u.pluckKey(attrs, 'class')) { for (let klass of u.wrapList(value)) { rootElement.classList.add(klass); } } if (value = u.pluckKey(attrs, 'style')) { setInlineStyle(rootElement, value); } if (value = u.pluckKey(attrs, 'text')) { // Use .textContent instead of .innerText, since .textContent preserves line breaks. rootElement.textContent = value; } if (value = u.pluckKey(attrs, 'content')) { rootElement.innerHTML = value; } setAttrs(rootElement, attrs); } return rootElement; } /*- Creates an element matching the given CSS selector and attaches it to the given parent element. To create a detached element from a selector, see `up.element.createFromSelector()`. Use `up.hello()` to activate JavaScript behavior within the created element. ### Example ```js element = up.element.affix(document.body, '.klass') element.parentElement // returns document.body element.className // returns 'klass' ``` @function up.element.affix @param {Element} parent The parent to which to attach the created element. @param {string} [position='beforeend'] The position of the new element in relation to `parent`. Can be one of the following values: - `'beforebegin'`: Before `parent`, as a new sibling. - `'afterbegin'`: Just inside `parent`, before its first child. - `'beforeend'`: Just inside `parent`, after its last child. - `'afterend'`: After `parent`, as a new sibling. @param {string} selector The CSS selector from which to create an element. @param {Object} attrs An object of attributes to set on the created element. @param {Object} attrs.text The [text content](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent) of the created element. @param {Object|string} attrs.style An object of CSS properties that will be set as the inline style of the created element. The given object may use kebab-case or camelCase keys. @return {Element} The created element. @stable */ function affix(parent, ...args) { let position, selector; const attributes = u.extractOptions(args); if (args.length === 2) { [position, selector] = args; } else { position = 'beforeend'; selector = args[0]; } const element = createFromSelector(selector, attributes); // https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentElement parent.insertAdjacentElement(position, element); return element; } /*- Returns a CSS selector that matches the given element as good as possible. Alias for `up.fragment.toTarget()`. @function up.element.toSelector @param {string|Element|jQuery} The element for which to create a selector. @stable */ function toSelector(...args) { return up.fragment.toTarget(...args); } const SINGLETON_TAG_NAMES = ['HTML', 'BODY', 'HEAD', 'TITLE']; const SINGLETON_PATTERN = new RegExp('\\b(' + SINGLETON_TAG_NAMES.join('|') + ')\\b', 'i'); /*- @function up.element.isSingleton @internal */ const isSingleton = up.mockable(element => matches(element, SINGLETON_TAG_NAMES.join(','))); function isSingletonSelector(selector) { return SINGLETON_PATTERN.test(selector); } function elementTagName(element) { return element.tagName.toLowerCase(); } /*- @function up.element.attributeSelector @internal */ function attributeSelector(attribute, value) { value = value.replace(/"/g, '\\"'); return `[${attribute}="${value}"]`; } function trueAttributeSelector(attribute) { return `[${attribute}]:not([${attribute}=false])`; } function idSelector(id) { if (id.match(/^[a-z0-9\-_]+$/i)) { return `#${id}`; } else { return attributeSelector('id', id); } } /*- @function up.element.classSelector @internal */ function classSelector(klass) { klass = klass.replace(/:/g, '\\:'); return `.${klass}`; } /*- Always creates a full document with a root, even if the given `html` is only a fragment. @function up.element.createDocumentFromHTML @internal */ function createDocumentFromHTML(html) { return new DOMParser().parseFromString(html, 'text/html'); } /*- Creates an element from the given HTML fragment. Use `up.hello()` to activate JavaScript behavior within the created element. ### Example ```js element = up.element.createFromHTML('
text
') element.className // returns 'foo' element.children[0] // returns element element.children[0].textContent // returns 'text' ``` @function up.element.createFromHTML @stable */ function createFromHTML(html) { // (1) We cannot use createDocumentFromHTML() here, since up.ResponseDoc // needs to create