Add register decorator for web components and a better inheriting system for html elements

This commit is contained in:
Antoine Bartuccio 2024-10-17 23:14:54 +02:00
parent cac185634d
commit 4165f8d4af
4 changed files with 48 additions and 33 deletions

View File

@ -1,16 +1,17 @@
import "tom-select/dist/css/tom-select.css";
import { InheritedComponent } from "#core:utils/web-components";
import { inheritHtmlElement, registerComponent } from "#core:utils/web-components";
import TomSelect from "tom-select";
import type { TomItem, TomLoadCallback, TomOption } from "tom-select/dist/types/types";
import type { escape_html } from "tom-select/dist/types/utils";
import { type UserProfileSchema, userSearchUsers } from "#openapi";
export class AjaxSelect extends InheritedComponent<"select"> {
widget: TomSelect;
filter?: <T>(items: T[]) => T[];
@registerComponent("ajax-select")
export class AjaxSelect extends inheritHtmlElement("select") {
public widget: TomSelect;
public filter?: <T>(items: T[]) => T[];
constructor() {
super("select");
super();
window.addEventListener("DOMContentLoaded", () => {
this.loadTomSelect();
@ -90,5 +91,3 @@ export class AjaxSelect extends InheritedComponent<"select"> {
});
}
}
window.customElements.define("ajax-select", AjaxSelect);

View File

@ -1,7 +1,7 @@
// biome-ignore lint/correctness/noUndeclaredDependencies: shipped by easymde
import "codemirror/lib/codemirror.css";
import "easymde/src/css/easymde.css";
import { InheritedComponent } from "#core:utils/web-components";
import { inheritHtmlElement, registerComponent } from "#core:utils/web-components";
// biome-ignore lint/correctness/noUndeclaredDependencies: Imported by EasyMDE
import type CodeMirror from "codemirror";
// biome-ignore lint/style/useNamingConvention: This is how they called their namespace
@ -9,7 +9,7 @@ import EasyMDE from "easymde";
import { markdownRenderMarkdown } from "#openapi";
const loadEasyMde = (textarea: HTMLTextAreaElement) => {
const easyMde = new EasyMDE({
new EasyMDE({
element: textarea,
spellChecker: false,
autoDownloadFontAwesome: false,
@ -183,12 +183,10 @@ const loadEasyMde = (textarea: HTMLTextAreaElement) => {
}
};
class MarkdownInput extends InheritedComponent<"textarea"> {
@registerComponent("markdown-input")
class MarkdownInput extends inheritHtmlElement("textarea") {
constructor() {
super("textarea");
super();
window.addEventListener("DOMContentLoaded", () => loadEasyMde(this.node));
}
}
window.customElements.define("markdown-input", MarkdownInput);

View File

@ -1,3 +1,15 @@
/**
* Class decorator to register components easily
* It's a wrapper around window.customElements.define
* What's nice about it is that you don't separate the component registration
* and the class definition
**/
export function registerComponent(name: string, options?: ElementDefinitionOptions) {
return (component: CustomElementConstructor) => {
window.customElements.define(name, component, options);
};
}
/**
* Safari doesn't support inheriting from HTML tags on web components
* The technique is to:
@ -6,28 +18,33 @@
* pass all attributes to the child component
* store is at as `node` inside the parent
*
* To use this, you must use the tag name twice, once for creating the class
* and the second time while calling super to pass it to the constructor
* Since we can't use the generic type to instantiate the node, we create a generator function
*
* ```js
* class MyClass extends inheritHtmlElement("select") {
* // do whatever
* }
* ```
**/
export class InheritedComponent<
K extends keyof HTMLElementTagNameMap,
> extends HTMLElement {
node: HTMLElementTagNameMap[K];
export function inheritHtmlElement<K extends keyof HTMLElementTagNameMap>(tagName: K) {
return class Inherited extends HTMLElement {
protected node: HTMLElementTagNameMap[K];
constructor(tagName: K) {
super();
this.node = document.createElement(tagName);
const attributes: Attr[] = []; // We need to make a copy to delete while iterating
for (const attr of this.attributes) {
if (attr.name in this.node) {
attributes.push(attr);
constructor() {
super();
this.node = document.createElement(tagName);
const attributes: Attr[] = []; // We need to make a copy to delete while iterating
for (const attr of this.attributes) {
if (attr.name in this.node) {
attributes.push(attr);
}
}
}
for (const attr of attributes) {
this.removeAttributeNode(attr);
this.node.setAttributeNode(attr);
for (const attr of attributes) {
this.removeAttributeNode(attr);
this.node.setAttributeNode(attr);
}
this.appendChild(this.node);
}
this.appendChild(this.node);
}
};
}

View File

@ -7,6 +7,7 @@
"target": "es6",
"allowJs": true,
"moduleResolution": "node",
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"types": ["jquery", "alpinejs"],