wip: portal
This commit is contained in:
parent
178c7c827e
commit
453cdcd600
@ -14,7 +14,14 @@ import {
|
|||||||
createComponentInstance,
|
createComponentInstance,
|
||||||
setupStatefulComponent
|
setupStatefulComponent
|
||||||
} from './component'
|
} 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 { TEXT, CLASS, STYLE, PROPS, KEYED, UNKEYED } from './patchFlags'
|
||||||
import { queueJob, queuePostFlushCb, flushPostFlushCbs } from './scheduler'
|
import { queueJob, queuePostFlushCb, flushPostFlushCbs } from './scheduler'
|
||||||
import { effect, stop, ReactiveEffectOptions } from '@vue/observer'
|
import { effect, stop, ReactiveEffectOptions } from '@vue/observer'
|
||||||
@ -69,6 +76,7 @@ export interface RendererOptions {
|
|||||||
setElementText(node: HostNode, text: string): void
|
setElementText(node: HostNode, text: string): void
|
||||||
parentNode(node: HostNode): HostNode | null
|
parentNode(node: HostNode): HostNode | null
|
||||||
nextSibling(node: HostNode): HostNode | null
|
nextSibling(node: HostNode): HostNode | null
|
||||||
|
querySelector(selector: string): HostNode | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createRenderer(options: RendererOptions) {
|
export function createRenderer(options: RendererOptions) {
|
||||||
@ -82,7 +90,8 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
setText: hostSetText,
|
setText: hostSetText,
|
||||||
setElementText: hostSetElementText,
|
setElementText: hostSetElementText,
|
||||||
parentNode: hostParentNode,
|
parentNode: hostParentNode,
|
||||||
nextSibling: hostNextSibling
|
nextSibling: hostNextSibling,
|
||||||
|
querySelector: hostQuerySelector
|
||||||
} = options
|
} = options
|
||||||
|
|
||||||
function patch(
|
function patch(
|
||||||
@ -111,12 +120,15 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
processFragment(n1, n2, container, anchor, optimized)
|
processFragment(n1, n2, container, anchor, optimized)
|
||||||
break
|
break
|
||||||
case Portal:
|
case Portal:
|
||||||
// TODO
|
processPortal(n1, n2, container, anchor, optimized)
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
if (isString(type)) {
|
if (isString(type)) {
|
||||||
processElement(n1, n2, container, anchor, optimized)
|
processElement(n1, n2, container, anchor, optimized)
|
||||||
} else {
|
} else {
|
||||||
|
if (__DEV__ && !isFunction(type) && !isObject(type)) {
|
||||||
|
// TODO warn invalid node type
|
||||||
|
}
|
||||||
processComponent(n1, n2, container, anchor)
|
processComponent(n1, n2, container, anchor)
|
||||||
}
|
}
|
||||||
break
|
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(
|
function processComponent(
|
||||||
n1: VNode | null,
|
n1: VNode | null,
|
||||||
n2: VNode,
|
n2: VNode,
|
||||||
@ -409,8 +476,9 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
patch(
|
patch(
|
||||||
prevTree,
|
prevTree,
|
||||||
nextTree,
|
nextTree,
|
||||||
container || hostParentNode(prevTree.el),
|
// may have moved
|
||||||
anchor || getNextHostNode(prevTree)
|
hostParentNode(prevTree.el),
|
||||||
|
getNextHostNode(prevTree)
|
||||||
)
|
)
|
||||||
if (next != null) {
|
if (next != null) {
|
||||||
next.el = nextTree.el
|
next.el = nextTree.el
|
||||||
|
@ -4,8 +4,7 @@ export {
|
|||||||
createBlock,
|
createBlock,
|
||||||
createVNode,
|
createVNode,
|
||||||
Fragment,
|
Fragment,
|
||||||
Text,
|
Portal
|
||||||
Empty
|
|
||||||
} from './vnode'
|
} from './vnode'
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { isArray, isFunction } from '@vue/shared'
|
import { isArray, isFunction, EMPTY_ARR } from '@vue/shared'
|
||||||
import { ComponentInstance } from './component'
|
import { ComponentInstance } from './component'
|
||||||
import { HostNode } from './createRenderer'
|
import { HostNode } from './createRenderer'
|
||||||
|
|
||||||
@ -29,6 +29,7 @@ export interface VNode {
|
|||||||
// DOM
|
// DOM
|
||||||
el: HostNode | null
|
el: HostNode | null
|
||||||
anchor: HostNode | null // fragment anchor
|
anchor: HostNode | null // fragment anchor
|
||||||
|
target: HostNode | null // portal target
|
||||||
|
|
||||||
// optimization only
|
// optimization only
|
||||||
patchFlag: number | null
|
patchFlag: number | null
|
||||||
@ -59,7 +60,7 @@ export function createBlock(
|
|||||||
shouldTrack = true
|
shouldTrack = true
|
||||||
const trackedNodes = blockStack.pop()
|
const trackedNodes = blockStack.pop()
|
||||||
vnode.dynamicChildren =
|
vnode.dynamicChildren =
|
||||||
trackedNodes && trackedNodes.length ? trackedNodes : null
|
trackedNodes && trackedNodes.length ? trackedNodes : EMPTY_ARR
|
||||||
// a block is always going to be patched
|
// a block is always going to be patched
|
||||||
trackDynamicNode(vnode)
|
trackDynamicNode(vnode)
|
||||||
return vnode
|
return vnode
|
||||||
@ -81,6 +82,7 @@ export function createVNode(
|
|||||||
component: null,
|
component: null,
|
||||||
el: null,
|
el: null,
|
||||||
anchor: null,
|
anchor: null,
|
||||||
|
target: null,
|
||||||
patchFlag,
|
patchFlag,
|
||||||
dynamicProps,
|
dynamicProps,
|
||||||
dynamicChildren: null
|
dynamicChildren: null
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { RendererOptions } from '@vue/runtime-core'
|
import { RendererOptions } from '@vue/runtime-core'
|
||||||
import { patchProp } from './patchProp'
|
import { patchProp } from './patchProp'
|
||||||
|
|
||||||
|
const doc = document
|
||||||
const svgNS = 'http://www.w3.org/2000/svg'
|
const svgNS = 'http://www.w3.org/2000/svg'
|
||||||
|
|
||||||
export const DOMRendererOptions: RendererOptions = {
|
export const DOMRendererOptions: RendererOptions = {
|
||||||
@ -23,11 +24,11 @@ export const DOMRendererOptions: RendererOptions = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
createElement: (tag: string, isSVG?: boolean): Element =>
|
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) => {
|
setText: (node: Text, text: string) => {
|
||||||
node.nodeValue = text
|
node.nodeValue = text
|
||||||
@ -39,5 +40,7 @@ export const DOMRendererOptions: RendererOptions = {
|
|||||||
|
|
||||||
parentNode: (node: Node): Node | null => node.parentNode,
|
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)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user