Skip to content

Move Element to use class mixins#53

Draft
LeaVerou wants to merge 82 commits intomainfrom
class-mixins
Draft

Move Element to use class mixins#53
LeaVerou wants to merge 82 commits intomainfrom
class-mixins

Conversation

@LeaVerou
Copy link
Contributor

No description provided.

@netlify
Copy link

netlify bot commented Oct 14, 2025

Deploy Preview for nude-element ready!

Name Link
🔨 Latest commit af7a105
🔍 Latest deploy log https://app.netlify.com/projects/nude-element/deploys/6924d2c280207900082339a7
😎 Deploy Preview https://deploy-preview-53--nude-element.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

LeaVerou and others added 3 commits October 15, 2025 17:42
- Apply mixins on initialization of the class itself and its children
- `observedAttributes` getter should have context
LeaVerou and others added 2 commits October 22, 2025 16:07
Co-Authored-By: Dmitry Sharabin <dmitrysharabin@gmail.com>
- Make it easier to check whether a style was adopted by a given root
- add `adoptCSSRecursive()` and use it in the globalStyles mixin
LeaVerou and others added 5 commits November 23, 2025 10:21
- Simplifies the global styles mixin by removing superclasses traversal and updating style resolution logic.
- Refactors fetchCSS to handle promises and failed fetches more robustly.
- `fetchCSS()` already uses a cache, so now we're reading it directly on render to get the most updated version

@DmitrySharabin can you please check that this works?
Co-Authored-By: Dmitry Sharabin <dmitrysharabin@gmail.com>
let skippedProperties = new Set(options.skippedProperties || []);
let sourceDescriptors = Object.getOwnPropertyDescriptors(source);

for (let prop of Reflect.ownKeys(sourceDescriptors)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tried Reflect.ownKeys() before switching to Object.keys() and Object.getOwnPropertySymbols(), and it seems like it gives us more properties than we get with for...in or iterating over object keys and symbols. For example, it gives us all enumerable properties (not only symbols). Is it okay?

Copy link
Contributor Author

@LeaVerou LeaVerou Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All own properties is what we need (strings and symbols). I'm not sure if we need non-own properties too, I'll need to think more about that. But we definitely want own ones.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, own should be enough. We handle superclasses separately in extend-class.js

properties: [
"labels",
"form",
"name",
Copy link
Member

@DmitrySharabin DmitrySharabin Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also remove name. It's not (yet?) in ElementInternals.prototype.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oof yes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, name should be a prop though, since that's how you name form controls. But that can be done later.

this[internals].setFormValue(this[valueProp]));
}

static formAssociated = true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that this shadows a class's own formAssociated properties when this mixin is in use. That means that in [onApply](), config is an empty object regardless. I might suggest the opposite. I don't know which part of the code should be fixed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooooh, well spotted. I have an idea: mixins can include their own conflictPolicy through a symbol to indicate which properties should be skipped, merged, etc. This one could be a property that is skipped. But I'll need to do that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, specifically for this one, I just had an idea that's easier.

[constructed] () {
let config = this.constructor[formAssociated] ?? this.constructor.formAssociated;
let { like, role, valueProp, changeEvent } = config;
let internals = this[internals] || this.attachInternals();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, we are accessing a variable at the time it is defined. We don't need the internals var anymore; it's been replaced by the internals symbol. Following up on our conversation in the PR, we should probably simply call this.attachInternals() here, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This internals var is used differently: to store the internals object. We should replace its usage with this[internals].

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not used anywhere in the code. One place I thought it was, is this one:
image

But here, we pass it as a prop (“prop name”), not the object itself. That's why I replaced this line with this[internals] ??= this.attachInternals() in my PR (and you mentioned that it was wrong), so IDK what to do with this line. Do we need it at all?

LeaVerou and others added 17 commits November 24, 2025 06:48
Co-authored-by: Dmitry Sharabin <dmitrysharabin@gmail.com>
Co-authored-by: Dmitry Sharabin <dmitrysharabin@gmail.com>
Co-authored-by: Dmitry Sharabin <dmitrysharabin@gmail.com>
`ConflictPolicy` seems like an overcomplex solution to the problems, but let's see how it goes, we can always hardcode its abstractions later
Updated ConflictPolicy to clarify strategy types, refactored property handling for merge, overwrite, skip, and throw, and improved exception management.
Simplifies the getSuper function to return only the superclass prototype, removing the ability to fetch a specific property. Updates attachInternals to use the new getSuper signature.
Avoids accidentally extending mixin lifecycle hooks, a problem spotted by @DmitrySharabin
Updated ConflictPolicy to support direct exceptions object and refactored extendClass to instantiate ConflictPolicy for both instance and static options.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants