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 "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 TomSelect from "tom-select";
import type { TomItem, TomLoadCallback, TomOption } from "tom-select/dist/types/types"; import type { TomItem, TomLoadCallback, TomOption } from "tom-select/dist/types/types";
import type { escape_html } from "tom-select/dist/types/utils"; import type { escape_html } from "tom-select/dist/types/utils";
import { type UserProfileSchema, userSearchUsers } from "#openapi"; import { type UserProfileSchema, userSearchUsers } from "#openapi";
export class AjaxSelect extends InheritedComponent<"select"> { @registerComponent("ajax-select")
widget: TomSelect; export class AjaxSelect extends inheritHtmlElement("select") {
filter?: <T>(items: T[]) => T[]; public widget: TomSelect;
public filter?: <T>(items: T[]) => T[];
constructor() { constructor() {
super("select"); super();
window.addEventListener("DOMContentLoaded", () => { window.addEventListener("DOMContentLoaded", () => {
this.loadTomSelect(); 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 // biome-ignore lint/correctness/noUndeclaredDependencies: shipped by easymde
import "codemirror/lib/codemirror.css"; import "codemirror/lib/codemirror.css";
import "easymde/src/css/easymde.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 // biome-ignore lint/correctness/noUndeclaredDependencies: Imported by EasyMDE
import type CodeMirror from "codemirror"; import type CodeMirror from "codemirror";
// biome-ignore lint/style/useNamingConvention: This is how they called their namespace // biome-ignore lint/style/useNamingConvention: This is how they called their namespace
@ -9,7 +9,7 @@ import EasyMDE from "easymde";
import { markdownRenderMarkdown } from "#openapi"; import { markdownRenderMarkdown } from "#openapi";
const loadEasyMde = (textarea: HTMLTextAreaElement) => { const loadEasyMde = (textarea: HTMLTextAreaElement) => {
const easyMde = new EasyMDE({ new EasyMDE({
element: textarea, element: textarea,
spellChecker: false, spellChecker: false,
autoDownloadFontAwesome: 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() { constructor() {
super("textarea"); super();
window.addEventListener("DOMContentLoaded", () => loadEasyMde(this.node)); 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 * Safari doesn't support inheriting from HTML tags on web components
* The technique is to: * The technique is to:
@ -6,15 +18,19 @@
* pass all attributes to the child component * pass all attributes to the child component
* store is at as `node` inside the parent * store is at as `node` inside the parent
* *
* To use this, you must use the tag name twice, once for creating the class * Since we can't use the generic type to instantiate the node, we create a generator function
* and the second time while calling super to pass it to the constructor *
* ```js
* class MyClass extends inheritHtmlElement("select") {
* // do whatever
* }
* ```
**/ **/
export class InheritedComponent< export function inheritHtmlElement<K extends keyof HTMLElementTagNameMap>(tagName: K) {
K extends keyof HTMLElementTagNameMap, return class Inherited extends HTMLElement {
> extends HTMLElement { protected node: HTMLElementTagNameMap[K];
node: HTMLElementTagNameMap[K];
constructor(tagName: K) { constructor() {
super(); super();
this.node = document.createElement(tagName); this.node = document.createElement(tagName);
const attributes: Attr[] = []; // We need to make a copy to delete while iterating const attributes: Attr[] = []; // We need to make a copy to delete while iterating
@ -30,4 +46,5 @@ export class InheritedComponent<
} }
this.appendChild(this.node); this.appendChild(this.node);
} }
};
} }

View File

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