type Cache = Map<CacheKey, VaporComponentInstance | VaporFragment>
type Keys = Set<CacheKey>
-export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({
- name: 'VaporKeepAlive',
- __isKeepAlive: true,
- props: {
- include: [String, RegExp, Array],
- exclude: [String, RegExp, Array],
- max: [String, Number],
- },
- setup(props: KeepAliveProps, { slots }) {
- if (!slots.default) {
- return undefined
- }
-
- const keepAliveInstance = currentInstance! as KeepAliveInstance
- const cache: Cache = new Map()
- const keys: Keys = new Set()
- const storageContainer = createElement('div')
- const keptAliveScopes = new Map<any, EffectScope>()
- let current: VaporComponentInstance | VaporFragment | undefined
+export const VaporKeepAliveImpl: ObjectVaporComponent =
+ /*@__PURE__*/ defineVaporComponent({
+ name: 'VaporKeepAlive',
+ __isKeepAlive: true,
+ props: {
+ include: [String, RegExp, Array],
+ exclude: [String, RegExp, Array],
+ max: [String, Number],
+ },
+ setup(props: KeepAliveProps, { slots }) {
+ if (!slots.default) {
+ return undefined
+ }
- if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
- ;(keepAliveInstance as any).__v_cache = cache
- }
+ const keepAliveInstance = currentInstance! as KeepAliveInstance
+ const cache: Cache = new Map()
+ const keys: Keys = new Set()
+ const storageContainer = createElement('div')
+ const keptAliveScopes = new Map<any, EffectScope>()
+ let current: VaporComponentInstance | VaporFragment | undefined
- keepAliveInstance.getStorageContainer = () => storageContainer
+ if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
+ ;(keepAliveInstance as any).__v_cache = cache
+ }
- keepAliveInstance.getCachedComponent = comp => cache.get(comp)
+ keepAliveInstance.getStorageContainer = () => storageContainer
- keepAliveInstance.activate = (instance, parentNode, anchor) => {
- current = instance
- activate(instance, parentNode, anchor)
- }
+ keepAliveInstance.getCachedComponent = comp => cache.get(comp)
- keepAliveInstance.deactivate = instance => {
- current = undefined
- deactivate(instance, storageContainer)
- }
+ keepAliveInstance.activate = (instance, parentNode, anchor) => {
+ current = instance
+ activate(instance, parentNode, anchor)
+ }
- const innerCacheBlock = (
- key: CacheKey,
- instance: VaporComponentInstance | VaporFragment,
- ) => {
- const { max } = props
+ keepAliveInstance.deactivate = instance => {
+ current = undefined
+ deactivate(instance, storageContainer)
+ }
- if (cache.has(key)) {
- // make this key the freshest
- keys.delete(key)
- keys.add(key)
- } else {
- keys.add(key)
- // prune oldest entry
- if (max && keys.size > parseInt(max as string, 10)) {
- pruneCacheEntry(keys.values().next().value!)
+ const innerCacheBlock = (
+ key: CacheKey,
+ instance: VaporComponentInstance | VaporFragment,
+ ) => {
+ const { max } = props
+
+ if (cache.has(key)) {
+ // make this key the freshest
+ keys.delete(key)
+ keys.add(key)
+ } else {
+ keys.add(key)
+ // prune oldest entry
+ if (max && keys.size > parseInt(max as string, 10)) {
+ pruneCacheEntry(keys.values().next().value!)
+ }
}
- }
- cache.set(key, instance)
- current = instance
- }
+ cache.set(key, instance)
+ current = instance
+ }
- const cacheBlock = () => {
- // TODO suspense
- const block = keepAliveInstance.block!
- const [innerBlock, interop] = getInnerBlock(block)
- if (!innerBlock || !shouldCache(innerBlock, props, interop)) return
- innerCacheBlock(
- interop ? innerBlock.vnode!.type : innerBlock.type,
- innerBlock,
- )
- }
+ const cacheBlock = () => {
+ // TODO suspense
+ const block = keepAliveInstance.block!
+ const [innerBlock, interop] = getInnerBlock(block)
+ if (!innerBlock || !shouldCache(innerBlock, props, interop)) return
+ innerCacheBlock(
+ interop ? innerBlock.vnode!.type : innerBlock.type,
+ innerBlock,
+ )
+ }
- const processFragment = (frag: DynamicFragment) => {
- const [innerBlock, interop] = getInnerBlock(frag.nodes)
- if (!innerBlock || !shouldCache(innerBlock!, props, interop)) return false
+ const processFragment = (frag: DynamicFragment) => {
+ const [innerBlock, interop] = getInnerBlock(frag.nodes)
+ if (!innerBlock || !shouldCache(innerBlock!, props, interop))
+ return false
- if (interop) {
- if (cache.has(innerBlock.vnode!.type)) {
- innerBlock.vnode!.shapeFlag! |= ShapeFlags.COMPONENT_KEPT_ALIVE
- }
- innerBlock.vnode!.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
- } else {
- if (cache.has(innerBlock!.type)) {
- innerBlock!.shapeFlag! |= ShapeFlags.COMPONENT_KEPT_ALIVE
+ if (interop) {
+ if (cache.has(innerBlock.vnode!.type)) {
+ innerBlock.vnode!.shapeFlag! |= ShapeFlags.COMPONENT_KEPT_ALIVE
+ }
+ innerBlock.vnode!.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
+ } else {
+ if (cache.has(innerBlock!.type)) {
+ innerBlock!.shapeFlag! |= ShapeFlags.COMPONENT_KEPT_ALIVE
+ }
+ innerBlock!.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
}
- innerBlock!.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
+ return true
}
- return true
- }
-
- const cacheFragment = (fragment: DynamicFragment) => {
- const [innerBlock, interop] = getInnerBlock(fragment.nodes)
- if (!innerBlock || !shouldCache(innerBlock, props, interop)) return
-
- let key: CacheKey
- if (interop) {
- innerBlock.vnode!.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
- key = innerBlock.vnode!.type
- } else {
- innerBlock.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
- key = innerBlock.type
- }
- innerCacheBlock(key, innerBlock)
- }
- const pruneCache = (filter: (name: string) => boolean) => {
- cache.forEach((cached, key) => {
- const instance = getInstanceFromCache(cached)
- if (!instance) return
- const name = getComponentName(instance.type)
- if (name && !filter(name)) {
- pruneCacheEntry(key)
+ const cacheFragment = (fragment: DynamicFragment) => {
+ const [innerBlock, interop] = getInnerBlock(fragment.nodes)
+ if (!innerBlock || !shouldCache(innerBlock, props, interop)) return
+
+ let key: CacheKey
+ if (interop) {
+ innerBlock.vnode!.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
+ key = innerBlock.vnode!.type
+ } else {
+ innerBlock.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
+ key = innerBlock.type
}
- })
- }
-
- const pruneCacheEntry = (key: CacheKey) => {
- const cached = cache.get(key)!
-
- resetCachedShapeFlag(cached)
+ innerCacheBlock(key, innerBlock)
+ }
- // don't unmount if the instance is the current one
- if (cached !== current) {
- remove(cached)
+ const pruneCache = (filter: (name: string) => boolean) => {
+ cache.forEach((cached, key) => {
+ const instance = getInstanceFromCache(cached)
+ if (!instance) return
+ const name = getComponentName(instance.type)
+ if (name && !filter(name)) {
+ pruneCacheEntry(key)
+ }
+ })
}
- cache.delete(key)
- keys.delete(key)
- }
- // prune cache on include/exclude prop change
- watch(
- () => [props.include, props.exclude],
- ([include, exclude]) => {
- include && pruneCache(name => matches(include, name))
- exclude && pruneCache(name => !matches(exclude, name))
- },
- // prune post-render after `current` has been updated
- { flush: 'post', deep: true },
- )
-
- onMounted(cacheBlock)
- onUpdated(cacheBlock)
- onBeforeUnmount(() => {
- cache.forEach((cached, key) => {
- const instance = getInstanceFromCache(cached)
- if (!instance) return
+ const pruneCacheEntry = (key: CacheKey) => {
+ const cached = cache.get(key)!
resetCachedShapeFlag(cached)
+
+ // don't unmount if the instance is the current one
+ if (cached !== current) {
+ remove(cached)
+ }
cache.delete(key)
+ keys.delete(key)
+ }
+
+ // prune cache on include/exclude prop change
+ watch(
+ () => [props.include, props.exclude],
+ ([include, exclude]) => {
+ include && pruneCache(name => matches(include, name))
+ exclude && pruneCache(name => !matches(exclude, name))
+ },
+ // prune post-render after `current` has been updated
+ { flush: 'post', deep: true },
+ )
- // current instance will be unmounted as part of keep-alive's unmount
- if (current) {
- const currentKey = isVaporComponent(current)
- ? current.type
- : current.vnode!.type
- if (currentKey === key) {
- // call deactivated hook
- const da = instance.da
- da && queuePostFlushCb(da)
- return
+ onMounted(cacheBlock)
+ onUpdated(cacheBlock)
+ onBeforeUnmount(() => {
+ cache.forEach((cached, key) => {
+ const instance = getInstanceFromCache(cached)
+ if (!instance) return
+
+ resetCachedShapeFlag(cached)
+ cache.delete(key)
+
+ // current instance will be unmounted as part of keep-alive's unmount
+ if (current) {
+ const currentKey = isVaporComponent(current)
+ ? current.type
+ : current.vnode!.type
+ if (currentKey === key) {
+ // call deactivated hook
+ const da = instance.da
+ da && queuePostFlushCb(da)
+ return
+ }
}
- }
- remove(cached, storageContainer)
+ remove(cached, storageContainer)
+ })
+ keptAliveScopes.forEach(scope => scope.stop())
+ keptAliveScopes.clear()
})
- keptAliveScopes.forEach(scope => scope.stop())
- keptAliveScopes.clear()
- })
-
- let children = slots.default()
- if (isArray(children)) {
- children = children.filter(child => !(child instanceof Comment))
- if (children.length > 1) {
- if (__DEV__) {
- warn(`KeepAlive should contain exactly one component child.`)
+
+ let children = slots.default()
+ if (isArray(children)) {
+ children = children.filter(child => !(child instanceof Comment))
+ if (children.length > 1) {
+ if (__DEV__) {
+ warn(`KeepAlive should contain exactly one component child.`)
+ }
+ return children
}
- return children
}
- }
- // inject hooks to DynamicFragment to cache components during updates
- const injectKeepAliveHooks = (frag: DynamicFragment) => {
- ;(frag.onBeforeTeardown || (frag.onBeforeTeardown = [])).push(
- (oldKey, nodes, scope) => {
- // if the fragment's nodes include a component that should be cached
- // return true to avoid tearing down the fragment's scope
- if (processFragment(frag)) {
- keptAliveScopes.set(oldKey, scope)
- return true
+ // inject hooks to DynamicFragment to cache components during updates
+ const injectKeepAliveHooks = (frag: DynamicFragment) => {
+ ;(frag.onBeforeTeardown || (frag.onBeforeTeardown = [])).push(
+ (oldKey, nodes, scope) => {
+ // if the fragment's nodes include a component that should be cached
+ // return true to avoid tearing down the fragment's scope
+ if (processFragment(frag)) {
+ keptAliveScopes.set(oldKey, scope)
+ return true
+ }
+ return false
+ },
+ )
+ ;(frag.onBeforeMount || (frag.onBeforeMount = [])).push(() =>
+ cacheFragment(frag),
+ )
+ frag.getScope = key => {
+ const scope = keptAliveScopes.get(key)
+ if (scope) {
+ keptAliveScopes.delete(key)
+ return scope
}
- return false
- },
- )
- ;(frag.onBeforeMount || (frag.onBeforeMount = [])).push(() =>
- cacheFragment(frag),
- )
- frag.getScope = key => {
- const scope = keptAliveScopes.get(key)
- if (scope) {
- keptAliveScopes.delete(key)
- return scope
}
}
- }
- // process shapeFlag
- if (isVaporComponent(children)) {
- children.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
- if (isAsyncWrapper(children)) {
- injectKeepAliveHooks(children.block as DynamicFragment)
- }
- } else if (isInteropFragment(children)) {
- children.vnode!.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
- } else if (isDynamicFragment(children)) {
- processFragment(children)
- injectKeepAliveHooks(children)
- if (isVaporComponent(children.nodes) && isAsyncWrapper(children.nodes)) {
- injectKeepAliveHooks(children.nodes.block as DynamicFragment)
+ // process shapeFlag
+ if (isVaporComponent(children)) {
+ children.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
+ if (isAsyncWrapper(children)) {
+ injectKeepAliveHooks(children.block as DynamicFragment)
+ }
+ } else if (isInteropFragment(children)) {
+ children.vnode!.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
+ } else if (isDynamicFragment(children)) {
+ processFragment(children)
+ injectKeepAliveHooks(children)
+ if (
+ isVaporComponent(children.nodes) &&
+ isAsyncWrapper(children.nodes)
+ ) {
+ injectKeepAliveHooks(children.nodes.block as DynamicFragment)
+ }
}
- }
- return children
- },
-})
+ return children
+ },
+ })
const shouldCache = (
block: GenericComponentInstance | VaporFragment,
return t
}
-export const VaporTransitionGroup: ObjectVaporComponent = decorate({
- name: 'VaporTransitionGroup',
-
- props: /*@__PURE__*/ extend({}, TransitionPropsValidators, {
- tag: String,
- moveClass: String,
- }),
-
- setup(props: TransitionGroupProps, { slots }) {
- const instance = currentInstance as VaporComponentInstance
- const state = useTransitionState()
-
- // use proxy to keep props reference stable
- let cssTransitionProps = resolveTransitionProps(props)
- const propsProxy = new Proxy({} as typeof cssTransitionProps, {
- get(_, key) {
- return cssTransitionProps[key as keyof typeof cssTransitionProps]
- },
- })
-
- renderEffect(() => {
- cssTransitionProps = resolveTransitionProps(props)
- })
-
- let prevChildren: TransitionBlock[]
- let children: TransitionBlock[]
- const slottedBlock = slots.default && slots.default()
-
- onBeforeUpdate(() => {
- prevChildren = []
- children = getTransitionBlocks(slottedBlock)
- if (children) {
- for (let i = 0; i < children.length; i++) {
- const child = children[i]
- if (isValidTransitionBlock(child)) {
- prevChildren.push(child)
- // disabled transition during enter, so the children will be
- // inserted into the correct position immediately. this prevents
- // `recordPosition` from getting incorrect positions in `onUpdated`
- child.$transition!.disabled = true
- positionMap.set(
- child,
- getTransitionElement(child).getBoundingClientRect(),
- )
+export const VaporTransitionGroup: ObjectVaporComponent =
+ /*@__PURE__*/ decorate({
+ name: 'VaporTransitionGroup',
+
+ props: /*@__PURE__*/ extend({}, TransitionPropsValidators, {
+ tag: String,
+ moveClass: String,
+ }),
+
+ setup(props: TransitionGroupProps, { slots }) {
+ const instance = currentInstance as VaporComponentInstance
+ const state = useTransitionState()
+
+ // use proxy to keep props reference stable
+ let cssTransitionProps = resolveTransitionProps(props)
+ const propsProxy = new Proxy({} as typeof cssTransitionProps, {
+ get(_, key) {
+ return cssTransitionProps[key as keyof typeof cssTransitionProps]
+ },
+ })
+
+ renderEffect(() => {
+ cssTransitionProps = resolveTransitionProps(props)
+ })
+
+ let prevChildren: TransitionBlock[]
+ let children: TransitionBlock[]
+ const slottedBlock = slots.default && slots.default()
+
+ onBeforeUpdate(() => {
+ prevChildren = []
+ children = getTransitionBlocks(slottedBlock)
+ if (children) {
+ for (let i = 0; i < children.length; i++) {
+ const child = children[i]
+ if (isValidTransitionBlock(child)) {
+ prevChildren.push(child)
+ // disabled transition during enter, so the children will be
+ // inserted into the correct position immediately. this prevents
+ // `recordPosition` from getting incorrect positions in `onUpdated`
+ child.$transition!.disabled = true
+ positionMap.set(
+ child,
+ getTransitionElement(child).getBoundingClientRect(),
+ )
+ }
}
}
- }
- })
+ })
- onUpdated(() => {
- if (!prevChildren.length) {
- return
- }
- const moveClass = props.moveClass || `${props.name || 'v'}-move`
- const firstChild = getFirstConnectedChild(prevChildren)
- if (
- !firstChild ||
- !hasCSSTransform(
- firstChild as ElementWithTransition,
- firstChild.parentNode as Node,
- moveClass,
+ onUpdated(() => {
+ if (!prevChildren.length) {
+ return
+ }
+ const moveClass = props.moveClass || `${props.name || 'v'}-move`
+ const firstChild = getFirstConnectedChild(prevChildren)
+ if (
+ !firstChild ||
+ !hasCSSTransform(
+ firstChild as ElementWithTransition,
+ firstChild.parentNode as Node,
+ moveClass,
+ )
+ ) {
+ prevChildren = []
+ return
+ }
+
+ prevChildren.forEach(callPendingCbs)
+ prevChildren.forEach(child => {
+ child.$transition!.disabled = false
+ recordPosition(child)
+ })
+ const movedChildren = prevChildren.filter(applyTranslation)
+
+ // force reflow to put everything in position
+ forceReflow()
+
+ movedChildren.forEach(c =>
+ handleMovedChildren(
+ getTransitionElement(c) as ElementWithTransition,
+ moveClass,
+ ),
)
- ) {
prevChildren = []
- return
- }
-
- prevChildren.forEach(callPendingCbs)
- prevChildren.forEach(child => {
- child.$transition!.disabled = false
- recordPosition(child)
})
- const movedChildren = prevChildren.filter(applyTranslation)
-
- // force reflow to put everything in position
- forceReflow()
-
- movedChildren.forEach(c =>
- handleMovedChildren(
- getTransitionElement(c) as ElementWithTransition,
- moveClass,
- ),
- )
- prevChildren = []
- })
-
- // store props and state on fragment for reusing during insert new items
- setTransitionHooksOnFragment(slottedBlock, {
- props: propsProxy,
- state,
- instance,
- } as VaporTransitionHooks)
-
- children = getTransitionBlocks(slottedBlock)
- for (let i = 0; i < children.length; i++) {
- const child = children[i]
- if (isValidTransitionBlock(child)) {
- if (child.$key != null) {
- const hooks = resolveTransitionHooks(
- child,
- propsProxy,
- state,
- instance!,
- )
- setTransitionHooks(child, hooks)
- } else if (__DEV__) {
- warn(`<transition-group> children must be keyed`)
+
+ // store props and state on fragment for reusing during insert new items
+ setTransitionHooksOnFragment(slottedBlock, {
+ props: propsProxy,
+ state,
+ instance,
+ } as VaporTransitionHooks)
+
+ children = getTransitionBlocks(slottedBlock)
+ for (let i = 0; i < children.length; i++) {
+ const child = children[i]
+ if (isValidTransitionBlock(child)) {
+ if (child.$key != null) {
+ const hooks = resolveTransitionHooks(
+ child,
+ propsProxy,
+ state,
+ instance!,
+ )
+ setTransitionHooks(child, hooks)
+ } else if (__DEV__) {
+ warn(`<transition-group> children must be keyed`)
+ }
}
}
- }
- const tag = props.tag
- if (tag) {
- const container = createElement(tag)
- insert(slottedBlock, container)
- return container
- } else {
- return slottedBlock
- }
- },
-})
+ const tag = props.tag
+ if (tag) {
+ const container = createElement(tag)
+ insert(slottedBlock, container)
+ return container
+ } else {
+ return slottedBlock
+ }
+ },
+ })
function getTransitionBlocks(block: Block) {
let children: TransitionBlock[] = []