diff --git a/integration_tests/snapshots/css/css-position/nonstatic-to-static.ts.cec9aab51.png b/integration_tests/snapshots/css/css-position/nonstatic-to-static.ts.cec9aab51.png new file mode 100644 index 0000000000..c851065325 Binary files /dev/null and b/integration_tests/snapshots/css/css-position/nonstatic-to-static.ts.cec9aab51.png differ diff --git a/integration_tests/snapshots/css/css-position/static-to-nostatic.ts.7d4ee39a1.png b/integration_tests/snapshots/css/css-position/static-to-nostatic.ts.7d4ee39a1.png new file mode 100644 index 0000000000..20734c78ef Binary files /dev/null and b/integration_tests/snapshots/css/css-position/static-to-nostatic.ts.7d4ee39a1.png differ diff --git a/integration_tests/specs/css/css-position/nonstatic-to-static.ts b/integration_tests/specs/css/css-position/nonstatic-to-static.ts index 038b779808..8805820d22 100644 --- a/integration_tests/specs/css/css-position/nonstatic-to-static.ts +++ b/integration_tests/specs/css/css-position/nonstatic-to-static.ts @@ -66,4 +66,57 @@ describe('Position non-static', () => { await snapshot(); }); + + it('children should reposition when parent position changed from relative to static', async (done) => { + let div; + let item1; + let item2; + div = createElement( + 'div', + { + style: { + position: 'relative', + width: '200px', + height: '100px', + display: 'flex', + flexDirection: 'row', + backgroundColor: 'green', + }, + }, + [ + (item1 = createElement('div', { + style: { + position: 'relative', + margin: '30px', + width: '100px', + height: '50px', + backgroundColor: 'yellow', + } + }, [ + createElement('div', { + style: {} + }, [ + (item2 = createElement('div', { + style: { + position: 'absolute', + top: 0, + left: 0, + width: '30px', + height: '30px', + backgroundColor: 'red' + } + })), + ]) + ])), + ] + ); + + BODY.appendChild(div); + + requestAnimationFrame(async () => { + item1.style.position = 'static'; + await snapshot(); + done(); + }); + }); }); diff --git a/integration_tests/specs/css/css-position/static-to-nostatic.ts b/integration_tests/specs/css/css-position/static-to-nostatic.ts index b0156fbdd9..f255f5f7bd 100644 --- a/integration_tests/specs/css/css-position/static-to-nostatic.ts +++ b/integration_tests/specs/css/css-position/static-to-nostatic.ts @@ -59,4 +59,56 @@ describe('Position static', () => { await snapshot(); }); + + it('children should reposition when parent position changed from static to relative', async (done) => { + let div; + let item1; + let item2; + div = createElement( + 'div', + { + style: { + position: 'relative', + width: '200px', + height: '100px', + display: 'flex', + flexDirection: 'row', + backgroundColor: 'green', + }, + }, + [ + (item1 = createElement('div', { + style: { + margin: '30px', + width: '100px', + height: '50px', + backgroundColor: 'yellow', + } + }, [ + createElement('div', { + style: {} + }, [ + (item2 = createElement('div', { + style: { + position: 'absolute', + top: 0, + left: 0, + width: '30px', + height: '30px', + backgroundColor: 'red' + } + })), + ]) + ])), + ] + ); + + BODY.appendChild(div); + + requestAnimationFrame(async () => { + item1.style.position = 'relative'; + await snapshot(); + done(); + }); + }); }); diff --git a/kraken/lib/src/css/style_declaration.dart b/kraken/lib/src/css/style_declaration.dart index a9ba50c110..5fd07cbff1 100644 --- a/kraken/lib/src/css/style_declaration.dart +++ b/kraken/lib/src/css/style_declaration.dart @@ -436,6 +436,10 @@ class CSSStyleDeclaration { for (String propertyName in propertyNames) { String? prevValue = prevValues[propertyName]; String currentValue = pendingProperties[propertyName]!; + + // Return if value has not changed. + if (currentValue == prevValue) return; + _emitPropertyChanged(propertyName, prevValue, currentValue); } } diff --git a/kraken/lib/src/dom/element.dart b/kraken/lib/src/dom/element.dart index 5e4b66d8eb..745a217ab4 100644 --- a/kraken/lib/src/dom/element.dart +++ b/kraken/lib/src/dom/element.dart @@ -200,10 +200,10 @@ class Element extends Node after = (previousRenderBoxModel.parentData as ContainerParentDataMixin).previousSibling; } - _detachRenderBoxModel(previousRenderBoxModel); + RenderBoxModel.detachRenderBox(previousRenderBoxModel); if (parentRenderObject != null) { - _attachRenderBoxModel(parentRenderObject, nextRenderBoxModel, after: after); + RenderBoxModel.attachRenderBox(parentRenderObject, nextRenderBoxModel, after: after); } } renderBoxModel = nextRenderBoxModel; @@ -414,14 +414,17 @@ class Element extends Node super.willDetachRenderer(); // Cancel running transition. renderStyle.cancelRunningTransition(); + + RenderBoxModel _renderBoxModel = renderBoxModel!; + // Remove all intersection change listeners. - renderBoxModel!.clearIntersectionChangeListeners(); + _renderBoxModel.clearIntersectionChangeListeners(); // Remove fixed children from root when element disposed. - _removeFixedChild(renderBoxModel!, ownerDocument.documentElement!._renderLayoutBox!); + _removeFixedChild(_renderBoxModel, ownerDocument.documentElement!._renderLayoutBox!); // Remove renderBox. - _removeRenderBoxModel(renderBoxModel!); + _renderBoxModel.detachFromContainingBlock(); // Remove pointer listener removeEventResponder(renderBoxModel!); @@ -483,43 +486,147 @@ class Element extends Node } } - void _updateRenderBoxModelWithPosition() { - RenderBoxModel _renderBoxModel = renderBoxModel!; - CSSPositionType currentPosition = renderStyle.position; + // Find all the nested position absolute elements which need to change the containing block + // from other element to this element when element's position is changed from static to relative. + // Take following html for example, div of id=4 should reposition from div of id=1 to div of id=2. + //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ List findNestedPositionAbsoluteChildren() { + List positionAbsoluteChildren = []; + + if (!isRendererAttached) return positionAbsoluteChildren; + + children.forEach((Element child) { + if (!child.isRendererAttached) return; - // Remove fixed children before convert to non repaint boundary renderObject - if (currentPosition != CSSPositionType.fixed) { - _removeFixedChild(_renderBoxModel, ownerDocument.documentElement!._renderLayoutBox!); + RenderBoxModel childRenderBoxModel = child.renderBoxModel!; + RenderStyle childRenderStyle = childRenderBoxModel.renderStyle; + if (childRenderStyle.position == CSSPositionType.absolute) { + positionAbsoluteChildren.add(child); + } + // No need to loop layout box whose position is not static. + if (childRenderStyle.position != CSSPositionType.static) { + return; + } + if (childRenderBoxModel is RenderLayoutBox) { + List mergedChildren = child.findNestedPositionAbsoluteChildren(); + for (Element child in mergedChildren) { + positionAbsoluteChildren.add(child); + } + } + }); + + return positionAbsoluteChildren; + } + + // Find all the direct position absolute elements which need to change the containing block + // from this element to other element when element's position is changed from relative to static. + // Take following html for example, div of id=4 should reposition from div of id=2 to div of id=1. + //
+ //
+ //
+ //
+ //
+ //
+ //
+ //
+ List findDirectPositionAbsoluteChildren() { + List directPositionAbsoluteChildren = []; + + if (!isRendererAttached) return directPositionAbsoluteChildren; + + RenderBox? child = (renderBoxModel as RenderLayoutBox).firstChild; + + while (child != null) { + final ContainerParentDataMixin? childParentData = + child.parentData as ContainerParentDataMixin?; + if (child is! RenderLayoutBox) { + child = childParentData!.nextSibling; + continue; + } + if (child.renderStyle.position == CSSPositionType.absolute) { + directPositionAbsoluteChildren.add(child.renderStyle.target); + } + child = childParentData!.nextSibling; } - RenderBox? previousSibling; - RenderPositionPlaceholder? renderPositionPlaceholder = _renderBoxModel.renderPositionPlaceholder; - // It needs to find the previous sibling of the previous sibling if the placeholder of - // positioned element exists and follows renderObject at the same time, eg. - //
- if (renderPositionPlaceholder != null) { - previousSibling = (renderPositionPlaceholder.parentData as ContainerParentDataMixin).previousSibling; - // The placeholder's previousSibling maybe the origin renderBox. - if (previousSibling == _renderBoxModel) { - previousSibling = (_renderBoxModel.parentData as ContainerParentDataMixin).previousSibling; + return directPositionAbsoluteChildren; + } + + void _updateRenderBoxModelWithPosition(CSSPositionType oldPosition) { + CSSPositionType currentPosition = renderStyle.position; + + // No need to detach and reattach renderBoxMode when its position + // changes between static and relative. + if (!(oldPosition == CSSPositionType.static && currentPosition == CSSPositionType.relative) + && !(oldPosition == CSSPositionType.relative && currentPosition == CSSPositionType.static) + ) { + RenderBoxModel _renderBoxModel = renderBoxModel!; + // Remove fixed children before convert to non repaint boundary renderObject + if (currentPosition != CSSPositionType.fixed) { + _removeFixedChild(_renderBoxModel, ownerDocument.documentElement!._renderLayoutBox!); + } + + // Find the renderBox of its containing block. + RenderBox? containingBlockRenderBox = getContainingBlockRenderBox(); + // Find the previous siblings to insert before renderBoxModel is detached. + RenderBox? previousSibling = _renderBoxModel.getPreviousSibling(); + // Detach renderBoxModel from its original parent. + _renderBoxModel.detachFromContainingBlock(); + // Change renderBoxModel type in cases such as position changes to fixed which + // need to create repaintBoundary. + _updateRenderBoxModel(); + // Original parent renderBox. + RenderBox parentRenderBox = parentNode!.renderer!; + // Attach renderBoxModel to its containing block. + renderBoxModel!.attachToContainingBlock(containingBlockRenderBox, parent: parentRenderBox, after: previousSibling); + + // Add fixed children after convert to repaint boundary renderObject. + if (currentPosition == CSSPositionType.fixed) { + _addFixedChild(renderBoxModel!, ownerDocument.documentElement!._renderLayoutBox!); } - _detachRenderBoxModel(renderPositionPlaceholder); - _renderBoxModel.renderPositionPlaceholder = null; - } else { - previousSibling = (_renderBoxModel.parentData as ContainerParentDataMixin).previousSibling; } - // Detach renderBoxModel from original parent. - _detachRenderBoxModel(_renderBoxModel); - _updateRenderBoxModel(); - _addToContainingBlock(after: previousSibling); + // Need to change the containing block of nested position absolute children from its upper parent + // to this element when element's position is changed from static to relative. + if (oldPosition == CSSPositionType.static) { + List positionAbsoluteChildren = findNestedPositionAbsoluteChildren(); + positionAbsoluteChildren.forEach((Element child) { + child.addToContainingBlock(); + }); - // Add fixed children after convert to repaint boundary renderObject. - if (currentPosition == CSSPositionType.fixed) { - _addFixedChild(renderBoxModel!, ownerDocument.documentElement!._renderLayoutBox!); + // Need to change the containing block of direct position absolute children from this element + // to its upper parent when element's position is changed from relative to static. + } else if (currentPosition == CSSPositionType.static) { + List directPositionAbsoluteChildren = findDirectPositionAbsoluteChildren(); + directPositionAbsoluteChildren.forEach((Element child) { + child.addToContainingBlock(); + }); } } + // Add element to its containing block which includes the steps of detach the renderBoxModel + // from its original parent and attach to its new containing block. + void addToContainingBlock() { + RenderBoxModel _renderBoxModel = renderBoxModel!; + // Find the renderBox of its containing block. + RenderBox? containingBlockRenderBox = getContainingBlockRenderBox(); + // Find the previous siblings to insert before renderBoxModel is detached. + RenderBox? previousSibling = _renderBoxModel.getPreviousSibling(); + // Detach renderBoxModel from its original parent. + _renderBoxModel.detachFromContainingBlock(); + // Original parent renderBox. + RenderBox parentRenderBox = parentNode!.renderer!; + // Attach renderBoxModel of to its containing block. + _renderBoxModel.attachToContainingBlock(containingBlockRenderBox, parent: parentRenderBox, after: previousSibling); + } + void addChild(RenderBox child) { if (_renderLayoutBox != null) { RenderLayoutBox? scrollingContentBox = _renderLayoutBox!.renderScrollingContent; @@ -582,7 +689,7 @@ class Element extends Node if (renderer != null) { // If element attach WidgetElement, render obeject should be attach to render tree when mount. if (parent is! WidgetElement) { - _attachRenderBoxModel(parent.renderer!, renderer!, after: after); + RenderBoxModel.attachRenderBox(parent.renderer!, renderer!, after: after); } // Flush pending style before child attached. @@ -736,30 +843,17 @@ class Element extends Node return super.replaceChild(newNode, oldNode); } - // The position and size of an element's box(es) are sometimes calculated relative to a certain rectangle, - // called the containing block of the element. - // Definition of "containing block": https://www.w3.org/TR/CSS21/visudet.html#containing-block-details - void _addToContainingBlock({RenderBox? after}) { - assert(parentNode != null); + RenderBox? getContainingBlockRenderBox() { + RenderBox? containingBlockRenderBox; CSSPositionType positionType = renderStyle.position; - RenderBoxModel _renderBoxModel = renderBoxModel!; - // HTML element's parentNode is viewportBox. - RenderBox parentRenderBox = parentNode!.renderer!; - - // The containing block of an element is defined as follows: - if (positionType == CSSPositionType.relative || positionType == CSSPositionType.static || positionType == CSSPositionType.sticky) { - // If the element's position is 'relative' or 'static', - // the containing block is formed by the content edge of the nearest block container ancestor box. - _attachRenderBoxModel(parentRenderBox, _renderBoxModel, after: after); - if (positionType == CSSPositionType.sticky) { - // Placeholder of sticky renderBox need to inherit offset from original renderBox, - // so it needs to layout before original renderBox. - _addPositionPlaceholder(parentRenderBox, _renderBoxModel, after: after); - } - } else { - RenderLayoutBox? containingBlockRenderBox; - if (positionType == CSSPositionType.absolute) { + switch(positionType) { + case CSSPositionType.relative: + case CSSPositionType.static: + case CSSPositionType.sticky: + containingBlockRenderBox = parentNode!.renderer; + break; + case CSSPositionType.absolute: // If the element has 'position: absolute', the containing block is established by the nearest ancestor with // a 'position' of 'absolute', 'relative' or 'fixed', in the following way: // 1. In the case that the ancestor is an inline element, the containing block is the bounding box around @@ -767,37 +861,14 @@ class Element extends Node // In CSS 2.1, if the inline element is split across multiple lines, the containing block is undefined. // 2. Otherwise, the containing block is formed by the padding edge of the ancestor. containingBlockRenderBox = _findContainingBlock(this, ownerDocument.documentElement!)?._renderLayoutBox; - } else if (positionType == CSSPositionType.fixed) { + break; + case CSSPositionType.fixed: // If the element has 'position: fixed', the containing block is established by the viewport // in the case of continuous media or the page area in the case of paged media. containingBlockRenderBox = ownerDocument.documentElement!._renderLayoutBox; - } - - if (containingBlockRenderBox == null) return; - - // If container block is same as origin parent, the placeholder must after the origin renderBox - // because placeholder depends the constraints in layout stage. - if (containingBlockRenderBox == parentRenderBox) { - after = _renderBoxModel; - } - - // Set custom positioned parentData. - RenderLayoutParentData parentData = RenderLayoutParentData(); - _renderBoxModel.parentData = CSSPositionedLayout.getPositionParentData(_renderBoxModel, parentData); - // Add child to containing block parent. - _attachRenderBoxModel(containingBlockRenderBox, _renderBoxModel, isLast: true); - // Add position holder to origin position parent. - _addPositionPlaceholder(parentRenderBox, _renderBoxModel, after: after); + break; } - } - - void _addPositionPlaceholder(RenderBox parentRenderBox, RenderBoxModel renderBoxModel, {RenderBox? after}) { - // Position holder size will be updated on layout. - RenderPositionPlaceholder renderPositionPlaceholder = RenderPositionPlaceholder(preferredSize: Size.zero); - renderBoxModel.renderPositionPlaceholder = renderPositionPlaceholder; - renderPositionPlaceholder.positioned = renderBoxModel; - - _attachRenderBoxModel(parentRenderBox, renderPositionPlaceholder, after: after); + return containingBlockRenderBox; } // FIXME: only compatible with kraken plugins @@ -823,30 +894,19 @@ class Element extends Node if (!isRendererAttached && parentElement != null && parentElement!.isRendererAttached) { // If element attach WidgetElement, render obeject should be attach to render tree when mount. if (parentNode is! WidgetElement) { - _addToContainingBlock(after: previousSibling?.renderer); + RenderBoxModel _renderBoxModel = renderBoxModel!; + // Find the renderBox of its containing block. + RenderBox? containingBlockRenderBox = getContainingBlockRenderBox(); + // Find the previous siblings to insert before renderBoxModel is detached. + RenderBox? preSibling = previousSibling?.renderer; + // Original parent renderBox. + RenderBox parentRenderBox = parentNode!.renderer!; + _renderBoxModel.attachToContainingBlock(containingBlockRenderBox, parent: parentRenderBox, after: preSibling); } ensureChildAttached(); } } - void _attachRenderBoxModel(RenderObject parentRenderObject, RenderBox renderBox, {RenderObject? after, bool isLast = false}) { - if (isLast) { - assert(after == null); - } - if (parentRenderObject is RenderObjectWithChildMixin) { // RenderViewportBox - parentRenderObject.child = renderBox; - } else if (parentRenderObject is ContainerRenderObjectMixin) { // RenderLayoutBox or RenderSliverList - // Should attach to renderScrollingContent if it is scrollable. - if (parentRenderObject is RenderLayoutBox) { - parentRenderObject = parentRenderObject.renderScrollingContent ?? parentRenderObject; - } - if (isLast) { - after = parentRenderObject.lastChild; - } - parentRenderObject.insert(renderBox, after: after); - } - } - void setRenderStyleProperty(String name, dynamic value) { // Memorize the variable value to renderStyle object. if (CSSVariable.isVariable(name)) { @@ -901,8 +961,9 @@ class Element extends Node renderStyle.contentVisibility = value; break; case POSITION: + CSSPositionType oldPosition = renderStyle.position; renderStyle.position = value; - _updateRenderBoxModelWithPosition(); + _updateRenderBoxModelWithPosition(oldPosition); break; case TOP: renderStyle.top = value; @@ -1519,37 +1580,7 @@ bool _hasIntersectionObserverEvent(Map eventHandlers) { eventHandlers.containsKey('intersectionchange'); } -void _detachRenderBoxModel(RenderObject renderBox) { - if (renderBox.parent == null) return; - - // Remove reference from parent - RenderObject? parentRenderObject = renderBox.parent as RenderObject; - if (parentRenderObject is RenderObjectWithChildMixin) { - parentRenderObject.child = null; // Case for single child, eg. RenderViewportBox - } else if (parentRenderObject is ContainerRenderObjectMixin) { - parentRenderObject.remove(renderBox); // Case for multi children, eg. RenderLayoutBox or RenderSliverList - } -} - -void _removeRenderBoxModel(RenderBoxModel renderBox) { - _detachRenderBoxModel(renderBox); - - // Remove scrolling content layout box of overflow element. - if (renderBox is RenderLayoutBox && renderBox.renderScrollingContent != null) { - renderBox.remove(renderBox.renderScrollingContent!); - } - // Remove placeholder of positioned element. - RenderPositionPlaceholder? renderPositionHolder = renderBox.renderPositionPlaceholder; - if (renderPositionHolder != null) { - RenderLayoutBox? parentLayoutBox = renderPositionHolder.parent as RenderLayoutBox?; - if (parentLayoutBox != null) { - parentLayoutBox.remove(renderPositionHolder); - renderBox.renderPositionPlaceholder = null; - } - } -} - -/// Cache fixed renderObject to root element +// Cache fixed renderObject to root element void _addFixedChild(RenderBoxModel childRenderBoxModel, RenderLayoutBox rootRenderLayoutBox) { rootRenderLayoutBox = rootRenderLayoutBox.renderScrollingContent ?? rootRenderLayoutBox; List fixedChildren = rootRenderLayoutBox.fixedChildren; @@ -1558,7 +1589,7 @@ void _addFixedChild(RenderBoxModel childRenderBoxModel, RenderLayoutBox rootRend } } -/// Remove non fixed renderObject from root element +// Remove non fixed renderObject from root element void _removeFixedChild(RenderBoxModel childRenderBoxModel, RenderLayoutBox rootRenderLayoutBox) { rootRenderLayoutBox = rootRenderLayoutBox.renderScrollingContent ?? rootRenderLayoutBox; List fixedChildren = rootRenderLayoutBox.fixedChildren; diff --git a/kraken/lib/src/rendering/box_model.dart b/kraken/lib/src/rendering/box_model.dart index 4ecd8065c8..bbdeb03955 100644 --- a/kraken/lib/src/rendering/box_model.dart +++ b/kraken/lib/src/rendering/box_model.dart @@ -1219,6 +1219,98 @@ class RenderBoxModel extends RenderBox } } + // Detach renderBoxModel from its containing block. + // Need to remove position placeholder besides removing itself. + void detachFromContainingBlock() { + detachRenderBox(this); + + // Remove placeholder of positioned element. + _detachPositionPlaceholder(this); + } + + // The position and size of an element's box(es) are sometimes calculated relative to a certain rectangle, + // called the containing block of the element. + // Definition of "containing block": https://www.w3.org/TR/CSS21/visudet.html#containing-block-details + void attachToContainingBlock( + RenderBox? containingBlockRenderBox, + { RenderBox? parent, RenderBox? after } + ) { + if (parent == null || containingBlockRenderBox == null) return; + + RenderBoxModel renderBoxModel = this; + CSSPositionType positionType = renderBoxModel.renderStyle.position; + // The containing block of an element is defined as follows: + if (positionType == CSSPositionType.relative + || positionType == CSSPositionType.static + || positionType == CSSPositionType.sticky + ) { + // If the element's position is 'relative' or 'static', + // the containing block is formed by the content edge of the nearest block container ancestor box. + attachRenderBox(containingBlockRenderBox, renderBoxModel, after: after); + + if (positionType == CSSPositionType.sticky) { + // Placeholder of sticky renderBox need to inherit offset from original renderBox, + // so it needs to layout before original renderBox. + _attachPositionPlaceholder(containingBlockRenderBox, renderBoxModel, after: after); + } + } else { + // Set custom positioned parentData. + RenderLayoutParentData parentData = RenderLayoutParentData(); + renderBoxModel.parentData = CSSPositionedLayout.getPositionParentData(renderBoxModel, parentData); + // Add child to containing block parent. + attachRenderBox(containingBlockRenderBox, renderBoxModel, isLast: true); + + // If container block is same as origin parent, the placeholder must after the origin renderBox + // because placeholder depends the constraints in layout stage. + RenderBox? previousSibling = containingBlockRenderBox == parent ? + renderBoxModel : after; + + // Add position holder to origin position parent. + _attachPositionPlaceholder(parent, renderBoxModel, after: previousSibling); + } + } + + // Find previous sibling renderObject of renderBoxModel, used for inserting to containing block. + // If renderBoxModel is positioned, find the original place (position placeholder) to insert to + // when its position changes to relative/static/sticky. + RenderBox? getPreviousSibling() { + RenderBoxModel renderBoxModel = this; + RenderBox? previousSibling; + RenderPositionPlaceholder? renderPositionPlaceholder = renderBoxModel.renderPositionPlaceholder; + // It needs to find the previous sibling of the previous sibling if the placeholder of + // positioned element exists and follows renderObject at the same time, eg. + //
+ if (renderPositionPlaceholder != null) { + previousSibling = (renderPositionPlaceholder.parentData as ContainerParentDataMixin).previousSibling; + // The placeholder's previousSibling maybe the origin renderBox. + if (previousSibling == renderBoxModel) { + previousSibling = (renderBoxModel.parentData as ContainerParentDataMixin).previousSibling; + } + } else { + previousSibling = (renderBoxModel.parentData as ContainerParentDataMixin).previousSibling; + } + return previousSibling; + } + + // Attach placeholder of renderBoxModel from tree. + void _attachPositionPlaceholder(RenderBox parentRenderBox, RenderBoxModel renderBoxModel, {RenderBox? after}) { + // Position holder size will be updated on layout. + RenderPositionPlaceholder renderPositionPlaceholder = RenderPositionPlaceholder(preferredSize: Size.zero); + renderBoxModel.renderPositionPlaceholder = renderPositionPlaceholder; + renderPositionPlaceholder.positioned = renderBoxModel; + + attachRenderBox(parentRenderBox, renderPositionPlaceholder, after: after); + } + + // Detach placeholder of renderBoxModel from tree. + void _detachPositionPlaceholder(RenderBoxModel renderBoxModel) { + RenderPositionPlaceholder? renderPositionHolder = renderBoxModel.renderPositionPlaceholder; + if (renderPositionHolder != null) { + detachRenderBox(renderPositionHolder); + renderBoxModel.renderPositionPlaceholder = null; + } + } + /// Called when its corresponding element disposed @mustCallSuper void dispose() { @@ -1235,7 +1327,7 @@ class RenderBoxModel extends RenderBox // Evict render decoration image cache. renderStyle.decoration?.image?.image.evict(); - // Remove reference from childs + // Remove reference from children. _detachAllChildren(); } @@ -1385,4 +1477,40 @@ class RenderBoxModel extends RenderBox debugTransformProperties(properties); debugOpacityProperties(properties); } + + // Attach renderBox from tree. + static void attachRenderBox( + RenderObject parentRenderObject, + RenderBox renderBox, + {RenderObject? after, bool isLast = false} + ) { + if (isLast) { + assert(after == null); + } + if (parentRenderObject is RenderObjectWithChildMixin) { // RenderViewportBox + parentRenderObject.child = renderBox; + } else if (parentRenderObject is ContainerRenderObjectMixin) { // RenderLayoutBox or RenderSliverList + // Should attach to renderScrollingContent if it is scrollable. + if (parentRenderObject is RenderLayoutBox) { + parentRenderObject = parentRenderObject.renderScrollingContent ?? parentRenderObject; + } + if (isLast) { + after = parentRenderObject.lastChild; + } + parentRenderObject.insert(renderBox, after: after); + } + } + + // Detach renderBox from tree. + static void detachRenderBox(RenderObject renderBox) { + if (renderBox.parent == null) return; + + // Remove reference from parent. + RenderObject? parentRenderObject = renderBox.parent as RenderObject; + if (parentRenderObject is RenderObjectWithChildMixin) { + parentRenderObject.child = null; // Case for single child, eg. RenderViewportBox. + } else if (parentRenderObject is ContainerRenderObjectMixin) { + parentRenderObject.remove(renderBox); // Case for multi children, eg. RenderLayoutBox or RenderSliverList. + } + } }