Skip to content

Commit

Permalink
- Support style prop
Browse files Browse the repository at this point in the history
- Support dangerouslySetInnerHTML prop
- Bug fixes around attributes. Remove attribute when false value is provided.
- Refactor attributes and config code.
  • Loading branch information
s-yadav committed Aug 7, 2019
1 parent a0d7dce commit cb3800c
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 61 deletions.
27 changes: 27 additions & 0 deletions src/configs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@

// reserved props which cannot be forward to component props
export const RESERVED_ATTRIBUTES = {
key: 1,
ref: 1,
};

export const MODIFIED_ATTRIBUTES = {
className: 'class',
htmlFor: 'for',
acceptCharset: 'accept-charset',
httpEquiv: 'http-equiv',
};

export const RENAMED_EVENTS = {
doubleclick: 'dblclick',
};

/**
* Regex taken from Preact. (https://github.com/preactjs/preact/blob/master/src/constants.js)
*/
export const IS_NON_DIMENSIONAL = /acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|^--/i;

/**
* xlink namespace for svgs
*/
export const XLINK_NS = 'http://www.w3.org/1999/xlink';
5 changes: 1 addition & 4 deletions src/reactEvents.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { getNodeName } from './utils';

const RENAMED_EVENTS = {
doubleclick: 'dblclick',
};
import { RENAMED_EVENTS } from './configs';

export const eventHandlerCache = new WeakMap();

Expand Down
137 changes: 93 additions & 44 deletions src/updateAttribute.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,36 @@ import {
eventHandlerCache,
} from './reactEvents';

const XLINK_NS = 'http://www.w3.org/1999/xlink';
import {
XLINK_NS,
IS_NON_DIMENSIONAL,
} from './configs';

/**
* Lot of diffing and applying attribute logic in this file is inspired/forked from Preact
* Reference:
* https://github.com/preactjs/preact/blob/master/src/diff/props.js
*/

function applyDiffProperty (newObj, oldObj, resetValue, cb) {
oldObj = oldObj || {};
// add new attributes
Object.entries(newObj).forEach(([key, value]) => {
const oldValue = oldObj[key];
if (
value !== oldValue
) {
cb(key, value, oldValue);
}
});

// remove old attributes
Object.entries(oldObj).forEach(([key, value]) => {
if (newObj[key] === undefined) {
cb(key, resetValue, value);
}
});
}

function setAttribute (node, attrName, attrValue, oldAttrValue, isSvgAttribute) {
/*
Expand All @@ -20,70 +49,90 @@ function setAttribute (node, attrName, attrValue, oldAttrValue, isSvgAttribute)
*/

const isEventAttr = isEventAttribute(attrName);
if ((attrName in node && !isSvgAttribute) || isEventAttr) {
const inputStateType = getInputStateType(node);
/*
if it is a property check if it is a event callback
or other property and handle it accordingly
*/
if (isEventAttr) {
let eventName = getEventName(attrName);
eventName = getEffectiveEventName(eventName, node);

// remove old event and assign it again
if (oldAttrValue) {
const oldPatchedHandler = eventHandlerCache.get(oldAttrValue) || oldAttrValue;
node.removeEventListener(eventName, oldPatchedHandler);
}

// if new event is defined assign new event handler
if (attrValue) {
const patchedEventHandler = getPatchedEventHandler(node, attrValue);
node.addEventListener(eventName, patchedEventHandler);
// Handle event attributes
if (isEventAttr) {
let eventName = getEventName(attrName);
eventName = getEffectiveEventName(eventName, node);

// remove old event and assign it again
if (oldAttrValue) {
const oldPatchedHandler = eventHandlerCache.get(oldAttrValue) || oldAttrValue;
node.removeEventListener(eventName, oldPatchedHandler);
}

// if new event is defined assign new event handler
if (attrValue) {
const patchedEventHandler = getPatchedEventHandler(node, attrValue);
node.addEventListener(eventName, patchedEventHandler);
}

// handle style attributes
} else if (attrName === 'style') {
const { style } = node;
applyDiffProperty(attrValue, oldAttrValue, '', (key, value) => {
/**
* check if it is custom property (--some-custom-property),then use setProperty to assign value
* otherwise just add the property in style object
*/
if (key[0] === '-') {
style.setProperty(key, value);
} else {
style[key] = typeof value === 'number' && IS_NON_DIMENSIONAL.test(key) === false
? value + 'px'
: value;
}
} else if (inputStateType) {
});

// handle dangerously set innerHTML
} else if (attrName === 'dangerouslySetInnerHTML') {
const oldHTML = oldAttrValue && oldAttrValue.__html;
const newHTML = attrValue && attrValue.__html;
if (newHTML !== oldHTML) {
node.innerHTML = newHTML == null ? '' : newHTML; // `==` here will check for undefined and null
}
// handle node properties
} else if ((attrName in node && !isSvgAttribute)) {
const inputStateType = getInputStateType(node);
/**
* if it is input element it has to be handled differently,
* otherwise just set the property value
*/
if (inputStateType) {
handleInputProperty(inputStateType, node, attrName, attrValue);
} else {
// if attribute is value property
node[attrName] = attrValue;
node[attrName] = attrValue == null ? '' : attrValue; // `==` here will check for undefined and null
}

// handle all other node attributes
} else {
/**
* If attribute is prefixed with xlink then we have to set attribute with namespace
* if attribute value is defined set the new attribute value and if
* it is not defined and oldAttribute is present remove the oldAttribute;
*/
let attrNameWithoutNS = attrName.replace(/^xlink:?/, '');

// if attribute value is null, undefined or false remove the attribute
const removeAttribute = attrValue == null || attrValue === false;

if (attrName !== attrNameWithoutNS) {
attrNameWithoutNS = attrNameWithoutNS.toLowerCase();
if (attrValue !== undefined) {
node.setAttributeNS(XLINK_NS, attrNameWithoutNS, attrValue);
} else if (oldAttrValue !== undefined) {
if (removeAttribute) {
node.removeAttributeNS(XLINK_NS, attrNameWithoutNS);
} else {
node.setAttributeNS(XLINK_NS, attrNameWithoutNS, attrValue);
}
} else if (attrValue !== undefined) {
node.setAttribute(attrName, attrValue);
} else if (oldAttrValue !== undefined) {
} else if (removeAttribute) {
node.removeAttribute(attrName);
} else {
node.setAttribute(attrName, attrValue);
}
}
}

export default function updateNodeAttributes (node, attributes, oldAttributes, isSvgAttribute) {
// add new attributes
Object.entries(attributes).forEach(([attrName, attrValue]) => {
const oldAttrValue = oldAttributes && oldAttributes[attrName];
if (
attrValue !== oldAttrValue
) {
setAttribute(node, attrName, attrValue, oldAttrValue, isSvgAttribute);
}
});

// remove old attributes
Object.entries(oldAttributes).forEach(([attrName, attrValue]) => {
if (attributes[attrName] === undefined) {
setAttribute(node, attrName, null, attrValue, isSvgAttribute);
}
applyDiffProperty(attributes, oldAttributes, null, (attrName, attrValue, oldAttrValue) => {
setAttribute(node, attrName, attrValue, oldAttrValue, isSvgAttribute);
});
}
2 changes: 1 addition & 1 deletion src/updater.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import updateNodeAttributes from './updateAttribute';
import { RESERVED_ATTRIBUTES, MODIFIED_ATTRIBUTES } from './utils';
import { RESERVED_ATTRIBUTES, MODIFIED_ATTRIBUTES } from './configs';
import updateNode from './updateNode';
import { setRef } from './refs';

Expand Down
12 changes: 0 additions & 12 deletions src/utils.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,5 @@
import { Component } from './Component';

export const RESERVED_ATTRIBUTES = {
key: 1,
ref: 1,
};

export const MODIFIED_ATTRIBUTES = {
className: 'class',
htmlFor: 'for',
acceptCharset: 'accept-charset',
httpEquiv: 'http-equiv',
};

/**
* Method to identify if a jsx element is a html element or custom component
* Taken from https://github.com/babel/babel/blob/master/packages/babel-types/src/validators/react/isCompatTag.js
Expand Down

0 comments on commit cb3800c

Please sign in to comment.