# 样式隔离与主题定制 (/zh/guide/style-isolation)



这套方案面向宿主 CSS 不可控的企业后台、OA、低代码平台、微前端、门户页面和第三方系统嵌入场景。目标是两件事同时成立：宿主页面的强侵入样式不影响预览器，业务又能稳定地改主题、工具栏和关键区域样式。

## 推荐结论 [#推荐结论]

| 场景                                        | 推荐方案                                                     |
| ----------------------------------------- | -------------------------------------------------------- |
| 新项目、传统后台、script 标签、低代码、微前端                | 使用当前技术栈对应的标准组件，默认 `styleIsolation:'auto'` 即会走 Shadow DOM |
| Vue / React / Svelte / jQuery 项目需要抗宿主全局样式 | 无需额外配置，标准框架组件使用同一套 Shadow DOM 边界                         |
| 需要继承外层字体、主题变量，但仍希望降低样式污染                  | 使用 `styleIsolation:'scoped'`                             |
| 旧项目依赖内部 class 深度覆盖                        | 暂时使用 `styleIsolation:'none'`，再逐步迁移到 tokens + parts       |

iframe 不是核心推荐路径。它能获得更极端的隔离，但会放大文件传输、打印、搜索、尺寸同步、事件桥接和离线资产部署复杂度。File Viewer 的默认隔离策略优先使用平台原生 Shadow DOM。

## Public API [#public-api]

`FileViewerOptions` 新增统一字段：

```ts
type FileViewerStyleIsolation = 'auto' | 'shadow' | 'scoped' | 'none'

const options = {
  styleIsolation: 'shadow',
  theme: 'light',
  toolbar: { position: 'bottom-right' }
}
```

| 值        | 行为                                                                    |
| -------- | --------------------------------------------------------------------- |
| `auto`   | 默认值。Web Component、IIFE、Vue、React、Svelte、jQuery 和 full 包均使用 Shadow DOM |
| `shadow` | 创建 ShadowRoot 作为渲染面，样式优先注入隔离根，overlay 也优先挂在隔离根内                       |
| `scoped` | 不创建 ShadowRoot，使用稳定根选择器和局部 reset 约束影响范围                               |
| `none`   | 保持历史 light DOM 行为，适合旧主题或深度 CSS 覆盖迁移期                                  |

Web Component 也支持 attribute：

```html
<flyfish-file-viewer
  src="/files/report.pdf"
  filename="report.pdf"
  style-isolation="shadow"
  theme="light"
  style="display:block;height:720px"
></flyfish-file-viewer>
```

需要回到旧行为：

```html
<flyfish-file-viewer style-isolation="none"></flyfish-file-viewer>
```

## CSS Tokens [#css-tokens]

优先通过 `--file-viewer-*` CSS 变量定制主题。这些变量可以写在 custom element、业务容器、`:root` 或主题 class 上；Shadow DOM 会继承 host 上的 CSS custom properties。

```css
flyfish-file-viewer {
  --file-viewer-bg: #f7f9fc;
  --file-viewer-text: #172033;
  --file-viewer-font: Inter, "Segoe UI", sans-serif;
  --file-viewer-toolbar-bg: rgba(255, 255, 255, 0.96);
  --file-viewer-toolbar-border: rgba(21, 75, 131, 0.16);
  --file-viewer-button-color: #154b83;
  --file-viewer-button-hover-bg: rgba(21, 75, 131, 0.08);
  --file-viewer-button-radius: 6px;
}
```

| Token                                                                                                     | 用途             |
| --------------------------------------------------------------------------------------------------------- | -------------- |
| `--file-viewer-bg` / `--file-viewer-text` / `--file-viewer-muted`                                         | 壳层背景、主文本和弱文本颜色 |
| `--file-viewer-font`                                                                                      | 组件壳、工具栏、状态面板字体 |
| `--file-viewer-border` / `--file-viewer-focus-ring`                                                       | 边框和键盘焦点样式      |
| `--file-viewer-toolbar-bg` / `--file-viewer-toolbar-border` / `--file-viewer-toolbar-shadow`              | 工具栏背景、描边和阴影    |
| `--file-viewer-toolbar-padding` / `--file-viewer-toolbar-gap` / `--file-viewer-toolbar-radius`            | 工具栏间距和圆角       |
| `--file-viewer-toolbar-floating-offset` / `--file-viewer-toolbar-floating-padding`                        | 浮动工具栏偏移和安全边距   |
| `--file-viewer-group-bg` / `--file-viewer-group-border`                                                   | 工具栏分组背景和边框     |
| `--file-viewer-button-color` / `--file-viewer-button-hover-color` / `--file-viewer-button-disabled-color` | 按钮文本色          |
| `--file-viewer-button-hover-bg` / `--file-viewer-button-radius`                                           | 按钮 hover 背景和圆角 |
| `--file-viewer-input-bg` / `--file-viewer-input-color`                                                    | 搜索输入等内置输入控件    |
| `--file-viewer-z-toolbar` / `--file-viewer-z-floating-toolbar`                                            | 工具栏层级          |

## Shadow Parts [#shadow-parts]

当变量不足以表达定制需求时，用 `::part()` 命中稳定外观面：

```css
flyfish-file-viewer::part(toolbar) {
  border-radius: 999px;
}

flyfish-file-viewer::part(button) {
  min-width: 32px;
  font-weight: 600;
}

flyfish-file-viewer::part(content) {
  background: #eef2f7;
}
```

| Part             | 当前用途                      |
| ---------------- | ------------------------- |
| `host`           | Web Component 内部 mount 节点 |
| `shell`          | 预览器壳层                     |
| `toolbar`        | 内置工具栏容器                   |
| `toolbar-group`  | 工具栏按钮分组                   |
| `toolbar-status` | 工具栏状态文本                   |
| `button`         | 内置按钮                      |
| `input`          | 搜索输入等内置输入控件               |
| `content`        | renderer 内容挂载区            |

推荐 renderer 和后续扩展继续使用 `state-panel`、`watermark` 这类稳定 part 名称暴露状态面板和水印层。业务侧不要依赖 `.file-viewer-*` 内部 class，它们可能随实现调整。

## 框架组件写法 [#框架组件写法]

Vue / React / Svelte / jQuery 默认使用同一套 Shadow DOM 边界。下面的显式配置与 `auto` 等效，适合希望固定隔离策略的项目：

```ts
const viewerOptions = {
  styleIsolation: 'shadow',
  preset: officePreset,
  rendererMode: 'replace',
  theme: 'light'
}
```

如果业务组件需要让外层 CSS 轻度影响预览器，但不希望全局 reset 污染文档内容，可以选择：

```ts
const viewerOptions = {
  styleIsolation: 'scoped'
}
```

## Hostile CSS 验证 [#hostile-css-验证]

上线前建议在宿主页加入一次强侵入 CSS harness，确认工具栏、按钮、正文、图片、canvas、表格和 SVG 都不被污染：

```css
* {
  box-sizing: content-box !important;
  font-size: 26px !important;
  line-height: 3 !important;
}

button,
input,
table,
img,
svg,
canvas,
a,
div {
  all: unset !important;
  display: block !important;
  border: 8px solid red !important;
}
```

期望结果：

* `styleIsolation:'auto'` 或 `'shadow'` 下，标准组件的工具栏和内容仍保持可用。
* 业务通过 `--file-viewer-*` 设置的主题变量仍能生效。
* 业务通过 `flyfish-file-viewer::part(toolbar)` 等选择器能精准定制。
* Web Component 的 DOM 事件仍然保持 `bubbles:true`、`composed:true`；框架组件继续通过自身事件系统跨越 Shadow 边界转发事件。

## 打印和导出 [#打印和导出]

Shadow DOM 下打印和 HTML 导出会从当前渲染面收集内容和必要样式。格式专属适配器仍优先使用自身的完整页输出，例如 PDF 和 Word；不可靠的虚拟表格、媒体、3D 等链路会继续动态隐藏打印按钮。

如果你维护自定义 renderer：

* 不要把预览样式写入 `document.head` 或 `document.body`。
* 优先使用 core 提供的渲染面和样式注入 helper。
* lightbox、菜单、弹层等 overlay 优先挂在 `context.surface.shadowRoot` 或当前 renderer 容器内。
* 需要对外定制的内部节点使用稳定 `part` 名称，不暴露临时 class 作为公共 API。

## 迁移建议 [#迁移建议]

1. 所有标准组件的新接入都保持默认 `auto`，不要主动关闭隔离。
2. 旧项目如果依赖深度 class 覆盖，先用 `styleIsolation:'none'` 保持行为。
3. 把颜色、字体、间距、圆角迁移到 `--file-viewer-*` tokens。
4. 把少量结构定制迁移到 `::part()`。
5. 最后切回 `auto` 或显式 `shadow`，并跑 hostile CSS harness 与常用格式 smoke。
