1
0
mirror of https://github.com/jaandrle/deka-dom-el synced 2024-11-22 16:55:23 +01:00

🚀 implementing #16

This commit is contained in:
Jan Andrle 2024-01-14 13:48:18 +01:00
parent 7e4c804184
commit 51ccd11f82
Signed by: jaandrle
GPG Key ID: B3A25AED155AFFAB
12 changed files with 145 additions and 101 deletions

File diff suppressed because one or more lines are too long

40
dist/dde.js vendored

File diff suppressed because one or more lines are too long

View File

@ -54,7 +54,7 @@ interface observable{
* */ * */
el<S extends any>(observable: Observable<S, any>, el: (v: S)=> Element | Element[] | DocumentFragment): DocumentFragment; el<S extends any>(observable: Observable<S, any>, el: (v: S)=> Element | Element[] | DocumentFragment): DocumentFragment;
attribute(name: string, initial?: string): Observable<string, {}>; observedAttributes(custom_element: HTMLElement): Record<string, Observable<any, any>>;
} }
export const observable: observable; export const observable: observable;
export const O: observable; export const O: observable;
@ -64,16 +64,11 @@ declare global {
type ddeActions<V>= Actions<V> type ddeActions<V>= Actions<V>
} }
type CustomElementTagNameMap= { '#text': Text, '#comment': Comment } type CustomElementTagNameMap= { '#text': Text, '#comment': Comment }
declare global {
interface ddePublicElementTagNameMap{
}
}
type SupportedElement= type SupportedElement=
HTMLElementTagNameMap[keyof HTMLElementTagNameMap] HTMLElementTagNameMap[keyof HTMLElementTagNameMap]
| SVGElementTagNameMap[keyof SVGElementTagNameMap] | SVGElementTagNameMap[keyof SVGElementTagNameMap]
| MathMLElementTagNameMap[keyof MathMLElementTagNameMap] | MathMLElementTagNameMap[keyof MathMLElementTagNameMap]
| CustomElementTagNameMap[keyof CustomElementTagNameMap] | CustomElementTagNameMap[keyof CustomElementTagNameMap]
| ddePublicElementTagNameMap[keyof ddePublicElementTagNameMap];
declare global { declare global {
type ddeComponentAttributes= Record<any, any> | undefined; type ddeComponentAttributes= Record<any, any> | undefined;
type ddeElementAddon<El extends SupportedElement | DocumentFragment>= (element: El)=> El | void; type ddeElementAddon<El extends SupportedElement | DocumentFragment>= (element: El)=> El | void;
@ -112,7 +107,7 @@ export function classListDeclarative<El extends SupportedElement>(element: El, c
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;
type textContent= string | ( (set?: string)=> string ); // TODO: for some reason `Observable<string, any>` leads to `attrs?: any` 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,
@ -239,9 +234,22 @@ export const scope: {
pop(): ReturnType<Array<Scope>["pop"]>, pop(): ReturnType<Array<Scope>["pop"]>,
}; };
/* TypeScript MEH // TODO for SVG */ export function customElementRender<
type ddeAppend<el>= (...nodes: (Node | string)[])=> el; EL extends HTMLElement,
P extends any = Record<string, any>
>(
custom_element: EL,
render: (props: P)=> SupportedElement,
props?: P | ((...args: any[])=> P)
): EL
export function customElementWithDDE<EL extends HTMLElement>(custom_element: EL): EL
export function lifecycleToEvents<EL extends HTMLElement>(custom_element: EL): EL
export function observedAttributes(custom_element: HTMLElement): Record<string, string>
/* TypeScript MEH */
declare global{ declare global{
type ddeAppend<el>= (...nodes: (Node | string)[])=> el;
interface ddeDocumentFragment extends DocumentFragment{ append: ddeAppend<ddeDocumentFragment>; } interface ddeDocumentFragment extends DocumentFragment{ append: ddeAppend<ddeDocumentFragment>; }
interface ddeHTMLElement extends HTMLElement{ append: ddeAppend<ddeHTMLElement>; } interface ddeHTMLElement extends HTMLElement{ append: ddeAppend<ddeHTMLElement>; }
interface ddeSVGElement extends SVGElement{ append: ddeAppend<ddeSVGElement>; } interface ddeSVGElement extends SVGElement{ append: ddeAppend<ddeSVGElement>; }

File diff suppressed because one or more lines are too long

26
dist/esm.d.ts vendored
View File

@ -54,7 +54,7 @@ interface observable{
* */ * */
el<S extends any>(observable: Observable<S, any>, el: (v: S)=> Element | Element[] | DocumentFragment): DocumentFragment; el<S extends any>(observable: Observable<S, any>, el: (v: S)=> Element | Element[] | DocumentFragment): DocumentFragment;
attribute(name: string, initial?: string): Observable<string, {}>; observedAttributes(custom_element: HTMLElement): Record<string, Observable<any, any>>;
} }
export const observable: observable; export const observable: observable;
export const O: observable; export const O: observable;
@ -64,16 +64,11 @@ declare global {
type ddeActions<V>= Actions<V> type ddeActions<V>= Actions<V>
} }
type CustomElementTagNameMap= { '#text': Text, '#comment': Comment } type CustomElementTagNameMap= { '#text': Text, '#comment': Comment }
declare global {
interface ddePublicElementTagNameMap{
}
}
type SupportedElement= type SupportedElement=
HTMLElementTagNameMap[keyof HTMLElementTagNameMap] HTMLElementTagNameMap[keyof HTMLElementTagNameMap]
| SVGElementTagNameMap[keyof SVGElementTagNameMap] | SVGElementTagNameMap[keyof SVGElementTagNameMap]
| MathMLElementTagNameMap[keyof MathMLElementTagNameMap] | MathMLElementTagNameMap[keyof MathMLElementTagNameMap]
| CustomElementTagNameMap[keyof CustomElementTagNameMap] | CustomElementTagNameMap[keyof CustomElementTagNameMap]
| ddePublicElementTagNameMap[keyof ddePublicElementTagNameMap];
declare global { declare global {
type ddeComponentAttributes= Record<any, any> | undefined; type ddeComponentAttributes= Record<any, any> | undefined;
type ddeElementAddon<El extends SupportedElement | DocumentFragment>= (element: El)=> El | void; type ddeElementAddon<El extends SupportedElement | DocumentFragment>= (element: El)=> El | void;
@ -112,7 +107,7 @@ export function classListDeclarative<El extends SupportedElement>(element: El, c
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;
type textContent= string | ( (set?: string)=> string ); // TODO: for some reason `Observable<string, any>` leads to `attrs?: any` 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,
@ -239,9 +234,22 @@ export const scope: {
pop(): ReturnType<Array<Scope>["pop"]>, pop(): ReturnType<Array<Scope>["pop"]>,
}; };
/* TypeScript MEH // TODO for SVG */ export function customElementRender<
type ddeAppend<el>= (...nodes: (Node | string)[])=> el; EL extends HTMLElement,
P extends any = Record<string, any>
>(
custom_element: EL,
render: (props: P)=> SupportedElement,
props?: P | ((...args: any[])=> P)
): EL
export function customElementWithDDE<EL extends HTMLElement>(custom_element: EL): EL
export function lifecycleToEvents<EL extends HTMLElement>(custom_element: EL): EL
export function observedAttributes(custom_element: HTMLElement): Record<string, string>
/* TypeScript MEH */
declare global{ declare global{
type ddeAppend<el>= (...nodes: (Node | string)[])=> el;
interface ddeDocumentFragment extends DocumentFragment{ append: ddeAppend<ddeDocumentFragment>; } interface ddeDocumentFragment extends DocumentFragment{ append: ddeAppend<ddeDocumentFragment>; }
interface ddeHTMLElement extends HTMLElement{ append: ddeAppend<ddeHTMLElement>; } interface ddeHTMLElement extends HTMLElement{ append: ddeAppend<ddeHTMLElement>; }
interface ddeSVGElement extends SVGElement{ append: ddeAppend<ddeSVGElement>; } interface ddeSVGElement extends SVGElement{ append: ddeAppend<ddeSVGElement>; }

2
dist/esm.js vendored

File diff suppressed because one or more lines are too long

12
index.d.ts vendored
View File

@ -1,16 +1,11 @@
import { Observable } from "./observables"; import { Observable } from "./observables";
type CustomElementTagNameMap= { '#text': Text, '#comment': Comment } type CustomElementTagNameMap= { '#text': Text, '#comment': Comment }
declare global {
interface ddePublicElementTagNameMap{
}
}
type SupportedElement= type SupportedElement=
HTMLElementTagNameMap[keyof HTMLElementTagNameMap] HTMLElementTagNameMap[keyof HTMLElementTagNameMap]
| SVGElementTagNameMap[keyof SVGElementTagNameMap] | SVGElementTagNameMap[keyof SVGElementTagNameMap]
| MathMLElementTagNameMap[keyof MathMLElementTagNameMap] | MathMLElementTagNameMap[keyof MathMLElementTagNameMap]
| CustomElementTagNameMap[keyof CustomElementTagNameMap] | CustomElementTagNameMap[keyof CustomElementTagNameMap]
| ddePublicElementTagNameMap[keyof ddePublicElementTagNameMap];
declare global { declare global {
type ddeComponentAttributes= Record<any, any> | undefined; type ddeComponentAttributes= Record<any, any> | undefined;
type ddeElementAddon<El extends SupportedElement | DocumentFragment>= (element: El)=> El | void; type ddeElementAddon<El extends SupportedElement | DocumentFragment>= (element: El)=> El | void;
@ -48,7 +43,7 @@ export function classListDeclarative<El extends SupportedElement>(element: El, c
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;
type textContent= string | ( (set?: string)=> string ); // TODO: for some reason `Observable<string, any>` leads to `attrs?: any` 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,
@ -187,9 +182,10 @@ export function customElementWithDDE<EL extends HTMLElement>(custom_element: EL)
export function lifecycleToEvents<EL extends HTMLElement>(custom_element: EL): EL export function lifecycleToEvents<EL extends HTMLElement>(custom_element: EL): EL
export function observedAttributes(custom_element: HTMLElement): Record<string, string> export function observedAttributes(custom_element: HTMLElement): Record<string, string>
/* TypeScript MEH // TODO for SVG */ /* TypeScript MEH */
type ddeAppend<el>= (...nodes: (Node | string)[])=> el;
declare global{ declare global{
type ddeAppend<el>= (...nodes: (Node | string)[])=> el;
interface ddeDocumentFragment extends DocumentFragment{ append: ddeAppend<ddeDocumentFragment>; } interface ddeDocumentFragment extends DocumentFragment{ append: ddeAppend<ddeDocumentFragment>; }
interface ddeHTMLElement extends HTMLElement{ append: ddeAppend<ddeHTMLElement>; } interface ddeHTMLElement extends HTMLElement{ append: ddeAppend<ddeHTMLElement>; }
interface ddeSVGElement extends SVGElement{ append: ddeAppend<ddeSVGElement>; } interface ddeSVGElement extends SVGElement{ append: ddeAppend<ddeSVGElement>; }

View File

@ -1,6 +1,6 @@
{ {
"name": "deka-dom-el", "name": "deka-dom-el",
"version": "0.7.7", "version": "0.7.8",
"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",
@ -59,18 +59,24 @@
"size-limit": [ "size-limit": [
{ {
"path": "./index.js", "path": "./index.js",
"limit": "10 kB", "limit": "10.5 kB",
"gzip": false, "gzip": false,
"brotli": false "brotli": false
}, },
{ {
"path": "./observables.js", "path": "./observables.js",
"limit": "11.5 kB", "limit": "12 kB",
"gzip": false, "gzip": false,
"brotli": false "brotli": false
}, },
{
"path": "./index-with-observables.js",
"limit": "15 kB",
"gzip": false,
"brotli": false
},
{ {
"path": "./index-with-observables.js", "path": "./index-with-observables.js",
"limit": "5 kB" "limit": "5 kB"

View File

@ -1,3 +1,4 @@
import { keyDM, keyLTE } from "./dom-common.js";
import { scope } from "./dom.js"; import { scope } from "./dom.js";
export function customElementRender(custom_element, render, props= observedAttributes){ export function customElementRender(custom_element, render, props= observedAttributes){
scope.push({ scope.push({
@ -11,20 +12,27 @@ export function customElementRender(custom_element, render, props= observedAttri
return out; return out;
} }
export function lifecycleToEvents(class_declaration){ export function lifecycleToEvents(class_declaration){
for (const name of [ "connected", "disconnected" ]) wrapMethod(class_declaration.prototype, "connectedCallback", function(target, thisArg, detail){
wrapMethod(class_declaration.prototype, name+"Callback", function(target, thisArg, detail){
target.apply(thisArg, detail); target.apply(thisArg, detail);
thisArg.dispatchEvent(new Event("dde:"+name)); thisArg.dispatchEvent(new Event("dde:connected"));
}); });
const name= "attributeChanged"; if(!class_declaration.prototype[keyDM])
wrapMethod(class_declaration.prototype, name+"Callback", function(target, thisArg, detail){ class_declaration.prototype[keyDM]= "dde";
wrapMethod(class_declaration.prototype, "disconnectedCallback", function(target, thisArg, detail){
target.apply(thisArg, detail);
const dispatch= ()=> thisArg.dispatchEvent(new Event("dde:disconnected"));
if(thisArg[keyDM]!=="dde")
return dispatch();
(queueMicrotask || setTimeout)(()=> !thisArg.isConnected && dispatch());
});
wrapMethod(class_declaration.prototype, "attributeChangedCallback", function(target, thisArg, detail){
const [ attribute, , value ]= detail; const [ attribute, , value ]= detail;
thisArg.dispatchEvent(new CustomEvent("dde:"+name, { thisArg.dispatchEvent(new CustomEvent("dde:attributeChanged", {
detail: [ attribute, value ] detail: [ attribute, value ]
})); }));
target.apply(thisArg, detail); target.apply(thisArg, detail);
}); });
class_declaration.prototype.__dde_lifecycleToEvents= true; class_declaration.prototype[keyLTE]= true;
return class_declaration; return class_declaration;
} }
export { lifecycleToEvents as customElementWithDDE }; export { lifecycleToEvents as customElementWithDDE };

View File

@ -26,3 +26,5 @@ function setDeleteAttr(obj, prop, val){
if(Reflect.get(obj, prop)==="undefined") if(Reflect.get(obj, prop)==="undefined")
return Reflect.set(obj, prop, ""); return Reflect.set(obj, prop, "");
} }
export const keyLTE= "__dde_lifecycleToEvents"; //boolean
export const keyDM= "__dde_disconnect_mode"; //native (unset) | dde | skip

View File

@ -1,5 +1,5 @@
export { registerReactivity } from './observables-common.js'; export { registerReactivity } from './observables-common.js';
import { enviroment as env } from './dom-common.js'; import { enviroment as env, keyDM, keyLTE } from './dom-common.js';
export function dispatchEvent(name, options, host){ export function dispatchEvent(name, options, host){
if(!options) options= {}; if(!options) options= {};
@ -26,17 +26,19 @@ const els_attribute_store= new WeakSet();
import { scope } from "./dom.js"; import { scope } from "./dom.js";
import { onAbort } from './helpers.js'; import { onAbort } from './helpers.js';
//TODO: cleanUp when event before abort? //TODO: cleanUp when event before abort?
//TODO: docs (e.g.) https://nolanlawson.com/2024/01/13/web-component-gotcha-constructor-vs-connectedcallback/
on.connected= function(listener, options){ on.connected= function(listener, options){
const { custom_element }= scope.current; const { custom_element }= scope.current;
const name= "connected"; const name= "connected";
if(typeof options !== "object") if(typeof options !== "object")
options= {}; options= {};
if(typeof options.once !== "boolean")
options.once= true; options.once= true;
return function registerElement(element){ return function registerElement(element){
if(custom_element) element= custom_element; if(custom_element) element= custom_element;
const event= "dde:"+name; const event= "dde:"+name;
element.addEventListener(event, listener, options); element.addEventListener(event, listener, options);
if(element.__dde_lifecycleToEvents) return element; if(element[keyLTE]) return element;
if(element.isConnected) return ( element.dispatchEvent(new Event(event)), element ); if(element.isConnected) return ( element.dispatchEvent(new Event(event)), element );
const c= onAbort(options.signal, ()=> c_ch_o.offConnected(element, listener)); const c= onAbort(options.signal, ()=> c_ch_o.offConnected(element, listener));
@ -49,12 +51,14 @@ on.disconnected= function(listener, options){
const name= "disconnected"; const name= "disconnected";
if(typeof options !== "object") if(typeof options !== "object")
options= {}; options= {};
if(typeof options.once !== "boolean")
options.once= true; options.once= true;
return function registerElement(element){ return function registerElement(element){
if(custom_element) element= custom_element; if(custom_element) element= custom_element;
if(!element[keyDM]) element[keyDM]= "dde";
const event= "dde:"+name; const event= "dde:"+name;
element.addEventListener(event, listener, options); element.addEventListener(event, listener, options);
if(element.__dde_lifecycleToEvents) return element; if(element[keyLTE]) return element;
const c= onAbort(options.signal, ()=> c_ch_o.offDisconnected(element, listener)); const c= onAbort(options.signal, ()=> c_ch_o.offDisconnected(element, listener));
if(c) c_ch_o.onDisconnected(element, listener); if(c) c_ch_o.onDisconnected(element, listener);
@ -77,7 +81,7 @@ on.attributeChanged= function(listener, options){
return function registerElement(element){ return function registerElement(element){
const event= "dde:"+name; const event= "dde:"+name;
element.addEventListener(event, listener, options); element.addEventListener(event, listener, options);
if(element.__dde_lifecycleToEvents || els_attribute_store.has(element)) if(element[keyLTE] || els_attribute_store.has(element))
return element; return element;
if(!env.M) return element; if(!env.M) return element;
@ -171,13 +175,13 @@ function connectionsChangesObserverConstructor(){
function requestIdle(){ return new Promise(function(resolve){ function requestIdle(){ return new Promise(function(resolve){
(requestIdleCallback || requestAnimationFrame)(resolve); (requestIdleCallback || requestAnimationFrame)(resolve);
}); } }); }
async function collectChildren(element, filter){ async function collectChildren(element){
if(store.size > 30)//TODO limit? if(store.size > 30)//TODO limit?
await requestIdle(); await requestIdle();
const out= []; const out= [];
if(!(element instanceof Node)) return out; if(!(element instanceof Node)) return out;
for(const el of store.keys()){ for(const el of store.keys()){
if(el===element || !(el instanceof Node) || filter(el)) continue; if(el===element || !(el instanceof Node)) continue;
if(element.contains(el)) if(element.contains(el))
out.push(el); out.push(el);
} }
@ -186,7 +190,7 @@ function connectionsChangesObserverConstructor(){
function observerAdded(addedNodes, is_root){ function observerAdded(addedNodes, is_root){
let out= false; let out= false;
for(const element of addedNodes){ for(const element of addedNodes){
if(is_root) collectChildren(element, el=> !el.isConnectedd).then(observerAdded); if(is_root) collectChildren(element).then(observerAdded);
if(!store.has(element)) continue; if(!store.has(element)) continue;
const ls= store.get(element); const ls= store.get(element);
@ -203,17 +207,25 @@ function connectionsChangesObserverConstructor(){
function observerRemoved(removedNodes, is_root){ function observerRemoved(removedNodes, is_root){
let out= false; let out= false;
for(const element of removedNodes){ for(const element of removedNodes){
if(is_root) collectChildren(element, el=> el.isConnectedd).then(observerRemoved); if(is_root) collectChildren(element).then(observerRemoved);
if(!store.has(element)) continue; if(!store.has(element)) continue;
const ls= store.get(element); const ls= store.get(element);
if(!ls.length_d) continue; if(!ls.length_d) continue;
const dispatch= dispatchRemove(element);
element.dispatchEvent(new Event("dde:disconnected")); if(element[keyDM]==="dde")
(queueMicrotask || setTimeout)(dispatch);
store.delete(element); else
dispatch();
out= true; out= true;
} }
return out; return out;
} }
function dispatchRemove(element){
return ()=> {
if(element.isConnected) return;
element.dispatchEvent(new Event("dde:disconnected"));
store.delete(element);
};
}
} }

View File

@ -161,7 +161,11 @@ export const observables_config= {
isObservable, isObservable,
processReactiveAttribute(element, key, attrs, set){ processReactiveAttribute(element, key, attrs, set){
if(!isObservable(attrs)) return attrs; if(!isObservable(attrs)) return attrs;
const l= attr=> set(key, attr); const l= attr=> {
if(!element.isConnected)
return removeObservableListener(attrs, l);
set(key, attr);
};
addObservableListener(attrs, l); addObservableListener(attrs, l);
removeObservablesFromElements(attrs, l, element, key); removeObservablesFromElements(attrs, l, element, key);
return attrs(); return attrs();