Skip to content

Commit

Permalink
improve types
Browse files Browse the repository at this point in the history
  • Loading branch information
benStre committed Sep 18, 2024
1 parent 156f29e commit d349f38
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 48 deletions.
39 changes: 37 additions & 2 deletions attributes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,19 @@ export type validHTMLElementSpecificAttrs<TAG extends string> = TAG extends keyo
[key in (typeof htmlElementAttributes)[TAG][number]]: TAG extends keyof htmlElementAttributeValues ? (key extends keyof htmlElementAttributeValues[TAG] ? htmlElementAttributeValues[TAG][key] : string) : string
} : unknown;

// export type validHTMLElementSpecificAttrs<TAG extends string> = TAG extends keyof typeof htmlElementAttributes ? (
// (TAG extends keyof htmlElementAttributeValues ? htmlElementAttributeValues[TAG] : unknown ) & {
// [key in (typeof htmlElementAttributes)[TAG][number]]: TAG extends keyof htmlElementAttributeValues ? (key extends keyof htmlElementAttributeValues[TAG] ? unknown : string) : string
// }
// ) : Record<string,never>;


export type validSVGElementSpecificAttrs<TAG extends string> = TAG extends keyof typeof svgElementAttributes ? {
[key in (typeof svgElementAttributes)[TAG][number]]: TAG extends keyof svgElementAttributeValues ? (key extends keyof svgElementAttributeValues[TAG] ? svgElementAttributeValues[TAG][key] : string) : string
} : unknown;


type a = validHTMLElementSpecificAttrs<"input">;

/** attribute definitions used by multiple elements */

Expand Down Expand Up @@ -144,7 +152,7 @@ export type htmlElementAttributeValues = {

link: href & rel,

input: widthAndHeight & src & {
input: (widthAndHeight & src & {
autocomplete: "on"|"off"
autofocus: boolean
checked: boolean
Expand All @@ -167,7 +175,34 @@ export type htmlElementAttributeValues = {
value: primitive|Date,
"value:out": Datex.Pointer,
"value:in": Datex.Pointer
},
})
// TODO: conditional attributes
// & (
// {
// "type": "text",
// value: primitive
// } |
// {
// "type": "number"|"range",
// value: number
// } |
// {
// "type": "date"|"time"|"datetime-local"|"month"|"week",
// value: number|string|Date
// } |
// {
// "type": "color",
// value: string
// } |
// {
// "type": "file",
// value: File
// } |
// {
// "type": "checkbox"|"radio",
// value: boolean
// }
// )

select: {
required: boolean,
Expand Down
103 changes: 64 additions & 39 deletions datex-bindings/dom-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ export type appendableContent = appendableContentBase|Promise<appendableContentB
export namespace DOMUtils {
export type elWithUIXAttributes = Element & {
[DOMUtils.EVENT_LISTENERS]:Map<keyof HTMLElementEventMap, Set<[(...args:any)=>any, boolean]>>
[DOMUtils.ATTR_BINDINGS]:Map<string, Datex.Ref>,
[DOMUtils.ATTR_DX_VALUES]:Map<string, Datex.Ref>,
[DOMUtils.STYLE_DX_VALUES]:Map<string, Datex.Ref<string>>,
[DOMUtils.ATTR_BINDINGS]:Map<string, Datex.ReactiveValue>,
[DOMUtils.ATTR_DX_VALUES]:Map<string, Datex.ReactiveValue>,
[DOMUtils.STYLE_DX_VALUES]:Map<string, Datex.ReactiveValue<string>>,
[DOMUtils.STYLE_WEAK_PROPS]:Map<string, boolean>,
[DOMUtils.CHILDREN_DX_VALUES]:Set<Datex.Ref>,
[DOMUtils.CHILDREN_DX_VALUES]:Set<Datex.ReactiveValue>,
[DOMUtils.DATEX_UPDATE_TYPE]?: string
}
}
Expand Down Expand Up @@ -63,11 +63,11 @@ export class DOMUtils {
const element = <HTMLElement>template.content.firstChild;
if (content != undefined) {
// set html
if (Datex.Ref.collapseValue(content,true,true) instanceof this.context.HTMLElement) this.setElementHTML(element, <HTMLElement>content);
if (Datex.ReactiveValue.collapseValue(content,true,true) instanceof this.context.HTMLElement) this.setElementHTML(element, <HTMLElement>content);
// set child nodes
if (content instanceof Array) {
for (const el of content){
if (Datex.Ref.collapseValue(el,true,true) instanceof this.context.HTMLElement) element.append(Datex.Ref.collapseValue(el,true,true))
if (Datex.ReactiveValue.collapseValue(el,true,true) instanceof this.context.HTMLElement) element.append(Datex.ReactiveValue.collapseValue(el,true,true))
else {
const container = this.document.createElement("div");
this.setElementText(container, el);
Expand All @@ -94,7 +94,7 @@ export class DOMUtils {
}

private updateElementText = function (this:HTMLElement, text:unknown){
if (this instanceof Datex.Ref) console.warn("update text invalid", this, text)
if (this instanceof Datex.ReactiveValue) console.warn("update text invalid", this, text)

if (text instanceof Datex.Markdown) {
this.innerHTML = (text.getHTML() as HTMLElement).children[0].innerHTML;
Expand All @@ -115,7 +115,7 @@ export class DOMUtils {
if (html == undefined || html === false) element.innerHTML = '';

// DatexValue
if (html instanceof Datex.Ref) {
if (html instanceof Datex.ReactiveValue) {
this.updateElementHTML.call(element, html.val);

// @ts-ignore: TODO: fix?
Expand All @@ -140,7 +140,7 @@ export class DOMUtils {
if (text == undefined || text === false) element.innerText = '';

// Datexv Ref
else if (text instanceof Datex.Ref) {
else if (text instanceof Datex.ReactiveValue) {
this.updateElementText.call(element, text.val);

text.observe(this.updateElementText, element);
Expand Down Expand Up @@ -172,14 +172,14 @@ export class DOMUtils {
if (children instanceof this.context.DocumentFragment && children._uix_children) children = children._uix_children

// is ref and iterable/element
if (!(parent instanceof this.context.DocumentFragment) && Datex.Ref.isRef(children) && (children instanceof Array || children instanceof Map || children instanceof Set)) {
if (!(parent instanceof this.context.DocumentFragment) && Datex.ReactiveValue.isRef(children) && (children instanceof Array || children instanceof Map || children instanceof Set)) {
// is iterable ref
// TODO: support promises

const ref:Datex.Ref = children instanceof Datex.Ref ? children : Datex.Pointer.getByValue(children)!;
const ref:Datex.ReactiveValue = children instanceof Datex.ReactiveValue ? children : Datex.Pointer.getByValue(children)!;

if (!(<DOMUtils.elWithUIXAttributes><unknown>parent)[DOMUtils.CHILDREN_DX_VALUES])
(<DOMUtils.elWithUIXAttributes><unknown>parent)[DOMUtils.CHILDREN_DX_VALUES] = new Set<Datex.Ref>();
(<DOMUtils.elWithUIXAttributes><unknown>parent)[DOMUtils.CHILDREN_DX_VALUES] = new Set<Datex.ReactiveValue>();
(<DOMUtils.elWithUIXAttributes><unknown>parent)[DOMUtils.CHILDREN_DX_VALUES].add(ref)

const startAnchor = new this.context.Comment("start " + Datex.Pointer.getByValue(children)?.idString())
Expand Down Expand Up @@ -230,7 +230,7 @@ export class DOMUtils {
// const scheduler = new TaskScheduler(true);
// let lastChildren: Node[] = [];

// Datex.Ref.observeAndInit(children, () => {
// Datex.ReactiveValue.observeAndInit(children, () => {
// scheduler.schedule(
// Task(resolve => {
// appendNew(parent, Array.isArray(children) ? children : [children], lastChildren, (e) => {
Expand All @@ -244,7 +244,7 @@ export class DOMUtils {
// null,
// {
// recursive: false,
// types: [Datex.Ref.UPDATE_TYPE.INIT]
// types: [Datex.ReactiveValue.UPDATE_TYPE.INIT]
// }
// )
// }
Expand Down Expand Up @@ -318,7 +318,7 @@ export class DOMUtils {
}

// Datex Ref
else if (value instanceof Datex.Ref) {
else if (value instanceof Datex.ReactiveValue) {
return this.setLiveAttribute(element, attr, value, rootPath);
}

Expand All @@ -335,12 +335,12 @@ export class DOMUtils {
// bind value (used for datex-over-http updates)
if (attr == "value" || attr == "checked") {
if (!(<DOMUtils.elWithUIXAttributes><unknown>element)[DOMUtils.ATTR_BINDINGS])
(<DOMUtils.elWithUIXAttributes><unknown>element)[DOMUtils.ATTR_BINDINGS] = new Map<string, Datex.Ref>();
(<DOMUtils.elWithUIXAttributes><unknown>element)[DOMUtils.ATTR_BINDINGS] = new Map<string, Datex.ReactiveValue>();
(<DOMUtils.elWithUIXAttributes><unknown>element)[DOMUtils.ATTR_BINDINGS].set(attr, value)
}
else {
if (!(<DOMUtils.elWithUIXAttributes><unknown>element)[DOMUtils.ATTR_DX_VALUES])
(<DOMUtils.elWithUIXAttributes><unknown>element)[DOMUtils.ATTR_DX_VALUES] = new Map<string, Datex.Ref>();
(<DOMUtils.elWithUIXAttributes><unknown>element)[DOMUtils.ATTR_DX_VALUES] = new Map<string, Datex.ReactiveValue>();
(<DOMUtils.elWithUIXAttributes><unknown>element)[DOMUtils.ATTR_DX_VALUES].set(attr, value)
}

Expand All @@ -351,7 +351,7 @@ export class DOMUtils {

const handleSetVal = async (val:any) => {
try {
await (value as Datex.Ref).setVal(val)
await (value as Datex.ReactiveValue).setVal(val)
element.setCustomValidity("")
element.reportValidity()
}
Expand Down Expand Up @@ -422,14 +422,15 @@ export class DOMUtils {
// default attributes

const valid = this.setAttribute(element, attr, value.val, rootPath)
if (valid) {
// observe pointer value (TODO: this observe currently only works if value is a primitive pointer, otherwise only internal updates are reflected reactively, e.g. for style objects or arrays - this is handled in setAttribute)
if (value.is_js_primitive && valid) {
const val = value;

weakAction({element},
({element}) => {
use (this, attr, rootPath, logger, val, isolatedScope);

const handler = isolatedScope((v:any) => {
const handler = isolatedScope((v:any,...args) => {
use (this, logger, rootPath, element, attr);
const deref = element.deref();
if (!deref) {
Expand All @@ -454,6 +455,8 @@ export class DOMUtils {

private setAttribute(element: Element, attr:string, val:unknown, root_path?:string|URL): boolean {

if (element.tagName=="INPUT" && attr=="class") console.warn("setattr",attr,val)

// special suffixes:

// non-module-relative paths if :route suffix
Expand Down Expand Up @@ -549,10 +552,10 @@ export class DOMUtils {
if (val) deref.showModal();
else deref.close()
});
Datex.Ref.observeAndInit(theVal, handler);
Datex.ReactiveValue.observeAndInit(theVal, handler);
return handler;
},
(handler) => use(Datex, theVal) && Datex.Ref.unobserve(theVal, handler)
(handler) => use(Datex, theVal) && Datex.ReactiveValue.unobserve(theVal, handler)
);

}
Expand All @@ -564,27 +567,49 @@ export class DOMUtils {

// class mapping
else if (attr == "class" && typeof val == "object") {

const update = (key:string, val:Datex.RefOrValue<boolean>) => {
if (Datex.Ref.collapseValue(val, true, true)) element.classList.add(key);
if (Datex.ReactiveValue.collapseValue(val, true, true)) element.classList.add(key);
else element.classList.remove(key);
}
const updateAll = (obj: Record<string, Datex.RefOrValue<boolean>>) => {
for (const [key, val] of Object.entries(obj)) update(key, val);
}

// handle array updates
let previousArray = [] as Array<string>;
const updateArray = (arr: Array<string>) => {
if (previousArray) {
for (const key of previousArray) {
if (!arr.includes(key)) {
console.log("remove", key,previousArray)
element.classList.remove(key);
}
}
}
element.classList.add(...arr);
previousArray = [...arr];
}


if (Datex.Ref.isRef(val)) {
Datex.Ref.observe(val, (v, k, t) => {
console.log(">>",v,k,t)
if (t == Datex.Pointer.UPDATE_TYPE.INIT) updateAll(v);
else if (typeof k == "string") update(k, v);
if (Datex.ReactiveValue.isRef(val)) {
Datex.ReactiveValue.observeAndInit(val, (v, k, t) => {
// update class list from array
if (val instanceof Array) updateArray(val);
// update class list from object
else {
if (t == Datex.Pointer.UPDATE_TYPE.INIT) updateAll(v);
else if (typeof k == "string") update(k, v);
}
})
}
// simple object with pointers as properties
else {
updateAll(val);
for (const [key, value] of Object.entries(val)) {
if (value instanceof Datex.Ref) value.observe(v => update(key, v));
if (val instanceof Array) updateArray(val);
else {
updateAll(val);
for (const [key, value] of Object.entries(val)) {
if (value instanceof Datex.ReactiveValue) value.observe(v => update(key, v));
}
}
}

Expand Down Expand Up @@ -690,7 +715,7 @@ export class DOMUtils {
this.setElementAttribute(element, "style", properties_object_or_property)
return element;
}
else properties = Datex.Ref.collapseValue(properties_object_or_property,true,true) as {[property:string]:Datex.CompatValue<string|number|undefined>};
else properties = Datex.ReactiveValue.collapseValue(properties_object_or_property,true,true) as {[property:string]:Datex.CompatValue<string|number|undefined>};

if (properties) {
for (const [property, value] of Object.entries(properties)) {
Expand Down Expand Up @@ -734,13 +759,13 @@ export class DOMUtils {
else {

// remember style ref binding
if (!weakBinding && Datex.Ref.isRef(value)) {
if (!weakBinding && Datex.ReactiveValue.isRef(value)) {
if (!(<DOMUtils.elWithUIXAttributes><unknown>element)[DOMUtils.STYLE_DX_VALUES])
(<DOMUtils.elWithUIXAttributes><unknown>element)[DOMUtils.STYLE_DX_VALUES] = new Map<string, Datex.Ref>();
(<DOMUtils.elWithUIXAttributes><unknown>element)[DOMUtils.STYLE_DX_VALUES] = new Map<string, Datex.ReactiveValue>();
(<DOMUtils.elWithUIXAttributes><unknown>element)[DOMUtils.STYLE_DX_VALUES].set(property, value)
}

Datex.Ref.observeAndInit(value, (v,k,t) => {
Datex.ReactiveValue.observeAndInit(value, (v,k,t) => {
if (property == "display" && typeof v == "boolean") {
v = v ? (globalThis.CSS?.supports("display: revert-layer") ? "revert-layer" : "revert") : "none";
}
Expand Down Expand Up @@ -905,7 +930,7 @@ export class DOMUtils {
})
}
// ref
else if (content instanceof Datex.Ref) {
else if (content instanceof Datex.ReactiveValue) {
this.bindTextNode(textNode, content)
}

Expand Down Expand Up @@ -944,12 +969,12 @@ export class DOMUtils {
}
});

Datex.Ref.observeAndInit(ref.deref(), handler);
Datex.ReactiveValue.observeAndInit(ref.deref(), handler);
return handler;
},
(handler, _, deps) => {
use(Datex);
Datex.Ref.unobserve(deps.ref, handler)
Datex.ReactiveValue.unobserve(deps.ref, handler)
},
{ref}
);
Expand Down
11 changes: 8 additions & 3 deletions jsx/jsx-definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ import type { validHTMLElementAttrs, validHTMLElementSpecificAttrs, validSVGElem
type DomElement = Element;// HTMLElement // TODO: Element?


type x = {[key: string]: Datex.RefOrValue<boolean>} & {$: any, $$: any}
type y = x['$'];

type RefOrValueUnion<U> = (U extends any ? Datex.RefOrValue<U> : never)


declare global {
namespace JSX {
// JSX node definition
Expand All @@ -32,13 +36,14 @@ declare global {
// enable as workaround to allow {...[elements]} type checking to work correctly
// type childrenOrChildrenPromise = _childrenOrChildrenPromise|_childrenOrChildrenPromise[]

// TODO: fix htmlAttrs to support correct constraint validation for conditional attributes like input type/value
type htmlAttrs<T extends Record<string,unknown>, allowPromises extends boolean = false> = Partial<DatexValueObject<Omit<T, 'children'|'style'|'class'>, allowPromises>>

// Common attributes of the standard HTML elements and JSX components
// using _IntrinsicAttributes (not IntrinsicAttributes) to prevent jsx default type behaviour
type _IntrinsicAttributes<El extends HTMLElement = HTMLElement> = {
style?: Datex.RefOrValue<string|{[key: string]: Datex.RefOrValue<string>|Datex.RefOrValue<number>|Datex.RefOrValue<boolean>}>,
class?: Datex.RefOrValue<string|{[key: string]: Datex.RefOrValue<boolean>}>,
style?: Datex.RefOrValue<string> | Datex.ObjectRef<{[key: string]: string|number|boolean}> | {[key: string]: Datex.RefOrValue<string>|Datex.RefOrValue<number>|Datex.RefOrValue<boolean>},
class?: Datex.RefOrValue<string> | Datex.ObjectRef<{[key: string]: boolean}> | {[key: string]: Datex.RefOrValue<boolean>} | Datex.RefOrValue<string[]>,
} & htmlAttrs<validHTMLElementAttrs<El>>

// TODO: enable for UIX - Common attributes of the UIX components only
Expand All @@ -48,7 +53,7 @@ declare global {
[key in keyof T]: T[key] extends (...args:unknown[])=>unknown ?
T[key] :
(T[key] extends boolean ?
Datex.RefOrValue<T[key]> :
Datex.RefOrValue<T[key]>:
(undefined extends T[key] ?
Datex.RefOrValue<T[key]> :
RefOrValueUnion<T[key]>
Expand Down
Loading

0 comments on commit d349f38

Please sign in to comment.