-
Notifications
You must be signed in to change notification settings - Fork 375
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
How to define APIs only for custom element authors #758
Comments
I have a similar realization the other day when polyfilling |
I don't quite understand why delivering (Another "cons" of |
I'm writing up the properties @caridy mentioned. The design problem we're trying to solve there is:
It also means that you can encapsulate semantic information about the host element without needing to register a custom element. However, I can imagine asking the question "why can't I set shadowed versions of any element property on the shadow root?" - which definitely sets us on a road to a kitchen sink scenario. Also, if your custom element doesn't otherwise need a shadow root, it seems like a shame to need to attach one for this purpose. We could theoretically create a new type of object to hold semantic properties, analogous to a constructable stylesheet, but unlike constructable stylesheets there's no precedent for the type of object this would need to be - it's really just a simple, small map of properties. Does anyone have any ideas for alternatives to using |
@alice @tkent-google's post has a number of alternatives, no? We'd need a pick a better name than |
@annevk I should have addressed those better, sorry! Here are my concerns with the those approaches (I count only two, really - the first alternative is just to use
|
That may be an overstatement. Web devs want to make components, so if they move off React or Vue (or etc) to Web Components, they will use ShadowDOM. It's not very low level, it's a normal part of organizing web UI. Any serious web developer should be expected to organize code with components of nested trees; it's standard. I'm in favor not to use it as a kitchen sink for component parts. Rather, ShadowDOM is it self a component part, and should keep it's concerns specific. Other parts can have other jobs. @tkent-google About |
@annevk and @tkent-google I just raised WICG/aom#127 to try and avoid further rabbit-holing here on this specific issue, but I'd appreciate your thoughts over there if you have time! |
One benefit of the With Also, if new |
Oops, nevermind, it was based on the idea of
I think that's my favorite idea. I added an example using that idea here: #738 (comment) To avoid enabling all features (and avoid runtime cost) maybe they can be opt-in? f.e., import Privates from './privates-helper'
const _ = new Privates
class MyEl extends HTMLElement {
useFeatures(features) {
_(this).cssStates = features.use('cssstates')
// or
_(this).cssStates = features.cssStates()
}
connectedCallback() {
// blink every second
setInterval(() => {
_(this).cssStates.has('enabled') ?
_(this).cssStates.remove('enabled') :
_(this).cssStates.add('enabled')
}, 1000)
}
} We could achieve this example with CSS, but this example is similar to how a |
It looks this idea is the most favored. Something might become yet another kitchen-sink, but I think that would be much better than using ShadowRoot here. Could you have a chance to make the idea more concrete one or prototype? |
Many people are favor of the third one. Let's proceed with the third one if no one has a strong opinion against it. We need to define the followings: Interface name
CallbackFirst I thought we could add a My current proposal is |
I guess adding a new custom element callback requires larger code in UA than allowing |
Another name idea: |
|
Would we put all of the properties on what I've called |
@alice I think a flatten structure is just fine. |
Extending from option 3, could the following help with performance? Option 4:
|
@alice @caridy I also suppose flatten structure. @trusktr I'm not sure I understand what you wrote correctly. Probably such feature list isn't necessary because we assume a single Re: callback name |
That's what I was trying to avoid with the idea of a list or mixins, because otherwise it means In Option 5 above, class-factory mixins provide a way to opt-in to using specific features (and avoid resource waste by creating the feature instances only when needed): import UserFeature from 'npm-package'
const {BuiltinFeature1, BuiltinFeature2} = customElements.elementFeatures
class MyEl extends BuiltinFeature1( BuiltinFeature2( UserFeature( HTMLElement ) ) ) {
// ...
}
customElements.define('my-el', MyEl) In that sample, the class will have only the features it has specified. If the list gets long, then const Features = BuiltinFeature1( BuiltinFeature2( UserFeature( HTMLElement ) ) )
class MyEl extends Features {
// ...
} |
@alice An example of option 5 for element semantics would be like // ...
const {withSemantics} = customElements.elementFeatures
class MyEl extends withSemantics( HTMLElement ) {
// ...
receiveSemantics( semantics ) {
const privates = elementPrivates.get(this);
privates.semantics = semantics;
}
}
customElements.define('my-el', MyEl) where in this example, the The The implementation of window.customElements.elementFeatures.withSemantics = function withSemantics(Base) {
return class WithSemantics extends Base {
constructor() {
super()
if (typeof this.receiveSemantics === 'function') {
const semantics = ___; // create the semantics for `this` element
this.receiveSemantics( semantics )
}
}
}
} It could also be placed on |
Another example could be observing children. A mixin could make this an opt-in feature: const {withChildren} = customElements.elementFeatures
class MyEl extends withChildren( HTMLElement ) {
// This callback is provided by the `withChildren` mixin.
childrenChangedCallback() {
// Work with children here, don't worry about
// if children exist in `connectedCallback`.
// Not only will this fire whenever children change, but
// also once after `connectedCallback` has been called.
// Maybe children here are guaranteed to be already
// upgraded if they are custom elements?
}
} |
@trusktr, with your ideas, can we realize APIs which are available only for custom element authors? |
Are you wondering if we can allow the mixins to be used only with Element classes? If so, then yeah we can: The mixin could check the base class to make sure it is an Element class, f.e., pseudo code of the native code in JS: function withAwesomeness(Base = HTMLElement) {
if (!extendsFrom(Base, Element)) throw new TypeError('mixin can only be used on classes that extend from Element')
// continue, return class extends Base...
} Or did you mean something else? |
…chInternals(). - Introduce empty ElementInternals interface - Add HTMLElement.prototype.attachInternals() - Add a flag to ElementRareData about ElementInternals attachment - Promote "ElementInternals" runtime flag to "test" All the change is behind the runtime flag. Some test cases in external/wpt/custom-elements/CustomElementRegistry.html fail by this CL because this CL enables GET() operation for "disabledFeatures", and these test cases doesn't expect it. We should update the testcases later. Bug: 905922 Bug: WICG/webcomponents#758 Change-Id: I7b3dbfd1d422c7eae5c7a6e01ddea50a00134a6e Reviewed-on: https://chromium-review.googlesource.com/c/1341734 Reviewed-by: Hayato Ito <[email protected]> Commit-Queue: Kent Tamura <[email protected]> Cr-Commit-Position: refs/heads/master@{#609231}
I don't think we should be contemplating such unusual patterns for the web platform; we should stick with normal class hierarchies. |
Isn't there a feature of Chrome where new features can be implemented in JavaScript before they are (if ever) converted to C/C++? I've seen the debugger pause on such builtin features before. And, I'm just guessing, but if they can be implemented in JS, could the same mechanics be used on the native side? Okay, assuming implementation details work out fine, is there anything bad about the pattern itself? Why can't WebIDL define such a sort of feature? Is it because it is language specific (JS has this ability)? In that sense, then aren't |
What constitutes a normal hierarchy? Can't we say that after we've mixed a mixin class into a new class definition, that the new definition is now a normal hierarchy? It works just like other hierarchies, plus we have things like By the way, I'm not absolutely married to the idea, but so far I like it. I'm curious to know why it would be a bad pattern compared to others. |
- Stop to make it mandatory It didn't make much sense because web developers can provide empty callbacks. - Add 'mode' argument It is 'restore' if the callback called for state restore by UA. It will be 'autocomplete' if UA's autofilling feature calls the callback. Bug: 905922 Bug: WICG/webcomponents#758 Change-Id: I747eb3f1cba97bd9d0b62ed18fd0504fae7250f9 Reviewed-on: https://chromium-review.googlesource.com/c/1438794 Commit-Queue: Kent Tamura <[email protected]> Auto-Submit: Kent Tamura <[email protected]> Reviewed-by: Hayato Ito <[email protected]> Cr-Commit-Position: refs/heads/master@{#627355}
Hell you all, I just want to reiterate that I feel passing options like
For this reason I think that mechanisms like The end user just needs to define the element, and use it. So in my opinion, authors of the custom elements should use Author: import styled from 'lib1'
import withRender from 'lib2'
import html from 'lib3'
import attribute from 'lib5'
// or const {attribute} = CustomElements // builtin
const [One, Two, Three] = window.ElementFeatures // builtin
@styled({
foo: {
color: 'skyblue'
}
})
class AwesomeElement extends withRender(HTMLElement) {
static elementInternals = [One, Two, Three]
static userAgentStyle = new window.CSSStyleSheet( ... )
@attribute('material-color', Color)
materialColor = new Color('red')
render() {
return html`<div class="${this.classes.foo}"></div>`
}
} End user: import AwesomeElement from 'awesome-element'
customElements.define('awesome-el', AwesomeElement)
document.body.innerHTML = `<awesome-el material-color="pink"></awesome-el>` But then again, the author could abstract it away. Author: // adding onto the same file:
export const define = name => {
customElements.define(name, AwesomeElement, {
userAgentStyle: new window.CSSStyleSheet( ... ),
elementInternals: [One, Two, Three]
})
} End user: import define from 'awesome-element'
define('awesome-el')
document.body.innerHTML = `<awesome-el></awesome-el>` I guess abstracting works, but now its an extra step. If I'm already using |
The only reason why class MyEl extends HTMLElement {
static get elementName() { return "my-el" }
}
customElements.define(MyEl) So unless it is critical for an end user to change something, then I feel it doesn't need to go into |
But then again, isn't an end user able to change stuff by extending from a F.e., a class can easily be extended by an end user and a class MyEl extends HTMLElement {
static get elementName() { return "my-el" }
}
class NewEl extends MyEl {
static get elementName() { return "new-el" }
}
customElements.define(MyEl) // defines <my-el> element
customElements.define(NewEl) // defines <new-el> element What would be the downside of having class-specifics inside the class definitions instead passing the specifics to I can see passing the name into |
As for |
This fixes a part of WICG/webcomponents#758
This fixes a part of WICG/webcomponents#758
This provides the ElementInternals interface, which can be obtained for custom elements via the element.attachInternals() method. For now ElementInternals is empty, but it will gain members in #4383. This also adds the ability for custom elements to set the disabledFeatures static property, to disable element internals and shadow DOM. Some DOM-side infrastructure work there is necessary in whatwg/dom#760. Tests: - web-platform-tests/wpt#15123 - web-platform-tests/wpt#15516 - web-platform-tests/wpt#16853 Fixes WICG/webcomponents#758.
This fixes a part of WICG/webcomponents#758
This was discussed as part of WICG/webcomponents#758, and integrates with whatwg/html#4324.
Does anyone have an ElementInternals explainer? |
@justinfagnani AFAIK, Form Participation API Explained is the only document other than the HTML specification. |
In due course we should probably add an introduction section to the HTML Standard. |
Maybe we should simply point out that customElements are not a magic thing without the define stuff and all that they are //CustomElement based on HTMLUnknownElement
const myCustomElement = document.createElement('my-element')
//Then apply some lhooks or what ever to it
myCustomElement.connectedCallback = ()=> {
this.innerHTML = '<p>Iam a Complex Application </p>'
}
document.body.append(myCustomElement)
// We can also use existing HTML Elements
const MYCustomElment = document.getElementById('my-element')
MYCustomElment.connectedCallback = () => {
this.innerHTML = '<p>Iam a Complex Application </p>'
}
document.body.append(myCustomElement) Now the leaved out question is how does get connectedCallback get called ? you can do it manual or via mutationObserve and register it then you can also attach more behavior or bindings its a customElement so your free as long as you understand a CustomElement is a modifyed version of a HTMLElement Instance. |
I wrote a new issue describing how an internals callback would be a lot better than the current |
The following issues need APIs which should be used by custom element authors and should not be used by custom element users.
At the March F2F we came up with one idea. However using
ShadowRoot
for such APIs looks very weird to me and I'd like to discuss it again.Candidates:
ShadowRoot
Pros: Usually only custom element implementation knows the instance of
ShadowRoot
which is used to implement the custom element. It's difficult for custom element users to get aShadowRoot
instance created by a custom element implementation [1]Cons:
ShadowRoot
is an interface for tree-encapsulation. Adding features unrelated to tree-encapsulation looks like a design defect. We should not makeShadowRoot
a kitchen sink.Cons: Not all custom element implementations need
ShadowRoot
. For example, a checkbox custom element won't needShadowRoot
. Creating unnecessaryShadowRoot
for such APIs is not reasonable.Cons: If a custom element implementation uses no
ShadowRoot
, a custom element user can callelement.attachShadow()
to get aShadowRoot
instance, and thus get access to these private APIs.new Something(customElement)
It throws if users try to create it for the same element twice. It throws if the constructor is called with non-custom elements. The interface
Something
is called as ElementStates in Custom pseudo-classes for host elements via shadow roots (:state) #738, and called as HTMLElementPrimitives in Need callback for form submit data #187.Pros:
ShadowRoot
won't have unrelated APIs.Pros: Usually only custom element implementation knows the instance of
Something
. It's difficult for custom element users to get aSomething
instance created by a custom element implementation [1]Cons: If a custom element implementation uses no
Something
, a custom element user can callnew Something(element)
successfully to get aSomething
instance.Deliver a
Something
instance by a custom element callbackSee Need callback for form submit data #187 (comment) for more details.
Pros:
ShadowRoot
won't have unrelated APIs.Pros: Custom element users can not create a
Something
instance unlike other two candidates.Pros: It's difficult for custom element users to get a
Something
instance delivered to a custom element [1]Cons: UA implementation would need larger code for a new callback, compared to the other two candidates.
IMO, the second one or the third one is much better than the first one.
@annevk @domenic @rniwa @trusktr What do you think?
[1] If a custom element implementation stores a
ShadowRoot
/Something
instance tothis.foo_
, a custom element user can get the instance by accessingyourElement.foo_
. There are some techniques to avoid such casual access.The text was updated successfully, but these errors were encountered: