1
0
mirror of https://github.com/jaandrle/deka-dom-el synced 2024-11-24 09:29:37 +01:00

💥 customElement (+enhance slotting simulation) + enh. types

This commit is contained in:
Jan Andrle 2023-12-22 16:49:59 +01:00
parent 64ddd3f41f
commit eb920f7bbd
Signed by: jaandrle
GPG Key ID: B3A25AED155AFFAB
14 changed files with 161 additions and 119 deletions

File diff suppressed because one or more lines are too long

29
dist/dde.js vendored

File diff suppressed because one or more lines are too long

View File

@ -107,18 +107,19 @@ type _fromElsInterfaces<EL extends SupportedElement>= Omit<EL, keyof AttrsModifi
* There is added support for `data[A-Z].*`/`aria[A-Z].*` to be converted to the kebab-case alternatives. * There is added support for `data[A-Z].*`/`aria[A-Z].*` to be converted to the kebab-case alternatives.
* @private * @private
*/ */
type ElementAttributes<T extends SupportedElement>= Partial<_fromElsInterfaces<T> & { [K in keyof _fromElsInterfaces<T>]: Observable<_fromElsInterfaces<T>[K], any> } & AttrsModified>; type ElementAttributes<T extends SupportedElement>= Partial<_fromElsInterfaces<T> & { [K in keyof _fromElsInterfaces<T>]: Observable<_fromElsInterfaces<T>[K], any> } & AttrsModified> & Record<string, any>;
export function classListDeclarative<El extends SupportedElement>(element: El, classList: AttrsModified["classList"]): El export function classListDeclarative<El extends SupportedElement>(element: El, classList: AttrsModified["classList"]): El
export function assign<El extends SupportedElement>(element: El, ...attrs_array: ElementAttributes<El>[]): El export function assign<El extends SupportedElement>(element: El, ...attrs_array: ElementAttributes<El>[]): El
export function assignAttribute<El extends SupportedElement, ATT extends keyof ElementAttributes<El>>(element: El, attr: ATT, value: ElementAttributes<El>[ATT]): ElementAttributes<El>[ATT] export function assignAttribute<El extends SupportedElement, ATT extends keyof ElementAttributes<El>>(element: El, attr: ATT, value: ElementAttributes<El>[ATT]): ElementAttributes<El>[ATT]
type ExtendedHTMLElementTagNameMap= HTMLElementTagNameMap & CustomElementTagNameMap & ddePublicElementTagNameMap type ExtendedHTMLElementTagNameMap= HTMLElementTagNameMap & CustomElementTagNameMap & ddePublicElementTagNameMap
type textContent= string | ( (set?: string)=> string ); // TODO: for some reason `Observable<string, any>` leads to `attrs?: any`
export function el< export function el<
TAG extends keyof ExtendedHTMLElementTagNameMap & string, TAG extends keyof ExtendedHTMLElementTagNameMap & string,
EL extends (TAG extends keyof ExtendedHTMLElementTagNameMap ? ExtendedHTMLElementTagNameMap[TAG] : HTMLElement) EL extends (TAG extends keyof ExtendedHTMLElementTagNameMap ? ExtendedHTMLElementTagNameMap[TAG] : HTMLElement)
>( >(
tag_name: TAG, tag_name: TAG,
attrs?: string | Observable<string, any> | ElementAttributes<EL>, attrs?: ElementAttributes<EL> | textContent,
...addons: ddeElementAddon<EL>[] ...addons: ddeElementAddon<EL>[]
): TAG extends keyof ddeHTMLElementTagNameMap ? ddeHTMLElementTagNameMap[TAG] : ddeHTMLElement ): TAG extends keyof ddeHTMLElementTagNameMap ? ddeHTMLElementTagNameMap[TAG] : ddeHTMLElement
export function el( export function el(
@ -126,34 +127,39 @@ export function el(
): ddeDocumentFragment ): ddeDocumentFragment
export function el< export function el<
A extends ddeComponentAttributes, C extends (attr: ddeComponentAttributes)=> SupportedElement | ddeDocumentFragment
C extends (attr: Partial<A>)=> SupportedElement | DocumentFragment
>( >(
component: C, component: C,
attrs?: A | string, attrs?: Parameters<C>[0] | textContent,
...addons: ddeElementAddon<ReturnType<C>>[] ...addons: ddeElementAddon<ReturnType<C>>[]
): ReturnType<C> ): ReturnType<C> extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap] ? ReturnType<C> : ( ReturnType<C> extends ddeDocumentFragment ? ReturnType<C> : ddeHTMLElement )
export { el as createElement } export { el as createElement }
export function elNS( export function elNS(
namespace: "http://www.w3.org/2000/svg" namespace: "http://www.w3.org/2000/svg"
): <TAG, EL extends ( TAG extends keyof ddeSVGElementTagNameMap ? ddeSVGElementTagNameMap[TAG] : ddeSVGElement ), KEYS extends keyof EL & { d: string }>( ): <
TAG extends keyof SVGElementTagNameMap & string,
EL extends ( TAG extends keyof SVGElementTagNameMap ? SVGElementTagNameMap[TAG] : SVGElement ),
>(
tag_name: TAG, tag_name: TAG,
attrs?: string | Partial<{ [key in KEYS]: EL[key] | string | number | boolean }>, attrs?: ElementAttributes<EL> | textContent,
...addons: ddeElementAddon<EL>[] ...addons: ddeElementAddon<EL>[]
)=> EL )=> TAG extends keyof ddeSVGElementTagNameMap ? ddeSVGElementTagNameMap[TAG] : ddeSVGElement
export function elNS( export function elNS(
namespace: "http://www.w3.org/1998/Math/MathML" namespace: "http://www.w3.org/1998/Math/MathML"
): <TAG extends keyof MathMLElementTagNameMap, KEYS extends keyof MathMLElementTagNameMap[TAG] & { d: string }>( ): <
TAG extends keyof MathMLElementTagNameMap & string,
EL extends ( TAG extends keyof MathMLElementTagNameMap ? MathMLElementTagNameMap[TAG] : MathMLElement ),
>(
tag_name: TAG, tag_name: TAG,
attrs?: string | Partial<{ [key in KEYS]: MathMLElementTagNameMap[TAG][key] | string | number | boolean }>, attrs?: string | textContent | Partial<{ [key in keyof EL]: EL[key] | Observable<EL[key], any> | string | number | boolean }>,
...addons: ddeElementAddon<MathMLElementTagNameMap[TAG]>[] ...addons: ddeElementAddon<EL>[]
)=> ddeMathMLElement )=> ddeMathMLElement
export function elNS( export function elNS(
namespace: string namespace: string
): ( ): (
tag_name: string, tag_name: string,
attrs?: string | Record<string, any>, attrs?: string | textContent | Record<string, any>,
...addons: ddeElementAddon<SupportedElement>[] ...addons: ddeElementAddon<SupportedElement>[]
)=> SupportedElement )=> SupportedElement
export { elNS as createElementNS } export { elNS as createElementNS }

File diff suppressed because one or more lines are too long

32
dist/esm.d.ts vendored
View File

@ -107,18 +107,19 @@ type _fromElsInterfaces<EL extends SupportedElement>= Omit<EL, keyof AttrsModifi
* There is added support for `data[A-Z].*`/`aria[A-Z].*` to be converted to the kebab-case alternatives. * There is added support for `data[A-Z].*`/`aria[A-Z].*` to be converted to the kebab-case alternatives.
* @private * @private
*/ */
type ElementAttributes<T extends SupportedElement>= Partial<_fromElsInterfaces<T> & { [K in keyof _fromElsInterfaces<T>]: Observable<_fromElsInterfaces<T>[K], any> } & AttrsModified>; type ElementAttributes<T extends SupportedElement>= Partial<_fromElsInterfaces<T> & { [K in keyof _fromElsInterfaces<T>]: Observable<_fromElsInterfaces<T>[K], any> } & AttrsModified> & Record<string, any>;
export function classListDeclarative<El extends SupportedElement>(element: El, classList: AttrsModified["classList"]): El export function classListDeclarative<El extends SupportedElement>(element: El, classList: AttrsModified["classList"]): El
export function assign<El extends SupportedElement>(element: El, ...attrs_array: ElementAttributes<El>[]): El export function assign<El extends SupportedElement>(element: El, ...attrs_array: ElementAttributes<El>[]): El
export function assignAttribute<El extends SupportedElement, ATT extends keyof ElementAttributes<El>>(element: El, attr: ATT, value: ElementAttributes<El>[ATT]): ElementAttributes<El>[ATT] export function assignAttribute<El extends SupportedElement, ATT extends keyof ElementAttributes<El>>(element: El, attr: ATT, value: ElementAttributes<El>[ATT]): ElementAttributes<El>[ATT]
type ExtendedHTMLElementTagNameMap= HTMLElementTagNameMap & CustomElementTagNameMap & ddePublicElementTagNameMap type ExtendedHTMLElementTagNameMap= HTMLElementTagNameMap & CustomElementTagNameMap & ddePublicElementTagNameMap
type textContent= string | ( (set?: string)=> string ); // TODO: for some reason `Observable<string, any>` leads to `attrs?: any`
export function el< export function el<
TAG extends keyof ExtendedHTMLElementTagNameMap & string, TAG extends keyof ExtendedHTMLElementTagNameMap & string,
EL extends (TAG extends keyof ExtendedHTMLElementTagNameMap ? ExtendedHTMLElementTagNameMap[TAG] : HTMLElement) EL extends (TAG extends keyof ExtendedHTMLElementTagNameMap ? ExtendedHTMLElementTagNameMap[TAG] : HTMLElement)
>( >(
tag_name: TAG, tag_name: TAG,
attrs?: string | Observable<string, any> | ElementAttributes<EL>, attrs?: ElementAttributes<EL> | textContent,
...addons: ddeElementAddon<EL>[] ...addons: ddeElementAddon<EL>[]
): TAG extends keyof ddeHTMLElementTagNameMap ? ddeHTMLElementTagNameMap[TAG] : ddeHTMLElement ): TAG extends keyof ddeHTMLElementTagNameMap ? ddeHTMLElementTagNameMap[TAG] : ddeHTMLElement
export function el( export function el(
@ -126,34 +127,39 @@ export function el(
): ddeDocumentFragment ): ddeDocumentFragment
export function el< export function el<
A extends ddeComponentAttributes, C extends (attr: ddeComponentAttributes)=> SupportedElement | ddeDocumentFragment
C extends (attr: Partial<A>)=> SupportedElement | DocumentFragment
>( >(
component: C, component: C,
attrs?: A | string, attrs?: Parameters<C>[0] | textContent,
...addons: ddeElementAddon<ReturnType<C>>[] ...addons: ddeElementAddon<ReturnType<C>>[]
): ReturnType<C> ): ReturnType<C> extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap] ? ReturnType<C> : ( ReturnType<C> extends ddeDocumentFragment ? ReturnType<C> : ddeHTMLElement )
export { el as createElement } export { el as createElement }
export function elNS( export function elNS(
namespace: "http://www.w3.org/2000/svg" namespace: "http://www.w3.org/2000/svg"
): <TAG, EL extends ( TAG extends keyof ddeSVGElementTagNameMap ? ddeSVGElementTagNameMap[TAG] : ddeSVGElement ), KEYS extends keyof EL & { d: string }>( ): <
TAG extends keyof SVGElementTagNameMap & string,
EL extends ( TAG extends keyof SVGElementTagNameMap ? SVGElementTagNameMap[TAG] : SVGElement ),
>(
tag_name: TAG, tag_name: TAG,
attrs?: string | Partial<{ [key in KEYS]: EL[key] | string | number | boolean }>, attrs?: ElementAttributes<EL> | textContent,
...addons: ddeElementAddon<EL>[] ...addons: ddeElementAddon<EL>[]
)=> EL )=> TAG extends keyof ddeSVGElementTagNameMap ? ddeSVGElementTagNameMap[TAG] : ddeSVGElement
export function elNS( export function elNS(
namespace: "http://www.w3.org/1998/Math/MathML" namespace: "http://www.w3.org/1998/Math/MathML"
): <TAG extends keyof MathMLElementTagNameMap, KEYS extends keyof MathMLElementTagNameMap[TAG] & { d: string }>( ): <
TAG extends keyof MathMLElementTagNameMap & string,
EL extends ( TAG extends keyof MathMLElementTagNameMap ? MathMLElementTagNameMap[TAG] : MathMLElement ),
>(
tag_name: TAG, tag_name: TAG,
attrs?: string | Partial<{ [key in KEYS]: MathMLElementTagNameMap[TAG][key] | string | number | boolean }>, attrs?: string | textContent | Partial<{ [key in keyof EL]: EL[key] | Observable<EL[key], any> | string | number | boolean }>,
...addons: ddeElementAddon<MathMLElementTagNameMap[TAG]>[] ...addons: ddeElementAddon<EL>[]
)=> ddeMathMLElement )=> ddeMathMLElement
export function elNS( export function elNS(
namespace: string namespace: string
): ( ): (
tag_name: string, tag_name: string,
attrs?: string | Record<string, any>, attrs?: string | textContent | Record<string, any>,
...addons: ddeElementAddon<SupportedElement>[] ...addons: ddeElementAddon<SupportedElement>[]
)=> SupportedElement )=> SupportedElement
export { elNS as createElementNS } export { elNS as createElementNS }

2
dist/esm.js vendored

File diff suppressed because one or more lines are too long

View File

@ -31,7 +31,7 @@ export function fullNameComponent(){
), ),
elSVG("svg", { viewBox: "0 0 240 80", style: { height: "80px", display: "block" } }).append( elSVG("svg", { viewBox: "0 0 240 80", style: { height: "80px", display: "block" } }).append(
//elSVG("style", { }) //elSVG("style", { })
elSVG("text", { x: 20, y: 35, textContent: "Text" }) elSVG("text", { x: 20, y: 35, textContent: "Text" }),
) )
); );
} }

View File

@ -1,4 +1,4 @@
import { el, on, scope } from "../../index.js"; import { el, on, customElementRender, customElementWithDDE, scope, simulateSlots } from "../../index.js";
import { O } from "../../observables.js"; import { O } from "../../observables.js";
/** /**
@ -42,35 +42,24 @@ export class CustomHTMLTestElement extends HTMLElement{
get preName(){ return this.getAttribute("pre-name"); } get preName(){ return this.getAttribute("pre-name"); }
set preName(value){ this.setAttribute("pre-name", value); } set preName(value){ this.setAttribute("pre-name", value); }
} }
// https://gist.github.com/WebReflection/ec9f6687842aa385477c4afca625bbf4 customElementWithDDE(CustomHTMLTestElement);
lifecycleToEvents(CustomHTMLTestElement)
customElements.define(CustomHTMLTestElement.tagName, CustomHTMLTestElement); customElements.define(CustomHTMLTestElement.tagName, CustomHTMLTestElement);
function customElementRender(_this, render, props= _this){ export class CustomSlottingHTMLElement extends HTMLElement{
console.log(_this.shadowRoot, _this.childList); static tagName= "custom-slotting";
scope.push({ scope: _this, host: (...c)=> c.length ? c.forEach(c=> c(_this)) : _this, custom_element: _this }); render(){
if(typeof props==="function") props= props(_this); return simulateSlots(this, el().append(
const out= render(props); el("p").append(
scope.pop(); "Ahoj ", el("slot", { name: "name", className: "name", textContent: "World" })
return out; ),
} el("p").append(
function lifecycleToEvents(class_declaration){ "BTW ", el("slot")
for (const name of [ "connected", "disconnected" ]) )
wrapMethod(class_declaration.prototype, name+"Callback", function(target, thisArg, detail){ ));
target.apply(thisArg, detail); }
thisArg.dispatchEvent(new Event("dde:"+name)); connectedCallback(){
}); this.append(customElementRender(this, this.render));
const name= "attributeChanged"; }
wrapMethod(class_declaration.prototype, name+"Callback", function(target, thisArg, detail){
const [ attribute, , value ]= detail;
thisArg.dispatchEvent(new CustomEvent("dde:"+name, {
detail: [ attribute, value ]
}));
target.apply(thisArg, detail);
});
class_declaration.prototype.__dde_lifecycleToEvents= true;
return class_declaration;
}
function wrapMethod(obj, method, apply){
obj[method]= new Proxy(obj[method] || (()=> {}), { apply });
} }
customElementWithDDE(CustomSlottingHTMLElement);
customElements.define(CustomSlottingHTMLElement.tagName, CustomSlottingHTMLElement);

View File

@ -2,7 +2,7 @@ import { style, el } from './exports.js';
document.head.append(style.element); document.head.append(style.element);
import { fullNameComponent } from './components/fullNameComponent.js'; import { fullNameComponent } from './components/fullNameComponent.js';
import { todosComponent } from './components/todosComponent.js'; import { todosComponent } from './components/todosComponent.js';
import { CustomHTMLTestElement } from "./components/webComponent.js"; import { CustomHTMLTestElement, CustomSlottingHTMLElement } from "./components/webComponent.js";
import { thirdParty } from "./components/3rd-party.js"; import { thirdParty } from "./components/3rd-party.js";
document.body.append( document.body.append(
@ -10,5 +10,9 @@ document.body.append(
el(fullNameComponent), el(fullNameComponent),
el(todosComponent), el(todosComponent),
el(CustomHTMLTestElement.tagName, { name: "attr" }), el(CustomHTMLTestElement.tagName, { name: "attr" }),
el(thirdParty) el(thirdParty),
el(CustomSlottingHTMLElement.tagName).append(
el("strong", { slot: "name", textContent: "Honzo" }),
el("span", "…default slot")
)
); );

2
index.d.ts vendored
View File

@ -68,7 +68,7 @@ export function el<
component: C, component: C,
attrs?: Parameters<C>[0] | textContent, attrs?: Parameters<C>[0] | textContent,
...addons: ddeElementAddon<ReturnType<C>>[] ...addons: ddeElementAddon<ReturnType<C>>[]
): ReturnType<C> extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap] ? ReturnType<C> : ddeHTMLElement ): ReturnType<C> extends ddeHTMLElementTagNameMap[keyof ddeHTMLElementTagNameMap] ? ReturnType<C> : ( ReturnType<C> extends ddeDocumentFragment ? ReturnType<C> : ddeHTMLElement )
export { el as createElement } export { el as createElement }
export function elNS( export function elNS(

View File

@ -1,2 +1,3 @@
export * from "./src/dom.js"; export * from "./src/dom.js";
export * from "./src/customElement.js";
export * from "./src/events.js"; export * from "./src/events.js";

View File

@ -1,6 +1,6 @@
{ {
"name": "deka-dom-el", "name": "deka-dom-el",
"version": "0.7.3", "version": "0.7.5",
"description": "A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks.", "description": "A low-code library that simplifies the creation of native DOM elements/components using small wrappers and tweaks.",
"author": "Jan Andrle <andrle.jan@centrum.cz>", "author": "Jan Andrle <andrle.jan@centrum.cz>",
"license": "MIT", "license": "MIT",
@ -58,7 +58,7 @@
"size-limit": [ "size-limit": [
{ {
"path": "./index.js", "path": "./index.js",
"limit": "9 kB", "limit": "9.75 kB",
"gzip": false, "gzip": false,
"brotli": false "brotli": false
@ -71,19 +71,7 @@
}, },
{ {
"path": "./jsdom.js", "path": "./index-with-observables.js",
"limit": "10 kB",
"gzip": false,
"brotli": false
},
{
"path": "./examples/components/webComponent.js",
"limit": "13 kB",
"gzip": false,
"brotli": false
},
{
"path": "./examples/components/webComponent.js",
"limit": "5 kB" "limit": "5 kB"
} }
], ],

33
src/customElement.js Normal file
View File

@ -0,0 +1,33 @@
import { scope } from "./dom.js";
export function customElementRender(custom_element, render, props= custom_element){
scope.push({
scope: custom_element,
host: (...c)=> c.length ? c.forEach(c=> c(custom_element)) : custom_element,
custom_element
});
const out= render.call(custom_element, props);
scope.pop();
return out;
}
export function lifecycleToEvents(class_declaration){
for (const name of [ "connected", "disconnected" ])
wrapMethod(class_declaration.prototype, name+"Callback", function(target, thisArg, detail){
target.apply(thisArg, detail);
thisArg.dispatchEvent(new Event("dde:"+name));
});
const name= "attributeChanged";
wrapMethod(class_declaration.prototype, name+"Callback", function(target, thisArg, detail){
const [ attribute, , value ]= detail;
thisArg.dispatchEvent(new CustomEvent("dde:"+name, {
detail: [ attribute, value ]
}));
target.apply(thisArg, detail);
});
class_declaration.prototype.__dde_lifecycleToEvents= true;
return class_declaration;
}
// https://gist.github.com/WebReflection/ec9f6687842aa385477c4afca625bbf4
export { lifecycleToEvents as customElementWithDDE };
function wrapMethod(obj, method, apply){
obj[method]= new Proxy(obj[method] || (()=> {}), { apply });
}

View File

@ -66,14 +66,14 @@ export function createElement(tag, attributes, ...addons){
scoped= 2; scoped= 2;
return el; return el;
} }
/** @param {HTMLElement} element */ /** @param {HTMLElement} element @param {HTMLElement} [root] */
export function simulateSlots(element){ export function simulateSlots(element, root= element, mapper= undefined){
const _default= Symbol.for("default"); const _default= Symbol.for("default");
const slots= Array.from(element.querySelectorAll("slot")) const slots= Array.from(root.querySelectorAll("slot"))
.reduce((out, curr)=> Reflect.set(out, curr.name || _default, curr) && out, {}); .reduce((out, curr)=> Reflect.set(out, curr.name || _default, curr) && out, {});
const has_d= Reflect.has(slots, _default); const has_d= Reflect.has(slots, _default);
element.append= new Proxy(element.append, { element.append= new Proxy(element.append, {
apply(_1, _2, els){ apply(orig, _, els){
if(!els.length) return element; if(!els.length) return element;
const d= document.createDocumentFragment(); const d= document.createDocumentFragment();
@ -83,19 +83,28 @@ export function simulateSlots(element){
const slot= slots[name]; const slot= slots[name];
elementAttribute(el, "remove", "slot"); elementAttribute(el, "remove", "slot");
if(!slot) continue; if(!slot) continue;
slot.replaceWith(el); simulateSlotReplace(slot, el, mapper);
Reflect.deleteProperty(slots, name); Reflect.deleteProperty(slots, name);
} }
if(has_d){ if(has_d){
slots[_default].replaceWith(d); slots[_default].replaceWith(d);
Reflect.deleteProperty(slots, _default); Reflect.deleteProperty(slots, _default);
} }
Object.values(slots) element.append= orig; //TODO: better memory management, but non-native behavior!
.forEach(slot=> slot.replaceWith(createElement().append(...Array.from(slot.childNodes))));
return element; return element;
} }
}); });
return element; if(element!==root){
const els= Array.from(element.childNodes);
els.forEach(el=> el.remove());
element.append(...els);
}
return root;
}
function simulateSlotReplace(slot, element, mapper){
if(mapper) mapper(slot, element);
try{ slot.replaceWith(assign(element, { className: [ element.className, slot.className ], dataset: { ...slot.dataset } })); }
catch(_){ slot.replaceWith(element); }
} }
/** /**
* @param { { type: "component", name: string, host: "this" | "parentElement" } | { type: "reactive" | "later" } } attrs * @param { { type: "component", name: string, host: "this" | "parentElement" } | { type: "reactive" | "later" } } attrs