diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ab7fddad1..96ad2a5964 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,16 @@ Changelog _Note: Gaps between patch versions are faulty, broken or test releases._ -## v3.??.?? (2024-??-??) +## v3.??.?? (2024-02-13) + +#### :boom: Breaking Change + +* Removed `shouldPerformDataRequest` prop in `b-virtual-scroll-new` `base/b-virtual-scroll-new` +* `tests/helpers/network/interceptor` no longer has a named export, the `RequestInterceptor` class is now exported as the default export `tests/helpers/network/interceptor` + +#### :rocket: New Feature + +* Added `preloadAmount` prop in b`-virtual-scroll-new` `base/b-virtual-scroll-new` #### :house: Internal @@ -22,11 +31,14 @@ _Note: Gaps between patch versions are faulty, broken or test releases._ * Removed bad import of async module from `tests/helpers/network/interceptor` -#### :boom: Breaking Change +## v3.65.1 (2024-02-13) -* `tests/helpers/network/interceptor` no longer has a named export, the `RequestInterceptor` class is now exported as the default export `tests/helpers/network/interceptor` +#### :bug: Bug Fix + +* Fixed a bug with opening the browser when clicking on the svg icon nested to the link `traits/i-icon` +* Added the `default` getter to static compiled routes `core/router/modules` -## v3.65.0 (2023-02-09) +## v3.65.0 (2024-02-09) #### :rocket: New Feature @@ -44,19 +56,19 @@ _Note: Gaps between patch versions are faulty, broken or test releases._ * Added tests for `b-virtual-scroll-new` from v4 branch -## v3.64.0 (2023-01-30) +## v3.64.0 (2024-01-30) #### :rocket: New Feature * Now the b-virtual-scroll component always attempts to perform the last rendering after the requests have been completed, even if there is no data left to render `base/b-virtual-scroll-new/b-virtual-scroll-new` -## v3.63.2 (2023-01-29) +## v3.63.2 (2024-01-29) #### :bug: Bug Fix * Fixed an issue where, during a request error in the `initLoadNext` call, the error was not handled properly, leading to incorrect behavior of the component, including in terms of slot rendering `base/b-virtual-scroll-new/b-virtual-scroll-new` -## v3.63.1 (2023-01-26) +## v3.63.1 (2024-01-26) #### :bug: Bug Fix diff --git a/config/default.js b/config/default.js index c7f49efc6c..5e1a316657 100644 --- a/config/default.js +++ b/config/default.js @@ -258,7 +258,7 @@ module.exports = config.createConfig({dirs: [__dirname, 'client']}, { */ testPort: o('test-port', { env: true, - default: 8000 + default: 9000 }), /** diff --git a/package.json b/package.json index 3187edc5bc..33c12fdde0 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "src/core/index.js", "typings": "index.d.ts", "license": "MIT", - "version": "3.65.0", + "version": "3.65.1", "author": { "name": "kobezzza", "email": "kobezzza@gmail.com", diff --git a/src/base/b-virtual-scroll-new/CHANGELOG.md b/src/base/b-virtual-scroll-new/CHANGELOG.md index b90965a1c8..5dc925d5ec 100644 --- a/src/base/b-virtual-scroll-new/CHANGELOG.md +++ b/src/base/b-virtual-scroll-new/CHANGELOG.md @@ -9,170 +9,12 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] -## v4.0.0-beta.?? (2023-??-??) +## v3.??.?? (2024-02-13) #### :boom: Breaking Change -* Major update. Visit [readme](./readme) to see migration guide. - -## v4.0.0-beta.36 (2023-10-23) - -#### :bug: Bug Fix - -* Added the missed `itemsProp` property - -## v3.30.1 (2022-10-25) - -#### :bug: Bug Fix - -* Fixed an issue with wrong arguments was provided into `getItemKey` - -## v3.18.2 (2022-03-22) - -#### :bug: Bug Fix - -* Fixed an issue with `initLoad` race condition - -## v3.15.4 (2022-01-24) - -#### :boom: Breaking Change - -* The event`chunkRenderStart` is renamed to `chunkRender:renderStart` and now it emits before a component driver renders components - -#### :rocket: New Feature - -* Added new events `chunkRender:*` - -## v3.9.0 (2021-11-08) - -#### :rocket: New Feature - -* [Added a new event `chunkRenderStart`](https://github.com/V4Fire/Client/issues/651) -* [Added `pageNumber` in `chunkLoaded` event](https://github.com/V4Fire/Client/issues/651) - -## v3.0.0-rc.182 (2021-04-28) - -#### :bug: Bug Fix - -* Fixed an issue with `optionKey` being ignored - -## v3.0.0-rc.181 (2021-04-20) - -#### :bug: Bug Fix - -* [Fixed an issue with `itemProps` not being provided to child components](https://github.com/V4Fire/Client/issues/512) - -## v3.0.0-rc.170 (2021-03-26) - -#### :rocket: New Feature - -* Added a new event `chunkRender` - -## v3.0.0-rc.164 (2021-03-22) - -#### :bug: Bug Fix - -* Now `bVirtualScroll` will throw an error if the rendering of components returns an empty array - -## v3.0.0-rc.153 (2021-03-04) - -#### :house: Internal - -* [`bVirtualScroll` is now implements `iItems` trait](https://github.com/V4Fire/Client/issues/471) - -## v3.0.0-rc.151 (2021-03-04) - -#### :house: Internal - -* Downgraded the delay before initializing to `15ms` -* Some optimizations - -## v3.0.0-rc.126 (2021-01-26) - -#### :bug: Bug Fix - -* Added handling of the empty request - -## v3.0.0-rc.122 (2021-01-13) - -#### :house: Internal - -* Removed iItems implementation. [Issue to move back](https://github.com/V4Fire/Client/issues/471) - -## v3.0.0-rc.102 (2020-11-26) - -#### :bug: Bug Fix - -* Fixed an issue with layout shifts after `reInit` - -## v3.0.0-rc.81 (2020-10-08) - -#### :bug: Bug Fix - -* Fixed an issue with `renderNext`: hasn't been data rendering after a loading error - -## v3.0.0-rc.74 (2020-10-06) - -#### :bug: Bug Fix - -* Fixed an issue with removing the progress modifier - -## v3.0.0-rc.68 (2020-09-23) - -#### :bug: Bug Fix - -* [Fixed an issue with the second data batch load affects initial rendering after reInit](https://github.com/V4Fire/Client/issues/346) - -## v3.0.0-rc.60 (2020-09-01) - -#### :bug: Bug Fix - -* [Fixed a possible memory leak](https://github.com/V4Fire/Client/pull/321) - -## v3.0.0-rc.59 (2020-08-10) - -#### :rocket: New Feature - -* [Added ability to render data manually](https://github.com/V4Fire/Client/issues/202) - -#### :nail_care: Polish - -* Improved documentation - -## v3.0.0-rc.39 (2020-07-22) +* Removed `shouldPerformDataRequest` prop in `b-virtual-scroll-new` `base/b-virtual-scroll-new` #### :rocket: New Feature -* [Added life cycle events](https://github.com/V4Fire/Client/issues/205) - -#### :bug: Bug Fix - -* [Fixed an issue when data from `lastLoadedData` and `lastLoadedChunk.normalized` aren't synchronized](https://github.com/V4Fire/Client/issues/281) -* [Fixed `lastLoadedChunk.raw` returns undefined](https://github.com/V4Fire/Client/issues/267) - -#### :house: Internal - -* [Refactoring of tests](https://github.com/V4Fire/Client/pull/293) -* [Fixed ESLint warnings](https://github.com/V4Fire/Client/pull/293) - -## v3.0.0-rc.31 (2020-06-17) - -#### :bug: Bug Fix - -* Fixed a problem with the disappearance of loaders before the content was rendered - -## v3.0.0-rc.25 (2020-06-03) - -#### :bug: Bug Fix - -* [Fixed an issue where skeletons disappeared](https://github.com/V4Fire/Client/issues/230) -* [Fixed an issue with a race condition `chunk-request/init`](https://github.com/V4Fire/Client/issues/203) -* [Fixed an issue where an `empty` slot appeared when there was data](https://github.com/V4Fire/Client/issues/259) - -## v3.0.0-rc.19 (2020-05-26) - -#### :bug: Bug Fix - -* [Fixed rendering of truncated data](https://github.com/V4Fire/Client/issues/231) -* [Fixed rendering of empty slot](https://github.com/V4Fire/Client/issues/241) -* [Fixed clear of `in-view`](https://github.com/V4Fire/Client/pull/201) +* Added `preloadAmount` prop in b`-virtual-scroll-new` `base/b-virtual-scroll-new` diff --git a/src/base/b-virtual-scroll-new/README.md b/src/base/b-virtual-scroll-new/README.md index 36a0ba5926..8f770abd57 100644 --- a/src/base/b-virtual-scroll-new/README.md +++ b/src/base/b-virtual-scroll-new/README.md @@ -31,13 +31,13 @@ - [Slots](#slots) - [API](#api) - [Props](#props) - - [\[shouldPerformDataRender = `(state: VirtualScrollState) => state.isInitialRender || state.remainingItems === 0`\]](#shouldperformdatarender--state-virtualscrollstate--stateisinitialrender--stateremainingitems--0) - - [\[shouldPerformDataRequest = `(state: VirtualScrollState) => state.lastLoadedData.length > 0`\]](#shouldperformdatarequest--state-virtualscrollstate--statelastloadeddatalength--0) - - [\[shouldStopRequestingData = `(state: VirtualScrollState) => state.lastLoadedData.length > 0`\]](#shouldstoprequestingdata--state-virtualscrollstate--statelastloadeddatalength--0) - - [\[chunkSize = `10`\]](#chunksize--10) - - [\[requestQuery\]](#requestquery) - - [\[itemsFactory\]](#itemsfactory) - - [\[itemsProcessors = `{}`\]](#itemsprocessors--) + - [[shouldPerformDataRender = `(state: VirtualScrollState) => state.isInitialRender || state.remainingItems === 0`]](#shouldperformdatarender--state-virtualscrollstate--stateisinitialrender--stateremainingitems--0) + - [[shouldStopRequestingData = `(state: VirtualScrollState) => state.lastLoadedData.length > 0`]](#shouldstoprequestingdata--state-virtualscrollstate--statelastloadeddatalength--0) + - [[chunkSize = `10`]](#chunksize--10) + - [[requestQuery]](#requestquery) + - [[itemsFactory]](#itemsfactory) + - [[itemsProcessors = `{}`]](#itemsprocessors--) + - [[preloadAmount = `0`]](#preloadamount--0) - [`tombstoneCount`](#tombstonecount) - [Methods](#methods) - [getNextDataSlice](#getnextdataslice) @@ -212,7 +212,7 @@ you need to pass the items prop which contains an array of data to be rendered b There are also some minor differences in the component's event model. Unlike `b-virtual-scroll` which uses a `dataProvider`, a component with items will not emit certain events, specifically `dataLoadStart` and `convertDataToDB`. -The component will also ignore the `shouldPerformDataRequest` and `shouldStopRequestingData` props, as they have no meaning when there is no `dataProvider`. +The component will also ignore the `shouldStopRequestingData` prop, as they have no meaning when there is no `dataProvider`. ### How to Implement Component Rendering on Click Instead of Scroll? @@ -419,30 +419,6 @@ There is no need to specify any additional props for `b-virtual-scroll`. For `b- #### Overview of Functions -The component provides several "should-like" props that determine whether to perform certain actions. Each of these functions serves a different purpose and is called at a specific moment in time. Let's take a detailed look at each of these functions and their purposes: - -- `shouldPerformDataRequest`: This function indicates the need to load a chunk of data. If it returns `true`, a data request will be made. This function takes the "internal" component state as input and should return a boolean value. It is called when any component rendered by `b-virtual-scroll`, which has not yet entered the viewport, enters the viewport. - - > It's important to note that clients do not need to check whether data is currently being loaded or not; the `b-virtual-scroll` component handles this check itself and prevents data from being requested if a loading process is already active. - - An example implementation of this function could be to check how many items are left in the viewport, and if half of the rendered items are within the viewport, start loading more: - - ```typescript - const shouldPerformDataRequest = (state: VirtualScrollState): boolean => { - // Example: Request data if the remaining items till the end is less than or equal to 10 - return state.remainingItems <= 10; - }; - ``` - - The default implementation checks whether anything was loaded in the last request and, if so, allows another request: - - ```typescript - const shouldPerformDataRequest = (state: VirtualScrollState, _ctx: bVirtualScroll): boolean => { - const isLastRequestNotEmpty = () => state.lastLoadedData.length > 0; - return isLastRequestNotEmpty(); - }; - ``` - - `shouldStopRequestingData`: This function indicates the need to stop requesting data and tells the component that the data loading lifecycle has completed. If it returns `true`, the `b-virtual-scroll` component will not attempt to request more data until the component is reinitialized, which leads to an update of the lifecycle. This function is called after every successful data load. An example implementation of this function could be to check whether the number of loaded items equals the total number of items that can be returned by the pagination for the current query: @@ -457,7 +433,7 @@ The component provides several "should-like" props that determine whether to per The default implementation checks whether anything was loaded in the last request and, if so, allows requests to continue: ```typescript - const shouldPerformDataRequest = (state: VirtualScrollState, _ctx: bVirtualScroll): boolean => { + const shouldStopRequestingData = (state: VirtualScrollState, _ctx: bVirtualScroll): boolean => { const isLastRequestNotEmpty = () => state.lastLoadedData.length > 0; return isLastRequestNotEmpty(); }; @@ -481,11 +457,12 @@ Here are some tips for efficiently implementing data loading on the client side For example, you can implement this approach as follows: - ```typescript - const shouldPerformDataRequest = (state: VirtualScrollState, _ctx: bVirtualScroll): boolean => { - // Start loading when half of the components are in the viewport - return state.remainingItems <= chunkSize / 2; - } + ```snakeskin + // Set `preloadAmount` prop for the component so that it loads data in advance + < b-virtual-scroll & + // ... + :preloadAmount = 30 + . ``` ```typescript @@ -495,7 +472,7 @@ Here are some tips for efficiently implementing data loading on the client side } ``` -- Avoid making the last useless request: This pertains to the `shouldPerformDataRequest` and `shouldStopRequestingData` functions. By default, these functions check the last data chunk to see if it returned anything. It's better to avoid this and inform the component in advance that all data has been loaded. You can achieve this by comparing the value returned by your server, indicating the total number of items with the current number of items in `b-virtual-scroll`, as demonstrated in the example above. +- Avoid making the last useless request: This pertains to the `shouldStopRequestingData` function. By default, this function check the last data chunk to see if it returned anything. It's better to avoid this and inform the component in advance that all data has been loaded. You can achieve this by comparing the value returned by your server, indicating the total number of items with the current number of items in `b-virtual-scroll`, as demonstrated in the example above. ### Control the Rendering Conveyor with `itemsFactory` @@ -734,9 +711,8 @@ Next, the component invokes the `renderGuard` to determine if the data can be re 5. `domInsertDone` - The DOM insertion has completed. This event is asynchronous as it uses RAF (Request Animation Frame) for DOM insertion. 6. `renderDone` - The component rendering has finished. -Afterward, the component waits for user actions, specifically when the user sees any component on the page. The component then calls the - -`shouldPerformDataRequest` or `shouldPerformDataRender` functions on the client side, depending on the availability of data. This process repeats until all data has been loaded and rendered. +Afterward, the component waits for user actions, specifically when the user sees any component on the page. +The component then calls the `shouldPerformDataRender` function on the client side. This process repeats until all data has been loaded and rendered. 1. `lifecycleDone` - Occurs when all data has been loaded and rendered on the page. @@ -754,7 +730,9 @@ flowchart TD A["Start: renderGuard Function"] --> B["Check if dataSlice.length < chunkSize"] B -- "True" --> C["Check if state.areRequestsStopped and state.isLastRender"] C -- "True" --> D["Return: {result: false, reason: 'done'}"] - C -- "False" --> E["Return: {result: false, reason: 'notEnoughData'}"] + C -- "False" --> E["Invoke shouldPerformDataRender"] + E -- "Not Defined or True" --> Z["Return: {result: false, reason: 'notEnoughData'}"] + E -- "False" --> X["Return: {result: false, reason: 'noPermission'}"] B -- "False" --> F["Check if state.isInitialRender"] F -- "True" --> G["Return: {result: true}"] F -- "False" --> H["Invoke shouldPerformDataRender"] @@ -775,11 +753,10 @@ graph TB G -- "done" --> H["Invoke onLifecycleDone()"] G -- "notEnoughData" --> I["Check if state.areRequestsStopped"] I -- "True" --> J["Invoke performRender() and onLifecycleDone()"] - I -- "False" --> K["Check if shouldPerformDataRequest()"] - K -- "True" --> L["Invoke initLoadNext()"] - K -- "False" --> M["Check if state.isInitialRender"] - M -- "True" --> N["Invoke performRender()"] - M -- "False" --> O["Return"] + I -- "False" --> L["Invoke initLoadNext()"] + L --> F["Check if state.areRequestsStopped"] + F -- "False" --> Z["Check if preloadAmount is reached"] + Z -- "False" --> X["Invoke initLoadNext()"] ``` #### Performing Last Render @@ -865,10 +842,6 @@ There may also be situations where you need to modify the `renderGuard`. Current }) ``` -- Can I use only `shouldPerformDataRequest` and not use `shouldStopRequestingData`? - - Hypothetically, you can. However, this may cause issues with the `done` slot and the `lifecycleDone` event; they will not work correctly. Therefore, it is strongly recommended to separate the logic into whether data should be loaded now (`shouldPerformDataRequest`) and whether data loading is completed (all data is loaded) (`shouldStopRequestingData`). - - Can I set `chunkSize` to 10 if the request returns 89 items at a time? Yes, you can. `b-virtual-scroll` will render the data in chunks until it has rendered all of it. @@ -993,27 +966,6 @@ const shouldPerformDataRender = (state: VirtualScrollState): boolean => { }; ``` -#### [shouldPerformDataRequest = `(state: VirtualScrollState) => state.lastLoadedData.length > 0`] - -The `shouldPerformDataRequest` property of `bVirtualScroll` allows you to control whether the component should request additional data based on the component state. -This function allows the component to understand whether the data loading lifecycle is complete or not. - -Here's an example of how you can use `shouldPerformDataRequest`: - -```typescript -const shouldPerformDataRequest = (state: VirtualScrollState): boolean => { - // Example: Request data if the remaining items till the end is less than or equal to 10 - return state.remainingItems <= 10; -}; -``` - -In this example, the function checks the `remainingItems` property of the component state. -If the remaining number of items till the end is less than or equal to 10, it returns `true` to indicate that the component should perform a data request. -You can adjust the condition based on your specific requirements. - -By implementing the `shouldPerformDataRequest` function, you have control over when the component should request additional data. -This allows you to customize the data loading behavior based on the state of the component. - #### [shouldStopRequestingData = `(state: VirtualScrollState) => state.lastLoadedData.length > 0`] This function is called on each data loading cycle. It determines whether the component should stop requesting new data. @@ -1123,6 +1075,27 @@ This prop is a middleware function that is called after `b-virtual-scroll` has c This function can be useful in cases where you need to implement some processing of the abstract representation of components, such as mutating props or adding additional components. +#### [preloadAmount = `0`] + +The amount of data that the component can preload and use afterwards. +By default, `b-virtual-scroll-new` requests data only when it is not enough to render a chunk, +but often it is necessary to have a behavior where data is preloaded in advance. + +This prop allows you to configure data preloading and allows `b-virtual-scroll-new` +to preload as much data as you specify. + +The prop can also be a function, for example, you can configure data preloading depending on loadPage: + +```typescript +preloadAmount(state: VirtualScrollState, _ctx: bVirtualScrollNew): number { + const + chunkSize = this.getRequestChunkSize(feed), + {loadPage} = v; + + return loadPage < 4 ? chunkSize : chunkSize * 4; +} +``` + #### `tombstoneCount` - Type: `number` diff --git a/src/base/b-virtual-scroll-new/b-virtual-scroll-new.ts b/src/base/b-virtual-scroll-new/b-virtual-scroll-new.ts index c6771fabdb..2206056ace 100644 --- a/src/base/b-virtual-scroll-new/b-virtual-scroll-new.ts +++ b/src/base/b-virtual-scroll-new/b-virtual-scroll-new.ts @@ -136,7 +136,7 @@ export default class bVirtualScrollNew extends iVirtualScrollHandlers implements */ getNextDataSlice(state: VirtualScrollState, chunkSize: number): object[] { const - nextDataSliceStartIndex = this.componentInternalState.getDataCursor(), + nextDataSliceStartIndex = this.componentInternalState.getDataOffset(), nextDataSliceEndIndex = nextDataSliceStartIndex + chunkSize; return state.data.slice(nextDataSliceStartIndex, nextDataSliceEndIndex); @@ -152,6 +152,16 @@ export default class bVirtualScrollNew extends iVirtualScrollHandlers implements this.chunkSize; } + /** + * Returns the amount of data that should be preloaded + * @param state - current lifecycle state + */ + getPreloadAmount(state: VirtualScrollState): number { + return Object.isFunction(this.preloadAmount) ? + this.preloadAmount(state, this) : + this.preloadAmount; + } + /** * Returns an items processors * @returns @@ -268,18 +278,6 @@ export default class bVirtualScrollNew extends iVirtualScrollHandlers implements return newVal; } - /** - * Short-hand wrapper for calling {@link bVirtualScrollNew.shouldPerformDataRequest}, removing the need to pass - * state and context when calling {@link bVirtualScrollNew.shouldPerformDataRequest}. - */ - protected shouldPerformDataRequestWrapper(): boolean { - if (this.componentMode === componentModes.items) { - return false; - } - - return this.shouldPerformDataRequest(this.getVirtualScrollState(), this); - } - /** * Resets the component state to its initial state */ @@ -310,9 +308,19 @@ export default class bVirtualScrollNew extends iVirtualScrollHandlers implements }; } + const + clientResponse = this.shouldPerformDataRender?.(state, this) ?? true; + + if (clientResponse) { + return { + result: false, + reason: renderGuardRejectionReason.notEnoughData + }; + } + return { - result: false, - reason: renderGuardRejectionReason.notEnoughData + result: clientResponse, + reason: renderGuardRejectionReason.noPermission }; } @@ -350,24 +358,37 @@ export default class bVirtualScrollNew extends iVirtualScrollHandlers implements {result, reason} = this.renderGuard(state); if (result) { - return this.performRender(); + this.performRender(); } - if (reason === renderGuardRejectionReason.done) { - this.onLifecycleDone(); - return; - } - - if (reason === renderGuardRejectionReason.notEnoughData) { - if (state.areRequestsStopped) { - this.performRender(); + switch (reason) { + case renderGuardRejectionReason.done: this.onLifecycleDone(); + break; + + case renderGuardRejectionReason.notEnoughData: + if (state.areRequestsStopped) { + this.performRender(); + this.onLifecycleDone(); + + return; + } - } else if (this.shouldPerformDataRequestWrapper()) { void this.initLoadNext(); - } else if (state.isInitialRender) { - this.performRender(); + break; + + default: { + const + preloadAmount = this.getPreloadAmount(state), + dataOffset = this.componentInternalState.getDataOffset(); + + if ( + !state.areRequestsStopped && + state.data.length - dataOffset - preloadAmount < 0 + ) { + void this.initLoadNext(); + } } } } diff --git a/src/base/b-virtual-scroll-new/const.ts b/src/base/b-virtual-scroll-new/const.ts index 005dd22aa5..3979db2973 100644 --- a/src/base/b-virtual-scroll-new/const.ts +++ b/src/base/b-virtual-scroll-new/const.ts @@ -177,12 +177,6 @@ export const defaultShouldProps = { return isLastRequestEmpty(); }, - /** {@link bVirtualScroll.shouldPerformDataRequest} */ - shouldPerformDataRequest: (state: VirtualScrollState, _ctx: bVirtualScrollNew): boolean => { - const isLastRequestNotEmpty = () => state.lastLoadedData.length > 0; - return isLastRequestNotEmpty(); - }, - /** {@link bVirtualScroll.shouldPerformDataRender} */ shouldPerformDataRender: (state: VirtualScrollState, _ctx: bVirtualScrollNew): boolean => state.isInitialRender || state.remainingItems === 0 diff --git a/src/base/b-virtual-scroll-new/modules/state/index.ts b/src/base/b-virtual-scroll-new/modules/state/index.ts index 285b786b34..236fda31aa 100644 --- a/src/base/b-virtual-scroll-new/modules/state/index.ts +++ b/src/base/b-virtual-scroll-new/modules/state/index.ts @@ -104,7 +104,7 @@ export class ComponentInternalState extends Friend { const chunkSize = ctx.getChunkSize(state), - dataOffset = this.getDataCursor() + chunkSize; + dataOffset = this.getDataOffset() + chunkSize; if (>state.data[dataOffset] == null) { state.isLastRender = true; @@ -194,7 +194,7 @@ export class ComponentInternalState extends Friend { /** * Returns the cursor indicating the last index of the last rendered data element */ - getDataCursor(): number { + getDataOffset(): number { return this.privateState.dataOffset; } @@ -212,7 +212,7 @@ export class ComponentInternalState extends Friend { updateDataOffset(): void { const {ctx, state} = this, - current = this.getDataCursor(), + current = this.getDataOffset(), chunkSize = ctx.getChunkSize(state); this.privateState.dataOffset = current + chunkSize; diff --git a/src/base/b-virtual-scroll-new/props.ts b/src/base/b-virtual-scroll-new/props.ts index 32317a8d21..a64cce0f90 100644 --- a/src/base/b-virtual-scroll-new/props.ts +++ b/src/base/b-virtual-scroll-new/props.ts @@ -279,6 +279,29 @@ export default abstract class iVirtualScrollProps extends iData { @prop({type: [Number, Function]}) readonly chunkSize: number | ShouldPerform = 10; + /** + * The amount of data that the component can preload and use afterwards. + * By default, `b-virtual-scroll-new` requests data only when it is not enough to render a chunk, + * but often it is necessary to have a behavior where data is preloaded in advance. + * + * This prop allows you to configure data preloading and allows `b-virtual-scroll-new` + * to preload as much data as you specify. + * + * The prop can also be a function, for example, you can configure data preloading depending on loadPage: + * + * ```typescript + * preloadAmount(state: VirtualScrollState, _ctx: bVirtualScrollNew): number { + * const + * chunkSize = this.getRequestChunkSize(feed), + * {loadPage} = v; + * + * return loadPage < 4 ? chunkSize : chunkSize * 4; + * } + * ``` + */ + @prop({type: [Number, Function]}) + readonly preloadAmount: number | ShouldPerform = 0; + /** * When this function returns true the component will stop to request new data. * This function will be called on each data loading cycle. @@ -290,17 +313,6 @@ export default abstract class iVirtualScrollProps extends iData { readonly shouldStopRequestingData!: ShouldPerform; - /** - * When this function returns true the component will be able to request additional data. - * This function will be called each time a new element enters the viewport. - */ - @prop({ - type: Function, - default: defaultShouldProps.shouldPerformDataRequest - }) - - readonly shouldPerformDataRequest!: ShouldPerform; - /** * This function is called in the {@link bVirtualScroll.renderGuard} after other checks are completed. * diff --git a/src/base/b-virtual-scroll-new/test/unit/functional/emitter/payload.ts b/src/base/b-virtual-scroll-new/test/unit/functional/emitter/payload.ts index ff92ffde3e..f13ef87335 100644 --- a/src/base/b-virtual-scroll-new/test/unit/functional/emitter/payload.ts +++ b/src/base/b-virtual-scroll-new/test/unit/functional/emitter/payload.ts @@ -85,13 +85,14 @@ test.describe('', () => { .withDefaultPaginationProviderProps({chunkSize: providerChunkSize}) .withProps({ chunkSize, - shouldPerformDataRequest: () => true, shouldStopRequestingData: ({lastLoadedData}) => lastLoadedData.length === 0, '@componentHook:beforeDataCreate': (ctx) => jestMock.spy(ctx, 'emit') }) .build(); await component.waitForChildCountEqualsTo(chunkSize); + await component.scrollToBottom(); + await component.waitForLifecycleDone(); const spy = await component.getSpy((ctx) => ctx.emit), @@ -137,7 +138,6 @@ test.describe('', () => { .withDefaultPaginationProviderProps({chunkSize: providerChunkSize}) .withProps({ chunkSize, - shouldPerformDataRequest: () => true, shouldStopRequestingData: ({lastLoadedData}) => lastLoadedData.length === 0, '@componentHook:beforeDataCreate': (ctx) => jestMock.spy(ctx, 'emit') }) diff --git a/src/base/b-virtual-scroll-new/test/unit/functional/props/props.ts b/src/base/b-virtual-scroll-new/test/unit/functional/props/props.ts index 08da85bc80..c211089d57 100644 --- a/src/base/b-virtual-scroll-new/test/unit/functional/props/props.ts +++ b/src/base/b-virtual-scroll-new/test/unit/functional/props/props.ts @@ -34,7 +34,7 @@ test.describe('', () => { }); test.describe('`chunkSize` prop changes after the first chunk has been rendered', () => { - test('Should render the second chunk with the new chunk size', async ({page}) => { + test('should render the second chunk with the new chunk size', async () => { const chunkSize = 12; @@ -48,7 +48,6 @@ test.describe('', () => { }) .build({useDummy: true}); - await page.pause(); await component.waitForChildCountEqualsTo(chunkSize); await component.updateProps({chunkSize: chunkSize * 2}); await component.scrollToBottom(); @@ -65,7 +64,7 @@ test.describe('', () => { }); test.describe('`requestQuery`', () => { - test('Should pass the parameters to the GET parameters of the request', async () => { + test('should pass the parameters to the GET parameters of the request', async () => { const chunkSize = 12; @@ -76,7 +75,6 @@ test.describe('', () => { .withProps({ chunkSize, requestQuery: () => ({get: {param1: 'param1'}}), - shouldPerformDataRequest: () => false, '@componentHook:beforeDataCreate': (ctx: bVirtualScrollNew['unsafe']) => jestMock.spy(ctx.componentFactory, 'produceComponentItems') }) .build(); @@ -97,7 +95,7 @@ test.describe('', () => { }); test.describe('`dbConverter`', () => { - test('Should convert data to the component', async () => { + test('should convert data to the component', async () => { const chunkSize = 12; @@ -107,7 +105,6 @@ test.describe('', () => { .withDefaultPaginationProviderProps({chunkSize}) .withProps({ chunkSize, - shouldPerformDataRequest: () => false, dbConverter: ({data: {nestedData}}) => ({data: nestedData}) }) .build(); @@ -115,7 +112,7 @@ test.describe('', () => { await test.expect(component.waitForChildCountEqualsTo(chunkSize)).resolves.toBeUndefined(); }); - test('Should convert second data chunk to the component', async () => { + test('should convert second data chunk to the component', async () => { const chunkSize = 12; @@ -125,7 +122,6 @@ test.describe('', () => { .withDefaultPaginationProviderProps({chunkSize}) .withProps({ chunkSize, - shouldPerformDataRequest: ({remainingItems}) => remainingItems === 0, dbConverter: ({data: {nestedData}}) => ({data: nestedData}) }) .build(); @@ -138,7 +134,7 @@ test.describe('', () => { }); test.describe('`itemsProcessors`', () => { - test('Should modify components before rendering', async () => { + test('should modify components before rendering', async () => { const chunkSize = 12; @@ -148,7 +144,6 @@ test.describe('', () => { .withDefaultPaginationProviderProps({chunkSize}) .withProps({ chunkSize, - shouldPerformDataRequest: () => false, itemsProcessors: (items) => items.concat([ { item: 'b-dummy', @@ -164,4 +159,112 @@ test.describe('', () => { await test.expect(component.container.locator('.b-dummy')).toHaveCount(1); }); }); + + test.describe('`preloadAmount`', () => { + test('should preload 30 data items', async () => { + const + chunkSize = 10, + preloadAmount = 30; + + provider.response(200, () => ({data: state.data.addData(chunkSize)})); + + await component + .withDefaultPaginationProviderProps({chunkSize}) + .withProps({ + chunkSize, + preloadAmount + }) + .build(); + + await component.waitForChildCountEqualsTo(chunkSize); + + const + currentState = await component.getVirtualScrollState(); + + test.expect(currentState.data).toHaveLength(chunkSize + preloadAmount); + }); + + test.describe('`shouldStopRequestingData` returns true during preload requests', () => { + test('should not continue to load data', async ({page}) => { + const + chunkSize = 10, + preloadAmount = 30; + + provider + .responseOnce(200, state.data.addData(chunkSize)) + .responseOnce(200, state.data.addData(chunkSize)) + .responseOnce(200, []); + + await component + .withDefaultPaginationProviderProps({chunkSize}) + .withProps({ + chunkSize, + preloadAmount + }) + .build(); + + await component.waitForChildCountEqualsTo(chunkSize); + await page.waitForFunction(([ctx]) => ctx.getVirtualScrollState().areRequestsStopped, [component.component]); + + const + currentState = await component.getVirtualScrollState(); + + test.expect(provider.calls).toHaveLength(3); + test.expect(currentState.data).toHaveLength(20); + }); + + test('should not complete the lifecycle until all elements have been rendered', async () => { + const + chunkSize = 10, + preloadAmount = 30; + + provider + .responseOnce(200, state.data.addData(chunkSize)) + .responseOnce(200, state.data.addData(chunkSize)) + .responseOnce(200, []); + + await component + .withDefaultPaginationProviderProps({chunkSize}) + .withProps({ + chunkSize, + preloadAmount + }) + .build(); + + await component.waitForChildCountEqualsTo(chunkSize); + + const + currentState = await component.getVirtualScrollState(); + + test.expect(currentState.isLifecycleDone).toBe(false); + }); + + test('should complete the lifecycle when all elements have been rendered', async ({page}) => { + const + chunkSize = 10, + preloadAmount = 30; + + provider + .responseOnce(200, state.data.addData(chunkSize)) + .responseOnce(200, state.data.addData(chunkSize)) + .responseOnce(200, []); + + await component + .withDefaultPaginationProviderProps({chunkSize}) + .withProps({ + chunkSize, + preloadAmount + }) + .build(); + + await page.waitForFunction(([ctx]) => ctx.getVirtualScrollState().areRequestsStopped, [component.component]); + await component.waitForChildCountEqualsTo(chunkSize); + await component.scrollToBottom(); + await component.waitForChildCountEqualsTo(chunkSize * 2); + await component.scrollToBottom(); + + await test.expect(component.waitForLifecycleDone()).resolves.toBeUndefined(); + }); + }); + }); }); diff --git a/src/base/b-virtual-scroll-new/test/unit/functional/state/default.ts b/src/base/b-virtual-scroll-new/test/unit/functional/state/default.ts index e0cff10b21..3a8d974cf2 100644 --- a/src/base/b-virtual-scroll-new/test/unit/functional/state/default.ts +++ b/src/base/b-virtual-scroll-new/test/unit/functional/state/default.ts @@ -68,8 +68,6 @@ test.describe('', () => { const shouldStopRequestingData = (defaultShouldProps.shouldStopRequestingData), - shouldPerformDataRequest = (({isInitialLoading, remainingItems: remainingItems, isLastEmpty}) => - isInitialLoading || (remainingItems === 0 && !isLastEmpty)), shouldPerformDataRender = (({isInitialRender, remainingItems: remainingItems}) => isInitialRender || remainingItems === 0); @@ -85,7 +83,6 @@ test.describe('', () => { .withProps({ chunkSize, shouldStopRequestingData, - shouldPerformDataRequest, shouldPerformDataRender }) .build(); diff --git a/src/base/b-virtual-scroll-new/test/unit/functional/state/emitter.ts b/src/base/b-virtual-scroll-new/test/unit/functional/state/emitter.ts index 2cd30f5a16..400354abc0 100644 --- a/src/base/b-virtual-scroll-new/test/unit/functional/state/emitter.ts +++ b/src/base/b-virtual-scroll-new/test/unit/functional/state/emitter.ts @@ -172,8 +172,12 @@ test.describe('', () => { }) .build(); + await component.waitForChildCountEqualsTo(chunkSize); + await component.scrollToBottom(); await component.waitForLifecycleDone(); await component.reload(); + await component.waitForChildCountEqualsTo(chunkSize); + await component.scrollToBottom(); await component.waitForLifecycleDone(); const diff --git a/src/base/b-virtual-scroll-new/test/unit/lifecycle/initialization/initialization.ts b/src/base/b-virtual-scroll-new/test/unit/lifecycle/initialization/initialization.ts index 9a613986c2..826309c240 100644 --- a/src/base/b-virtual-scroll-new/test/unit/lifecycle/initialization/initialization.ts +++ b/src/base/b-virtual-scroll-new/test/unit/lifecycle/initialization/initialization.ts @@ -13,7 +13,6 @@ import test from 'tests/config/unit/test'; import type bVirtualScrollNew from 'base/b-virtual-scroll-new/b-virtual-scroll-new'; -import { defaultShouldProps } from 'base/b-virtual-scroll-new/const'; import { createTestHelpers } from 'base/b-virtual-scroll-new/test/api/helpers'; import type { VirtualScrollTestHelpers } from 'base/b-virtual-scroll-new/test/api/helpers/interface'; @@ -42,191 +41,6 @@ test.describe('', () => { await provider.start(); }); - test.describe('property `chunkSize` is set to 12', () => { - test.describe('loaded data array is half length of the `chunkSize` prop', () => { - test.describe('`shouldPerformDataRequest` returns true after the initial loading', () => { - let - shouldStopRequestingData, - shouldPerformDataRequest; - - let - firstDataChunk, - secondDataChunk; - - const - chunkSize = 12; - - test.beforeEach(async () => { - const providerChunkSize = chunkSize / 2; - - shouldStopRequestingData = await component.mockFn(() => false); - shouldPerformDataRequest = await component.mockFn(defaultShouldProps.shouldPerformDataRequest); - - firstDataChunk = state.data.addData(providerChunkSize); - secondDataChunk = state.data.addData(providerChunkSize); - - state.data.addItems(chunkSize); - - await component - .withDefaultPaginationProviderProps({chunkSize: providerChunkSize}) - .withProps({ - chunkSize, - shouldStopRequestingData, - shouldPerformDataRequest, - disableObserver: true, - ...hookProp - }) - .build(); - - await component.waitForChildCountEqualsTo(chunkSize); - }); - - test('should render 12 items', async () => { - await test.expect(component.getChildCount()).resolves.toBe(chunkSize); - }); - - test('should call `shouldStopRequestingData` twice', async () => { - await test.expect(shouldStopRequestingData.calls).resolves.toEqual([ - [ - state.compile({ - remainingItems: undefined, - remainingChildren: undefined, - maxViewedItem: undefined, - maxViewedChild: undefined, - areRequestsStopped: false, - lastLoadedData: firstDataChunk, - lastLoadedRawData: {data: firstDataChunk}, - data: firstDataChunk, - loadPage: 1 - }), - test.expect.any(Object) - ], - [ - state.compile({ - remainingItems: undefined, - remainingChildren: undefined, - maxViewedItem: undefined, - maxViewedChild: undefined, - areRequestsStopped: false, - isInitialLoading: false, - lastLoadedData: secondDataChunk, - lastLoadedRawData: {data: secondDataChunk}, - data: state.data.data, - loadPage: 2 - }), - test.expect.any(Object) - ] - ]); - }); - - test('should call `shouldPerformDataRequest` once', async () => { - await test.expect(shouldPerformDataRequest.calls).resolves.toEqual([ - [ - state.compile({ - remainingItems: undefined, - remainingChildren: undefined, - maxViewedItem: undefined, - maxViewedChild: undefined, - areRequestsStopped: false, - lastLoadedData: firstDataChunk, - lastLoadedRawData: {data: firstDataChunk}, - data: firstDataChunk, - loadPage: 1 - }), - test.expect.any(Object) - ] - ]); - }); - - test('should call `initLoad` once', async () => { - await test.expect(initLoadSpy.calls).resolves.toEqual([[]]); - }); - - test('should call `initLoadNext` once', async () => { - const - spy = await component.getSpy((ctx) => ctx.initLoadNext); - - await test.expect(spy.calls).resolves.toEqual([[]]); - }); - }); - }); - }); - - test.describe('property `chunkSize` is set to 12', () => { - test.describe('loaded data array is half length of the `chunkSize` prop', () => { - test.describe('`shouldPerformDataRequest` returns false after the initial loading', () => { - let - shouldStopRequestingData, - shouldPerformDataRequest; - - const - chunkSize = 12, - providerChunkSize = chunkSize / 2; - - test.beforeEach(async () => { - shouldStopRequestingData = await component.mockFn(() => false); - shouldPerformDataRequest = await component.mockFn(() => false); - - state.data.addData(providerChunkSize); - state.data.addItems(providerChunkSize); - - await component - .withDefaultPaginationProviderProps({chunkSize: providerChunkSize}) - .withProps({ - chunkSize, - shouldStopRequestingData, - shouldPerformDataRequest, - disableObserver: true, - ...hookProp - }) - .build(); - - await component.waitForChildCountEqualsTo(providerChunkSize); - }); - - test('should render 6 items', async () => { - await test.expect(component.getChildCount()).resolves.toBe(providerChunkSize); - }); - - test('should call `shouldStopRequestingData` once', async () => { - await test.expect(shouldStopRequestingData.calls).resolves.toEqual([ - [ - state.compile({ - remainingItems: undefined, - remainingChildren: undefined, - maxViewedItem: undefined, - maxViewedChild: undefined, - areRequestsStopped: false, - loadPage: 1 - }), - test.expect.any(Object) - ] - ]); - }); - - test('should call `shouldPerformDataRequest` once', async () => { - await test.expect(shouldPerformDataRequest.calls).resolves.toEqual([ - [ - state.compile({ - remainingItems: undefined, - remainingChildren: undefined, - maxViewedItem: undefined, - maxViewedChild: undefined, - areRequestsStopped: false, - loadPage: 1 - }), - test.expect.any(Object) - ] - ]); - }); - - test('should call `initLoad` once', async () => { - await test.expect(initLoadSpy.calls).resolves.toEqual([[]]); - }); - }); - }); - }); - test.describe('property `chunkSize` is set to 12', () => { test.describe('loaded data array is half length of the `chunkSize` prop', () => { test.describe('`shouldStopRequestingData` returns true after the initial loading', () => { @@ -235,12 +49,10 @@ test.describe('', () => { providerChunkSize = chunkSize / 2; let - shouldStopRequestingData, - shouldPerformDataRequest; + shouldStopRequestingData; test.beforeEach(async () => { shouldStopRequestingData = await component.mockFn(() => true); - shouldPerformDataRequest = await component.mockFn(() => false); state.data.addData(providerChunkSize); state.data.addItems(providerChunkSize); @@ -250,7 +62,6 @@ test.describe('', () => { .withProps({ chunkSize, shouldStopRequestingData, - shouldPerformDataRequest, disableObserver: true, ...hookProp }) @@ -279,10 +90,6 @@ test.describe('', () => { ]); }); - test('should call `shouldPerformDataRequest` once', async () => { - await test.expect(shouldPerformDataRequest.calls).resolves.toEqual([]); - }); - test('should call `initLoad` once', async () => { await test.expect(initLoadSpy.calls).resolves.toEqual([[]]); }); diff --git a/src/base/b-virtual-scroll-new/test/unit/lifecycle/slots/slots.ts b/src/base/b-virtual-scroll-new/test/unit/lifecycle/slots/slots.ts index 9640464b2f..b179e351c4 100644 --- a/src/base/b-virtual-scroll-new/test/unit/lifecycle/slots/slots.ts +++ b/src/base/b-virtual-scroll-new/test/unit/lifecycle/slots/slots.ts @@ -89,6 +89,8 @@ test.describe('', () => { .withProps({chunkSize}) .build(); + await component.waitForChildCountEqualsTo(chunkSize); + await component.scrollToBottom(); await component.waitForSlotState('done', true); const @@ -121,6 +123,7 @@ test.describe('', () => { }) .build(); + await component.waitForDataIndexChild(chunkSize - 1); await component.scrollToBottom(); await component.waitForSlotState('done', true); @@ -178,9 +181,6 @@ test.describe('', () => { .responseOnce(200, {data: state.data.addData(chunkSize)}) .response(200, {data: []}); - const shouldPerformDataRequest = - (({isInitialLoading, remainingItems}) => isInitialLoading || remainingItems === 0); - const shouldPerformDataRender = (({isInitialRender, remainingItems}) => isInitialRender || remainingItems === 0); @@ -188,7 +188,6 @@ test.describe('', () => { .withDefaultPaginationProviderProps({chunkSize}) .withProps({ chunkSize, - shouldPerformDataRequest, shouldPerformDataRender }) .build(); diff --git a/src/base/b-virtual-scroll-new/test/unit/scenario/reload.ts b/src/base/b-virtual-scroll-new/test/unit/scenario/reload.ts index 058061403d..8c717b7058 100644 --- a/src/base/b-virtual-scroll-new/test/unit/scenario/reload.ts +++ b/src/base/b-virtual-scroll-new/test/unit/scenario/reload.ts @@ -43,7 +43,6 @@ test.describe('', () => { .withDefaultPaginationProviderProps({chunkSize: chunkSize[0]}) .withProps({ chunkSize: chunkSize[0], - shouldPerformDataRequest: ({remainingItems}) => remainingItems === 0, '@componentHook:beforeDataCreate': (ctx) => jestMock.spy(ctx, 'emit') }) .build({useDummy: true}); @@ -113,7 +112,6 @@ test.describe('', () => { .withDefaultPaginationProviderProps({chunkSize}) .withProps({ chunkSize, - shouldPerformDataRequest: ({remainingItems}) => remainingItems === 0, '@componentHook:beforeDataCreate': (ctx) => jestMock.spy(ctx, 'emit') }) .build(); diff --git a/src/base/b-virtual-scroll-new/test/unit/scenario/retry.ts b/src/base/b-virtual-scroll-new/test/unit/scenario/retry.ts index c4eeb2644c..7bc47aa0ac 100644 --- a/src/base/b-virtual-scroll-new/test/unit/scenario/retry.ts +++ b/src/base/b-virtual-scroll-new/test/unit/scenario/retry.ts @@ -148,6 +148,7 @@ test.describe('', () => { await component.waitForChildCountEqualsTo(chunkSize); await component.waitForDataIndexChild(chunkSize - 1); + await component.scrollToBottom(); await component.waitForLifecycleDone(); await test.expect(component.waitForChildCountEqualsTo(chunkSize)).resolves.toBeUndefined(); @@ -168,6 +169,8 @@ test.describe('', () => { .withDefaultPaginationProviderProps({chunkSize}) .build(); + await component.waitForChildCountEqualsTo(chunkSize); + await component.scrollToBottom(); await component.node.locator('#retry').click(); test.expect(provider.mock.mock.calls.length).toBe(3); diff --git a/src/core/router/modules/helpers.ts b/src/core/router/modules/helpers.ts index 17e535ebc1..da5e53ba39 100644 --- a/src/core/router/modules/helpers.ts +++ b/src/core/router/modules/helpers.ts @@ -375,6 +375,10 @@ export function compileStaticRoutes(routes: StaticRoutes, opts: CompileRoutesOpt return this.meta.default; }, + get default(): boolean { + return this.meta.default; + }, + meta: { name, external: isExternal.test(pattern), @@ -415,6 +419,10 @@ export function compileStaticRoutes(routes: StaticRoutes, opts: CompileRoutesOpt return this.meta.default; }, + get default(): boolean { + return this.meta.default; + }, + meta: { ...route, diff --git a/src/icons/b-icon/test/index.js b/src/icons/b-icon/test/index.js index c509309919..898410a644 100644 --- a/src/icons/b-icon/test/index.js +++ b/src/icons/b-icon/test/index.js @@ -30,12 +30,12 @@ module.exports = (page) => { target = await init({value: 'foo'}); expect(clrfx(await target.evaluate((ctx) => ctx.$el.innerHTML))) - .toBe(''); + .toBe(''); await Promise.resolve('next tick'); expect(clrfx(await target.evaluate((ctx) => ctx.$el.innerHTML))) - .toBe(''); + .toBe(''); }); }); diff --git a/src/traits/i-icon/CHANGELOG.md b/src/traits/i-icon/CHANGELOG.md index 8d1ba7a8ed..24f7654837 100644 --- a/src/traits/i-icon/CHANGELOG.md +++ b/src/traits/i-icon/CHANGELOG.md @@ -9,6 +9,12 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] +## v3.62.3 (2023-12-15) + +#### :bug: Bug Fix + +* Fixed a bug with opening the browser when clicking on the svg icon nested to the link + ## v3.60.0 (2023-09-28) #### :rocket: New Feature diff --git a/src/traits/i-icon/i-icon.ts b/src/traits/i-icon/i-icon.ts index ea74a7cb56..d12e85d3a3 100644 --- a/src/traits/i-icon/i-icon.ts +++ b/src/traits/i-icon/i-icon.ts @@ -55,7 +55,6 @@ export default abstract class iIcon { */ static updateIconHref: AddSelf = (component, id, el: SVGUseElement, href?) => { const { - async: $a, $normalParent } = component.unsafe; @@ -65,37 +64,11 @@ export default abstract class iIcon { iIcon.setDSIconStyles(el, id); - const group = {group: el.getAttribute(ID_ATTRIBUTE) ?? undefined}; - $a.clearAll(group); - - const - parent = el.parentNode; - - if (!parent) { - return; - } - - Object.forEach(parent.querySelectorAll('[data-tmp]'), (el: Node) => parent.removeChild(el)); - if (!Object.isTruly(href)) { return; } - const - newEl = document.createElementNS('http://www.w3.org/2000/svg', 'use'); - - newEl.setAttributeNS('http://www.w3.org/1999/xlink', 'href', href!); - newEl.setAttribute('data-tmp', ''); - - $a.requestAnimationFrame(() => { - parent.appendChild(newEl); - }, group); - - $a.worker(() => { - try { - parent.removeChild(newEl); - } catch {} - }, group); + el.setAttributeNS('http://www.w3.org/1999/xlink', 'href', href!); }; /**