@nsnanocat/preference-panes 1.1.3 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -29,7 +29,7 @@ preferences.destroy();
29
29
 
30
30
  前端支持 BoxJS 字段数组、单个 app 和 apps 订阅,并要求输入恰好包含一个模块。模块名、存储根和 Settings 路径都从 `@Root.Module.Settings.key` 字段 ID 推导,不从 app 名称或调用参数补充。
31
31
 
32
- 默认样式随包内置。嵌入页面只跟随宿主根元素的 `data-theme` `--pp-keyboard-height`,不识别客户端 User-Agent,也不加载项目 CSS 或客户端 SDK。
32
+ 默认样式随包内置。动态模块 HTML 可通过 `css` 查询参数或优先级更高的 `X-PreferencePanes-CSS` Header 直接加载一个项目 stylesheet;未提供时只使用默认样式。项目 stylesheet 位于默认样式之后,可覆盖 `pp-*` 变量和组件规则。页面脚本不二次下载 CSS,也不识别客户端 User-Agent 或加载客户端 SDK。
33
33
 
34
34
  ## 页面与 API
35
35
 
@@ -39,7 +39,7 @@ preferences.destroy();
39
39
  - `GET /settings/assets/index.mjs`
40
40
  - `GET /settings/assets/navigation.mjs`
41
41
 
42
- 模块页面从 URL 或 `ModuleFrame` 的模块标记取得模块名,通过同源 `/api/{module}` 读取原始 BoxJS,然后调用 `mount(boxjs)`。页面不接受 JSON/CSS 查询参数、私有请求头或兼容资源别名,也不直接访问 `/configs/**`。
42
+ 模块页面从 URL 或 `ModuleFrame` 的模块标记取得模块名,通过同源 `/api/{module}` 读取原始 BoxJS,然后调用 `mount(boxjs)`。`web.js` 只在生成 HTML 时解析可选 CSS 地址并写入 `<link data-preference-panes-stylesheet>`;CSS 地址必须解析为 HTTP(S)。页面不接受 JSON 输入或兼容资源别名,也不直接访问 `/configs/**`。
43
43
 
44
44
  业务模块模板直接处理:
45
45
 
@@ -72,12 +72,15 @@ import { ModuleFrame, ModuleStatus } from "@nsnanocat/preference-panes/navigatio
72
72
  const status = new ModuleStatus(statusElement);
73
73
  await status.check("/api/Module");
74
74
 
75
- const frame = new ModuleFrame("/settings/Module", { signal });
75
+ const frame = new ModuleFrame("/settings/Module", {
76
+ signal,
77
+ headers: { "X-PreferencePanes-CSS": "https://example.org/theme.css" },
78
+ });
76
79
  container.append(frame.element);
77
80
  await frame.load();
78
81
  ```
79
82
 
80
- `ModuleFrame` 只携带模块身份和取消信号。宿主通过 `change`、`confirm`、`notice` 事件同步标题、操作菜单、确认框和提示,不读取或改写 iframe 内部 DOM。项目主页、Bilibili JSBridge、原生导航和视觉样式仍由宿主负责。
83
+ `ModuleFrame` 接受 `{ signal?: AbortSignal; headers?: HeadersInit }`,使用这些选项发送一次 GET,并将返回 HTML 原样设置为 `iframe.srcdoc`。iframe dataset 只保存模块身份,不保存 CSS 地址。宿主通过 `change`、`confirm`、`notice` 事件同步标题、操作菜单、确认框和提示,不读取或改写 iframe 内部 DOM。项目主页、Bilibili JSBridge、原生导航和视觉样式仍由宿主负责。
81
84
 
82
85
  ## 构建与验证
83
86
 
@@ -6,9 +6,10 @@
6
6
  <meta name="color-scheme" content="light dark">
7
7
  <title>Module Preferences</title>
8
8
  <style>body { margin: 0; }</style>
9
+
9
10
  </head>
10
11
  <body>
11
12
  <main id="preferences"></main>
12
- <script type="module" src="/settings/assets/index.mjs?v=1.1.3"></script>
13
+ <script type="module" src="/settings/assets/index.mjs?v=1.2.0"></script>
13
14
  </body>
14
15
  </html>
@@ -276,8 +276,8 @@ function requestConfirmation(host, message) {
276
276
  }
277
277
 
278
278
  /**
279
- * 共用三点按钮和底部操作菜单;弹层挂载到文档根部,不受标题栏显示状态影响。
280
- * Shared overflow trigger and bottom action sheet; the layer is mounted at document level and remains independent of header visibility.
279
+ * 独立页面的三点按钮和底部操作菜单;嵌入页面由宿主提供对应界面。
280
+ * Overflow trigger and bottom action sheet for standalone pages; embedded pages use host-provided chrome.
281
281
  */
282
282
  class ActionMenu {
283
283
  #button;
@@ -817,13 +817,9 @@ class PreferencesPanel {
817
817
  const title = definition.metadata?.name ?? definition.module;
818
818
  const document = root.ownerDocument;
819
819
  const window = document.defaultView;
820
+ const frame = window.frameElement?.dataset.preferencePanes ? window.frameElement : undefined;
820
821
  const shell = element("div", "pp-panel");
821
822
  shell.dataset.module = definition.module;
822
- const header = element("header", "pp-header");
823
- const back = element("button", "pp-back", "‹");
824
- back.setAttribute("aria-label", "返回");
825
- back.type = "button";
826
- const heading = element("h1", "pp-title", title);
827
823
  const handlers = new Map();
828
824
  const menuItems = [
829
825
  { id: "viewSettings", label: "查看设置" },
@@ -831,37 +827,51 @@ class PreferencesPanel {
831
827
  { id: "clearCaches", label: "清空缓存", destructive: true },
832
828
  { id: "reset", label: "重置设置", destructive: true },
833
829
  ];
834
- const menu = new ActionMenu(id => runAction(id));
835
- const trailing = element("span", "pp-nav-spacer");
836
- trailing.append(menu.element);
837
830
  const viewport = element("div", "pp-viewport");
831
+ let back, heading, menu;
832
+ if (frame) shell.append(viewport);
833
+ else {
834
+ const header = element("header", "pp-header");
835
+ back = element("button", "pp-back", "‹");
836
+ back.setAttribute("aria-label", "返回");
837
+ back.type = "button";
838
+ heading = element("h1", "pp-title", title);
839
+ menu = new ActionMenu(id => runAction(id));
840
+ const trailing = element("span", "pp-nav-spacer");
841
+ trailing.append(menu.element);
842
+ header.append(back, heading, trailing);
843
+ shell.append(header, viewport);
844
+ }
838
845
  let toast;
839
- header.append(back, heading, trailing);
840
- shell.append(header, viewport);
841
846
  root.append(shell);
842
847
  // 嵌入模式向宿主发布导航状态,宿主不读取或修改模块内部 DOM。
843
848
  // Embedded mode publishes navigation state without host reads or mutations of the module DOM.
844
849
  const publishNavigation = () => {
845
850
  const actions = handlers.size ? menuItems : [];
846
- menu.update(actions, saving);
847
- const frame = window.frameElement;
848
- if (!frame?.dataset.preferencePanes) return;
849
- frame.dispatchEvent(
850
- new frame.ownerDocument.defaultView.CustomEvent("preferencepanes:change", {
851
- detail: { title: heading.textContent, module: definition.module, busy: saving, canGoBack: !back.disabled, actions },
852
- }),
853
- );
851
+ if (frame)
852
+ frame.dispatchEvent(
853
+ new frame.ownerDocument.defaultView.CustomEvent("preferencepanes:change", {
854
+ detail: { title: currentTitle, module: definition.module, busy: saving, canGoBack, actions },
855
+ }),
856
+ );
857
+ else {
858
+ heading.textContent = currentTitle;
859
+ back.disabled = !canGoBack;
860
+ menu.update(actions, saving);
861
+ }
854
862
  };
855
863
  const onAction = event => {
856
864
  if (!saving && handlers.has(event.detail)) runAction(event.detail);
857
865
  };
858
- window.frameElement?.addEventListener("preferencepanes:action", onAction);
866
+ frame?.addEventListener("preferencepanes:action", onAction);
859
867
  let timer,
860
868
  navigation,
861
869
  generation = 0,
862
870
  active = null,
863
871
  saving = false,
864
- destroyed = false;
872
+ destroyed = false,
873
+ currentTitle = title,
874
+ canGoBack = window.history.length > 1;
865
875
  /**
866
876
  * 展示短暂通知,不刷新设置数据。
867
877
  * Display a transient notification without refreshing settings.
@@ -890,7 +900,6 @@ class PreferencesPanel {
890
900
  }
891
901
  // 宿主接管时不创建网页 Toast,也不运行其计时器。
892
902
  // A host-owned notice creates no web Toast and starts no local timer.
893
- const frame = window.frameElement;
894
903
  if (frame && !frame.dispatchEvent(new frame.ownerDocument.defaultView.CustomEvent("preferencepanes:notice", { cancelable: true, detail: { kind: event.kind, message } }))) return;
895
904
  if (!toast) {
896
905
  toast = element("div", "pp-toast");
@@ -928,8 +937,8 @@ class PreferencesPanel {
928
937
  async function open(module) {
929
938
  const version = ++generation;
930
939
  active = module;
931
- back.disabled = window.history.length <= 1;
932
- heading.textContent = module;
940
+ currentTitle = module;
941
+ canGoBack = window.history.length > 1;
933
942
  publishNavigation();
934
943
  viewport.replaceChildren(statusView("读取设置…"));
935
944
  try {
@@ -948,7 +957,7 @@ class PreferencesPanel {
948
957
  */
949
958
  function controls() {
950
959
  const { definition, values } = client.snapshot();
951
- heading.textContent = definition.metadata?.name || active;
960
+ currentTitle = definition.metadata?.name || active;
952
961
  const view = element("section", "pp-fields");
953
962
  /**
954
963
  * 挂载后执行的多行高度更新
@@ -968,8 +977,8 @@ class PreferencesPanel {
968
977
  */
969
978
  const updateNavigation = () => {
970
979
  const editor = editors.get(navigation.current);
971
- heading.textContent = editor?.title ?? definition.metadata?.name ?? active;
972
- back.disabled = saving || !navigation.canGoBack;
980
+ currentTitle = editor?.title ?? definition.metadata?.name ?? active;
981
+ canGoBack = !saving && navigation.canGoBack;
973
982
  publishNavigation();
974
983
  };
975
984
  /**
@@ -983,7 +992,7 @@ class PreferencesPanel {
983
992
  function perform(action, success, failure = () => {}) {
984
993
  pendingWrites++;
985
994
  saving = true;
986
- back.disabled = true;
995
+ canGoBack = false;
987
996
  publishNavigation();
988
997
  queue = queue
989
998
  .then(action)
@@ -999,7 +1008,7 @@ class PreferencesPanel {
999
1008
  pendingWrites--;
1000
1009
  saving = pendingWrites > 0;
1001
1010
  if (destroyed && !saving) client.leave();
1002
- back.disabled = saving || !navigation.canGoBack;
1011
+ canGoBack = !saving && navigation.canGoBack;
1003
1012
  publishNavigation();
1004
1013
  });
1005
1014
  return queue;
@@ -1273,16 +1282,17 @@ class PreferencesPanel {
1273
1282
  * Loaded forms delegate back to navigation; loading views can return to the previous document.
1274
1283
  * @returns {void} 无返回值 / No return value.
1275
1284
  */
1276
- back.onclick = () => {
1277
- if (saving) return;
1278
- if (navigation) navigation.back();
1279
- else window.history.back();
1280
- };
1285
+ if (back)
1286
+ back.onclick = () => {
1287
+ if (saving) return;
1288
+ if (navigation) navigation.back();
1289
+ else window.history.back();
1290
+ };
1281
1291
  open(definition.module);
1282
1292
  return () => {
1283
1293
  destroyed = true;
1284
- menu.destroy();
1285
- window.frameElement?.removeEventListener("preferencepanes:action", onAction);
1294
+ menu?.destroy();
1295
+ frame?.removeEventListener("preferencepanes:action", onAction);
1286
1296
  navigation?.destroy();
1287
1297
  generation++;
1288
1298
  if (active && !saving) client.leave();
@@ -1301,9 +1311,10 @@ class PreferencesPanel {
1301
1311
  }
1302
1312
  }
1303
1313
 
1304
- var defaults = "/* 通用默认样式只使用 pp 命名空间;项目可通过 CSS 输入覆盖变量和组件。\n * Generic defaults use only the pp namespace; projects may override variables and components through CSS input. */\n.pp-panel {\n --pp-text: #18191c;\n --pp-background: #f6f7f8;\n --pp-surface: #fff;\n --pp-field: #f1f2f3;\n --pp-border: #e3e5e7;\n --pp-muted: #797f87;\n --pp-accent: #1677ff;\n font:\n 15px / 1.5 -apple-system,\n BlinkMacSystemFont,\n \"Segoe UI\",\n sans-serif;\n color: var(--pp-text);\n background: var(--pp-background);\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%;\n max-width: 100vw;\n min-width: 0;\n height: 100vh;\n overflow: hidden;\n}\n\n:root[data-theme=\"dark\"] .pp-panel {\n --pp-text: #f1f2f3;\n --pp-background: #0d0e0f;\n --pp-surface: #18191c;\n --pp-field: #2f3238;\n --pp-border: #2f3238;\n --pp-muted: #9499a0;\n}\n.pp-panel * {\n box-sizing: border-box;\n letter-spacing: 0;\n}\n.pp-header {\n flex: none;\n height: calc(52px + env(safe-area-inset-top));\n padding: env(safe-area-inset-top) 12px 0;\n display: flex;\n align-items: center;\n background: var(--pp-surface);\n border-bottom: 1px solid var(--pp-border);\n position: relative;\n z-index: 2;\n}\n.pp-title {\n flex: 1;\n text-align: center;\n font-size: 17px;\n font-weight: 500;\n margin: 0;\n min-width: 0;\n overflow-wrap: anywhere;\n}\n.pp-nav-spacer {\n width: 44px;\n flex: none;\n}\n.pp-panel button {\n font: inherit;\n cursor: pointer;\n border: 0;\n background: none;\n color: inherit;\n}\n.pp-panel .pp-back {\n width: 44px;\n height: 44px;\n flex: none;\n font-size: 34px;\n line-height: 32px;\n padding: 0;\n}\n.pp-panel button:disabled {\n opacity: 0.5;\n cursor: wait;\n}\n.pp-viewport {\n position: relative;\n flex: 1;\n min-width: 0;\n min-height: 0;\n overflow: hidden;\n}\n:root[data-preference-panes-embedded] .pp-header {\n display: none;\n}\n@supports (height: 100dvh) {\n .pp-panel {\n height: 100dvh;\n }\n}\n.pp-fields,\n.pp-choice-page,\n.pp-settings-page,\n.pp-cache-page {\n position: absolute;\n inset: 0;\n min-width: 0;\n overflow-x: hidden;\n overflow-y: auto;\n padding: 12px max(16px, calc((100% - 688px) / 2)) calc(28px + env(safe-area-inset-bottom) + var(--pp-keyboard-height, 0px));\n scroll-padding-bottom: var(--pp-keyboard-height, 0px);\n background: var(--pp-background);\n}\n.pp-choice-link {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 8px;\n max-width: 45%;\n min-width: 44px;\n min-height: 44px;\n padding: 0;\n text-align: right;\n flex: 1;\n}\n.pp-summary {\n color: var(--pp-muted);\n font-size: 13px;\n line-height: 18px;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n overflow-wrap: anywhere;\n}\n.pp-chevron {\n color: var(--pp-muted);\n font-size: 22px;\n flex: none;\n}\n.pp-editor {\n flex: none;\n width: 45%;\n min-width: 0;\n min-height: 36px;\n padding: 8px 10px;\n font: inherit;\n color: var(--pp-text);\n background: var(--pp-field);\n border: 0;\n border-radius: 6px;\n}\n.pp-panel .pp-multiline {\n display: block;\n}\n.pp-multiline .pp-editor {\n width: 100%;\n margin-top: 10px;\n}\n.pp-panel [hidden] {\n display: none !important;\n}\n.pp-label {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n margin-right: 16px;\n}\n.pp-field-name {\n color: var(--pp-text);\n font-size: 15px;\n}\n.pp-field-description {\n margin-top: 2px;\n color: var(--pp-muted);\n font-size: 12px;\n}\n.pp-group {\n margin-top: 16px;\n}\n.pp-group-title {\n margin: 0 0 8px;\n color: var(--pp-muted);\n font-size: 15px;\n font-weight: 400;\n}\n.pp-row {\n min-width: 0;\n min-height: 48px;\n padding: 16px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n background: var(--pp-surface);\n border-bottom: 1px solid var(--pp-border);\n}\n.pp-rows > :last-child {\n border-bottom: 0 !important;\n}\n.pp-switch {\n flex: none;\n accent-color: var(--pp-accent);\n}\n.pp-choice {\n justify-content: space-between;\n cursor: pointer;\n}\n.pp-choice input {\n width: 20px;\n height: 20px;\n flex: none;\n accent-color: var(--pp-accent);\n margin: 0;\n}\n.pp-description {\n font-size: 12px;\n line-height: 1.6;\n color: var(--pp-muted);\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n}\n.pp-module-info {\n display: flex;\n gap: 12px;\n margin: 12px 0;\n}\n.pp-module-details {\n min-width: 0;\n overflow-wrap: anywhere;\n}\n.pp-module-source {\n color: inherit;\n text-decoration: underline;\n}\n.pp-status {\n position: fixed;\n inset: 0;\n display: grid;\n place-content: center;\n justify-items: center;\n gap: 12px;\n min-width: 0;\n min-height: 0;\n margin: 0;\n padding: 24px;\n color: var(--pp-muted, GrayText);\n text-align: center;\n background: var(--pp-background, Canvas);\n}\n.pp-viewport > .pp-status {\n position: absolute;\n}\n.pp-status-spinner {\n box-sizing: border-box;\n width: 28px;\n height: 28px;\n border: 3px solid color-mix(in srgb, currentColor 25%, transparent);\n border-top-color: var(--pp-accent, AccentColor);\n border-radius: 50%;\n animation: pp-status-spin 0.8s linear infinite;\n}\n.pp-status-message {\n max-width: 100%;\n margin: 0;\n overflow-wrap: anywhere;\n}\n.pp-status-action {\n min-width: 96px;\n min-height: 44px;\n padding: 8px 16px;\n border: 0;\n border-radius: 6px;\n color: var(--pp-text, ButtonText);\n font: inherit;\n cursor: pointer;\n background: var(--pp-surface, ButtonFace);\n}\n@keyframes pp-status-spin {\n to {\n transform: rotate(1turn);\n }\n}\n.pp-cache {\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n}\n.pp-toast {\n pointer-events: none;\n position: fixed;\n bottom: calc(30px + env(safe-area-inset-bottom));\n left: 50%;\n transform: translateX(-50%);\n max-width: 90vw;\n padding: 10px 16px;\n border-radius: 8px;\n background: #333e;\n color: white;\n font-size: 13px;\n z-index: 20;\n}\n.pp-toast[data-kind=\"error\"] {\n background: #8d2424;\n}\n.pp-panel :focus-visible {\n outline: 2px solid var(--pp-accent);\n outline-offset: -2px;\n}\n";
1314
+ var defaults = "/* 通用默认样式只使用 pp 命名空间;项目可通过 CSS 输入覆盖变量和组件。\n * Generic defaults use only the pp namespace; projects may override variables and components through CSS input. */\n.pp-panel {\n --pp-text: #18191c;\n --pp-background: #f6f7f8;\n --pp-surface: #fff;\n --pp-field: #f1f2f3;\n --pp-border: #e3e5e7;\n --pp-muted: #797f87;\n --pp-accent: #1677ff;\n font:\n 15px / 1.5 -apple-system,\n BlinkMacSystemFont,\n \"Segoe UI\",\n sans-serif;\n color: var(--pp-text);\n background: var(--pp-background);\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%;\n max-width: 100vw;\n min-width: 0;\n height: 100vh;\n overflow: hidden;\n}\n\n:root[data-theme=\"dark\"] .pp-panel {\n --pp-text: #f1f2f3;\n --pp-background: #0d0e0f;\n --pp-surface: #18191c;\n --pp-field: #2f3238;\n --pp-border: #2f3238;\n --pp-muted: #9499a0;\n}\n.pp-panel * {\n box-sizing: border-box;\n letter-spacing: 0;\n}\n.pp-header {\n flex: none;\n height: calc(52px + env(safe-area-inset-top));\n padding: env(safe-area-inset-top) 12px 0;\n display: flex;\n align-items: center;\n background: var(--pp-surface);\n border-bottom: 1px solid var(--pp-border);\n position: relative;\n z-index: 2;\n}\n.pp-title {\n flex: 1;\n text-align: center;\n font-size: 17px;\n font-weight: 500;\n margin: 0;\n min-width: 0;\n overflow-wrap: anywhere;\n}\n.pp-nav-spacer {\n width: 44px;\n flex: none;\n}\n.pp-panel button {\n font: inherit;\n cursor: pointer;\n border: 0;\n background: none;\n color: inherit;\n}\n.pp-panel .pp-back {\n width: 44px;\n height: 44px;\n flex: none;\n font-size: 34px;\n line-height: 32px;\n padding: 0;\n}\n.pp-panel button:disabled {\n opacity: 0.5;\n cursor: wait;\n}\n.pp-viewport {\n position: relative;\n flex: 1;\n min-width: 0;\n min-height: 0;\n overflow: hidden;\n}\n@supports (height: 100dvh) {\n .pp-panel {\n height: 100dvh;\n }\n}\n.pp-fields,\n.pp-choice-page,\n.pp-settings-page,\n.pp-cache-page {\n position: absolute;\n inset: 0;\n min-width: 0;\n overflow-x: hidden;\n overflow-y: auto;\n padding: 12px max(16px, calc((100% - 688px) / 2)) calc(28px + env(safe-area-inset-bottom) + var(--pp-keyboard-height, 0px));\n scroll-padding-bottom: var(--pp-keyboard-height, 0px);\n background: var(--pp-background);\n}\n.pp-choice-link {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 8px;\n max-width: 45%;\n min-width: 44px;\n min-height: 44px;\n padding: 0;\n text-align: right;\n flex: 1;\n}\n.pp-summary {\n color: var(--pp-muted);\n font-size: 13px;\n line-height: 18px;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n overflow-wrap: anywhere;\n}\n.pp-chevron {\n color: var(--pp-muted);\n font-size: 22px;\n flex: none;\n}\n.pp-editor {\n flex: none;\n width: 45%;\n min-width: 0;\n min-height: 36px;\n padding: 8px 10px;\n font: inherit;\n color: var(--pp-text);\n background: var(--pp-field);\n border: 0;\n border-radius: 6px;\n}\n.pp-panel .pp-multiline {\n display: block;\n}\n.pp-multiline .pp-editor {\n width: 100%;\n margin-top: 10px;\n}\n.pp-panel [hidden] {\n display: none !important;\n}\n.pp-label {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n margin-right: 16px;\n}\n.pp-field-name {\n color: var(--pp-text);\n font-size: 15px;\n}\n.pp-field-description {\n margin-top: 2px;\n color: var(--pp-muted);\n font-size: 12px;\n}\n.pp-group {\n margin-top: 16px;\n}\n.pp-group-title {\n margin: 0 0 8px;\n color: var(--pp-muted);\n font-size: 15px;\n font-weight: 400;\n}\n.pp-row {\n min-width: 0;\n min-height: 48px;\n padding: 16px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n background: var(--pp-surface);\n border-bottom: 1px solid var(--pp-border);\n}\n.pp-rows > :last-child {\n border-bottom: 0 !important;\n}\n.pp-switch {\n flex: none;\n accent-color: var(--pp-accent);\n}\n.pp-choice {\n justify-content: space-between;\n cursor: pointer;\n}\n.pp-choice input {\n width: 20px;\n height: 20px;\n flex: none;\n accent-color: var(--pp-accent);\n margin: 0;\n}\n.pp-description {\n font-size: 12px;\n line-height: 1.6;\n color: var(--pp-muted);\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n}\n.pp-module-info {\n display: flex;\n gap: 12px;\n margin: 12px 0;\n}\n.pp-module-details {\n min-width: 0;\n overflow-wrap: anywhere;\n}\n.pp-module-source {\n color: inherit;\n text-decoration: underline;\n}\n.pp-status {\n position: fixed;\n inset: 0;\n display: grid;\n place-content: center;\n justify-items: center;\n gap: 12px;\n min-width: 0;\n min-height: 0;\n margin: 0;\n padding: 24px;\n color: var(--pp-muted, GrayText);\n text-align: center;\n background: var(--pp-background, Canvas);\n}\n.pp-viewport > .pp-status {\n position: absolute;\n}\n.pp-status-spinner {\n box-sizing: border-box;\n width: 28px;\n height: 28px;\n border: 3px solid color-mix(in srgb, currentColor 25%, transparent);\n border-top-color: var(--pp-accent, AccentColor);\n border-radius: 50%;\n animation: pp-status-spin 0.8s linear infinite;\n}\n.pp-status-message {\n max-width: 100%;\n margin: 0;\n overflow-wrap: anywhere;\n}\n.pp-status-action {\n min-width: 96px;\n min-height: 44px;\n padding: 8px 16px;\n border: 0;\n border-radius: 6px;\n color: var(--pp-text, ButtonText);\n font: inherit;\n cursor: pointer;\n background: var(--pp-surface, ButtonFace);\n}\n@keyframes pp-status-spin {\n to {\n transform: rotate(1turn);\n }\n}\n.pp-cache {\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n}\n.pp-toast {\n pointer-events: none;\n position: fixed;\n bottom: calc(30px + env(safe-area-inset-bottom));\n left: 50%;\n transform: translateX(-50%);\n max-width: 90vw;\n padding: 10px 16px;\n border-radius: 8px;\n background: #333e;\n color: white;\n font-size: 13px;\n z-index: 20;\n}\n.pp-toast[data-kind=\"error\"] {\n background: #8d2424;\n}\n.pp-panel :focus-visible {\n outline: 2px solid var(--pp-accent);\n outline-offset: -2px;\n}\n";
1305
1315
 
1306
1316
  const selector = "style[data-preference-panes-defaults]";
1317
+ const stylesheetSelector = "link[data-preference-panes-stylesheet]";
1307
1318
 
1308
1319
  /**
1309
1320
  * 在文档中安装一次默认样式,并标记当前调用方是否拥有该节点。
@@ -1317,7 +1328,7 @@ function installDefaultStyles(document) {
1317
1328
  const element = document.createElement("style");
1318
1329
  element.dataset.preferencePanesDefaults = "";
1319
1330
  element.textContent = defaults;
1320
- document.head.append(element);
1331
+ document.head.insertBefore(element, document.head.querySelector(stylesheetSelector));
1321
1332
  return { element, owned: true };
1322
1333
  }
1323
1334
 
@@ -1,6 +1,6 @@
1
1
  /**
2
- * 共用三点按钮和底部操作菜单;弹层挂载到文档根部,不受标题栏显示状态影响。
3
- * Shared overflow trigger and bottom action sheet; the layer is mounted at document level and remains independent of header visibility.
2
+ * 独立页面的三点按钮和底部操作菜单;嵌入页面由宿主提供对应界面。
3
+ * Overflow trigger and bottom action sheet for standalone pages; embedded pages use host-provided chrome.
4
4
  */
5
5
  class ActionMenu {
6
6
  #button;
@@ -147,8 +147,8 @@ class ActionMenu {
147
147
  }
148
148
 
149
149
  /**
150
- * 模块文档容器:原始 HTML 不改写,只向 iframe 标记模块身份。
151
- * Module document container: preserve HTML verbatim and mark only the module identity on the iframe.
150
+ * 模块文档容器:发送 GET 请求并将原始 HTML 交给 iframe,只在元素上标记模块身份。
151
+ * Module document container: send a GET request, preserve HTML verbatim and mark only the module identity on the iframe.
152
152
  */
153
153
  class ModuleFrame extends EventTarget {
154
154
  #url;
@@ -173,14 +173,14 @@ class ModuleFrame extends EventTarget {
173
173
  * 建立 iframe;调用方挂载 element 后调用 load。
174
174
  * Create the iframe; callers mount element and then call load.
175
175
  * @param {string | URL} url 模块请求地址 / Module request URL.
176
- * @param {{signal?: AbortSignal}} [options] 外部取消信号 / External cancellation signal.
176
+ * @param {{signal?: AbortSignal, headers?: HeadersInit}} [options] 请求选项 / Request options.
177
177
  */
178
178
  constructor(url, options = {}) {
179
179
  super();
180
180
  this.#url = new URL(url, document.baseURI);
181
181
  const match = /^\/settings\/([a-zA-Z0-9_-]+)\/?$/.exec(this.#url.pathname);
182
182
  if (!match) throw new TypeError("Open a concrete module URL");
183
- this.#options = options;
183
+ this.#options = { signal: options.signal, headers: options.headers };
184
184
  this.element = document.createElement("iframe");
185
185
  this.element.title = `${match[1]} 设置`;
186
186
  this.element.dataset.preferencePanes = "true";
@@ -209,7 +209,7 @@ class ModuleFrame extends EventTarget {
209
209
  if (this.#options.signal?.aborted) this.destroy();
210
210
  const timer = setTimeout(() => this.#controller.abort(), 10000);
211
211
  try {
212
- const response = await fetch(this.#url, { cache: "no-store", credentials: "omit", signal: this.#controller.signal });
212
+ const response = await fetch(this.#url, { method: "GET", cache: "no-store", credentials: "omit", headers: this.#options.headers, signal: this.#controller.signal });
213
213
  if (response.status !== 200) throw new Error(`HTTP ${response.status}`);
214
214
  const html = await response.text();
215
215
  this.#controller.signal.throwIfAborted();
@@ -276,8 +276,8 @@ function requestConfirmation(host, message) {
276
276
  }
277
277
 
278
278
  /**
279
- * 共用三点按钮和底部操作菜单;弹层挂载到文档根部,不受标题栏显示状态影响。
280
- * Shared overflow trigger and bottom action sheet; the layer is mounted at document level and remains independent of header visibility.
279
+ * 独立页面的三点按钮和底部操作菜单;嵌入页面由宿主提供对应界面。
280
+ * Overflow trigger and bottom action sheet for standalone pages; embedded pages use host-provided chrome.
281
281
  */
282
282
  class ActionMenu {
283
283
  #button;
@@ -817,13 +817,9 @@ class PreferencesPanel {
817
817
  const title = definition.metadata?.name ?? definition.module;
818
818
  const document = root.ownerDocument;
819
819
  const window = document.defaultView;
820
+ const frame = window.frameElement?.dataset.preferencePanes ? window.frameElement : undefined;
820
821
  const shell = element("div", "pp-panel");
821
822
  shell.dataset.module = definition.module;
822
- const header = element("header", "pp-header");
823
- const back = element("button", "pp-back", "‹");
824
- back.setAttribute("aria-label", "返回");
825
- back.type = "button";
826
- const heading = element("h1", "pp-title", title);
827
823
  const handlers = new Map();
828
824
  const menuItems = [
829
825
  { id: "viewSettings", label: "查看设置" },
@@ -831,37 +827,51 @@ class PreferencesPanel {
831
827
  { id: "clearCaches", label: "清空缓存", destructive: true },
832
828
  { id: "reset", label: "重置设置", destructive: true },
833
829
  ];
834
- const menu = new ActionMenu(id => runAction(id));
835
- const trailing = element("span", "pp-nav-spacer");
836
- trailing.append(menu.element);
837
830
  const viewport = element("div", "pp-viewport");
831
+ let back, heading, menu;
832
+ if (frame) shell.append(viewport);
833
+ else {
834
+ const header = element("header", "pp-header");
835
+ back = element("button", "pp-back", "‹");
836
+ back.setAttribute("aria-label", "返回");
837
+ back.type = "button";
838
+ heading = element("h1", "pp-title", title);
839
+ menu = new ActionMenu(id => runAction(id));
840
+ const trailing = element("span", "pp-nav-spacer");
841
+ trailing.append(menu.element);
842
+ header.append(back, heading, trailing);
843
+ shell.append(header, viewport);
844
+ }
838
845
  let toast;
839
- header.append(back, heading, trailing);
840
- shell.append(header, viewport);
841
846
  root.append(shell);
842
847
  // 嵌入模式向宿主发布导航状态,宿主不读取或修改模块内部 DOM。
843
848
  // Embedded mode publishes navigation state without host reads or mutations of the module DOM.
844
849
  const publishNavigation = () => {
845
850
  const actions = handlers.size ? menuItems : [];
846
- menu.update(actions, saving);
847
- const frame = window.frameElement;
848
- if (!frame?.dataset.preferencePanes) return;
849
- frame.dispatchEvent(
850
- new frame.ownerDocument.defaultView.CustomEvent("preferencepanes:change", {
851
- detail: { title: heading.textContent, module: definition.module, busy: saving, canGoBack: !back.disabled, actions },
852
- }),
853
- );
851
+ if (frame)
852
+ frame.dispatchEvent(
853
+ new frame.ownerDocument.defaultView.CustomEvent("preferencepanes:change", {
854
+ detail: { title: currentTitle, module: definition.module, busy: saving, canGoBack, actions },
855
+ }),
856
+ );
857
+ else {
858
+ heading.textContent = currentTitle;
859
+ back.disabled = !canGoBack;
860
+ menu.update(actions, saving);
861
+ }
854
862
  };
855
863
  const onAction = event => {
856
864
  if (!saving && handlers.has(event.detail)) runAction(event.detail);
857
865
  };
858
- window.frameElement?.addEventListener("preferencepanes:action", onAction);
866
+ frame?.addEventListener("preferencepanes:action", onAction);
859
867
  let timer,
860
868
  navigation,
861
869
  generation = 0,
862
870
  active = null,
863
871
  saving = false,
864
- destroyed = false;
872
+ destroyed = false,
873
+ currentTitle = title,
874
+ canGoBack = window.history.length > 1;
865
875
  /**
866
876
  * 展示短暂通知,不刷新设置数据。
867
877
  * Display a transient notification without refreshing settings.
@@ -890,7 +900,6 @@ class PreferencesPanel {
890
900
  }
891
901
  // 宿主接管时不创建网页 Toast,也不运行其计时器。
892
902
  // A host-owned notice creates no web Toast and starts no local timer.
893
- const frame = window.frameElement;
894
903
  if (frame && !frame.dispatchEvent(new frame.ownerDocument.defaultView.CustomEvent("preferencepanes:notice", { cancelable: true, detail: { kind: event.kind, message } }))) return;
895
904
  if (!toast) {
896
905
  toast = element("div", "pp-toast");
@@ -928,8 +937,8 @@ class PreferencesPanel {
928
937
  async function open(module) {
929
938
  const version = ++generation;
930
939
  active = module;
931
- back.disabled = window.history.length <= 1;
932
- heading.textContent = module;
940
+ currentTitle = module;
941
+ canGoBack = window.history.length > 1;
933
942
  publishNavigation();
934
943
  viewport.replaceChildren(statusView("读取设置…"));
935
944
  try {
@@ -948,7 +957,7 @@ class PreferencesPanel {
948
957
  */
949
958
  function controls() {
950
959
  const { definition, values } = client.snapshot();
951
- heading.textContent = definition.metadata?.name || active;
960
+ currentTitle = definition.metadata?.name || active;
952
961
  const view = element("section", "pp-fields");
953
962
  /**
954
963
  * 挂载后执行的多行高度更新
@@ -968,8 +977,8 @@ class PreferencesPanel {
968
977
  */
969
978
  const updateNavigation = () => {
970
979
  const editor = editors.get(navigation.current);
971
- heading.textContent = editor?.title ?? definition.metadata?.name ?? active;
972
- back.disabled = saving || !navigation.canGoBack;
980
+ currentTitle = editor?.title ?? definition.metadata?.name ?? active;
981
+ canGoBack = !saving && navigation.canGoBack;
973
982
  publishNavigation();
974
983
  };
975
984
  /**
@@ -983,7 +992,7 @@ class PreferencesPanel {
983
992
  function perform(action, success, failure = () => {}) {
984
993
  pendingWrites++;
985
994
  saving = true;
986
- back.disabled = true;
995
+ canGoBack = false;
987
996
  publishNavigation();
988
997
  queue = queue
989
998
  .then(action)
@@ -999,7 +1008,7 @@ class PreferencesPanel {
999
1008
  pendingWrites--;
1000
1009
  saving = pendingWrites > 0;
1001
1010
  if (destroyed && !saving) client.leave();
1002
- back.disabled = saving || !navigation.canGoBack;
1011
+ canGoBack = !saving && navigation.canGoBack;
1003
1012
  publishNavigation();
1004
1013
  });
1005
1014
  return queue;
@@ -1273,16 +1282,17 @@ class PreferencesPanel {
1273
1282
  * Loaded forms delegate back to navigation; loading views can return to the previous document.
1274
1283
  * @returns {void} 无返回值 / No return value.
1275
1284
  */
1276
- back.onclick = () => {
1277
- if (saving) return;
1278
- if (navigation) navigation.back();
1279
- else window.history.back();
1280
- };
1285
+ if (back)
1286
+ back.onclick = () => {
1287
+ if (saving) return;
1288
+ if (navigation) navigation.back();
1289
+ else window.history.back();
1290
+ };
1281
1291
  open(definition.module);
1282
1292
  return () => {
1283
1293
  destroyed = true;
1284
- menu.destroy();
1285
- window.frameElement?.removeEventListener("preferencepanes:action", onAction);
1294
+ menu?.destroy();
1295
+ frame?.removeEventListener("preferencepanes:action", onAction);
1286
1296
  navigation?.destroy();
1287
1297
  generation++;
1288
1298
  if (active && !saving) client.leave();
@@ -1301,9 +1311,10 @@ class PreferencesPanel {
1301
1311
  }
1302
1312
  }
1303
1313
 
1304
- var defaults = "/* 通用默认样式只使用 pp 命名空间;项目可通过 CSS 输入覆盖变量和组件。\n * Generic defaults use only the pp namespace; projects may override variables and components through CSS input. */\n.pp-panel {\n --pp-text: #18191c;\n --pp-background: #f6f7f8;\n --pp-surface: #fff;\n --pp-field: #f1f2f3;\n --pp-border: #e3e5e7;\n --pp-muted: #797f87;\n --pp-accent: #1677ff;\n font:\n 15px / 1.5 -apple-system,\n BlinkMacSystemFont,\n \"Segoe UI\",\n sans-serif;\n color: var(--pp-text);\n background: var(--pp-background);\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%;\n max-width: 100vw;\n min-width: 0;\n height: 100vh;\n overflow: hidden;\n}\n\n:root[data-theme=\"dark\"] .pp-panel {\n --pp-text: #f1f2f3;\n --pp-background: #0d0e0f;\n --pp-surface: #18191c;\n --pp-field: #2f3238;\n --pp-border: #2f3238;\n --pp-muted: #9499a0;\n}\n.pp-panel * {\n box-sizing: border-box;\n letter-spacing: 0;\n}\n.pp-header {\n flex: none;\n height: calc(52px + env(safe-area-inset-top));\n padding: env(safe-area-inset-top) 12px 0;\n display: flex;\n align-items: center;\n background: var(--pp-surface);\n border-bottom: 1px solid var(--pp-border);\n position: relative;\n z-index: 2;\n}\n.pp-title {\n flex: 1;\n text-align: center;\n font-size: 17px;\n font-weight: 500;\n margin: 0;\n min-width: 0;\n overflow-wrap: anywhere;\n}\n.pp-nav-spacer {\n width: 44px;\n flex: none;\n}\n.pp-panel button {\n font: inherit;\n cursor: pointer;\n border: 0;\n background: none;\n color: inherit;\n}\n.pp-panel .pp-back {\n width: 44px;\n height: 44px;\n flex: none;\n font-size: 34px;\n line-height: 32px;\n padding: 0;\n}\n.pp-panel button:disabled {\n opacity: 0.5;\n cursor: wait;\n}\n.pp-viewport {\n position: relative;\n flex: 1;\n min-width: 0;\n min-height: 0;\n overflow: hidden;\n}\n:root[data-preference-panes-embedded] .pp-header {\n display: none;\n}\n@supports (height: 100dvh) {\n .pp-panel {\n height: 100dvh;\n }\n}\n.pp-fields,\n.pp-choice-page,\n.pp-settings-page,\n.pp-cache-page {\n position: absolute;\n inset: 0;\n min-width: 0;\n overflow-x: hidden;\n overflow-y: auto;\n padding: 12px max(16px, calc((100% - 688px) / 2)) calc(28px + env(safe-area-inset-bottom) + var(--pp-keyboard-height, 0px));\n scroll-padding-bottom: var(--pp-keyboard-height, 0px);\n background: var(--pp-background);\n}\n.pp-choice-link {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 8px;\n max-width: 45%;\n min-width: 44px;\n min-height: 44px;\n padding: 0;\n text-align: right;\n flex: 1;\n}\n.pp-summary {\n color: var(--pp-muted);\n font-size: 13px;\n line-height: 18px;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n overflow-wrap: anywhere;\n}\n.pp-chevron {\n color: var(--pp-muted);\n font-size: 22px;\n flex: none;\n}\n.pp-editor {\n flex: none;\n width: 45%;\n min-width: 0;\n min-height: 36px;\n padding: 8px 10px;\n font: inherit;\n color: var(--pp-text);\n background: var(--pp-field);\n border: 0;\n border-radius: 6px;\n}\n.pp-panel .pp-multiline {\n display: block;\n}\n.pp-multiline .pp-editor {\n width: 100%;\n margin-top: 10px;\n}\n.pp-panel [hidden] {\n display: none !important;\n}\n.pp-label {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n margin-right: 16px;\n}\n.pp-field-name {\n color: var(--pp-text);\n font-size: 15px;\n}\n.pp-field-description {\n margin-top: 2px;\n color: var(--pp-muted);\n font-size: 12px;\n}\n.pp-group {\n margin-top: 16px;\n}\n.pp-group-title {\n margin: 0 0 8px;\n color: var(--pp-muted);\n font-size: 15px;\n font-weight: 400;\n}\n.pp-row {\n min-width: 0;\n min-height: 48px;\n padding: 16px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n background: var(--pp-surface);\n border-bottom: 1px solid var(--pp-border);\n}\n.pp-rows > :last-child {\n border-bottom: 0 !important;\n}\n.pp-switch {\n flex: none;\n accent-color: var(--pp-accent);\n}\n.pp-choice {\n justify-content: space-between;\n cursor: pointer;\n}\n.pp-choice input {\n width: 20px;\n height: 20px;\n flex: none;\n accent-color: var(--pp-accent);\n margin: 0;\n}\n.pp-description {\n font-size: 12px;\n line-height: 1.6;\n color: var(--pp-muted);\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n}\n.pp-module-info {\n display: flex;\n gap: 12px;\n margin: 12px 0;\n}\n.pp-module-details {\n min-width: 0;\n overflow-wrap: anywhere;\n}\n.pp-module-source {\n color: inherit;\n text-decoration: underline;\n}\n.pp-status {\n position: fixed;\n inset: 0;\n display: grid;\n place-content: center;\n justify-items: center;\n gap: 12px;\n min-width: 0;\n min-height: 0;\n margin: 0;\n padding: 24px;\n color: var(--pp-muted, GrayText);\n text-align: center;\n background: var(--pp-background, Canvas);\n}\n.pp-viewport > .pp-status {\n position: absolute;\n}\n.pp-status-spinner {\n box-sizing: border-box;\n width: 28px;\n height: 28px;\n border: 3px solid color-mix(in srgb, currentColor 25%, transparent);\n border-top-color: var(--pp-accent, AccentColor);\n border-radius: 50%;\n animation: pp-status-spin 0.8s linear infinite;\n}\n.pp-status-message {\n max-width: 100%;\n margin: 0;\n overflow-wrap: anywhere;\n}\n.pp-status-action {\n min-width: 96px;\n min-height: 44px;\n padding: 8px 16px;\n border: 0;\n border-radius: 6px;\n color: var(--pp-text, ButtonText);\n font: inherit;\n cursor: pointer;\n background: var(--pp-surface, ButtonFace);\n}\n@keyframes pp-status-spin {\n to {\n transform: rotate(1turn);\n }\n}\n.pp-cache {\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n}\n.pp-toast {\n pointer-events: none;\n position: fixed;\n bottom: calc(30px + env(safe-area-inset-bottom));\n left: 50%;\n transform: translateX(-50%);\n max-width: 90vw;\n padding: 10px 16px;\n border-radius: 8px;\n background: #333e;\n color: white;\n font-size: 13px;\n z-index: 20;\n}\n.pp-toast[data-kind=\"error\"] {\n background: #8d2424;\n}\n.pp-panel :focus-visible {\n outline: 2px solid var(--pp-accent);\n outline-offset: -2px;\n}\n";
1314
+ var defaults = "/* 通用默认样式只使用 pp 命名空间;项目可通过 CSS 输入覆盖变量和组件。\n * Generic defaults use only the pp namespace; projects may override variables and components through CSS input. */\n.pp-panel {\n --pp-text: #18191c;\n --pp-background: #f6f7f8;\n --pp-surface: #fff;\n --pp-field: #f1f2f3;\n --pp-border: #e3e5e7;\n --pp-muted: #797f87;\n --pp-accent: #1677ff;\n font:\n 15px / 1.5 -apple-system,\n BlinkMacSystemFont,\n \"Segoe UI\",\n sans-serif;\n color: var(--pp-text);\n background: var(--pp-background);\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%;\n max-width: 100vw;\n min-width: 0;\n height: 100vh;\n overflow: hidden;\n}\n\n:root[data-theme=\"dark\"] .pp-panel {\n --pp-text: #f1f2f3;\n --pp-background: #0d0e0f;\n --pp-surface: #18191c;\n --pp-field: #2f3238;\n --pp-border: #2f3238;\n --pp-muted: #9499a0;\n}\n.pp-panel * {\n box-sizing: border-box;\n letter-spacing: 0;\n}\n.pp-header {\n flex: none;\n height: calc(52px + env(safe-area-inset-top));\n padding: env(safe-area-inset-top) 12px 0;\n display: flex;\n align-items: center;\n background: var(--pp-surface);\n border-bottom: 1px solid var(--pp-border);\n position: relative;\n z-index: 2;\n}\n.pp-title {\n flex: 1;\n text-align: center;\n font-size: 17px;\n font-weight: 500;\n margin: 0;\n min-width: 0;\n overflow-wrap: anywhere;\n}\n.pp-nav-spacer {\n width: 44px;\n flex: none;\n}\n.pp-panel button {\n font: inherit;\n cursor: pointer;\n border: 0;\n background: none;\n color: inherit;\n}\n.pp-panel .pp-back {\n width: 44px;\n height: 44px;\n flex: none;\n font-size: 34px;\n line-height: 32px;\n padding: 0;\n}\n.pp-panel button:disabled {\n opacity: 0.5;\n cursor: wait;\n}\n.pp-viewport {\n position: relative;\n flex: 1;\n min-width: 0;\n min-height: 0;\n overflow: hidden;\n}\n@supports (height: 100dvh) {\n .pp-panel {\n height: 100dvh;\n }\n}\n.pp-fields,\n.pp-choice-page,\n.pp-settings-page,\n.pp-cache-page {\n position: absolute;\n inset: 0;\n min-width: 0;\n overflow-x: hidden;\n overflow-y: auto;\n padding: 12px max(16px, calc((100% - 688px) / 2)) calc(28px + env(safe-area-inset-bottom) + var(--pp-keyboard-height, 0px));\n scroll-padding-bottom: var(--pp-keyboard-height, 0px);\n background: var(--pp-background);\n}\n.pp-choice-link {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 8px;\n max-width: 45%;\n min-width: 44px;\n min-height: 44px;\n padding: 0;\n text-align: right;\n flex: 1;\n}\n.pp-summary {\n color: var(--pp-muted);\n font-size: 13px;\n line-height: 18px;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n overflow-wrap: anywhere;\n}\n.pp-chevron {\n color: var(--pp-muted);\n font-size: 22px;\n flex: none;\n}\n.pp-editor {\n flex: none;\n width: 45%;\n min-width: 0;\n min-height: 36px;\n padding: 8px 10px;\n font: inherit;\n color: var(--pp-text);\n background: var(--pp-field);\n border: 0;\n border-radius: 6px;\n}\n.pp-panel .pp-multiline {\n display: block;\n}\n.pp-multiline .pp-editor {\n width: 100%;\n margin-top: 10px;\n}\n.pp-panel [hidden] {\n display: none !important;\n}\n.pp-label {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n margin-right: 16px;\n}\n.pp-field-name {\n color: var(--pp-text);\n font-size: 15px;\n}\n.pp-field-description {\n margin-top: 2px;\n color: var(--pp-muted);\n font-size: 12px;\n}\n.pp-group {\n margin-top: 16px;\n}\n.pp-group-title {\n margin: 0 0 8px;\n color: var(--pp-muted);\n font-size: 15px;\n font-weight: 400;\n}\n.pp-row {\n min-width: 0;\n min-height: 48px;\n padding: 16px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n background: var(--pp-surface);\n border-bottom: 1px solid var(--pp-border);\n}\n.pp-rows > :last-child {\n border-bottom: 0 !important;\n}\n.pp-switch {\n flex: none;\n accent-color: var(--pp-accent);\n}\n.pp-choice {\n justify-content: space-between;\n cursor: pointer;\n}\n.pp-choice input {\n width: 20px;\n height: 20px;\n flex: none;\n accent-color: var(--pp-accent);\n margin: 0;\n}\n.pp-description {\n font-size: 12px;\n line-height: 1.6;\n color: var(--pp-muted);\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n}\n.pp-module-info {\n display: flex;\n gap: 12px;\n margin: 12px 0;\n}\n.pp-module-details {\n min-width: 0;\n overflow-wrap: anywhere;\n}\n.pp-module-source {\n color: inherit;\n text-decoration: underline;\n}\n.pp-status {\n position: fixed;\n inset: 0;\n display: grid;\n place-content: center;\n justify-items: center;\n gap: 12px;\n min-width: 0;\n min-height: 0;\n margin: 0;\n padding: 24px;\n color: var(--pp-muted, GrayText);\n text-align: center;\n background: var(--pp-background, Canvas);\n}\n.pp-viewport > .pp-status {\n position: absolute;\n}\n.pp-status-spinner {\n box-sizing: border-box;\n width: 28px;\n height: 28px;\n border: 3px solid color-mix(in srgb, currentColor 25%, transparent);\n border-top-color: var(--pp-accent, AccentColor);\n border-radius: 50%;\n animation: pp-status-spin 0.8s linear infinite;\n}\n.pp-status-message {\n max-width: 100%;\n margin: 0;\n overflow-wrap: anywhere;\n}\n.pp-status-action {\n min-width: 96px;\n min-height: 44px;\n padding: 8px 16px;\n border: 0;\n border-radius: 6px;\n color: var(--pp-text, ButtonText);\n font: inherit;\n cursor: pointer;\n background: var(--pp-surface, ButtonFace);\n}\n@keyframes pp-status-spin {\n to {\n transform: rotate(1turn);\n }\n}\n.pp-cache {\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n}\n.pp-toast {\n pointer-events: none;\n position: fixed;\n bottom: calc(30px + env(safe-area-inset-bottom));\n left: 50%;\n transform: translateX(-50%);\n max-width: 90vw;\n padding: 10px 16px;\n border-radius: 8px;\n background: #333e;\n color: white;\n font-size: 13px;\n z-index: 20;\n}\n.pp-toast[data-kind=\"error\"] {\n background: #8d2424;\n}\n.pp-panel :focus-visible {\n outline: 2px solid var(--pp-accent);\n outline-offset: -2px;\n}\n";
1305
1315
 
1306
1316
  const selector = "style[data-preference-panes-defaults]";
1317
+ const stylesheetSelector = "link[data-preference-panes-stylesheet]";
1307
1318
 
1308
1319
  /**
1309
1320
  * 在文档中安装一次默认样式,并标记当前调用方是否拥有该节点。
@@ -1317,7 +1328,7 @@ function installDefaultStyles(document) {
1317
1328
  const element = document.createElement("style");
1318
1329
  element.dataset.preferencePanesDefaults = "";
1319
1330
  element.textContent = defaults;
1320
- document.head.append(element);
1331
+ document.head.insertBefore(element, document.head.querySelector(stylesheetSelector));
1321
1332
  return { element, owned: true };
1322
1333
  }
1323
1334