Skip to content

Commit

Permalink
feat: attribute support
Browse files Browse the repository at this point in the history
  • Loading branch information
aidenybai committed Aug 16, 2021
1 parent 9e0c993 commit c9902db
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 42 deletions.
14 changes: 14 additions & 0 deletions src/__test__/m.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,20 @@ describe('.m', () => {
);
});

it('should create a tag with dataset attribute', () => {
expect(h('div', { attributes: { 'data-test': 'foo' } })).toEqual({
tag: 'div',
attributes: {
'data-test': 'foo',
},
children: undefined,
delta: undefined,
flag: undefined,
key: undefined,
props: {},
});
});

it('should attach ns to props with children with props', () => {
const vnode = {
tag: 'svg',
Expand Down
14 changes: 11 additions & 3 deletions src/__test__/patch.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,17 @@ describe('.patch', () => {

patch(el, h('div', { id: 'el' }, 'bar'));
expect(el).toEqual(createElement(h('div', { id: 'el' }, 'bar')));
expect(el).toEqual(createElement(h('div', { id: 'el' }, 'bar')));
patch(el, h('div', { id: 'el', class: 'new' }, 'baz'));
expect(el).toEqual(createElement(h('div', { id: 'el', class: 'new' }, 'baz')));
patch(el, h('div', { id: 'el', className: 'foo' }, 'baz'));
expect(el).toEqual(createElement(h('div', { id: 'el', className: 'foo' }, 'baz')));

document.body.textContent = '';
});

it('should patch attributes', () => {
const el = createElement(h('div', { id: 'el' }, 'foo'));

patch(el, h('div', { attributes: { 'data-test': 'foo' } }, 'bar'));
expect(el).toEqual(createElement(h('div', { attributes: { 'data-test': 'foo' } }, 'bar')));

document.body.textContent = '';
});
Expand Down
7 changes: 4 additions & 3 deletions src/createElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import { OLD_VNODE_FIELD, VNode } from './structs';

/**
* Creates an element from a VNode
* @param {VNode} vnode - VNode to convert to HTMLElement or Text
* @param {boolean} attachField - Attach OLD_VNODE_FIELD
* @returns {HTMLElement|Text}
*/
export const createElement = (vnode: VNode, attachField = true): HTMLElement | Text => {
if (typeof vnode === 'string') return document.createTextNode(vnode);
const el = <HTMLElement>Object.assign(document.createElement(vnode.tag), vnode.props);

Object.entries(vnode.attributes || {}).forEach(([attrName, attrValue]) => {
el.setAttribute(attrName, attrValue);
});

vnode.children?.forEach((child) => {
el.appendChild(createElement(child));
});
Expand Down
6 changes: 3 additions & 3 deletions src/jsx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const h = (tag: string, props?: VProps, children?: VNode[], delta?: VDelta) => {
}
}
if (typeof props?.className === 'object') {
props.className = className(props.className);
props.className = className(<Record<string, boolean>>(<unknown>props.className));
}
if (typeof props?.style === 'object') {
props.style = style(props.style);
Expand Down Expand Up @@ -51,7 +51,7 @@ const normalizeChildren = (children: JSXVNode[], normalizedChildren: VNode[]) =>
return normalizedChildren;
};

const jsx = (tag: string | FC, props?: VProps, ...children: JSXVNode[]): VNode => {
const jsx = (tag: string | FC, props?: VProps, ...children: JSXVNode[]): VNode | (() => VNode) => {
let delta: VDelta | undefined;
if (props) {
const rawDelta = <VDelta>(<unknown>props.delta);
Expand All @@ -66,7 +66,7 @@ const jsx = (tag: string | FC, props?: VProps, ...children: JSXVNode[]): VNode =
}
const normalizedChildren = normalizeChildren(children, []);
if (typeof tag === 'function') {
return tag(props, normalizedChildren, delta);
return () => tag(props, normalizedChildren, delta);
} else {
return h(tag, props, normalizedChildren, delta);
}
Expand Down
24 changes: 7 additions & 17 deletions src/m.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
VAttributes,
VDelta,
VDeltaOperation,
VDeltaOperationTypes,
Expand All @@ -10,8 +11,6 @@ import {

/**
* Attaches ns props to svg element
* @param {VElement} vnode - SVG VNode
* @returns {VElement}
*/
export const svg = (vnode: VElement): VElement => {
/* istanbul ignore next */
Expand All @@ -31,8 +30,6 @@ export const ns = (tag: string, props: VProps, children?: VNode[]): void => {

/**
* Generates a className string based on a classObject
* @param {object} classObject - Object with classes paired with boolean values to toggle
* @returns {string}
*/
export const className = (classObject: Record<string, boolean>): string =>
Object.keys(classObject)
Expand All @@ -41,8 +38,6 @@ export const className = (classObject: Record<string, boolean>): string =>

/**
* Generates a style string based on a styleObject
* @param {object} styleObject - Object with styles
* @returns {string}
*/
export const style = (styleObject: Record<string, string>): string =>
Object.entries(styleObject)
Expand All @@ -51,8 +46,6 @@ export const style = (styleObject: Record<string, string>): string =>

/**
* Returns an insert (creation) delta operation
* @param {number} positionIdx - Index of delta operation
* @returns {VDeltaOperation}
*/
export const INSERT = (positionIdx = 0): VDeltaOperation => [
VDeltaOperationTypes.INSERT,
Expand All @@ -61,8 +54,6 @@ export const INSERT = (positionIdx = 0): VDeltaOperation => [

/**
* Returns an update (modification) delta operation
* @param {number} positionIdx - Index of delta operation
* @returns {VDeltaOperation}
*/
export const UPDATE = (positionIdx = 0): VDeltaOperation => [
VDeltaOperationTypes.UPDATE,
Expand All @@ -71,8 +62,6 @@ export const UPDATE = (positionIdx = 0): VDeltaOperation => [

/**
* Returns an delete (removal) delta operation
* @param {number} positionIdx - Index of delta operation
* @returns {VDeltaOperation}
*/
export const DELETE = (positionIdx = 0): VDeltaOperation => [
VDeltaOperationTypes.DELETE,
Expand All @@ -81,11 +70,6 @@ export const DELETE = (positionIdx = 0): VDeltaOperation => [

/**
* Helper method for creating a VNode
* @param {string} tag - The tagName of an HTMLElement
* @param {VProps=} props - DOM properties and attributes of an HTMLElement
* @param {VNode[]=} children - Children of an HTMLElement
* @param {VFlags=} flag - Compiler flag for VNode
* @returns {VElement}
*/
export const m = (
tag: string,
Expand All @@ -95,13 +79,19 @@ export const m = (
delta?: VDelta,
): VElement => {
let key;
let attributes;
if (props?.key) {
key = <string | undefined>props.key;
delete props.key;
}
if (props?.attributes) {
attributes = <VAttributes>props.attributes;
delete props.attributes;
}
return {
tag,
props,
attributes,
children,
key,
flag,
Expand Down
39 changes: 24 additions & 15 deletions src/patch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,12 @@ import {

/**
* Diffs two VNode props and modifies the DOM node based on the necessary changes
* @param {HTMLElement} el - Target element to be modified
* @param {VProps} oldProps - Old VNode props
* @param {VProps} newProps - New VNode props
* @returns {void}
*/
export const patchProps = (
el: HTMLElement,
oldProps: VProps,
newProps: VProps,
areAttributes: boolean,
workQueue: (() => void)[],
): void => {
const skip = new Set<string>();
Expand All @@ -29,12 +26,20 @@ export const patchProps = (
if (newPropValue) {
const oldPropValue = oldProps[oldPropName];
if (newPropValue !== oldPropValue) {
if (typeof oldPropValue === 'function' && typeof newPropValue === 'function') {
if (
!areAttributes &&
typeof oldPropValue === 'function' &&
typeof newPropValue === 'function'
) {
if (oldPropValue.toString() !== newPropValue.toString()) {
workQueue.push(() => (el[oldPropName] = newPropValue));
}
} else {
workQueue.push(() => (el[oldPropName] = newPropValue));
workQueue.push(() =>
areAttributes
? el.setAttribute(oldPropName, String(newPropValue))
: (el[oldPropName] = newPropValue),
);
}
}
skip.add(oldPropName);
Expand All @@ -48,17 +53,17 @@ export const patchProps = (

for (const newPropName of Object.keys(newProps)) {
if (!skip.has(newPropName)) {
workQueue.push(() => (el[newPropName] = newProps[newPropName]));
workQueue.push(() =>
areAttributes
? el.setAttribute(newPropName, String(newProps[newPropName]))
: (el[newPropName] = newProps[newPropName]),
);
}
}
};

/**
* Diffs two VNode children and modifies the DOM node based on the necessary changes
* @param {HTMLElement} el - Target element to be modified
* @param {VNode[]} oldVNodeChildren - Old VNode children
* @param {VNode[]} newVNodeChildren - New VNode children
* @returns {void}
*/
export const patchChildren = (
el: HTMLElement,
Expand Down Expand Up @@ -186,10 +191,6 @@ export const patchChildren = (

/**
* Diffs two VNodes and modifies the DOM node based on the necessary changes
* @param {HTMLElement|Text} el - Target element to be modified
* @param {VNode} newVNode - New VNode
* @param {VNode=} prevVNode - Previous VNode
* @returns {void}
*/
export const patch = (
el: HTMLElement | Text,
Expand Down Expand Up @@ -217,6 +218,14 @@ export const patch = (
el,
(<VElement>oldVNode)?.props || {},
(<VElement>newVNode).props || {},
false,
workQueue,
);
patchProps(
el,
(<VElement>oldVNode)?.attributes || {},
(<VElement>newVNode).attributes || {},
true,
workQueue,
);

Expand Down
4 changes: 3 additions & 1 deletion src/structs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
export const OLD_VNODE_FIELD = '__m_old_vnode';

// Props can contain a standard value or a callback function (for events)
export type VProps = Record<string, string | boolean | (() => void)>;
export type VProps = Record<string, string | boolean | (() => void) | VAttributes>;
export type VAttributes = Record<string, string>;
export type VNode = VElement | string;
export type VDeltaOperation = [VDeltaOperationTypes, number];
export type VDelta = VDeltaOperation[];
Expand All @@ -13,6 +14,7 @@ export type VTask = () => void;
export interface VElement {
tag: string;
props?: VProps;
attributes?: VAttributes;
children?: VNode[];
key?: string;
flag?: VFlags;
Expand Down

0 comments on commit c9902db

Please sign in to comment.