fix(editor): centerize iframe modal in mobile (#13073)

Close
[BS-3160](https://linear.app/affine-design/issue/BS-3160/新的-embed-输入的-sheet-没有弹起来)

#### PR Dependency Tree


* **PR #13073** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Improved popup positioning and responsiveness when using virtual
keyboards, ensuring popups remain visible and correctly placed above the
keyboard.
* Standardized how popups and overlays are created and referenced
throughout the app, reducing inconsistencies and potential display
issues.
* Enhanced stability of date picker, AI panels, and slash menu popovers
by refining how their elements are managed and updated.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
L-Sun
2025-07-07 19:17:57 +08:00
committed by GitHub
parent 0833d0314c
commit 339bfdae0f
9 changed files with 46 additions and 17 deletions

View File

@@ -11,6 +11,7 @@ import {
type IframeOptions, type IframeOptions,
LinkPreviewServiceIdentifier, LinkPreviewServiceIdentifier,
NotificationProvider, NotificationProvider,
VirtualKeyboardProvider,
} from '@blocksuite/affine-shared/services'; } from '@blocksuite/affine-shared/services';
import { matchModels } from '@blocksuite/affine-shared/utils'; import { matchModels } from '@blocksuite/affine-shared/utils';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions'; import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
@@ -213,9 +214,33 @@ export class EmbedIframeBlockComponent extends CaptionedBlockComponent<EmbedIfra
this._linkInputAbortController.abort(); this._linkInputAbortController.abort();
} }
const keyboard = this.host.std.getOptional(VirtualKeyboardProvider);
const computePosition = keyboard
? {
referenceElement: document.body,
placement: 'top' as const,
middleware: [
offset(({ rects }) => ({
mainAxis:
-rects.floating.height -
(window.innerHeight -
rects.floating.height -
keyboard.height$.value) /
2,
})),
],
autoUpdate: { animationFrame: true },
}
: {
referenceElement: this._blockContainer,
placement: 'bottom' as const,
middleware: [flip(), offset(LINK_CREATE_POPUP_OFFSET), shift()],
autoUpdate: { animationFrame: true },
};
this._linkInputAbortController = new AbortController(); this._linkInputAbortController = new AbortController();
createLitPortal({ const { update } = createLitPortal({
template: html`<embed-iframe-link-input-popup template: html`<embed-iframe-link-input-popup
.model=${this.model} .model=${this.model}
.abortController=${this._linkInputAbortController} .abortController=${this._linkInputAbortController}
@@ -224,15 +249,19 @@ export class EmbedIframeBlockComponent extends CaptionedBlockComponent<EmbedIfra
.options=${options} .options=${options}
></embed-iframe-link-input-popup>`, ></embed-iframe-link-input-popup>`,
container: document.body, container: document.body,
computePosition: { computePosition,
referenceElement: this._blockContainer,
placement: 'bottom',
middleware: [flip(), offset(LINK_CREATE_POPUP_OFFSET), shift()],
autoUpdate: { animationFrame: true },
},
abortController: this._linkInputAbortController, abortController: this._linkInputAbortController,
closeOnClickAway: true, closeOnClickAway: true,
}); });
if (keyboard) {
this._linkInputAbortController.signal.addEventListener(
'abort',
keyboard.height$.subscribe(() => {
update();
})
);
}
}; };
/** /**

View File

@@ -116,7 +116,7 @@ export class LatexBlockComponent extends CaptionedBlockComponent<LatexBlockModel
this.selection.setGroup('note', []); this.selection.setGroup('note', []);
const portal = createLitPortal({ const { portal } = createLitPortal({
template: html`<latex-editor-menu template: html`<latex-editor-menu
.std=${this.std} .std=${this.std}
.latexSignal=${this.model.props.latex$} .latexSignal=${this.model.props.latex$}

View File

@@ -179,7 +179,7 @@ export class HoverController implements ReactiveController {
this._portal = createLitPortal({ this._portal = createLitPortal({
...portalOptions, ...portalOptions,
abortController: this._abortController, abortController: this._abortController,
}); }).portal;
const transition = this._hoverOptions.transition; const transition = this._hoverOptions.transition;
if (transition) { if (transition) {

View File

@@ -161,7 +161,7 @@ export function createLitPortal({
} }
if (!positionConfigOrFn) { if (!positionConfigOrFn) {
return portalRoot; return { portal: portalRoot, update: () => {} };
} }
const visibility = portalRoot.style.visibility; const visibility = portalRoot.style.visibility;
@@ -221,5 +221,5 @@ export function createLitPortal({
}); });
} }
return portalRoot; return { portal: portalRoot, update };
} }

View File

@@ -78,7 +78,7 @@ export class DateCell extends BaseCellRenderer<number, number> {
}, },
}); });
} else { } else {
const root = createLitPortal({ const { portal } = createLitPortal({
abortController, abortController,
closeOnClickAway: true, closeOnClickAway: true,
computePosition: { computePosition: {
@@ -107,7 +107,7 @@ export class DateCell extends BaseCellRenderer<number, number> {
// for now the slide-layout-modal's z-index is `1001` // for now the slide-layout-modal's z-index is `1001`
// the z-index of popover should be higher than it // the z-index of popover should be higher than it
// root.style.zIndex = 'var(--affine-z-index-popover)'; // root.style.zIndex = 'var(--affine-z-index-popover)';
root.style.zIndex = '1002'; portal.style.zIndex = '1002';
} }
}; };

View File

@@ -190,7 +190,7 @@ export class AffineLatexNode extends SignalWatcher(
blockComponent.selection.setGroup('note', []); blockComponent.selection.setGroup('note', []);
const portal = createLitPortal({ const { portal } = createLitPortal({
template: html`<latex-editor-menu template: html`<latex-editor-menu
.std=${this.std} .std=${this.std}
.latexSignal=${this.latexEditorSignal} .latexSignal=${this.latexEditorSignal}

View File

@@ -378,7 +378,7 @@ export class InnerSlashMenu extends WithDisposable(LitElement) {
this._closeSubMenu(); this._closeSubMenu();
}); });
const subMenuElement = createLitPortal({ const { portal: subMenuElement } = createLitPortal({
shadowDom: false, shadowDom: false,
template: html`<inner-slash-menu template: html`<inner-slash-menu
.context=${this.context} .context=${this.context}

View File

@@ -305,6 +305,6 @@ export function handleInlineAskAIAction(
}, },
abortController: abortController, abortController: abortController,
closeOnClickAway: true, closeOnClickAway: true,
}); }).portal;
}, 0); }, 0);
} }

View File

@@ -65,7 +65,7 @@ export class AskAIToolbarButton extends WithDisposable(LitElement) {
}, },
abortController: this._abortController, abortController: this._abortController,
closeOnClickAway: true, closeOnClickAway: true,
}); }).portal;
}; };
private readonly _generateAnswer: AffineAIPanelWidgetConfig['generateAnswer'] = private readonly _generateAnswer: AffineAIPanelWidgetConfig['generateAnswer'] =