]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: portal
authorEvan You <yyx990803@gmail.com>
Wed, 29 May 2019 08:10:25 +0000 (16:10 +0800)
committerEvan You <yyx990803@gmail.com>
Wed, 29 May 2019 08:10:25 +0000 (16:10 +0800)
packages/runtime-core/src/createRenderer.ts
packages/runtime-core/src/index.ts
packages/runtime-core/src/vnode.ts
packages/runtime-dom/src/rendererOptions.ts

index 2f433290bfcb9b7839327605f9bd396f8beca2f2..3edf4866f1f49ce28c3b74d43c03870cb20c28d9 100644 (file)
@@ -14,7 +14,14 @@ import {
   createComponentInstance,
   setupStatefulComponent
 } from './component'
-import { isString, isArray, EMPTY_OBJ, EMPTY_ARR } from '@vue/shared'
+import {
+  isString,
+  isArray,
+  isFunction,
+  isObject,
+  EMPTY_OBJ,
+  EMPTY_ARR
+} from '@vue/shared'
 import { TEXT, CLASS, STYLE, PROPS, KEYED, UNKEYED } from './patchFlags'
 import { queueJob, queuePostFlushCb, flushPostFlushCbs } from './scheduler'
 import { effect, stop, ReactiveEffectOptions } from '@vue/observer'
@@ -69,6 +76,7 @@ export interface RendererOptions {
   setElementText(node: HostNode, text: string): void
   parentNode(node: HostNode): HostNode | null
   nextSibling(node: HostNode): HostNode | null
+  querySelector(selector: string): HostNode | null
 }
 
 export function createRenderer(options: RendererOptions) {
@@ -82,7 +90,8 @@ export function createRenderer(options: RendererOptions) {
     setText: hostSetText,
     setElementText: hostSetElementText,
     parentNode: hostParentNode,
-    nextSibling: hostNextSibling
+    nextSibling: hostNextSibling,
+    querySelector: hostQuerySelector
   } = options
 
   function patch(
@@ -111,12 +120,15 @@ export function createRenderer(options: RendererOptions) {
         processFragment(n1, n2, container, anchor, optimized)
         break
       case Portal:
-        // TODO
+        processPortal(n1, n2, container, anchor, optimized)
         break
       default:
         if (isString(type)) {
           processElement(n1, n2, container, anchor, optimized)
         } else {
+          if (__DEV__ && !isFunction(type) && !isObject(type)) {
+            // TODO warn invalid node type
+          }
           processComponent(n1, n2, container, anchor)
         }
         break
@@ -340,6 +352,61 @@ export function createRenderer(options: RendererOptions) {
     }
   }
 
+  function processPortal(
+    n1: VNode | null,
+    n2: VNode,
+    container: HostNode,
+    anchor?: HostNode,
+    optimized?: boolean
+  ) {
+    const targetSelector = n2.props && n2.props.target
+    if (n1 == null) {
+      const children = n2.children
+      const target = (n2.target = isString(targetSelector)
+        ? hostQuerySelector(targetSelector)
+        : null)
+      if (target != null) {
+        if (isString(children)) {
+          hostSetElementText(target, children)
+        } else if (children != null) {
+          mountChildren(children, target)
+        }
+      } else {
+        // TODO warn missing or invalid target
+      }
+    } else {
+      // update content
+      const target = (n2.target = n1.target)
+      if (n2.patchFlag === TEXT) {
+        hostSetElementText(target, n2.children as string)
+      } else if (!optimized) {
+        patchChildren(n1, n2, target)
+      }
+      // target changed
+      if (targetSelector !== (n1.props && n1.props.target)) {
+        const nextTarget = (n2.target = isString(targetSelector)
+          ? hostQuerySelector(targetSelector)
+          : null)
+        if (nextTarget != null) {
+          // move content
+          const children = n2.children
+          if (isString(children)) {
+            hostSetElementText(target, '')
+            hostSetElementText(nextTarget, children)
+          } else if (children != null) {
+            for (let i = 0; i < children.length; i++) {
+              move(children[i] as VNode, nextTarget, null)
+            }
+          }
+        } else {
+          // TODO warn missing or invalid target
+        }
+      }
+    }
+    // insert an empty node as the placeholder for the portal
+    processEmptyNode(n1, n2, container, anchor)
+  }
+
   function processComponent(
     n1: VNode | null,
     n2: VNode,
@@ -409,8 +476,9 @@ export function createRenderer(options: RendererOptions) {
         patch(
           prevTree,
           nextTree,
-          container || hostParentNode(prevTree.el),
-          anchor || getNextHostNode(prevTree)
+          // may have moved
+          hostParentNode(prevTree.el),
+          getNextHostNode(prevTree)
         )
         if (next != null) {
           next.el = nextTree.el
index d212e935f7a5dd50d2864ebcd150827a66bde7de..9a40d995afca904314ed91ef2c55405b87c090ba 100644 (file)
@@ -4,8 +4,7 @@ export {
   createBlock,
   createVNode,
   Fragment,
-  Text,
-  Empty
+  Portal
 } from './vnode'
 
 export {
index 8e5afa4b74a97401c04d5d8539cbf28af72c5b96..6432b98116749b951c336effa1e2e736d95eb370 100644 (file)
@@ -1,4 +1,4 @@
-import { isArray, isFunction } from '@vue/shared'
+import { isArray, isFunction, EMPTY_ARR } from '@vue/shared'
 import { ComponentInstance } from './component'
 import { HostNode } from './createRenderer'
 
@@ -29,6 +29,7 @@ export interface VNode {
   // DOM
   el: HostNode | null
   anchor: HostNode | null // fragment anchor
+  target: HostNode | null // portal target
 
   // optimization only
   patchFlag: number | null
@@ -59,7 +60,7 @@ export function createBlock(
   shouldTrack = true
   const trackedNodes = blockStack.pop()
   vnode.dynamicChildren =
-    trackedNodes && trackedNodes.length ? trackedNodes : null
+    trackedNodes && trackedNodes.length ? trackedNodes : EMPTY_ARR
   // a block is always going to be patched
   trackDynamicNode(vnode)
   return vnode
@@ -81,6 +82,7 @@ export function createVNode(
     component: null,
     el: null,
     anchor: null,
+    target: null,
     patchFlag,
     dynamicProps,
     dynamicChildren: null
index 7ac294385610b3fb7dd2f4977950c67c29b67a76..af2325d05f1a57bbf23022631864e77555cebda3 100644 (file)
@@ -1,6 +1,7 @@
 import { RendererOptions } from '@vue/runtime-core'
 import { patchProp } from './patchProp'
 
+const doc = document
 const svgNS = 'http://www.w3.org/2000/svg'
 
 export const DOMRendererOptions: RendererOptions = {
@@ -23,11 +24,11 @@ export const DOMRendererOptions: RendererOptions = {
   },
 
   createElement: (tag: string, isSVG?: boolean): Element =>
-    isSVG ? document.createElementNS(svgNS, tag) : document.createElement(tag),
+    isSVG ? doc.createElementNS(svgNS, tag) : doc.createElement(tag),
 
-  createText: (text: string): Text => document.createTextNode(text),
+  createText: (text: string): Text => doc.createTextNode(text),
 
-  createComment: (text: string): Comment => document.createComment(text),
+  createComment: (text: string): Comment => doc.createComment(text),
 
   setText: (node: Text, text: string) => {
     node.nodeValue = text
@@ -39,5 +40,7 @@ export const DOMRendererOptions: RendererOptions = {
 
   parentNode: (node: Node): Node | null => node.parentNode,
 
-  nextSibling: (node: Node): Node | null => node.nextSibling
+  nextSibling: (node: Node): Node | null => node.nextSibling,
+
+  querySelector: (selector: string): Node | null => doc.querySelector(selector)
 }