function prepatch(oldVNode: VNode, newVNode: VNode) { const animations = newVNode.data.animations as Animations const oldChildren = oldVNode.children || [] const newChildren = newVNode.children || [] const oldKeys = lift(oldChildren).map(c => c.key || '').toSet().value() const newKeys = lift(newChildren).map(c => c.key || '').toSet().value() // children making an exit oldChildren.forEach(child => { if (newKeys[child.key || '']) return child.data.hook = child.data.hook || {} const otherHook = child.data.hook.remove child.data.hook.remove = (vnode: VNode.Assigned, cb: Function) => { if (otherHook) otherHook(vnode, noop) animations.remove(vnode.elm, cb) } }) // children making an entrance newChildren.forEach(child => { if (oldKeys[child.key || '']) return child.data.hook = child.data.hook || {} const otherHook = child.data.hook.create child.data.hook.create = (emptyNode: {}, vnode: VNode.Assigned) => { if (otherHook) otherHook(emptyNode, vnode) animations.create(vnode.elm) } }) }
// Translate our RouteDefs into abyssa States function transformRouteTree( name: string, route: RouteDef<RT>, parent: RouteDef<RT> | undefined = undefined ): State { routeByName[name] = route route.parent = parent route.fullName = name const children = route.children ? lift(route.children) .mapValues((childName, childRoute) => transformRouteTree(`${name}.${childName}`, childRoute, route)) .value() : {} return State(route.uri, { enter(_, __, router) { components.push( route.enter(router, currentRoute!)) }, update() { if (route.update) route.update(currentRoute!) }, exit() { components.pop() if (route.exit) route.exit() } }, children) }
export function startApp<RT extends string>(options: RouterOptions<RT>): void { // The lookup of our custom route objects by full name const routeByName: Record<string, RouteDef<RT>> = {} // The components currently mounted, top-down const components: Array<(route: Route<RT>, child: VNode) => VNode> = [] // The current route in the transition let currentRoute: Route<RT> | undefined // The current app VNode let currentVNode: VNode | undefined // Translate our RouteDefs into abyssa States function transformRouteTree( name: string, route: RouteDef<RT>, parent: RouteDef<RT> | undefined = undefined ): State { routeByName[name] = route route.parent = parent route.fullName = name const children = route.children ? lift(route.children) .mapValues((childName, childRoute) => transformRouteTree(`${name}.${childName}`, childRoute, route)) .value() : {} return State(route.uri, { enter(_, __, router) { components.push( route.enter(router, currentRoute!)) }, update() { if (route.update) route.update(currentRoute!) }, exit() { components.pop() if (route.exit) route.exit() } }, children) } const rootStates = lift({ app: options.app }).mapValues(transformRouteTree).value() const router = AbyssaRouter(rootStates) const abyssaOptions = Object.assign({}, options, { notFound: options.notFound && options.notFound.fullName }) router.configure(abyssaOptions) router.on('started', newState => { currentRoute = Object.assign({}, newState, { fullName: newState.fullName.replace('app.', ''), isIn: (parent: string) => newState.isIn(`app.${ parent }`) }) }) router.on('ended', () => { const newAppNode = components.reduceRight((previous, current) => { return current(currentRoute!, previous) }, emptyVNode()) if (currentVNode) { Render.into(currentVNode, newAppNode) } else { startKaijuApp({ app: newAppNode, elm: options.elm, replaceElm: options.replaceElm, snabbdomModules: options.snabbdomModules }) } currentVNode = newAppNode }) router.init() }