-
-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
observing the composed tree synchronously #3
Comments
There are some other issues I stumbled on of people wanting to observe the composed tree, but I can't find them right now. It will be nice to link them here. |
I've added tests to Lume here locally now, and have verified they easily break expectations just as with |
My algo relies on features like All Lume current elements have mode:open roots, but I can't guarantee that some user of Lume doesn't make a new element with mode:closed. EDIT: Hmm, well I suppose I can patch global |
There's indeed a great need for observing the composed tree. And the My initial thoughts on a
Not sure how much of a good idea this is but will give things a try. |
I think if we can make something reliable and easy to use, its a good idea! I'd imagine thise would be built on top of realtime, and could possible be a separate module (import separately only if needed, to avoid globals that have a bunch of unused APIs). I'm imagining there'd be something separate for use on any element from the outside: class ComposedChildObserver {
constructor(callback) {
this.callback = callback
}
observe(element) {
// implement with realtime()
}
}
const observer = new ComposedChildObserver((changes) => {
for (const change of changes) {
for (const composed of change.composedChildren) console.log(composed)
for (const uncomposed of change.uncomposedChildren) console.log(composed)
}
})
observer.observe(someElement) or similar. And maybe then also And then a mixin like what I have in Lume could be impemented using those: function CompositionTracker(Base) {
return class extends Base {
composedCallback(composedParent, compositionType) {/*...subclass implements...*/}
uncomposedCallback(uncomposedParent, compositionType) {/*...subclass implements...*/}
childComposedCallback(composedChild, compositionType) {/*...subclass implements...*/}
childUncomposedCallback(uncomposedChild, compositionType) {/*...subclass implements...*/}
// ... use realtime() as needed to call those methods if they are defined ...
}
} |
After we get that far, it would be interesting to implement reactive interfaces with Solid, f.e.: const parent = createParentSignal(someElement)
const parent2 = createParentSignal(anotherElement)
createEffect(() => {
// any time either parent changes, log:
console.log(parent(), parent2())
}) And then after that :D perhaps an object API that creates the instantiates the underlying observation primitives only upon access (downside is it includes all code, so importing this API would have a bigger size): const proxy = createElementProxy(someElement) // not sure about the `createElementProxy` name, but for sake of example
createEffect(() => {
// any time the parent, children, or rootNode change, log them:
console.log(proxy.parent, proxy.children, proxy.rootNode)
})
createEffect(() => {
// log pointer states (bunching them up in a single effect is probably not what we want, but for sake of example):
console.log(
proxy.pointer.down.x, proxy.pointer.down.y,
proxy.pointer.move.x, proxy.pointer.move.y,
proxy.pointer.up.shiftKey)
}) |
…early, before anything is loaded. See the [`<loading-icon>` docs and example](https://docs.lume.io/api/examples/LoadingIcon)! - feat: add `composedCallback(composedParent, compositionType)` and `uncomposedCallback(uncomposedParent, compositionType)` methods that an element can implement to observe its _composed parent_ in the DOM _composed tree_. ```js class MyElement extends CompositionTracker(HTMLElement) { // as usual connectedCallback() {/*...*/} disconnectedCallback() {/*...*/} childComposedCallback(composedChild, compositionType) {/*...*/} childUncomposedCallback(uncomposedChild, compositionType) {/*...*/} // new methods composedCallback(composedParent, compositionType) {/*...*/} uncomposedCallback(uncomposedParent, compositionType) {/*...*/} } ``` - feat: add a new `.backgroundIntensity` property (`background-intensity` attribute) to the `Scene` class for controlling the scene background texture's intensity. `1` is full color and `0` is black. ```html <lume-scene background="cityscape.jpg" background-intensity="0.6" equirectagular-background></lume-scene> ``` - refactor: simplify and robustify camera handling by making `Scene`, `CameraRig`, `PerspectiveCamera`, `ScrollFling`, `PinchFling`, and `FlingRotation` classes more reactive in various spots and less imperative. Gotta love signals and effects! - fix: when an element was disconnected and reconnected to DOM, a behavior might erroneously try to pull property values from its host element that could be `undefined`, passing them on to Three.js objects such as materials, which would cause meshes to silently render as invisible due to broken materials and without any errors in console. Now behaviors fall back to their own value for `undefined` values. - fix: improve composition tracking so that child disconnected/connected events are not missed when a child is synchronously removed from and added to the same parent, otherwise state would get out of sync with state that is controlled by a child's connectedCallback and disconnectedCallback. Add manual tests in `/examples/tests/basic-shadow-dom.html`. - fix: add a manual test cases in `/examples/tests/basic-shadow-dom.html` to catch the edge cases with slotchange events and composition reactions that prevented us from running `composedCallback` and `uncomposedCallback` in the correct order, and fix the edge cases. We will improve this code by subsequently migrating `CompositionTracker` to being implemented with Oxford Harrison's `realdom` library. webqit/realdom#3 - fix: use the same timing in `Css3dRendererThree` as with `WebGLRendererThree` on resize, so that the visual output of both render modes are not out of sync by one frame when the scene is resized. - refactor: a scene's `webgl` and `enable-css` attributes no longer unload all Three.js objects in the whole tree when disabled, and now only the renderers used by the scene are cleaned up but all elements in the tree keep their objects intact. This makes everything a lot simpler. If you want to clean an element up, remove it from the DOM and drop your reference to it. - refactor: remove `sizechange` observation from renderers, and instead have the Scene call back to the renderers via their `.updateResolution()` method when the scene's size changes. - BREAKING: The `<lume-camera-rig>` element without `distance`, `min-distance`, or `max-distance` supplied now shows the same view as a scene's default camera when no camera is explicitly used in the tree, and the rig automatically adjusts itself in order to show the same view as the default camera will based on a scene's `perspective` value, matching the behavior of CSS `perspective`. When a scene's `perspective` changes, the camera rig's `distance` and the camera's `fov` will adjust to match CSS perspective behavior, and the rig's `min-distance` and `max-distance` properties will auto-adjust to be 1/2 and 2 times the `distance` respectively so that the default rotating view feels roughly the same no matter what `perspective` is applied. If your initial camera view is off when you're using `<lume-camera-rig>`, migrate by providing initial values for `distance`, `min-distance`, and `max-distance`. - BREAKING: if you are using `observeChildren`, the `skipTextNodes` option has been renamed to `includeTextNodes` and text nodes and comments nodes are now ignored by default so you will need to set this to `true` to get back the previous default behavior, or delete the `skipTextNodes` option to keep skipping text and comment nodes. By default `observeChildren` will now call `onConnect` and `onDisconnect` for all mutaion records, not only the final result. Previously, disconnecting and reconnecting a node from and to the same parent would result in a net zero change and callbacks would not be called, but now they will be called. Set the new `weighted` option to `true` to get back the previous behavior. - BREAKING: remove `GL_LOAD`/`GL_UNLOAD`/`CSS_LOAD`/`CSS_UNLOAD` events. When an element is connected, GL content is created, no need for the events, and toggling a scene's `webgl` or `enable-css` attributes no longer destroys all rendering objects in the tree, only enables or disables the renderers that the scene uses. To migrate, run your logic when elements are connected, and clean up when they are disconnected. Before: ```js const el = document.createElement('lume-mesh') el.on('GL_LOAD', () => { console.log(el.three.material.color) }) // ...later scene.append(el) ``` After: ```js const el = document.createElement('lume-mesh') // ...later scene.append(el) console.log(el.three.material.color) ``` - BREAKING: removed `_loadGL`, `_unloadGL`, `_loadCSS`, `_unloadCSS` methods in element classes (and same for the non-`_`-prefixed methods of the same name in behavior classes) and moved logic to `connectedCallback` and `disconnectedCallback` methods (in both elements and behaviors) to make things simpler. We also removed `createGLEffect` and `createCSSEffect` and replaced usages with `createEffect`. To migrate, use `connectedCallback` and `disconnectedCallback` for creation/cleanup, and create effects with `createEffect`. Before: ```js import {Mesh, element, attribute} from 'lume' @element('my-mesh') class MyMesh extends Mesh { @Attribute foo = "bar" _loadGL() { if (!super._loadGL()) return this.createGLEffect(() => console.log(this.foo)) return true } } ``` After: ```js import {Mesh, element, attribute} from 'lume' @element('my-mesh') class MyMesh extends Mesh { @Attribute foo = "bar" connectedCallback() { super.connectedCallback() this.createEffect(() => console.log(this.foo)) } } ``` And similarly with `*CSS*` variants. - BREAKING: Along with the previous point, we removed the `glLoaded` and `cssLoaded` reactive properties from both elements and behaviors. If you depended on those, run your logic after an element is connected into the DOM, cleanup after it is removed (rather than using an effect and returning early when `glLoaded` or `cssLoaded` is false). Before: ```js const el = document.createElement('lume-mesh') createEffect(() => { if (!el.glLoaded) return console.log(el.three.material.color) }) // ...later scene.append(el) ``` After: ```js const el = document.createElement('lume-mesh') // ...later scene.append(el) console.log(el.three.material.color) ``` - refactor: Remove some `disconnectedCallback`s and instead use `onCleanup` in effects created in `connectedCallback`s.
^ See the whatwg/dom issue I linked.
I've added more manually-operated tests here. I see you implemented the
|
I think this might be a good foundation to start to observe the composed tree synchronously with. What I want to do is run logic synchronously when
and similar for all uncomposed reactions.
I have an initial version of this in my
CompositionTracker
class mixin. It has some limintations: currently it only works with a tree of elements that all extend from the same base class (i.e. it only detects composition with Lume elements, but not composition of a Lume element into any arbitrary non-Lume element because the implementation relies on custom element callbacks which built-in elements don't have, but that's whererealdom
comes in).Also after trying a bunch of things, my code in that area has become a bit messy, and some details that should be in
CompositionTracker
are currently also here inSharedAPI
. I need to clean it up and makeCompositionTracker
full generic, and I'd like for it to detect composition for a custom element regardless if the custom element is composed to a custom element or built-in element.There's a problem with
slotchange
events: we do not get a set of mutation records, we can only detect the final nodes that are slotted, and for example we cannot run observations for nodes that are both removed and added within the same tick:SlotChangeObserver
API similar toMutationObserver
withchildList:true
WICG/webcomponents#1042But I believe that with
realdom
we can synchronously track the set of possible mutations that would lead to a slotchange event being fired, and instead of relying onslotchange
we can fire our own handlers at those moments.With
slotchange
events, just as with theMutationObserver
ordering problem,childList
records across multipleMutationObserver
s are observed in fundamentally incorrect order whatwg/dom#1111there is also the chance that when a node is unslotted from one slot and slotted to another, the slotted reaction may fire before the unslotted reaction, leaving things in a broken state. Because of this, some edge cases in Lume are for sure broken (I haven't added tests for these cases yet, but when Lume goes mainstream, I really don't want anyone to run into such obscure problems).
The text was updated successfully, but these errors were encountered: