diff --git a/src/apis/StyleSheet/processTransform.js b/src/apis/StyleSheet/processTransform.js new file mode 100644 index 00000000..177e3188 --- /dev/null +++ b/src/apis/StyleSheet/processTransform.js @@ -0,0 +1,24 @@ +// { scale: 2 } => 'scale(2)' +const mapTransform = (transform) => { + var key = Object.keys(transform)[0] + return `${key}(${transform[key]})` +} + +// [1,2,3,4,5,6] => 'matrix3d(1,2,3,4,5,6)' +const convertTransformMatrix = (transformMatrix) => { + var matrix = transformMatrix.join(',') + return `matrix3d(${matrix})` +} + +const processTransform = (style) => { + if (style) { + if (style.transform) { + style.transform = style.transform.map(mapTransform).join(' ') + } else if (style.transformMatrix) { + style.transformMatrix = convertTransformMatrix(style.transformMatrix) + } + } + return style +} + +module.exports = processTransform diff --git a/src/apis/UIManager/__tests__/index-test.js b/src/apis/UIManager/__tests__/index-test.js index e12cf88f..390712ad 100644 --- a/src/apis/UIManager/__tests__/index-test.js +++ b/src/apis/UIManager/__tests__/index-test.js @@ -73,8 +73,28 @@ suite('apis/UIManager', () => { }) }) + suite('measureInWindow', () => { + test('provides correct layout to callback', () => { + const node = createNode({ height: '10px', width: '10px' }) + const middle = createNode({ padding: '20px' }) + const context = createNode({ padding: '20px' }) + middle.appendChild(node) + context.appendChild(middle) + document.body.appendChild(context) + + UIManager.measureInWindow(node, (x, y, width, height) => { + assert.equal(x, 40) + assert.equal(y, 40) + assert.equal(width, 10) + assert.equal(height, 10) + }) + + document.body.removeChild(context) + }) + }) + suite('updateView', () => { - test('adds new className to existing className', () => { + test('add new className to existing className', () => { const node = createNode() node.className = 'existing' const props = { className: 'extra' } @@ -89,7 +109,20 @@ suite('apis/UIManager', () => { assert.equal(node.getAttribute('style'), 'color: red; opacity: 0;') }) - test('sets attribute values', () => { + test('replaces input and textarea text', () => { + const node = createNode() + node.value = 'initial' + const textProp = { text: 'expected-text' } + const valueProp = { value: 'expected-value' } + + UIManager.updateView(node, textProp) + assert.equal(node.value, 'expected-text') + + UIManager.updateView(node, valueProp) + assert.equal(node.value, 'expected-value') + }) + + test('sets other attribute values', () => { const node = createNode() const props = { 'aria-level': '4', 'data-of-type': 'string' } UIManager.updateView(node, props) diff --git a/src/apis/UIManager/index.js b/src/apis/UIManager/index.js index dc1d0888..3c21d4bb 100644 --- a/src/apis/UIManager/index.js +++ b/src/apis/UIManager/index.js @@ -1,6 +1,8 @@ import CSSPropertyOperations from 'react/lib/CSSPropertyOperations' +import flattenStyle from '../StyleSheet/flattenStyle' +import processTransform from '../StyleSheet/processTransform' -const measureAll = (node, callback, relativeToNativeNode) => { +const _measureLayout = (node, relativeToNativeNode, callback) => { const relativeNode = relativeToNativeNode || node.parentNode const relativeRect = relativeNode.getBoundingClientRect() const { height, left, top, width } = node.getBoundingClientRect() @@ -10,23 +12,52 @@ const measureAll = (node, callback, relativeToNativeNode) => { } const UIManager = { + blur(node) { + try { node.blur() } catch (err) {} + }, + + focus(node) { + try { node.focus() } catch (err) {} + }, + measure(node, callback) { - measureAll(node, callback) + _measureLayout(node, null, callback) + }, + + measureInWindow(node, callback) { + const { height, left, top, width } = node.getBoundingClientRect() + callback(left, top, width, height) }, measureLayout(node, relativeToNativeNode, onFail, onSuccess) { - measureAll(node, (x, y, width, height) => onSuccess(x, y, width, height), relativeToNativeNode) + const relativeTo = relativeToNativeNode || node.parentNode + _measureLayout(node, relativeTo, onSuccess) }, updateView(node, props) { for (const prop in props) { + let nativeProp const value = props[prop] - if (prop === 'style') { - CSSPropertyOperations.setValueForStyles(node, value) - } else if (prop === 'className') { - node.classList.add(value) - } else { - node.setAttribute(prop, value) + + switch(prop) { + case 'style': + // convert styles to DOM-styles + CSSPropertyOperations.setValueForStyles(node, processTransform(flattenStyle(value))) + break; + case 'class': + case 'className': + nativeProp = 'class' + // prevent class names managed by React Native from being replaced + const className = node.getAttribute(nativeProp) + ' ' + value + node.setAttribute(nativeProp, className) + break; + case 'text': + case 'value': + // native platforms use `text` prop to replace text input value + node.value = value + break; + default: + node.setAttribute(prop, value) } } }