diff --git a/src/components/left/main/AccountMenuItems.tsx b/src/components/left/main/AccountMenuItems.tsx index 70b1b9421..071887c60 100644 --- a/src/components/left/main/AccountMenuItems.tsx +++ b/src/components/left/main/AccountMenuItems.tsx @@ -69,7 +69,7 @@ const AccountMenuItems = ({ }, [accounts, currentCount, totalLimit]); return ( -
+ <> {Object.entries(accounts || {}) .sort(([, account]) => (account.userId === currentUser.id ? -1 : 1)) .map(([slot, account], index, arr) => { @@ -116,7 +116,7 @@ const AccountMenuItems = ({ {lang('MenuAddAccount')} )} -
+ ); }; diff --git a/src/components/test/TestFragment2.tsx b/src/components/test/TestFragment2.tsx index 4e0502408..b22d1ca8f 100644 --- a/src/components/test/TestFragment2.tsx +++ b/src/components/test/TestFragment2.tsx @@ -11,12 +11,7 @@ export function App() { }} >

Click to update

- {true && ( - <> - {trigger && fragment} - {trigger && content} - - )} +
This should always go last.
@@ -24,4 +19,12 @@ export function App() { ); } +function FragmentContainer({ items }: { items: number[] }) { + return ( + <> + {items.map((n) =>
{n}
)} + + ); +} + export default App; diff --git a/src/lib/teact/teact-dom.ts b/src/lib/teact/teact-dom.ts index d42603f2b..169f958ea 100644 --- a/src/lib/teact/teact-dom.ts +++ b/src/lib/teact/teact-dom.ts @@ -303,6 +303,15 @@ function mountChildren( }, ) { const { children } = $element; + + // Add a placeholder comment node for empty fragments to maintain position + if ($element.type === VirtualType.Fragment && children.length === 0) { + const fragmentEl = $element as VirtualElementFragment; + fragmentEl.placeholderTarget = document.createComment('empty-fragment'); + insertBefore(options.fragment || parentEl, fragmentEl.placeholderTarget, options.nextSibling); + return; + } + for (let i = 0, l = children.length; i < l; i++) { const $child = children[i]; const $renderedChild = renderWithVirtual(parentEl, undefined, $child, $element, currentContext, i, options); @@ -390,7 +399,14 @@ function remount( function unmountRealTree($element: VirtualElement) { if ($element.type === VirtualType.Component) { unmountComponent($element.componentInstance); - } else if ($element.type !== VirtualType.Fragment) { + } else if ($element.type === VirtualType.Fragment) { + // Remove placeholder for empty fragments + const fragment = $element as VirtualElementFragment; + if (fragment.placeholderTarget && fragment.children.length === 0) { + fragment.placeholderTarget.parentNode?.removeChild(fragment.placeholderTarget); + fragment.placeholderTarget = undefined; + } + } else { if ($element.type === VirtualType.Tag) { extraClasses.delete($element.target!); setElementRef($element, undefined); @@ -419,6 +435,15 @@ function insertBefore(parentEl: DOMElement | DocumentFragment, node: Node, nextS function getNextSibling($current: VirtualElement): ChildNode | undefined { if ($current.type === VirtualType.Component || $current.type === VirtualType.Fragment) { + if ($current.children.length === 0) { + // For empty fragments, use the placeholder node to track position + const fragment = $current as VirtualElementFragment; + if (fragment.placeholderTarget) { + return fragment.placeholderTarget.nextSibling || undefined; + } + return undefined; + } + const lastChild = $current.children[$current.children.length - 1]; return getNextSibling(lastChild); } @@ -444,6 +469,28 @@ function renderChildren( return; } + // Handle transitions between empty and non-empty fragments + if ($current.type === VirtualType.Fragment && $new.type === VirtualType.Fragment) { + const currentFragment = $current as VirtualElementFragment; + const newFragment = $new as VirtualElementFragment; + + // If transitioning from empty to non-empty, use the placeholder's position + if (currentFragment.children.length === 0 && newFragment.children.length > 0 && currentFragment.placeholderTarget) { + nextSibling = currentFragment.placeholderTarget.nextSibling || undefined; + // Remove the placeholder as we're adding real content + currentFragment.placeholderTarget.parentNode?.removeChild(currentFragment.placeholderTarget); + currentFragment.placeholderTarget = undefined; + } + + // If transitioning from non-empty to empty, add a placeholder + if (currentFragment.children.length > 0 && newFragment.children.length === 0) { + const lastCurrentChild = currentFragment.children[currentFragment.children.length - 1]; + const siblingAfterFragment = getNextSibling(lastCurrentChild); + newFragment.placeholderTarget = document.createComment('empty-fragment'); + insertBefore(currentEl, newFragment.placeholderTarget, siblingAfterFragment); + } + } + const currentChildren = $current.children; const newChildren = $new.children; diff --git a/src/lib/teact/teact.ts b/src/lib/teact/teact.ts index 8e3382d84..778ccf45d 100644 --- a/src/lib/teact/teact.ts +++ b/src/lib/teact/teact.ts @@ -56,6 +56,7 @@ export interface VirtualElementComponent { export interface VirtualElementFragment { type: VirtualType.Fragment; children: VirtualElementChildren; + placeholderTarget?: Comment; } export type StateHookSetter = (newValue: ((current: T) => T) | T) => void;