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

Compare commits

..

No commits in common. "51ccd11f82a104774c8eef44dddcadd75b91406c" and "14427882ed0742669dd37cb27449867f1a953f41" have entirely different histories.

13 changed files with 117 additions and 157 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;
observedAttributes(custom_element: HTMLElement): Record<string, Observable<any, any>>;
attribute(name: string, initial?: string): Observable<string, {}>;
}
export const observable: observable;
export const O: observable;
@ -64,11 +64,16 @@ declare global {
type ddeActions<V>= Actions<V>
}
type CustomElementTagNameMap= { '#text': Text, '#comment': Comment }
declare global {
interface ddePublicElementTagNameMap{
}
}
type SupportedElement=
HTMLElementTagNameMap[keyof HTMLElementTagNameMap]
| SVGElementTagNameMap[keyof SVGElementTagNameMap]
| MathMLElementTagNameMap[keyof MathMLElementTagNameMap]
| CustomElementTagNameMap[keyof CustomElementTagNameMap]
| ddePublicElementTagNameMap[keyof ddePublicElementTagNameMap];
declare global {
type ddeComponentAttributes= Record<any, any> | undefined;
type ddeElementAddon<El extends SupportedElement | DocumentFragment>= (element: El)=> El | void;
@ -107,7 +112,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 assignAttribute<El extends SupportedElement, ATT extends keyof ElementAttributes<El>>(element: El, attr: ATT, value: ElementAttributes<El>[ATT]): ElementAttributes<El>[ATT]
type ExtendedHTMLElementTagNameMap= HTMLElementTagNameMap & CustomElementTagNameMap;
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<
TAG extends keyof ExtendedHTMLElementTagNameMap & string,
@ -234,22 +239,9 @@ export const scope: {
pop(): ReturnType<Array<Scope>["pop"]>,
};
export function customElementRender<
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 */
/* TypeScript MEH // TODO for SVG */
type ddeAppend<el>= (...nodes: (Node | string)[])=> el;
declare global{
type ddeAppend<el>= (...nodes: (Node | string)[])=> el;
interface ddeDocumentFragment extends DocumentFragment{ append: ddeAppend<ddeDocumentFragment>; }
interface ddeHTMLElement extends HTMLElement{ append: ddeAppend<ddeHTMLElement>; }
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;
observedAttributes(custom_element: HTMLElement): Record<string, Observable<any, any>>;
attribute(name: string, initial?: string): Observable<string, {}>;
}
export const observable: observable;
export const O: observable;
@ -64,11 +64,16 @@ declare global {
type ddeActions<V>= Actions<V>
}
type CustomElementTagNameMap= { '#text': Text, '#comment': Comment }
declare global {
interface ddePublicElementTagNameMap{
}
}
type SupportedElement=
HTMLElementTagNameMap[keyof HTMLElementTagNameMap]
| SVGElementTagNameMap[keyof SVGElementTagNameMap]
| MathMLElementTagNameMap[keyof MathMLElementTagNameMap]
| CustomElementTagNameMap[keyof CustomElementTagNameMap]
| ddePublicElementTagNameMap[keyof ddePublicElementTagNameMap];
declare global {
type ddeComponentAttributes= Record<any, any> | undefined;
type ddeElementAddon<El extends SupportedElement | DocumentFragment>= (element: El)=> El | void;
@ -107,7 +112,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 assignAttribute<El extends SupportedElement, ATT extends keyof ElementAttributes<El>>(element: El, attr: ATT, value: ElementAttributes<El>[ATT]): ElementAttributes<El>[ATT]
type ExtendedHTMLElementTagNameMap= HTMLElementTagNameMap & CustomElementTagNameMap;
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<
TAG extends keyof ExtendedHTMLElementTagNameMap & string,
@ -234,22 +239,9 @@ export const scope: {
pop(): ReturnType<Array<Scope>["pop"]>,
};
export function customElementRender<
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 */
/* TypeScript MEH // TODO for SVG */
type ddeAppend<el>= (...nodes: (Node | string)[])=> el;
declare global{
type ddeAppend<el>= (...nodes: (Node | string)[])=> el;
interface ddeDocumentFragment extends DocumentFragment{ append: ddeAppend<ddeDocumentFragment>; }
interface ddeHTMLElement extends HTMLElement{ append: ddeAppend<ddeHTMLElement>; }
interface ddeSVGElement extends SVGElement{ append: ddeAppend<ddeSVGElement>; }

2
dist/esm.js vendored

File diff suppressed because one or more lines are too long

View File

@ -81,11 +81,24 @@ function todoComponent({ textContent, value }){
textContent(ev.target.value);
is_editable(false);
});
const memo= initMemo(host);
return el("li").append(
O.el(is_editable, is=> is
? el("input", { value: textContent(), type: "text" }, onedited)
: el("span", { textContent, onclick: is_editable.bind(null, true) })
? memo("view", ()=> el("input", { value: textContent(), type: "text" }, onedited))
: memo("edit", ()=> el("span", { textContent, onclick: is_editable.bind(null, true) }))
),
el("button", { type: "button", value, textContent: "-" }, onclick)
);
}
function initMemo(host){
const memos= new Map();
host(on.disconnected(()=> memos.clear()));
return function useMemo(key, fn){
let memo= memos.get(key);
if(!memo){
memo= fn();
memos.set(key, memo);
}
return memo;
}
}

12
index.d.ts vendored
View File

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

View File

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

View File

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

View File

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

View File

@ -96,7 +96,7 @@ observable.el= function(o, map){
out.append(mark_start, mark_end);
const { current }= scope;
const reRenderReactiveElement= v=> {
if(!mark_start.parentNode || !mark_end.parentNode) // isConnected or wasnt yet rendered
if(!mark_start.parentNode || !mark_end.parentNode)
return removeObservableListener(o, reRenderReactiveElement);
scope.push(current);
let els= map(v);
@ -107,21 +107,12 @@ observable.el= function(o, map){
while(( el_r= mark_start.nextSibling ) !== mark_end)
el_r.remove();
mark_start.after(...els);
if(mark_start.isConnected)
requestCleanUpReactives(current.host());
};
addObservableListener(o, reRenderReactiveElement);
removeObservablesFromElements(o, reRenderReactiveElement, mark_start, map);
reRenderReactiveElement(o());
return out;
};
function requestCleanUpReactives(host){
if(!host || !host[key_reactive]) return;
(requestIdleCallback || setTimeout)(function(){
host[key_reactive]= host[key_reactive]
.filter(([ o, el ])=> el.isConnected ? true : (removeObservableListener(...o), false));
});
}
import { on } from "./events.js";
import { observedAttributes } from "./helpers.js";
const observedAttributeActions= {
@ -161,11 +152,7 @@ export const observables_config= {
isObservable,
processReactiveAttribute(element, key, attrs, set){
if(!isObservable(attrs)) return attrs;
const l= attr=> {
if(!element.isConnected)
return removeObservableListener(attrs, l);
set(key, attr);
};
const l= attr=> set(key, attr);
addObservableListener(attrs, l);
removeObservablesFromElements(attrs, l, element, key);
return attrs();