diff --git a/packages/demo/meta.config.js b/packages/demo/meta.config.js index f329eb864..03ee68e3e 100644 --- a/packages/demo/meta.config.js +++ b/packages/demo/meta.config.js @@ -4,6 +4,9 @@ import { prototyping } from '@studiometa/webpack-config-preset-prototyping'; export default defineConfig({ presets: [prototyping({ ts: true })], + server(config) { + config.snippet = false; + }, webpack(config) { config.resolve.alias = { ...config.resolve.alias, diff --git a/packages/demo/src/js/app.ts b/packages/demo/src/js/app.ts index b89a31323..60757c5e2 100644 --- a/packages/demo/src/js/app.ts +++ b/packages/demo/src/js/app.ts @@ -11,6 +11,7 @@ import { BaseConfig, withDrag, withName, + getInstances, } from '@studiometa/js-toolkit'; import { matrix } from '@studiometa/js-toolkit/utils'; import ScrollToDemo from './components/ScrollToDemo.js'; @@ -24,52 +25,6 @@ import ScrolledInViewOffset from './components/ScrolledInViewOffset.js'; import MediaQueryDemo from './components/MediaQueryDemo.js'; import PointerProps from './components/PointerProps.js'; -let numberOfTick = 0; -let time = performance.now(); -let interval = setInterval(() => { - const newTime = performance.now(); - numberOfTick += 1; - console.log('#%d blocking time: %d ms', numberOfTick, newTime - time); - time = newTime; - - if (numberOfTick > total * 2) { - clearInterval(interval) - } -}, 0) - -let count = 0; -const total = 66; - -function getDeepNestedComponentName(index) { - return `TestDeepNested${index}` -} - -function makeDeepNestedComponent(index) { - return class extends withExtraConfig(Base, { name: getDeepNestedComponentName(index), components: {} }) { - mounted() { - // console.log(this.$id); - } - }; -} - -const TestDeepNested = makeDeepNestedComponent(0); -let CurrentClass = TestDeepNested; -while (count < total) { - count += 1; - const NewClass = makeDeepNestedComponent(count); - // @ts-ignore - CurrentClass.config.components[NewClass.config.name] = NewClass; - - CurrentClass = NewClass; -} - - -const TestManyInstance = class extends withExtraConfig(Base, { name: 'TestManyInstance', debug: false }) { - mounted() { - // console.log(this.$id); - } -}; - /** * App class. */ @@ -98,8 +53,8 @@ class App extends Base { AnimateTestMultiple, ResponsiveOptions, ScrolledInViewOffset, - TestDeepNested, - TestManyInstance, + // TestDeepNested, + // TestManyInstance, Accordion: (app) => importWhenVisible( async () => { @@ -183,9 +138,15 @@ class App extends Base { * @inheritdoc */ mounted() { + window.APP = this; this.$log('Mounted 🎉'); } + updated() { + console.log(this.$children.TestManyInstance) + console.log(getInstances()); + } + onModalOpen(...args) { this.$log('onModalOpen', ...args); } diff --git a/packages/demo/src/js/components/ParentNativeEvent/index.js b/packages/demo/src/js/components/ParentNativeEvent/index.js index 71bcddf07..da02ef527 100644 --- a/packages/demo/src/js/components/ParentNativeEvent/index.js +++ b/packages/demo/src/js/components/ParentNativeEvent/index.js @@ -11,6 +11,7 @@ export default class ParentNativeEvent extends Base { static config = { name: 'ParentNativeEvent', log: true, + debug: true, components: { Child, }, diff --git a/packages/js-toolkit/Base/Base.ts b/packages/js-toolkit/Base/Base.ts index c3d2c7837..1a1a60392 100644 --- a/packages/js-toolkit/Base/Base.ts +++ b/packages/js-toolkit/Base/Base.ts @@ -326,46 +326,38 @@ export class Base { if (!service.has(key)) { service.add(key, (props) => { const updates = new Map any>(); - const terminations = new Map any>(); - console.log(props.mutations); for (const instance of getInstances()) { for (const mutation of props.mutations) { - // Update components whose children have changed their data-component attribute - if (mutation.type === 'attributes') { - if (!updates.has(instance) && instance.$el.contains(mutation.target.parentNode)) { - updates.set(instance, () => instance.$update()); - } - - continue; - } - - // Terminate components whose root element has been removed from the DOM - // @todo move this to the ChildrenManager within the `$update()` Base method - for (const node of mutation.removedNodes) { - if (node.isConnected) continue; - - if (!terminations.has(instance) && node.contains(instance.$el)) { - terminations.set(instance, () => instance.$terminate()); - } - } - - // Update components whose children have been changed - for (const node of mutation.addedNodes) { - if (!updates.has(instance) && instance.$el.contains(node)) { - updates.set(instance, () => instance.$update()); - } + const shouldUpdateAfterAttributeChange = + mutation.type === 'attributes' && instance.$el.contains(mutation.target.parentNode); + const shouldUpdateAfterNodeRemoval = + mutation.type === 'childList' && + Array.from(mutation.removedNodes).some((node) => instance.$el !== node && instance.$el.contains(node)); + const shouldUpdateAfterNodeAddition = + mutation.type === 'childList' && + Array.from(mutation.addedNodes).some((node) => instance.$el.contains(node)); + + // @todo removedNode has no parent + console.log(instance.$id, { + shouldUpdateAfterAttributeChange, + shouldUpdateAfterNodeRemoval, + shouldUpdateAfterNodeAddition, + }); + if ( + shouldUpdateAfterAttributeChange || + shouldUpdateAfterNodeRemoval || + shouldUpdateAfterNodeAddition + ) { + updates.set(instance, () => instance.$update()); } } } + console.log(updates); for (const update of updates.values()) { update(); } - - for (const termination of terminations.values()) { - termination(); - } }); } @@ -425,6 +417,7 @@ export class Base { await Promise.all([ // Undo addToQueue(() => this.__refs.unregisterAll()), + addToQueue(() => this.__children.unregisterAll()), addToQueue(() => this.__services.disableAll()), // Redo addToQueue(() => this.__children.registerAll()), diff --git a/packages/js-toolkit/Base/managers/ChildrenManager.ts b/packages/js-toolkit/Base/managers/ChildrenManager.ts index 8481de8bf..449b53299 100644 --- a/packages/js-toolkit/Base/managers/ChildrenManager.ts +++ b/packages/js-toolkit/Base/managers/ChildrenManager.ts @@ -5,7 +5,9 @@ import { getComponentElements, addToQueue } from '../utils.js'; /** * Children manager. */ -export class ChildrenManager extends AbstractManager { +export class ChildrenManager< + T extends Record[]>, +> extends AbstractManager { /** * Store async component promises to avoid calling them multiple times and * waiting for them when they are already resolved. @@ -23,6 +25,20 @@ export class ChildrenManager extends AbstractManager { return Object.keys(this.props); } + async unregisterAll() { + for (const instances of Object.values(this.props)) { + for (let instance of instances) { + if (instance instanceof Promise) { + instance = await instance; + } + + if (!instance.$el.isConnected) { + instance.$terminate(); + } + } + } + } + /** * Register instances of all children components. */ @@ -91,7 +107,7 @@ export class ChildrenManager extends AbstractManager { Object.defineProperty(this.props, name, { enumerable: true, configurable: true, - get: () => this.__getValue(name, component), + value: this.__getValue(name, component), }); }