* Hydrate a list node and its children. There are some assumptions about the * child nodes that are made here: * 1. The child nodes are either: * - Are scalars and their values can be exchanged. * - Are groups and should maintain node identity. * 2. The value of the list will be a 1-1 rep
(node: FormKitNode, context: FormKitContext)
| 2076 | * @param node - A {@link FormKitNode | FormKitNode} |
| 2077 | */ |
| 2078 | function syncListNodes(node: FormKitNode, context: FormKitContext) { |
| 2079 | const _value = node._value |
| 2080 | if (!Array.isArray(_value)) return |
| 2081 | |
| 2082 | const newChildren: Array<FormKitNode | FormKitPlaceholderNode | null> = [] |
| 2083 | const unused = new Set(context.children) |
| 2084 | const placeholderValues = new Map<unknown, number[]>() |
| 2085 | |
| 2086 | // 1. Iterate over the values and if the values at the same index are equal |
| 2087 | // then we can reuse the node. Otherwise we add a `null` placeholder. |
| 2088 | _value.forEach((value, i) => { |
| 2089 | if (context.children[i] && context.children[i]._value === value) { |
| 2090 | newChildren.push(context.children[i]) |
| 2091 | unused.delete(context.children[i]) |
| 2092 | } else { |
| 2093 | newChildren.push(null) |
| 2094 | |
| 2095 | const indexes = placeholderValues.get(value) || [] |
| 2096 | indexes.push(i) |
| 2097 | placeholderValues.set(value, indexes) |
| 2098 | } |
| 2099 | }) |
| 2100 | |
| 2101 | // 2. If there are unused nodes, and there are null nodes in the new children |
| 2102 | // then we attempt to match those irregardless of their index. |
| 2103 | if (unused.size && placeholderValues.size) { |
| 2104 | unused.forEach((child) => { |
| 2105 | if (placeholderValues.has(child._value)) { |
| 2106 | /* eslint-disable @typescript-eslint/no-non-null-assertion */ |
| 2107 | const indexes = placeholderValues.get(child._value)! |
| 2108 | const index = indexes.shift()! |
| 2109 | /* eslint-enable @typescript-eslint/no-non-null-assertion */ |
| 2110 | newChildren[index] = child |
| 2111 | unused.delete(child) |
| 2112 | if (!indexes.length) placeholderValues.delete(child._value) |
| 2113 | } |
| 2114 | }) |
| 2115 | } |
| 2116 | |
| 2117 | // 3. If there are still unused nodes, and unused placeholders, we assign the |
| 2118 | // unused nodes to the unused placeholders in order. |
| 2119 | // IMPORTANT: Collect empty indexes in sequential order (0, 1, 2, ...) rather |
| 2120 | // than iterating placeholderValues Map which groups indexes by value. |
| 2121 | const emptyIndexes: number[] = [] |
| 2122 | for (let i = 0; i < newChildren.length; i++) { |
| 2123 | if (newChildren[i] === null) { |
| 2124 | emptyIndexes.push(i) |
| 2125 | } |
| 2126 | } |
| 2127 | |
| 2128 | while (unused.size && emptyIndexes.length) { |
| 2129 | const child = unused.values().next().value |
| 2130 | const index = emptyIndexes.shift() |
| 2131 | if (index === undefined || child === undefined) break |
| 2132 | newChildren[index] = child |
| 2133 | unused.delete(child) |
| 2134 | } |
| 2135 |
no test coverage detected