1
0
mirror of https://github.com/jaandrle/deka-dom-el synced 2025-04-01 19:55:53 +02:00

Compare commits

...

8 Commits

Author SHA1 Message Date
64566f17af
🔤 converter - convert also comments 2025-03-14 21:58:40 +01:00
2fcec0551c
🔤 2025-03-14 20:43:30 +01:00
7ed2856298
🐛 signal.set(value, force) 2025-03-14 19:24:17 +01:00
a2b0223c4f
🔤 converter 2025-03-14 19:15:26 +01:00
19b0e4666e
🔤 cdn 2025-03-14 14:54:38 +01:00
3823b66003
🐛 on.* types 2025-03-14 13:54:02 +01:00
ba13055d7d
on.host 2025-03-14 13:49:01 +01:00
36fab5276d
reorganize files 2025-03-14 13:15:31 +01:00
44 changed files with 1716 additions and 735 deletions

View File

@ -31,7 +31,7 @@ function EmojiCounter({ initial }) {
el("p", {
className: "output",
textContent: S(() =>
`Hello World ${emoji.get().repeat(clicks.get())}`),
`Hello World ${emoji.get().repeat(count.get())}`),
}),
// 🎮 Controls - Update state on events
@ -39,12 +39,12 @@ function EmojiCounter({ initial }) {
on("click", () => count.set(count.get() + 1))
),
el("select", null,
el("select", null, on.host(el=> el.value= initial),
on("change", e => emoji.set(e.target.value))
).append(
el(Option, "🎉", isSelected),
el(Option, "🚀", isSelected),
el(Option, "💖", isSelected),
el(Option, "🎉"),
el(Option, "🚀"),
el(Option, "💖"),
)
);
}
@ -93,11 +93,23 @@ into existing projects.
# npm install deka-dom-el
```
#### Direct Script
#### CDN / Direct Script
For CDN links and various build formats (ESM/IIFE, with/without signals, minified/unminified), see the [interactive
format selector](https://jaandrle.github.io/deka-dom-el/) on the documentation site.
```html
<!-- Example with IIFE build (creates a global DDE object) -->
<script src="https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/iife-with-signals.min.js"></script>
<script type="module">
<script>
const { el, S } = DDE;
// Your code here
</script>
<!-- Or with ES modules -->
<script type="module">
import { el, S } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.min.js";
// Your code here
</script>
```

View File

@ -4,7 +4,7 @@ export interface Signal<V, A> {
/** The current value of the signal */
get(): V;
/** Set new value of the signal */
set(value: V): V;
set(value: V, force?: boolean): V;
toJSON(): V;
valueOf(): V;
}
@ -173,17 +173,28 @@ declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options?
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void;
export interface On {
/** Listens to the DOM event. See {@link Document.addEventListener} */
<Event extends keyof DocumentEventMap, EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: Event, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): EE;
<Event extends keyof DocumentEventMap, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE;
/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
connected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<El>) => any, options?: AddEventListenerOptions): EE;
connected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<NoInfer<EL>>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
disconnected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): EE;
/** Listens to the element attribute changes. In case of custom elements uses [`attributeChangedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
attributeChanged<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<[
string,
string
]>) => any, options?: AddEventListenerOptions): EE;
disconnected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
/**
* Fires when the host element is "ready", for host element itsel, it is just an alias for `scope.host(listener)`.
* This is handy to apply some property depending on full template such as:
* ```js
* const selected= "Z";
* //...
* return el("form").append(
* el("select", null, on.host(e=> e.value=selected)).append(
* el("option", { value: "A", textContent: "A" }),
* //...
* el("option", { value: "Z", textContent: "Z" }),
* ),
* );
* ```
* */
host<EL extends SupportedElement>(listener: (element: EL) => any, host?: Host<SupportedElement>): ddeElementAddon<EL>;
}
export const on: On;
export type Scope = {

View File

@ -55,38 +55,7 @@ var Defined = class extends Error {
}
};
// src/signals-lib/common.js
var signals_global = {
/**
* Checks if a value is a signal
* @param {any} attributes - Value to check
* @returns {boolean} Whether the value is a signal
*/
isSignal(attributes) {
return false;
},
/**
* Processes an attribute that might be reactive
* @param {Element} obj - Element that owns the attribute
* @param {string} key - Attribute name
* @param {any} attr - Attribute value
* @param {Function} set - Function to set the attribute
* @returns {any} Processed attribute value
*/
processReactiveAttribute(obj, key, attr, set) {
return attr;
}
};
function registerReactivity(def, global = true) {
if (global) return oAssign(signals_global, def);
Object.setPrototypeOf(def, signals_global);
return def;
}
function signals(_this) {
return isProtoFrom(_this, signals_global) && _this !== signals_global ? _this : signals_global;
}
// src/dom-common.js
// src/dom-lib/common.js
var enviroment = {
setDeleteAttr,
ssr: "",
@ -112,7 +81,7 @@ var evc = "dde:connected";
var evd = "dde:disconnected";
var eva = "dde:attributeChanged";
// src/events-observer.js
// src/dom-lib/events-observer.js
var c_ch_o = enviroment.M ? connectionsChangesObserverConstructor() : new Proxy({}, {
get() {
return () => {
@ -276,7 +245,7 @@ function connectionsChangesObserverConstructor() {
}
}
// src/events.js
// src/dom-lib/events.js
function dispatchEvent(name, options, host) {
if (typeof options === "function") {
host = options;
@ -321,10 +290,7 @@ on.disconnected = function(listener, options) {
};
};
// src/dom.js
function queue(promise) {
return enviroment.q(promise);
}
// src/dom-lib/scopes.js
var scopes = [{
get scope() {
return enviroment.D.body;
@ -399,6 +365,61 @@ var scope = {
return scopes.pop();
}
};
on.host = (fn, host = scope.host) => (el) => host(() => fn(el));
// src/signals-lib/common.js
var signals_global = {
/**
* Checks if a value is a signal
* @param {any} attributes - Value to check
* @returns {boolean} Whether the value is a signal
*/
isSignal(attributes) {
return false;
},
/**
* Processes an attribute that might be reactive
* @param {Element} obj - Element that owns the attribute
* @param {string} key - Attribute name
* @param {any} attr - Attribute value
* @param {Function} set - Function to set the attribute
* @returns {any} Processed attribute value
*/
processReactiveAttribute(obj, key, attr, set) {
return attr;
}
};
function registerReactivity(def, global = true) {
if (global) return oAssign(signals_global, def);
Object.setPrototypeOf(def, signals_global);
return def;
}
function signals(_this) {
return isProtoFrom(_this, signals_global) && _this !== signals_global ? _this : signals_global;
}
// src/dom-lib/helpers.js
function setRemove(obj, prop, key, val) {
return obj[(isUndef(val) ? "remove" : "set") + prop](key, val);
}
function setRemoveNS(obj, prop, key, val, ns = null) {
return obj[(isUndef(val) ? "remove" : "set") + prop + "NS"](ns, key, val);
}
function setDelete(obj, key, val) {
Reflect.set(obj, key, val);
if (!isUndef(val)) return;
return Reflect.deleteProperty(obj, key);
}
function elementAttribute(element, op, key, value) {
if (isInstance(element, enviroment.H))
return element[op + "Attribute"](key, value);
return element[op + "AttributeNS"](null, key, value);
}
// src/dom-lib/el.js
function queue(promise) {
return enviroment.q(promise);
}
function append(...els) {
this.appendOriginal(...els);
return this;
@ -468,38 +489,6 @@ function createElementNS(ns) {
return el;
};
}
function simulateSlots(element, root = element) {
const mark_e = "\xB9\u2070", mark_s = "\u2713";
const slots = Object.fromEntries(
Array.from(root.querySelectorAll("slot")).filter((s) => !s.name.endsWith(mark_e)).map((s) => [s.name += mark_e, s])
);
element.append = new Proxy(element.append, {
apply(orig, _, els) {
if (els[0] === root) return orig.apply(element, els);
for (const el of els) {
const name = (el.slot || "") + mark_e;
try {
elementAttribute(el, "remove", "slot");
} catch (_error) {
}
const slot = slots[name];
if (!slot) return;
if (!slot.name.startsWith(mark_s)) {
slot.childNodes.forEach((c) => c.remove());
slot.name = mark_s + name;
}
slot.append(el);
}
element.append = orig;
return element;
}
});
if (element !== root) {
const els = Array.from(element.childNodes);
element.append(...els);
}
return root;
}
var assign_context = /* @__PURE__ */ new WeakMap();
var { setDeleteAttr: setDeleteAttr2 } = enviroment;
function assign(element, ...attributes) {
@ -562,11 +551,6 @@ function classListDeclarative(element, toggle) {
);
return element;
}
function elementAttribute(element, op, key, value) {
if (isInstance(element, enviroment.H))
return element[op + "Attribute"](key, value);
return element[op + "AttributeNS"](null, key, value);
}
function isPropSetter(el, key) {
if (!(key in el)) return false;
const des = getPropDescriptor(el, key);
@ -590,19 +574,40 @@ function forEachEntries(s, target, element, obj, cb) {
cb(key, val);
});
}
function setRemove(obj, prop, key, val) {
return obj[(isUndef(val) ? "remove" : "set") + prop](key, val);
}
function setRemoveNS(obj, prop, key, val, ns = null) {
return obj[(isUndef(val) ? "remove" : "set") + prop + "NS"](ns, key, val);
}
function setDelete(obj, key, val) {
Reflect.set(obj, key, val);
if (!isUndef(val)) return;
return Reflect.deleteProperty(obj, key);
}
// src/customElement.js
// src/dom-lib/customElement.js
function simulateSlots(element, root = element) {
const mark_e = "\xB9\u2070", mark_s = "\u2713";
const slots = Object.fromEntries(
Array.from(root.querySelectorAll("slot")).filter((s) => !s.name.endsWith(mark_e)).map((s) => [s.name += mark_e, s])
);
element.append = new Proxy(element.append, {
apply(orig, _, els) {
if (els[0] === root) return orig.apply(element, els);
for (const el of els) {
const name = (el.slot || "") + mark_e;
try {
elementAttribute(el, "remove", "slot");
} catch (_error) {
}
const slot = slots[name];
if (!slot) return;
if (!slot.name.startsWith(mark_s)) {
slot.childNodes.forEach((c) => c.remove());
slot.name = mark_s + name;
}
slot.append(el);
}
element.append = orig;
return element;
}
});
if (element !== root) {
const els = Array.from(element.childNodes);
element.append(...els);
}
return root;
}
function customElementRender(target, render, props = {}) {
const custom_element = target.host || target;
scope.push({
@ -1004,7 +1009,6 @@ export {
dispatchEvent,
createElement as el,
createElementNS as elNS,
elementAttribute,
isSignal,
lifecyclesToEvents,
memo,

View File

@ -4,7 +4,7 @@ export interface Signal<V, A> {
/** The current value of the signal */
get(): V;
/** Set new value of the signal */
set(value: V): V;
set(value: V, force?: boolean): V;
toJSON(): V;
valueOf(): V;
}
@ -173,17 +173,28 @@ declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options?
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void;
export interface On {
/** Listens to the DOM event. See {@link Document.addEventListener} */
<Event extends keyof DocumentEventMap, EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: Event, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): EE;
<Event extends keyof DocumentEventMap, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE;
/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
connected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<El>) => any, options?: AddEventListenerOptions): EE;
connected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<NoInfer<EL>>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
disconnected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): EE;
/** Listens to the element attribute changes. In case of custom elements uses [`attributeChangedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
attributeChanged<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<[
string,
string
]>) => any, options?: AddEventListenerOptions): EE;
disconnected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
/**
* Fires when the host element is "ready", for host element itsel, it is just an alias for `scope.host(listener)`.
* This is handy to apply some property depending on full template such as:
* ```js
* const selected= "Z";
* //...
* return el("form").append(
* el("select", null, on.host(e=> e.value=selected)).append(
* el("option", { value: "A", textContent: "A" }),
* //...
* el("option", { value: "Z", textContent: "Z" }),
* ),
* );
* ```
* */
host<EL extends SupportedElement>(listener: (element: EL) => any, host?: Host<SupportedElement>): ddeElementAddon<EL>;
}
export const on: On;
export type Scope = {

File diff suppressed because one or more lines are too long

29
dist/esm.d.ts vendored
View File

@ -4,7 +4,7 @@ export interface Signal<V, A> {
/** The current value of the signal */
get(): V;
/** Set new value of the signal */
set(value: V): V;
set(value: V, force?: boolean): V;
toJSON(): V;
valueOf(): V;
}
@ -172,17 +172,28 @@ declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options?
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void;
export interface On {
/** Listens to the DOM event. See {@link Document.addEventListener} */
<Event extends keyof DocumentEventMap, EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: Event, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): EE;
<Event extends keyof DocumentEventMap, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE;
/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
connected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<El>) => any, options?: AddEventListenerOptions): EE;
connected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<NoInfer<EL>>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
disconnected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): EE;
/** Listens to the element attribute changes. In case of custom elements uses [`attributeChangedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
attributeChanged<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<[
string,
string
]>) => any, options?: AddEventListenerOptions): EE;
disconnected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
/**
* Fires when the host element is "ready", for host element itsel, it is just an alias for `scope.host(listener)`.
* This is handy to apply some property depending on full template such as:
* ```js
* const selected= "Z";
* //...
* return el("form").append(
* el("select", null, on.host(e=> e.value=selected)).append(
* el("option", { value: "A", textContent: "A" }),
* //...
* el("option", { value: "Z", textContent: "Z" }),
* ),
* );
* ```
* */
host<EL extends SupportedElement>(listener: (element: EL) => any, host?: Host<SupportedElement>): ddeElementAddon<EL>;
}
export const on: On;
export type Scope = {

180
dist/esm.js vendored
View File

@ -26,38 +26,7 @@ function onAbort(signal, listener) {
};
}
// src/signals-lib/common.js
var signals_global = {
/**
* Checks if a value is a signal
* @param {any} attributes - Value to check
* @returns {boolean} Whether the value is a signal
*/
isSignal(attributes) {
return false;
},
/**
* Processes an attribute that might be reactive
* @param {Element} obj - Element that owns the attribute
* @param {string} key - Attribute name
* @param {any} attr - Attribute value
* @param {Function} set - Function to set the attribute
* @returns {any} Processed attribute value
*/
processReactiveAttribute(obj, key, attr, set) {
return attr;
}
};
function registerReactivity(def, global = true) {
if (global) return oAssign(signals_global, def);
Object.setPrototypeOf(def, signals_global);
return def;
}
function signals(_this) {
return isProtoFrom(_this, signals_global) && _this !== signals_global ? _this : signals_global;
}
// src/dom-common.js
// src/dom-lib/common.js
var enviroment = {
setDeleteAttr,
ssr: "",
@ -83,7 +52,7 @@ var evc = "dde:connected";
var evd = "dde:disconnected";
var eva = "dde:attributeChanged";
// src/events-observer.js
// src/dom-lib/events-observer.js
var c_ch_o = enviroment.M ? connectionsChangesObserverConstructor() : new Proxy({}, {
get() {
return () => {
@ -247,7 +216,7 @@ function connectionsChangesObserverConstructor() {
}
}
// src/events.js
// src/dom-lib/events.js
function dispatchEvent(name, options, host) {
if (typeof options === "function") {
host = options;
@ -292,10 +261,7 @@ on.disconnected = function(listener, options) {
};
};
// src/dom.js
function queue(promise) {
return enviroment.q(promise);
}
// src/dom-lib/scopes.js
var scopes = [{
get scope() {
return enviroment.D.body;
@ -370,6 +336,61 @@ var scope = {
return scopes.pop();
}
};
on.host = (fn, host = scope.host) => (el) => host(() => fn(el));
// src/signals-lib/common.js
var signals_global = {
/**
* Checks if a value is a signal
* @param {any} attributes - Value to check
* @returns {boolean} Whether the value is a signal
*/
isSignal(attributes) {
return false;
},
/**
* Processes an attribute that might be reactive
* @param {Element} obj - Element that owns the attribute
* @param {string} key - Attribute name
* @param {any} attr - Attribute value
* @param {Function} set - Function to set the attribute
* @returns {any} Processed attribute value
*/
processReactiveAttribute(obj, key, attr, set) {
return attr;
}
};
function registerReactivity(def, global = true) {
if (global) return oAssign(signals_global, def);
Object.setPrototypeOf(def, signals_global);
return def;
}
function signals(_this) {
return isProtoFrom(_this, signals_global) && _this !== signals_global ? _this : signals_global;
}
// src/dom-lib/helpers.js
function setRemove(obj, prop, key, val) {
return obj[(isUndef(val) ? "remove" : "set") + prop](key, val);
}
function setRemoveNS(obj, prop, key, val, ns = null) {
return obj[(isUndef(val) ? "remove" : "set") + prop + "NS"](ns, key, val);
}
function setDelete(obj, key, val) {
Reflect.set(obj, key, val);
if (!isUndef(val)) return;
return Reflect.deleteProperty(obj, key);
}
function elementAttribute(element, op, key, value) {
if (isInstance(element, enviroment.H))
return element[op + "Attribute"](key, value);
return element[op + "AttributeNS"](null, key, value);
}
// src/dom-lib/el.js
function queue(promise) {
return enviroment.q(promise);
}
function append(...els) {
this.appendOriginal(...els);
return this;
@ -439,38 +460,6 @@ function createElementNS(ns) {
return el;
};
}
function simulateSlots(element, root = element) {
const mark_e = "\xB9\u2070", mark_s = "\u2713";
const slots = Object.fromEntries(
Array.from(root.querySelectorAll("slot")).filter((s) => !s.name.endsWith(mark_e)).map((s) => [s.name += mark_e, s])
);
element.append = new Proxy(element.append, {
apply(orig, _, els) {
if (els[0] === root) return orig.apply(element, els);
for (const el of els) {
const name = (el.slot || "") + mark_e;
try {
elementAttribute(el, "remove", "slot");
} catch (_error) {
}
const slot = slots[name];
if (!slot) return;
if (!slot.name.startsWith(mark_s)) {
slot.childNodes.forEach((c) => c.remove());
slot.name = mark_s + name;
}
slot.append(el);
}
element.append = orig;
return element;
}
});
if (element !== root) {
const els = Array.from(element.childNodes);
element.append(...els);
}
return root;
}
var assign_context = /* @__PURE__ */ new WeakMap();
var { setDeleteAttr: setDeleteAttr2 } = enviroment;
function assign(element, ...attributes) {
@ -533,11 +522,6 @@ function classListDeclarative(element, toggle) {
);
return element;
}
function elementAttribute(element, op, key, value) {
if (isInstance(element, enviroment.H))
return element[op + "Attribute"](key, value);
return element[op + "AttributeNS"](null, key, value);
}
function isPropSetter(el, key) {
if (!(key in el)) return false;
const des = getPropDescriptor(el, key);
@ -561,19 +545,40 @@ function forEachEntries(s, target, element, obj, cb) {
cb(key, val);
});
}
function setRemove(obj, prop, key, val) {
return obj[(isUndef(val) ? "remove" : "set") + prop](key, val);
}
function setRemoveNS(obj, prop, key, val, ns = null) {
return obj[(isUndef(val) ? "remove" : "set") + prop + "NS"](ns, key, val);
}
function setDelete(obj, key, val) {
Reflect.set(obj, key, val);
if (!isUndef(val)) return;
return Reflect.deleteProperty(obj, key);
}
// src/customElement.js
// src/dom-lib/customElement.js
function simulateSlots(element, root = element) {
const mark_e = "\xB9\u2070", mark_s = "\u2713";
const slots = Object.fromEntries(
Array.from(root.querySelectorAll("slot")).filter((s) => !s.name.endsWith(mark_e)).map((s) => [s.name += mark_e, s])
);
element.append = new Proxy(element.append, {
apply(orig, _, els) {
if (els[0] === root) return orig.apply(element, els);
for (const el of els) {
const name = (el.slot || "") + mark_e;
try {
elementAttribute(el, "remove", "slot");
} catch (_error) {
}
const slot = slots[name];
if (!slot) return;
if (!slot.name.startsWith(mark_s)) {
slot.childNodes.forEach((c) => c.remove());
slot.name = mark_s + name;
}
slot.append(el);
}
element.append = orig;
return element;
}
});
if (element !== root) {
const els = Array.from(element.childNodes);
element.append(...els);
}
return root;
}
function customElementRender(target, render, props = {}) {
const custom_element = target.host || target;
scope.push({
@ -662,7 +667,6 @@ export {
dispatchEvent,
createElement as el,
createElementNS as elNS,
elementAttribute,
lifecyclesToEvents,
memo,
on,

29
dist/esm.min.d.ts vendored
View File

@ -4,7 +4,7 @@ export interface Signal<V, A> {
/** The current value of the signal */
get(): V;
/** Set new value of the signal */
set(value: V): V;
set(value: V, force?: boolean): V;
toJSON(): V;
valueOf(): V;
}
@ -172,17 +172,28 @@ declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options?
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void;
export interface On {
/** Listens to the DOM event. See {@link Document.addEventListener} */
<Event extends keyof DocumentEventMap, EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: Event, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): EE;
<Event extends keyof DocumentEventMap, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE;
/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
connected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<El>) => any, options?: AddEventListenerOptions): EE;
connected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<NoInfer<EL>>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
disconnected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): EE;
/** Listens to the element attribute changes. In case of custom elements uses [`attributeChangedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
attributeChanged<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<[
string,
string
]>) => any, options?: AddEventListenerOptions): EE;
disconnected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
/**
* Fires when the host element is "ready", for host element itsel, it is just an alias for `scope.host(listener)`.
* This is handy to apply some property depending on full template such as:
* ```js
* const selected= "Z";
* //...
* return el("form").append(
* el("select", null, on.host(e=> e.value=selected)).append(
* el("option", { value: "A", textContent: "A" }),
* //...
* el("option", { value: "Z", textContent: "Z" }),
* ),
* );
* ```
* */
host<EL extends SupportedElement>(listener: (element: EL) => any, host?: Host<SupportedElement>): ddeElementAddon<EL>;
}
export const on: On;
export type Scope = {

2
dist/esm.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -4,7 +4,7 @@ export interface Signal<V, A> {
/** The current value of the signal */
get(): V;
/** Set new value of the signal */
set(value: V): V;
set(value: V, force?: boolean): V;
toJSON(): V;
valueOf(): V;
}
@ -173,17 +173,28 @@ declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options?
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void;
export interface On {
/** Listens to the DOM event. See {@link Document.addEventListener} */
<Event extends keyof DocumentEventMap, EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: Event, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): EE;
<Event extends keyof DocumentEventMap, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE;
/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
connected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<El>) => any, options?: AddEventListenerOptions): EE;
connected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<NoInfer<EL>>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
disconnected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): EE;
/** Listens to the element attribute changes. In case of custom elements uses [`attributeChangedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
attributeChanged<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<[
string,
string
]>) => any, options?: AddEventListenerOptions): EE;
disconnected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
/**
* Fires when the host element is "ready", for host element itsel, it is just an alias for `scope.host(listener)`.
* This is handy to apply some property depending on full template such as:
* ```js
* const selected= "Z";
* //...
* return el("form").append(
* el("select", null, on.host(e=> e.value=selected)).append(
* el("option", { value: "A", textContent: "A" }),
* //...
* el("option", { value: "Z", textContent: "Z" }),
* ),
* );
* ```
* */
host<EL extends SupportedElement>(listener: (element: EL) => any, host?: Host<SupportedElement>): ddeElementAddon<EL>;
}
export const on: On;
export type Scope = {

View File

@ -32,7 +32,6 @@ var DDE = (() => {
dispatchEvent: () => dispatchEvent,
el: () => createElement,
elNS: () => createElementNS,
elementAttribute: () => elementAttribute,
isSignal: () => isSignal,
lifecyclesToEvents: () => lifecyclesToEvents,
memo: () => memo,
@ -101,38 +100,7 @@ var DDE = (() => {
}
};
// src/signals-lib/common.js
var signals_global = {
/**
* Checks if a value is a signal
* @param {any} attributes - Value to check
* @returns {boolean} Whether the value is a signal
*/
isSignal(attributes) {
return false;
},
/**
* Processes an attribute that might be reactive
* @param {Element} obj - Element that owns the attribute
* @param {string} key - Attribute name
* @param {any} attr - Attribute value
* @param {Function} set - Function to set the attribute
* @returns {any} Processed attribute value
*/
processReactiveAttribute(obj, key, attr, set) {
return attr;
}
};
function registerReactivity(def, global = true) {
if (global) return oAssign(signals_global, def);
Object.setPrototypeOf(def, signals_global);
return def;
}
function signals(_this) {
return isProtoFrom(_this, signals_global) && _this !== signals_global ? _this : signals_global;
}
// src/dom-common.js
// src/dom-lib/common.js
var enviroment = {
setDeleteAttr,
ssr: "",
@ -158,7 +126,7 @@ var DDE = (() => {
var evd = "dde:disconnected";
var eva = "dde:attributeChanged";
// src/events-observer.js
// src/dom-lib/events-observer.js
var c_ch_o = enviroment.M ? connectionsChangesObserverConstructor() : new Proxy({}, {
get() {
return () => {
@ -322,7 +290,7 @@ var DDE = (() => {
}
}
// src/events.js
// src/dom-lib/events.js
function dispatchEvent(name, options, host) {
if (typeof options === "function") {
host = options;
@ -367,10 +335,7 @@ var DDE = (() => {
};
};
// src/dom.js
function queue(promise) {
return enviroment.q(promise);
}
// src/dom-lib/scopes.js
var scopes = [{
get scope() {
return enviroment.D.body;
@ -445,6 +410,61 @@ var DDE = (() => {
return scopes.pop();
}
};
on.host = (fn, host = scope.host) => (el) => host(() => fn(el));
// src/signals-lib/common.js
var signals_global = {
/**
* Checks if a value is a signal
* @param {any} attributes - Value to check
* @returns {boolean} Whether the value is a signal
*/
isSignal(attributes) {
return false;
},
/**
* Processes an attribute that might be reactive
* @param {Element} obj - Element that owns the attribute
* @param {string} key - Attribute name
* @param {any} attr - Attribute value
* @param {Function} set - Function to set the attribute
* @returns {any} Processed attribute value
*/
processReactiveAttribute(obj, key, attr, set) {
return attr;
}
};
function registerReactivity(def, global = true) {
if (global) return oAssign(signals_global, def);
Object.setPrototypeOf(def, signals_global);
return def;
}
function signals(_this) {
return isProtoFrom(_this, signals_global) && _this !== signals_global ? _this : signals_global;
}
// src/dom-lib/helpers.js
function setRemove(obj, prop, key, val) {
return obj[(isUndef(val) ? "remove" : "set") + prop](key, val);
}
function setRemoveNS(obj, prop, key, val, ns = null) {
return obj[(isUndef(val) ? "remove" : "set") + prop + "NS"](ns, key, val);
}
function setDelete(obj, key, val) {
Reflect.set(obj, key, val);
if (!isUndef(val)) return;
return Reflect.deleteProperty(obj, key);
}
function elementAttribute(element, op, key, value) {
if (isInstance(element, enviroment.H))
return element[op + "Attribute"](key, value);
return element[op + "AttributeNS"](null, key, value);
}
// src/dom-lib/el.js
function queue(promise) {
return enviroment.q(promise);
}
function append(...els) {
this.appendOriginal(...els);
return this;
@ -514,38 +534,6 @@ var DDE = (() => {
return el;
};
}
function simulateSlots(element, root = element) {
const mark_e = "\xB9\u2070", mark_s = "\u2713";
const slots = Object.fromEntries(
Array.from(root.querySelectorAll("slot")).filter((s) => !s.name.endsWith(mark_e)).map((s) => [s.name += mark_e, s])
);
element.append = new Proxy(element.append, {
apply(orig, _, els) {
if (els[0] === root) return orig.apply(element, els);
for (const el of els) {
const name = (el.slot || "") + mark_e;
try {
elementAttribute(el, "remove", "slot");
} catch (_error) {
}
const slot = slots[name];
if (!slot) return;
if (!slot.name.startsWith(mark_s)) {
slot.childNodes.forEach((c) => c.remove());
slot.name = mark_s + name;
}
slot.append(el);
}
element.append = orig;
return element;
}
});
if (element !== root) {
const els = Array.from(element.childNodes);
element.append(...els);
}
return root;
}
var assign_context = /* @__PURE__ */ new WeakMap();
var { setDeleteAttr: setDeleteAttr2 } = enviroment;
function assign(element, ...attributes) {
@ -608,11 +596,6 @@ var DDE = (() => {
);
return element;
}
function elementAttribute(element, op, key, value) {
if (isInstance(element, enviroment.H))
return element[op + "Attribute"](key, value);
return element[op + "AttributeNS"](null, key, value);
}
function isPropSetter(el, key) {
if (!(key in el)) return false;
const des = getPropDescriptor(el, key);
@ -636,19 +619,40 @@ var DDE = (() => {
cb(key, val);
});
}
function setRemove(obj, prop, key, val) {
return obj[(isUndef(val) ? "remove" : "set") + prop](key, val);
}
function setRemoveNS(obj, prop, key, val, ns = null) {
return obj[(isUndef(val) ? "remove" : "set") + prop + "NS"](ns, key, val);
}
function setDelete(obj, key, val) {
Reflect.set(obj, key, val);
if (!isUndef(val)) return;
return Reflect.deleteProperty(obj, key);
}
// src/customElement.js
// src/dom-lib/customElement.js
function simulateSlots(element, root = element) {
const mark_e = "\xB9\u2070", mark_s = "\u2713";
const slots = Object.fromEntries(
Array.from(root.querySelectorAll("slot")).filter((s) => !s.name.endsWith(mark_e)).map((s) => [s.name += mark_e, s])
);
element.append = new Proxy(element.append, {
apply(orig, _, els) {
if (els[0] === root) return orig.apply(element, els);
for (const el of els) {
const name = (el.slot || "") + mark_e;
try {
elementAttribute(el, "remove", "slot");
} catch (_error) {
}
const slot = slots[name];
if (!slot) return;
if (!slot.name.startsWith(mark_s)) {
slot.childNodes.forEach((c) => c.remove());
slot.name = mark_s + name;
}
slot.append(el);
}
element.append = orig;
return element;
}
});
if (element !== root) {
const els = Array.from(element.childNodes);
element.append(...els);
}
return root;
}
function customElementRender(target, render, props = {}) {
const custom_element = target.host || target;
scope.push({

View File

@ -4,7 +4,7 @@ export interface Signal<V, A> {
/** The current value of the signal */
get(): V;
/** Set new value of the signal */
set(value: V): V;
set(value: V, force?: boolean): V;
toJSON(): V;
valueOf(): V;
}
@ -173,17 +173,28 @@ declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options?
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void;
export interface On {
/** Listens to the DOM event. See {@link Document.addEventListener} */
<Event extends keyof DocumentEventMap, EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: Event, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): EE;
<Event extends keyof DocumentEventMap, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE;
/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
connected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<El>) => any, options?: AddEventListenerOptions): EE;
connected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<NoInfer<EL>>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
disconnected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): EE;
/** Listens to the element attribute changes. In case of custom elements uses [`attributeChangedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
attributeChanged<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<[
string,
string
]>) => any, options?: AddEventListenerOptions): EE;
disconnected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
/**
* Fires when the host element is "ready", for host element itsel, it is just an alias for `scope.host(listener)`.
* This is handy to apply some property depending on full template such as:
* ```js
* const selected= "Z";
* //...
* return el("form").append(
* el("select", null, on.host(e=> e.value=selected)).append(
* el("option", { value: "A", textContent: "A" }),
* //...
* el("option", { value: "Z", textContent: "Z" }),
* ),
* );
* ```
* */
host<EL extends SupportedElement>(listener: (element: EL) => any, host?: Host<SupportedElement>): ddeElementAddon<EL>;
}
export const on: On;
export type Scope = {

File diff suppressed because one or more lines are too long

29
dist/iife.d.ts vendored
View File

@ -4,7 +4,7 @@ export interface Signal<V, A> {
/** The current value of the signal */
get(): V;
/** Set new value of the signal */
set(value: V): V;
set(value: V, force?: boolean): V;
toJSON(): V;
valueOf(): V;
}
@ -172,17 +172,28 @@ declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options?
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void;
export interface On {
/** Listens to the DOM event. See {@link Document.addEventListener} */
<Event extends keyof DocumentEventMap, EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: Event, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): EE;
<Event extends keyof DocumentEventMap, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE;
/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
connected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<El>) => any, options?: AddEventListenerOptions): EE;
connected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<NoInfer<EL>>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
disconnected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): EE;
/** Listens to the element attribute changes. In case of custom elements uses [`attributeChangedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
attributeChanged<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<[
string,
string
]>) => any, options?: AddEventListenerOptions): EE;
disconnected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
/**
* Fires when the host element is "ready", for host element itsel, it is just an alias for `scope.host(listener)`.
* This is handy to apply some property depending on full template such as:
* ```js
* const selected= "Z";
* //...
* return el("form").append(
* el("select", null, on.host(e=> e.value=selected)).append(
* el("option", { value: "A", textContent: "A" }),
* //...
* el("option", { value: "Z", textContent: "Z" }),
* ),
* );
* ```
* */
host<EL extends SupportedElement>(listener: (element: EL) => any, host?: Host<SupportedElement>): ddeElementAddon<EL>;
}
export const on: On;
export type Scope = {

180
dist/iife.js vendored
View File

@ -31,7 +31,6 @@ var DDE = (() => {
dispatchEvent: () => dispatchEvent,
el: () => createElement,
elNS: () => createElementNS,
elementAttribute: () => elementAttribute,
lifecyclesToEvents: () => lifecyclesToEvents,
memo: () => memo,
on: () => on,
@ -69,38 +68,7 @@ var DDE = (() => {
};
}
// src/signals-lib/common.js
var signals_global = {
/**
* Checks if a value is a signal
* @param {any} attributes - Value to check
* @returns {boolean} Whether the value is a signal
*/
isSignal(attributes) {
return false;
},
/**
* Processes an attribute that might be reactive
* @param {Element} obj - Element that owns the attribute
* @param {string} key - Attribute name
* @param {any} attr - Attribute value
* @param {Function} set - Function to set the attribute
* @returns {any} Processed attribute value
*/
processReactiveAttribute(obj, key, attr, set) {
return attr;
}
};
function registerReactivity(def, global = true) {
if (global) return oAssign(signals_global, def);
Object.setPrototypeOf(def, signals_global);
return def;
}
function signals(_this) {
return isProtoFrom(_this, signals_global) && _this !== signals_global ? _this : signals_global;
}
// src/dom-common.js
// src/dom-lib/common.js
var enviroment = {
setDeleteAttr,
ssr: "",
@ -126,7 +94,7 @@ var DDE = (() => {
var evd = "dde:disconnected";
var eva = "dde:attributeChanged";
// src/events-observer.js
// src/dom-lib/events-observer.js
var c_ch_o = enviroment.M ? connectionsChangesObserverConstructor() : new Proxy({}, {
get() {
return () => {
@ -290,7 +258,7 @@ var DDE = (() => {
}
}
// src/events.js
// src/dom-lib/events.js
function dispatchEvent(name, options, host) {
if (typeof options === "function") {
host = options;
@ -335,10 +303,7 @@ var DDE = (() => {
};
};
// src/dom.js
function queue(promise) {
return enviroment.q(promise);
}
// src/dom-lib/scopes.js
var scopes = [{
get scope() {
return enviroment.D.body;
@ -413,6 +378,61 @@ var DDE = (() => {
return scopes.pop();
}
};
on.host = (fn, host = scope.host) => (el) => host(() => fn(el));
// src/signals-lib/common.js
var signals_global = {
/**
* Checks if a value is a signal
* @param {any} attributes - Value to check
* @returns {boolean} Whether the value is a signal
*/
isSignal(attributes) {
return false;
},
/**
* Processes an attribute that might be reactive
* @param {Element} obj - Element that owns the attribute
* @param {string} key - Attribute name
* @param {any} attr - Attribute value
* @param {Function} set - Function to set the attribute
* @returns {any} Processed attribute value
*/
processReactiveAttribute(obj, key, attr, set) {
return attr;
}
};
function registerReactivity(def, global = true) {
if (global) return oAssign(signals_global, def);
Object.setPrototypeOf(def, signals_global);
return def;
}
function signals(_this) {
return isProtoFrom(_this, signals_global) && _this !== signals_global ? _this : signals_global;
}
// src/dom-lib/helpers.js
function setRemove(obj, prop, key, val) {
return obj[(isUndef(val) ? "remove" : "set") + prop](key, val);
}
function setRemoveNS(obj, prop, key, val, ns = null) {
return obj[(isUndef(val) ? "remove" : "set") + prop + "NS"](ns, key, val);
}
function setDelete(obj, key, val) {
Reflect.set(obj, key, val);
if (!isUndef(val)) return;
return Reflect.deleteProperty(obj, key);
}
function elementAttribute(element, op, key, value) {
if (isInstance(element, enviroment.H))
return element[op + "Attribute"](key, value);
return element[op + "AttributeNS"](null, key, value);
}
// src/dom-lib/el.js
function queue(promise) {
return enviroment.q(promise);
}
function append(...els) {
this.appendOriginal(...els);
return this;
@ -482,38 +502,6 @@ var DDE = (() => {
return el;
};
}
function simulateSlots(element, root = element) {
const mark_e = "\xB9\u2070", mark_s = "\u2713";
const slots = Object.fromEntries(
Array.from(root.querySelectorAll("slot")).filter((s) => !s.name.endsWith(mark_e)).map((s) => [s.name += mark_e, s])
);
element.append = new Proxy(element.append, {
apply(orig, _, els) {
if (els[0] === root) return orig.apply(element, els);
for (const el of els) {
const name = (el.slot || "") + mark_e;
try {
elementAttribute(el, "remove", "slot");
} catch (_error) {
}
const slot = slots[name];
if (!slot) return;
if (!slot.name.startsWith(mark_s)) {
slot.childNodes.forEach((c) => c.remove());
slot.name = mark_s + name;
}
slot.append(el);
}
element.append = orig;
return element;
}
});
if (element !== root) {
const els = Array.from(element.childNodes);
element.append(...els);
}
return root;
}
var assign_context = /* @__PURE__ */ new WeakMap();
var { setDeleteAttr: setDeleteAttr2 } = enviroment;
function assign(element, ...attributes) {
@ -576,11 +564,6 @@ var DDE = (() => {
);
return element;
}
function elementAttribute(element, op, key, value) {
if (isInstance(element, enviroment.H))
return element[op + "Attribute"](key, value);
return element[op + "AttributeNS"](null, key, value);
}
function isPropSetter(el, key) {
if (!(key in el)) return false;
const des = getPropDescriptor(el, key);
@ -604,19 +587,40 @@ var DDE = (() => {
cb(key, val);
});
}
function setRemove(obj, prop, key, val) {
return obj[(isUndef(val) ? "remove" : "set") + prop](key, val);
}
function setRemoveNS(obj, prop, key, val, ns = null) {
return obj[(isUndef(val) ? "remove" : "set") + prop + "NS"](ns, key, val);
}
function setDelete(obj, key, val) {
Reflect.set(obj, key, val);
if (!isUndef(val)) return;
return Reflect.deleteProperty(obj, key);
}
// src/customElement.js
// src/dom-lib/customElement.js
function simulateSlots(element, root = element) {
const mark_e = "\xB9\u2070", mark_s = "\u2713";
const slots = Object.fromEntries(
Array.from(root.querySelectorAll("slot")).filter((s) => !s.name.endsWith(mark_e)).map((s) => [s.name += mark_e, s])
);
element.append = new Proxy(element.append, {
apply(orig, _, els) {
if (els[0] === root) return orig.apply(element, els);
for (const el of els) {
const name = (el.slot || "") + mark_e;
try {
elementAttribute(el, "remove", "slot");
} catch (_error) {
}
const slot = slots[name];
if (!slot) return;
if (!slot.name.startsWith(mark_s)) {
slot.childNodes.forEach((c) => c.remove());
slot.name = mark_s + name;
}
slot.append(el);
}
element.append = orig;
return element;
}
});
if (element !== root) {
const els = Array.from(element.childNodes);
element.append(...els);
}
return root;
}
function customElementRender(target, render, props = {}) {
const custom_element = target.host || target;
scope.push({

29
dist/iife.min.d.ts vendored
View File

@ -4,7 +4,7 @@ export interface Signal<V, A> {
/** The current value of the signal */
get(): V;
/** Set new value of the signal */
set(value: V): V;
set(value: V, force?: boolean): V;
toJSON(): V;
valueOf(): V;
}
@ -172,17 +172,28 @@ declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options?
declare function dispatchEvent$1(name: keyof DocumentEventMap | string, options: EventInit | null, host: Host<SupportedElement>): (data?: any) => void;
export interface On {
/** Listens to the DOM event. See {@link Document.addEventListener} */
<Event extends keyof DocumentEventMap, EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: Event, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): EE;
<Event extends keyof DocumentEventMap, EL extends SupportedElement>(type: Event, listener: (this: EL, ev: DocumentEventMap[Event]) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
<EE extends ddeElementAddon<SupportedElement> = ddeElementAddon<HTMLElement>>(type: string, listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: Event | CustomEvent) => any, options?: AddEventListenerOptions): EE;
/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
connected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<El>) => any, options?: AddEventListenerOptions): EE;
connected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<NoInfer<EL>>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
disconnected<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): EE;
/** Listens to the element attribute changes. In case of custom elements uses [`attributeChangedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */ // editorconfig-checker-disable-line
attributeChanged<EE extends ddeElementAddon<SupportedElement>, El extends (EE extends ddeElementAddon<infer El> ? El : never)>(listener: (this: El, event: CustomEvent<[
string,
string
]>) => any, options?: AddEventListenerOptions): EE;
disconnected<EL extends SupportedElement>(listener: (this: EL, event: CustomEvent<void>) => any, options?: AddEventListenerOptions): ddeElementAddon<EL>;
/**
* Fires when the host element is "ready", for host element itsel, it is just an alias for `scope.host(listener)`.
* This is handy to apply some property depending on full template such as:
* ```js
* const selected= "Z";
* //...
* return el("form").append(
* el("select", null, on.host(e=> e.value=selected)).append(
* el("option", { value: "A", textContent: "A" }),
* //...
* el("option", { value: "Z", textContent: "Z" }),
* ),
* );
* ```
* */
host<EL extends SupportedElement>(listener: (element: EL) => any, host?: Host<SupportedElement>): ddeElementAddon<EL>;
}
export const on: On;
export type Scope = {

2
dist/iife.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -184,7 +184,7 @@ import { el } from "deka-dom-el";
* @param {string} [attrs.className]
* @param {URL} [attrs.src] Example code file path
* @param {string} [attrs.content] Example code
* @param {"js"|"ts"|"html"|"css"} [attrs.language="js"] Language of the code
* @param {"js"|"ts"|"html"|"css"|"shell"} [attrs.language="js"] Language of the code
* @param {string} [attrs.page_id] ID of the page, if setted it registers shiki
* */
export function code({ id, src, content, language= "js", className= host.slice(1), page_id }){

View File

@ -0,0 +1,175 @@
import { styles } from "../ssr.js";
styles.css`
#html-to-dde-converter {
grid-column: full-main;
display: flex;
flex-direction: column;
gap: 1.5rem;
padding: 1.5rem;
border-radius: var(--border-radius);
background-color: var(--bg-sidebar);
box-shadow: var(--shadow);
border: 1px solid var(--border);
}
#html-to-dde-converter h3 {
margin-top: 0;
color: var(--primary);
}
#html-to-dde-converter .description {
color: var(--text-light);
font-size: 0.95rem;
margin-top: -1rem;
}
#html-to-dde-converter .converter-form {
display: flex;
flex-direction: column;
gap: 1rem;
}
#html-to-dde-converter .input-group,
#html-to-dde-converter .output-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
#html-to-dde-converter [type="number"]{
width: 3em;
font-variant-numeric: tabular-nums;
font-size: 1rem;
}
#html-to-dde-converter label {
font-weight: 500;
display: flex;
justify-content: space-between;
}
#html-to-dde-converter .options {
display: flex;
flex-wrap: wrap;
gap: 1rem;
margin-bottom: 0.5rem;
}
#html-to-dde-converter .option-group {
display: flex;
align-items: center;
gap: 0.5rem;
}
#html-to-dde-converter textarea {
font-family: var(--font-mono);
font-size: 0.9rem;
padding: 1rem;
border-radius: var(--border-radius);
border: 1px solid var(--border);
background-color: var(--bg);
color: var(--text);
min-height: 200px;
resize: vertical;
}
#html-to-dde-converter textarea:focus {
outline: 2px solid var(--primary-light);
outline-offset: 1px;
}
#html-to-dde-converter .button-group {
display: flex;
gap: 0.5rem;
justify-content: space-between;
align-items: center;
}
#html-to-dde-converter button {
padding: 0.5rem 1rem;
border-radius: var(--border-radius);
border: none;
background-color: var(--primary);
color: var(--button-text);
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s ease;
}
#html-to-dde-converter button:hover {
background-color: var(--primary-dark);
}
#html-to-dde-converter button.secondary {
background-color: transparent;
border: 1px solid var(--border);
color: var(--text);
}
#html-to-dde-converter button.secondary:hover {
background-color: var(--bg);
border-color: var(--primary);
}
#html-to-dde-converter .copy-button {
background-color: var(--secondary);
}
#html-to-dde-converter .copy-button:hover {
background-color: var(--secondary-dark);
}
#html-to-dde-converter .status {
font-size: 0.9rem;
color: var(--text-light);
}
#html-to-dde-converter .error {
color: hsl(0, 100%, 60%);
font-size: 0.9rem;
margin-top: 0.5rem;
}
/* Sample HTML examples list */
#html-to-dde-converter .examples-list {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-top: 0.5rem;
}
#html-to-dde-converter .example-button {
font-size: 0.85rem;
padding: 0.25rem 0.5rem;
}
`;
import { ireland } from "./ireland.html.js";
import { el } from "deka-dom-el";
const fileURL= url=> new URL(url, import.meta.url);
export function converter({ page_id }){
registerClientPart(page_id);
return el(ireland, {
src: fileURL("./converter.js.js"),
exportName: "converter",
page_id,
});
}
let is_registered= {};
/** @param {string} page_id */
function registerClientPart(page_id){
if(is_registered[page_id]) return;
document.head.append(
el("script", {
// src: "https://unpkg.com/@beforesemicolon/html-parser/dist/client.js",
src: "https://cdn.jsdelivr.net/npm/@beforesemicolon/html-parser/dist/client.js",
type: "text/javascript",
charset: "utf-8",
defer: true
}),
);
is_registered[page_id]= true;
}

View File

@ -0,0 +1,383 @@
import { el, on } from "deka-dom-el";
import { S } from "deka-dom-el/signals";
const { parse }= globalThis.BFS || { parse(){ return { children: [ "not implemented" ] } } };
// Example HTML snippets
const examples = [
{
name: "Simple Component",
html: `<div class="card">
<img src="image.jpg" alt="Card Image" class="card-image">
<h2 class="card-title">Card Title</h2>
<p class="card-text">This is a simple card component</p>
<button aria-pressed="mixed" type="button" class="card-button">Click Me</button>
</div>`
},
{
name: "Navigation",
html: `<nav class="main-nav">
<ul>
<li><a href="/" class="active">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/services">Services</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>`
},
{
name: "Form",
html: `<form class="contact-form" onsubmit="submitForm(event)">
<div class="form-group">
<label for="name">Name:</label>
<input type="text" id="name" name="name" required>
</div>
<div class="form-group">
<label for="email">Email:</label>
<input type="email" id="email" name="email" required>
</div>
<div class="form-group">
<label for="message">Message:</label>
<textarea id="message" name="message" rows="4" required></textarea>
</div>
<button type="submit" class="submit-btn">Send Message</button>
</form>`
}
];
// Convert HTML to dd<el> code
function convertHTMLtoDDE(html, options = {}) {
try {
const parsed = parse(html);
const content = parsed.children[0] || parsed.childNodes[0];
return !content ? "" : nodeToDDE(content, options);
} catch (error) {
console.error("Parsing error:", error);
return `// Error parsing HTML: ${error.message}`;
}
}
// Node types based on standard DOM nodeType values
const NODE_TYPE = {
ELEMENT: 1, // Standard element node (equivalent to node.type === "element")
TEXT: 3, // Text node (equivalent to node.type === "text")
COMMENT: 8 // Comment node (equivalent to node.type === "comment")
};
// Convert a parsed node to dd<el> code
function nodeToDDE(node, options = {}, level = 0) {
const tab= options.indent === "-1" ? "\t" : " ".repeat(options.indent);
const indent = tab.repeat(level);
const nextIndent = tab.repeat(level + 1);
const { nodeType } = node;
// Handle text nodes
if (nodeType === NODE_TYPE.TEXT) {
const text = el("i", { innerText: node.nodeValue }).textContent;
if (!text.trim()) return null;
// Return as plain text or template string for longer text
return text.includes("\n") || text.includes('"')
? `\`${text}\``
: `"${text}"`;
}
// Handle comment nodes
if (nodeType === NODE_TYPE.COMMENT) {
const text = node.nodeValue;
if (!text.trim()) return null;
return text.includes("\n")
? [ "/*", ...text.trim().split("\n").map(l=> tab+l), "*/" ]
: [ `// ${text}` ];
}
// For element nodes
if (nodeType === NODE_TYPE.ELEMENT) {
// Special case for SVG elements
const isNS = node.tagName === "svg";
const elFunction = isNS ? "elNS" : "el";
// Get tag name
let tagStr = `"${node.tagName}"`;
// Process attributes
const attrs = [];
const sets = {
aria: {},
data: {},
}
for (const { name: key, value } of node.attributes) {
// Handle class attribute
if (key === "class") {
attrs.push(`className: "${value}"`);
continue;
}
// Handle style attribute
if (key === "style") {
if (options.styleAsObject) {
// Convert inline style to object
const styleObj = {};
value.split(";").forEach(part => {
const [propRaw, valueRaw] = part.split(":");
if (propRaw && valueRaw) {
const prop = propRaw.trim();
const propCamel = prop.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
styleObj[propCamel] = valueRaw.trim();
}
});
if (Object.keys(styleObj).length > 0) {
const styleStr = JSON.stringify(styleObj).replace(/"([^"]+)":/g, "$1:");
attrs.push(`style: ${styleStr}`);
}
} else {
// Keep as string
attrs.push(`style: "${value}"`);
}
continue;
}
// Handle boolean attributes
if (value === "" || value === key) {
attrs.push(`${key}: true`);
continue;
}
// Handle data/aria attributes
if (key.startsWith("data-") || key.startsWith("aria-")) {
const keyName = key.startsWith("aria-") ? "aria" : "data";
const keyCamel = key.slice(keyName.length + 1).replace(/-([a-z])/g, (_, c) => c.toUpperCase());
sets[keyName][keyCamel] = value;
continue;
}
// Regular attributes
const keyRegular = key==="for"
? "htmlFor"
: key.startsWith("on")
? `"=${key}"`
: key.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
attrs.push(`${keyRegular}: "${value}"`);
}
// Process sets
for (const [name, set] of Object.entries(sets)) {
if(options.dataAttrsAsCamel)
for (const [key, value] of Object.entries(set))
attrs.push(`${name}${key[0].toUpperCase() + key.substring(1)}: "${value}"`);
else {
const setStr= Object.entries(set).map(([key, value]) => `${key}: "${value}"`).join(",");
if (setStr !== "")
attrs.push(`${name}set: { ${setStr} }`);
}
}
// Process children
const children = [];
for (const child of node.childNodes) {
const childCode = nodeToDDE(child, options, level + 1);
if (!childCode) continue;
children.push(childCode);
}
if(node.childNodes.length===1 && node.childNodes[0].nodeType===NODE_TYPE.TEXT){
const textContent= children.pop().slice(1, -1);
attrs.unshift(`textContent: "${textContent}"`);
}
// Build the element creation code
let result = `${elFunction}("${node.tagName.toLowerCase()}"`;
// Add attributes if any
if (attrs.length > 0) {
const tooLong= attrs.join(``).length+result.length > 55;
if(options.expaned || tooLong || attrs.length > 3)
result += `, {\n${nextIndent}${attrs.join(`,\n${nextIndent}`)},\n${indent}}`;
else
result += `, { ${attrs.join(", ")} }`;
}
// Add children if any
if (children.length > 0) {
const chs= children.map(ch=>
Array.isArray(ch) ? ch.map(l=> nextIndent + l).join("\n") :
nextIndent + ch + ",");
result += `).append(\n${chs.join("\n")}\n${indent})`;
} else {
result += ")";
}
return result;
}
return null;
}
export function converter() {
// State for the converter
const htmlInput = S(examples[0].html);
const error = S("");
const status = S("");
const showStatus= msg => {
status.set(msg);
// Clear status after 3 seconds
setTimeout(() => status.set(""), 3000);
};
// Options state
const options = {
styleAsObject: {
title: "Convert style to object",
value: S(true),
},
dataAttrsAsCamel: {
title: "dataKey/ariaKey (or dataset/ariaset)",
value: S(true),
},
indent: {
title: "Indentation (-1 for tabs)",
value: S("-1"),
type: "number",
},
expaned: {
title: "Force multiline",
value: S(false),
}
};
const getOptions = ()=> Object.fromEntries(Object.entries(options)
.map(([key, option]) => ([
key,
option.value.get()
]))
);
// Update the dd<el> output when input or options change
const ddeOutput = S(() => {
try {
const result = convertHTMLtoDDE(htmlInput.get(), getOptions());
error.set("");
return result;
} catch (err) {
error.set(`Error: ${err.message}`);
return "";
}
});
// Event handlers
const onConvert = on("submit", e => {
e.preventDefault();
htmlInput.set(htmlInput.get(), true);
showStatus("Converted!");
});
const onCopy = on("click", async () => {
if (!ddeOutput.get()) return;
try {
await navigator.clipboard.writeText(ddeOutput.get());
showStatus("Copied to clipboard!");
} catch (err) {
error.set(`Could not copy: ${err.message}`);
}
});
const onClear = on("click", () => {
htmlInput.set("");
showStatus("Input cleared");
});
const onExampleLoad = (example) => on("click", () => {
htmlInput.set(example.html);
showStatus(`Loaded "${example.name}" example`);
});
const optionsElements = () => Object.entries(options)
.map(([key, option]) =>
el("label", { className: "option-group" }).append(
option.type==="number"
? el("input", {
type: option.type || "checkbox",
name: key,
value: option.value.get(),
max: 10,
}, on("change", e => option.value.set(e.target.value)))
: el("input", {
type: option.type || "checkbox",
name: key,
checked: option.value.get(),
}, on("change", e => option.value.set(e.target.checked))),
option.title,
)
);
const exampleButtons = examples.map(example =>
el("button", {
type: "button",
className: "secondary example-button"
}, onExampleLoad(example)).append(example.name)
);
return el("div", { id: "html-to-dde-converter" }).append(
el("h3", "HTML to dd<el> Converter"),
el("p", { className: "description" }).append(
"Convert HTML markup to dd<el> JavaScript code. Paste your HTML below or choose from an example."
),
el("form", { className: "converter-form" }, onConvert).append(
el("div", { className: "options" }).append(...optionsElements()),
el("div", { className: "examples-list" }).append(
el("label", "Examples: "),
...exampleButtons
),
el("div", { className: "editor-container" }).append(
el("div", { className: "input-group" }).append(
el("label", { htmlFor: "html-input" }).append(
"HTML Input",
el("div", { className: "button-group" }).append(
el("button", {
type: "button",
className: "secondary",
title: "Clear input"
}, onClear).append("Clear")
)
),
el("textarea", {
id: "html-input",
spellcheck: false,
value: htmlInput,
placeholder: "Paste your HTML here or choose an example",
oninput: e => htmlInput.set(e.target.value)
})
),
el("div", { className: "output-group" }).append(
el("label", { htmlFor: "dde-output" }).append(
"dd<el> Output",
el("div", { className: "button-group" }).append(
el("button", {
type: "button",
className: "copy-button",
title: "Copy to clipboard",
disabled: S(() => !ddeOutput.get())
}, onCopy).append("Copy")
)
),
el("textarea", {
id: "dde-output",
readonly: true,
spellcheck: false,
placeholder: "The converted dd<el> code will appear here",
value: S(() => ddeOutput.get() || "// Convert HTML to see results here")
})
)
),
el("div", { className: "button-group" }).append(
S.el(error, error => !error ? el() : el("div", { className: "error" }).append(error)),
el("div", { className: "status", textContent: status }),
el("button", { type: "submit" }).append("Convert")
)
)
);
}

View File

@ -1,13 +1,28 @@
import { el, on } from "deka-dom-el";
const paragraph= el("p", "See lifecycle events in console.",
el=> log({ type: "dde:created", detail: el }),
on.connected(log),
on.disconnected(log),
);
function allLifecycleEvents(){
return el("form", null,
el=> log({ type: "dde:created", detail: el }),
on.connected(log),
on.disconnected(log),
).append(
el("select", { id: "country" }, on.host(select => {
// This runs when the host (select) is ready with all its options
select.value = "cz"; // Pre-select Czechia
log({ type: "dde:on.host", detail: select });
})).append(
el("option", { value: "au", textContent: "Australia" }),
el("option", { value: "ca", textContent: "Canada" }),
el("option", { value: "cz", textContent: "Czechia" }),
),
el("p", "See lifecycle events in console."),
);
}
document.body.append(
paragraph,
el("button", "Remove", on("click", ()=> paragraph.remove()))
el(allLifecycleEvents),
el("button", "Remove Element", on("click", function(){
this.previousSibling.remove();
}))
);
/** @param {Partial<CustomEvent>} event */

View File

@ -1,8 +0,0 @@
import { styles } from "../ssr.js";
styles.css`
#library-url-form {
display: flex;
flex-flow: column nowrap;
}
`;

View File

@ -0,0 +1,83 @@
import { styles } from "../ssr.js";
styles.css`
#library-url-form {
display: flex;
flex-flow: column nowrap;
gap: 1rem;
padding: 1.5rem;
border-radius: var(--border-radius);
background-color: var(--bg-sidebar);
box-shadow: var(--shadow);
border: 1px solid var(--border);
margin: 1.5rem 0;
}
#library-url-form .selectors {
display: flex;
flex-flow: row wrap;
gap: 0.75rem;
}
#library-url-form output {
display: flex;
flex-flow: column nowrap;
gap: 0.75rem;
margin-top: 0.5rem;
}
#library-url-form output p {
font-weight: 500;
margin: 0.25rem 0;
color: var(--text-light);
}
#library-url-form .url-title {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: -0.25rem;
}
#library-url-form .url-title strong {
font-family: var(--font-mono);
font-size: 0.95rem;
}
#library-url-form .url-title span {
color: var(--text-light);
font-size: 0.9rem;
}
#library-url-form .code {
margin-bottom: 1rem;
}
#library-url-form .info-text {
font-size: 0.9rem;
font-style: italic;
margin-top: 1rem;
color: var(--text-light);
}
@media (max-width: 768px) {
#library-url-form .selectors {
flex-direction: column;
}
#library-url-form select {
width: 100%;
}
}
`;
import { el } from "deka-dom-el";
import { ireland } from "./ireland.html.js";
export function getLibraryUrl({ page_id }){
return el(ireland, {
src: new URL("./getLibraryUrl.js.js", import.meta.url),
exportName: "getLibraryUrl",
page_id,
});
}

View File

@ -8,6 +8,13 @@ const url_base= {
export function getLibraryUrl(){
const lib= S([ "esm", "-with-signals", ".min" ]);
const url= S(()=> url_base.jsdeka+lib.get().join(""));
const urlLabel= S(() => {
const [format, signalsPart, minified] = lib.get();
const formatText = format === "esm" ? "ES Module" : "IIFE";
const signalsText = signalsPart ? " with signals" : "";
const minText = minified ? " (minified)" : "";
return `${formatText}${signalsText}${minText}`;
})
const onSubmit= on("submit", ev => {
ev.preventDefault();
const form= new FormData(/** @type {HTMLFormElement} */ (ev.target));
@ -20,56 +27,66 @@ export function getLibraryUrl(){
const onChangeSubmit= on("change",
ev=> /** @type {HTMLSelectElement} */(ev.target).form.requestSubmit()
);
const onCopy= on("click", ev => {
const code= /** @type {HTMLDivElement} */ (ev.target).previousElementSibling;
navigator.clipboard.writeText(code.textContent);
});
return el("form", { id: "library-url-form" }, onSubmit).append(
el("select", { name: "module" }, onChangeSubmit).append(
el("option", { value: "esm", textContent: "ESM — modern JavaScript module" },
isSelected(lib.get()[0])),
el("option", { value: "iife", textContent: "IIFE — legacy JavaScript with DDE global variable" },
isSelected(lib.get()[0])),
),
el("select", { name: "what" }, onChangeSubmit).append(
el("option", { value: "", textContent: "only DOM part" },
isSelected(lib.get()[1])),
el("option", { value: "-with-signals", textContent: "DOM part and signals library" },
isSelected(lib.get()[1])),
),
el("select", { name: "minified" }, onChangeSubmit).append(
el("option", { value: "", textContent: "unminified" },
isSelected(lib.get()[2])),
el("option", { value: ".min", textContent: "minified" },
isSelected(lib.get()[2])),
el("h4", "Select your preferred library format:"),
el("div", { className: "selectors" }).append(
el("select", { name: "module" }, onChangeSubmit,
on.host(select => select.value = lib.get()[0]),
).append(
el("option", { value: "esm", textContent: "ESM — modern JavaScript module" }),
el("option", { value: "iife", textContent: "IIFE — legacy JavaScript with DDE global variable" }),
),
el("select", { name: "what" }, onChangeSubmit,
on.host(select => select.value = lib.get()[1]),
).append(
el("option", { value: "", textContent: "DOM part only" }),
el("option", { value: "-with-signals", textContent: "DOM + signals" }),
),
el("select", { name: "minified" }, onChangeSubmit,
on.host(select => select.value = lib.get()[2]),
).append(
el("option", { value: "", textContent: "Unminified" }),
el("option", { value: ".min", textContent: "Minified" }),
),
),
el("output").append(
el("p", "Library URL:"),
el("div", { className: "code", dataJs: "done", tabIndex: 0 }).append(
el("code").append(
el("pre", S(()=> url.get()+".js")),
),
el("button", {
className: "copy-button",
textContent: "Copy",
ariaLabel: "Copy code to clipboard",
}, onCopy),
el("div", { className: "url-title" }).append(
el("strong", "JavaScript:"),
el("span", urlLabel),
),
el("p", "Library type definition:"),
el("div", { className: "code", dataJs: "done", tabIndex: 0 }).append(
el("code").append(
el("pre", S(()=> url.get()+".d.ts")),
),
el("button", {
className: "copy-button",
textContent: "Copy",
ariaLabel: "Copy code to clipboard",
}, onCopy),
el(code, { value: S(()=> url.get()+".js") }),
el("div", { className: "url-title" }).append(
el("strong", "TypeScript definition:")
),
el(code, { value: S(()=> url.get()+".d.ts") }),
el("p", { className: "info-text",
textContent: "Use the CDN URL in your HTML or import it in your JavaScript files."
})
)
)
}
function isSelected(value){
return element=> element.selected= element.value===value;
/** @param {{ value: ddeSignal<string> }} props */
function code({ value }){
/** @type {ddeSignal<"Copy"|"Copied!">} */
const textContent= S("Copy");
const onCopy= on("click", () => {
navigator.clipboard.writeText(value.get());
textContent.set("Copied!");
setTimeout(() => {
textContent.set("Copy");
}, 1500);
});
return el("div", { className: "code", dataJs: "done", tabIndex: 0 }).append(
el("code").append(
el("pre", value),
),
el("button", {
className: "copy-button",
textContent,
ariaLabel: "Copy code to clipboard",
}, onCopy)
)
;
}

View File

@ -1,3 +1,33 @@
import { styles } from "../ssr.js";
styles.css`
[data-dde-mark] {
opacity: .5;
filter: grayscale();
@media (prefers-reduced-motion: no-preference) {
animation: fadein 2s infinite ease forwards;;
}
position: relative;
&::after {
content: "Loading Ireland…";
background-color: rgba(0, 0, 0, .5);
color: white;
font-weight: bold;
border-radius: 5px;
padding: 5px 10px;
position: absolute;
top: 3%;
left: 50%;
transform: translateX(-50%);
}
}
@keyframes fadein {
from { opacity: .5; }
to { opacity: .85; }
}
`;
import { el, queue } from "deka-dom-el";
import { addEventListener, registerClientFile } from "../ssr.js";
import { relative } from "node:path";
@ -21,10 +51,17 @@ export function ireland({ src, exportName = "default", props = {} }) {
const path= "./"+relative(dir, src.pathname);
const id = "ireland-" + generateComponentId(src);
const element = el.mark({ type: "later", name: ireland.name });
queue(import(path).then(module => {
const component = module[exportName];
element.replaceWith(el(component, props, mark(id)));
}));
queue(
import(path)
.then(module => {
const component = module[exportName];
const content= el(component, props, mark(id));
element.replaceWith(content);
content.querySelectorAll("input, textarea, button")
.forEach(el=> el.disabled= true);
})
.catch(console.error)
);
if(!componentsRegistry.size)
addEventListener("oneachrender", registerClientPart);

View File

@ -37,3 +37,14 @@ styles.css`
}
}
`;
import { el } from "deka-dom-el";
import { ireland } from "./ireland.html.js";
export function scrollTop(){
return el(ireland, {
src: new URL("./scrollTop.js.js", import.meta.url),
exportName: "scrollTop",
page_id: "*",
});
}

View File

@ -86,7 +86,7 @@ html {
}
:focus-visible {
outline: 3px solid hsl(231, 48%, 70%);
outline: 3px solid var(--primary-light);
outline-offset: 2px;
}
@ -207,6 +207,20 @@ figure {
max-width: 100%;
}
}
select {
padding: 0.5rem 0.75rem;
border-radius: var(--border-radius);
border: 1px solid var(--border);
background-color: var(--bg);
color: var(--text);
cursor: pointer;
font-size: 0.95rem;
font-family: var(--font-main);
}
select:hover {
border-color: var(--primary);
}
/* Layout */
body {

View File

@ -1,4 +1,4 @@
import "./components/getLibraryUrl.css.js";
import "./components/getLibraryUrl.html.js";
import { t, T } from "./utils/index.js";
export const info= {
href: "./",
@ -12,7 +12,7 @@ import { simplePage } from "./layout/simplePage.html.js";
import { h3 } from "./components/pageUtils.html.js";
import { example } from "./components/example.html.js";
import { code } from "./components/code.html.js";
import { ireland } from "./components/ireland.html.js";
import { getLibraryUrl } from "./components/getLibraryUrl.html.js";
/** @param {string} url */
const fileURL= url=> new URL(url, import.meta.url);
const references= {
@ -101,13 +101,29 @@ export function page({ pkg, info }){
el(h3, t`Getting Started`),
el("p").append(T`
To get builded dd<el> to be used immediately in your browser, you can download the latest version from:
There are multiple ways to include dd<el> in your project. You can use npm for a full development setup,
or directly include it from a CDN for quick prototyping.
`),
el(ireland, {
src: fileURL("./components/getLibraryUrl.js.js"),
exportName: "getLibraryUrl",
page_id,
}),
el("h4", "npm installation"),
el(code, { content: "npm install deka-dom-el # Coming soon", language: "shell", page_id }),
el("h4", "CDN / Direct Script Usage"),
el("p").append(T`
Use the interactive selector below to choose your preferred format:
`),
el(getLibraryUrl, { page_id }),
el("div", { className: "note" }).append(
el("p").append(T`
Based on your selection, you can use dd<el> in your project like this:
`),
el(code, { content: `
// ESM format (modern JavaScript with import/export)
import { el, on } from "https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/esm-with-signals.min.js";
// Or with IIFE format (creates a global DDE object)
// <script src="https://cdn.jsdelivr.net/gh/jaandrle/deka-dom-el/dist/iife-with-signals.min.js"></script>
const { el, on } = DDE;
`, language: "js", page_id }),
),
el(h3, t`How to Use This Documentation`),
el("p").append(T`

View File

@ -3,10 +3,7 @@ import { el, simulateSlots } from "deka-dom-el";
import { header } from "./head.html.js";
import { prevNext } from "../components/pageUtils.html.js";
import { ireland } from "../components/ireland.html.js";
import "../components/scrollTop.css.js";
/** @param {string} url */
const fileURL= url=> new URL(url, import.meta.url);
import { scrollTop } from "../components/scrollTop.html.js";
/** @param {Pick<import("../types.d.ts").PageAttrs, "pkg" | "info">} attrs */
export function simplePage({ pkg, info }){
@ -33,6 +30,6 @@ export function simplePage({ pkg, info }){
),
// Scroll to top button
el(ireland, { src: fileURL("../components/scrollTop.js.js"), exportName: "scrollTop" })
el(scrollTop),
));
}

View File

@ -155,8 +155,8 @@ export function page({ pkg, info }){
You can think of an Addon as an oncreate event handler.
`),
el("p").append(T`
dd<el> provides two additional lifecycle events that correspond to ${el("a", { textContent:
"custom element", ...references.mdn_customElements })} lifecycle callbacks:
dd<el> provides three additional lifecycle events that correspond to ${el("a", { textContent:
"custom element", ...references.mdn_customElements })} lifecycle callbacks and component patterns:
`),
el("div", { className: "function-table" }).append(
el("dl").append(
@ -165,6 +165,10 @@ export function page({ pkg, info }){
el("dt", t`on.disconnected(callback)`),
el("dd", t`Fires when the element is removed from the DOM`),
el("dt", t`on.host(callback, host?)`),
el("dd", t`Fires when the host element is "ready" and allows applying properties based on the fully
built template`),
)
),
el(example, { src: fileURL("./components/examples/events/live-cycle.js"), page_id }),

View File

@ -0,0 +1,99 @@
import "./components/converter.html.js";
import { T, t } from "./utils/index.js";
export const info= {
title: t`Convert to dd<el>`,
fullTitle: t`HTML to dd<el> Converter`,
description: t`Convert your HTML markup to dd<el> JavaScript code with our interactive tool`,
};
import { el } from "deka-dom-el";
import { simplePage } from "./layout/simplePage.html.js";
import { h3 } from "./components/pageUtils.html.js";
import { code } from "./components/code.html.js";
import { converter } from "./components/converter.html.js";
/** @param {import("./types.d.ts").PageAttrs} attrs */
export function page({ pkg, info }){
const page_id= info.id;
return el(simplePage, { info, pkg }).append(
el("p").append(T`
Transitioning from HTML to dd<el> is simple with our interactive converter. This tool helps you quickly
transform existing HTML markup into dd<el> JavaScript code, making it easier to adopt dd<el> in your projects.
`),
el("div", { className: "callout" }).append(
el("h4", t`Features`),
el("ul").append(
el("li", t`Convert any HTML snippet to dd<el> code instantly`),
el("li", t`Choose between different output formats (append vs arrays, style handling)`),
el("li", t`Try pre-built examples or paste your own HTML`),
el("li", t`Copy results to clipboard with one click`)
)
),
el("h3", t`How to Use the Converter`),
el("ol").append(
el("li").append(T`
${el("strong", "Paste your HTML")} into the input box or select one of the example templates
`),
el("li").append(T`
${el("strong", "Configure options")} to match your preferred coding style:
${el("ul").append(
el("li", t`Convert inline styles to JavaScript objects`),
el("li", t`Transform data-attributes/aria-attributes`),
)}
`),
el("li").append(T`
${el("strong", "Click convert")} to generate dd<el> code
`),
el("li").append(T`
${el("strong", "Copy the result")} to your project
`)
),
// The actual converter component
el(converter, { page_id }),
el("h3", t`How the Converter Works`),
el("p").append(T`
The converter uses a three-step process:
`),
el("ol").append(
el("li").append(T`
${el("strong", "Parsing:")} The HTML is parsed into a structured AST (Abstract Syntax Tree)
`),
el("li").append(T`
${el("strong", "Transformation:")} Each HTML node is converted to its dd<el> equivalent
`),
el("li").append(T`
${el("strong", "Code Generation:")} The final JavaScript code is properly formatted and indented
`)
),
el("div", { className: "warning" }).append(
el("p").append(T`
While the converter handles most basic HTML patterns, complex attributes or specialized elements might
need manual adjustments. Always review the generated code before using it in production.
`)
),
el("h3", t`Next Steps`),
el("p").append(T`
After converting your HTML to dd<el>, you might want to:
`),
el("ul").append(
el("li").append(T`
Add signal bindings for dynamic content (see ${el("a", { href: "p04-signals.html",
textContent: "Signals section" })})
`),
el("li").append(T`
Organize your components with scopes (see ${el("a", { href: "p05-scopes.html",
textContent: "Scopes section" })})
`),
el("li").append(T`
Add event handlers for interactivity (see ${el("a", { href: "p03-events.html",
textContent: "Events section" })})
`)
)
);
}

49
index.d.ts vendored
View File

@ -184,12 +184,12 @@ interface On{
/** Listens to the DOM event. See {@link Document.addEventListener} */
<
Event extends keyof DocumentEventMap,
EE extends ddeElementAddon<SupportedElement>= ddeElementAddon<HTMLElement>,
EL extends SupportedElement
>(
type: Event,
listener: (this: EE extends ddeElementAddon<infer El> ? El : never, ev: DocumentEventMap[Event]) => any,
listener: (this: EL, ev: DocumentEventMap[Event]) => any,
options?: AddEventListenerOptions
) : EE;
) : ddeElementAddon<EL>;
<
EE extends ddeElementAddon<SupportedElement>= ddeElementAddon<HTMLElement>,
>(
@ -199,28 +199,39 @@ interface On{
) : EE;
/** Listens to the element is connected to the live DOM. In case of custom elements uses [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */// editorconfig-checker-disable-line
connected<
EE extends ddeElementAddon<SupportedElement>,
El extends ( EE extends ddeElementAddon<infer El> ? El : never )
EL extends SupportedElement
>(
listener: (this: El, event: CustomEvent<El>) => any,
listener: (this: EL, event: CustomEvent<NoInfer<EL>>) => any,
options?: AddEventListenerOptions
) : EE;
) : ddeElementAddon<EL>;
/** Listens to the element is disconnected from the live DOM. In case of custom elements uses [`disconnectedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */// editorconfig-checker-disable-line
disconnected<
EE extends ddeElementAddon<SupportedElement>,
El extends ( EE extends ddeElementAddon<infer El> ? El : never )
EL extends SupportedElement
>(
listener: (this: El, event: CustomEvent<void>) => any,
listener: (this: EL, event: CustomEvent<void>) => any,
options?: AddEventListenerOptions
) : EE;
/** Listens to the element attribute changes. In case of custom elements uses [`attributeChangedCallback`](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements#custom_element_lifecycle_callbacks), or {@link MutationObserver} else where */// editorconfig-checker-disable-line
attributeChanged<
EE extends ddeElementAddon<SupportedElement>,
El extends ( EE extends ddeElementAddon<infer El> ? El : never )
>(
listener: (this: El, event: CustomEvent<[ string, string ]>) => any,
options?: AddEventListenerOptions
) : EE;
) : ddeElementAddon<EL>;
/**
* Fires when the host element is "ready", for host element itsel, it is just an alias for `scope.host(listener)`.
* This is handy to apply some property depending on full template such as:
* ```js
* const selected= "Z";
* //...
* return el("form").append(
* el("select", null, on.host(e=> e.value=selected)).append(
* el("option", { value: "A", textContent: "A" }),
* //...
* el("option", { value: "Z", textContent: "Z" }),
* ),
* );
* ```
* */
host<
EL extends SupportedElement
>(
listener: (element: EL) => any,
host?: Host<SupportedElement>
) : ddeElementAddon<EL>;
}
export const on: On;

View File

@ -1,5 +1,3 @@
export * from "./src/dom.js";
export * from "./src/customElement.js";
export * from "./src/events.js";
export { registerReactivity } from "./src/signals-lib/common.js";
export * from "./src/dom-lib/index.js";
export { memo } from "./src/memo.js";
export { registerReactivity } from "./src/signals-lib/common.js";

View File

@ -1,4 +1,4 @@
import { enviroment as env } from './src/dom-common.js';
import { enviroment as env } from './src/dom-lib/common.js';
env.ssr= " ssr";
const q_store= new Set();

2
signals.d.ts vendored
View File

@ -2,7 +2,7 @@ export interface Signal<V, A> {
/** The current value of the signal */
get(): V;
/** Set new value of the signal */
set(value: V): V;
set(value: V, force?: boolean): V;
toJSON(): V;
valueOf(): V;
}

View File

@ -21,7 +21,7 @@ export const enviroment= {
M: globalThis.MutationObserver,
q: p=> p || Promise.resolve(),
};
import { isInstance, isUndef } from './helpers.js';
import { isInstance, isUndef } from '../helpers.js';
/**
* Handles attribute setting with special undefined handling

View File

@ -1,6 +1,47 @@
import { keyLTE, evc, evd, eva } from "./dom-common.js";
import { scope } from "./dom.js";
import { keyLTE, evc, evd, eva } from "./common.js";
import { scope } from "./scopes.js";
import { c_ch_o } from "./events-observer.js";
import { elementAttribute } from "./helpers.js";
/**
* Simulates slot functionality for elements
*
* @param {HTMLElement} element - Parent element
* @param {HTMLElement} [root=element] - Root element containing slots
* @returns {HTMLElement} The root element
*/
export function simulateSlots(element, root= element){
const mark_e= "¹⁰", mark_s= "✓"; //NOTE: Markers to identify slots processed by this function. Also “prevents” native behavior as it is unlikely to use these in names. // editorconfig-checker-disable-line
const slots= Object.fromEntries(
Array.from(root.querySelectorAll("slot"))
.filter(s => !s.name.endsWith(mark_e))
.map(s => [(s.name += mark_e), s]));
element.append= new Proxy(element.append, {
apply(orig, _, els){
if(els[0]===root) return orig.apply(element, els);
for(const el of els){
const name= (el.slot||"")+mark_e;
try{ elementAttribute(el, "remove", "slot"); } catch(_error){}
const slot= slots[name];
if(!slot) return;
if(!slot.name.startsWith(mark_s)){
slot.childNodes.forEach(c=> c.remove());
slot.name= mark_s+name;
}
slot.append(el);
//TODO?: el.dispatchEvent(new CustomEvent("dde:slotchange", { detail: slot }));
}
element.append= orig; //TODO?: better memory management, but non-native behavior!
return element;
}
});
if(element!==root){
const els= Array.from(element.childNodes);
//TODO?: els.forEach(el=> el.remove());
element.append(...els);
}
return root;
}
/**
* Renders content into a custom element or shadow root

View File

@ -1,7 +1,6 @@
import { signals } from "./signals-lib/common.js";
import { enviroment as env } from './dom-common.js';
import { isInstance, isUndef, oAssign } from "./helpers.js";
import { on } from "./events.js";
import { signals } from "../signals-lib/common.js";
import { enviroment as env } from './common.js';
import { isInstance, isUndef, oAssign } from "../helpers.js";
/**
* Queues a promise, this is helpful for crossplatform components (on server side we can wait for all registered
@ -11,84 +10,6 @@ import { on } from "./events.js";
*/
export function queue(promise){ return env.q(promise); }
/**
* Array of scope contexts for tracking component hierarchies
* @type {{ scope: object, prevent: boolean, host: function }[]}
*/
const scopes= [ {
get scope(){ return env.D.body; },
host: c=> c ? c(env.D.body) : env.D.body,
prevent: true,
} ];
/** Store for disconnect abort controllers */
const store_abort= new WeakMap();
/**
* Scope management utility for tracking component hierarchies
*/
export const scope= {
/**
* Gets the current scope
* @returns {Object} Current scope context
*/
get current(){ return scopes[scopes.length-1]; },
/**
* Gets the host element of the current scope
* @returns {Function} Host accessor function
*/
get host(){ return this.current.host; },
/**
* Creates/gets an AbortController that triggers when the element disconnects
* */
get signal(){
const { host }= this;
if(store_abort.has(host)) return store_abort.get(host);
const a= new AbortController();
store_abort.set(host, a);
host(on.disconnected(()=> a.abort()));
return a.signal;
},
/**
* Prevents default behavior in the current scope
* @returns {Object} Current scope context
*/
preventDefault(){
const { current }= this;
current.prevent= true;
return current;
},
/**
* Gets a copy of the current scope stack
* @returns {Array} Copy of scope stack
*/
get state(){ return [ ...scopes ]; },
/**
* Pushes a new scope to the stack
* @param {Object} [s={}] - Scope object to push
* @returns {number} New length of the scope stack
*/
push(s= {}){ return scopes.push(oAssign({}, this.current, { prevent: false }, s)); },
/**
* Pushes the root scope to the stack
* @returns {number} New length of the scope stack
*/
pushRoot(){ return scopes.push(scopes[0]); },
/**
* Pops the current scope from the stack
* @returns {Object|undefined} Popped scope or undefined if only one scope remains
*/
pop(){
if(scopes.length===1) return;
return scopes.pop();
},
};
/**
* Chainable append function for elements
* @private
@ -107,6 +28,7 @@ export function chainableAppend(el){
/** Current namespace for element creation */
let namespace;
import { scope } from "./scopes.js";
/**
* Creates a DOM element with specified tag, attributes and addons
*
@ -191,46 +113,6 @@ export function createElementNS(ns){
/** Alias for createElementNS */
export { createElementNS as elNS };
/**
* Simulates slot functionality for elements
*
* @param {HTMLElement} element - Parent element
* @param {HTMLElement} [root=element] - Root element containing slots
* @returns {HTMLElement} The root element
*/
export function simulateSlots(element, root= element){
const mark_e= "¹⁰", mark_s= "✓"; //NOTE: Markers to identify slots processed by this function. Also “prevents” native behavior as it is unlikely to use these in names. // editorconfig-checker-disable-line
const slots= Object.fromEntries(
Array.from(root.querySelectorAll("slot"))
.filter(s => !s.name.endsWith(mark_e))
.map(s => [(s.name += mark_e), s]));
element.append= new Proxy(element.append, {
apply(orig, _, els){
if(els[0]===root) return orig.apply(element, els);
for(const el of els){
const name= (el.slot||"")+mark_e;
try{ elementAttribute(el, "remove", "slot"); } catch(_error){}
const slot= slots[name];
if(!slot) return;
if(!slot.name.startsWith(mark_s)){
slot.childNodes.forEach(c=> c.remove());
slot.name= mark_s+name;
}
slot.append(el);
//TODO?: el.dispatchEvent(new CustomEvent("dde:slotchange", { detail: slot }));
}
element.append= orig; //TODO?: better memory management, but non-native behavior!
return element;
}
});
if(element!==root){
const els= Array.from(element.childNodes);
//TODO?: els.forEach(el=> el.remove());
element.append(...els);
}
return root;
}
/** Store for element assignment contexts */
const assign_context= new WeakMap();
const { setDeleteAttr }= env;
@ -251,6 +133,7 @@ export function assign(element, ...attributes){
assign_context.delete(element);
return element;
}
import { setDelete } from "./helpers.js";
/**
* Assigns a single attribute to an element
*
@ -290,6 +173,7 @@ export function assignAttribute(element, key, value){
}
return isPropSetter(element, key) ? setDeleteAttr(element, key, value) : setRemoveAttr(key, value);
}
import { setRemove, setRemoveNS } from "./helpers.js";
/**
* Gets or creates assignment context for an element
*
@ -320,21 +204,6 @@ export function classListDeclarative(element, toggle){
return element;
}
/**
* Generic element attribute manipulation
*
* @param {Element} element - Element to manipulate
* @param {string} op - Operation ("set" or "remove")
* @param {string} key - Attribute name
* @param {any} [value] - Attribute value
* @returns {void}
*/
export function elementAttribute(element, op, key, value){
if(isInstance(element, env.H))
return element[op+"Attribute"](key, value);
return element[op+"AttributeNS"](null, key, value);
}
//TODO: add cache? `(Map/Set)<el.tagName+key,isUndef>`
/**
* Checks if a property can be set on an element
@ -385,45 +254,3 @@ function forEachEntries(s, target, element, obj, cb){
cb(key, val);
});
}
/**
* Sets or removes an attribute based on value
*
* @param {Element} obj - Element to modify
* @param {string} prop - Property suffix ("Attribute")
* @param {string} key - Attribute name
* @param {any} val - Attribute value
* @returns {void}
* @private
*/
function setRemove(obj, prop, key, val){
return obj[ (isUndef(val) ? "remove" : "set") + prop ](key, val);
}
/**
* Sets or removes a namespaced attribute based on value
*
* @param {Element} obj - Element to modify
* @param {string} prop - Property suffix ("Attribute")
* @param {string} key - Attribute name
* @param {any} val - Attribute value
* @param {string|null} [ns=null] - Namespace URI
* @returns {void}
* @private
*/
function setRemoveNS(obj, prop, key, val, ns= null){
return obj[ (isUndef(val) ? "remove" : "set") + prop + "NS" ](ns, key, val);
}
/**
* Sets or deletes a property based on value
*
* @param {Object} obj - Object to modify
* @param {string} key - Property name
* @param {any} val - Property value
* @returns {void}
* @private
*/
function setDelete(obj, key, val){
Reflect.set(obj, key, val); if(!isUndef(val)) return; return Reflect.deleteProperty(obj, key);
}

View File

@ -1,5 +1,5 @@
import { enviroment as env, evc, evd } from './dom-common.js';
import { isInstance } from "./helpers.js";
import { enviroment as env, evc, evd } from './common.js';
import { isInstance } from "../helpers.js";
/**
* Connection changes observer for tracking element connection/disconnection

View File

@ -1,5 +1,5 @@
import { keyLTE, evc, evd } from './dom-common.js';
import { oAssign, onAbort } from './helpers.js';
import { keyLTE, evc, evd } from './common.js';
import { oAssign, onAbort } from '../helpers.js';
/**
* Creates a function to dispatch events on elements

57
src/dom-lib/helpers.js Normal file
View File

@ -0,0 +1,57 @@
import { enviroment as env } from './common.js';
import { isInstance, isUndef } from "../helpers.js";
/**
* Sets or removes an attribute based on value
*
* @param {Element} obj - Element to modify
* @param {string} prop - Property suffix ("Attribute")
* @param {string} key - Attribute name
* @param {any} val - Attribute value
* @returns {void}
* @private
*/
export function setRemove(obj, prop, key, val){
return obj[ (isUndef(val) ? "remove" : "set") + prop ](key, val);
}
/**
* Sets or removes a namespaced attribute based on value
*
* @param {Element} obj - Element to modify
* @param {string} prop - Property suffix ("Attribute")
* @param {string} key - Attribute name
* @param {any} val - Attribute value
* @param {string|null} [ns=null] - Namespace URI
* @returns {void}
* @private
*/
export function setRemoveNS(obj, prop, key, val, ns= null){
return obj[ (isUndef(val) ? "remove" : "set") + prop + "NS" ](ns, key, val);
}
/**
* Sets or deletes a property based on value
*
* @param {Object} obj - Object to modify
* @param {string} key - Property name
* @param {any} val - Property value
* @returns {void}
* @private
*/
export function setDelete(obj, key, val){
Reflect.set(obj, key, val); if(!isUndef(val)) return; return Reflect.deleteProperty(obj, key);
}
/**
* Generic element attribute manipulation
*
* @param {Element} element - Element to manipulate
* @param {string} op - Operation ("set" or "remove")
* @param {string} key - Attribute name
* @param {any} [value] - Attribute value
* @returns {void}
*/
export function elementAttribute(element, op, key, value){
if(isInstance(element, env.H))
return element[op+"Attribute"](key, value);
return element[op+"AttributeNS"](null, key, value);
}

4
src/dom-lib/index.js Normal file
View File

@ -0,0 +1,4 @@
export * from "./scopes.js";
export * from "./el.js";
export * from "./events.js";
export * from "./customElement.js";

84
src/dom-lib/scopes.js Normal file
View File

@ -0,0 +1,84 @@
import { enviroment as env } from './common.js';
import { oAssign } from "../helpers.js";
import { on } from "./events.js";
/**
* Array of scope contexts for tracking component hierarchies
* @type {{ scope: object, prevent: boolean, host: function }[]}
*/
const scopes= [ {
get scope(){ return env.D.body; },
host: c=> c ? c(env.D.body) : env.D.body,
prevent: true,
} ];
/** Store for disconnect abort controllers */
const store_abort= new WeakMap();
/**
* Scope management utility for tracking component hierarchies
*/
export const scope= {
/**
* Gets the current scope
* @returns {Object} Current scope context
*/
get current(){ return scopes[scopes.length-1]; },
/**
* Gets the host element of the current scope
* @returns {Function} Host accessor function
*/
get host(){ return this.current.host; },
/**
* Creates/gets an AbortController that triggers when the element disconnects
* */
get signal(){
const { host }= this;
if(store_abort.has(host)) return store_abort.get(host);
const a= new AbortController();
store_abort.set(host, a);
host(on.disconnected(()=> a.abort()));
return a.signal;
},
/**
* Prevents default behavior in the current scope
* @returns {Object} Current scope context
*/
preventDefault(){
const { current }= this;
current.prevent= true;
return current;
},
/**
* Gets a copy of the current scope stack
* @returns {Array} Copy of scope stack
*/
get state(){ return [ ...scopes ]; },
/**
* Pushes a new scope to the stack
* @param {Object} [s={}] - Scope object to push
* @returns {number} New length of the scope stack
*/
push(s= {}){ return scopes.push(oAssign({}, this.current, { prevent: false }, s)); },
/**
* Pushes the root scope to the stack
* @returns {number} New length of the scope stack
*/
pushRoot(){ return scopes.push(scopes[0]); },
/**
* Pops the current scope from the stack
* @returns {Object|undefined} Popped scope or undefined if only one scope remains
*/
pop(){
if(scopes.length===1) return;
return scopes.pop();
},
};
// TODO: better place while no cross-import?
on.host= (fn, host= scope.host)=> el=> host(()=> fn(el));

View File

@ -159,10 +159,10 @@ signal.clear= function(...signals){
};
/** Property key for tracking reactive elements */
const key_reactive= "__dde_reactive";
import { enviroment as env, eva } from "../dom-common.js";
import { el } from "../dom.js";
import { scope } from "../dom.js";
import { on } from "../events.js";
import { enviroment as env, eva } from "../dom-lib/common.js";
import { el } from "../dom-lib/index.js";
import { scope } from "../dom-lib/scopes.js";
import { on } from "../dom-lib/events.js";
import { memo } from "../memo.js";
/**