diff --git a/source/components/datatable/dataset.mjs b/source/components/datatable/dataset.mjs index c0409b689b0e91c9ae574e512746dbe7b287961b..8192779ce33196b8f7fd6396636dc3aa4b741f2c 100644 --- a/source/components/datatable/dataset.mjs +++ b/source/components/datatable/dataset.mjs @@ -236,7 +236,7 @@ class DataSet extends CustomElement { [assembleMethodSymbol]() { super[assembleMethodSymbol](); - requestAnimationFrame(() => { + setTimeout(() => { if (!this[datasourceLinkedElementSymbol]) { const selector = this.getOption("datasource.selector"); @@ -278,7 +278,7 @@ class DataSet extends CustomElement { } initEventHandler.call(this); - }); + },10); } /** diff --git a/source/components/datatable/datatable.mjs b/source/components/datatable/datatable.mjs index 04eaf3ccb7826b05608535a780b00057e95bb0c1..7764dfdce1cdf4d337347b4f19126c963f762920 100644 --- a/source/components/datatable/datatable.mjs +++ b/source/components/datatable/datatable.mjs @@ -414,7 +414,7 @@ class DataTable extends CustomElement { this[datasourceLinkedElementSymbol] = element; - getWindow().requestAnimationFrame(() => { + queueMicrotask(() => { handleDataSourceChanges.call(this); if (element && "datasource" in element) { element.datasource.attachObserver( diff --git a/source/components/datatable/filter.mjs b/source/components/datatable/filter.mjs index b697a14db8af1544d34167439c7d6e7e30a69223..e268d4c877572486cdf99327ac5cd2029c06c9f3 100644 --- a/source/components/datatable/filter.mjs +++ b/source/components/datatable/filter.mjs @@ -547,11 +547,9 @@ function updateFilterSelections() { this[filterSelectElementSymbol].setOption("options", options); setTimeout(() => { - window.requestAnimationFrame(() => { - this[filterSelectElementSymbol].value = - this[settingsSymbol].getSelected(); - }); - }, 0); + this[filterSelectElementSymbol].value = + this[settingsSymbol].getSelected(); + }, 10); }); } diff --git a/source/components/datatable/pagination.mjs b/source/components/datatable/pagination.mjs index 95dd24f8fc76205153da98c082de7a6e96560431..145616b36832e788d6a59341358e595a4a620a99 100644 --- a/source/components/datatable/pagination.mjs +++ b/source/components/datatable/pagination.mjs @@ -516,7 +516,7 @@ function handleDataSourceChanges() { pagination.items = []; } - getWindow().requestAnimationFrame(() => { + queueMicrotask(() => { this.setOption("pagination", pagination); }); } diff --git a/source/dom/customcontrol.mjs b/source/dom/customcontrol.mjs index e2f61adb61ac01abc6110e59f43fa86b159bed84..3247d50b92bec8446a5df92a0af3cb4b45b170df 100644 --- a/source/dom/customcontrol.mjs +++ b/source/dom/customcontrol.mjs @@ -12,15 +12,15 @@ * SPDX-License-Identifier: AGPL-3.0 */ -import { extend } from "../data/extend.mjs"; -import { addAttributeToken } from "./attributes.mjs"; -import { ATTRIBUTE_ERRORMESSAGE } from "./constants.mjs"; -import { CustomElement, attributeObserverSymbol } from "./customelement.mjs"; -import { instanceSymbol } from "../constants.mjs"; -import { DeadMansSwitch } from "../util/deadmansswitch.mjs"; -import { addErrorAttribute } from "./error.mjs"; +import {extend} from "../data/extend.mjs"; +import {addAttributeToken} from "./attributes.mjs"; +import {ATTRIBUTE_ERRORMESSAGE} from "./constants.mjs"; +import {CustomElement, attributeObserverSymbol} from "./customelement.mjs"; +import {instanceSymbol} from "../constants.mjs"; +import {DeadMansSwitch} from "../util/deadmansswitch.mjs"; +import {addErrorAttribute} from "./error.mjs"; -export { CustomControl }; +export {CustomControl}; /** * @private @@ -53,285 +53,286 @@ const attachedInternalSymbol = Symbol("attachedInternal"); * @since 1.14.0 */ class CustomControl extends CustomElement { - /** - * The constructor method of CustomControl, which is called when creating a new instance. - * It checks whether the element supports `attachInternals()` and initializes an internal form-associated element - * if supported. Additionally, it initializes a MutationObserver to watch for attribute changes. - * - * See the links below for more information: - * {@link https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-define|CustomElementRegistry.define()} - * {@link https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-get|CustomElementRegistry.get()} - * and {@link https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals|ElementInternals} - * - * @inheritdoc - * @throws {Error} the ElementInternals is not supported and a polyfill is necessary - */ - constructor() { - super(); - - // check if element supports `attachInternals()` - if (typeof this["attachInternals"] === "function") { - this[attachedInternalSymbol] = this.attachInternals(); - } else { - // `attachInternals()` is not supported, so a polyfill is necessary - throw Error( - "the ElementInternals is not supported and a polyfill is necessary", - ); - } - - // watch for attribute value changes - initValueAttributeObserver.call(this); - } - - /** - * This method is called by the `instanceof` operator. - * @return {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/dom/custom-control@@instance"); - } - - /** - * This method determines which attributes are to be monitored by `attributeChangedCallback()`. - * - * @return {string[]} - * @since 1.15.0 - */ - static get observedAttributes() { - return super.observedAttributes; - } - - /** - * Adding a static `formAssociated` property, with a true value, makes an autonomous custom element a form-associated custom element. - * - * @see [attachInternals()]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals} - * @see [Custom Elements Face Example]{@link https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-face-example} - * @return {boolean} - */ - static formAssociated = true; - - /** - * @inheritdoc - **/ - get defaults() { - return extend({}, super.defaults); - } - - /** - * Must be overridden by a derived class and return the value of the control. - * - * This is a method of [internal API](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals), which is a part of the web standard for custom elements. - * - * @throws {Error} the value getter must be overwritten by the derived class - */ - get value() { - throw Error("the value getter must be overwritten by the derived class"); - } - - /** - * Must be overridden by a derived class and set the value of the control. - * - * This is a method of [internal API](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals), which is a part of the web standard for custom elements. - * - * @param {*} value The value to set. - * @throws {Error} the value setter must be overwritten by the derived class - */ - set value(value) { - throw Error("the value setter must be overwritten by the derived class"); - } - - /** - * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) - * - * @return {NodeList} - * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/labels} - * @throws {Error} the ElementInternals is not supported and a polyfill is necessary - */ - get labels() { - return getInternal.call(this)?.labels; - } - - /** - * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) - * - * @return {string|null} - */ - get name() { - return this.getAttribute("name"); - } - - /** - * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) - * - * @return {string} - */ - get type() { - return this.constructor.getTag(); - } - - /** - * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) - * - * @return {ValidityState} - * @throws {Error} the ElementInternals is not supported and a polyfill is necessary - * @see [ValidityState]{@link https://developer.mozilla.org/en-US/docs/Web/API/ValidityState} - * @see [validity]{@link https://developer.mozilla.org/en-US/docs/Web/API/validity} - */ - get validity() { - return getInternal.call(this)?.validity; - } - - /** - * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) - * - * @return {string} - * @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/validationMessage - * @throws {Error} the ElementInternals is not supported and a polyfill is necessary - */ - get validationMessage() { - return getInternal.call(this)?.validationMessage; - } - - /** - * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) - * - * @return {boolean} - * @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/willValidate - * @throws {Error} the ElementInternals is not supported and a polyfill is necessary - */ - get willValidate() { - return getInternal.call(this)?.willValidate; - } - - /** - * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) - * - * @return {boolean} - * @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/states - * @throws {Error} the ElementInternals is not supported and a polyfill is necessary - */ - get states() { - return getInternal.call(this)?.states; - } - - /** - * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) - * - * @return {HTMLFontElement|null} - * @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/form - * @throws {Error} the ElementInternals is not supported and a polyfill is necessary - */ - get form() { - return getInternal.call(this)?.form; - } - - /** - * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) - * - * ``` - * // Use the control's name as the base name for submitted data - * const n = this.getAttribute('name'); - * const entries = new FormData(); - * entries.append(n + '-first-name', this.firstName_); - * entries.append(n + '-last-name', this.lastName_); - * this.setFormValue(entries); - * ``` - * - * @param {File|string|FormData} value - * @param {File|string|FormData} state - * @return {undefined} - * @throws {DOMException} NotSupportedError - * @throws {Error} the ElementInternals is not supported and a polyfill is necessary - * @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/setFormValue - */ - setFormValue(value, state) { - getInternal.call(this).setFormValue(value, state); - } - - /** - * - * @param {object} flags - * @param {string|undefined} message - * @param {HTMLElement} anchor - * @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/setValidity - * @return {undefined} - * @throws {DOMException} NotSupportedError - * @throws {Error} the ElementInternals is not supported and a polyfill is necessary - */ - setValidity(flags, message, anchor) { - getInternal.call(this).setValidity(flags, message, anchor); - } - - /** - * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) - * - * @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/checkValidity - * @return {boolean} - * @throws {DOMException} NotSupportedError - * @throws {Error} the ElementInternals is not supported and a polyfill is necessary - */ - checkValidity() { - return getInternal.call(this)?.checkValidity(); - } - - /** - * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) - * - * @return {boolean} - * @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/reportValidity - * @throws {Error} the ElementInternals is not supported and a polyfill is necessary - * @throws {DOMException} NotSupportedError - */ - reportValidity() { - return getInternal.call(this)?.reportValidity(); - } - - /** - * Sets the `form` attribute of the custom control to the `id` of the passed form element. - * If no form element is passed, removes the `form` attribute. - * - * @param {HTMLFormElement} form - The form element to associate with the control - */ - formAssociatedCallback(form) { - if (form) { - if (form.id) { - this.setAttribute("form", form.id); - } - } else { - this.removeAttribute("form"); - } - } - - /** - * Sets or removes the `disabled` attribute of the custom control based on the passed value. - * - * @param {boolean} disabled - Whether or not the control should be disabled - */ - formDisabledCallback(disabled) { - if (disabled) { - if (!this.hasAttribute("disabled")) { - this.setAttribute("disabled", ""); - } - } else { - if (this.hasAttribute("disabled")) { - this.removeAttribute("disabled"); - } - } - } - - /** - * @param {string} state - * @param {string} mode - */ - formStateRestoreCallback(state, mode) {} - - /** - * - */ - formResetCallback() { - this.value = ""; - } + /** + * The constructor method of CustomControl, which is called when creating a new instance. + * It checks whether the element supports `attachInternals()` and initializes an internal form-associated element + * if supported. Additionally, it initializes a MutationObserver to watch for attribute changes. + * + * See the links below for more information: + * {@link https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-define|CustomElementRegistry.define()} + * {@link https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-get|CustomElementRegistry.get()} + * and {@link https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals|ElementInternals} + * + * @inheritdoc + * @throws {Error} the ElementInternals is not supported and a polyfill is necessary + */ + constructor() { + super(); + + // check if element supports `attachInternals()` + if (typeof this["attachInternals"] === "function") { + this[attachedInternalSymbol] = this.attachInternals(); + } else { + // `attachInternals()` is not supported, so a polyfill is necessary + throw Error( + "the ElementInternals is not supported and a polyfill is necessary", + ); + } + + // watch for attribute value changes + initValueAttributeObserver.call(this); + } + + /** + * This method is called by the `instanceof` operator. + * @return {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/dom/custom-control@@instance"); + } + + /** + * This method determines which attributes are to be monitored by `attributeChangedCallback()`. + * + * @return {string[]} + * @since 1.15.0 + */ + static get observedAttributes() { + return super.observedAttributes; + } + + /** + * Adding a static `formAssociated` property, with a true value, makes an autonomous custom element a form-associated custom element. + * + * @see [attachInternals()]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals} + * @see [Custom Elements Face Example]{@link https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-face-example} + * @return {boolean} + */ + static formAssociated = true; + + /** + * @inheritdoc + **/ + get defaults() { + return extend({}, super.defaults); + } + + /** + * Must be overridden by a derived class and return the value of the control. + * + * This is a method of [internal API](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals), which is a part of the web standard for custom elements. + * + * @throws {Error} the value getter must be overwritten by the derived class + */ + get value() { + throw Error("the value getter must be overwritten by the derived class"); + } + + /** + * Must be overridden by a derived class and set the value of the control. + * + * This is a method of [internal API](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals), which is a part of the web standard for custom elements. + * + * @param {*} value The value to set. + * @throws {Error} the value setter must be overwritten by the derived class + */ + set value(value) { + throw Error("the value setter must be overwritten by the derived class"); + } + + /** + * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) + * + * @return {NodeList} + * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/labels} + * @throws {Error} the ElementInternals is not supported and a polyfill is necessary + */ + get labels() { + return getInternal.call(this)?.labels; + } + + /** + * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) + * + * @return {string|null} + */ + get name() { + return this.getAttribute("name"); + } + + /** + * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) + * + * @return {string} + */ + get type() { + return this.constructor.getTag(); + } + + /** + * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) + * + * @return {ValidityState} + * @throws {Error} the ElementInternals is not supported and a polyfill is necessary + * @see [ValidityState]{@link https://developer.mozilla.org/en-US/docs/Web/API/ValidityState} + * @see [validity]{@link https://developer.mozilla.org/en-US/docs/Web/API/validity} + */ + get validity() { + return getInternal.call(this)?.validity; + } + + /** + * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) + * + * @return {string} + * @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/validationMessage + * @throws {Error} the ElementInternals is not supported and a polyfill is necessary + */ + get validationMessage() { + return getInternal.call(this)?.validationMessage; + } + + /** + * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) + * + * @return {boolean} + * @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/willValidate + * @throws {Error} the ElementInternals is not supported and a polyfill is necessary + */ + get willValidate() { + return getInternal.call(this)?.willValidate; + } + + /** + * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) + * + * @return {boolean} + * @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/states + * @throws {Error} the ElementInternals is not supported and a polyfill is necessary + */ + get states() { + return getInternal.call(this)?.states; + } + + /** + * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) + * + * @return {HTMLFontElement|null} + * @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/form + * @throws {Error} the ElementInternals is not supported and a polyfill is necessary + */ + get form() { + return getInternal.call(this)?.form; + } + + /** + * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) + * + * ``` + * // Use the control's name as the base name for submitted data + * const n = this.getAttribute('name'); + * const entries = new FormData(); + * entries.append(n + '-first-name', this.firstName_); + * entries.append(n + '-last-name', this.lastName_); + * this.setFormValue(entries); + * ``` + * + * @param {File|string|FormData} value + * @param {File|string|FormData} state + * @return {undefined} + * @throws {DOMException} NotSupportedError + * @throws {Error} the ElementInternals is not supported and a polyfill is necessary + * @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/setFormValue + */ + setFormValue(value, state) { + getInternal.call(this).setFormValue(value, state); + } + + /** + * + * @param {object} flags + * @param {string|undefined} message + * @param {HTMLElement} anchor + * @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/setValidity + * @return {undefined} + * @throws {DOMException} NotSupportedError + * @throws {Error} the ElementInternals is not supported and a polyfill is necessary + */ + setValidity(flags, message, anchor) { + getInternal.call(this).setValidity(flags, message, anchor); + } + + /** + * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/checkValidity + * @return {boolean} + * @throws {DOMException} NotSupportedError + * @throws {Error} the ElementInternals is not supported and a polyfill is necessary + */ + checkValidity() { + return getInternal.call(this)?.checkValidity(); + } + + /** + * This is a method of [internal api](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) + * + * @return {boolean} + * @see https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals/reportValidity + * @throws {Error} the ElementInternals is not supported and a polyfill is necessary + * @throws {DOMException} NotSupportedError + */ + reportValidity() { + return getInternal.call(this)?.reportValidity(); + } + + /** + * Sets the `form` attribute of the custom control to the `id` of the passed form element. + * If no form element is passed, removes the `form` attribute. + * + * @param {HTMLFormElement} form - The form element to associate with the control + */ + formAssociatedCallback(form) { + if (form) { + if (form.id) { + this.setAttribute("form", form.id); + } + } else { + this.removeAttribute("form"); + } + } + + /** + * Sets or removes the `disabled` attribute of the custom control based on the passed value. + * + * @param {boolean} disabled - Whether or not the control should be disabled + */ + formDisabledCallback(disabled) { + if (disabled) { + if (!this.hasAttribute("disabled")) { + this.setAttribute("disabled", ""); + } + } else { + if (this.hasAttribute("disabled")) { + this.removeAttribute("disabled"); + } + } + } + + /** + * @param {string} state + * @param {string} mode + */ + formStateRestoreCallback(state, mode) { + } + + /** + * + */ + formResetCallback() { + this.value = ""; + } } /** @@ -341,13 +342,13 @@ class CustomControl extends CustomElement { * @this CustomControl */ function getInternal() { - if (!(attachedInternalSymbol in this)) { - throw new Error( - "ElementInternals is not supported and a polyfill is necessary", - ); - } + if (!(attachedInternalSymbol in this)) { + throw new Error( + "ElementInternals is not supported and a polyfill is necessary", + ); + } - return this[attachedInternalSymbol]; + return this[attachedInternalSymbol]; } const debounceValueSymbol = Symbol("debounceValue"); @@ -361,31 +362,32 @@ const debounceValueSymbol = Symbol("debounceValue"); * @this CustomControl */ function initValueAttributeObserver() { - const self = this; - - this[attributeObserverSymbol]["value"] = () => { - if (self[debounceValueSymbol] instanceof DeadMansSwitch) { - try { - self[debounceValueSymbol].touch(); - return; - } catch (e) { - // catch Error("has already run"); - if (e.message !== "has already run") { - throw e; - } - delete self[debounceValueSymbol]; - } - } - - self[debounceValueSymbol] = new DeadMansSwitch(50, () => { - requestAnimationFrame(() => { - const oldValue = self.getAttribute("value"); - const newValue = self.getOption("value"); - - if (oldValue !== newValue) { - this.setOption("value", this.getAttribute("value")); - } - }); - }); - }; + const self = this; + + this[attributeObserverSymbol]["value"] = () => { + if (self[debounceValueSymbol] instanceof DeadMansSwitch) { + try { + self[debounceValueSymbol].touch(); + return; + } catch (e) { + // catch Error("has already run"); + if (e.message !== "has already run") { + throw e; + } + delete self[debounceValueSymbol]; + } + } + + self[debounceValueSymbol] = new DeadMansSwitch(50, () => { + const oldValue = self.getAttribute("value"); + const newValue = self.getOption("value"); + + if (oldValue !== newValue) { + queueMicrotask(() => { + this.setOption("value", this.getAttribute("value")); + }); + + } + }); + }; }