Per the Custom Elements spec, it is possible to extend native elements.
I am wondering how to do this using stencil. A specific use case would be if I wanted to create a specific select component (call it yes-no-select) and encapsulate the options. Using plain old custom elements, I would automatically be able to inherit all of the select options using the following code:
(function(){
customElements.define('yes-no-select', class extends HTMLSelectElement{
constructor(){super()}
options(selected) {
return [
{
name: '',
value: ''
},
{
name: 'Yes',
value: true
},
{
name: 'No',
value: false
}].map(row => {
return selected != undefined && Boolean(selected) === row.value ? `<option value="${row.value}" selected>${row.name}</option>` : `<option value="${row.value}">${row.name}</option>`
}).join('')
}
connectedCallback() {
this.setAttribute('style', 'display: block')
this.innerHTML = `${this.options(this.getAttribute('selected-value'))}`
}
}, {extends: 'select'})
})()
I am wondering how this would translate.
Hello! Thanks for using Stencil! While the spec does call for allowing you to extend native elements, like button, this has not been implemented in browsers as the webkit/Safari team had some concerns around this. So for now, you can only extend HTMLElement, which the web components that stencil generates do extend.
Hi there, sorry for commenting on a 1+ year old comment :) but this functionality is still not there.
I came across this blog post (https://hackernoon.com/extending-built-in-elements-9dce404b75b4) where the author suggests to use https://github.com/WebReflection/built-in-element/#built-in-element as a polyfill for Safari and others. It's very lightweight, so I was wondering if it could be included as part of the Stencil toolkit? Here's the demo: https://webreflection.github.io/built-in-element/test/es5/
Having said that, my current pain point is that I need to migrate an existing component library (which in this particular scenario means I don't have a lot of choice on the decisions made on the original component library), where I have components that implement a single html element, like <ti-link> which renders an <a>.
This means that if I want the <a> to have all the default attributes (and I can't use the <a> tag directly), then I need to replicate all the a attributes in the ti-link component. Having <a is="ti-link"> would solve my problems, but since it's not there I've come up with an alternative solution that I'd like to share with you.
I've created this helper function in a module:
export function cloneAttributes<T = HTMLAttributes | NamedNodeMap>(el: { attributes: HTMLAttributes | NamedNodeMap }) {
return Object.values(<T>el.attributes).reduce((acc: any, attr: Attr): T => {
acc[attr.name] = attr.value;
return acc;
}, {});
}
and it's used inside a component like this:
import {Component, Element, JSXElements} from "@stencil/core";
import {cloneAttributes} from "../../ti-component";
import ImgHTMLAttributes = JSXElements.ImgHTMLAttributes;
@Component({
tag: 'ti-img',
styleUrl: 'img.scss'
})
export class Img {
@Element() el: HTMLImageElement;
render() {
return <img {...cloneAttributes<ImgHTMLAttributes<HTMLImageElement>>(this.el)}/>;
}
}
Once again, sorry for the zombie resurrection, but there does not appear to have been any progress on this issue. Wrapping native HTML elements without loss of functionality is a super common issue, so it's a little baffling to me that this hasn't seen more action.
My question is for @fsodano - how does your solution play with accessibility? For example, if I want to use this technique to create a <my-select id="mySelect">, and I want to use a <label for="mySelect">, this will not actually work to pass-through focus to the underlying <select> element. Correct? I would need to manually pass-through the id, since it must be unique. I believe other a11y properties would be problematic for similar reasons.
Have you encountered this? And have you found a clean solution?
In your case, I would place the label inside the <my-select> component, this should be part of your components API, and everything should be sorted out inside
But, it's been a while since I've used Stencil since I couldn't get around this issue.
Most helpful comment
Hi there, sorry for commenting on a 1+ year old comment :) but this functionality is still not there.
I came across this blog post (https://hackernoon.com/extending-built-in-elements-9dce404b75b4) where the author suggests to use https://github.com/WebReflection/built-in-element/#built-in-element as a polyfill for Safari and others. It's very lightweight, so I was wondering if it could be included as part of the Stencil toolkit? Here's the demo: https://webreflection.github.io/built-in-element/test/es5/
Having said that, my current pain point is that I need to migrate an existing component library (which in this particular scenario means I don't have a lot of choice on the decisions made on the original component library), where I have components that implement a single html element, like
<ti-link>which renders an<a>.This means that if I want the
<a>to have all the default attributes (and I can't use the<a>tag directly), then I need to replicate all theaattributes in theti-linkcomponent. Having<a is="ti-link">would solve my problems, but since it's not there I've come up with an alternative solution that I'd like to share with you.I've created this helper function in a module:
and it's used inside a component like this: