luda
Version:
A library helps to build cross-framework UI components.
120 lines (105 loc) • 4.47 kB
text/coffeescript
import arrayEqual from '../../base/array-equal.coffee'
import expando from '../../base/expando.coffee'
import find from '../../base/find.coffee'
import findAll from '../../base/find-all.coffee'
import matches from '../../base/matches.coffee'
import splitValues from '../../base/split-values.coffee'
import Type from '../../base/type.coffee'
import unique from '../../base/unique.coffee'
import log from '../../log/log.coffee'
import unnested from './unnested.coffee'
config =
childList: true
subtree: true
attributes: true
attributeOldValue: true
cur = (ins, callback, target) ->
proto = ins.constructor.prototype
isInProto = Object.values(proto).includes callback
if isInProto then ins else target
runNodeCallbacks = (type, mutation, watches, nestable) ->
ins = mutation.ins
C = ins.constructor
mu = mutation.mu
nodes = Array.from mu["#{type}Nodes"]
watches.node.forEach (node) ->
els = []
nodes.forEach (n) -> els = els.concat(findAll node.selector, n)
return unless els.length
not nestable and els = unnested ins, unique(els)
els.length and node.callbacks.forEach (callback) ->
ctx = cur ins, callback, els
unless callback is C.prototype.cleanTraversal
log "#{C.id} ID: #{ins.id} executes nodes #{type} callback.",
'Root element', ins.root.els[0], 'Cache', C.instances[ins.id],
"Nodes #{type}", els, "Callback #{callback.name or ''}", callback,
'Context', ctx, 'Arguments', [els, type]
callback.call ctx, els, type
runAttrCallbacks = (mutation, watches, nestable) ->
ins = mutation.ins
mu = mutation.mu
C = ins.constructor
name = mu.attributeName
target = mu.target
oldVal = mu.oldValue
return unless name and Type.isElement target
return if not nestable and not unnested(ins, [target]).length
watches.attr.forEach (attr) ->
return unless attr.name.includes name
return unless matches target, attr.selector
attr.callbacks.forEach (callback) ->
ctx = cur(ins, callback, target)
log "#{C.id} ID: #{ins.id} executes #{name} changed callback.",
'Root element', ins.root.els[0], 'Cache', C.instances[ins.id],
'Changed target', target, "Callback #{callback.name or ''}", callback,
'Context', ctx, 'Arguments', [target, oldVal]
callback.call ctx, target, oldVal
executeMutations = (C, mutations, nestable) ->
mutations.forEach (mutation) ->
runNodeCallbacks 'added', mutation, C.watches, nestable
runNodeCallbacks 'removed', mutation, C.watches, nestable
runAttrCallbacks mutation, C.watches, nestable
nodesEqual = (nodesOne, nodesTwo) ->
arrayEqual Array.from(nodesOne), Array.from(nodesTwo), true
findSameMutation = (mutations, mu) ->
theSameMutation = null
mutations.some (mutation) ->
return theSameMutation = mutation if mu is mutation.mu
return unless mu.type is mutation.mu.type
return unless mu.target is mutation.mu.target
return unless mu.attributeName is mutation.mu.attributeName
return unless mu.oldValue is mutation.mu.oldValue
return unless nodesEqual mu.addedNodes, mutation.mu.addedNodes
return unless nodesEqual mu.removedNodes, mutation.mu.removedNodes
theSameMutation = mutation
theSameMutation
createObserver = (C, instance) ->
inses = C.instances
rootEl = instance.root.els[0]
nestable = Type.isDocument C.root
observer = new MutationObserver (mus) ->
mutations = mus.map (mu) -> {ins: instance, mu: mu}
not nestable and find(C.root, rootEl).forEach (el) ->
return unless cached = inses[el[expando]]
return unless ins = cached.instance
return unless watcher = cached.watcher
watcher.takeRecords().forEach (mu) ->
nestedMutation = findSameMutation mutations, mu
nestedMutation.ins = ins if nestedMutation
executeMutations C, mutations, nestable
observer.observe rootEl, config
observer
watch = (C, ins) ->
unless C.watches
conf = C.helpers.watch.call C.prototype
C.watches =
node: (conf.node or []).map (d) ->
selector: if Type.isFunction d[0] then '*' else d[0]
callbacks: if Type.isFunction d[0] then d else d.slice 1
attr: (conf.attr or []).map (a) ->
name: splitValues a[0]
selector: if Type.isFunction a[1] then '*' else a[1]
callbacks: if Type.isFunction a[1] then a.slice 1 else a.slice 2
createObserver C, ins
stopWatch = (ins, watcher) -> watcher.disconnect()
export {watch, stopWatch}