Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
titouanmathis committed Dec 18, 2024
1 parent 4c143da commit 151a17d
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 59 deletions.
11 changes: 1 addition & 10 deletions packages/demo/src/js/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,6 @@ class App extends Base {
this.$log('Mounted 🎉');
}

updated() {
console.log(this.$children.TestManyInstance)
console.log(getInstances());
}

onModalOpen(...args) {
this.$log('onModalOpen', ...args);
}
Expand All @@ -159,13 +154,9 @@ class App extends Base {
this.$log('resized', props);
}

onDocumentClick(event) {
console.log('onDocumentClick', event);
}

onWindowResize(event) {
console.log('onWindowResize', event);
}
}

export default createApp(App);
export default createApp(App, { root: document.querySelector('main') });
23 changes: 23 additions & 0 deletions packages/demo/src/js/components/ParentNativeEvent/Child.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,28 @@ export default class Child extends Base {
*/
static config = {
name: 'Child',
log: true,
debug: false,
};

msg(...msg) {
this.$el.textContent = `[${this.$id}] ${msg.join(' ')}`;
}

mounted() {
this.msg('Mounted', performance.now());
}

updated() {
this.msg('Updated', performance.now());
}

destroyed() {
this.$log('destroyed');
}

terminated() {
this.$log('terminated');
this.msg('Terminated', performance.now());
}
}
21 changes: 16 additions & 5 deletions packages/demo/src/js/components/ParentNativeEvent/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Base } from '@studiometa/js-toolkit';
import { createElement } from '@studiometa/js-toolkit/utils';
import Child from './Child.js';

/**
Expand All @@ -11,17 +12,27 @@ export default class ParentNativeEvent extends Base {
static config = {
name: 'ParentNativeEvent',
log: true,
debug: true,
debug: false,
components: {
Child,
},
};

onChildClick(...args) {
this.$log(this.$id, 'onChildClick', ...args);
updated() {
this.$log(this.$children.Child);
}

onChildDede(...args) {
this.$log(this.$id, 'onChildDede', ...args);
onDocumentClick({ event }) {
if (event.metaKey) {
this.$el.firstElementChild.remove();
} else if (event.altKey) {
this.$children.Child[0].$el.dataset.component = '';
} else {
this.$el.append(
createElement('button', {
dataComponent: 'Child',
}),
);
}
}
}
3 changes: 1 addition & 2 deletions packages/demo/src/templates/pages/child-native-event.twig
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{% extends '@layouts/base.twig' %}
{% block main %}
<div data-component="ParentNativeEvent">
<button data-component="Child">Click me</button>
<div data-component="ParentNativeEvent" class="inline-grid gap-4">
<button data-component="Child">Click me</button>
</div>
{% endblock %}
68 changes: 40 additions & 28 deletions packages/js-toolkit/Base/Base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ export class Base<T extends BaseProps = BaseProps> {
this[`__${service.toLowerCase()}`] = new this.__managers[`${service}Manager`](this);
}

const service = useMutation(document.documentElement, {
const service = useMutation(document.querySelector('main'), {
childList: true,
subtree: true,
attributes: true,
Expand All @@ -325,36 +325,49 @@ export class Base<T extends BaseProps = BaseProps> {
const key = this.constructor.__mutationSymbol;
if (!service.has(key)) {
service.add(key, (props) => {
const updates = new Map<Base, () => any>();

for (const instance of getInstances()) {
for (const mutation of props.mutations) {
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());
const updates = new Map<any, () => any>();
const terminations = new Map<any, () => any>();
const instances = getInstances();

for (const mutation of props.mutations) {
// Update parent instance when an instance node has been removed from the DOM.
for (const node of mutation.removedNodes) {
if (node.nodeType !== Node.ELEMENT_NODE) continue;

for (const instance of instances) {
if (updates.has(instance)) continue;
if (instance.$el.isConnected) continue;

if (node.contains(instance.$el)) {
updates.set(instance.$parent, () => instance.$parent.$update());
}
}
}

// Update instances whose child DOM has changed
for (const node of mutation.addedNodes) {
if (node.nodeType !== Node.ELEMENT_NODE) continue;
if (updates.has(node)) continue;

for (const instance of instances) {
if (instance.$el.contains(node)) {
updates.set(node, () => instance.$update());
}
}
}

// Update instances when a data-component attribute has changed
if (mutation.type === 'attributes') {
for (const instance of instances) {
if (updates.has(instance)) continue;

if (instance.$el.contains(mutation.target)) {
updates.set(instance, () => instance.$update());
}
}
}
}

console.log(updates);
for (const update of updates.values()) {
update();
}
Expand Down Expand Up @@ -417,7 +430,6 @@ export class Base<T extends BaseProps = BaseProps> {
await Promise.all([
// Undo
addToQueue(() => this.__refs.unregisterAll()),
addToQueue(() => this.__children.unregisterAll()),
addToQueue(() => this.__services.disableAll()),
// Redo
addToQueue(() => this.__children.registerAll()),
Expand Down
44 changes: 30 additions & 14 deletions packages/js-toolkit/Base/managers/ChildrenManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,27 +25,36 @@ export class ChildrenManager<
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.
*/
async registerAll() {
const previousProps = { ...this.__children };

for (const [name, component] of Object.entries(this.__config.components)) {
this.__register(name, component);
}

let childrenToDestroy = new Set<Base>();
for (const [previousName, previousChildren] of Object.entries(previousProps)) {
if (!this.props[previousName]) {
childrenToDestroy = childrenToDestroy.union(new Set(previousChildren));

Check failure on line 41 in packages/js-toolkit/Base/managers/ChildrenManager.ts

View workflow job for this annotation

GitHub Actions / build

No overload matches this call.

Check failure on line 41 in packages/js-toolkit/Base/managers/ChildrenManager.ts

View workflow job for this annotation

GitHub Actions / code-quality

No overload matches this call.
continue;
}

const previousChildrenSet = new Set(previousChildren);

Check failure on line 45 in packages/js-toolkit/Base/managers/ChildrenManager.ts

View workflow job for this annotation

GitHub Actions / build

No overload matches this call.

Check failure on line 45 in packages/js-toolkit/Base/managers/ChildrenManager.ts

View workflow job for this annotation

GitHub Actions / code-quality

No overload matches this call.
const childrenSet = new Set(this.props[previousName]);

Check failure on line 46 in packages/js-toolkit/Base/managers/ChildrenManager.ts

View workflow job for this annotation

GitHub Actions / build

No overload matches this call.

Check failure on line 46 in packages/js-toolkit/Base/managers/ChildrenManager.ts

View workflow job for this annotation

GitHub Actions / code-quality

No overload matches this call.
const diff = previousChildrenSet.difference(childrenSet);
childrenToDestroy = childrenToDestroy.union(diff);
}

for (const child of childrenToDestroy) {
if (child instanceof Promise) {
child.then(instance => instance.$destroy())
} else {
child.$destroy();
}
}
}

/**
Expand Down Expand Up @@ -94,6 +103,8 @@ export class ChildrenManager<
await instance[hook]();
}

__children: T = {} as T;

/**
* Register instance of a child component.
*
Expand All @@ -105,6 +116,11 @@ export class ChildrenManager<
*/
__register(name: string, component: BaseConstructor | BaseAsyncConstructor) {
Object.defineProperty(this.props, name, {
enumerable: true,
configurable: true,
get: () => this.__getValue(name, component),
});
Object.defineProperty(this.__children, name, {
enumerable: true,
configurable: true,
value: this.__getValue(name, component),
Expand Down

0 comments on commit 151a17d

Please sign in to comment.