|
|
@@ -11,7 +11,7 @@ import {
|
|
|
toReadonly,
|
|
|
watch,
|
|
|
} from '@vue/reactivity'
|
|
|
-import { isArray, isObject, isString } from '@vue/shared'
|
|
|
+import { getSequence, isArray, isObject, isString } from '@vue/shared'
|
|
|
import { createComment, createTextNode } from './dom/node'
|
|
|
import {
|
|
|
type Block,
|
|
|
@@ -50,9 +50,6 @@ import {
|
|
|
class ForBlock extends VaporFragment {
|
|
|
scope: EffectScope | undefined
|
|
|
key: any
|
|
|
- prev?: ForBlock
|
|
|
- next?: ForBlock
|
|
|
- prevAnchor?: ForBlock
|
|
|
|
|
|
itemRef: ShallowRef<any>
|
|
|
keyRef: ShallowRef<any> | undefined
|
|
|
@@ -234,169 +231,151 @@ export const createFor = (
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- const commonLength = Math.min(oldLength, newLength)
|
|
|
- const oldKeyIndexPairs: [any, number][] = new Array(oldLength)
|
|
|
- const queuedBlocks: [
|
|
|
- index: number,
|
|
|
- item: ReturnType<typeof getItem>,
|
|
|
- key: any,
|
|
|
- ][] = new Array(newLength)
|
|
|
-
|
|
|
- let endOffset = 0
|
|
|
- let queuedBlocksLength = 0
|
|
|
- let oldKeyIndexPairsLength = 0
|
|
|
-
|
|
|
- while (endOffset < commonLength) {
|
|
|
- const index = newLength - endOffset - 1
|
|
|
- const item = getItem(source, index)
|
|
|
- const key = getKey(...item)
|
|
|
- const existingBlock = oldBlocks[oldLength - endOffset - 1]
|
|
|
- if (existingBlock.key !== key) break
|
|
|
- update(existingBlock, ...item)
|
|
|
- newBlocks[index] = existingBlock
|
|
|
- endOffset++
|
|
|
- }
|
|
|
-
|
|
|
- const e1 = commonLength - endOffset
|
|
|
- const e2 = oldLength - endOffset
|
|
|
- const e3 = newLength - endOffset
|
|
|
+ let i = 0
|
|
|
+ let e1 = oldLength - 1
|
|
|
+ let e2 = newLength - 1
|
|
|
|
|
|
- for (let i = 0; i < e1; i++) {
|
|
|
- const currentItem = getItem(source, i)
|
|
|
- const currentKey = getKey(...currentItem)
|
|
|
+ while (i <= e1 && i <= e2) {
|
|
|
const oldBlock = oldBlocks[i]
|
|
|
- const oldKey = oldBlock.key
|
|
|
- if (oldKey === currentKey) {
|
|
|
- update((newBlocks[i] = oldBlock), currentItem[0])
|
|
|
- } else {
|
|
|
- queuedBlocks[queuedBlocksLength++] = [i, currentItem, currentKey]
|
|
|
- oldKeyIndexPairs[oldKeyIndexPairsLength++] = [oldKey, i]
|
|
|
- }
|
|
|
+ const item = getItem(source, i)
|
|
|
+ if (oldBlock.key !== getKey(...item)) break
|
|
|
+ update((newBlocks[i] = oldBlock), ...item)
|
|
|
+ i++
|
|
|
}
|
|
|
|
|
|
- for (let i = e1; i < e2; i++) {
|
|
|
- oldKeyIndexPairs[oldKeyIndexPairsLength++] = [oldBlocks[i].key, i]
|
|
|
+ while (i <= e1 && i <= e2) {
|
|
|
+ const oldBlock = oldBlocks[e1]
|
|
|
+ const item = getItem(source, e2)
|
|
|
+ if (oldBlock.key !== getKey(...item)) break
|
|
|
+ update((newBlocks[e2] = oldBlock), ...item)
|
|
|
+ e1--
|
|
|
+ e2--
|
|
|
}
|
|
|
|
|
|
- for (let i = e1; i < e3; i++) {
|
|
|
- const blockItem = getItem(source, i)
|
|
|
- const blockKey = getKey(...blockItem)
|
|
|
- queuedBlocks[queuedBlocksLength++] = [i, blockItem, blockKey]
|
|
|
- }
|
|
|
+ if (i > e1) {
|
|
|
+ for (let j = e2; j >= i; j--) {
|
|
|
+ const nextIndex = j + 1
|
|
|
+ mount(
|
|
|
+ source,
|
|
|
+ j,
|
|
|
+ nextIndex < newLength
|
|
|
+ ? normalizeAnchor(newBlocks[nextIndex].nodes)
|
|
|
+ : parentAnchor,
|
|
|
+ )
|
|
|
+ }
|
|
|
+ } else if (i <= e2) {
|
|
|
+ const s1 = i
|
|
|
+ const s2 = i
|
|
|
+ const toBePatched = e2 - s2 + 1
|
|
|
+ const keyToNewIndexMap = new Map<any, number>()
|
|
|
+ const newItems = new Array<ReturnType<typeof getItem>>(toBePatched)
|
|
|
+ const newKeys = new Array<any>(toBePatched)
|
|
|
+ const nullKeyNewIndices: number[] = []
|
|
|
+
|
|
|
+ for (i = s2; i <= e2; i++) {
|
|
|
+ const item = getItem(source, i)
|
|
|
+ const key = getKey(...item)
|
|
|
+ const index = i - s2
|
|
|
+ newItems[index] = item
|
|
|
+ newKeys[index] = key
|
|
|
+ if (key != null) {
|
|
|
+ keyToNewIndexMap.set(key, i)
|
|
|
+ } else {
|
|
|
+ nullKeyNewIndices.push(i)
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- queuedBlocks.length = queuedBlocksLength
|
|
|
- oldKeyIndexPairs.length = oldKeyIndexPairsLength
|
|
|
+ const newIndexToOldIndexMap = new Array(toBePatched).fill(0)
|
|
|
+ const unmountIndices: number[] = []
|
|
|
|
|
|
- interface MountOper {
|
|
|
- source: ResolvedSource
|
|
|
- index: number
|
|
|
- item: ReturnType<typeof getItem>
|
|
|
- key: any
|
|
|
- }
|
|
|
- interface MoveOper {
|
|
|
- index: number
|
|
|
- block: ForBlock
|
|
|
- }
|
|
|
+ let moved = false
|
|
|
+ let maxNewIndexSoFar = 0
|
|
|
+ let patched = 0
|
|
|
+ let nullKeyCursor = 0
|
|
|
|
|
|
- const oldKeyIndexMap = new Map(oldKeyIndexPairs)
|
|
|
- const opers: (MountOper | MoveOper)[] = new Array(queuedBlocks.length)
|
|
|
-
|
|
|
- let mountCounter = 0
|
|
|
- let opersLength = 0
|
|
|
-
|
|
|
- for (let i = queuedBlocks.length - 1; i >= 0; i--) {
|
|
|
- const [index, item, key] = queuedBlocks[i]
|
|
|
- const oldIndex = oldKeyIndexMap.get(key)
|
|
|
- if (oldIndex !== undefined) {
|
|
|
- oldKeyIndexMap.delete(key)
|
|
|
- const reusedBlock = (newBlocks[index] = oldBlocks[oldIndex])
|
|
|
- update(reusedBlock, ...item)
|
|
|
- opers[opersLength++] = { index, block: reusedBlock }
|
|
|
- } else {
|
|
|
- mountCounter++
|
|
|
- opers[opersLength++] = { source, index, item, key }
|
|
|
+ for (i = s1; i <= e1; i++) {
|
|
|
+ const oldBlock = oldBlocks[i]
|
|
|
+ if (patched >= toBePatched) {
|
|
|
+ unmountIndices.push(i)
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ let newIndex: number | undefined
|
|
|
+ if (oldBlock.key != null) {
|
|
|
+ newIndex = keyToNewIndexMap.get(oldBlock.key)
|
|
|
+ } else {
|
|
|
+ while (nullKeyCursor < nullKeyNewIndices.length) {
|
|
|
+ const j = nullKeyNewIndices[nullKeyCursor++]
|
|
|
+ const index = j - s2
|
|
|
+ if (newIndexToOldIndexMap[index] === 0) {
|
|
|
+ newIndex = j
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (newIndex === undefined) {
|
|
|
+ unmountIndices.push(i)
|
|
|
+ } else {
|
|
|
+ newIndexToOldIndexMap[newIndex - s2] = i + 1
|
|
|
+ if (newIndex >= maxNewIndexSoFar) {
|
|
|
+ maxNewIndexSoFar = newIndex
|
|
|
+ } else {
|
|
|
+ moved = true
|
|
|
+ }
|
|
|
+ const item = newItems[newIndex - s2]
|
|
|
+ update(oldBlock, ...item)
|
|
|
+ newBlocks[newIndex] = oldBlock
|
|
|
+ patched++
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- const useFastRemove = mountCounter === newLength
|
|
|
|
|
|
- for (const leftoverIndex of oldKeyIndexMap.values()) {
|
|
|
- unmount(
|
|
|
- oldBlocks[leftoverIndex],
|
|
|
- !(useFastRemove && canUseFastRemove),
|
|
|
- !useFastRemove,
|
|
|
- )
|
|
|
- }
|
|
|
- if (useFastRemove) {
|
|
|
- for (const selector of selectors) {
|
|
|
- selector.cleanup()
|
|
|
- }
|
|
|
- if (canUseFastRemove) {
|
|
|
- parent!.textContent = ''
|
|
|
- parent!.appendChild(parentAnchor)
|
|
|
- }
|
|
|
- }
|
|
|
+ const mountCounter = toBePatched - patched
|
|
|
+ const useFastRemove = mountCounter === newLength
|
|
|
|
|
|
- if (opers.length === mountCounter) {
|
|
|
- for (const { source, index, item, key } of opers as MountOper[]) {
|
|
|
- mount(
|
|
|
- source,
|
|
|
- index,
|
|
|
- index < newLength - 1
|
|
|
- ? normalizeAnchor(newBlocks[index + 1].nodes)
|
|
|
- : parentAnchor,
|
|
|
- item,
|
|
|
- key,
|
|
|
+ for (const index of unmountIndices) {
|
|
|
+ unmount(
|
|
|
+ oldBlocks[index],
|
|
|
+ !(useFastRemove && canUseFastRemove),
|
|
|
+ !useFastRemove,
|
|
|
)
|
|
|
}
|
|
|
- } else if (opers.length) {
|
|
|
- let anchor = oldBlocks[0]
|
|
|
- let blocksTail: ForBlock | undefined
|
|
|
- for (let i = 0; i < oldLength; i++) {
|
|
|
- const block = oldBlocks[i]
|
|
|
- if (oldKeyIndexMap.has(block.key)) {
|
|
|
- continue
|
|
|
+ if (useFastRemove) {
|
|
|
+ for (const selector of selectors) {
|
|
|
+ selector.cleanup()
|
|
|
}
|
|
|
- block.prevAnchor = anchor
|
|
|
- anchor = oldBlocks[i + 1]
|
|
|
- if (blocksTail !== undefined) {
|
|
|
- blocksTail.next = block
|
|
|
- block.prev = blocksTail
|
|
|
+ if (canUseFastRemove) {
|
|
|
+ parent!.textContent = ''
|
|
|
+ parent!.appendChild(parentAnchor)
|
|
|
}
|
|
|
- blocksTail = block
|
|
|
}
|
|
|
- for (const action of opers) {
|
|
|
- const { index } = action
|
|
|
- if (index < newLength - 1) {
|
|
|
- const nextBlock = newBlocks[index + 1]
|
|
|
- let anchorNode = normalizeAnchor(nextBlock.prevAnchor!.nodes)
|
|
|
- if (!anchorNode.parentNode)
|
|
|
- anchorNode = normalizeAnchor(nextBlock.nodes)
|
|
|
- if ('source' in action) {
|
|
|
- const { item, key } = action
|
|
|
- const block = mount(source, index, anchorNode, item, key)
|
|
|
- moveLink(block, nextBlock.prev, nextBlock)
|
|
|
- } else if (action.block.next !== nextBlock) {
|
|
|
- insert(action.block, parent!, anchorNode)
|
|
|
- moveLink(action.block, nextBlock.prev, nextBlock)
|
|
|
+
|
|
|
+ const increasingNewIndexSequence = moved
|
|
|
+ ? getSequence(newIndexToOldIndexMap)
|
|
|
+ : []
|
|
|
+ let seqIdx = increasingNewIndexSequence.length - 1
|
|
|
+
|
|
|
+ if (mountCounter > 0 || moved) {
|
|
|
+ for (i = toBePatched - 1; i >= 0; i--) {
|
|
|
+ const newIndex = s2 + i
|
|
|
+ const nextIndex = newIndex + 1
|
|
|
+ const anchor =
|
|
|
+ nextIndex < newLength
|
|
|
+ ? normalizeAnchor(newBlocks[nextIndex].nodes)
|
|
|
+ : parentAnchor
|
|
|
+ if (newIndexToOldIndexMap[i] === 0) {
|
|
|
+ mount(source, newIndex, anchor, newItems[i], newKeys[i])
|
|
|
+ } else if (moved) {
|
|
|
+ if (seqIdx < 0 || i !== increasingNewIndexSequence[seqIdx]) {
|
|
|
+ insert(newBlocks[newIndex], parent!, anchor)
|
|
|
+ } else {
|
|
|
+ seqIdx--
|
|
|
+ }
|
|
|
}
|
|
|
- } else if ('source' in action) {
|
|
|
- const { item, key } = action
|
|
|
- const block = mount(source, index, parentAnchor, item, key)
|
|
|
- moveLink(block, blocksTail)
|
|
|
- blocksTail = block
|
|
|
- } else if (action.block.next !== undefined) {
|
|
|
- let anchorNode = anchor
|
|
|
- ? normalizeAnchor(anchor.nodes)
|
|
|
- : parentAnchor
|
|
|
- if (!anchorNode.parentNode) anchorNode = parentAnchor
|
|
|
- insert(action.block, parent!, anchorNode)
|
|
|
- moveLink(action.block, blocksTail)
|
|
|
- blocksTail = action.block
|
|
|
}
|
|
|
}
|
|
|
- for (const block of newBlocks) {
|
|
|
- block.prevAnchor = block.next = block.prev = undefined
|
|
|
+ } else {
|
|
|
+ while (i <= e1) {
|
|
|
+ unmount(oldBlocks[i])
|
|
|
+ i++
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -573,22 +552,6 @@ export const createFor = (
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-function moveLink(block: ForBlock, newPrev?: ForBlock, newNext?: ForBlock) {
|
|
|
- const { prev: oldPrev, next: oldNext } = block
|
|
|
- if (oldPrev) oldPrev.next = oldNext
|
|
|
- if (oldNext) {
|
|
|
- oldNext.prev = oldPrev
|
|
|
- if (block.prevAnchor !== block) {
|
|
|
- oldNext.prevAnchor = block.prevAnchor
|
|
|
- }
|
|
|
- }
|
|
|
- if (newPrev) newPrev.next = block
|
|
|
- if (newNext) newNext.prev = block
|
|
|
- block.prev = newPrev
|
|
|
- block.next = newNext
|
|
|
- block.prevAnchor = block
|
|
|
-}
|
|
|
-
|
|
|
export function createForSlots(
|
|
|
rawSource: Source,
|
|
|
getSlot: (item: any, key: any, index?: number) => DynamicSlot,
|