Skip to content

Commit

Permalink
Merge pull request #105 from WebReflection/sane
Browse files Browse the repository at this point in the history
Back to the chalk board
  • Loading branch information
WebReflection committed Jan 18, 2024
2 parents 0dbf8af + e65a2bc commit 2d91c1a
Show file tree
Hide file tree
Showing 31 changed files with 844 additions and 341 deletions.
19 changes: 7 additions & 12 deletions esm/creator.js
@@ -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
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
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
@@ -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
@@ -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

0 comments on commit 2d91c1a

Please sign in to comment.