@nsnanocat/preference-panes 0.6.0 → 0.7.1
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 +60 -116
- package/dist/module/app.mjs +1170 -0
- package/dist/module/index.html +14 -0
- package/dist/module/navigation.mjs +160 -0
- package/dist/preference-panes.config.js +839 -813
- package/dist/preference-panes.mjs +1052 -825
- package/dist/preference-panes.proxy.js +1751 -2123
- package/package.json +66 -67
- package/src/BoxJS.mjs +86 -0
- package/src/Store.mjs +127 -0
- package/src/browser/Navigation.d.mts +43 -0
- package/src/browser/Navigation.mjs +158 -0
- package/src/browser/app.mjs +30 -148
- package/src/browser/client.d.mts +150 -0
- package/src/browser/client.mjs +191 -212
- package/src/browser/components.mjs +59 -0
- package/src/browser/index.d.ts +19 -152
- package/src/browser/index.mjs +60 -5
- package/src/browser/module.html +14 -0
- package/src/browser/panel.css +245 -223
- package/src/browser/panel.mjs +414 -514
- package/src/build.mjs +33 -0
- package/src/index.d.ts +227 -133
- package/src/index.mjs +3 -6
- package/src/lib/boxjs.mjs +77 -99
- package/src/lib/page-inputs.mjs +17 -0
- package/src/lib/response.mjs +16 -0
- package/src/lib/settings-path.mjs +10 -23
- package/src/proxy/config.mjs +6 -11
- package/src/proxy/handler.mjs +48 -27
- package/src/proxy/response.mjs +16 -0
- package/dist/preference-panes.request.js +0 -2126
- package/dist/settings/app.mjs +0 -1044
- package/dist/settings/home.css +0 -134
- package/dist/settings/index.html +0 -15
- package/dist/settings/panel.css +0 -325
- package/src/PreferencesHandler.mjs +0 -50
- package/src/SettingsHandler.mjs +0 -144
- package/src/browser/home.css +0 -134
- package/src/browser/site.html +0 -15
- package/src/proxy/request.mjs +0 -5
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
|
|
6
|
+
<meta name="color-scheme" content="light dark">
|
|
7
|
+
<title>Module Preferences</title>
|
|
8
|
+
<style>body { margin: 0; }</style>
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<main id="preferences"></main>
|
|
12
|
+
<script type="module" src="/settings/assets/app.mjs?v=0.7.1"></script>
|
|
13
|
+
</body>
|
|
14
|
+
</html>
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 同一文档内的主页/子页导航;iframe 各自的实例通过浏览器联合历史协作。
|
|
3
|
+
* Navigate home/detail views within a document; iframe instances cooperate through joint browser history.
|
|
4
|
+
*/
|
|
5
|
+
class Navigation extends EventTarget {
|
|
6
|
+
#container;
|
|
7
|
+
#home;
|
|
8
|
+
#create;
|
|
9
|
+
#window;
|
|
10
|
+
#key = null;
|
|
11
|
+
#view;
|
|
12
|
+
#retiring;
|
|
13
|
+
#controller;
|
|
14
|
+
#animation;
|
|
15
|
+
#scroll = new WeakMap();
|
|
16
|
+
#onHistory = () => this.#route();
|
|
17
|
+
#onPageShow = event => {
|
|
18
|
+
if (event.persisted) this.#route(true);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 根视图始终保留;工厂按需提供子页,可用 signal 取消离开后的异步加载。
|
|
23
|
+
* Retain the home view and create details on demand; signal cancels async work after departure.
|
|
24
|
+
* @param {HTMLElement} container 由调用方布局的页面容器 / Caller-styled view container.
|
|
25
|
+
* @param {HTMLElement} home 已创建的主页节点 / Existing home view.
|
|
26
|
+
* @param {(key: string, signal: AbortSignal) => HTMLElement | undefined} create 子页工厂;未知路径返回 undefined / Detail factory; undefined for unknown routes.
|
|
27
|
+
*/
|
|
28
|
+
constructor(container, home, create) {
|
|
29
|
+
super();
|
|
30
|
+
this.#container = container;
|
|
31
|
+
this.#home = home;
|
|
32
|
+
this.#create = create;
|
|
33
|
+
this.#window = container.ownerDocument.defaultView;
|
|
34
|
+
container.replaceChildren(home);
|
|
35
|
+
this.#window.addEventListener("popstate", this.#onHistory);
|
|
36
|
+
this.#window.addEventListener("hashchange", this.#onHistory);
|
|
37
|
+
this.#window.addEventListener("pageshow", this.#onPageShow);
|
|
38
|
+
this.#route();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 当前子页键;空字符串表示主页。
|
|
43
|
+
* Current detail key; empty means home.
|
|
44
|
+
*/
|
|
45
|
+
get current() {
|
|
46
|
+
return this.#key;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 是否可以返回上一级或先前文档。
|
|
51
|
+
* Whether a parent view or previous document is available.
|
|
52
|
+
*/
|
|
53
|
+
get canGoBack() {
|
|
54
|
+
return Boolean(this.#key) || this.#window.history.length > 1;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 加入子页历史;使用文档自身 URL,避免 srcdoc 按宿主 base URL 跳转。
|
|
59
|
+
* Push a detail using the document URL, avoiding srcdoc navigation against the host base URL.
|
|
60
|
+
* @param {string} key 子页键 / Detail key.
|
|
61
|
+
* @returns {void} 无返回值 / No return value.
|
|
62
|
+
*/
|
|
63
|
+
open(key) {
|
|
64
|
+
if (key === this.#key) return;
|
|
65
|
+
const url = new URL(this.#window.location.href);
|
|
66
|
+
url.hash = encodeURIComponent(key);
|
|
67
|
+
this.#window.history.pushState({ ...this.#window.history.state, preferencePanesRoute: key }, "", url.href);
|
|
68
|
+
this.#route();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 沿浏览器联合历史返回,根页可退回宿主或上个文档。
|
|
73
|
+
* Go back through joint history, including a host or previous document from home.
|
|
74
|
+
* @returns {void} 无返回值 / No return value.
|
|
75
|
+
*/
|
|
76
|
+
back() {
|
|
77
|
+
if (this.canGoBack) this.#window.history.back();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 解析 URL 并统一处理页面切换、加载取消与动画结束后的释放。
|
|
82
|
+
* Resolve the URL and coordinate transitions, cancellation and release after animation.
|
|
83
|
+
* @param {boolean} [reload] 从页面缓存恢复时重新创建子页 / Recreate a detail after bfcache restoration.
|
|
84
|
+
* @returns {void} 无返回值 / No return value.
|
|
85
|
+
*/
|
|
86
|
+
#route(reload = false) {
|
|
87
|
+
const url = new URL(this.#window.location.href);
|
|
88
|
+
let key;
|
|
89
|
+
try {
|
|
90
|
+
key = decodeURIComponent(url.hash.slice(1));
|
|
91
|
+
} catch (error) {
|
|
92
|
+
if (!(error instanceof URIError)) throw error;
|
|
93
|
+
key = "";
|
|
94
|
+
}
|
|
95
|
+
if (!reload && key === this.#key) return;
|
|
96
|
+
this.#controller?.abort();
|
|
97
|
+
this.#controller = new AbortController();
|
|
98
|
+
const next = key ? this.#create(key, this.#controller.signal) : undefined;
|
|
99
|
+
if (!next) key = "";
|
|
100
|
+
const history = this.#window.history;
|
|
101
|
+
// 直接打开子页时建立一次主页历史;刷新不重复堆叠。
|
|
102
|
+
// Seed home history once for direct details, without stacking entries on reload.
|
|
103
|
+
if (url.hash && history.state?.preferencePanesRoute !== key) {
|
|
104
|
+
url.hash = "";
|
|
105
|
+
history.replaceState({ ...history.state, preferencePanesRoute: "" }, "", url.href);
|
|
106
|
+
if (key) {
|
|
107
|
+
url.hash = encodeURIComponent(key);
|
|
108
|
+
history.pushState({ ...history.state, preferencePanesRoute: key }, "", url.href);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const previous = this.#view;
|
|
112
|
+
const position = previous ? this.#window.getComputedStyle(previous).transform : "none";
|
|
113
|
+
this.#animation?.cancel();
|
|
114
|
+
this.#retiring?.remove();
|
|
115
|
+
this.#retiring = previous;
|
|
116
|
+
if (previous) {
|
|
117
|
+
this.#scroll.set(previous, previous.scrollTop);
|
|
118
|
+
previous.inert = true;
|
|
119
|
+
}
|
|
120
|
+
this.#key = key;
|
|
121
|
+
this.#view = next;
|
|
122
|
+
this.#home.inert = Boolean(next);
|
|
123
|
+
if (next) {
|
|
124
|
+
next.inert = false;
|
|
125
|
+
this.#container.append(next);
|
|
126
|
+
next.scrollTop = this.#scroll.get(next) ?? 0;
|
|
127
|
+
}
|
|
128
|
+
const moving = next ?? previous;
|
|
129
|
+
if (moving) {
|
|
130
|
+
const animation = moving.animate([{ transform: next ? "translateX(100%)" : position }, { transform: next ? "translateX(0)" : "translateX(100%)" }], { duration: this.#window.matchMedia("(prefers-reduced-motion: reduce)").matches ? 0 : 280, easing: "cubic-bezier(.22,.61,.36,1)", fill: "forwards" });
|
|
131
|
+
this.#animation = animation;
|
|
132
|
+
animation.onfinish = () => {
|
|
133
|
+
if (this.#animation !== animation) return;
|
|
134
|
+
this.#retiring?.remove();
|
|
135
|
+
this.#retiring = undefined;
|
|
136
|
+
animation.cancel();
|
|
137
|
+
this.#animation = undefined;
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
this.dispatchEvent(new Event("change"));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* 释放监听器、加载、动画和节点;调用方可重新创建导航。
|
|
145
|
+
* Release listeners, loads, animations and nodes so callers can recreate navigation.
|
|
146
|
+
* @returns {void} 无返回值 / No return value.
|
|
147
|
+
*/
|
|
148
|
+
destroy() {
|
|
149
|
+
this.#window.removeEventListener("popstate", this.#onHistory);
|
|
150
|
+
this.#window.removeEventListener("hashchange", this.#onHistory);
|
|
151
|
+
this.#window.removeEventListener("pageshow", this.#onPageShow);
|
|
152
|
+
this.#controller?.abort();
|
|
153
|
+
this.#animation?.cancel();
|
|
154
|
+
this.#retiring?.remove();
|
|
155
|
+
this.#view?.remove();
|
|
156
|
+
this.#home.remove();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export { Navigation };
|