diff --git a/dist/esm-with-signals.d.ts b/dist/esm-with-signals.d.ts index 672e3fa..0e26b90 100644 --- a/dist/esm-with-signals.d.ts +++ b/dist/esm-with-signals.d.ts @@ -1,69 +1,4 @@ -export type Signal= (set?: V)=> V & A; -type Action= (this: { value: V, stopPropagation(): void }, ...a: any[])=> typeof signal._ | void; -//type SymbolSignal= Symbol; -type SymbolOnclear= symbol; -type Actions= Record>; -type OnListenerOptions= Pick & { first_time?: boolean }; -interface signal{ - _: Symbol - /** - * Simple example: - * ```js - * const hello= S("Hello Signal"); - * ``` - * …simple todo signal: - * ```js - * const todos= S([], { - * add(v){ this.value.push(S(v)); }, - * remove(i){ this.value.splice(i, 1); }, - * [S.symbols.onclear](){ S.clear(...this.value); }, - * }); - * ``` - * …computed signal: - * ```js - * const name= S("Jan"); - * const surname= S("Andrle"); - * const fullname= S(()=> name()+" "+surname()); - * ``` - * @param value Initial signal value. Or function computing value from other signals. - * @param actions Use to define actions on the signal. Such as add item to the array. - * There is also a reserved function `S.symbol.onclear` which is called when the signal is cleared - * by `S.clear`. - * */ - >(value: V, actions?: A): Signal; - /** - * Computations signal. This creates a signal which is computed from other signals. - * */ - (computation: ()=> V): Signal - action>, A extends (S extends Signal ? A : never), N extends keyof A>( - signal: S, - name: N, - ...params: A[N] extends (...args: infer P)=> any ? P : never - ): void; - clear(...signals: Signal[]): void; - on(signal: Signal, onchange: (a: T)=> void, options?: OnListenerOptions): void; - symbols: { - //signal: SymbolSignal; - onclear: SymbolOnclear; - } - /** - * Reactive element, which is rendered based on the given signal. - * ```js - * S.el(signal, value=> value ? el("b", "True") : el("i", "False")); - * S.el(listS, list=> list.map(li=> el("li", li))); - * ``` - * */ - el(signal: Signal, el: (v: S)=> Element | Element[] | DocumentFragment): DocumentFragment; - - observedAttributes(custom_element: HTMLElement): Record>; -} -export const signal: signal; -export const S: signal; -declare global { - type ddeSignal= Signal; - type ddeAction= Action - type ddeActions= Actions -} +declare global{ /* ddeSignal */ } type CustomElementTagNameMap= { '#text': Text, '#comment': Comment } type SupportedElement= HTMLElementTagNameMap[keyof HTMLElementTagNameMap] @@ -80,20 +15,20 @@ type AttrsModified= { /** * Use string like in HTML (internally uses `*.setAttribute("style", *)`), or object representation (like DOM API). */ - style: string | Partial | Signal | Partial<{ [K in keyof CSSStyleDeclaration]: Signal }> + style: string | Partial | ddeSignal | Partial<{ [K in keyof CSSStyleDeclaration]: ddeSignal }> /** * Provide option to add/remove/toggle CSS clasess (index of object) using 1/0/-1. In fact `el.classList.toggle(class_name)` for `-1` and `el.classList.toggle(class_name, Boolean(...))` for others. */ - classList: Record>, + classList: Record>, /** * By default simiral to `className`, but also supports `string[]` * */ - className: string | (string|boolean|undefined|Signal)[]; + className: string | (string|boolean|undefined|ddeSignal)[]; /** * Sets `aria-*` simiraly to `dataset` * */ - ariaset: Record>, -} & Record<`=${string}` | `data${PascalCase}` | `aria${PascalCase}`, string|Signal> & Record<`.${string}`, any> + ariaset: Record>, +} & Record<`=${string}` | `data${PascalCase}` | `aria${PascalCase}`, string|ddeSignal> & Record<`.${string}`, any> type _fromElsInterfaces= Omit; /** * Just element attributtes @@ -103,15 +38,15 @@ type _fromElsInterfaces= Omit= Partial<_fromElsInterfaces & { [K in keyof _fromElsInterfaces]: Signal<_fromElsInterfaces[K], any> } & AttrsModified> & Record; +type ElementAttributes= Partial<{ [K in keyof _fromElsInterfaces]: _fromElsInterfaces[K] | ddeSignal<_fromElsInterfaces[K]> } & AttrsModified> & Record; export function classListDeclarative(element: El, classList: AttrsModified["classList"]): El export function assign(element: El, ...attrs_array: ElementAttributes[]): El export function assignAttribute>(element: El, attr: ATT, value: ElementAttributes[ATT]): ElementAttributes[ATT] type ExtendedHTMLElementTagNameMap= HTMLElementTagNameMap & CustomElementTagNameMap; -type textContent= string | ( (set?: string)=> string ); // TODO: for some reason `Signal` leads to `attrs?: any` +type textContent= string | ddeSignal; export function el< - TAG extends keyof ExtendedHTMLElementTagNameMap & string, + TAG extends keyof ExtendedHTMLElementTagNameMap, EL extends (TAG extends keyof ExtendedHTMLElementTagNameMap ? ExtendedHTMLElementTagNameMap[TAG] : HTMLElement) >( tag_name: TAG, @@ -121,6 +56,11 @@ export function el< export function el( tag_name?: "<>", ): ddeDocumentFragment +export function el( + tag_name: string, + attrs?: ElementAttributes, + ...addons: ddeElementAddon[] +): ddeHTMLElement export function el< C extends (attr: ddeComponentAttributes)=> SupportedElement | ddeDocumentFragment @@ -148,7 +88,7 @@ export function elNS( EL extends ( TAG extends keyof MathMLElementTagNameMap ? MathMLElementTagNameMap[TAG] : MathMLElement ), >( tag_name: TAG, - attrs?: string | textContent | Partial<{ [key in keyof EL]: EL[key] | Signal | string | number | boolean }>, + attrs?: string | textContent | Partial<{ [key in keyof EL]: EL[key] | ddeSignal | string | number | boolean }>, ...addons: ddeElementAddon[] )=> ddeMathMLElement export function elNS( @@ -189,7 +129,7 @@ interface On{ EE extends ddeElementAddon, El extends ( EE extends ddeElementAddon ? El : never ) >( - listener: (this: El, event: CustomEvent) => any, + listener: (this: El, event: CustomEvent) => any, options?: AddEventListenerOptions ) : EE; /** 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 */ @@ -224,7 +164,7 @@ export const scope: { * It can be also used to register Addon(s) (functions to be called when component is initized) * — `scope.host(on.connected(console.log))`. * */ - host: (...addons: ddeElementAddon[])=> HTMLElement, + host: (...addons: ddeElementAddon[])=> HTMLElement, state: Scope[], /** Adds new child scope. All attributes are inherited by default. */ @@ -237,15 +177,15 @@ export const scope: { export function customElementRender< EL extends HTMLElement, - P extends any = Record + P extends any = Record> >( custom_element: EL, target: ShadowRoot | EL, - render: (props: P)=> SupportedElement, - props?: P | ((...args: any[])=> P) + render: (props: P)=> SupportedElement | DocumentFragment, + props?: P | ((el: EL)=> P) ): EL -export function customElementWithDDE(custom_element: EL): EL -export function lifecyclesToEvents(custom_element: EL): EL +export function customElementWithDDE HTMLElement)>(custom_element: EL): EL +export function lifecyclesToEvents HTMLElement)>(custom_element: EL): EL export function observedAttributes(custom_element: HTMLElement): Record /* TypeScript MEH */ @@ -525,4 +465,70 @@ interface ddeSVGTextPathElement extends SVGTextPathElement{ append: ddeAppend
; } interface ddeSVGTSpanElement extends SVGTSpanElement{ append: ddeAppend; } interface ddeSVGUseElement extends SVGUseElement{ append: ddeAppend; } -interface ddeSVGViewElement extends SVGViewElement{ append: ddeAppend; } \ No newline at end of file +interface ddeSVGViewElement extends SVGViewElement{ append: ddeAppend; } +export type Signal= (set?: V)=> V & A; +type Action= (this: { value: V, stopPropagation(): void }, ...a: any[])=> typeof signal._ | void; +//type SymbolSignal= Symbol; +type SymbolOnclear= symbol; +type Actions= Record>; +type OnListenerOptions= Pick & { first_time?: boolean }; +interface signal{ + _: Symbol + /** + * Computations signal. This creates a signal which is computed from other signals. + * */ + any>(computation: V): Signal, {}> + /** + * Simple example: + * ```js + * const hello= S("Hello Signal"); + * ``` + * …simple todo signal: + * ```js + * const todos= S([], { + * add(v){ this.value.push(S(v)); }, + * remove(i){ this.value.splice(i, 1); }, + * [S.symbols.onclear](){ S.clear(...this.value); }, + * }); + * ``` + * …computed signal: + * ```js + * const name= S("Jan"); + * const surname= S("Andrle"); + * const fullname= S(()=> name()+" "+surname()); + * ``` + * @param value Initial signal value. Or function computing value from other signals. + * @param actions Use to define actions on the signal. Such as add item to the array. + * There is also a reserved function `S.symbol.onclear` which is called when the signal is cleared + * by `S.clear`. + * */ + >(value: V, actions?: A): Signal; + action>, A extends (S extends Signal ? A : never), N extends keyof A>( + signal: S, + name: N, + ...params: A[N] extends (...args: infer P)=> any ? P : never + ): void; + clear(...signals: Signal[]): void; + on(signal: Signal, onchange: (a: T)=> void, options?: OnListenerOptions): void; + symbols: { + //signal: SymbolSignal; + onclear: SymbolOnclear; + } + /** + * Reactive element, which is rendered based on the given signal. + * ```js + * S.el(signal, value=> value ? el("b", "True") : el("i", "False")); + * S.el(listS, list=> list.map(li=> el("li", li))); + * ``` + * */ + el(signal: Signal, el: (v: S)=> Element | Element[] | DocumentFragment): DocumentFragment; + + observedAttributes(custom_element: HTMLElement): Record>; +} +export const signal: signal; +export const S: signal; +declare global { + type ddeSignal= Signal; + type ddeAction= Action + type ddeActions= Actions +} \ No newline at end of file diff --git a/dist/esm.d.ts b/dist/esm.d.ts index 672e3fa..3cc6e74 100644 --- a/dist/esm.d.ts +++ b/dist/esm.d.ts @@ -1,69 +1,4 @@ -export type Signal= (set?: V)=> V & A; -type Action= (this: { value: V, stopPropagation(): void }, ...a: any[])=> typeof signal._ | void; -//type SymbolSignal= Symbol; -type SymbolOnclear= symbol; -type Actions= Record>; -type OnListenerOptions= Pick & { first_time?: boolean }; -interface signal{ - _: Symbol - /** - * Simple example: - * ```js - * const hello= S("Hello Signal"); - * ``` - * …simple todo signal: - * ```js - * const todos= S([], { - * add(v){ this.value.push(S(v)); }, - * remove(i){ this.value.splice(i, 1); }, - * [S.symbols.onclear](){ S.clear(...this.value); }, - * }); - * ``` - * …computed signal: - * ```js - * const name= S("Jan"); - * const surname= S("Andrle"); - * const fullname= S(()=> name()+" "+surname()); - * ``` - * @param value Initial signal value. Or function computing value from other signals. - * @param actions Use to define actions on the signal. Such as add item to the array. - * There is also a reserved function `S.symbol.onclear` which is called when the signal is cleared - * by `S.clear`. - * */ - >(value: V, actions?: A): Signal; - /** - * Computations signal. This creates a signal which is computed from other signals. - * */ - (computation: ()=> V): Signal - action>, A extends (S extends Signal ? A : never), N extends keyof A>( - signal: S, - name: N, - ...params: A[N] extends (...args: infer P)=> any ? P : never - ): void; - clear(...signals: Signal[]): void; - on(signal: Signal, onchange: (a: T)=> void, options?: OnListenerOptions): void; - symbols: { - //signal: SymbolSignal; - onclear: SymbolOnclear; - } - /** - * Reactive element, which is rendered based on the given signal. - * ```js - * S.el(signal, value=> value ? el("b", "True") : el("i", "False")); - * S.el(listS, list=> list.map(li=> el("li", li))); - * ``` - * */ - el(signal: Signal, el: (v: S)=> Element | Element[] | DocumentFragment): DocumentFragment; - - observedAttributes(custom_element: HTMLElement): Record>; -} -export const signal: signal; -export const S: signal; -declare global { - type ddeSignal= Signal; - type ddeAction= Action - type ddeActions= Actions -} +declare global{ /* ddeSignal */ } type CustomElementTagNameMap= { '#text': Text, '#comment': Comment } type SupportedElement= HTMLElementTagNameMap[keyof HTMLElementTagNameMap] @@ -80,20 +15,20 @@ type AttrsModified= { /** * Use string like in HTML (internally uses `*.setAttribute("style", *)`), or object representation (like DOM API). */ - style: string | Partial | Signal | Partial<{ [K in keyof CSSStyleDeclaration]: Signal }> + style: string | Partial | ddeSignal | Partial<{ [K in keyof CSSStyleDeclaration]: ddeSignal }> /** * Provide option to add/remove/toggle CSS clasess (index of object) using 1/0/-1. In fact `el.classList.toggle(class_name)` for `-1` and `el.classList.toggle(class_name, Boolean(...))` for others. */ - classList: Record>, + classList: Record>, /** * By default simiral to `className`, but also supports `string[]` * */ - className: string | (string|boolean|undefined|Signal)[]; + className: string | (string|boolean|undefined|ddeSignal)[]; /** * Sets `aria-*` simiraly to `dataset` * */ - ariaset: Record>, -} & Record<`=${string}` | `data${PascalCase}` | `aria${PascalCase}`, string|Signal> & Record<`.${string}`, any> + ariaset: Record>, +} & Record<`=${string}` | `data${PascalCase}` | `aria${PascalCase}`, string|ddeSignal> & Record<`.${string}`, any> type _fromElsInterfaces= Omit; /** * Just element attributtes @@ -103,15 +38,15 @@ type _fromElsInterfaces= Omit= Partial<_fromElsInterfaces & { [K in keyof _fromElsInterfaces]: Signal<_fromElsInterfaces[K], any> } & AttrsModified> & Record; +type ElementAttributes= Partial<{ [K in keyof _fromElsInterfaces]: _fromElsInterfaces[K] | ddeSignal<_fromElsInterfaces[K]> } & AttrsModified> & Record; export function classListDeclarative(element: El, classList: AttrsModified["classList"]): El export function assign(element: El, ...attrs_array: ElementAttributes[]): El export function assignAttribute>(element: El, attr: ATT, value: ElementAttributes[ATT]): ElementAttributes[ATT] type ExtendedHTMLElementTagNameMap= HTMLElementTagNameMap & CustomElementTagNameMap; -type textContent= string | ( (set?: string)=> string ); // TODO: for some reason `Signal` leads to `attrs?: any` +type textContent= string | ddeSignal; export function el< - TAG extends keyof ExtendedHTMLElementTagNameMap & string, + TAG extends keyof ExtendedHTMLElementTagNameMap, EL extends (TAG extends keyof ExtendedHTMLElementTagNameMap ? ExtendedHTMLElementTagNameMap[TAG] : HTMLElement) >( tag_name: TAG, @@ -121,6 +56,11 @@ export function el< export function el( tag_name?: "<>", ): ddeDocumentFragment +export function el( + tag_name: string, + attrs?: ElementAttributes, + ...addons: ddeElementAddon[] +): ddeHTMLElement export function el< C extends (attr: ddeComponentAttributes)=> SupportedElement | ddeDocumentFragment @@ -148,7 +88,7 @@ export function elNS( EL extends ( TAG extends keyof MathMLElementTagNameMap ? MathMLElementTagNameMap[TAG] : MathMLElement ), >( tag_name: TAG, - attrs?: string | textContent | Partial<{ [key in keyof EL]: EL[key] | Signal | string | number | boolean }>, + attrs?: string | textContent | Partial<{ [key in keyof EL]: EL[key] | ddeSignal | string | number | boolean }>, ...addons: ddeElementAddon[] )=> ddeMathMLElement export function elNS( @@ -189,7 +129,7 @@ interface On{ EE extends ddeElementAddon, El extends ( EE extends ddeElementAddon ? El : never ) >( - listener: (this: El, event: CustomEvent) => any, + listener: (this: El, event: CustomEvent) => any, options?: AddEventListenerOptions ) : EE; /** 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 */ @@ -224,7 +164,7 @@ export const scope: { * It can be also used to register Addon(s) (functions to be called when component is initized) * — `scope.host(on.connected(console.log))`. * */ - host: (...addons: ddeElementAddon[])=> HTMLElement, + host: (...addons: ddeElementAddon[])=> HTMLElement, state: Scope[], /** Adds new child scope. All attributes are inherited by default. */ @@ -237,15 +177,15 @@ export const scope: { export function customElementRender< EL extends HTMLElement, - P extends any = Record + P extends any = Record> >( custom_element: EL, target: ShadowRoot | EL, - render: (props: P)=> SupportedElement, - props?: P | ((...args: any[])=> P) + render: (props: P)=> SupportedElement | DocumentFragment, + props?: P | ((el: EL)=> P) ): EL -export function customElementWithDDE(custom_element: EL): EL -export function lifecyclesToEvents(custom_element: EL): EL +export function customElementWithDDE HTMLElement)>(custom_element: EL): EL +export function lifecyclesToEvents HTMLElement)>(custom_element: EL): EL export function observedAttributes(custom_element: HTMLElement): Record /* TypeScript MEH */ diff --git a/docs/p06-customElement.html b/docs/p06-customElement.html index 9b15916..7235e83 100644 --- a/docs/p06-customElement.html +++ b/docs/p06-customElement.html @@ -87,4 +87,46 @@ customElements.define(HTMLCustomElement.tagName, HTMLCustomElement); document.body.append( el(HTMLCustomElement.tagName, { attr: "Attribute" }) ); -

…as you can see, you can use components created based on the documentation previously introduced. To unlock full potential, use with combination customElementWithDDE (allows to use livecycle events) and observedAttributes (converts attributes to render function arguments — default) or S.observedAttributes (converts attributes to signals).

# Mnemonic

  • customElementRender(<custom-element>, <connect-target>, <render-function>[, <properties>]) — use function to render DOM structure for given <custom-element>
  • customElementWithDDE(<custom-element>) — register <custom-element> to DDE library, see also `lifecyclesToEvents`, can be also used as decorator
  • observedAttributes(<custom-element>) — returns record of observed attributes (keys uses camelCase)
  • S.observedAttributes(<custom-element>) — returns record of observed attributes (keys uses camelCase and values are signals)
  • lifecyclesToEvents(<class-declaration>) — convert lifecycle methods to events, can be also used as decorator
\ No newline at end of file +

…as you can see, you can use components created based on the documentation previously introduced. To unlock full potential, use with combination customElementWithDDE (allows to use livecycle events) and observedAttributes (converts attributes to render function arguments — default) or S.observedAttributes (converts attributes to signals).

import { + customElementRender, + customElementWithDDE, + observedAttributes, + el, on, scope +} from "./esm-with-signals.js"; +import { S } from "./esm-with-signals.js"; +export class HTMLCustomElement extends HTMLElement{ + static tagName= "custom-element"; + static observedAttributes= [ "attr" ]; + connectedCallback(){ + console.log(observedAttributes(this)); + customElementRender( + this, + this.attachShadow({ mode: "open" }), + ddeComponent, + S.observedAttributes + ); + } + set attr(value){ this.setAttribute("attr", value); } + get attr(){ return this.getAttribute("attr"); } +} + +/** @param {{ attr: ddeSignal<string, {}> }} props */ +function ddeComponent({ attr }){ + scope.host( + on.connected(e=> console.log(e.target.outerHTML)), + ); + return el().append( + el("p", S(()=> `Hello from Custom Element with attribute '${attr()}'`)) + ); +} +customElementWithDDE(HTMLCustomElement); +customElements.define(HTMLCustomElement.tagName, HTMLCustomElement); + +document.body.append( + el(HTMLCustomElement.tagName, { attr: "Attribute" }) +); +setTimeout( + ()=> document.querySelector(HTMLCustomElement.tagName).setAttribute("attr", "New Value"), + 3*750 +); +

# Mnemonic

  • customElementRender(<custom-element>, <connect-target>, <render-function>[, <properties>]) — use function to render DOM structure for given <custom-element>
  • customElementWithDDE(<custom-element>) — register <custom-element> to DDE library, see also `lifecyclesToEvents`, can be also used as decorator
  • observedAttributes(<custom-element>) — returns record of observed attributes (keys uses camelCase)
  • S.observedAttributes(<custom-element>) — returns record of observed attributes (keys uses camelCase and values are signals)
  • lifecyclesToEvents(<class-declaration>) — convert lifecycle methods to events, can be also used as decorator
\ No newline at end of file diff --git a/docs_src/components/examples/customElement/observedAttributes.js b/docs_src/components/examples/customElement/observedAttributes.js new file mode 100644 index 0000000..2b76280 --- /dev/null +++ b/docs_src/components/examples/customElement/observedAttributes.js @@ -0,0 +1,42 @@ +import { + customElementRender, + customElementWithDDE, + observedAttributes, + el, on, scope +} from "deka-dom-el"; +import { S } from "deka-dom-el/signals"; +export class HTMLCustomElement extends HTMLElement{ + static tagName= "custom-element"; + static observedAttributes= [ "attr" ]; + connectedCallback(){ + console.log(observedAttributes(this)); + customElementRender( + this, + this.attachShadow({ mode: "open" }), + ddeComponent, + S.observedAttributes + ); + } + set attr(value){ this.setAttribute("attr", value); } + get attr(){ return this.getAttribute("attr"); } +} + +/** @param {{ attr: ddeSignal }} props */ +function ddeComponent({ attr }){ + scope.host( + on.connected(e=> console.log(e.target.outerHTML)), + ); + return el().append( + el("p", S(()=> `Hello from Custom Element with attribute '${attr()}'`)) + ); +} +customElementWithDDE(HTMLCustomElement); +customElements.define(HTMLCustomElement.tagName, HTMLCustomElement); + +document.body.append( + el(HTMLCustomElement.tagName, { attr: "Attribute" }) +); +setTimeout( + ()=> document.querySelector(HTMLCustomElement.tagName).setAttribute("attr", "New Value"), + 3*750 +); diff --git a/docs_src/p06-customElement.html.js b/docs_src/p06-customElement.html.js index c6caadd..0e0d830 100644 --- a/docs_src/p06-customElement.html.js +++ b/docs_src/p06-customElement.html.js @@ -81,6 +81,7 @@ export function page({ pkg, info }){ and ${el("code", "observedAttributes")} (converts attributes to render function arguments — ${el("em", "default")}) or ${el("code", "S.observedAttributes")} (converts attributes to signals). `), + el(example, { src: fileURL("./components/examples/customElement/observedAttributes.js"), page_id }), el(mnemonic) diff --git a/index.d.ts b/index.d.ts index af12b5e..60c07db 100644 --- a/index.d.ts +++ b/index.d.ts @@ -177,12 +177,12 @@ export const scope: { export function customElementRender< EL extends HTMLElement, - P extends any = Record + P extends any = Record> >( custom_element: EL, target: ShadowRoot | EL, render: (props: P)=> SupportedElement | DocumentFragment, - props?: P | ((...args: any[])=> P) + props?: P | ((el: EL)=> P) ): EL export function customElementWithDDE HTMLElement)>(custom_element: EL): EL export function lifecyclesToEvents HTMLElement)>(custom_element: EL): EL diff --git a/signals.d.ts b/signals.d.ts index 517f83b..ebae316 100644 --- a/signals.d.ts +++ b/signals.d.ts @@ -55,7 +55,7 @@ interface signal{ * */ el(signal: Signal, el: (v: S)=> Element | Element[] | DocumentFragment): DocumentFragment; - observedAttributes(custom_element: HTMLElement): Record>; + observedAttributes(custom_element: HTMLElement): Record>; } export const signal: signal; export const S: signal;