Skip to content

Commit

Permalink
feat(rdom): add DOM comment support (#367), other refactorings
Browse files Browse the repository at this point in the history
- add $comment(), isComment()
- add Component.$comment() syntax sugar
- add comment check/branch in $tree()
- update args for $addChild(), $remove(), $moveTo()
- update $text(), $html() to support SVG elements
- add doc strings
  • Loading branch information
postspectacular committed Nov 30, 2022
1 parent fe952d5 commit 3fd5f8e
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 19 deletions.
18 changes: 18 additions & 0 deletions packages/rdom/src/checks.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
import { implementsFunction } from "@thi.ng/checks/implements-function";
import { COMMENT } from "@thi.ng/hiccup/api";
import type { IComponent } from "./api.js";

/**
* Returns true if given hiccup component describes a comment node. I.e. is of
* the form `[COMMENT, "foo"...]`.
*
* @remarks
* See thi.ng/hiccup docs for reference:
* - https://docs.thi.ng/umbrella/hiccup/functions/serialize.html
*
* @param tree
*/
export const isComment = (tree: any[]) => tree[0] === COMMENT;

/**
* Returns true, if given value has a {@link IComponent.mount} function.
*
* @param x
*/
export const isComponent = (x: any): x is IComponent<any> =>
implementsFunction(x, "mount");
102 changes: 98 additions & 4 deletions packages/rdom/src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { $compile } from "./compile.js";
import {
$attribs,
$clear,
$comment,
$el,
$html,
$moveTo,
Expand Down Expand Up @@ -35,6 +36,16 @@ export abstract class Component<T = any> implements IComponent<T> {
// @ts-ignore args
update(state?: T) {}

/**
* Syntax sugar for {@link $el}, using this component's
* {@link IComponent.el} as default `parent`.
*
* @param tag
* @param attribs
* @param body
* @param parent
* @param idx
*/
$el(
tag: string,
attribs?: any,
Expand All @@ -45,38 +56,121 @@ export abstract class Component<T = any> implements IComponent<T> {
return $el(tag, attribs, body, parent, idx);
}

/**
* Syntax sugar for {@link $comment}, creates a new comment DOM node using
* this component's {@link IComponent.el} as default `parent`.
*
* @param body
* @param parent
* @param idx
*/
$comment(body: string | string[], parent = this.el, idx?: NumOrElement) {
return $comment(body, parent, idx);
}

/**
* Syntax sugar for {@link $clear}, using this component's
* {@link IComponent.el} as default element to clear.
*
* @param el
*/
$clear(el = this.el!) {
return $clear(el);
}

/**
* Same as {@link $compile}.
*
* @param tree
*/
$compile(tree: any) {
return $compile(tree);
}

/**
* Same as {@link $tree}.
*
* @param tree
* @param root
* @param index
*/
$tree(tree: any, root = this.el!, index?: NumOrElement) {
return $tree(tree, root, index);
}

$text(body: any) {
this.el && $text(<any>this.el, body);
/**
* Syntax sugar for {@link $text}, using this component's
* {@link IComponent.el} as default element to edit.
*
* @remarks
* If using the default element, assumes `this.el` is an existing
* `HTMLElement`.
*
* @param body
* @param el
*/
$text(body: any, el: HTMLElement = <HTMLElement>this.el!) {
$text(el, body);
}

$html(body: MaybeDeref<string>) {
this.el && $html(<any>this.el, body);
/**
* Syntax sugar for {@link $html}, using this component's
* {@link IComponent.el} as default element to edit.
*
* @remarks
* If using the default element, assumes `this.el` is an existing
* `HTMLElement` or `SVGElement`.
*
* @param body
* @param el
*/
$html(
body: MaybeDeref<string>,
el: HTMLElement | SVGElement = <HTMLElement>this.el!
) {
$html(el, body);
}

/**
* Syntax sugar for {@link $attribs}, using this component's
* {@link IComponent.el} as default element to edit.
*
* @param attribs
* @param el
*/
$attribs(attribs: any, el = this.el!) {
$attribs(el, attribs);
}

/**
* Syntax sugar for {@link $style}, using this component's
* {@link IComponent.el} as default element to edit.
*
* @param rules
* @param el
*/
$style(rules: any, el = this.el!) {
$style(el, rules);
}

/**
* Syntax sugar for {@link $remove}, using this component's
* {@link IComponent.el} as default element to remove.
*
* @param el
*/
$remove(el = this.el!) {
$remove(el);
}

/**
* Syntax sugar for {@link $moveTo}, using this component's
* {@link IComponent.el} as default element to migrate.
*
* @param newParent
* @param el
* @param idx
*/
$moveTo(newParent: Element, el = this.el!, idx?: NumOrElement) {
$moveTo(newParent, el, idx);
}
Expand Down
94 changes: 79 additions & 15 deletions packages/rdom/src/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,29 @@ import { mergeClasses, mergeEmmetAttribs } from "@thi.ng/hiccup/attribs";
import { formatPrefixes } from "@thi.ng/hiccup/prefix";
import { XML_SVG, XML_XLINK, XML_XMLNS } from "@thi.ng/prefixes/xml";
import type { NumOrElement } from "./api.js";
import { isComponent } from "./checks.js";
import { isComment, isComponent } from "./checks.js";

/**
* hdom-style DOM tree creation from hiccup format. Returns DOM element
* of `tree` root. See {@link $el} for further details.
* hdom-style DOM tree creation from hiccup format. Returns DOM element of
* `tree` root. See {@link $el} for further details.
*
* @remarks
* Supports elements given in these forms:
*
* - {@link IComponent} instance
* - {@link IDeref} instance (must resolve to another supported type in
* this list)
* - {@link IDeref} instance (must resolve to another supported type in this
* list)
* - `["div#id.class", {...attribs}, ...children]`
* - `[COMMENT, "foo", "bar"...]` (DOM comment node)
* - `[IComponent, ...mountargs]`
* - `[function, ...args]`
* - ES6 iterable of the above (for child values only!)
*
* Any other values will be cast to strings and added as spans to
* current `parent`.
* Any other values will be cast to strings and added as spans to current
* `parent`.
*
* Note: `COMMENT` is defined as constant in thi.ng/hiccup package. Also see
* {@link $comment} function to create comments directly.
*
* @param tree -
* @param parent -
Expand All @@ -48,7 +52,9 @@ export const $tree = async (
idx: NumOrElement = -1
): Promise<any> =>
isArray(tree)
? $treeElem(tree, parent, idx)
? isComment(tree)
? $comment(tree.slice(1), parent, idx)
: $treeElem(tree, parent, idx)
: isComponent(tree)
? tree.mount(parent, idx)
: isDeref(tree)
Expand Down Expand Up @@ -144,9 +150,47 @@ export const $el = (
return el;
};

/**
* Similar to {@link $el}, but creates a new comment DOM node using provided
* body. If `parent` is given, the comment will be attached or inserted as child
* at `idx`. Returns comment node.
*
* @remarks
* See thi.ng/hiccup docs for reference:
* - https://docs.thi.ng/umbrella/hiccup/functions/serialize.html
*
* @param body
* @param parent
* @param idx
*/
export const $comment = (
body: string | string[],
parent?: Element,
idx: NumOrElement = -1
) => {
const comment = document.createComment(
isString(body)
? body
: body.length < 2
? body[0] || ""
: ["", ...body, ""].join("\n")
);
parent && $addChild(parent, comment, idx);
return comment;
};

/**
* Appends or inserts `child` as child element of `parent`. The default `idx` of
* -1 means the child will be appended, else uses `parent.insertBefore()` to
* insert at given index.
*
* @param parent
* @param child
* @param idx
*/
export const $addChild = (
parent: Element,
child: Element,
child: Element | Comment,
idx: NumOrElement = -1
) => {
isNumber(idx)
Expand All @@ -156,17 +200,35 @@ export const $addChild = (
: parent.insertBefore(child, idx);
};

export const $remove = (el: Element) => el.remove();
/**
* Removes given element or comment from the DOM.
*
* @param el
*/
export const $remove = (el: Element | Comment) => el.remove();

/**
* Migrates given element to `newParent`, following the same append or insertion
* logic as {@link $addChild}.
*
* @param newParent
* @param el
* @param idx
*/
export const $moveTo = (
newParent: Element,
el: Element,
el: Element | Comment,
idx: NumOrElement = -1
) => {
$remove(el);
$addChild(newParent, el, idx);
};

/**
* Removes all content from given element.
*
* @param el
*/
export const $clear = (el: Element) => ((el.innerHTML = ""), el);

/**
Expand All @@ -177,12 +239,12 @@ export const $clear = (el: Element) => ((el.innerHTML = ""), el);
* @param el -
* @param body -
*/
export const $text = (el: HTMLElement, body: any) => {
export const $text = (el: HTMLElement | SVGElement, body: any) => {
body = String(deref(body));
if (el.namespaceURI === XML_SVG) {
$clear(el).appendChild(document.createTextNode(body));
} else {
el.innerText = body;
(<HTMLElement>el).innerText = body;
}
};

Expand All @@ -193,8 +255,10 @@ export const $text = (el: HTMLElement, body: any) => {
* @param el -
* @param body -
*/

export const $html = (el: HTMLElement, body: MaybeDeref<string>) => {
export const $html = (
el: HTMLElement | SVGElement,
body: MaybeDeref<string>
) => {
el.innerHTML = String(deref(body));
};

Expand Down

0 comments on commit 3fd5f8e

Please sign in to comment.