Skip to content

Commit

Permalink
feat: add option to include absolute elements in auto resizer (#901)
Browse files Browse the repository at this point in the history
* feat: introduce some additional logic for window.updateHeight method in order to count absolute positioned elements.

* feat: check for absolute positioned elements with MutationObserver

* test: add mocked Array<MutationRecord> to test file

* refactor: apply review suggestions

Co-authored-by: Andi Pätzold <[email protected]>

* refactor: get height based on highest getBoundingClientRect.bottom

* refactor: always run observer to store absolute elements

* feat: update type definition for startAutoResizer

* fix: call updateHeight after checkAbsoluteElements is updated

* feat: do not check elements with negative bottom

* Update lib/window.ts

* fix: code readability

* fix: ignore tranformed elements

* Revert "fix: ignore tranformed elements"

This reverts commit 624e9f3.

* fix: do not check for negative bottom

* docs: add warning about negative bottom and transformed elements

* Update lib/types/window.types.ts

Co-authored-by: Andi Pätzold <[email protected]>

Co-authored-by: Renato Massao Yonamine <[email protected]>
Co-authored-by: Renato Massao Yonamine <[email protected]>
Co-authored-by: Andi Pätzold <[email protected]>
  • Loading branch information
4 people authored Jun 1, 2022
1 parent 88aa356 commit 0269b00
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 20 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@
.env
.env.local
.envrc

# editors
.vscode/
.idea/
11 changes: 9 additions & 2 deletions lib/types/window.types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
export interface WindowAPI {
/** Sets the iframe height to the given value in pixels or using scrollHeight if value is not passed */
updateHeight: (height?: number) => void
/** Listens for DOM changes and updates height when the size changes. */
startAutoResizer: () => void
/**
* Listens for DOM changes and updates height when the size changes.
*
* When passing absoluteElements true, an infinite loop can happen if elements are always rendered below the height of the window.
* e.g. transformed elements or absolute elements with negative bottom
* @param {Object} opts - Options to be passed to auto resize
* @param {Boolean} opts.absoluteElements - Defines if auto resize should consider absolut elements
*/
startAutoResizer: ({ absoluteElements }?: { absoluteElements?: boolean }) => void
/** Stops resizing the iframe automatically. */
stopAutoResizer: () => void
}
99 changes: 84 additions & 15 deletions lib/window.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,92 @@
import { Channel } from './channel'
import { WindowAPI } from './types'
import { WindowAPI } from './types/window.types'

export default function createWindow(currentWindow: Window, channel: Channel): WindowAPI {
// We assume MutationObserver and ResizeObserver were defined by the web-app
const { document, MutationObserver, ResizeObserver } = currentWindow as any

const autoUpdateHeight = () => {
self.updateHeight()
}
const mutationObserver = new MutationObserver(autoUpdateHeight)
const resizeObserver = new ResizeObserver(autoUpdateHeight)
let oldHeight: number
let isAutoResizing = false
let checkAbsoluteElements = false
const absolutePositionedElems: Set<Element> = new Set()

const mutationObserver = new MutationObserver((mutations: Array<MutationRecord>) => {
checkAbsolutePositionedElems(mutations)
if (isAutoResizing) {
self.updateHeight()
}
})

const resizeObserver = new ResizeObserver(() => {
self.updateHeight()
})

mutationObserver.observe(document.body, {
attributes: true,
childList: true,
subtree: true,
characterData: true,
})

const self = { startAutoResizer, stopAutoResizer, updateHeight }
return self

function startAutoResizer() {
function checkAbsoluteElementStyle(type: MutationRecordType, element: Element) {
const computedStyle = getComputedStyle(element)

if (computedStyle.position !== 'absolute') {
return false
}

switch (type) {
case 'attributes':
return computedStyle.display !== 'none'

default:
return true
}
}

function checkAbsolutePositionedElems(mutations: Array<MutationRecord>) {
mutations.forEach((mutation) => {
switch (mutation.type) {
case 'attributes':
if (mutation.target.nodeType === Node.ELEMENT_NODE) {
const element = mutation.target as Element
if (checkAbsoluteElementStyle(mutation.type, element)) {
absolutePositionedElems.add(element)
} else {
absolutePositionedElems.delete(element)
}
}
break

case 'childList':
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
const element = node as Element
if (checkAbsoluteElementStyle(mutation.type, element)) {
absolutePositionedElems.add(element)
}
}
})

mutation.removedNodes.forEach((node) => {
const element = node as Element
absolutePositionedElems.delete(element)
})
break
}
})
}

function startAutoResizer({ absoluteElements = false } = {}) {
checkAbsoluteElements = Boolean(absoluteElements)
self.updateHeight()
if (isAutoResizing) {
return
}
isAutoResizing = true
mutationObserver.observe(document.body, {
attributes: true,
childList: true,
subtree: true,
characterData: true,
})
resizeObserver.observe(document.body)
}

Expand All @@ -36,13 +95,23 @@ export default function createWindow(currentWindow: Window, channel: Channel): W
return
}
isAutoResizing = false
mutationObserver.disconnect()
resizeObserver.disconnect()
}

function updateHeight(height: number | null = null) {
if (height === null) {
height = Math.ceil(document.documentElement.getBoundingClientRect().height)
const documentHeight = Math.ceil(document.documentElement.getBoundingClientRect().height)

// Only check for absolute elements if option is provided to startAutoResizer
if (checkAbsoluteElements && absolutePositionedElems.size) {
let maxHeight = documentHeight
absolutePositionedElems.forEach((element) => {
maxHeight = Math.max(element.getBoundingClientRect().bottom, maxHeight)
})
height = maxHeight
} else {
height = documentHeight
}
}

if (height !== oldHeight) {
Expand Down
12 changes: 9 additions & 3 deletions test/unit/window.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,13 @@ describe(`createWindow()`, () => {
expect(updateHeightSpy).to.have.callCount(1)
done()
})
modifyDOM()
modifyDOM([
{
type: 'childList',
addedNodes: [],
removedNodes: [],
},
])
})

it(`listens to size changes and invokes .updateHeight()`, (done) => {
Expand All @@ -67,12 +73,12 @@ describe(`createWindow()`, () => {
updateHeightSpy.reset()
})

it(`stops observing DOM and does not invoke updateHeight()`, (done) => {
it(`does not invoke updateHeight()`, (done) => {
setTimeout(() => {
expect(updateHeightSpy).to.have.callCount(0)
done()
}, 0)
modifyDOM()
modifyDOM([])
})

it(`stops observing size changes and does not invoke updateHeight()`, (done) => {
Expand Down

0 comments on commit 0269b00

Please sign in to comment.