mirror of
https://github.com/Darkatse/TauriTavern.git
synced 2026-10-19 07:35:14 +00:00
fix (iOS): fixed an issue in WKWebView on iOS where automatic safe-area insets caused a dead zone at the bottom.
This commit is contained in:
parent
66ace9d643
commit
6e19134edb
19 changed files with 215 additions and 204 deletions
|
|
@ -1,6 +1,6 @@
|
|||
# TauriTavern 移动端开发说明
|
||||
# TauriTavern Android 端开发说明
|
||||
|
||||
本文档记录当前移动端(Android / iOS)开发中已经踩过的关键问题、根因分析、已落地方案,以及对应的架构改动。目标是避免重复踩坑,并为后续替换官方修复留出清晰迁移路径。
|
||||
本文档记录当前移 Android 端开发中已经踩过的关键问题、根因分析、已落地方案,以及对应的架构改动。目标是避免重复踩坑,并为后续替换官方修复留出清晰迁移路径。
|
||||
|
||||
## 1. Android WebView 安全区注入时机竞态
|
||||
|
||||
|
|
@ -33,14 +33,17 @@ https://github.com/tauri-apps/tauri/issues/14240
|
|||
|
||||
- 保留 edge-to-edge 与透明系统栏配置(沉浸基础);
|
||||
- 监听系统栏与 IME inset;
|
||||
- 将 inset 写入前端 CSS 变量:
|
||||
- `--tt-safe-area-top/right/left/bottom`(系统安全区)
|
||||
- Android native 注入的 CSS 变量(provider 层):
|
||||
- `--tt-inset-top/right/left/bottom`(布局应避开的有效 inset:system bars + cutout 等,沉浸模式下为 0)
|
||||
- `--tt-ime-bottom`(输入法可见时的底部 inset)
|
||||
- `--tt-base-viewport-height`(无 IME 时的基准 viewport 高度)
|
||||
- 前端布局消费的 CSS 变量(contract 层):
|
||||
- `--tt-inset-top/right/left/bottom`(有效避让 inset;iOS 由 `env()` 提供,Android 由 native 注入覆盖)
|
||||
- `--tt-viewport-bottom-inset`(前端通过 `max()` 合成有效底部 inset)
|
||||
|
||||
Android 语义说明:
|
||||
Android 语义说明(以 contract 层为准):
|
||||
|
||||
- `--tt-safe-area-*` 表示**当前布局应避开的有效安全区**;
|
||||
- `--tt-inset-*` 表示**当前布局应避开的有效 inset**;
|
||||
- 非沉浸模式下,它反映 system bars + `displayCutout`(刘海/打孔)的可见/稳定 inset;
|
||||
- 沉浸模式下,它会回落为 `0`,允许应用顶部 UI 与第三方 fixed 浮层以 full-bleed 方式沉入状态栏区域。
|
||||
|
||||
|
|
@ -54,7 +57,7 @@ Android 语义说明:
|
|||
前端消费变量在:
|
||||
|
||||
- `src/style.css`(变量定义与 fallback)
|
||||
- `src/css/mobile-styles.css`(顶部栏与容器定位使用变量)
|
||||
- `src/css/mobile-styles.css`(顶部栏与容器定位使用 contract 变量)
|
||||
|
||||
### 1.4 维护原则
|
||||
|
||||
|
|
@ -165,7 +168,7 @@ https://v2.tauri.app/develop/resources/#android
|
|||
### 4.4 前端接入点
|
||||
|
||||
- `src/scripts/templates.js`:模板读取在 Tauri 环境下走 `invoke('read_frontend_template')`
|
||||
- `src/css/mobile-styles.css` + `src/style.css`:通过 `--tt-safe-area-*` 消费原生注入的安全区变量
|
||||
- `src/css/mobile-styles.css` + `src/style.css`:通过 `--tt-inset-*` 消费布局契约
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -244,8 +247,8 @@ https://v2.tauri.app/develop/resources/#android
|
|||
- 在 Tauri mobile 安装 overlay safe-area 兼容控制器:
|
||||
- 实现:`src/tauri/main/compat/mobile/mobile-overlay-compat-controller.js`
|
||||
- 入口:`src/tauri/main/bootstrap.js`(仅 Android/iOS UA)
|
||||
- 策略:只观察 `document.body` 直接子节点新增/移除;对疑似第三方浮层(`position: fixed` 且顶边贴近 0)按元素级别设置 `top: max(var(--tt-safe-area-top), <原top>) !important`;并在 `html.style`/`visualViewport` 变化时重新校验。
|
||||
- 依赖:`--tt-safe-area-top` 表示当前布局策略;因此非沉浸模式下会提供顶部避让,沉浸模式下会自然退化为 no-op。
|
||||
- 策略:只观察 `document.body` 直接子节点新增/移除;对疑似第三方浮层(`position: fixed` 且顶边贴近 0)按元素级别设置 `top: max(var(--tt-inset-top), <原top>) !important`;并在 `html.style`/`visualViewport` 变化时重新校验。
|
||||
- 依赖:`--tt-inset-top` 表示当前布局策略;因此非沉浸模式下会提供顶部避让,沉浸模式下会自然退化为 no-op。
|
||||
|
||||
设计约束:
|
||||
|
||||
|
|
@ -11,13 +11,15 @@
|
|||
|
||||
结论(当前实现的核心要点):
|
||||
|
||||
1. **Safe‑area 是“宿主提供的布局契约”**:Android 由 native 监听 `WindowInsets` 并注入当前布局应避开的 `--tt-safe-area-*`;iOS 以 CSS `env(safe-area-inset-*)` 为主。
|
||||
2. **沉浸模式是 full-bleed 策略开关**:Android 沉浸(system bars 隐藏)时,`--tt-safe-area-*` 回落为 `0`,因此第一方顶部 UI 与第三方 fixed 浮层都允许沉入状态栏/刘海区域。
|
||||
1. **Insets 是“宿主提供的布局契约”**:前端布局只消费 `--tt-inset-*`;Android 由 native 监听 `WindowInsets` 并直接注入当前布局应避开的 inset(`--tt-inset-*`),iOS 以 CSS `env(safe-area-inset-*)` 提供 `--tt-inset-*`。
|
||||
2. **沉浸模式是 full-bleed 策略开关**:Android 沉浸(system bars 隐藏)时,`--tt-inset-*` 回落为 `0`,因此第一方顶部 UI 与第三方 fixed 浮层都允许沉入状态栏/刘海区域。
|
||||
3. **第三方浮层只做元素级最小修正**:不重写 `<style>` 文本,不做全局 subtree observer;仅在出现“顶边贴近 0 的 fixed 浮层”时 patch `top`。
|
||||
4. **iOS 禁用 WKWebView 的自动 content inset 调整**:将 `scrollView.contentInsetAdjustmentBehavior = .never` 并清空 `contentInset/scrollIndicatorInsets`,确保 `window.innerHeight` 真正覆盖到全屏;safe-area 只通过 `env(safe-area-inset-*)` 交给前端消费。
|
||||
|
||||
本目录记录“现状快照”,更完整的问题推导与历史路径见:
|
||||
|
||||
- `docs/MobileDevelopment.md`(6.3)
|
||||
- `docs/AndroidDevelopment.md`
|
||||
- `docs/iOSDevelopment.md`
|
||||
- `docs/MobileDynamicStyleSafeAreaPatch.md`(历史链路)
|
||||
|
||||
## 2. 端到端链路(Android)
|
||||
|
|
@ -30,7 +32,7 @@
|
|||
|
||||
- `WindowCompat.setDecorFitsSystemWindows(window, false)`:启用 edge‑to‑edge。
|
||||
- 状态栏/导航栏透明;允许内容延伸到系统栏区域。
|
||||
- `layoutInDisplayCutoutMode = SHORT_EDGES`:允许在刘海区域布局;是否避让由 `--tt-safe-area-*` 的当前策略决定。
|
||||
- `layoutInDisplayCutoutMode = SHORT_EDGES`:允许在刘海区域布局;是否避让由 `--tt-inset-*` 的当前策略决定。
|
||||
- system bars behavior 使用 `BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE`。
|
||||
|
||||
沉浸模式:
|
||||
|
|
@ -47,14 +49,14 @@
|
|||
|
||||
CSS 变量(对前端的稳定契约):
|
||||
|
||||
- `--tt-safe-area-top/right/left/bottom`:安全区 inset(px)。
|
||||
- `--tt-ime-bottom`:输入法可见时的底部 inset(px),注入在 `#sheld`(优先)或 `html`。
|
||||
- `--tt-inset-top/right/left/bottom`:布局消费的有效避让 inset(px)。
|
||||
- `--tt-ime-bottom`:输入法可见时的底部 inset(px),注入在 `#sheld`。
|
||||
- `--tt-base-viewport-height`:记录“无 IME 时”的基准 viewport 高度(用于稳定高度计算)。
|
||||
|
||||
关键语义(沉浸模式 + 刘海):
|
||||
|
||||
- Android 非沉浸模式下,`--tt-safe-area-*` 反映当前布局应避开的可见/稳定 safe area;
|
||||
- Android 沉浸模式下,`--tt-safe-area-*` 回落为 `0`,应用以 full-bleed 方式覆盖到状态栏/刘海区域。
|
||||
- Android 非沉浸模式下,`--tt-inset-*` 反映当前布局应避开的可见/稳定 safe area;
|
||||
- Android 沉浸模式下,`--tt-inset-*` 回落为 `0`,应用以 full-bleed 方式覆盖到状态栏/刘海区域。
|
||||
|
||||
## 3. 前端消费(CSS / JS)
|
||||
|
||||
|
|
@ -62,8 +64,8 @@ CSS 变量(对前端的稳定契约):
|
|||
|
||||
`src/style.css` 提供默认值(iOS/浏览器主要依赖):
|
||||
|
||||
- `--tt-safe-area-* = env(safe-area-inset-*, 0px)`
|
||||
- `--tt-viewport-bottom-inset = max(var(--tt-safe-area-bottom), var(--tt-ime-bottom))`
|
||||
- `--tt-inset-* = env(safe-area-inset-*, 0px)`(iOS)
|
||||
- `--tt-viewport-bottom-inset = max(var(--tt-inset-bottom), var(--tt-ime-bottom))`
|
||||
|
||||
补充兜底:
|
||||
|
||||
|
|
@ -77,8 +79,8 @@ Android 说明:
|
|||
|
||||
`src/css/mobile-styles.css` 消费上述变量,主要约束点:
|
||||
|
||||
- 顶部容器(如 `#top-settings-holder/#top-bar`)使用 `top: max(var(--tt-safe-area-top), 0px)` 并加入左右 padding。
|
||||
- 主容器 `#sheld` 以 `safe-area-top + topBarBlockSize` 定位,并用 `--tt-base-viewport-height`/`--doc-height` 计算高度;底部通过 `--tt-safe-area-bottom` 与 `--tt-ime-bottom` 合成有效 bottom inset。
|
||||
- 顶部容器(如 `#top-settings-holder/#top-bar`)使用 `top: max(var(--tt-inset-top), 0px)` 并加入左右 padding。
|
||||
- 主容器 `#sheld` 以 `inset-top + topBarBlockSize` 定位,并用 `--tt-base-viewport-height`/`--doc-height` 计算高度;底部通过 `#form_sheld { padding-bottom: var(--tt-inset-bottom) }` 消化 safe-area,同时用 `--tt-ime-bottom` 推导键盘偏移。
|
||||
|
||||
这些规则的目标是:在非沉浸模式下避开顶部/底部安全区与键盘,在沉浸模式下保持 full-bleed。
|
||||
|
||||
|
|
@ -91,11 +93,11 @@ Android 说明:
|
|||
|
||||
- **Admission**:仅观察 `document.body` 的直系子节点新增/移除(`subtree: false`)。
|
||||
- **判定**:对 `position: fixed` 且计算后的 `top` 贴近 0(阈值范围内)的元素进行处理。
|
||||
- **补丁**:对命中元素设置 `top: max(var(--tt-safe-area-top), <原top>) !important`。
|
||||
- **补丁**:对命中元素设置 `top: max(var(--tt-inset-top), <原top>) !important`。
|
||||
- **排除**:明确跳过 `body/#sheld/#chat` 等核心容器(避免影响主界面)。
|
||||
- **Revalidate**:监听 `html.style` 变化(native 注入会触发)+ `visualViewport`/`resize`/`orientationchange` 以重新校验 active set。
|
||||
|
||||
该控制器的边界是:只对“第三方顶层浮层贴顶”做最小修正,不承担全局样式重写职责;在沉浸模式下由于 `--tt-safe-area-top = 0`,该补丁会自然退化为不额外避让顶部安全区。
|
||||
该控制器的边界是:只对“第三方顶层浮层贴顶”做最小修正,不承担全局样式重写职责;在沉浸模式下由于 `--tt-inset-top = 0`,该补丁会自然退化为不额外避让顶部安全区。
|
||||
|
||||
### 3.4 旧 WebView JS 能力补齐(移动端)
|
||||
|
||||
|
|
@ -133,10 +135,10 @@ native 侧实现:`src-tauri/gen/android/app/src/main/java/com/tauritavern/clie
|
|||
|
||||
已支持:
|
||||
|
||||
- Android edge‑to‑edge + safe‑area 变量注入(包含 IME)。
|
||||
- Android 沉浸模式下以 full-bleed 策略运行,顶部 safe-area 不再额外避让刘海/状态栏。
|
||||
- iOS `viewport-fit=cover` + `env(safe-area-inset-*)` 消费。
|
||||
- 第三方脚本 fixed 浮层的 safe‑area top 元素级修正(移动端)。
|
||||
- Android edge‑to‑edge + inset 契约变量(包含 IME)。
|
||||
- Android 沉浸模式下以 full-bleed 策略运行,顶部 inset 不再额外避让刘海/状态栏。
|
||||
- iOS `viewport-fit=cover` + `env(safe-area-inset-*)` 提供 `--tt-inset-*`。
|
||||
- 第三方脚本 fixed 浮层的 inset top 元素级修正(移动端)。
|
||||
- 聊天导航类场景不再自动聚焦 `#send_textarea`,移动端键盘只在真正进入输入/编辑意图时弹出。
|
||||
|
||||
明确不支持 / 不承诺:
|
||||
|
|
@ -155,7 +157,7 @@ native 侧实现:`src-tauri/gen/android/app/src/main/java/com/tauritavern/clie
|
|||
|
||||
快速调试点:
|
||||
|
||||
- `getComputedStyle(document.documentElement).getPropertyValue('--tt-safe-area-top')`
|
||||
- `getComputedStyle(document.documentElement).getPropertyValue('--tt-inset-top')`
|
||||
- 沉浸模式期望接近 `0px`
|
||||
- 非沉浸模式期望反映当前顶部 safe area
|
||||
- `window.__TAURITAVERN_MOBILE_OVERLAY_COMPAT__` 是否已安装
|
||||
|
|
|
|||
|
|
@ -232,9 +232,9 @@ src/
|
|||
- 触发条件:仅处理第三方浮层节点(`position: fixed` 且顶边贴近 0)。
|
||||
- 处理策略:
|
||||
- 观察 `document.body` 直接子节点新增/移除;
|
||||
- 对命中元素设置 `top: max(var(--tt-safe-area-top), <原top>) !important`;
|
||||
- 对命中元素设置 `top: max(var(--tt-inset-top), <原top>) !important`;
|
||||
- 明确排除 `body/#sheld/#chat` 等应用核心容器,避免影响主界面布局。
|
||||
- Android 变量语义:`--tt-safe-area-top` 表示当前布局应避开的有效 inset;非沉浸模式下反映顶部 safe area,沉浸模式下回落为 `0`,因此 overlay patch 不再额外避开顶部状态栏/刘海区域。
|
||||
- Android 变量语义:`--tt-inset-top` 表示当前布局应避开的有效 inset;非沉浸模式下反映顶部 safe area,沉浸模式下回落为 `0`,因此 overlay patch 不再额外避开顶部状态栏/刘海区域。
|
||||
|
||||
该策略用于修复 JS-Slash-Runner 等脚本在运行时注入固定定位弹窗样式时,关闭按钮落入状态栏导致不可点击的问题。
|
||||
|
||||
|
|
|
|||
|
|
@ -10,9 +10,11 @@
|
|||
4. [前端宿主契约](./FrontendHostContract.md) - Host Kernel 对上游/插件/脚本可观察行为的契约清单(重构必读)
|
||||
5. [后端结构](./BackendStructure.md) - 后端架构和模块说明
|
||||
6. [实施计划](./ImplementationPlan.md) - 项目实施的阶段和里程碑
|
||||
7. [移动端开发说明](./MobileDevelopment.md) - Android/iOS 关键坑位、资源访问与路径解析方案
|
||||
8. [现状说明](./CurrentState/README.md) - 当前实现状态快照与持续开发约束
|
||||
9. [性能重构计划:Prompt History Backfill](./PerformanceRefactoringPlan.md) - Windowed Payload 下补齐生成上下文的方案(方案 C + 页缓存)
|
||||
7. [Android 端开发说明](./AndroidDevelopment.md) - Android WebView/Insets 注入、资源访问与路径解析方案
|
||||
8. [iOS 端开发说明](./iOSDevelopment.md) - WKWebView 行为差异、safe-area/viewport-fit 与底部死区修复
|
||||
9. [现状说明](./CurrentState/README.md) - 当前实现状态快照与持续开发约束
|
||||
10. [性能重构计划:Prompt History Backfill](./PerformanceRefactoringPlan.md) - Windowed Payload 下补齐生成上下文的方案(方案 C + 页缓存)
|
||||
11. [移动端 Insets 解耦重构计划](./MobileInsetsDecouplingPlan.md) - Android 原生注入与 iOS CSS safe-area 解耦,修复底部死区并收敛契约
|
||||
|
||||
## 项目概述
|
||||
|
||||
|
|
|
|||
51
docs/iOSDevelopment.md
Normal file
51
docs/iOSDevelopment.md
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# TauriTavern iOS 端开发说明
|
||||
|
||||
本文档记录当前 iOS 端开发中已经踩过的关键问题、根因分析、已落地方案,以及对应的架构改动。目标是避免重复踩坑,并确保移动端样式契约(`--tt-inset-*`)在 iOS 上可预测、可维护。
|
||||
|
||||
## 1. WKWebView safe-area 自动 inset 导致底部死区
|
||||
|
||||
### 1.1 现象
|
||||
|
||||
- 页面底部出现一块灰色、不可交互的区域。
|
||||
- 前端根节点(如 `#sheld`)看似已撑满 `window.innerHeight`,但依然无法覆盖到屏幕底边。
|
||||
|
||||
### 1.2 关键定位信号
|
||||
|
||||
当出现如下特征时,优先判断为 **iOS native 侧对 WebView 做了 safe-area 自动 inset 调整**,而不是纯前端 CSS 高度问题:
|
||||
|
||||
- `screen.height - window.innerHeight` 显著大于 0(例如 `96px`)
|
||||
- 同时 `env(safe-area-inset-bottom)`(或 `--tt-inset-bottom`)仍为非 0(例如 `34px`)
|
||||
|
||||
这通常意味着:**Web 内容的 viewport 被系统按 safe-area 扣掉了(顶部 + 底部)**,因此 DOM 只能布局在“安全区内的可视内容区域”,无法触达屏幕真实底边。
|
||||
|
||||
### 1.3 根因
|
||||
|
||||
WKWebView 内部是 `UIScrollView` 承载 Web 内容;在默认行为下,iOS 可能对该 scroll view 启用自动的内容 inset 调整(safe-area / scroll indicator insets),导致:
|
||||
|
||||
- Web 内容 viewport 变小(`window.innerHeight` 被扣减)
|
||||
- 产生“看得见但不可交互”的底部空白区域(它不是 DOM 的一部分)
|
||||
|
||||
这会与当前的移动端布局契约冲突:iOS 侧 safe-area 应由前端通过 `env(safe-area-inset-*)` → `--tt-inset-*` 统一消费,而不是由 native 再额外“帮你扣一遍”。
|
||||
|
||||
### 1.4 已落地方案(fail-fast)
|
||||
|
||||
在 iOS 端创建主窗口后,对 WKWebView 的 `scrollView` 做一次性配置:
|
||||
|
||||
- `scrollView.contentInsetAdjustmentBehavior = .never`
|
||||
- 清空 `contentInset` 与 `scrollIndicatorInsets`
|
||||
- 关闭 `automaticallyAdjustsScrollIndicatorInsets`
|
||||
|
||||
该策略的目标是:让 `window.innerHeight` 覆盖到 full-bleed viewport;safe-area 的避让完全交给 CSS contract(`--tt-inset-*`)控制。
|
||||
|
||||
实现位置:
|
||||
|
||||
- iOS 配置入口:`src-tauri/src/infrastructure/ios_webview.rs`
|
||||
- 调用时机:`src-tauri/src/lib.rs`(主窗口 build 后立刻调用)
|
||||
|
||||
### 1.5 验收建议
|
||||
|
||||
修复后应满足:
|
||||
|
||||
- `screen.height - window.innerHeight` 接近 0(允许 1px 内的 rounding)
|
||||
- `--tt-inset-bottom` 仍保持合理的 safe-area 值(如 `34px`),且输入框/按钮不被 home indicator 遮挡
|
||||
|
||||
2
src-tauri/Cargo.lock
generated
2
src-tauri/Cargo.lock
generated
|
|
@ -5026,6 +5026,8 @@ dependencies = [
|
|||
"local-ip-address",
|
||||
"miktik",
|
||||
"mime_guess",
|
||||
"objc2",
|
||||
"objc2-ui-kit",
|
||||
"percent-encoding",
|
||||
"png",
|
||||
"qrcode",
|
||||
|
|
|
|||
|
|
@ -58,6 +58,10 @@ qrcode = { version = "0.14", default-features = false, features = ["svg"] }
|
|||
rustls = { version = "0.23", default-features = false, features = ["std"] }
|
||||
webpki-roots = "1"
|
||||
|
||||
[target.'cfg(target_os = "ios")'.dependencies]
|
||||
objc2 = "0.6.4"
|
||||
objc2-ui-kit = { version = "0.3.2", default-features = false, features = ["std", "objc2-core-foundation", "UIGeometry", "UIScrollView"] }
|
||||
|
||||
[features]
|
||||
default = ["custom-protocol"]
|
||||
custom-protocol = ["tauri/custom-protocol"]
|
||||
|
|
|
|||
|
|
@ -225,7 +225,11 @@ class AndroidInsetsBridge(
|
|||
companion object {
|
||||
private val PAGE_READY_SCRIPT =
|
||||
"""
|
||||
(() => document.readyState !== 'loading' && location.href !== 'about:blank')();
|
||||
(() =>
|
||||
document.readyState !== 'loading' &&
|
||||
location.href !== 'about:blank' &&
|
||||
Boolean(document.getElementById('sheld'))
|
||||
)();
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,17 +24,19 @@ class WebViewInsetsStyleApplier(private val resources: Resources) {
|
|||
}
|
||||
|
||||
private fun buildApplyScript(snapshot: InsetsSnapshot): String {
|
||||
val safeTop = toCssPxNumber(snapshot.systemBars.top)
|
||||
val safeRight = toCssPxNumber(snapshot.systemBars.right)
|
||||
val safeLeft = toCssPxNumber(snapshot.systemBars.left)
|
||||
val safeBottom = toCssPxNumber(snapshot.systemBars.bottom)
|
||||
val insetTop = toCssPxNumber(snapshot.systemBars.top)
|
||||
val insetRight = toCssPxNumber(snapshot.systemBars.right)
|
||||
val insetLeft = toCssPxNumber(snapshot.systemBars.left)
|
||||
val insetBottom = toCssPxNumber(snapshot.systemBars.bottom)
|
||||
val imeBottom = toCssPxNumber(snapshot.imeBottom)
|
||||
|
||||
return """
|
||||
(() => {
|
||||
const bridge = window.__TAURITAVERN_INSETS__;
|
||||
if (!bridge || typeof bridge.apply !== 'function') return;
|
||||
bridge.apply($safeTop, $safeRight, $safeLeft, $safeBottom, $imeBottom);
|
||||
if (!bridge || typeof bridge.apply !== 'function') {
|
||||
throw new Error('[TauriTavern] Android insets bridge unavailable.');
|
||||
}
|
||||
bridge.apply($insetTop, $insetRight, $insetLeft, $insetBottom, $imeBottom);
|
||||
})();
|
||||
""".trimIndent()
|
||||
}
|
||||
|
|
@ -54,29 +56,27 @@ class WebViewInsetsStyleApplier(private val resources: Resources) {
|
|||
const state = {
|
||||
baseViewportHeight: 0,
|
||||
baseViewportHeightCss: '',
|
||||
safeAreaTop: '',
|
||||
safeAreaRight: '',
|
||||
safeAreaLeft: '',
|
||||
safeAreaBottom: '',
|
||||
insetTop: '',
|
||||
insetRight: '',
|
||||
insetLeft: '',
|
||||
insetBottom: '',
|
||||
imeBottom: '',
|
||||
imeTargetId: '',
|
||||
imeTargetRef: null,
|
||||
};
|
||||
|
||||
const setVarIfChanged = (target, cssName, stateKey, nextValue) => {
|
||||
if (!target || state[stateKey] === nextValue) {
|
||||
if (state[stateKey] === nextValue) {
|
||||
return;
|
||||
}
|
||||
state[stateKey] = nextValue;
|
||||
target.style.setProperty(cssName, nextValue);
|
||||
};
|
||||
|
||||
const resolveImeTarget = (root) => document.getElementById('sheld') || root;
|
||||
|
||||
window.__TAURITAVERN_INSETS__ = {
|
||||
apply(safeTop, safeRight, safeLeft, safeBottom, imeBottom) {
|
||||
apply(insetTop, insetRight, insetLeft, insetBottom, imeBottom) {
|
||||
const root = document.documentElement;
|
||||
if (!root) return;
|
||||
if (!root) {
|
||||
throw new Error('[TauriTavern] documentElement unavailable while applying insets.');
|
||||
}
|
||||
|
||||
const viewport = window.visualViewport;
|
||||
const viewportHeight =
|
||||
|
|
@ -104,34 +104,17 @@ class WebViewInsetsStyleApplier(private val resources: Resources) {
|
|||
: 0;
|
||||
const effectiveImeBottom = Math.max(0, imeBottom - viewportShrink);
|
||||
|
||||
setVarIfChanged(root, '--tt-safe-area-top', 'safeAreaTop', safeTop.toFixed(2) + 'px');
|
||||
setVarIfChanged(root, '--tt-safe-area-right', 'safeAreaRight', safeRight.toFixed(2) + 'px');
|
||||
setVarIfChanged(root, '--tt-safe-area-left', 'safeAreaLeft', safeLeft.toFixed(2) + 'px');
|
||||
setVarIfChanged(
|
||||
root,
|
||||
'--tt-safe-area-bottom',
|
||||
'safeAreaBottom',
|
||||
safeBottom.toFixed(2) + 'px',
|
||||
);
|
||||
setVarIfChanged(root, '--tt-inset-top', 'insetTop', insetTop.toFixed(2) + 'px');
|
||||
setVarIfChanged(root, '--tt-inset-right', 'insetRight', insetRight.toFixed(2) + 'px');
|
||||
setVarIfChanged(root, '--tt-inset-left', 'insetLeft', insetLeft.toFixed(2) + 'px');
|
||||
setVarIfChanged(root, '--tt-inset-bottom', 'insetBottom', insetBottom.toFixed(2) + 'px');
|
||||
|
||||
const imeBottomCss = effectiveImeBottom.toFixed(2) + 'px';
|
||||
const imeTarget = resolveImeTarget(root);
|
||||
const imeTargetId = imeTarget === root ? 'root' : 'sheld';
|
||||
|
||||
if (
|
||||
state.imeTargetRef &&
|
||||
state.imeTargetRef !== imeTarget &&
|
||||
state.imeTargetRef.isConnected
|
||||
) {
|
||||
state.imeTargetRef.style.removeProperty('--tt-ime-bottom');
|
||||
}
|
||||
|
||||
if (state.imeBottom !== imeBottomCss || state.imeTargetId !== imeTargetId) {
|
||||
state.imeBottom = imeBottomCss;
|
||||
state.imeTargetId = imeTargetId;
|
||||
state.imeTargetRef = imeTarget;
|
||||
imeTarget.style.setProperty('--tt-ime-bottom', imeBottomCss);
|
||||
const imeTarget = document.getElementById('sheld');
|
||||
if (!imeTarget) {
|
||||
throw new Error('[TauriTavern] #sheld unavailable while applying IME insets.');
|
||||
}
|
||||
setVarIfChanged(imeTarget, '--tt-ime-bottom', 'imeBottom', imeBottomCss);
|
||||
},
|
||||
};
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.0</string>
|
||||
<string>1.5.1</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0.0</string>
|
||||
<string>1.5.1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
|
|
|
|||
27
src-tauri/src/infrastructure/ios_webview.rs
Normal file
27
src-tauri/src/infrastructure/ios_webview.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
use tauri::WebviewWindow;
|
||||
|
||||
pub fn disable_wkwebview_content_inset_adjustment(window: &WebviewWindow) -> tauri::Result<()> {
|
||||
window.with_webview(|webview| unsafe {
|
||||
use objc2::rc::Retained;
|
||||
use objc2::runtime::AnyObject;
|
||||
use objc2_ui_kit::{
|
||||
UIScrollView, UIScrollViewContentInsetAdjustmentBehavior, UIEdgeInsetsZero,
|
||||
};
|
||||
|
||||
let wkwebview_ptr = webview.inner();
|
||||
assert!(
|
||||
!wkwebview_ptr.is_null(),
|
||||
"PlatformWebview.inner() returned a null WKWebView pointer"
|
||||
);
|
||||
|
||||
let wkwebview = &*wkwebview_ptr.cast::<AnyObject>();
|
||||
let scroll_view: Retained<UIScrollView> = objc2::msg_send![wkwebview, scrollView];
|
||||
|
||||
scroll_view.setContentInsetAdjustmentBehavior(
|
||||
UIScrollViewContentInsetAdjustmentBehavior::Never,
|
||||
);
|
||||
scroll_view.setContentInset(UIEdgeInsetsZero);
|
||||
scroll_view.setScrollIndicatorInsets(UIEdgeInsetsZero);
|
||||
scroll_view.setAutomaticallyAdjustsScrollIndicatorInsets(false);
|
||||
})
|
||||
}
|
||||
|
|
@ -2,6 +2,8 @@
|
|||
pub mod apis;
|
||||
pub mod assets;
|
||||
pub mod http_client;
|
||||
#[cfg(target_os = "ios")]
|
||||
pub mod ios_webview;
|
||||
pub mod lan_sync;
|
||||
pub mod logging;
|
||||
pub mod paths;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use std::error::Error;
|
||||
#[cfg(not(target_os = "ios"))]
|
||||
use std::io;
|
||||
#[cfg(target_os = "android")]
|
||||
use std::path::Path;
|
||||
|
|
@ -6,7 +7,9 @@ use std::path::PathBuf;
|
|||
|
||||
use tauri::{AppHandle, Manager};
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
const RUNTIME_MODE_ENV: &str = "TAURITAVERN_RUNTIME_MODE";
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
const PORTABLE_MARKER_FILE: &str = "portable.flag";
|
||||
const DATA_ARCHIVE_ROOT_DIR: &str = ".data-archive";
|
||||
const DATA_ARCHIVE_IMPORTS_DIR: &str = "imports";
|
||||
|
|
@ -15,6 +18,7 @@ const DATA_ARCHIVE_EXPORTS_DIR: &str = "exports";
|
|||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RuntimeMode {
|
||||
Standard,
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
Portable,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ fn create_main_window(
|
|||
let global_extensions_dir = third_party_dirs.global_dir;
|
||||
let user_dirs = user_dirs;
|
||||
|
||||
tauri::webview::WebviewWindowBuilder::from_config(app.handle(), window_config)?
|
||||
let _window = tauri::webview::WebviewWindowBuilder::from_config(app.handle(), window_config)?
|
||||
.on_web_resource_request(move |request, response| {
|
||||
handle_third_party_asset_web_request(
|
||||
&local_extensions_dir,
|
||||
|
|
@ -100,5 +100,8 @@ fn create_main_window(
|
|||
})
|
||||
.build()?;
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
infrastructure::ios_webview::disable_wkwebview_content_inset_adjustment(&_window)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -221,11 +221,11 @@
|
|||
#top-settings-holder,
|
||||
#top-bar {
|
||||
position: fixed;
|
||||
top: max(var(--tt-safe-area-top), 0px);
|
||||
top: max(var(--tt-inset-top), 0px);
|
||||
width: 100vw;
|
||||
width: 100dvw;
|
||||
padding-right: max(var(--tt-safe-area-right), 0px);
|
||||
padding-left: max(var(--tt-safe-area-left), 0px);
|
||||
padding-right: max(var(--tt-inset-right), 0px);
|
||||
padding-left: max(var(--tt-inset-left), 0px);
|
||||
}
|
||||
|
||||
#bg1,
|
||||
|
|
@ -249,18 +249,18 @@
|
|||
max-width: 100dvw;
|
||||
left: 0 !important;
|
||||
resize: none !important;
|
||||
top: calc(var(--topBarBlockSize) + max(var(--tt-safe-area-top), 0px));
|
||||
top: calc(var(--topBarBlockSize) + max(var(--tt-inset-top), 0px));
|
||||
}
|
||||
|
||||
#sheld {
|
||||
--tt-ime-bottom: 0px;
|
||||
--tt-safe-bottom-offset: max(var(--tt-safe-area-bottom), 0px);
|
||||
--tt-viewport-bottom-inset-local: max(var(--tt-safe-bottom-offset), var(--tt-ime-bottom));
|
||||
--tt-keyboard-offset: max(calc(var(--tt-viewport-bottom-inset-local) - var(--tt-safe-bottom-offset)), 0px);
|
||||
height: calc(100vh - var(--topBarBlockSize) - max(var(--tt-safe-area-top), 0px) - var(--tt-safe-bottom-offset));
|
||||
height: calc(var(--tt-base-viewport-height, var(--doc-height)) - var(--topBarBlockSize) - max(var(--tt-safe-area-top), 0px) - var(--tt-safe-bottom-offset));
|
||||
max-height: calc(100vh - var(--topBarBlockSize) - max(var(--tt-safe-area-top), 0px) - var(--tt-safe-bottom-offset));
|
||||
max-height: calc(var(--tt-base-viewport-height, var(--doc-height)) - var(--topBarBlockSize) - max(var(--tt-safe-area-top), 0px) - var(--tt-safe-bottom-offset));
|
||||
--tt-bottom-inset: max(var(--tt-inset-bottom), 0px);
|
||||
--tt-viewport-bottom-inset-local: max(var(--tt-bottom-inset), var(--tt-ime-bottom));
|
||||
--tt-keyboard-offset: max(calc(var(--tt-viewport-bottom-inset-local) - var(--tt-bottom-inset)), 0px);
|
||||
height: calc(100vh - var(--topBarBlockSize) - max(var(--tt-inset-top), 0px));
|
||||
height: calc(var(--tt-base-viewport-height, var(--doc-height)) - var(--topBarBlockSize) - max(var(--tt-inset-top), 0px));
|
||||
max-height: calc(100vh - var(--topBarBlockSize) - max(var(--tt-inset-top), 0px));
|
||||
max-height: calc(var(--tt-base-viewport-height, var(--doc-height)) - var(--topBarBlockSize) - max(var(--tt-inset-top), 0px));
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
|
|
@ -300,8 +300,9 @@
|
|||
z-index: 31;
|
||||
transform: translate3d(0, calc(-1 * var(--tt-keyboard-offset)), 0);
|
||||
will-change: transform;
|
||||
padding-right: max(var(--tt-safe-area-right), 0px);
|
||||
padding-left: max(var(--tt-safe-area-left), 0px);
|
||||
padding-right: max(var(--tt-inset-right), 0px);
|
||||
padding-left: max(var(--tt-inset-left), 0px);
|
||||
padding-bottom: var(--tt-bottom-inset);
|
||||
}
|
||||
|
||||
#chat {
|
||||
|
|
@ -321,24 +322,25 @@
|
|||
/* Enforce mobile safe-area + IME layout even when themes override core containers. */
|
||||
body #top-settings-holder,
|
||||
body #top-bar {
|
||||
top: max(var(--tt-safe-area-top), var(--top-distance, 0px)) !important;
|
||||
padding-right: max(var(--tt-safe-area-right), 0px) !important;
|
||||
padding-left: max(var(--tt-safe-area-left), 0px) !important;
|
||||
top: max(var(--tt-inset-top), var(--top-distance, 0px)) !important;
|
||||
padding-right: max(var(--tt-inset-right), 0px) !important;
|
||||
padding-left: max(var(--tt-inset-left), 0px) !important;
|
||||
}
|
||||
|
||||
body #sheld {
|
||||
top: calc(var(--topBarBlockSize) + max(var(--tt-safe-area-top), 0px)) !important;
|
||||
height: calc(100vh - var(--topBarBlockSize) - max(var(--tt-safe-area-top), 0px) - var(--tt-safe-bottom-offset)) !important;
|
||||
height: calc(var(--tt-base-viewport-height, var(--doc-height)) - var(--topBarBlockSize) - max(var(--tt-safe-area-top), 0px) - var(--tt-safe-bottom-offset)) !important;
|
||||
max-height: calc(100vh - var(--topBarBlockSize) - max(var(--tt-safe-area-top), 0px) - var(--tt-safe-bottom-offset)) !important;
|
||||
max-height: calc(var(--tt-base-viewport-height, var(--doc-height)) - var(--topBarBlockSize) - max(var(--tt-safe-area-top), 0px) - var(--tt-safe-bottom-offset)) !important;
|
||||
top: calc(var(--topBarBlockSize) + max(var(--tt-inset-top), 0px)) !important;
|
||||
height: calc(100vh - var(--topBarBlockSize) - max(var(--tt-inset-top), 0px)) !important;
|
||||
height: calc(var(--tt-base-viewport-height, var(--doc-height)) - var(--topBarBlockSize) - max(var(--tt-inset-top), 0px)) !important;
|
||||
max-height: calc(100vh - var(--topBarBlockSize) - max(var(--tt-inset-top), 0px)) !important;
|
||||
max-height: calc(var(--tt-base-viewport-height, var(--doc-height)) - var(--topBarBlockSize) - max(var(--tt-inset-top), 0px)) !important;
|
||||
}
|
||||
|
||||
body #form_sheld {
|
||||
position: relative !important;
|
||||
transform: translate3d(0, calc(-1 * var(--tt-keyboard-offset)), 0) !important;
|
||||
padding-right: max(var(--tt-safe-area-right), 0px) !important;
|
||||
padding-left: max(var(--tt-safe-area-left), 0px) !important;
|
||||
padding-right: max(var(--tt-inset-right), 0px) !important;
|
||||
padding-left: max(var(--tt-inset-left), 0px) !important;
|
||||
padding-bottom: var(--tt-bottom-inset) !important;
|
||||
}
|
||||
|
||||
body #chat {
|
||||
|
|
@ -382,7 +384,7 @@
|
|||
border-right: 1px solid var(--SmartThemeBorderColor);
|
||||
border-bottom: 1px solid var(--SmartThemeBorderColor);
|
||||
border-radius: 0 0 20px 20px;
|
||||
top: calc(var(--topBarBlockSize) + max(var(--tt-safe-area-top), 0px)) !important;
|
||||
top: calc(var(--topBarBlockSize) + max(var(--tt-inset-top), 0px)) !important;
|
||||
left: 0 !important;
|
||||
backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2));
|
||||
|
||||
|
|
@ -425,7 +427,7 @@
|
|||
width: 100%;
|
||||
border-radius: 0 0 20px 20px;
|
||||
margin-top: 0px;
|
||||
height: calc(100% - var(--topBarBlockSize) - max(var(--tt-safe-area-top), 0px));
|
||||
height: calc(100% - var(--topBarBlockSize) - max(var(--tt-inset-top), 0px));
|
||||
}
|
||||
|
||||
.drawer25pWidth {
|
||||
|
|
@ -569,79 +571,3 @@
|
|||
height: unset;
|
||||
}
|
||||
}
|
||||
|
||||
/*iOS specific*/
|
||||
@supports (-webkit-touch-callout: none) {
|
||||
body {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
#top-bar {
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
#sheld {
|
||||
margin: unset;
|
||||
padding: unset;
|
||||
width: unset;
|
||||
height: unset;
|
||||
min-width: unset;
|
||||
max-width: unset;
|
||||
min-height: unset;
|
||||
max-height: unset;
|
||||
width: 100vw;
|
||||
width: 100dvw;
|
||||
height: calc(100vh - 36px);
|
||||
height: calc(100dvh - 36px);
|
||||
padding-right: max(env(safe-area-inset-right), 0px);
|
||||
padding-left: max(env(safe-area-inset-left), 0px);
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
body.PWA #sheld {
|
||||
padding-right: max(env(safe-area-inset-right), 2px);
|
||||
padding-left: max(env(safe-area-inset-left), 2px);
|
||||
padding-bottom: max(env(safe-area-inset-bottom), 15px);
|
||||
|
||||
}
|
||||
|
||||
#character_popup,
|
||||
#world_popup,
|
||||
#left-nav-panel,
|
||||
#right-nav-panel,
|
||||
.drawer-content {
|
||||
width: unset;
|
||||
height: unset;
|
||||
min-width: unset;
|
||||
max-width: unset;
|
||||
min-height: unset;
|
||||
max-height: unset;
|
||||
backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2));
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
margin: 0 auto;
|
||||
height: calc(100vh - 70px);
|
||||
height: calc(100dvh - 70px);
|
||||
width: calc(100dvw - 5px);
|
||||
max-height: calc(100vh - 70px);
|
||||
max-height: calc(100dvh - 70px);
|
||||
max-width: calc(100dvw - 5px);
|
||||
|
||||
}
|
||||
|
||||
#character_popup,
|
||||
#world_popup,
|
||||
.drawer-content {
|
||||
margin-top: 36px;
|
||||
}
|
||||
|
||||
.scrollableInner {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
#horde_model {
|
||||
height: unset;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,9 +88,9 @@ const defaultExampleSeparator = '***';
|
|||
const defaultChatStart = '***';
|
||||
const defaultToastPosition = 'toast-top-center';
|
||||
const MOBILE_THEME_CHAT_WIDTH_VW = 100;
|
||||
const MOBILE_SAFE_AREA_TOP = 'max(var(--tt-safe-area-top), 0px)';
|
||||
const MOBILE_SAFE_AREA_RIGHT = 'max(var(--tt-safe-area-right), 0px)';
|
||||
const MOBILE_SAFE_AREA_LEFT = 'max(var(--tt-safe-area-left), 0px)';
|
||||
const MOBILE_INSET_TOP = 'max(var(--tt-inset-top), 0px)';
|
||||
const MOBILE_INSET_RIGHT = 'max(var(--tt-inset-right), 0px)';
|
||||
const MOBILE_INSET_LEFT = 'max(var(--tt-inset-left), 0px)';
|
||||
|
||||
const avatar_styles = {
|
||||
ROUND: 0,
|
||||
|
|
@ -1181,9 +1181,9 @@ function enforceMobileTopBarSafeArea() {
|
|||
continue;
|
||||
}
|
||||
|
||||
element.style.setProperty('top', MOBILE_SAFE_AREA_TOP, 'important');
|
||||
element.style.setProperty('padding-right', MOBILE_SAFE_AREA_RIGHT, 'important');
|
||||
element.style.setProperty('padding-left', MOBILE_SAFE_AREA_LEFT, 'important');
|
||||
element.style.setProperty('top', MOBILE_INSET_TOP, 'important');
|
||||
element.style.setProperty('padding-right', MOBILE_INSET_RIGHT, 'important');
|
||||
element.style.setProperty('padding-left', MOBILE_INSET_LEFT, 'important');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -113,13 +113,13 @@
|
|||
--topBarBlockSize: calc(var(--topBarIconSize) + var(--topBarBlockPadding));
|
||||
--topBarBlockPadding: calc(var(--mainFontSize) / 3);
|
||||
|
||||
/* Android WebView can report zero env(safe-area-*) values. Native layer overrides these on Android. */
|
||||
--tt-safe-area-top: env(safe-area-inset-top, 0px);
|
||||
--tt-safe-area-right: env(safe-area-inset-right, 0px);
|
||||
--tt-safe-area-left: env(safe-area-inset-left, 0px);
|
||||
--tt-safe-area-bottom: env(safe-area-inset-bottom, 0px);
|
||||
/* Layout contract: Android is native-injected, iOS uses env(safe-area-inset-*). */
|
||||
--tt-inset-top: env(safe-area-inset-top, 0px);
|
||||
--tt-inset-right: env(safe-area-inset-right, 0px);
|
||||
--tt-inset-left: env(safe-area-inset-left, 0px);
|
||||
--tt-inset-bottom: env(safe-area-inset-bottom, 0px);
|
||||
--tt-ime-bottom: 0px;
|
||||
--tt-viewport-bottom-inset: max(var(--tt-safe-area-bottom), var(--tt-ime-bottom));
|
||||
--tt-viewport-bottom-inset: max(var(--tt-inset-bottom), var(--tt-ime-bottom));
|
||||
|
||||
/*styles for the color picker*/
|
||||
--tool-cool-color-picker-btn-bg: transparent;
|
||||
|
|
|
|||
2
src/tauri/main/bootstrap.js
vendored
2
src/tauri/main/bootstrap.js
vendored
|
|
@ -351,7 +351,6 @@ export function bootstrapTauriMain() {
|
|||
.catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
async function initializeTauriIntegration(context, interceptors, downloadBridge, perfEnabled, perfReadyPromise) {
|
||||
if (perfEnabled && perfReadyPromise) {
|
||||
try {
|
||||
|
|
@ -360,7 +359,6 @@ async function initializeTauriIntegration(context, interceptors, downloadBridge,
|
|||
// Ignore perf HUD load failures.
|
||||
}
|
||||
}
|
||||
|
||||
if (perfEnabled) {
|
||||
safePerfMark('tt:tauri:init:start');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
const CONTROLLER_KEY = '__TAURITAVERN_MOBILE_OVERLAY_COMPAT__';
|
||||
|
||||
const SAFE_AREA_TOP_VAR = '--tt-safe-area-top';
|
||||
const SAFE_AREA_TOP_REFERENCE_PATTERN = /(?:--tt-safe-area-top|safe-area-inset-top)/i;
|
||||
const INSET_TOP_VAR = '--tt-inset-top';
|
||||
const INSET_TOP_REFERENCE_PATTERN = /(?:--tt-inset-top|safe-area-inset-top)/i;
|
||||
const NON_NUMERIC_TOP_VALUE_PATTERN = /^(auto|inherit|initial|unset|revert|revert-layer)$/i;
|
||||
const ZERO_TOP_VALUE_PATTERN = /^0(?:\.0+)?(?:[a-z%]+)?$/i;
|
||||
|
||||
|
|
@ -228,9 +228,9 @@ function parsePixelValue(rawValue) {
|
|||
return Number.isFinite(parsed) ? parsed : null;
|
||||
}
|
||||
|
||||
function normalizeSafeAreaTopValue(rawValue) {
|
||||
function normalizeInsetTopValue(rawValue) {
|
||||
const value = String(rawValue || '').trim();
|
||||
if (!value || SAFE_AREA_TOP_REFERENCE_PATTERN.test(value)) {
|
||||
if (!value || INSET_TOP_REFERENCE_PATTERN.test(value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -240,7 +240,7 @@ function normalizeSafeAreaTopValue(rawValue) {
|
|||
}
|
||||
|
||||
const normalizedBaseValue = ZERO_TOP_VALUE_PATTERN.test(baseValue) ? '0px' : baseValue;
|
||||
return `max(var(${SAFE_AREA_TOP_VAR}), ${normalizedBaseValue})`;
|
||||
return `max(var(${INSET_TOP_VAR}), ${normalizedBaseValue})`;
|
||||
}
|
||||
|
||||
function patchOverlayTop(element) {
|
||||
|
|
@ -260,16 +260,16 @@ function patchOverlayTop(element) {
|
|||
}
|
||||
|
||||
const inlineTop = String(element.style.getPropertyValue('top') || '').trim();
|
||||
const safeAreaTopValue = normalizeSafeAreaTopValue(inlineTop || computedTop);
|
||||
if (!safeAreaTopValue) {
|
||||
const insetTopValue = normalizeInsetTopValue(inlineTop || computedTop);
|
||||
if (!insetTopValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentTop = String(element.style.getPropertyValue('top') || '').trim();
|
||||
const currentPriority = element.style.getPropertyPriority('top') || '';
|
||||
if (currentTop === safeAreaTopValue && currentPriority === 'important') {
|
||||
if (currentTop === insetTopValue && currentPriority === 'important') {
|
||||
return;
|
||||
}
|
||||
|
||||
element.style.setProperty('top', safeAreaTopValue, 'important');
|
||||
element.style.setProperty('top', insetTopValue, 'important');
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue