Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Back to the chalk board #105

Merged
merged 2 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 7 additions & 12 deletions esm/creator.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { COMMENT_NODE } from 'domconstants/constants';

import { PersistentFragment } from './persistent-fragment.js';
import { detail, parsed } from './literals.js';
import { empty } from './utils.js';
Expand All @@ -16,19 +14,16 @@ const childNodesIndex = (node, i) => node.childNodes[i];
export default parse => (
/** @param {(template: TemplateStringsArray, values: any[]) => import("./literals.js").Parsed} parse */
(template, values) => {
const { c: content, e: entries, l: length } = parse(template, values);
const root = content.cloneNode(true);
// reverse loop to avoid missing paths while populating
// TODO: is it even worth to pre-populate nodes? see rabbit.js too
let current, prev, i = entries.length, details = i ? entries.slice(0) : empty;
while (i--) {
const { t: type, p: path, u: update, n: name } = entries[i];
const { f: fragment, e: entries, d: direct } = parse(template, values);
const root = fragment.cloneNode(true);
let current, prev, details = entries === empty ? empty : [];
for (let i = 0; i < entries.length; i++) {
const { p: path, u: update, n: name } = entries[i];
const node = path === prev ? current : (current = find(root, (prev = path)));
const callback = type === COMMENT_NODE ? update() : update;
details[i] = detail(callback(node, values[i], name, empty), callback, node, name);
details[i] = detail(empty, update, node, name);
}
return parsed(
length === 1 ? (root.firstChild || current) : new PersistentFragment(root),
direct ? root.firstChild : new PersistentFragment(root),
details
);
}
Expand Down
25 changes: 0 additions & 25 deletions esm/dom/element.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,13 @@ import tokenList from './token-list.js';

import { parseString } from './string-parser.js';
import { cloned, setParentNode, withNewParent } from './utils.js';
import { push, splice, unshift } from './array.js';

import { attributes, name, value, localName, childNodes, nodeType, ownerDocument, ownerElement, parentNode } from './symbols.js';

const getAttributes = element => (
element[attributes] || (element[attributes] = new Map)
);

const map = (values, parent) => values.map(withNewParent, parent);

/** @typedef {import("./attribute.js").Attribute} Attribute */

export default class Element extends Parent {
Expand Down Expand Up @@ -150,28 +147,6 @@ export default class Element extends Parent {
return element;
}

/**
* @param {...import("./node.js").Child} values
*/
after(...values) {
const { [parentNode]: parent } = this;
const { [childNodes]: nodes } = parent;
const i = nodes.indexOf(this) + 1;
if (i === nodes.length) push(nodes, map(values, parent));
else if (i) splice(nodes, i - 1, 0, map(values, parent));
}

/**
* @param {...import("./node.js").Child} values
*/
before(...values) {
const { [parentNode]: parent } = this;
const { [childNodes]: nodes } = parent;
const i = nodes.indexOf(this);
if (!i) unshift(nodes, map(values, parent));
else if (i > 0) splice(nodes, i, 0, map(values, parent));
}

/**
* @param {string} name
* @returns {string?}
Expand Down
26 changes: 25 additions & 1 deletion esm/dom/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import {

import { childNodes, nodeType, ownerDocument, parentNode } from './symbols.js';
import { changeParentNode, withNewParent } from './utils.js';
import { splice } from './array.js';
import { push, splice, unshift } from './array.js';

/** @typedef {string | Node} Child */

const map = (values, parent) => values.map(withNewParent, parent);

export default class Node {
static {
this.ELEMENT_NODE = ELEMENT_NODE;
Expand Down Expand Up @@ -101,6 +103,28 @@ export default class Node {
return [];
}

/**
* @param {...import("./node.js").Child} values
*/
after(...values) {
const { [parentNode]: parent } = this;
const { [childNodes]: nodes } = parent;
const i = nodes.indexOf(this) + 1;
if (i === nodes.length) push(nodes, map(values, parent));
else if (i) splice(nodes, i - 1, 0, map(values, parent));
}

/**
* @param {...import("./node.js").Child} values
*/
before(...values) {
const { [parentNode]: parent } = this;
const { [childNodes]: nodes } = parent;
const i = nodes.indexOf(this);
if (!i) unshift(nodes, map(values, parent));
else if (i > 0) splice(nodes, i, 0, map(values, parent));
}

remove() {
changeParentNode(this, null);
}
Expand Down
75 changes: 40 additions & 35 deletions esm/handler.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import udomdiff from 'udomdiff';
import { empty, gPD, isArray, set } from './utils.js';
import { diffFragment } from './persistent-fragment.js';
import { comment } from './literals.js';
import drop from './range.js';

const setAttribute = (element, name, value) =>
element.setAttribute(name, value);

/**
* @param {Element} element
* @param {string} name
* @returns {void}
*/
export const removeAttribute = (element, name) =>
element.removeAttribute(name);

Expand All @@ -26,8 +30,6 @@ export const aria = (element, value) => {
return value;
};

export const arrayComment = () => array;

let listeners;

/**
Expand All @@ -49,36 +51,37 @@ export const at = (element, value, name) => {
return value;
};

/** @type {WeakMap<Node, Element | import("./persistent-fragment.js").PersistentFragment>} */
const holes = new WeakMap;

/**
* @template T
* @this {import("./literals.js").HoleDetails}
* @this {import("./literals.js").Detail}
* @param {Node} node
* @param {T} value
* @returns {T}
*/
function hole(node, value) {
const n = this.n || (this.n = node);
export function hole(node, value) {
let { n: hole } = this, nullish = false;
switch (typeof value) {
case 'string':
case 'number':
case 'boolean': {
if (n !== node) n.replaceWith((this.n = node));
this.n.data = value;
break;
}
case 'object':
case 'undefined': {
if (value == null) (this.n = node).data = '';
else this.n = value.valueOf();
n.replaceWith(this.n);
if (value !== null) {
(hole || node).replaceWith((this.n = value.valueOf()));
break;
}
case 'undefined':
nullish = true;
default:
node.data = nullish ? '' : value;
if (hole) {
this.n = null;
hole.replaceWith(node);
}
break;
}
}
return value;
};

export const boundComment = () => hole.bind(comment());

/**
* @template T
* @param {Element} element
Expand Down Expand Up @@ -192,25 +195,27 @@ export const toggle = (element, value, name) => (
* @param {Node[]} prev
* @returns {Node[]}
*/
export const array = (node, value, _, prev) => {
export const array = (node, value, prev) => {
// normal diff
if (value.length)
const { length } = value;
node.data = `[${length}]`;
if (length)
return udomdiff(node.parentNode, prev, value, diffFragment, node);
let { length } = prev;
// something to remove
if (length--) {
// lot to remove: grab first and last child nodes
if (length) {
const start = diffFragment(prev[0], 0);
const end = diffFragment(prev[length], -0);
drop(start, end, false);
}
/* c8 ignore start */
// just one node or fragment to remove
else
/* c8 ignore start */
switch (prev.length) {
case 1:
prev[0].remove();
/* c8 ignore stop */
case 0:
break;
default:
drop(
diffFragment(prev[0], 0),
diffFragment(prev.at(-1), -0),
false
);
break;
}
/* c8 ignore stop */
return empty;
};

Expand Down
27 changes: 6 additions & 21 deletions esm/literals.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import { empty } from './utils.js';

/** @typedef {import("domconstants/constants").ATTRIBUTE_NODE} ATTRIBUTE_NODE */
/** @typedef {import("domconstants/constants").TEXT_NODE} TEXT_NODE */
/** @typedef {import("domconstants/constants").COMMENT_NODE} COMMENT_NODE */
/** @typedef {ATTRIBUTE_NODE | TEXT_NODE | COMMENT_NODE} Type */

/** @typedef {import("./persistent-fragment.js").PersistentFragment} PersistentFragment */
/** @typedef {import("./rabbit.js").Hole} Hole */

Expand All @@ -14,27 +9,18 @@ import { empty } from './utils.js';

/**
* @typedef {Object} Entry
* @property {Type} type
* @property {number[]} path
* @property {function} update
* @property {string} name
*/

/**
* @param {PersistentFragment} c content retrieved from the template
* @param {DocumentFragment} f content retrieved from the template
* @param {Entry[]} e entries per each hole in the template
* @param {number} l the length of content childNodes
* @param {boolean} d direct node to handle
* @returns
*/
export const cel = (c, e, l) => ({ c, e, l });

/**
* @typedef {Object} HoleDetails
* @property {null | Node | PersistentFragment} n the current live node, if any and not the `t` one
*/

/** @type {() => HoleDetails} */
export const comment = () => ({ n: null });
export const cel = (f, e, d) => ({ f, e, d });

/**
* @typedef {Object} Detail
Expand All @@ -48,19 +34,18 @@ export const comment = () => ({ n: null });
* @param {any} v the current value of the interpolation / hole
* @param {function} u the callback to update the value
* @param {Node} t the target comment node or element
* @param {string} n the name of the attribute, if any
* @param {string?} n the attribute name, if any, or `null`
* @returns {Detail}
*/
export const detail = (v, u, t, n) => ({ v, u, t, n });

/**
* @param {Type} t the operation type
* @param {number[]} p the path to retrieve the node
* @param {function} u the update function
* @param {string} n the attribute name, if any
* @param {string?} n the attribute name, if any, or `null`
* @returns {Entry}
*/
export const entry = (t, p, u, n = '') => ({ t, p, u, n });
export const entry = (p, u, n) => ({ p, u, n });

/**
* @typedef {Object} Cache
Expand Down