@nsnanocat/preference-panes 1.1.2 → 1.1.4

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.
@@ -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
  export class ActionMenu {
6
6
  #button;
@@ -76,7 +76,7 @@ export class ModuleStatus extends EventTarget {
76
76
  const response = await probeModule(url, { ...options, signal: controller.signal });
77
77
  if (controller !== this.#controller) return response;
78
78
  const version = response.status === 200 ? response.headers.get("X-PreferencePanes-Version")?.trim() || null : null;
79
- this.#render(response.status === 200 ? "installed" : "missing", version);
79
+ this.#render(version ? "installed" : "missing", version);
80
80
  return response;
81
81
  } catch (error) {
82
82
  if (controller !== this.#controller) return;
@@ -25,6 +25,7 @@ export function normalizeBoxJs(config, module) {
25
25
  if (!storageKey || storageKey.startsWith("@") || parts.length < 2) throw new TypeError("A BoxJS setting must be below a literal storage root and module");
26
26
  validatePathParts(parts);
27
27
  const name = parts[0];
28
+ if (["get", "set", "delete"].includes(name)) throw new TypeError(`Reserved API module name: ${name}`);
28
29
  let target = modules.get(name);
29
30
  if (!target) {
30
31
  target = { module: name, storageKey, entries: [], owners: new Set() };
@@ -87,9 +87,6 @@
87
87
  min-height: 0;
88
88
  overflow: hidden;
89
89
  }
90
- :root[data-preference-panes-embedded] .pp-header {
91
- display: none;
92
- }
93
90
  @supports (height: 100dvh) {
94
91
  .pp-panel {
95
92
  height: 100dvh;
@@ -32,13 +32,9 @@ export class PreferencesPanel {
32
32
  const title = definition.metadata?.name ?? definition.module;
33
33
  const document = root.ownerDocument;
34
34
  const window = document.defaultView;
35
+ const frame = window.frameElement?.dataset.preferencePanes ? window.frameElement : undefined;
35
36
  const shell = node("div", "pp-panel");
36
37
  shell.dataset.module = definition.module;
37
- const header = node("header", "pp-header");
38
- const back = node("button", "pp-back", "‹");
39
- back.setAttribute("aria-label", "返回");
40
- back.type = "button";
41
- const heading = node("h1", "pp-title", title);
42
38
  const handlers = new Map();
43
39
  const menuItems = [
44
40
  { id: "viewSettings", label: "查看设置" },
@@ -46,37 +42,51 @@ export class PreferencesPanel {
46
42
  { id: "clearCaches", label: "清空缓存", destructive: true },
47
43
  { id: "reset", label: "重置设置", destructive: true },
48
44
  ];
49
- const menu = new ActionMenu(id => runAction(id));
50
- const trailing = node("span", "pp-nav-spacer");
51
- trailing.append(menu.element);
52
45
  const viewport = node("div", "pp-viewport");
46
+ let back, heading, menu;
47
+ if (frame) shell.append(viewport);
48
+ else {
49
+ const header = node("header", "pp-header");
50
+ back = node("button", "pp-back", "‹");
51
+ back.setAttribute("aria-label", "返回");
52
+ back.type = "button";
53
+ heading = node("h1", "pp-title", title);
54
+ menu = new ActionMenu(id => runAction(id));
55
+ const trailing = node("span", "pp-nav-spacer");
56
+ trailing.append(menu.element);
57
+ header.append(back, heading, trailing);
58
+ shell.append(header, viewport);
59
+ }
53
60
  let toast;
54
- header.append(back, heading, trailing);
55
- shell.append(header, viewport);
56
61
  root.append(shell);
57
62
  // 嵌入模式向宿主发布导航状态,宿主不读取或修改模块内部 DOM。
58
63
  // Embedded mode publishes navigation state without host reads or mutations of the module DOM.
59
64
  const publishNavigation = () => {
60
65
  const actions = handlers.size ? menuItems : [];
61
- menu.update(actions, saving);
62
- const frame = window.frameElement;
63
- if (!frame?.dataset.preferencePanes) return;
64
- frame.dispatchEvent(
65
- new frame.ownerDocument.defaultView.CustomEvent("preferencepanes:change", {
66
- detail: { title: heading.textContent, module: definition.module, busy: saving, canGoBack: !back.disabled, actions },
67
- }),
68
- );
66
+ if (frame)
67
+ frame.dispatchEvent(
68
+ new frame.ownerDocument.defaultView.CustomEvent("preferencepanes:change", {
69
+ detail: { title: currentTitle, module: definition.module, busy: saving, canGoBack, actions },
70
+ }),
71
+ );
72
+ else {
73
+ heading.textContent = currentTitle;
74
+ back.disabled = !canGoBack;
75
+ menu.update(actions, saving);
76
+ }
69
77
  };
70
78
  const onAction = event => {
71
79
  if (!saving && handlers.has(event.detail)) runAction(event.detail);
72
80
  };
73
- window.frameElement?.addEventListener("preferencepanes:action", onAction);
81
+ frame?.addEventListener("preferencepanes:action", onAction);
74
82
  let timer,
75
83
  navigation,
76
84
  generation = 0,
77
85
  active = null,
78
86
  saving = false,
79
- destroyed = false;
87
+ destroyed = false,
88
+ currentTitle = title,
89
+ canGoBack = window.history.length > 1;
80
90
  /**
81
91
  * 展示短暂通知,不刷新设置数据。
82
92
  * Display a transient notification without refreshing settings.
@@ -105,7 +115,6 @@ export class PreferencesPanel {
105
115
  }
106
116
  // 宿主接管时不创建网页 Toast,也不运行其计时器。
107
117
  // A host-owned notice creates no web Toast and starts no local timer.
108
- const frame = window.frameElement;
109
118
  if (frame && !frame.dispatchEvent(new frame.ownerDocument.defaultView.CustomEvent("preferencepanes:notice", { cancelable: true, detail: { kind: event.kind, message } }))) return;
110
119
  if (!toast) {
111
120
  toast = node("div", "pp-toast");
@@ -143,8 +152,8 @@ export class PreferencesPanel {
143
152
  async function open(module) {
144
153
  const version = ++generation;
145
154
  active = module;
146
- back.disabled = window.history.length <= 1;
147
- heading.textContent = module;
155
+ currentTitle = module;
156
+ canGoBack = window.history.length > 1;
148
157
  publishNavigation();
149
158
  viewport.replaceChildren(statusView("读取设置…"));
150
159
  try {
@@ -163,7 +172,7 @@ export class PreferencesPanel {
163
172
  */
164
173
  function controls() {
165
174
  const { definition, values } = client.snapshot();
166
- heading.textContent = definition.metadata?.name || active;
175
+ currentTitle = definition.metadata?.name || active;
167
176
  const view = node("section", "pp-fields");
168
177
  /**
169
178
  * 挂载后执行的多行高度更新
@@ -183,8 +192,8 @@ export class PreferencesPanel {
183
192
  */
184
193
  const updateNavigation = () => {
185
194
  const editor = editors.get(navigation.current);
186
- heading.textContent = editor?.title ?? definition.metadata?.name ?? active;
187
- back.disabled = saving || !navigation.canGoBack;
195
+ currentTitle = editor?.title ?? definition.metadata?.name ?? active;
196
+ canGoBack = !saving && navigation.canGoBack;
188
197
  publishNavigation();
189
198
  };
190
199
  /**
@@ -198,7 +207,7 @@ export class PreferencesPanel {
198
207
  function perform(action, success, failure = () => {}) {
199
208
  pendingWrites++;
200
209
  saving = true;
201
- back.disabled = true;
210
+ canGoBack = false;
202
211
  publishNavigation();
203
212
  queue = queue
204
213
  .then(action)
@@ -214,7 +223,7 @@ export class PreferencesPanel {
214
223
  pendingWrites--;
215
224
  saving = pendingWrites > 0;
216
225
  if (destroyed && !saving) client.leave();
217
- back.disabled = saving || !navigation.canGoBack;
226
+ canGoBack = !saving && navigation.canGoBack;
218
227
  publishNavigation();
219
228
  });
220
229
  return queue;
@@ -489,16 +498,17 @@ export class PreferencesPanel {
489
498
  * Loaded forms delegate back to navigation; loading views can return to the previous document.
490
499
  * @returns {void} 无返回值 / No return value.
491
500
  */
492
- back.onclick = () => {
493
- if (saving) return;
494
- if (navigation) navigation.back();
495
- else window.history.back();
496
- };
501
+ if (back)
502
+ back.onclick = () => {
503
+ if (saving) return;
504
+ if (navigation) navigation.back();
505
+ else window.history.back();
506
+ };
497
507
  open(definition.module);
498
508
  return () => {
499
509
  destroyed = true;
500
- menu.destroy();
501
- window.frameElement?.removeEventListener("preferencepanes:action", onAction);
510
+ menu?.destroy();
511
+ frame?.removeEventListener("preferencepanes:action", onAction);
502
512
  navigation?.destroy();
503
513
  generation++;
504
514
  if (active && !saving) client.leave();