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

Compare commits

..

No commits in common. "971b5959275f7ccba9fd1790d61088f31428b4c6" and "a5d43e692568d2dddd7baae09f42cf6c9c82eacd" have entirely different histories.

17 changed files with 93 additions and 105 deletions

File diff suppressed because one or more lines are too long

32
dist/dde.js vendored

File diff suppressed because one or more lines are too long

View File

@ -245,7 +245,7 @@ export function customElementRender<
props?: P | ((...args: any[])=> P)
): EL
export function customElementWithDDE<EL extends HTMLElement>(custom_element: EL): EL
export function lifecyclesToEvents<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 */

File diff suppressed because one or more lines are too long

2
dist/esm.d.ts vendored
View File

@ -245,7 +245,7 @@ export function customElementRender<
props?: P | ((...args: any[])=> P)
): EL
export function customElementWithDDE<EL extends HTMLElement>(custom_element: EL): EL
export function lifecyclesToEvents<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 */

2
dist/esm.js vendored

File diff suppressed because one or more lines are too long

View File

@ -9,7 +9,7 @@ import { O } from "deka-dom-el/observables";
O.observedAttributes;
// “internal” utils
import { lifecyclesToEvents } from "deka-dom-el";
import { lifecycleToEvents } from "deka-dom-el";
</code></div><h3 id="h-custom-elements-introduction"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-custom-elements-introduction" tabindex="-1">#</a> Custom Elements Introduction</h3><p><a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements" title="Article about custom elements on MDN">Using custom elements</a></p><div class="code" data-js="todo"><!--<dde:mark type="component" name="code" host="parentElement" ssr/>--><code class="language-js">class CustomHTMLElement extends HTMLElement{
static tagName = "custom-element"; // just suggestion, we can use `el(CustomHTMLElement.tagName)`
static observedAttributes= [ "custom-attribute" ];
@ -31,4 +31,4 @@ import { lifecyclesToEvents } from "deka-dom-el";
set customAttribute(value){ this.setAttribute("custom-attribute", value); }
}
customElements.define(CustomHTMLElement.tagName, CustomHTMLElement);
</code></div><p><a href="https://gist.github.com/WebReflection/ec9f6687842aa385477c4afca625bbf4" title="Ideas and tips from WebReflection">Handy Custom Elements' Patterns</a></p><div class="notice"><!--<dde:mark type="component" name="mnemonic" host="parentElement" ssr/>--><h3 id="h-mnemonic"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-mnemonic" tabindex="-1">#</a> Mnemonic</h3><ul><li><code>customElementRender(&lt;custom-element&gt;, &lt;render-function&gt;[, &lt;properties&gt;])</code> — use function to render DOM structure for given &lt;custom-element&gt;</li><li><code>customElementWithDDE(&lt;custom-element&gt;)</code> — register &lt;custom-element&gt; to DDE library, see also `lifecycleToEvents`, can be also used as decorator</li><li><code>observedAttributes(&lt;custom-element&gt;)</code> — returns record of observed attributes (keys uses camelCase)</li><li><code>O.observedAttributes(&lt;custom-element&gt;)</code> — returns record of observed attributes (keys uses camelCase and values are observables)</li><li><code>lifecycleToEvents(&lt;class-declaration&gt;)</code> — convert lifecycle methods to events, can be also used as decorator</li></ul></div><div class="prevNext"><!--<dde:mark type="component" name="prevNext" host="parentElement" ssr/>--><a rel="prev" href="p05-scopes" title="Organizing UI into components"><!--<dde:mark type="component" name="pageLink" host="parentElement" ssr/>-->Scopes and components (previous)</a><!--<dde:mark type="component" name="pageLink" host="this" ssr/>--></div></main></body></html>
</code></div><p><a href="https://gist.github.com/WebReflection/ec9f6687842aa385477c4afca625bbf4" title="Ideas and tips from WebReflection">Handy Custom Elements' Patterns</a></p><div class="notice"><!--<dde:mark type="component" name="mnemonic" host="parentElement" ssr/>--><h3 id="h-mnemonic"><!--<dde:mark type="component" name="h3" host="parentElement" ssr/>--><a href="#h-mnemonic" tabindex="-1">#</a> Mnemonic</h3><ul><li><code>customElementRender(&lt;custom-element&gt;, &lt;render-function&gt;[, &lt;properties&gt;])</code> — use function to render DOM structure for given &lt;custom-element&gt;</li><li><code>customElementWithDDE(&lt;custom-element&gt;)</code> — register &lt;custom-element&gt; to DDE library, see also `lifecycleToEvents`, can be also used as decorator</li><li><code>observedAttributes(&lt;custom-element&gt;)</code> — returns record of observed attributes (keys uses camelCase)</li><li><code>O.observedAttributes(&lt;custom-element&gt;)</code> — returns record of observed attributes (keys uses camelCase and values are observables)</li><li><code>lifecycleToEvents(&lt;class-declaration&gt;)</code> — convert lifecycle methods to events, can be also used as decorator</li></ul></div><div class="prevNext"><!--<dde:mark type="component" name="prevNext" host="parentElement" ssr/>--><a rel="prev" href="p05-scopes" title="Organizing UI into components"><!--<dde:mark type="component" name="pageLink" host="parentElement" ssr/>-->Scopes and components (previous)</a><!--<dde:mark type="component" name="pageLink" host="this" ssr/>--></div></main></body></html>

View File

@ -9,4 +9,4 @@ import { O } from "deka-dom-el/observables";
O.observedAttributes;
// “internal” utils
import { lifecyclesToEvents } from "deka-dom-el";
import { lifecycleToEvents } from "deka-dom-el";

View File

@ -7,7 +7,7 @@ export function mnemonic(){
el("code", "customElementRender(<custom-element>, <render-function>[, <properties>])"), " — use function to render DOM structure for given <custom-element>",
),
el("li").append(
el("code", "customElementWithDDE(<custom-element>)"), " — register <custom-element> to DDE library, see also `lifecyclesToEvents`, can be also used as decorator",
el("code", "customElementWithDDE(<custom-element>)"), " — register <custom-element> to DDE library, see also `lifecycleToEvents`, can be also used as decorator",
),
el("li").append(
el("code", "observedAttributes(<custom-element>)"), " — returns record of observed attributes (keys uses camelCase)",
@ -16,7 +16,7 @@ export function mnemonic(){
el("code", "O.observedAttributes(<custom-element>)"), " — returns record of observed attributes (keys uses camelCase and values are observables)",
),
el("li").append(
el("code", "lifecyclesToEvents(<class-declaration>)"), " — convert lifecycle methods to events, can be also used as decorator",
el("code", "lifecycleToEvents(<class-declaration>)"), " — convert lifecycle methods to events, can be also used as decorator",
)
);
}

View File

@ -16,12 +16,10 @@ const className= style.host(todosComponent).css`
`;
/** @param {{ todos: string[] }} */
export function todosComponent({ todos= [ "Task A" ] }= {}){
let key= 0;
const todosO= O(new Map(), {
add(v){ this.value.set(key++, O(v)); },
remove(key){ O.clear(this.value.get(key)); this.value.delete(key); }
const todosO= O(todos.map(t=> O(t)), {
add(v){ this.value.push(O(v)); },
remove(i){ O.clear(this.value.splice(i, 1)[0]); }
});
todos.forEach(text=> O.action(todosO, "add", text));
const name= "todoName";
const onsubmitAdd= on("submit", event=> {
@ -33,16 +31,24 @@ export function todosComponent({ todos= [ "Task A" ] }= {}){
const onremove= on("remove", event=>
O.action(todosO, "remove", event.detail));
const ul_todos_version= 1; // ul_todos_v1/ul_todos_v2
const ul_todos_v1= el("ul").append(
O.el(todosO, ts=> ts
.map((textContent, value)=>
el(todoComponent, { textContent, value, className }, onremove))
));
const ul_todos_v2= ts=> el("ul").append(
...ts.map((textContent, value)=>
el(todoComponent, { textContent, value, className }, onremove))
);
return el("div", { className }).append(
el("div").append(
el("h2", "Todos:"),
el("h3", "List of todos:"),
O.el(todosO, (ts, memo)=> !ts.size
O.el(todosO, ts=> !ts.length
? el("p", "No todos yet")
: el("ul").append(
...Array.from(ts).map(([ value, textContent ])=>
memo(value, ()=> el(todoComponent, { textContent, value, className }, onremove)))
)
: ( !(ul_todos_version-1) ? ul_todos_v1 : ul_todos_v2(ts) )
),
el("p", "Click to the text to edit it.")
),
@ -55,7 +61,7 @@ export function todosComponent({ todos= [ "Task A" ] }= {}){
),
el("div").append(
el("h3", "Output (JSON):"),
el("output", O(()=> JSON.stringify(Array.from(todosO()), null, "\t")))
el("output", O(()=> JSON.stringify(todosO, null, "\t")))
)
)
}

2
index.d.ts vendored
View File

@ -180,7 +180,7 @@ export function customElementRender<
props?: P | ((...args: any[])=> P)
): EL
export function customElementWithDDE<EL extends HTMLElement>(custom_element: EL): EL
export function lifecyclesToEvents<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 */

View File

@ -79,7 +79,7 @@
},
{
"path": "./index-with-observables.js",
"limit": "5.25 kB"
"limit": "5 kB"
}
],
"modifyEsbuildConfig": {

View File

@ -8,7 +8,7 @@ export function customElementRender(custom_element, target, render, props= obser
});
if(typeof props==="function") props= props.call(custom_element, custom_element);
const is_lte= custom_element[keyLTE];
if(!is_lte) lifecyclesToEvents(custom_element);
if(!is_lte) lifecycleToEvents(custom_element);
const out= render.call(custom_element, props);
if(!is_lte) custom_element.dispatchEvent(new Event(evc));
if(target.nodeType===11 && typeof target.mode==="string") // is ShadowRoot
@ -16,7 +16,7 @@ export function customElementRender(custom_element, target, render, props= obser
scope.pop();
return target.append(out);
}
export function lifecyclesToEvents(class_declaration){
export function lifecycleToEvents(class_declaration){
wrapMethod(class_declaration.prototype, "connectedCallback", function(target, thisArg, detail){
target.apply(thisArg, detail);
thisArg.dispatchEvent(new Event(evc));
@ -37,7 +37,7 @@ export function lifecyclesToEvents(class_declaration){
class_declaration.prototype[keyLTE]= true;
return class_declaration;
}
export { lifecyclesToEvents as customElementWithDDE };
export { lifecycleToEvents as customElementWithDDE };
function wrapMethod(obj, method, apply){
obj[method]= new Proxy(obj[method] || (()=> {}), { apply });
}

View File

@ -26,7 +26,7 @@ function setDeleteAttr(obj, prop, val){
if(Reflect.get(obj, prop)==="undefined")
return Reflect.set(obj, prop, "");
}
export const keyLTE= "__dde_lifecyclesToEvents"; //boolean
export const keyLTE= "__dde_lifecycleToEvents"; //boolean
export const evc= "dde:connected";
export const evd= "dde:disconnected";
export const eva= "dde:attributeChanged";

View File

@ -65,13 +65,12 @@ export function createElement(tag, attributes, ...addons){
scoped= 2;
return el;
}
import { hasOwn } from "./helpers.js";
/** @param {HTMLElement} element @param {HTMLElement} [root] */
export function simulateSlots(element, root= element, mapper= undefined){
const _default= Symbol.for("default");
const slots= Array.from(root.querySelectorAll("slot"))
.reduce((out, curr)=> Reflect.set(out, curr.name || _default, curr) && out, {});
const has_d= hasOwn(slots, _default);
const has_d= Reflect.has(slots, _default);
element.append= new Proxy(element.append, {
apply(orig, _, els){
if(!els.length) return element;
@ -199,7 +198,7 @@ export function elementAttribute(element, op, key, value){
import { isUndef } from "./helpers.js";
//TODO add cache? `(Map/Set)<el.tagName+key,isUndef>`
function isPropSetter(el, key){
if(!(key in el)) return false;
if(!Reflect.has(el, key)) return false;
const des= getPropDescriptor(el, key);
return !isUndef(des.set);
}

View File

@ -1,4 +1,3 @@
export const hasOwn= (...a)=> Object.prototype.hasOwnProperty.call(...a);
export function isUndef(value){ return typeof value==="undefined"; }
export function typeOf(v){
const t= typeof v;
@ -20,7 +19,7 @@ export function observedAttributes(instance, observedAttribute){
const { observedAttributes= [] }= instance.constructor;
return observedAttributes
.reduce(function(out, name){
out[kebabToCamel(name)]= observedAttribute(instance, name);
Reflect.set(out, kebabToCamel(name), observedAttribute(instance, name));
return out;
}, {});
}

View File

@ -1,8 +1,7 @@
export const mark= "__dde_observable";
import { hasOwn } from "./helpers.js";
export function isObservable(candidate){
try{ return hasOwn(candidate, mark); }
try{ return Reflect.has(candidate, mark); }
catch(e){ return false; }
}
/** @type {function[]} */
@ -46,10 +45,10 @@ export function observable(value, actions){
export { observable as O };
observable.action= function(o, name, ...a){
const s= o[mark], { actions }= s;
if(!actions || !(name in actions))
if(!actions || !Reflect.has(actions, name))
throw new Error(`'${o}' has no action with name '${name}'!`);
actions[name].apply(s, a);
if(s.skip) return (delete s.skip);
if(s.skip) return Reflect.deleteProperty(s, "skip");
s.listeners.forEach(l=> l(s.value));
};
observable.on= function on(o, listener, options= {}){
@ -66,12 +65,11 @@ observable.symbols= {
};
observable.clear= function(...observables){
for(const o of observables){
Reflect.deleteProperty(o, "toJSON");
const s= o[mark];
if(!s) continue;
delete o.toJSON;
s.onclear.forEach(f=> f.call(s));
clearListDeps(o, s);
delete o[mark];
Reflect.deleteProperty(o, mark);
}
function clearListDeps(o, s){
s.listeners.forEach(l=> {
@ -91,38 +89,24 @@ const key_reactive= "__dde_reactive";
import { enviroment as env } from "./dom-common.js";
import { el } from "./dom.js";
import { scope } from "./dom.js";
// TODO: third argument for handle `cache_tmp` in re-render
observable.el= function(o, map){
const mark_start= el.mark({ type: "reactive" }, true);
const mark_end= mark_start.end;
const out= env.D.createDocumentFragment();
out.append(mark_start, mark_end);
const { current }= scope;
let cache= {};
const reRenderReactiveElement= v=> {
if(!mark_start.parentNode || !mark_end.parentNode) // === `isConnected` or wasnt yet rendered
if(!mark_start.parentNode || !mark_end.parentNode) // isConnected or wasnt yet rendered
return removeObservableListener(o, reRenderReactiveElement);
const cache_tmp= cache; // will be reused in the useCache or removed in the while loop on the end
cache= {};
scope.push(current);
let els= map(v, function useCache(key, fun){
let value;
if(hasOwn(cache_tmp, key)){
value= cache_tmp[key];
delete cache_tmp[key];
} else
value= fun();
cache[key]= value;
return value;
});
let els= map(v);
scope.pop();
if(!Array.isArray(els))
els= [ els ];
mark_start.after(...els);
const el_last_keep= els[els.length-1];
let el_r;
while(( el_r= el_last_keep.nextSibling ) !== mark_end)
let el_r= mark_start;
while(( el_r= mark_start.nextSibling ) !== mark_end)
el_r.remove();
mark_start.after(...els);
if(mark_start.isConnected)
requestCleanUpReactives(current.host());
};
@ -199,7 +183,7 @@ function removeObservablesFromElements(o, listener, ...notes){
* You can investigate the `__dde_reactive` key of the element.
* */
element[key_reactive].forEach(([ [ o, listener ] ])=>
removeObservableListener(o, listener, o[mark] && o[mark].host && o[mark].host() === element))
removeObservableListener(o, listener, o[mark]?.host() === element))
)(element);
}
element[key_reactive].push([ [ o, listener ], ...notes ]);
@ -232,7 +216,7 @@ function toObservable(o, value, actions, readonly= false){
const { onclear: ocs }= observable.symbols;
if(actions[ocs]){
onclear.push(actions[ocs]);
delete actions[ocs];
Reflect.deleteProperty(actions, ocs);
}
const { host }= scope;
Reflect.defineProperty(o, mark, {
@ -286,4 +270,4 @@ function removeObservableListener(o, listener, clear_when_empty){
deps.get(c).forEach(sig=> removeObservableListener(sig, c, true));
}
return out;
}
}