(
node: T,
mode: 'head' | 'prev-sibling',
onRestore = Function.prototype,
)
| 74 | } |
| 75 | |
| 76 | export function watchForNodePosition<T extends Node>( |
| 77 | node: T, |
| 78 | mode: 'head' | 'prev-sibling', |
| 79 | onRestore = Function.prototype, |
| 80 | ): NodePosetionWatcher { |
| 81 | const MAX_ATTEMPTS_COUNT = 10; |
| 82 | const RETRY_TIMEOUT = getDuration({seconds: 2}); |
| 83 | const ATTEMPTS_INTERVAL = getDuration({seconds: 10}); |
| 84 | let prevSibling = node.previousSibling; |
| 85 | let parent = node.parentNode; |
| 86 | if (!parent) { |
| 87 | throw new Error('Unable to watch for node position: parent element not found'); |
| 88 | } |
| 89 | if (mode === 'prev-sibling' && !prevSibling) { |
| 90 | throw new Error('Unable to watch for node position: there is no previous sibling'); |
| 91 | } |
| 92 | let attempts = 0; |
| 93 | let start: number | null = null; |
| 94 | let timeoutId: ReturnType<typeof setTimeout> | null = null; |
| 95 | const restore = throttle(() => { |
| 96 | if (timeoutId) { |
| 97 | return; |
| 98 | } |
| 99 | attempts++; |
| 100 | const now = Date.now(); |
| 101 | if (start == null) { |
| 102 | start = now; |
| 103 | } else if (attempts >= MAX_ATTEMPTS_COUNT) { |
| 104 | if (now - start < ATTEMPTS_INTERVAL) { |
| 105 | logWarn(`Node position watcher paused: retry in ${RETRY_TIMEOUT}ms`, node, prevSibling); |
| 106 | timeoutId = setTimeout(() => { |
| 107 | start = null; |
| 108 | attempts = 0; |
| 109 | timeoutId = null; |
| 110 | restore(); |
| 111 | }, RETRY_TIMEOUT); |
| 112 | return; |
| 113 | } |
| 114 | start = now; |
| 115 | attempts = 1; |
| 116 | } |
| 117 | |
| 118 | if (mode === 'head') { |
| 119 | if (prevSibling && prevSibling.parentNode !== parent) { |
| 120 | logWarn('Sibling moved, moving node to the head end', node, prevSibling, parent); |
| 121 | prevSibling = document.head.lastChild; |
| 122 | } |
| 123 | } |
| 124 | |
| 125 | if (mode === 'prev-sibling') { |
| 126 | if (prevSibling!.parentNode == null) { |
| 127 | logWarn('Unable to restore node position: sibling was removed', node, prevSibling, parent); |
| 128 | stop(); |
| 129 | return; |
| 130 | } |
| 131 | if (prevSibling!.parentNode !== parent) { |
| 132 | logWarn('Style was moved to another parent', node, prevSibling, parent); |
| 133 | updateParent(prevSibling!.parentNode); |
no test coverage detected