Skip to content

Commit 3e9c5d3

Browse files
committed
WIP
1 parent f20c8a3 commit 3e9c5d3

File tree

7 files changed

+124
-80
lines changed

7 files changed

+124
-80
lines changed

packages/demo/meta.config.js

+3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import { prototyping } from '@studiometa/webpack-config-preset-prototyping';
44

55
export default defineConfig({
66
presets: [prototyping({ ts: true })],
7+
server(config) {
8+
config.snippet = false;
9+
},
710
webpack(config) {
811
config.resolve.alias = {
912
...config.resolve.alias,

packages/demo/src/js/app.ts

+5-53
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
BaseConfig,
1212
withDrag,
1313
withName,
14+
getInstances,
1415
} from '@studiometa/js-toolkit';
1516
import { matrix } from '@studiometa/js-toolkit/utils';
1617
import ScrollToDemo from './components/ScrollToDemo.js';
@@ -24,52 +25,6 @@ import ScrolledInViewOffset from './components/ScrolledInViewOffset.js';
2425
import MediaQueryDemo from './components/MediaQueryDemo.js';
2526
import PointerProps from './components/PointerProps.js';
2627

27-
let numberOfTick = 0;
28-
let time = performance.now();
29-
let interval = setInterval(() => {
30-
const newTime = performance.now();
31-
numberOfTick += 1;
32-
console.log('#%d blocking time: %d ms', numberOfTick, newTime - time);
33-
time = newTime;
34-
35-
if (numberOfTick > total * 2) {
36-
clearInterval(interval)
37-
}
38-
}, 0)
39-
40-
let count = 0;
41-
const total = 66;
42-
43-
function getDeepNestedComponentName(index) {
44-
return `TestDeepNested${index}`
45-
}
46-
47-
function makeDeepNestedComponent(index) {
48-
return class extends withExtraConfig(Base, { name: getDeepNestedComponentName(index), components: {} }) {
49-
mounted() {
50-
// console.log(this.$id);
51-
}
52-
};
53-
}
54-
55-
const TestDeepNested = makeDeepNestedComponent(0);
56-
let CurrentClass = TestDeepNested;
57-
while (count < total) {
58-
count += 1;
59-
const NewClass = makeDeepNestedComponent(count);
60-
// @ts-ignore
61-
CurrentClass.config.components[NewClass.config.name] = NewClass;
62-
63-
CurrentClass = NewClass;
64-
}
65-
66-
67-
const TestManyInstance = class extends withExtraConfig(Base, { name: 'TestManyInstance', debug: false }) {
68-
mounted() {
69-
// console.log(this.$id);
70-
}
71-
};
72-
7328
/**
7429
* App class.
7530
*/
@@ -98,8 +53,8 @@ class App extends Base {
9853
AnimateTestMultiple,
9954
ResponsiveOptions,
10055
ScrolledInViewOffset,
101-
TestDeepNested,
102-
TestManyInstance,
56+
// TestDeepNested,
57+
// TestManyInstance,
10358
Accordion: (app) =>
10459
importWhenVisible(
10560
async () => {
@@ -183,6 +138,7 @@ class App extends Base {
183138
* @inheritdoc
184139
*/
185140
mounted() {
141+
window.APP = this;
186142
this.$log('Mounted 🎉');
187143
}
188144

@@ -198,13 +154,9 @@ class App extends Base {
198154
this.$log('resized', props);
199155
}
200156

201-
onDocumentClick(event) {
202-
console.log('onDocumentClick', event);
203-
}
204-
205157
onWindowResize(event) {
206158
console.log('onWindowResize', event);
207159
}
208160
}
209161

210-
export default createApp(App);
162+
export default createApp(App, { root: document.querySelector('main') });

packages/demo/src/js/components/ParentNativeEvent/Child.js

+23
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,28 @@ export default class Child extends Base {
99
*/
1010
static config = {
1111
name: 'Child',
12+
log: true,
13+
debug: false,
1214
};
15+
16+
msg(...msg) {
17+
this.$el.textContent = `[${this.$id}] ${msg.join(' ')}`;
18+
}
19+
20+
mounted() {
21+
this.msg('Mounted', performance.now());
22+
}
23+
24+
updated() {
25+
this.msg('Updated', performance.now());
26+
}
27+
28+
destroyed() {
29+
this.$log('destroyed');
30+
}
31+
32+
terminated() {
33+
this.$log('terminated');
34+
this.msg('Terminated', performance.now());
35+
}
1336
}

packages/demo/src/js/components/ParentNativeEvent/index.js

+16-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Base } from '@studiometa/js-toolkit';
2+
import { createElement } from '@studiometa/js-toolkit/utils';
23
import Child from './Child.js';
34

45
/**
@@ -11,16 +12,27 @@ export default class ParentNativeEvent extends Base {
1112
static config = {
1213
name: 'ParentNativeEvent',
1314
log: true,
15+
debug: false,
1416
components: {
1517
Child,
1618
},
1719
};
1820

19-
onChildClick(...args) {
20-
this.$log(this.$id, 'onChildClick', ...args);
21+
updated() {
22+
this.$log(this.$children.Child);
2123
}
2224

23-
onChildDede(...args) {
24-
this.$log(this.$id, 'onChildDede', ...args);
25+
onDocumentClick({ event }) {
26+
if (event.metaKey) {
27+
this.$el.firstElementChild.remove();
28+
} else if (event.altKey) {
29+
this.$children.Child[0].$el.dataset.component = '';
30+
} else {
31+
this.$el.append(
32+
createElement('button', {
33+
dataComponent: 'Child',
34+
}),
35+
);
36+
}
2537
}
2638
}
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
{% extends '@layouts/base.twig' %}
22
{% block main %}
3-
<div data-component="ParentNativeEvent">
4-
<button data-component="Child">Click me</button>
3+
<div data-component="ParentNativeEvent" class="inline-grid gap-4">
54
<button data-component="Child">Click me</button>
65
</div>
76
{% endblock %}

packages/js-toolkit/Base/Base.ts

+42-20
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
} from './managers/index.js';
1616
import { useMutation } from '../services/MutationService.js';
1717
import { noop, isDev, isFunction, isArray } from '../utils/index.js';
18+
import { features } from './features.js';
1819

1920
let id = 0;
2021

@@ -315,44 +316,65 @@ export class Base<T extends BaseProps = BaseProps> {
315316
this[`__${service.toLowerCase()}`] = new this.__managers[`${service}Manager`](this);
316317
}
317318

318-
const service = useMutation(document.documentElement, { childList: true, subtree: true });
319+
const service = useMutation(document.querySelector('main'), {
320+
childList: true,
321+
subtree: true,
322+
attributes: true,
323+
attributeFilter: [features.get('attributes').component],
324+
});
319325
const key = this.constructor.__mutationSymbol;
320326
if (!service.has(key)) {
321327
service.add(key, (props) => {
322-
const updates = new Map<Base, () => any>();
323-
const terminations = new Map<Base, () => any>();
328+
const actions = new Map<any, () => any>();
329+
const instances = getInstances();
324330

325331
for (const mutation of props.mutations) {
326-
if (mutation.type !== 'childList') continue;
327-
328-
// Terminate components whose root element has been removed from the DOM
332+
// Update parent instance when an instance node has been removed from the DOM.
329333
for (const node of mutation.removedNodes) {
330-
if (node.isConnected) continue;
334+
if (node.nodeType !== Node.ELEMENT_NODE) continue;
335+
336+
for (const instance of instances) {
337+
if (actions.has(instance.$parent ?? instance)) continue;
338+
if (instance.$el.isConnected) continue;
339+
if (!node.contains(instance.$el)) continue;
340+
341+
actions.set(instance.$parent ?? instance, () => {
342+
if (instance.$parent) {
343+
instance.$update();
344+
} else {
345+
instance.$destroy();
346+
}
347+
});
348+
}
349+
}
350+
351+
// Update instances whose child DOM has changed
352+
for (const node of mutation.addedNodes) {
353+
if (node.nodeType !== Node.ELEMENT_NODE) continue;
354+
if (actions.has(node)) continue;
331355

332-
for (const instance of getInstances()) {
333-
if (!terminations.has(instance) && (node === instance.$el || node.contains(instance.$el))) {
334-
terminations.set(instance, () => instance.$terminate());
356+
for (const instance of instances) {
357+
if (instance.$el.contains(node)) {
358+
actions.set(node, () => instance.$update());
335359
}
336360
}
337361
}
338362

339-
// Update components whose children have been terminationd
340-
for (const node of mutation.addedNodes) {
341-
for (const instance of getInstances()) {
342-
if (!updates.has(instance) && instance.$el.contains(node)) {
343-
updates.set(instance, () => instance.$update());
363+
// Update instances when a data-component attribute has changed
364+
if (mutation.type === 'attributes') {
365+
for (const instance of instances) {
366+
if (actions.has(instance)) continue;
367+
368+
if (instance.$el.contains(mutation.target)) {
369+
actions.set(instance, () => instance.$update());
344370
}
345371
}
346372
}
347373
}
348374

349-
for (const update of updates.values()) {
375+
for (const update of actions.values()) {
350376
update();
351377
}
352-
353-
for (const termination of terminations.values()) {
354-
termination();
355-
}
356378
});
357379
}
358380

packages/js-toolkit/Base/managers/ChildrenManager.ts

+34-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import { getComponentElements, addToQueue } from '../utils.js';
55
/**
66
* Children manager.
77
*/
8-
export class ChildrenManager<T> extends AbstractManager<T> {
8+
export class ChildrenManager<
9+
T extends Record<string, Base[] | Promise<Base>[]>,
10+
> extends AbstractManager<T> {
911
/**
1012
* Store async component promises to avoid calling them multiple times and
1113
* waiting for them when they are already resolved.
@@ -27,9 +29,32 @@ export class ChildrenManager<T> extends AbstractManager<T> {
2729
* Register instances of all children components.
2830
*/
2931
async registerAll() {
32+
const previousProps = { ...this.__children };
33+
3034
for (const [name, component] of Object.entries(this.__config.components)) {
3135
this.__register(name, component);
3236
}
37+
38+
let childrenToDestroy = new Set<Base>();
39+
for (const [previousName, previousChildren] of Object.entries(previousProps)) {
40+
if (!this.props[previousName]) {
41+
childrenToDestroy = childrenToDestroy.union(new Set(previousChildren));
42+
continue;
43+
}
44+
45+
const previousChildrenSet = new Set(previousChildren);
46+
const childrenSet = new Set(this.props[previousName]);
47+
const diff = previousChildrenSet.difference(childrenSet);
48+
childrenToDestroy = childrenToDestroy.union(diff);
49+
}
50+
51+
for (const child of childrenToDestroy) {
52+
if (child instanceof Promise) {
53+
child.then(instance => instance.$destroy())
54+
} else {
55+
child.$destroy();
56+
}
57+
}
3358
}
3459

3560
/**
@@ -78,6 +103,8 @@ export class ChildrenManager<T> extends AbstractManager<T> {
78103
await instance[hook]();
79104
}
80105

106+
__children: T = {} as T;
107+
81108
/**
82109
* Register instance of a child component.
83110
*
@@ -114,6 +141,12 @@ export class ChildrenManager<T> extends AbstractManager<T> {
114141
}
115142
}
116143

144+
Object.defineProperty(this.__children, name, {
145+
enumerable: true,
146+
configurable: true,
147+
value: children,
148+
});
149+
117150
return children;
118151
}
119152

0 commit comments

Comments
 (0)