block.ts 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. import { isArray } from '@vue/shared'
  2. import {
  3. type VaporComponentInstance,
  4. isVaporComponent,
  5. mountComponent,
  6. unmountComponent,
  7. } from './component'
  8. import { _child } from './dom/node'
  9. import { isComment, isHydrating } from './dom/hydration'
  10. import {
  11. MoveType,
  12. type TransitionHooks,
  13. type TransitionProps,
  14. type TransitionState,
  15. performTransitionEnter,
  16. performTransitionLeave,
  17. } from '@vue/runtime-dom'
  18. import {
  19. type DynamicFragment,
  20. type VaporFragment,
  21. isFragment,
  22. } from './fragment'
  23. import { isTeleportFragment } from './components/Teleport'
  24. export interface VaporTransitionHooks extends TransitionHooks {
  25. state: TransitionState
  26. props: TransitionProps
  27. instance: VaporComponentInstance
  28. // mark transition hooks as disabled
  29. disabled?: boolean
  30. // TransitionGroup sets this to handle applying hooks to list children
  31. applyGroup?: (block: Block, hooks: VaporTransitionHooks) => void
  32. }
  33. export interface TransitionOptions {
  34. $key?: any
  35. $transition?: VaporTransitionHooks
  36. }
  37. export type TransitionBlock = (
  38. | Node
  39. | VaporFragment
  40. | DynamicFragment
  41. | VaporComponentInstance
  42. ) &
  43. TransitionOptions
  44. export type Block =
  45. | Node
  46. | VaporFragment
  47. | DynamicFragment
  48. | VaporComponentInstance
  49. | Block[]
  50. export type BlockFn = (...args: any[]) => Block
  51. export function isBlock(val: NonNullable<unknown>): val is Block {
  52. return (
  53. val instanceof Node ||
  54. isArray(val) ||
  55. isVaporComponent(val) ||
  56. isFragment(val)
  57. )
  58. }
  59. export function isValidBlock(block: Block): boolean {
  60. if (block instanceof Node) {
  61. return !(block instanceof Comment)
  62. } else if (isVaporComponent(block)) {
  63. return isValidBlock(block.block)
  64. } else if (isArray(block)) {
  65. return block.length > 0 && block.some(isValidBlock)
  66. } else {
  67. // fragment
  68. return isValidBlock(block.nodes)
  69. }
  70. }
  71. export function insert(
  72. block: Block,
  73. parent: ParentNode & { $fc?: Node | null },
  74. anchor: Node | null | 0 = null, // 0 means prepend
  75. parentSuspense?: any, // TODO Suspense
  76. ): void {
  77. anchor = anchor === 0 ? parent.$fc || _child(parent) : anchor
  78. if (block instanceof Node) {
  79. if (!isHydrating) {
  80. // only apply transition on Element nodes
  81. if (
  82. block instanceof Element &&
  83. (block as TransitionBlock).$transition &&
  84. !(block as TransitionBlock).$transition!.disabled
  85. ) {
  86. performTransitionEnter(
  87. block,
  88. (block as TransitionBlock).$transition as TransitionHooks,
  89. () => parent.insertBefore(block, anchor as Node),
  90. parentSuspense,
  91. )
  92. } else {
  93. parent.insertBefore(block, anchor)
  94. }
  95. }
  96. } else if (isVaporComponent(block)) {
  97. if (block.isMounted && !block.isDeactivated) {
  98. insert(block.block!, parent, anchor)
  99. } else {
  100. mountComponent(block, parent, anchor)
  101. }
  102. } else if (isArray(block)) {
  103. for (const b of block) {
  104. insert(b, parent, anchor)
  105. }
  106. } else {
  107. if (block.anchor) {
  108. insert(block.anchor, parent, anchor)
  109. anchor = block.anchor
  110. }
  111. // fragment
  112. if (block.insert) {
  113. block.insert(parent, anchor, (block as TransitionBlock).$transition)
  114. } else {
  115. insert(block.nodes, parent, anchor, parentSuspense)
  116. }
  117. }
  118. }
  119. export function move(
  120. block: Block,
  121. parent: ParentNode & { $fc?: Node | null },
  122. anchor: Node | null | 0 = null, // 0 means prepend
  123. moveType: MoveType = MoveType.LEAVE,
  124. parentComponent?: VaporComponentInstance,
  125. parentSuspense?: any, // TODO Suspense
  126. ): void {
  127. anchor = anchor === 0 ? parent.$fc || _child(parent) : anchor
  128. if (block instanceof Node) {
  129. // only apply transition on Element nodes
  130. if (
  131. block instanceof Element &&
  132. (block as TransitionBlock).$transition &&
  133. !(block as TransitionBlock).$transition!.disabled &&
  134. moveType !== MoveType.REORDER
  135. ) {
  136. if (moveType === MoveType.ENTER) {
  137. performTransitionEnter(
  138. block,
  139. (block as TransitionBlock).$transition as TransitionHooks,
  140. () => parent.insertBefore(block, anchor as Node),
  141. parentSuspense,
  142. true,
  143. )
  144. } else {
  145. performTransitionLeave(
  146. block,
  147. (block as TransitionBlock).$transition as TransitionHooks,
  148. () => {
  149. // if the component is unmounted after leave finish, remove the block
  150. // to avoid retaining a detached node.
  151. if (
  152. moveType === MoveType.LEAVE &&
  153. parentComponent &&
  154. parentComponent.isUnmounted
  155. ) {
  156. block.remove()
  157. } else {
  158. parent.insertBefore(block, anchor as Node)
  159. }
  160. },
  161. parentSuspense,
  162. true,
  163. )
  164. }
  165. } else {
  166. parent.insertBefore(block, anchor)
  167. }
  168. } else if (isVaporComponent(block)) {
  169. if (block.isMounted) {
  170. move(
  171. block.block!,
  172. parent,
  173. anchor,
  174. moveType,
  175. parentComponent,
  176. parentSuspense,
  177. )
  178. } else {
  179. mountComponent(block, parent, anchor)
  180. }
  181. } else if (isArray(block)) {
  182. for (const b of block) {
  183. move(b, parent, anchor, moveType, parentComponent, parentSuspense)
  184. }
  185. } else {
  186. if (block.anchor) {
  187. move(
  188. block.anchor,
  189. parent,
  190. anchor,
  191. moveType,
  192. parentComponent,
  193. parentSuspense,
  194. )
  195. anchor = block.anchor
  196. }
  197. // fragment
  198. if (block.insert) {
  199. block.insert(parent, anchor, (block as TransitionBlock).$transition)
  200. } else {
  201. move(
  202. block.nodes,
  203. parent,
  204. anchor,
  205. moveType,
  206. parentComponent,
  207. parentSuspense,
  208. )
  209. }
  210. }
  211. }
  212. export function prepend(parent: ParentNode, ...blocks: Block[]): void {
  213. let i = blocks.length
  214. while (i--) insert(blocks[i], parent, 0)
  215. }
  216. export function remove(block: Block, parent?: ParentNode): void {
  217. if (block instanceof Node) {
  218. if ((block as TransitionBlock).$transition && block instanceof Element) {
  219. performTransitionLeave(
  220. block,
  221. (block as TransitionBlock).$transition as TransitionHooks,
  222. () => parent && parent.removeChild(block),
  223. )
  224. } else {
  225. parent && parent.removeChild(block)
  226. }
  227. } else if (isVaporComponent(block)) {
  228. unmountComponent(block, parent)
  229. } else if (isArray(block)) {
  230. for (let i = 0; i < block.length; i++) {
  231. remove(block[i], parent)
  232. }
  233. } else {
  234. // fragment
  235. if (block.remove) {
  236. block.remove(parent, (block as TransitionBlock).$transition)
  237. } else {
  238. remove(block.nodes, parent)
  239. }
  240. if (block.anchor) remove(block.anchor, parent)
  241. if ((block as DynamicFragment).scope) {
  242. ;(block as DynamicFragment).scope!.stop()
  243. }
  244. }
  245. }
  246. /**
  247. * dev / test only
  248. */
  249. export function normalizeBlock(block: Block): Node[] {
  250. if (!__DEV__ && !__TEST__) {
  251. throw new Error(
  252. 'normalizeBlock should not be used in production code paths',
  253. )
  254. }
  255. const nodes: Node[] = []
  256. if (block instanceof Node) {
  257. nodes.push(block)
  258. } else if (isArray(block)) {
  259. block.forEach(child => nodes.push(...normalizeBlock(child)))
  260. } else if (isVaporComponent(block)) {
  261. nodes.push(...normalizeBlock(block.block!))
  262. } else {
  263. if (isTeleportFragment(block)) {
  264. nodes.push(block.placeholder!, block.anchor!)
  265. } else {
  266. nodes.push(...normalizeBlock(block.nodes))
  267. block.anchor && nodes.push(block.anchor)
  268. }
  269. }
  270. return nodes
  271. }
  272. export function findBlockNode(block: Block): {
  273. parentNode: Node | null
  274. nextNode: Node | null
  275. } {
  276. const lastChild = findLastChild(block)!
  277. let { parentNode, nextSibling: nextNode } = lastChild
  278. // if nodes render as a fragment and the current nextNode is fragment
  279. // end anchor, need to move to the next node. Skip this when the block
  280. // already includes its own end anchor (for example VDOM Fragment ranges).
  281. if (
  282. nextNode &&
  283. isComment(nextNode, ']') &&
  284. isFragmentBlock(block) &&
  285. !isComment(lastChild, ']')
  286. ) {
  287. nextNode = nextNode.nextSibling
  288. }
  289. return {
  290. parentNode,
  291. nextNode,
  292. }
  293. }
  294. function findLastChild(node: Block): Node | undefined | null {
  295. if (node && node instanceof Node) {
  296. return node
  297. } else if (isArray(node)) {
  298. return findLastChild(node[node.length - 1])
  299. } else if (isVaporComponent(node)) {
  300. return findLastChild(node.block!)
  301. } else {
  302. if (node.anchor) return node.anchor
  303. return findLastChild(node.nodes!)
  304. }
  305. }
  306. export function isFragmentBlock(block: Block): boolean {
  307. if (isArray(block)) {
  308. return true
  309. } else if (isVaporComponent(block)) {
  310. return isFragmentBlock(block.block!)
  311. } else if (isFragment(block)) {
  312. return isFragmentBlock(block.nodes)
  313. }
  314. return false
  315. }
  316. export { setScopeId, setComponentScopeId } from './scopeId'
  317. export {
  318. registerTransitionHooks,
  319. applyTransitionHooks,
  320. applyTransitionLeaveHooks,
  321. } from './transition'