From d84a028763cbdc8e89f0ec2c99cf1cf76d752236 Mon Sep 17 00:00:00 2001 From: Volker Schukai <volker.schukai@schukai.com> Date: Sun, 15 Sep 2024 18:16:33 +0200 Subject: [PATCH] fix: Replace `sleep` with `requestAnimationFrame` for smoother UI #234 --- source/components/tree-menu/tree-menu.mjs | 20 +- source/data/buildtree.mjs | 126 ++--- source/data/pathfinder.mjs | 610 +++++++++++----------- source/dom/updater.mjs | 17 +- 4 files changed, 389 insertions(+), 384 deletions(-) diff --git a/source/components/tree-menu/tree-menu.mjs b/source/components/tree-menu/tree-menu.mjs index ea022d007..b5dda631c 100644 --- a/source/components/tree-menu/tree-menu.mjs +++ b/source/components/tree-menu/tree-menu.mjs @@ -217,7 +217,6 @@ class TreeMenu extends CustomElement { * @param value */ selectEntry(value) { - this.shadowRoot .querySelectorAll("[data-monster-role=entry]") .forEach((entry) => { @@ -242,7 +241,7 @@ class TreeMenu extends CustomElement { let intend = parseInt(currentNode.getAttribute(ATTRIBUTE_INTEND)); if (intend > 0) { - const refSet = new Set() + const refSet = new Set(); let ref = currentNode.previousElementSibling; while (ref && ref.hasAttribute(ATTRIBUTE_INTEND)) { const i = parseInt(ref.getAttribute(ATTRIBUTE_INTEND)); @@ -254,9 +253,9 @@ class TreeMenu extends CustomElement { if (i < intend) { if (ref.getAttribute("data-monster-state") !== "open") { ref.click(); - + //console.log(ref.getAttribute("data-monster-state") ) - + //refSet.add(ref) //console.log(ref) } @@ -326,9 +325,8 @@ function initEventHandler() { } this[preventChangeSymbol] = true; setTimeout(() => { - importEntries.call(this); - },10) - + importEntries.call(this); + }, 10); }), ); }); @@ -448,7 +446,7 @@ function initEventHandler() { } return this; -} +} /** * @private @@ -518,9 +516,9 @@ function importEntries() { } setTimeout(() => { - this.setOption("entries", options); - },5) - + this.setOption("entries", options); + }, 5); + return this; } diff --git a/source/data/buildtree.mjs b/source/data/buildtree.mjs index c88d2d590..0e3b36b9b 100644 --- a/source/data/buildtree.mjs +++ b/source/data/buildtree.mjs @@ -12,14 +12,14 @@ * SPDX-License-Identifier: AGPL-3.0 */ -import {isArray, isObject} from "../types/is.mjs"; -import {Node} from "../types/node.mjs"; -import {NodeList} from "../types/nodelist.mjs"; -import {clone} from "../util/clone.mjs"; -import {assembleParts} from "./buildmap.mjs"; -import {extend} from "./extend.mjs"; +import { isArray, isObject } from "../types/is.mjs"; +import { Node } from "../types/node.mjs"; +import { NodeList } from "../types/nodelist.mjs"; +import { clone } from "../util/clone.mjs"; +import { assembleParts } from "./buildmap.mjs"; +import { extend } from "./extend.mjs"; -export {buildTree}; +export { buildTree }; /** * @private @@ -140,69 +140,69 @@ const rootSymbol = Symbol("root"); * @since 1.26.0 */ function buildTree(subject, selector, idKey, parentIDKey, options) { - const nodes = new Map(); - - const maxDepth = 100; - - if (!isObject(options)) { - options = {}; - } - - options = extend( - {}, - { - rootReferences: [null, undefined], - filter: undefined, - }, - options, - ); - - const filter = options?.filter; - let rootReferences = options.rootReferences; - if (!isArray(rootReferences)) { - rootReferences = [rootReferences]; - } - - const childMap = assembleParts(subject, selector, filter, function (o, k, m) { - const key = o?.[idKey]; - let ref = o?.[parentIDKey]; - if (rootReferences.indexOf(ref) !== -1) ref = rootSymbol; - - if (key === undefined) { - throw new Error("the object has no value for the specified id"); - } - - o[parentSymbol] = ref; - - const node = new Node(o); - this.has(ref) - ? this.get(ref).add(node) - : this.set(ref, new NodeList().add(node)); - nodes.set(key, node); - }); - - nodes.forEach((node) => { + const nodes = new Map(); + + const maxDepth = 100; + + if (!isObject(options)) { + options = {}; + } + + options = extend( + {}, + { + rootReferences: [null, undefined], + filter: undefined, + }, + options, + ); + + const filter = options?.filter; + let rootReferences = options.rootReferences; + if (!isArray(rootReferences)) { + rootReferences = [rootReferences]; + } + + const childMap = assembleParts(subject, selector, filter, function (o, k, m) { + const key = o?.[idKey]; + let ref = o?.[parentIDKey]; + if (rootReferences.indexOf(ref) !== -1) ref = rootSymbol; + + if (key === undefined) { + throw new Error("the object has no value for the specified id"); + } + + o[parentSymbol] = ref; + + const node = new Node(o); + this.has(ref) + ? this.get(ref).add(node) + : this.set(ref, new NodeList().add(node)); + nodes.set(key, node); + }); + + nodes.forEach((node) => { const id = node?.["value"]?.[idKey]; - if (id === undefined) { - throw new Error("the object has no value for the specified id"); - } + if (id === undefined) { + throw new Error("the object has no value for the specified id"); + } if (childMap.has(id)) { - node.childNodes = childMap.get(id); - childMap.delete(id); + node.childNodes = childMap.get(id); + childMap.delete(id); } }); - const list = new NodeList(); + const list = new NodeList(); - childMap.forEach((s) => { - if (s instanceof Set) { - s.forEach((n) => { - list.add(n); - }); - } - }); + childMap.forEach((s) => { + if (s instanceof Set) { + s.forEach((n) => { + list.add(n); + }); + } + }); - return list; + return list; } diff --git a/source/data/pathfinder.mjs b/source/data/pathfinder.mjs index 98bf04194..c0fd5f10d 100644 --- a/source/data/pathfinder.mjs +++ b/source/data/pathfinder.mjs @@ -12,22 +12,22 @@ * SPDX-License-Identifier: AGPL-3.0 */ -import {Base} from "../types/base.mjs"; +import { Base } from "../types/base.mjs"; import { - isArray, - isInteger, - isObject, - isPrimitive, - isString, + isArray, + isInteger, + isObject, + isPrimitive, + isString, } from "../types/is.mjs"; -import {Stack} from "../types/stack.mjs"; +import { Stack } from "../types/stack.mjs"; import { - validateInteger, - validateBoolean, - validateString, + validateInteger, + validateBoolean, + validateString, } from "../types/validate.mjs"; -export {Pathfinder, DELIMITER, WILDCARD}; +export { Pathfinder, DELIMITER, WILDCARD }; /** * path separator @@ -89,102 +89,101 @@ const WILDCARD = "*"; * @memberOf Monster.Data */ class Pathfinder extends Base { - /** - * Creates a new instance of the constructor. - * - * @param {object} object - The object parameter for the constructor. - * - * @throws {Error} Throws an error if the provided object parameter is a simple type. - */ - constructor(object) { - super(); - - if (isPrimitive(object)) { - throw new Error("the parameter must not be a simple type"); - } - - this.object = object; - this.wildCard = WILDCARD; - } - - /** - * set wildcard - * - * @param {string} wildcard - * @return {Pathfinder} - * @since 1.7.0 - */ - setWildCard(wildcard) { - validateString(wildcard); - this.wildCard = wildcard; - return this; - } - - /** - * - * @param {string|array} path - * @since 1.4.0 - * @returns {*} - * @throws {TypeError} unsupported type - * @throws {Error} the journey is not at its end - * @throws {TypeError} value is not a string - * @throws {TypeError} value is not an integer - * @throws {Error} unsupported action for this data type - */ - getVia(path) { - return getValueViaPath.call(this, this.object, path); - } - - /** - * - * @param {string|array} path - * @param {*} value - * @returns {Pathfinder} - * @since 1.4.0 - * @throws {TypeError} unsupported type - * @throws {TypeError} value is not a string - * @throws {TypeError} value is not an integer - * @throws {Error} unsupported action for this data type - */ - setVia(path, value) { - setValueViaPath.call(this, this.object, path, value); - return this; - } - - /** - * Delete Via Path - * - * @param {string|array} path - * @returns {Pathfinder} - * @since 1.6.0 - * @throws {TypeError} unsupported type - * @throws {TypeError} value is not a string - * @throws {TypeError} value is not an integer - * @throws {Error} unsupported action for this data type - */ - deleteVia(path) { - deleteValueViaPath.call(this, this.object, path); - return this; - } - - /** - * - * @param {string|array} path - * @return {bool} - * @throws {TypeError} unsupported type - * @throws {TypeError} value is not a string - * @throws {TypeError} value is not an integer - * @since 1.4.0 - */ - exists(path) { - try { - getValueViaPath.call(this, this.object, path, true); - return true; - } catch (e) { - } - - return false; - } + /** + * Creates a new instance of the constructor. + * + * @param {object} object - The object parameter for the constructor. + * + * @throws {Error} Throws an error if the provided object parameter is a simple type. + */ + constructor(object) { + super(); + + if (isPrimitive(object)) { + throw new Error("the parameter must not be a simple type"); + } + + this.object = object; + this.wildCard = WILDCARD; + } + + /** + * set wildcard + * + * @param {string} wildcard + * @return {Pathfinder} + * @since 1.7.0 + */ + setWildCard(wildcard) { + validateString(wildcard); + this.wildCard = wildcard; + return this; + } + + /** + * + * @param {string|array} path + * @since 1.4.0 + * @returns {*} + * @throws {TypeError} unsupported type + * @throws {Error} the journey is not at its end + * @throws {TypeError} value is not a string + * @throws {TypeError} value is not an integer + * @throws {Error} unsupported action for this data type + */ + getVia(path) { + return getValueViaPath.call(this, this.object, path); + } + + /** + * + * @param {string|array} path + * @param {*} value + * @returns {Pathfinder} + * @since 1.4.0 + * @throws {TypeError} unsupported type + * @throws {TypeError} value is not a string + * @throws {TypeError} value is not an integer + * @throws {Error} unsupported action for this data type + */ + setVia(path, value) { + setValueViaPath.call(this, this.object, path, value); + return this; + } + + /** + * Delete Via Path + * + * @param {string|array} path + * @returns {Pathfinder} + * @since 1.6.0 + * @throws {TypeError} unsupported type + * @throws {TypeError} value is not a string + * @throws {TypeError} value is not an integer + * @throws {Error} unsupported action for this data type + */ + deleteVia(path) { + deleteValueViaPath.call(this, this.object, path); + return this; + } + + /** + * + * @param {string|array} path + * @return {bool} + * @throws {TypeError} unsupported type + * @throws {TypeError} value is not a string + * @throws {TypeError} value is not an integer + * @since 1.4.0 + */ + exists(path) { + try { + getValueViaPath.call(this, this.object, path, true); + return true; + } catch (e) {} + + return false; + } } /** @@ -199,27 +198,27 @@ class Pathfinder extends Base { * @private */ function iterate(subject, path, check) { - if (check === undefined) { - check = false; - } - validateBoolean(check); - - const result = new Map(); - - if (isArray(path)) { - path = path.join(DELIMITER); - } - - if (isObject(subject) || isArray(subject)) { - for (const [key, value] of Object.entries(subject)) { - result.set(key, getValueViaPath.call(this, value, path, check)); - } - } else { - const key = path.split(DELIMITER).shift(); - result.set(key, getValueViaPath.call(this, subject, path, check)); - } - - return result; + if (check === undefined) { + check = false; + } + validateBoolean(check); + + const result = new Map(); + + if (isArray(path)) { + path = path.join(DELIMITER); + } + + if (isObject(subject) || isArray(subject)) { + for (const [key, value] of Object.entries(subject)) { + result.set(key, getValueViaPath.call(this, value, path, check)); + } + } else { + const key = path.split(DELIMITER).shift(); + result.set(key, getValueViaPath.call(this, subject, path, check)); + } + + return result; } /** @@ -233,71 +232,73 @@ function iterate(subject, path, check) { * @throws {Error} unsupported action for this data type */ function getValueViaPath(subject, path, check) { - if (check === undefined) { - check = false; - } - validateBoolean(check); - - if (!(isArray(path) || isString(path))) { - throw new Error("type error: a path must be a string or an array in getValueViaPath"); - } - - let parts; - if (isString(path)) { - if (path === "") { - return subject; - } - - parts = path.split(DELIMITER); - } - - let current = parts.shift(); - - if (current === this.wildCard) { - return iterate.call(this, subject, parts.join(DELIMITER), check); - } - - if (isObject(subject) || isArray(subject)) { - let anchor; - if (subject instanceof Map || subject instanceof WeakMap) { - anchor = subject.get(current); - } else if (subject instanceof Set || subject instanceof WeakSet) { - current = parseInt(current); - validateInteger(current); - anchor = [...subject]?.[current]; - } else if (typeof WeakRef === "function" && subject instanceof WeakRef) { - throw Error("unsupported action for this data type (WeakRef)"); - } else if (isArray(subject)) { - current = parseInt(current); - validateInteger(current); - anchor = subject?.[current]; - } else { - anchor = subject?.[current]; - } - - if (isObject(anchor) || isArray(anchor)) { - return getValueViaPath.call(this, anchor, parts.join(DELIMITER), check); - } - - if (parts.length > 0) { - throw Error(`the journey is not at its end (${parts.join(DELIMITER)})`); - } - - if (check === true) { - const descriptor = Object.getOwnPropertyDescriptor( - Object.getPrototypeOf(subject), - current, - ); - - if (!subject.hasOwnProperty(current) && descriptor === undefined) { - throw Error("unknown value " + current); - } - } - - return anchor; - } - - throw TypeError(`unsupported type ${typeof subject} for path ${path}`); + if (check === undefined) { + check = false; + } + validateBoolean(check); + + if (!(isArray(path) || isString(path))) { + throw new Error( + "type error: a path must be a string or an array in getValueViaPath", + ); + } + + let parts; + if (isString(path)) { + if (path === "") { + return subject; + } + + parts = path.split(DELIMITER); + } + + let current = parts.shift(); + + if (current === this.wildCard) { + return iterate.call(this, subject, parts.join(DELIMITER), check); + } + + if (isObject(subject) || isArray(subject)) { + let anchor; + if (subject instanceof Map || subject instanceof WeakMap) { + anchor = subject.get(current); + } else if (subject instanceof Set || subject instanceof WeakSet) { + current = parseInt(current); + validateInteger(current); + anchor = [...subject]?.[current]; + } else if (typeof WeakRef === "function" && subject instanceof WeakRef) { + throw Error("unsupported action for this data type (WeakRef)"); + } else if (isArray(subject)) { + current = parseInt(current); + validateInteger(current); + anchor = subject?.[current]; + } else { + anchor = subject?.[current]; + } + + if (isObject(anchor) || isArray(anchor)) { + return getValueViaPath.call(this, anchor, parts.join(DELIMITER), check); + } + + if (parts.length > 0) { + throw Error(`the journey is not at its end (${parts.join(DELIMITER)})`); + } + + if (check === true) { + const descriptor = Object.getOwnPropertyDescriptor( + Object.getPrototypeOf(subject), + current, + ); + + if (!subject.hasOwnProperty(current) && descriptor === undefined) { + throw Error("unknown value " + current); + } + } + + return anchor; + } + + throw TypeError(`unsupported type ${typeof subject} for path ${path}`); } /** @@ -313,73 +314,72 @@ function getValueViaPath(subject, path, check) { * @private */ function setValueViaPath(subject, path, value) { - if (!(isArray(path) || isString(path))) { - throw new Error("type error: a path must be a string or an array"); - } - - let parts; - if (isArray(path)) { - if (path.length === 0) { - return; - } - - parts = path; - } else { - parts = path.split(DELIMITER); - } - - let last = parts.pop(); - const subpath = parts.join(DELIMITER); - - const stack = new Stack(); - let current = subpath; - while (true) { - try { - getValueViaPath.call(this, subject, current, true); - break; - } catch (e) { - } - - stack.push(current); - parts.pop(); - current = parts.join(DELIMITER); - - if (current === "") break; - } - - while (!stack.isEmpty()) { - current = stack.pop(); - let obj = {}; - - if (!stack.isEmpty()) { - const n = stack.peek().split(DELIMITER).pop(); - if (isInteger(parseInt(n))) { - obj = []; - } - } - - setValueViaPath.call(this, subject, current, obj); - } - - const anchor = getValueViaPath.call(this, subject, subpath); - - if (!(isObject(subject) || isArray(subject))) { - throw TypeError(`unsupported type: ${typeof subject} in setValueViaPath`); - } - - if (anchor instanceof Map || anchor instanceof WeakMap) { - anchor.set(last, value); - } else if (anchor instanceof Set || anchor instanceof WeakSet) { - anchor.append(value); - } else if (typeof WeakRef === "function" && anchor instanceof WeakRef) { - throw Error("unsupported action for this data type in setValueViaPath"); - } else if (isArray(anchor)) { - last = parseInt(last); - validateInteger(last); - assignProperty(anchor, "" + last, value); - } else { - assignProperty(anchor, last, value); - } + if (!(isArray(path) || isString(path))) { + throw new Error("type error: a path must be a string or an array"); + } + + let parts; + if (isArray(path)) { + if (path.length === 0) { + return; + } + + parts = path; + } else { + parts = path.split(DELIMITER); + } + + let last = parts.pop(); + const subpath = parts.join(DELIMITER); + + const stack = new Stack(); + let current = subpath; + while (true) { + try { + getValueViaPath.call(this, subject, current, true); + break; + } catch (e) {} + + stack.push(current); + parts.pop(); + current = parts.join(DELIMITER); + + if (current === "") break; + } + + while (!stack.isEmpty()) { + current = stack.pop(); + let obj = {}; + + if (!stack.isEmpty()) { + const n = stack.peek().split(DELIMITER).pop(); + if (isInteger(parseInt(n))) { + obj = []; + } + } + + setValueViaPath.call(this, subject, current, obj); + } + + const anchor = getValueViaPath.call(this, subject, subpath); + + if (!(isObject(subject) || isArray(subject))) { + throw TypeError(`unsupported type: ${typeof subject} in setValueViaPath`); + } + + if (anchor instanceof Map || anchor instanceof WeakMap) { + anchor.set(last, value); + } else if (anchor instanceof Set || anchor instanceof WeakSet) { + anchor.append(value); + } else if (typeof WeakRef === "function" && anchor instanceof WeakRef) { + throw Error("unsupported action for this data type in setValueViaPath"); + } else if (isArray(anchor)) { + last = parseInt(last); + validateInteger(last); + assignProperty(anchor, "" + last, value); + } else { + assignProperty(anchor, last, value); + } } /** @@ -389,16 +389,16 @@ function setValueViaPath(subject, path, value) { * @param {*} value */ function assignProperty(object, key, value) { - if (!object.hasOwnProperty(key)) { - object[key] = value; - return; - } + if (!object.hasOwnProperty(key)) { + object[key] = value; + return; + } - if (value === undefined) { - delete object[key]; - } + if (value === undefined) { + delete object[key]; + } - object[key] = value; + object[key] = value; } /** @@ -415,40 +415,42 @@ function assignProperty(object, key, value) { * @private */ function deleteValueViaPath(subject, path) { - if (!(isArray(path) || isString(path))) { - throw new Error("type error: a path must be a string or an array in deleteValueViaPath"); - } - - let parts; - if (isArray(path)) { - if (path.length === 0) { - return; - } - - parts = path; - } else { - parts = path.split(DELIMITER); - } - - let last = parts.pop(); - const subPath = parts.join(DELIMITER); - - const anchor = getValueViaPath.call(this, subject, subPath); - - if (anchor instanceof Map) { - anchor.delete(last); - } else if ( - anchor instanceof Set || - anchor instanceof WeakMap || - anchor instanceof WeakSet || - (typeof WeakRef === "function" && anchor instanceof WeakRef) - ) { - throw Error("unsupported action for this data type in deleteValueViaPath"); - } else if (isArray(anchor)) { - last = parseInt(last); - validateInteger(last); - delete anchor[last]; - } else { - delete anchor[last]; - } + if (!(isArray(path) || isString(path))) { + throw new Error( + "type error: a path must be a string or an array in deleteValueViaPath", + ); + } + + let parts; + if (isArray(path)) { + if (path.length === 0) { + return; + } + + parts = path; + } else { + parts = path.split(DELIMITER); + } + + let last = parts.pop(); + const subPath = parts.join(DELIMITER); + + const anchor = getValueViaPath.call(this, subject, subPath); + + if (anchor instanceof Map) { + anchor.delete(last); + } else if ( + anchor instanceof Set || + anchor instanceof WeakMap || + anchor instanceof WeakSet || + (typeof WeakRef === "function" && anchor instanceof WeakRef) + ) { + throw Error("unsupported action for this data type in deleteValueViaPath"); + } else if (isArray(anchor)) { + last = parseInt(last); + validateInteger(last); + delete anchor[last]; + } else { + delete anchor[last]; + } } diff --git a/source/dom/updater.mjs b/source/dom/updater.mjs index 790ce09a9..cc7aab6d0 100644 --- a/source/dom/updater.mjs +++ b/source/dom/updater.mjs @@ -40,6 +40,7 @@ import { addAttributeToken, addToObjectLink } from "./attributes.mjs"; import { updaterTransformerMethodsSymbol } from "./customelement.mjs"; import { findTargetElementFromEvent } from "./events.mjs"; import { findDocumentTemplate } from "./template.mjs"; +import { getWindow } from "./util.mjs"; export { Updater, addObjectWithUpdaterToElement }; @@ -111,11 +112,15 @@ class Updater extends Base { for (const [, change] of Object.entries(diffResult)) { promises.push( - Sleep(1).then(() => { - removeElement.call(this, change); - insertElement.call(this, change); - updateContent.call(this, change); - updateAttributes.call(this, change); + new Promise((resolve) => { + getWindow().requestAnimationFrame(() => { + removeElement.call(this, change); + insertElement.call(this, change); + updateContent.call(this, change); + updateAttributes.call(this, change); + + resolve(); + }); }), ); } @@ -534,7 +539,7 @@ function insertElement(change) { const available = new Set(); - for (const [i, ] of Object.entries(value)) { + for (const [i] of Object.entries(value)) { const ref = refPrefix + i; const currentPath = `${dataPath}.${i}`; -- GitLab