fix(editor): edgeless can't slider with finger (#15091)

fix bug edgeless can't slider with finger 

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

* **New Features**
* Added mobile immersive edgeless mode with dynamic chrome auto-hide and
tap-gesture controls.
  * Added a mobile zoom ruler UI for edgeless.
* **Bug Fixes**
* Improved iOS rendering/zoom by applying low-zoom survival behavior,
gesture-aware refresh deferral, and effective-DPR canvas scaling.
* Fixed iOS webview zoom/bounce and process-termination reload behavior.
  * Improved placeholder styling with theme-aware colors.
* **Chores**
  * Updated local ignore rules and iOS app build/version configuration.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: DarkSky <darksky2048@gmail.com>
This commit is contained in:
keepClamDown
2026-06-16 21:19:31 +08:00
committed by GitHub
parent c51bdb74de
commit a77d89bb1a
43 changed files with 4749 additions and 273 deletions

View File

@@ -2,12 +2,14 @@ import {
AFFINE_EDGELESS_ZOOM_TOOLBAR_WIDGET,
AffineEdgelessZoomToolbarWidget,
} from '.';
import { MobileZoomRuler } from './mobile-zoom-ruler';
import { ZoomBarToggleButton } from './zoom-bar-toggle-button';
import { EdgelessZoomToolbar } from './zoom-toolbar';
export function effects() {
customElements.define('edgeless-zoom-toolbar', EdgelessZoomToolbar);
customElements.define('zoom-bar-toggle-button', ZoomBarToggleButton);
customElements.define('mobile-zoom-ruler', MobileZoomRuler);
customElements.define(
AFFINE_EDGELESS_ZOOM_TOOLBAR_WIDGET,
AffineEdgelessZoomToolbarWidget

View File

@@ -1,5 +1,6 @@
import { EdgelessLegacySlotIdentifier } from '@blocksuite/affine-block-surface';
import type { RootBlockModel } from '@blocksuite/affine-model';
import { IS_MOBILE } from '@blocksuite/global/env';
import { WidgetComponent, WidgetViewExtension } from '@blocksuite/std';
import { GfxControllerIdentifier } from '@blocksuite/std/gfx';
import { effect } from '@preact/signals-core';
@@ -14,15 +15,20 @@ export class AffineEdgelessZoomToolbarWidget extends WidgetComponent<RootBlockMo
static override styles = css`
:host {
position: absolute;
bottom: 20px;
bottom: var(--affine-edgeless-zoom-toolbar-bottom, 20px);
left: 12px;
z-index: var(--affine-z-index-popover);
display: flex;
justify-content: center;
pointer-events: none;
-webkit-user-select: none;
user-select: none;
}
mobile-zoom-ruler {
pointer-events: auto;
}
@container viewport (width <= 1200px) {
edgeless-zoom-toolbar {
display: none;
@@ -73,10 +79,14 @@ export class AffineEdgelessZoomToolbarWidget extends WidgetComponent<RootBlockMo
}
override render() {
if (this._hide || !this.edgeless) {
if (this._hide) {
return nothing;
}
if (IS_MOBILE) {
return html`<mobile-zoom-ruler .std=${this.std}></mobile-zoom-ruler>`;
}
return html`
<edgeless-zoom-toolbar .std=${this.std}></edgeless-zoom-toolbar>
<zoom-bar-toggle-button .std=${this.std}></zoom-bar-toggle-button>

View File

@@ -0,0 +1,139 @@
import { stopPropagation } from '@blocksuite/affine-shared/utils';
import { WithDisposable } from '@blocksuite/global/lit';
import { ViewBarIcon } from '@blocksuite/icons/lit';
import type { BlockStdScope } from '@blocksuite/std';
import { GfxControllerIdentifier } from '@blocksuite/std/gfx';
import { baseTheme } from '@toeverything/theme';
import { css, html, LitElement, unsafeCSS } from 'lit';
import { property } from 'lit/decorators.js';
/**
* Compact zoom indicator for narrow / mobile edgeless viewports.
* Shows the live zoom percentage and a fit-to-screen action in a pill HUD
* anchored to the bottom-left of the canvas.
*/
export class MobileZoomRuler extends WithDisposable(LitElement) {
static override styles = css`
:host {
display: flex;
pointer-events: auto;
font-family: ${unsafeCSS(baseTheme.fontSansFamily)};
}
.zoom-pill {
display: flex;
align-items: center;
height: 32px;
background: var(--affine-background-overlay-panel-color);
border: 1px solid var(--affine-border-color);
border-radius: 999px;
box-shadow: var(--affine-shadow-1);
overflow: hidden;
}
.zoom-label {
display: flex;
align-items: center;
justify-content: center;
min-width: 44px;
padding: 0 12px;
font-size: 12px;
font-weight: 500;
line-height: 1;
color: var(--affine-text-secondary-color);
white-space: nowrap;
user-select: none;
}
.divider {
width: 1px;
height: 16px;
background: var(--affine-border-color);
flex-shrink: 0;
}
.fit-button {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 100%;
padding: 0;
border: none;
background: transparent;
color: var(--affine-icon-color);
cursor: pointer;
}
.fit-button:hover:not(:disabled) {
background: var(--affine-hover-color);
color: var(--affine-primary-color);
}
.fit-button:disabled {
cursor: not-allowed;
color: var(--affine-text-disable-color);
}
.fit-button svg {
width: 20px;
height: 20px;
}
`;
get gfx() {
return this.std.get(GfxControllerIdentifier);
}
get viewport() {
return this.gfx.viewport;
}
get zoom() {
if (!this.viewport) {
return 1;
}
return this.viewport.zoom;
}
override firstUpdated() {
const { disposables } = this;
const viewport = this.viewport;
if (!viewport) {
return;
}
disposables.add(
viewport.viewportUpdated.subscribe(() => this.requestUpdate())
);
disposables.add(viewport.zoomUpdated.subscribe(() => this.requestUpdate()));
}
override render() {
const formattedZoom = `${Math.round(this.zoom * 100)}%`;
const locked = this.viewport?.locked || this.std.store.readonly;
return html`
<div
class="zoom-pill"
@dblclick=${stopPropagation}
@mousedown=${stopPropagation}
@mouseup=${stopPropagation}
@pointerdown=${stopPropagation}
>
<span class="zoom-label">${formattedZoom}</span>
<span class="divider"></span>
<button
class="fit-button"
aria-label="Fit to screen"
?disabled=${locked}
@click=${() => this.gfx.fitToScreen()}
>
${ViewBarIcon()}
</button>
</div>
`;
}
@property({ attribute: false })
accessor std!: BlockStdScope;
}