Skip to content

Commit a540c75

Browse files
committed
feat: add moveChildren, style processing
add in moveChild bridge method to move from one index to other on the react native layer for existing children also split up updateChildren to append and remove finally adds style processing to the style handling using native view config
1 parent 8a7fe7c commit a540c75

File tree

2 files changed

+167
-45
lines changed

2 files changed

+167
-45
lines changed

dom/src/dom.js

Lines changed: 163 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { UIManager, DevSettings } from 'react-native'
1+
import { UIManager } from 'react-native'
2+
import getNativeComponentAttributes from 'react-native/Libraries/ReactNative/getNativeComponentAttributes'
23

34
/**
45
* @todo: figure out a way to handle refresh based renders
@@ -12,11 +13,12 @@ const CURRENT_STYLE = Symbol.for('current')
1213
const OWNER_NODE = Symbol.for('owner')
1314

1415
const BINDINGS = new Map()
15-
const INSTANCES = new WeakMap()
16-
const NODES = new WeakMap()
16+
let INSTANCES = new WeakMap()
17+
let NODES = new WeakMap()
1718

1819
let VIEWS_RENDERED = false
1920
let pChain = Promise.resolve()
21+
let processing = false
2022
let renderQ = []
2123

2224
export const REACT_ELEMENT_TYPE =
@@ -41,6 +43,13 @@ const bridge = {
4143
const node = NODES.get(binding)
4244

4345
switch (method) {
46+
case 'clear': {
47+
const childIndices = (node.childNodes || []).map((_, i) => i)
48+
try {
49+
UIManager.manageChildren(ROOT_TAG, [], [], [], [], childIndices)
50+
} catch (err) {}
51+
break
52+
}
4453
case 'create': {
4554
const type = params[1]
4655
if (type === '#document') {
@@ -65,18 +74,39 @@ const bridge = {
6574
updateNodeProps(id)
6675
break
6776
}
68-
case 'updateChildren': {
77+
case 'moveChild': {
78+
const fromIndex = params[1]
79+
const toIndex = params[2]
80+
UIManager.manageChildren(
81+
id, // containerID
82+
[fromIndex], // moveFromIndices
83+
[toIndex], // moveToIndices
84+
[], // addChildReactTags
85+
[], // addAtIndices
86+
[] // removeAtIndices
87+
)
88+
break
89+
}
90+
case 'appendChild': {
91+
let parentTag = id
92+
const toAdd = params[1]
6993
if (binding.type === '#document') {
70-
UIManager.setChildren(
71-
ROOT_TAG,
72-
node.children.map(x => x[BINDING].id)
73-
)
74-
} else {
75-
UIManager.setChildren(
76-
binding.id,
77-
node.children.map(x => x[BINDING].id)
78-
)
94+
parentTag = ROOT_TAG
95+
console.log('appending to root', ROOT_TAG)
7996
}
97+
UIManager.setChildren(parentTag, [toAdd])
98+
break
99+
}
100+
case 'removeChild': {
101+
const removeAt = params[1]
102+
UIManager.manageChildren(
103+
id, // containerID
104+
[], // moveFromIndices
105+
[], // moveToIndices
106+
[], // addChildReactTags
107+
[], // addAtIndices
108+
[removeAt] // removeAtIndices
109+
)
80110
break
81111
}
82112
}
@@ -88,7 +118,10 @@ const bridge = {
88118
params,
89119
}) === 1
90120
) {
91-
pChain.then(process)
121+
if (typeof ROOT_TAG != null && !processing) {
122+
processing = true
123+
pChain = pChain.then(process)
124+
}
92125
}
93126
},
94127
}
@@ -130,13 +163,30 @@ class Node {
130163

131164
appendChild(node) {
132165
node.parent = this
133-
this.children.push(node)
134-
this[BINDING].updateChildren()
166+
const existingChild = this.children.findIndex(
167+
x => x[BINDING].id === node[BINDING].id
168+
)
169+
if (existingChild > -1) {
170+
this.children.splice(existingChild, 1)
171+
this.children.push(node)
172+
this[BINDING].moveChild(existingChild, this.children.length - 1)
173+
} else {
174+
this.children.push(node)
175+
this[BINDING].appendChild(node[BINDING].id)
176+
}
135177
}
136178

137179
removeChild(node) {
138-
this.children.filter(x => x[BINDING].id === node[BINDING].id)
139-
this[BINDING].updateChildren()
180+
let index = -1
181+
this.children.filter((x, i) => {
182+
if (x[BINDING].id === node[BINDING].id) {
183+
index = i
184+
}
185+
return x[BINDING].id !== node[BINDING].id
186+
})
187+
if (index > -1) {
188+
this[BINDING].removeChild(index)
189+
}
140190
}
141191

142192
get ref() {
@@ -148,9 +198,12 @@ class Node {
148198
}
149199

150200
class Element extends Node {
151-
constructor(type) {
201+
constructor(type, reset) {
152202
super(type)
153203
this.style = createStyleBinding(this[BINDING].id)
204+
if (reset) {
205+
this[BINDING].clear()
206+
}
154207
this[BINDING].create()
155208
}
156209

@@ -240,7 +293,6 @@ class Element extends Node {
240293
ref: x => {
241294
if (!VIEWS_RENDERED) {
242295
VIEWS_RENDERED = true
243-
pChain.then(process)
244296
}
245297
_self.ref = x
246298
INSTANCES.set(this[BINDING], x)
@@ -280,7 +332,7 @@ class Text extends Node {
280332
export class Document extends Element {
281333
constructor(rootTag) {
282334
ROOT_TAG = rootTag
283-
super('#document')
335+
super('#document', true)
284336
}
285337

286338
createElement(type) {
@@ -310,12 +362,20 @@ export function render(node) {
310362
}
311363

312364
function createBinding(node) {
313-
const id = ++bridge.currentId
365+
let nextId = ++bridge.currentId
366+
if (nextId === ROOT_TAG) {
367+
nextId = ++bridge.currentId
368+
}
369+
const id = nextId
314370
const props = new Map()
315371
return {
316372
id,
317373
props,
318374
type: node.localName,
375+
clear() {
376+
renderQ = []
377+
bridge.enqueue('clear', [id])
378+
},
319379
create() {
320380
bridge.enqueue('create', [id, node.localName])
321381
},
@@ -333,26 +393,98 @@ function createBinding(node) {
333393
}
334394
return res
335395
},
336-
updateChildren() {
337-
bridge.enqueue('updateChildren', [id])
396+
moveChild(x, y) {
397+
bridge.enqueue('moveChild', [id, x, y])
398+
},
399+
appendChild(nodeId) {
400+
bridge.enqueue('appendChild', [id, nodeId])
401+
},
402+
removeChild(atIndex) {
403+
bridge.enqueue('removeChild', [id, atIndex])
338404
},
339405
}
340406
}
341407

342408
function process() {
343-
let toProcess
344-
while ((toProcess = renderQ.shift())) {
345-
bridge.call(toProcess.method, toProcess.params)
409+
let methodDef = renderQ.shift()
410+
if (methodDef) {
411+
dispatch(methodDef)
412+
setTimeout(() => {
413+
process()
414+
}, 10)
415+
} else {
416+
processing = false
346417
}
347418
}
348419

420+
function dispatch(methodDef) {
421+
bridge.call(methodDef.method, methodDef.params)
422+
}
423+
349424
function updateNodeProps(id) {
350425
const binding = BINDINGS.get(id)
351426
const instance = INSTANCES.get(binding)
352427
const props = Object.fromEntries(binding.props)
428+
353429
if (instance) {
354430
instance.setNativeProps(props)
355431
}
432+
433+
if (TYPES[binding.type]) {
434+
const managerName = TYPES[binding.type].type
435+
const viewConfig = getNativeComponentAttributes(managerName)
436+
const validProps = processProps(props, viewConfig.validAttributes)
437+
UIManager.updateView(id, viewConfig.uiViewClassName, validProps)
438+
}
439+
}
440+
441+
function processProps(props, validAttributes) {
442+
const result = {}
443+
444+
for (var key in props) {
445+
if (!validAttributes[key]) {
446+
continue
447+
}
448+
449+
if (key == 'style') {
450+
normalizeStyle(props[key])
451+
}
452+
453+
const propItem = props[key]
454+
const config = validAttributes[key]
455+
456+
if (typeof propItem == 'undefined') {
457+
continue
458+
}
459+
result[key] = propItem
460+
461+
if (typeof propItem == 'object' && typeof config == 'object') {
462+
result[key] = processProps(propItem, config)
463+
}
464+
465+
if (typeof config.process == 'function') {
466+
result[key] = config.process(propItem)
467+
}
468+
}
469+
470+
// flatten styles to the top level props
471+
Object.assign(result, result.style)
472+
473+
return result
474+
}
475+
476+
function normalizeStyle(styleProps) {
477+
for (let key in styleProps) {
478+
const old = styleProps[key]
479+
try {
480+
styleProps[key] = styleProps[key].replace(/(px)/g, '')
481+
if (!isNaN(styleProps[key])) {
482+
styleProps[key] = Number(styleProps[key])
483+
}
484+
} catch (err) {
485+
styleProps[key] = old
486+
}
487+
}
356488
}
357489

358490
function updateTextNode(id) {
@@ -387,19 +519,9 @@ function createStyleBinding(id) {
387519
return new Proxy(style, STYLE_PROXY)
388520
}
389521

390-
function onRefresh(cb) {
391-
if (__DEV__) {
392-
let oldRefresh = DevSettings.onFastRefresh
393-
DevSettings.onFastRefresh = () => {
394-
cb && cb()
395-
oldRefresh && oldRefresh()
396-
}
397-
}
398-
}
399-
400-
function noop() {}
401-
402522
export function createDOM(rootTag) {
403-
onRefresh(noop)
404-
return new Document(rootTag)
523+
bridge.currentId = 0
524+
BINDINGS.clear()
525+
const rootNode = new Document(rootTag)
526+
return rootNode
405527
}

example/App.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@ class RenderableComponent extends Component {
2020
}
2121

2222
componentDidMount() {
23-
setInterval(() => {
24-
this.setState({ count: this.state.count + 1 })
25-
}, 1000)
23+
// setInterval(() => {
24+
// this.setState({ count: this.state.count + 1 })
25+
// }, 1000)
2626
}
2727

2828
render() {
2929
return h('SafeAreaView', {}, h('Text', {}, `Count ${this.state.count}`))
3030
}
3131
}
3232

33-
export default App
33+
export default App

0 commit comments

Comments
 (0)