Skip to content

Commit f991e17

Browse files
committed
Release 0.2.8
1 parent 9a1ae95 commit f991e17

4 files changed

Lines changed: 150 additions & 148 deletions

File tree

docs/dist.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "endr",
33
"type": "module",
4-
"version": "0.2.7",
4+
"version": "0.2.8",
55
"author": "Casey Foster <c@sey.me>",
66
"license": "MIT",
77
"repository": {

src/index.js

Lines changed: 145 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@
318318
* inserts: Vnode[];
319319
* nodeUpdates: Parameters<typeof updateNode>[];
320320
* removes: (Element | Text)[];
321-
* updates: Vnode[];
321+
* updates: Set<Vnode>;
322322
* }} Queues
323323
*/
324324

@@ -366,7 +366,7 @@
366366

367367
/**
368368
* @template T
369-
* @typedef {[T, SetState<T>]} State
369+
* @typedef {[T, SetState<T>, SetState<T>]} State
370370
*/
371371

372372
const { console, Element, queueMicrotask } = globalThis;
@@ -430,6 +430,30 @@ export const Try = props => {
430430
return props.children;
431431
};
432432

433+
/**
434+
* @param {Props} prev
435+
* @param {Props} next
436+
*/
437+
const defaultMemo = (prev, next) => {
438+
if (prev === next) return true;
439+
440+
for (const key in prev) if (prev[key] !== next[key]) return false;
441+
442+
for (const key in next) if (!(key in prev)) return false;
443+
444+
return true;
445+
};
446+
447+
/**
448+
* @template {Component} T
449+
* @param {T} Component
450+
* @param {typeof defaultMemo} [memo]
451+
*/
452+
export const memo = (Component, memo = defaultMemo) => {
453+
Component.memo = memo;
454+
return Component;
455+
};
456+
433457
/**
434458
* @template T
435459
* @param {T} value
@@ -492,64 +516,6 @@ const createNode = (type, props, parentNode) => {
492516
);
493517
};
494518

495-
/**
496-
* @template {Element} Node
497-
* @param {Node} node
498-
* @param {keyof Node} key
499-
*/
500-
const isSimpleProperty = (node, key) =>
501-
key in node && (node[key] == null || !isObject(node[key]));
502-
503-
/**
504-
* @template {Element | Text} T
505-
* @param {T} node
506-
* @param {Props<T>} prev
507-
* @param {Props<T>} next
508-
*/
509-
const updateNode = (node, prev, next) => {
510-
if (prev === next) return;
511-
512-
// Element.prototype.TEXT_NODE
513-
if (node.nodeType === 3) {
514-
node.nodeValue = /** @type {string} */ (next.nodeValue);
515-
return;
516-
}
517-
518-
for (const key in prev) {
519-
if (key === 'children' || key in next) continue;
520-
521-
if (key === 'ref') {
522-
if (prev.ref) prev.ref.current = null;
523-
// @ts-expect-error assignment of a readonly property would fail
524-
} else if (isSimpleProperty(node, key)) node[key] = '';
525-
else /** @type {Element} */ (node).removeAttribute(key);
526-
}
527-
528-
for (const key in next) {
529-
if (key === 'children' || prev[key] === next[key]) continue;
530-
531-
if (key === 'ref') {
532-
if (prev.ref) prev.ref.current = null;
533-
if (next.ref) next.ref.current = node;
534-
} else if (key === 'style' && isObject(next[key])) {
535-
const { style } = /** @type {HTMLElement | SVGElement} */ (node);
536-
const prevStyle = /** @type {Partial<CSSStyleDeclaration>} */ (
537-
isObject(prev[key]) ? prev[key] : emptyObject
538-
);
539-
const nextStyle = /** @type {Partial<CSSStyleDeclaration>} */ (next[key]);
540-
for (const key in prevStyle) if (!(key in nextStyle)) style[key] = '';
541-
for (const key in nextStyle) style[key] = nextStyle[key] ?? '';
542-
// @ts-expect-error assignment of a readonly property would fail
543-
} else if (isSimpleProperty(node, key)) node[key] = next[key] ?? '';
544-
else if (next[key] != null) {
545-
/** @type {Element} */ (node).setAttribute(
546-
key,
547-
/** @type {string} */ (next[key])
548-
);
549-
} else /** @type {Element} */ (node).removeAttribute(key);
550-
}
551-
};
552-
553519
/** @type {Vnode | null} */
554520
let currentVnode = null;
555521

@@ -572,6 +538,21 @@ export const useRef = initial => {
572538
return ref;
573539
};
574540

541+
/**
542+
* @param {unknown[]} before
543+
* @param {unknown[]} after
544+
*/
545+
const depsChanged = (before, after) => {
546+
if (before === after) return false;
547+
548+
let { length } = before;
549+
if (length !== after.length) return true;
550+
551+
while (length--) if (before[length] !== after[length]) return true;
552+
553+
return false;
554+
};
555+
575556
/**
576557
* @param {AfterEffect} fn
577558
* @param {unknown[]} [deps]
@@ -601,6 +582,48 @@ export const useMemo = (fn, deps = emptyArray) => {
601582
return ref.current.value;
602583
};
603584

585+
/**
586+
* @param {Vnode} a
587+
* @param {Vnode} b
588+
*/
589+
const batchComparator = (a, b) => {
590+
while (a.depth > b.depth) {
591+
a = /** @type {Vnode} */ (a.parent);
592+
if (a === b) return 1;
593+
}
594+
595+
while (b.depth > a.depth) {
596+
b = /** @type {Vnode} */ (b.parent);
597+
if (b === a) return -1;
598+
}
599+
600+
while (a.parent !== b.parent) {
601+
a = /** @type {Vnode} */ (a.parent);
602+
b = /** @type {Vnode} */ (b.parent);
603+
}
604+
605+
return a.index - b.index;
606+
};
607+
608+
/** @param {Vnode} vnode */
609+
const queueUpdate = vnode => {
610+
if (vnode.state === 3) return;
611+
612+
const { queues } = vnode;
613+
const { updates } = queues;
614+
615+
if (updates.size === 0) {
616+
queueMicrotask(() => {
617+
const batch = [...updates].sort(batchComparator);
618+
updates.clear();
619+
for (const vnode of batch) if (vnode.state === 1) update(vnode);
620+
flush(queues);
621+
});
622+
}
623+
updates.add(vnode);
624+
vnode.state = 1;
625+
};
626+
604627
/**
605628
* @template T
606629
* @param {T | (() => T)} initial
@@ -617,26 +640,13 @@ export const useState = initial => {
617640
queueUpdate(vnode);
618641
}
619642
return value;
620-
}
643+
},
644+
maybeValue =>
645+
(state[0] = isFunction(maybeValue) ? maybeValue(state[0]) : maybeValue)
621646
]);
622647
return state;
623648
};
624649

625-
/**
626-
* @param {unknown[]} before
627-
* @param {unknown[]} after
628-
*/
629-
const depsChanged = (before, after) => {
630-
if (before === after) return false;
631-
632-
let { length } = before;
633-
if (length !== after.length) return true;
634-
635-
while (length--) if (before[length] !== after[length]) return true;
636-
637-
return false;
638-
};
639-
640650
/**
641651
* @template {AnyFunction} T
642652
* @param {T} fn
@@ -649,30 +659,6 @@ export const useCallback = fn => {
649659
);
650660
};
651661

652-
/**
653-
* @param {Props} prev
654-
* @param {Props} next
655-
*/
656-
const defaultMemo = (prev, next) => {
657-
if (prev === next) return true;
658-
659-
for (const key in prev) if (prev[key] !== next[key]) return false;
660-
661-
for (const key in next) if (!(key in prev)) return false;
662-
663-
return true;
664-
};
665-
666-
/**
667-
* @template {Component} T
668-
* @param {T} Component
669-
* @param {typeof defaultMemo} [memo]
670-
*/
671-
export const memo = (Component, memo = defaultMemo) => {
672-
Component.memo = memo;
673-
return Component;
674-
};
675-
676662
/**
677663
* @template {Context<any>} T
678664
* @param {T} Context
@@ -692,48 +678,6 @@ export const useContext = Context => {
692678
return /** @type {T['value']} */ ((context ?? Context).value);
693679
};
694680

695-
/**
696-
* @param {Vnode} a
697-
* @param {Vnode} b
698-
*/
699-
const batchComparator = (a, b) => {
700-
while (a.depth > b.depth) {
701-
a = /** @type {Vnode} */ (a.parent);
702-
if (a === b) return 1;
703-
}
704-
705-
while (b.depth > a.depth) {
706-
b = /** @type {Vnode} */ (b.parent);
707-
if (b === a) return -1;
708-
}
709-
710-
while (a.parent !== b.parent) {
711-
a = /** @type {Vnode} */ (a.parent);
712-
b = /** @type {Vnode} */ (b.parent);
713-
}
714-
715-
return a.index - b.index;
716-
};
717-
718-
/** @param {Vnode} vnode */
719-
const queueUpdate = vnode => {
720-
if (vnode.state === 1 || vnode.state === 3) return;
721-
722-
const { queues } = vnode;
723-
const { updates } = queues;
724-
725-
if (updates.length === 0) {
726-
queueMicrotask(() => {
727-
const batch = updates.sort(batchComparator);
728-
queues.updates = [];
729-
for (const vnode of batch) if (vnode.state === 1) update(vnode);
730-
flush(queues);
731-
});
732-
}
733-
updates.push(vnode);
734-
vnode.state = 1;
735-
};
736-
737681
/** @param {Vnode} vnode */
738682
const getDefs = vnode => {
739683
const { props, type } = vnode;
@@ -778,6 +722,64 @@ const normalizeDef = def => {
778722
return jsx(textType, { nodeValue: def });
779723
};
780724

725+
/**
726+
* @template {Element} Node
727+
* @param {Node} node
728+
* @param {keyof Node} key
729+
*/
730+
const isSimpleProperty = (node, key) =>
731+
key in node && (node[key] == null || !isObject(node[key]));
732+
733+
/**
734+
* @template {Element | Text} T
735+
* @param {T} node
736+
* @param {Props<T>} prev
737+
* @param {Props<T>} next
738+
*/
739+
const updateNode = (node, prev, next) => {
740+
if (prev === next) return;
741+
742+
// Element.prototype.TEXT_NODE
743+
if (node.nodeType === 3) {
744+
node.nodeValue = /** @type {string} */ (next.nodeValue);
745+
return;
746+
}
747+
748+
for (const key in prev) {
749+
if (key === 'children' || key in next) continue;
750+
751+
if (key === 'ref') {
752+
if (prev.ref) prev.ref.current = null;
753+
// @ts-expect-error assignment of a readonly property would fail
754+
} else if (isSimpleProperty(node, key)) node[key] = '';
755+
else /** @type {Element} */ (node).removeAttribute(key);
756+
}
757+
758+
for (const key in next) {
759+
if (key === 'children' || prev[key] === next[key]) continue;
760+
761+
if (key === 'ref') {
762+
if (prev.ref) prev.ref.current = null;
763+
if (next.ref) next.ref.current = node;
764+
} else if (key === 'style' && isObject(next[key])) {
765+
const { style } = /** @type {HTMLElement | SVGElement} */ (node);
766+
const prevStyle = /** @type {Partial<CSSStyleDeclaration>} */ (
767+
isObject(prev[key]) ? prev[key] : emptyObject
768+
);
769+
const nextStyle = /** @type {Partial<CSSStyleDeclaration>} */ (next[key]);
770+
for (const key in prevStyle) if (!(key in nextStyle)) style[key] = '';
771+
for (const key in nextStyle) style[key] = nextStyle[key] ?? '';
772+
// @ts-expect-error assignment of a readonly property would fail
773+
} else if (isSimpleProperty(node, key)) node[key] = next[key] ?? '';
774+
else if (next[key] != null) {
775+
/** @type {Element} */ (node).setAttribute(
776+
key,
777+
/** @type {string} */ (next[key])
778+
);
779+
} else /** @type {Element} */ (node).removeAttribute(key);
780+
}
781+
};
782+
781783
/** @param {Vnode} vnode */
782784
const updateChild = vnode => {
783785
let { child, state } = vnode;
@@ -1015,7 +1017,7 @@ export const createRender = parentNode => {
10151017
inserts: [],
10161018
nodeUpdates: [],
10171019
removes: [],
1018-
updates: []
1020+
updates: new Set()
10191021
},
10201022
refs: null,
10211023
sibling: null,

0 commit comments

Comments
 (0)