@1outres/dsh-plugin-mobile 0.0.0-stage → 0.7.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/lib/client.js ADDED
@@ -0,0 +1,1084 @@
1
+ /* Generated by scripts/build.mjs — do not edit. */
2
+ window.__ModuleLoader__.load({
3
+ id: "@1outres/dsh-plugin-mobile",
4
+ factory: (require) => {
5
+ var module = { exports: {} };
6
+ var exports = module.exports;
7
+ "use strict";
8
+ var __defProp = Object.defineProperty;
9
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
10
+ var __getOwnPropNames = Object.getOwnPropertyNames;
11
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
12
+ var __export = (target, all) => {
13
+ for (var name in all)
14
+ __defProp(target, name, { get: all[name], enumerable: true });
15
+ };
16
+ var __copyProps = (to, from, except, desc) => {
17
+ if (from && typeof from === "object" || typeof from === "function") {
18
+ for (let key of __getOwnPropNames(from))
19
+ if (!__hasOwnProp.call(to, key) && key !== except)
20
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
21
+ }
22
+ return to;
23
+ };
24
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
25
+
26
+ // src/client/index.tsx
27
+ var index_exports = {};
28
+ __export(index_exports, {
29
+ SETTINGS_SLOT: () => SETTINGS_SLOT,
30
+ apply: () => apply,
31
+ inject: () => inject
32
+ });
33
+ module.exports = __toCommonJS(index_exports);
34
+
35
+ // src/client/config.ts
36
+ var NAMESPACE = "mobile";
37
+ var DEFAULT_CONFIG = {
38
+ enterKey: "newline",
39
+ swipe: true,
40
+ fitKeyboard: true,
41
+ blockPullToRefresh: true,
42
+ autoFocusComposer: false
43
+ };
44
+ var ENTER_KEYS = ["newline", "send"];
45
+ function oneOf(value, allowed, fallback) {
46
+ return typeof value === "string" && allowed.includes(value) ? value : fallback;
47
+ }
48
+ function flag(value, fallback) {
49
+ return typeof value === "boolean" ? value : fallback;
50
+ }
51
+ function normalizeConfig(value) {
52
+ return {
53
+ enterKey: oneOf(value?.enterKey, ENTER_KEYS, DEFAULT_CONFIG.enterKey),
54
+ swipe: flag(value?.swipe, DEFAULT_CONFIG.swipe),
55
+ fitKeyboard: flag(value?.fitKeyboard, DEFAULT_CONFIG.fitKeyboard),
56
+ blockPullToRefresh: flag(value?.blockPullToRefresh, DEFAULT_CONFIG.blockPullToRefresh),
57
+ autoFocusComposer: flag(value?.autoFocusComposer, DEFAULT_CONFIG.autoFocusComposer)
58
+ };
59
+ }
60
+
61
+ // src/client/frame.ts
62
+ var FRAME_ANCHOR = "[data-rightbar-col]";
63
+ var COLLAPSED_ATTRIBUTE = "data-sidebar-collapsed";
64
+ var FULLSCREEN_ATTRIBUTE = "data-rightbar-fullscreen";
65
+ var DRAWER_MODAL = "sidebar";
66
+ function findFrame(root) {
67
+ return root.querySelector(FRAME_ANCHOR)?.parentElement ?? null;
68
+ }
69
+ function isExpanded(frame) {
70
+ return frame !== null && !frame.hasAttribute(COLLAPSED_ATTRIBUTE);
71
+ }
72
+ var NO_SWIPE = "pre, table, input, textarea, select, [contenteditable='true'], [role='slider'], [data-sidebar-opener], [data-sidebar-toggle], [data-dsh-mobile-no-swipe]";
73
+ var OTHER_MODAL = `[aria-modal='true']:not([data-shortcut-modal='${DRAWER_MODAL}'])`;
74
+ function otherModalOpen(root) {
75
+ return root.querySelector(OTHER_MODAL) !== null;
76
+ }
77
+ function swipeBlocked(target, root, overflowX) {
78
+ if (findFrame(root)?.hasAttribute(FULLSCREEN_ATTRIBUTE) === true) return true;
79
+ if (otherModalOpen(root)) return true;
80
+ const element = target;
81
+ if (element === null || typeof element.closest !== "function") return false;
82
+ if (element.closest(NO_SWIPE) !== null) return true;
83
+ let current = element;
84
+ for (let depth = 0; current !== null && depth < 12; depth += 1) {
85
+ if (current.scrollWidth > current.clientWidth + 1) {
86
+ const overflow = overflowX(current);
87
+ if (overflow === "auto" || overflow === "scroll") return true;
88
+ }
89
+ current = current.parentElement;
90
+ }
91
+ return false;
92
+ }
93
+
94
+ // src/client/drawer.ts
95
+ var DRAWER_ATTRIBUTE = "data-sidebar-drawer";
96
+ function observeMutations(target, options, callback) {
97
+ const observer = new MutationObserver(callback);
98
+ observer.observe(target, options);
99
+ return observer;
100
+ }
101
+ function createDrawerSource(document2, observe) {
102
+ const listeners = /* @__PURE__ */ new Set();
103
+ let frame = null;
104
+ let frameObserver;
105
+ let value = false;
106
+ const readFrame = () => document2.querySelector(FRAME_ANCHOR)?.parentElement ?? null;
107
+ const refresh = () => {
108
+ const next = frame?.hasAttribute(DRAWER_ATTRIBUTE) ?? false;
109
+ if (next === value) return;
110
+ value = next;
111
+ for (const listener of [...listeners]) listener();
112
+ };
113
+ const attach = () => {
114
+ const next = readFrame();
115
+ if (next === frame) {
116
+ refresh();
117
+ return;
118
+ }
119
+ frameObserver?.disconnect();
120
+ frame = next;
121
+ frameObserver = frame === null ? void 0 : observe(frame, { attributes: true, attributeFilter: [DRAWER_ATTRIBUTE] }, refresh);
122
+ refresh();
123
+ };
124
+ const documentObserver = observe(
125
+ document2.documentElement,
126
+ { childList: true, subtree: true },
127
+ () => {
128
+ if (frame === null || frame.isConnected === false) attach();
129
+ }
130
+ );
131
+ attach();
132
+ return {
133
+ get: () => value,
134
+ subscribe(listener) {
135
+ listeners.add(listener);
136
+ return () => {
137
+ listeners.delete(listener);
138
+ };
139
+ },
140
+ dispose() {
141
+ documentObserver.disconnect();
142
+ frameObserver?.disconnect();
143
+ listeners.clear();
144
+ }
145
+ };
146
+ }
147
+
148
+ // src/client/enter.ts
149
+ var COMPOSER_INPUT = "[data-composer-input]";
150
+ var OPEN_MENU = "[data-composer-overlay] [role='listbox']";
151
+ function shouldInsertNewline(event, target) {
152
+ if (event.key !== "Enter") return false;
153
+ if (event.isComposing === true || event.keyCode === 229) return false;
154
+ if (event.shiftKey || event.ctrlKey || event.metaKey || event.altKey) return false;
155
+ const input = target?.closest(COMPOSER_INPUT) ?? null;
156
+ if (input === null || input.isContentEditable === false) return false;
157
+ const card = input.closest("[data-composer-card]");
158
+ if (card !== null && card.querySelector(OPEN_MENU) !== null) return false;
159
+ return true;
160
+ }
161
+ function asClosest(target) {
162
+ const candidate = target;
163
+ return candidate !== null && typeof candidate.closest === "function" ? candidate : null;
164
+ }
165
+ function installEnterNewline(target, active) {
166
+ const listener = (event) => {
167
+ if (!active()) return;
168
+ if (!shouldInsertNewline(event, asClosest(event.target))) return;
169
+ event.stopImmediatePropagation();
170
+ };
171
+ target.addEventListener("keydown", listener, true);
172
+ return () => target.removeEventListener("keydown", listener, true);
173
+ }
174
+
175
+ // src/client/focus.ts
176
+ var INTENT_WINDOW_MS = 1500;
177
+ var COMPOSER_CARD = "[data-composer-card]";
178
+ var CONTROL = "button, a, input, select, textarea, [role='button'], [role='menuitem'], [role='option']";
179
+ var COMMAND_BUTTON = "[aria-haspopup='listbox']";
180
+ function asTarget(target) {
181
+ const candidate = target;
182
+ return candidate !== null && typeof candidate?.closest === "function" ? candidate : null;
183
+ }
184
+ function tapWantsKeyboard(target) {
185
+ const element = asTarget(target);
186
+ if (element === null) return false;
187
+ if (element.closest(COMPOSER_INPUT) !== null) return true;
188
+ if (element.closest(COMPOSER_CARD) === null) return false;
189
+ return element.closest(CONTROL) === null || element.closest(COMMAND_BUTTON) !== null;
190
+ }
191
+ function initialIntent() {
192
+ return {
193
+ tapAt: Number.NEGATIVE_INFINITY,
194
+ tapWantsKeyboard: false,
195
+ keyAt: Number.NEGATIVE_INFINITY
196
+ };
197
+ }
198
+ function shouldBlockFocus(target, intent, now) {
199
+ const element = asTarget(target);
200
+ if (element === null || element.closest(COMPOSER_INPUT) === null) return false;
201
+ if (intent.tapWantsKeyboard && now - intent.tapAt <= INTENT_WINDOW_MS) return false;
202
+ if (intent.keyAt > intent.tapAt && now - intent.keyAt <= INTENT_WINDOW_MS) return false;
203
+ return true;
204
+ }
205
+ function installFocusGuard(target, active, now = Date.now) {
206
+ const intent = initialIntent();
207
+ const onPointer = (event) => {
208
+ intent.tapAt = now();
209
+ intent.tapWantsKeyboard = tapWantsKeyboard(event.target);
210
+ };
211
+ const onKey = () => {
212
+ intent.keyAt = now();
213
+ };
214
+ const onFocus = (event) => {
215
+ if (!active() || !shouldBlockFocus(event.target, intent, now())) return;
216
+ const element = event.target;
217
+ element?.blur?.();
218
+ };
219
+ target.addEventListener("pointerdown", onPointer, true);
220
+ target.addEventListener("keydown", onKey, true);
221
+ target.addEventListener("focusin", onFocus, true);
222
+ return () => {
223
+ target.removeEventListener("pointerdown", onPointer, true);
224
+ target.removeEventListener("keydown", onKey, true);
225
+ target.removeEventListener("focusin", onFocus, true);
226
+ };
227
+ }
228
+
229
+ // src/client/i18n.ts
230
+ var en = {
231
+ nav: "Mobile",
232
+ "settings.title": "Mobile",
233
+ "settings.description": "Adds phone refinements on top of the Harness layout: the page fits above the on-screen keyboard, Enter adds a line, the keyboard stays closed until you tap the composer, and an edge swipe opens the navigation drawer that the Harness uses below 768px. Changes apply at once in every open page.",
234
+ "settings.statusMobile": "The Harness is using its phone layout here ({width}px wide).",
235
+ "settings.statusDesktop": "The Harness is using its desktop layout here ({width}px wide); it switches to the phone layout below 768px.",
236
+ "settings.enterKey": "Enter key in the composer",
237
+ "settings.enterKeyHint": "On a touch screen in the phone layout. With New line, send with the send button. IME conversion and command menus keep their Enter.",
238
+ "settings.enterKey.newline": "New line",
239
+ "settings.enterKey.send": "Send",
240
+ "settings.autoFocusComposer": "Focus the composer automatically",
241
+ "settings.autoFocusComposerHint": "Let a composer button or another Harness action focus the input and open the on-screen keyboard. Off keeps the keyboard closed until you tap the input.",
242
+ "settings.swipe": "Swipe to open the sidebar",
243
+ "settings.swipeHint": "Swipe right from the left edge to open the Harness navigation drawer, swipe left to close it.",
244
+ "settings.fitKeyboard": "Fit the page to the keyboard",
245
+ "settings.fitKeyboardHint": "Keep the header and the composer on screen while the on-screen keyboard is up.",
246
+ "settings.blockPullToRefresh": "Block pull-to-refresh",
247
+ "settings.blockPullToRefreshHint": "Scrolling up past the top of a long conversation does not reload the page.",
248
+ "settings.reset": "Restore defaults",
249
+ "settings.unavailable": "This configuration is not available.",
250
+ "settings.readOnly": "Configuration is read-only: a higher configuration layer sets it.",
251
+ "settings.writeError": "The Host refused the change."
252
+ };
253
+ var zh = {
254
+ nav: "\u79FB\u52A8\u7AEF",
255
+ "settings.title": "\u79FB\u52A8\u7AEF",
256
+ "settings.description": "\u5728 Harness \u5E03\u5C40\u4E4B\u4E0A\u8865\u5145\u624B\u673A\u4F53\u9A8C\uFF1A\u9875\u9762\u9002\u914D\u5C4F\u5E55\u952E\u76D8\u4E0A\u65B9\u7684\u53EF\u89C1\u533A\u57DF\uFF0CEnter \u6362\u884C\uFF0C\u70B9\u6309\u8F93\u5165\u6846\u4E4B\u524D\u4E0D\u5F39\u51FA\u952E\u76D8\uFF0C\u5C4F\u5E55\u7A84\u4E8E 768px \u65F6\u4ECE\u8FB9\u7F18\u6ED1\u52A8\u53EF\u6253\u5F00 Harness \u7684\u5BFC\u822A\u62BD\u5C49\u3002\u4FEE\u6539\u4F1A\u7ACB\u5373\u5E94\u7528\u5230\u6240\u6709\u6253\u5F00\u7684\u9875\u9762\u3002",
257
+ "settings.statusMobile": "Harness \u5F53\u524D\u4F7F\u7528\u624B\u673A\u5E03\u5C40\uFF08\u5BBD {width}px\uFF09\u3002",
258
+ "settings.statusDesktop": "Harness \u5F53\u524D\u4F7F\u7528\u684C\u9762\u5E03\u5C40\uFF08\u5BBD {width}px\uFF09\uFF1B\u7A84\u4E8E 768px \u65F6\u5207\u6362\u4E3A\u624B\u673A\u5E03\u5C40\u3002",
259
+ "settings.enterKey": "\u8F93\u5165\u6846\u4E2D\u7684 Enter \u952E",
260
+ "settings.enterKeyHint": "\u4EC5\u5728\u89E6\u5C4F\u8BBE\u5907\u7684\u624B\u673A\u5E03\u5C40\u4E2D\u751F\u6548\u3002\u9009\u62E9\u6362\u884C\u65F6\u7528\u53D1\u9001\u6309\u94AE\u53D1\u9001\u3002\u8F93\u5165\u6CD5\u786E\u8BA4\u548C\u547D\u4EE4\u83DC\u5355\u4ECD\u4F7F\u7528 Enter\u3002",
261
+ "settings.enterKey.newline": "\u6362\u884C",
262
+ "settings.enterKey.send": "\u53D1\u9001",
263
+ "settings.autoFocusComposer": "\u81EA\u52A8\u805A\u7126\u8F93\u5165\u6846",
264
+ "settings.autoFocusComposerHint": "\u6309\u4E0B\u8F93\u5165\u6846\u4E2D\u7684\u6309\u94AE\u6216\u7531 Harness \u7684\u5176\u4ED6\u64CD\u4F5C\u628A\u7126\u70B9\u79FB\u5230\u8F93\u5165\u6846\u65F6\uFF0C\u5141\u8BB8\u805A\u7126\u5E76\u5F39\u51FA\u5C4F\u5E55\u952E\u76D8\u3002\u5173\u95ED\u65F6\uFF0C\u70B9\u6309\u8F93\u5165\u6846\u4E4B\u524D\u952E\u76D8\u4E0D\u4F1A\u5F39\u51FA\u3002",
265
+ "settings.swipe": "\u6ED1\u52A8\u6253\u5F00\u4FA7\u8FB9\u680F",
266
+ "settings.swipeHint": "\u4ECE\u5DE6\u8FB9\u7F18\u5411\u53F3\u6ED1\u52A8\u6253\u5F00 Harness \u5BFC\u822A\u62BD\u5C49\uFF0C\u5411\u5DE6\u6ED1\u52A8\u5173\u95ED\u3002",
267
+ "settings.fitKeyboard": "\u9875\u9762\u9002\u914D\u952E\u76D8",
268
+ "settings.fitKeyboardHint": "\u5C4F\u5E55\u952E\u76D8\u5F39\u51FA\u65F6\uFF0C\u6807\u9898\u680F\u548C\u8F93\u5165\u6846\u4FDD\u6301\u5728\u5C4F\u5E55\u5185\u3002",
269
+ "settings.blockPullToRefresh": "\u963B\u6B62\u4E0B\u62C9\u5237\u65B0",
270
+ "settings.blockPullToRefreshHint": "\u5728\u957F\u4F1A\u8BDD\u9876\u90E8\u7EE7\u7EED\u4E0A\u6ED1\u4E0D\u4F1A\u5237\u65B0\u9875\u9762\u3002",
271
+ "settings.reset": "\u6062\u590D\u9ED8\u8BA4",
272
+ "settings.unavailable": "\u6B64\u914D\u7F6E\u4E0D\u53EF\u7528\u3002",
273
+ "settings.readOnly": "\u914D\u7F6E\u4E3A\u53EA\u8BFB\uFF1A\u7531\u66F4\u9AD8\u7684\u914D\u7F6E\u5C42\u8BBE\u7F6E\u3002",
274
+ "settings.writeError": "Host \u62D2\u7EDD\u4E86\u6B64\u4FEE\u6539\u3002"
275
+ };
276
+ var ja = {
277
+ nav: "\u30E2\u30D0\u30A4\u30EB",
278
+ "settings.title": "\u30E2\u30D0\u30A4\u30EB",
279
+ "settings.description": "Harness \u306E\u30EC\u30A4\u30A2\u30A6\u30C8\u306B\u30B9\u30DE\u30FC\u30C8\u30D5\u30A9\u30F3\u5411\u3051\u306E\u8ABF\u6574\u3092\u8DB3\u3057\u307E\u3059\u3002\u30AA\u30F3\u30B9\u30AF\u30EA\u30FC\u30F3\u30AD\u30FC\u30DC\u30FC\u30C9\u306E\u4E0A\u306B\u53CE\u307E\u308B\u3088\u3046\u30DA\u30FC\u30B8\u3092\u7E2E\u3081\u3001Enter \u3067\u6539\u884C\u3057\u3001\u5165\u529B\u6B04\u3092\u30BF\u30C3\u30D7\u3059\u308B\u307E\u3067\u30AD\u30FC\u30DC\u30FC\u30C9\u3092\u958B\u304B\u305A\u3001768px \u672A\u6E80\u3067\u4F7F\u308F\u308C\u308B Harness \u306E\u30CA\u30D3\u30B2\u30FC\u30B7\u30E7\u30F3\u30C9\u30ED\u30EF\u30FC\u3092\u753B\u9762\u7AEF\u306E\u30B9\u30EF\u30A4\u30D7\u3067\u958B\u9589\u3067\u304D\u308B\u3088\u3046\u306B\u3057\u307E\u3059\u3002\u5909\u66F4\u306F\u958B\u3044\u3066\u3044\u308B\u3059\u3079\u3066\u306E\u30DA\u30FC\u30B8\u306B\u3059\u3050\u53CD\u6620\u3055\u308C\u307E\u3059\u3002",
280
+ "settings.statusMobile": "Harness \u306F\u3053\u306E\u30DA\u30FC\u30B8\u3067\u30B9\u30DE\u30DB\u8868\u793A\u3092\u4F7F\u3063\u3066\u3044\u307E\u3059\uFF08\u5E45 {width}px\uFF09\u3002",
281
+ "settings.statusDesktop": "Harness \u306F\u3053\u306E\u30DA\u30FC\u30B8\u3067\u30C7\u30B9\u30AF\u30C8\u30C3\u30D7\u8868\u793A\u3092\u4F7F\u3063\u3066\u3044\u307E\u3059\uFF08\u5E45 {width}px\uFF09\u3002768px \u672A\u6E80\u3067\u30B9\u30DE\u30DB\u8868\u793A\u306B\u5207\u308A\u66FF\u308F\u308A\u307E\u3059\u3002",
282
+ "settings.enterKey": "\u5165\u529B\u6B04\u306E Enter \u30AD\u30FC",
283
+ "settings.enterKeyHint": "\u30BF\u30C3\u30C1\u7AEF\u672B\u306E\u30B9\u30DE\u30DB\u8868\u793A\u3067\u3060\u3051\u52B9\u304D\u307E\u3059\u3002\u300C\u6539\u884C\u300D\u306E\u3068\u304D\u306F\u9001\u4FE1\u30DC\u30BF\u30F3\u3067\u9001\u308A\u307E\u3059\u3002\u5909\u63DB\u306E\u78BA\u5B9A\u3068\u30B3\u30DE\u30F3\u30C9\u30E1\u30CB\u30E5\u30FC\u3067\u306F\u5F93\u6765\u3069\u304A\u308A Enter \u304C\u52B9\u304D\u307E\u3059\u3002",
284
+ "settings.enterKey.newline": "\u6539\u884C",
285
+ "settings.enterKey.send": "\u9001\u4FE1",
286
+ "settings.autoFocusComposer": "\u5165\u529B\u6B04\u306B\u81EA\u52D5\u3067\u30D5\u30A9\u30FC\u30AB\u30B9\u3059\u308B",
287
+ "settings.autoFocusComposerHint": "\u5165\u529B\u6B04\u306E\u30DC\u30BF\u30F3\u3084 Harness \u306E\u307B\u304B\u306E\u64CD\u4F5C\u3067\u5165\u529B\u6B04\u306B\u30D5\u30A9\u30FC\u30AB\u30B9\u304C\u79FB\u3063\u305F\u3068\u304D\u3001\u305D\u306E\u307E\u307E\u30D5\u30A9\u30FC\u30AB\u30B9\u3057\u3066\u30AD\u30FC\u30DC\u30FC\u30C9\u3092\u958B\u304D\u307E\u3059\u3002\u30AA\u30D5\u306B\u3059\u308B\u3068\u3001\u5165\u529B\u6B04\u3092\u30BF\u30C3\u30D7\u3059\u308B\u307E\u3067\u30AD\u30FC\u30DC\u30FC\u30C9\u306F\u958B\u304D\u307E\u305B\u3093\u3002",
288
+ "settings.swipe": "\u30B9\u30EF\u30A4\u30D7\u3067\u30B5\u30A4\u30C9\u30D0\u30FC\u3092\u958B\u304F",
289
+ "settings.swipeHint": "\u5DE6\u7AEF\u304B\u3089\u53F3\u3078\u30B9\u30EF\u30A4\u30D7\u3067 Harness \u306E\u30CA\u30D3\u30B2\u30FC\u30B7\u30E7\u30F3\u30C9\u30ED\u30EF\u30FC\u3092\u958B\u304D\u3001\u5DE6\u3078\u30B9\u30EF\u30A4\u30D7\u3067\u9589\u3058\u307E\u3059\u3002",
290
+ "settings.fitKeyboard": "\u30AD\u30FC\u30DC\u30FC\u30C9\u306B\u5408\u308F\u305B\u3066\u30DA\u30FC\u30B8\u3092\u7E2E\u3081\u308B",
291
+ "settings.fitKeyboardHint": "\u30AA\u30F3\u30B9\u30AF\u30EA\u30FC\u30F3\u30AD\u30FC\u30DC\u30FC\u30C9\u306E\u8868\u793A\u4E2D\u3082\u3001\u30D8\u30C3\u30C0\u30FC\u3068\u5165\u529B\u6B04\u3092\u753B\u9762\u5185\u306B\u6B8B\u3057\u307E\u3059\u3002",
292
+ "settings.blockPullToRefresh": "\u5F15\u3063\u5F35\u3063\u3066\u66F4\u65B0\u3092\u6B62\u3081\u308B",
293
+ "settings.blockPullToRefreshHint": "\u9577\u3044\u4F1A\u8A71\u306E\u5148\u982D\u3067\u3055\u3089\u306B\u4E0A\u3078\u30B9\u30AF\u30ED\u30FC\u30EB\u3057\u3066\u3082\u518D\u8AAD\u307F\u8FBC\u307F\u3057\u307E\u305B\u3093\u3002",
294
+ "settings.reset": "\u65E2\u5B9A\u5024\u306B\u623B\u3059",
295
+ "settings.unavailable": "\u3053\u306E\u8A2D\u5B9A\u306F\u5229\u7528\u3067\u304D\u307E\u305B\u3093\u3002",
296
+ "settings.readOnly": "\u4E0A\u4F4D\u306E\u8A2D\u5B9A\u30EC\u30A4\u30E4\u30FC\u304C\u5024\u3092\u6C7A\u3081\u3066\u3044\u308B\u305F\u3081\u3001\u8AAD\u307F\u53D6\u308A\u5C02\u7528\u3067\u3059\u3002",
297
+ "settings.writeError": "Host \u304C\u5909\u66F4\u3092\u62D2\u5426\u3057\u307E\u3057\u305F\u3002"
298
+ };
299
+
300
+ // src/client/page.ts
301
+ var TOUCH_QUERY = "(hover: none) and (pointer: coarse)";
302
+ function readPageState(drawer, touch) {
303
+ return { mobile: drawer, touch };
304
+ }
305
+ function mediaSource(matchMedia, query) {
306
+ const media = matchMedia(query);
307
+ return {
308
+ get: () => media.matches,
309
+ subscribe(listener) {
310
+ media.addEventListener("change", listener);
311
+ return () => {
312
+ media.removeEventListener("change", listener);
313
+ };
314
+ }
315
+ };
316
+ }
317
+ function createPageStore(signals) {
318
+ const listeners = /* @__PURE__ */ new Set();
319
+ let state = { mobile: false, touch: false };
320
+ const compute = () => {
321
+ const next = readPageState(signals.drawer.get(), signals.touch.get());
322
+ if (next.mobile === state.mobile && next.touch === state.touch) return;
323
+ state = next;
324
+ for (const listener of [...listeners]) listener();
325
+ };
326
+ const offDrawer = signals.drawer.subscribe(compute);
327
+ const offTouch = signals.touch.subscribe(compute);
328
+ compute();
329
+ return {
330
+ getSnapshot: () => state,
331
+ subscribe(listener) {
332
+ listeners.add(listener);
333
+ return () => {
334
+ listeners.delete(listener);
335
+ };
336
+ },
337
+ dispose() {
338
+ offDrawer();
339
+ offTouch();
340
+ signals.drawer.dispose?.();
341
+ signals.touch.dispose?.();
342
+ listeners.clear();
343
+ }
344
+ };
345
+ }
346
+
347
+ // src/client/SettingsPage.tsx
348
+ var import_react = require("react");
349
+ var import_jsx_runtime = require("react/jsx-runtime");
350
+ var FIELDS = Object.keys(DEFAULT_CONFIG);
351
+ var ENTER_KEYS2 = ["newline", "send"];
352
+ var sectionStyle = {
353
+ maxWidth: 680,
354
+ padding: "4px 0 32px",
355
+ color: "var(--dsw-alias-label-primary)"
356
+ };
357
+ var headingStyle = { margin: 0, fontSize: 18, fontWeight: 600, lineHeight: 1.4 };
358
+ var introStyle = {
359
+ margin: "8px 0 0",
360
+ color: "var(--dsw-alias-label-secondary)",
361
+ fontSize: 13,
362
+ lineHeight: 1.6
363
+ };
364
+ var statusStyle = {
365
+ margin: "12px 0 0",
366
+ padding: "8px 10px",
367
+ borderRadius: 8,
368
+ background: "var(--dsw-alias-interactive-bg-hover)",
369
+ color: "var(--dsw-alias-label-secondary)",
370
+ fontSize: 12,
371
+ lineHeight: 1.5
372
+ };
373
+ var fieldStyle = {
374
+ display: "flex",
375
+ flexDirection: "column",
376
+ gap: 8,
377
+ marginTop: 22
378
+ };
379
+ var rowStyle = {
380
+ display: "flex",
381
+ alignItems: "center",
382
+ justifyContent: "space-between",
383
+ gap: 16,
384
+ marginTop: 20
385
+ };
386
+ var labelStyle = { fontSize: 13, fontWeight: 600 };
387
+ var hintStyle = {
388
+ margin: 0,
389
+ color: "var(--dsw-alias-label-tertiary)",
390
+ fontSize: 12,
391
+ lineHeight: 1.5,
392
+ fontWeight: 400
393
+ };
394
+ var errorStyle = {
395
+ margin: 0,
396
+ color: "var(--dsw-alias-state-error-primary)",
397
+ fontSize: 12,
398
+ lineHeight: 1.5
399
+ };
400
+ var groupStyle = {
401
+ margin: "26px 0 0",
402
+ paddingTop: 4,
403
+ borderTop: "1px solid var(--dsw-alias-border-l2)"
404
+ };
405
+ var buttonStyle = {
406
+ marginTop: 26,
407
+ padding: "8px 12px",
408
+ borderRadius: 8,
409
+ border: "1px solid var(--dsw-alias-border-l2)",
410
+ background: "var(--dsw-alias-bg-layer-1)",
411
+ color: "inherit",
412
+ font: "inherit",
413
+ cursor: "pointer"
414
+ };
415
+ var mutedStyle = {
416
+ color: "var(--dsw-alias-label-tertiary)",
417
+ fontSize: 13,
418
+ lineHeight: 1.6
419
+ };
420
+ function useObservable(observable) {
421
+ const subscribe = (0, import_react.useCallback)(
422
+ (listener) => observable.subscribe(listener),
423
+ [observable]
424
+ );
425
+ const getSnapshot = (0, import_react.useCallback)(() => observable.getSnapshot(), [observable]);
426
+ return (0, import_react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
427
+ }
428
+ function subscribeResize(listener) {
429
+ window.addEventListener("resize", listener);
430
+ return () => window.removeEventListener("resize", listener);
431
+ }
432
+ function useViewportWidth() {
433
+ return (0, import_react.useSyncExternalStore)(
434
+ subscribeResize,
435
+ () => Math.round(window.innerWidth),
436
+ () => 0
437
+ );
438
+ }
439
+ function SettingsPage({ form, page, t }) {
440
+ const subscribe = (0, import_react.useCallback)((listener) => form.subscribe(listener), [form]);
441
+ const getSnapshot = (0, import_react.useCallback)(() => form.getSnapshot(), [form]);
442
+ const snapshot = (0, import_react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
443
+ const config = normalizeConfig(snapshot.value);
444
+ const state = useObservable(page);
445
+ const width = useViewportWidth();
446
+ const [writeFailed, setWriteFailed] = (0, import_react.useState)(false);
447
+ const write = (field, value) => {
448
+ void form.set(field, value).then((accepted) => setWriteFailed(!accepted)).catch(() => setWriteFailed(true));
449
+ };
450
+ const reset = () => {
451
+ void Promise.all(FIELDS.map((field) => form.unset(field))).then((results) => setWriteFailed(results.some((accepted) => !accepted))).catch(() => setWriteFailed(true));
452
+ };
453
+ if (snapshot.status === "unavailable") {
454
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: mutedStyle, children: t("settings.unavailable") });
455
+ }
456
+ const disabled = snapshot.writable === false;
457
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: sectionStyle, className: "dsh-mobile-settings", children: [
458
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { style: headingStyle, children: t("settings.title") }),
459
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: introStyle, children: t("settings.description") }),
460
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: statusStyle, role: "status", children: state.mobile ? t("settings.statusMobile", { width }) : t("settings.statusDesktop", { width }) }),
461
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: fieldStyle, children: [
462
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: labelStyle, children: t("settings.enterKey") }),
463
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
464
+ Segmented,
465
+ {
466
+ label: t("settings.enterKey"),
467
+ options: ENTER_KEYS2.map((value) => ({
468
+ value,
469
+ label: t(`settings.enterKey.${value}`)
470
+ })),
471
+ value: config.enterKey,
472
+ disabled,
473
+ onChange: (next) => write("enterKey", next)
474
+ }
475
+ ),
476
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: hintStyle, children: t("settings.enterKeyHint") })
477
+ ] }),
478
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: groupStyle, children: [
479
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
480
+ Toggle,
481
+ {
482
+ label: t("settings.autoFocusComposer"),
483
+ hint: t("settings.autoFocusComposerHint"),
484
+ checked: config.autoFocusComposer,
485
+ disabled,
486
+ onChange: (next) => write("autoFocusComposer", next)
487
+ }
488
+ ),
489
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
490
+ Toggle,
491
+ {
492
+ label: t("settings.swipe"),
493
+ hint: t("settings.swipeHint"),
494
+ checked: config.swipe,
495
+ disabled,
496
+ onChange: (next) => write("swipe", next)
497
+ }
498
+ ),
499
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
500
+ Toggle,
501
+ {
502
+ label: t("settings.fitKeyboard"),
503
+ hint: t("settings.fitKeyboardHint"),
504
+ checked: config.fitKeyboard,
505
+ disabled,
506
+ onChange: (next) => write("fitKeyboard", next)
507
+ }
508
+ ),
509
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
510
+ Toggle,
511
+ {
512
+ label: t("settings.blockPullToRefresh"),
513
+ hint: t("settings.blockPullToRefreshHint"),
514
+ checked: config.blockPullToRefresh,
515
+ disabled,
516
+ onChange: (next) => write("blockPullToRefresh", next)
517
+ }
518
+ )
519
+ ] }),
520
+ disabled ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { ...hintStyle, marginTop: 20 }, children: t("settings.readOnly") }) : null,
521
+ writeFailed ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { ...errorStyle, marginTop: 20 }, children: t("settings.writeError") }) : null,
522
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", style: buttonStyle, disabled, onClick: reset, children: t("settings.reset") })
523
+ ] });
524
+ }
525
+ function Segmented({
526
+ label,
527
+ options,
528
+ value,
529
+ disabled,
530
+ onChange
531
+ }) {
532
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("fieldset", { className: "dsh-mobile-segmented", "aria-label": label, children: options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
533
+ "button",
534
+ {
535
+ type: "button",
536
+ "aria-pressed": option.value === value,
537
+ className: "dsh-mobile-segment",
538
+ disabled,
539
+ onClick: () => {
540
+ if (option.value !== value) onChange(option.value);
541
+ },
542
+ children: option.label
543
+ },
544
+ option.value
545
+ )) });
546
+ }
547
+ function Toggle({ label, hint, checked, disabled, onChange }) {
548
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: rowStyle, children: [
549
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { display: "flex", flexDirection: "column", gap: 4, minWidth: 0 }, children: [
550
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: labelStyle, children: label }),
551
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: hintStyle, children: hint })
552
+ ] }),
553
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
554
+ "button",
555
+ {
556
+ type: "button",
557
+ role: "switch",
558
+ "aria-checked": checked,
559
+ "aria-label": label,
560
+ className: "dsh-mobile-switch",
561
+ disabled,
562
+ onClick: (event) => {
563
+ event.stopPropagation();
564
+ onChange(!checked);
565
+ },
566
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-mobile-thumb" })
567
+ }
568
+ )
569
+ ] });
570
+ }
571
+
572
+ // src/client/styles.ts
573
+ var MOBILE_ATTRIBUTE = "data-dsh-mobile";
574
+ var TOUCH_ATTRIBUTE = "data-dsh-touch";
575
+ var NO_PULL_ATTRIBUTE = "data-dsh-no-pull";
576
+ var M = `html[${MOBILE_ATTRIBUTE}]`;
577
+ var T = `html[${TOUCH_ATTRIBUTE}]`;
578
+ var SETTINGS = `${M} [data-shortcut-modal="settings"][role="dialog"]`;
579
+ var MOBILE_CSS = `
580
+ .dsh-mobile-segmented {
581
+ box-sizing: border-box;
582
+ min-width: 0;
583
+ margin: 0;
584
+ border: 0;
585
+ display: inline-flex;
586
+ align-self: flex-start;
587
+ max-width: 100%;
588
+ gap: 2px;
589
+ padding: 2px;
590
+ border-radius: 8px;
591
+ background: var(--dsw-alias-interactive-bg-hover);
592
+ }
593
+ .dsh-mobile-segment {
594
+ box-sizing: border-box;
595
+ min-height: 30px;
596
+ padding: 0 14px;
597
+ border: 0;
598
+ border-radius: 6px;
599
+ background: transparent;
600
+ color: var(--dsw-alias-label-secondary);
601
+ font: inherit;
602
+ font-size: 13px;
603
+ white-space: nowrap;
604
+ cursor: pointer;
605
+ touch-action: manipulation;
606
+ }
607
+ .dsh-mobile-segment[aria-pressed="true"] {
608
+ background: var(--dsw-alias-bg-layer-1);
609
+ color: var(--dsw-alias-label-primary);
610
+ font-weight: 600;
611
+ box-shadow: 0 0 0 0.5px var(--dsw-alias-border-l2);
612
+ }
613
+ .dsh-mobile-segment:disabled {
614
+ cursor: default;
615
+ opacity: 0.5;
616
+ }
617
+ .dsh-mobile-segment:focus-visible,
618
+ .dsh-mobile-switch:focus-visible {
619
+ outline: var(--dsw-focus-ring-width) solid
620
+ var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));
621
+ outline-offset: 2px;
622
+ }
623
+ .dsh-mobile-switch {
624
+ box-sizing: border-box;
625
+ position: relative;
626
+ flex: 0 0 auto;
627
+ width: 36px;
628
+ height: 20px;
629
+ padding: 2px;
630
+ border: 0;
631
+ border-radius: 999px;
632
+ background: var(--dsw-alias-border-l3);
633
+ cursor: pointer;
634
+ touch-action: manipulation;
635
+ }
636
+ .dsh-mobile-switch[aria-checked="true"] {
637
+ background: var(--dsw-alias-brand-primary);
638
+ }
639
+ .dsh-mobile-switch:disabled {
640
+ cursor: default;
641
+ opacity: 0.5;
642
+ }
643
+ .dsh-mobile-thumb {
644
+ display: block;
645
+ width: 16px;
646
+ height: 16px;
647
+ border-radius: 50%;
648
+ background: var(--dsw-alias-label-primary-foreground);
649
+ transition: transform 0.12s ease;
650
+ }
651
+ .dsh-mobile-switch[aria-checked="false"] .dsh-mobile-thumb {
652
+ background: var(--dsw-alias-switch-thumb);
653
+ }
654
+ .dsh-mobile-switch[aria-checked="true"] .dsh-mobile-thumb {
655
+ transform: translate(16px);
656
+ }
657
+
658
+ ${M} body {
659
+ height: var(--dsh-mobile-vvh, 100dvh);
660
+ overflow: hidden;
661
+ }
662
+ ${M} #root {
663
+ box-sizing: border-box;
664
+ height: var(--dsh-mobile-vvh, 100dvh);
665
+ padding: env(safe-area-inset-top) env(safe-area-inset-right) 0 env(safe-area-inset-left);
666
+ }
667
+ html[${NO_PULL_ATTRIBUTE}],
668
+ html[${NO_PULL_ATTRIBUTE}] body {
669
+ overscroll-behavior: none;
670
+ }
671
+
672
+ ${M} [data-conversation-content] {
673
+ --dsh-composer-side-clearance: 8px;
674
+ }
675
+ ${M} div[data-sidebar-drawer] > :has(> [data-slot="sidebar"]) {
676
+ padding-bottom: env(safe-area-inset-bottom);
677
+ }
678
+ ${M} [data-composer-seat] {
679
+ --dsh-composer-text-max-height: min(336px, calc(var(--dsh-mobile-vvh, 100dvh) * 0.3));
680
+ padding-bottom: env(safe-area-inset-bottom);
681
+ }
682
+ ${M}[data-dsh-keyboard] [data-composer-seat] {
683
+ padding-bottom: 0;
684
+ }
685
+
686
+ ${SETTINGS} {
687
+ box-sizing: border-box;
688
+ flex-direction: column;
689
+ width: 100vw;
690
+ max-width: 100vw;
691
+ height: var(--dsh-mobile-vvh, 100dvh);
692
+ padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom)
693
+ env(safe-area-inset-left);
694
+ border-radius: 0;
695
+ }
696
+ ${SETTINGS} > nav {
697
+ width: auto;
698
+ gap: 10px;
699
+ padding: 14px 12px 4px;
700
+ }
701
+ ${SETTINGS} > nav > :first-child {
702
+ padding-right: 48px;
703
+ }
704
+ ${SETTINGS} > nav > :last-child {
705
+ flex-direction: row;
706
+ gap: 4px;
707
+ overflow-x: auto;
708
+ scrollbar-width: none;
709
+ }
710
+ ${SETTINGS} > nav > :last-child > button {
711
+ flex: none;
712
+ height: 36px;
713
+ }
714
+ ${SETTINGS} > :last-child {
715
+ min-height: 0;
716
+ }
717
+ ${SETTINGS} > :last-child > :first-child {
718
+ position: absolute;
719
+ top: calc(env(safe-area-inset-top) + 10px);
720
+ right: calc(env(safe-area-inset-right) + 10px);
721
+ height: auto;
722
+ padding: 0;
723
+ }
724
+ ${SETTINGS} > :last-child > :last-child {
725
+ padding: 8px 16px 16px;
726
+ }
727
+
728
+ ${T} {
729
+ -webkit-tap-highlight-color: transparent;
730
+ }
731
+ ${T} :is(button, a, [role="button"], [role="tab"], [role="treeitem"], [role="menuitem"], [role="option"]) {
732
+ touch-action: manipulation;
733
+ }
734
+ ${T} :is(
735
+ input:not([type="checkbox"], [type="radio"], [type="range"], [type="file"], [type="color"], [type="button"], [type="submit"]),
736
+ textarea,
737
+ select,
738
+ [data-composer-input],
739
+ [data-composer-placeholder]
740
+ ) {
741
+ font-size: max(16px, 1em);
742
+ }
743
+ ${T} [role="tooltip"] {
744
+ display: none !important;
745
+ }
746
+ ${T} [data-row-key^="session:"][aria-selected="true"] > [class*="_rowActions"] {
747
+ display: inline-flex;
748
+ }
749
+ ${T} [data-row-key^="session:"][aria-selected="true"] > :is([class*="_time"], [class*="_pinIndicator"]) {
750
+ display: none;
751
+ }
752
+ ${T} [data-row-key] > [class*="_rowActions"] {
753
+ gap: 4px;
754
+ }
755
+ ${T} [data-row-key^="session:"]:not([aria-selected="true"]):hover > [class*="_rowActions"],
756
+ ${T} [data-row-key^="workspace:"]:hover > [class*="_chevron"],
757
+ ${T} [class*="_searchResultRow"]:hover [class*="_rowActions"] {
758
+ display: none;
759
+ }
760
+ ${T} [data-row-key^="session:"]:not([aria-selected="true"]):hover > [class*="_time"] {
761
+ display: inline;
762
+ }
763
+ ${T} [data-row-key^="session:"]:not([aria-selected="true"]):hover > [class*="_pinIndicator"],
764
+ ${T} [data-row-key^="workspace:"]:hover > [class*="_folder"],
765
+ ${T} [data-row-key^="workspace:"] > [class*="_rowActions"] {
766
+ display: inline-flex;
767
+ }
768
+ ${T} [data-row-key] > [class*="_rowActions"] button {
769
+ width: 28px;
770
+ height: 28px;
771
+ }
772
+
773
+ @media (prefers-reduced-motion: reduce) {
774
+ .dsh-mobile-thumb {
775
+ transition: none;
776
+ }
777
+ }
778
+ `;
779
+
780
+ // src/client/swipe.ts
781
+ var DEFAULT_SWIPE = { edge: 24, distance: 56, slop: 10 };
782
+ function createSwipeRecognizer(options = DEFAULT_SWIPE) {
783
+ let gesture;
784
+ const lock = (x, y) => {
785
+ if (gesture === void 0 || gesture.axis !== void 0) return;
786
+ const dx = Math.abs(x - gesture.x);
787
+ const dy = Math.abs(y - gesture.y);
788
+ if (dx < options.slop && dy < options.slop) return;
789
+ gesture.axis = dx > dy * 1.2 ? "x" : "y";
790
+ };
791
+ return {
792
+ start(x, y, allowed) {
793
+ const open = allowed.open && x <= options.edge;
794
+ gesture = open || allowed.close ? { x, y, open, close: allowed.close, axis: void 0 } : void 0;
795
+ },
796
+ move(x, y) {
797
+ lock(x, y);
798
+ },
799
+ end(x, y) {
800
+ lock(x, y);
801
+ const current = gesture;
802
+ gesture = void 0;
803
+ if (current === void 0 || current.axis !== "x") return null;
804
+ const dx = x - current.x;
805
+ if (Math.abs(y - current.y) > Math.abs(dx)) return null;
806
+ if (current.open && dx >= options.distance) return "open";
807
+ if (current.close && dx <= -options.distance) return "close";
808
+ return null;
809
+ },
810
+ cancel() {
811
+ gesture = void 0;
812
+ }
813
+ };
814
+ }
815
+ function installSwipe(target, host, options = DEFAULT_SWIPE) {
816
+ const recognizer = createSwipeRecognizer(options);
817
+ const passive = { passive: true, capture: true };
818
+ const onStart = (event) => {
819
+ const touch = event.touches[0];
820
+ if (!host.enabled() || event.touches.length !== 1 || touch === void 0) {
821
+ recognizer.cancel();
822
+ return;
823
+ }
824
+ if (host.blocked(event.target)) {
825
+ recognizer.cancel();
826
+ return;
827
+ }
828
+ const expanded = host.expanded();
829
+ recognizer.start(touch.clientX, touch.clientY, { open: !expanded, close: expanded });
830
+ };
831
+ const onMove = (event) => {
832
+ const touch = event.touches[0];
833
+ if (event.touches.length !== 1 || touch === void 0) {
834
+ recognizer.cancel();
835
+ return;
836
+ }
837
+ recognizer.move(touch.clientX, touch.clientY);
838
+ };
839
+ const onEnd = (event) => {
840
+ const touch = event.changedTouches[0];
841
+ if (touch === void 0) {
842
+ recognizer.cancel();
843
+ return;
844
+ }
845
+ const action = recognizer.end(touch.clientX, touch.clientY);
846
+ if (action === null || !host.enabled()) return;
847
+ const expanded = host.expanded();
848
+ if (action === "open" && !expanded || action === "close" && expanded) host.toggle();
849
+ };
850
+ const onCancel = () => recognizer.cancel();
851
+ target.addEventListener("touchstart", onStart, passive);
852
+ target.addEventListener("touchmove", onMove, passive);
853
+ target.addEventListener("touchend", onEnd, passive);
854
+ target.addEventListener("touchcancel", onCancel, passive);
855
+ return () => {
856
+ target.removeEventListener("touchstart", onStart, passive);
857
+ target.removeEventListener("touchmove", onMove, passive);
858
+ target.removeEventListener("touchend", onEnd, passive);
859
+ target.removeEventListener("touchcancel", onCancel, passive);
860
+ };
861
+ }
862
+
863
+ // src/client/viewport.ts
864
+ var VIEWPORT_ADDITIONS = {
865
+ "viewport-fit": "cover",
866
+ "interactive-widget": "resizes-content"
867
+ };
868
+ var APP_META = [
869
+ ["apple-mobile-web-app-capable", "yes"],
870
+ ["mobile-web-app-capable", "yes"],
871
+ ["apple-mobile-web-app-title", "DSH"]
872
+ ];
873
+ function mergeViewportContent(content, additions) {
874
+ const entries = [];
875
+ for (const part of content.split(",")) {
876
+ const [rawKey, ...rest] = part.split("=");
877
+ const key = rawKey?.trim().toLowerCase() ?? "";
878
+ if (key.length === 0) continue;
879
+ entries.push([key, rest.join("=").trim()]);
880
+ }
881
+ for (const [key, value] of Object.entries(additions)) {
882
+ const found = entries.find((entry) => entry[0] === key);
883
+ if (found === void 0) entries.push([key, value]);
884
+ else found[1] = value;
885
+ }
886
+ return entries.map(([key, value]) => value.length > 0 ? `${key}=${value}` : key).join(", ");
887
+ }
888
+ var OWNER = "@1outres/dsh-plugin-mobile";
889
+ function installMeta(doc) {
890
+ const added = [];
891
+ let viewport = doc.querySelector('meta[name="viewport"]');
892
+ let original;
893
+ if (viewport === null) {
894
+ viewport = doc.createElement("meta");
895
+ viewport.name = "viewport";
896
+ viewport.content = mergeViewportContent(
897
+ "width=device-width, initial-scale=1",
898
+ VIEWPORT_ADDITIONS
899
+ );
900
+ viewport.dataset.plugin = OWNER;
901
+ doc.head.append(viewport);
902
+ added.push(viewport);
903
+ } else {
904
+ original = viewport.content;
905
+ viewport.content = mergeViewportContent(original, VIEWPORT_ADDITIONS);
906
+ }
907
+ for (const [name, content] of APP_META) {
908
+ if (doc.querySelector(`meta[name="${name}"]`) !== null) continue;
909
+ const tag = doc.createElement("meta");
910
+ tag.name = name;
911
+ tag.content = content;
912
+ tag.dataset.plugin = OWNER;
913
+ doc.head.append(tag);
914
+ added.push(tag);
915
+ }
916
+ const patched = viewport;
917
+ return () => {
918
+ if (original !== void 0) patched.content = original;
919
+ for (const tag of added) tag.remove();
920
+ };
921
+ }
922
+ var KEYBOARD_THRESHOLD = 120;
923
+ function measureViewport(viewport, baseline, innerHeight) {
924
+ if (Math.abs(viewport.scale - 1) > 0.01) {
925
+ return {
926
+ height: innerHeight,
927
+ keyboard: false,
928
+ baseline: baseline ?? { width: viewport.width, height: innerHeight }
929
+ };
930
+ }
931
+ const next = baseline === void 0 || Math.abs(baseline.width - viewport.width) > 1 ? { width: viewport.width, height: Math.max(viewport.height, innerHeight) } : { width: baseline.width, height: Math.max(baseline.height, viewport.height) };
932
+ return {
933
+ height: viewport.height,
934
+ keyboard: next.height - viewport.height > KEYBOARD_THRESHOLD,
935
+ baseline: next
936
+ };
937
+ }
938
+ var HEIGHT_VARIABLE = "--dsh-mobile-vvh";
939
+ var KEYBOARD_ATTRIBUTE = "data-dsh-keyboard";
940
+ function trackViewport(win, root) {
941
+ const viewport = win.visualViewport;
942
+ if (viewport === void 0 || viewport === null) return () => {
943
+ };
944
+ let baseline;
945
+ let frame;
946
+ const update = () => {
947
+ frame = void 0;
948
+ const measure = measureViewport(viewport, baseline, win.innerHeight);
949
+ baseline = measure.baseline;
950
+ root.style.setProperty(HEIGHT_VARIABLE, `${Math.round(measure.height)}px`);
951
+ root.toggleAttribute(KEYBOARD_ATTRIBUTE, measure.keyboard);
952
+ if (measure.keyboard && win.scrollY !== 0) win.scrollTo(0, 0);
953
+ };
954
+ const schedule = () => {
955
+ frame ?? (frame = win.requestAnimationFrame(update));
956
+ };
957
+ viewport.addEventListener("resize", schedule);
958
+ viewport.addEventListener("scroll", schedule);
959
+ update();
960
+ return () => {
961
+ viewport.removeEventListener("resize", schedule);
962
+ viewport.removeEventListener("scroll", schedule);
963
+ if (frame !== void 0) win.cancelAnimationFrame(frame);
964
+ root.style.removeProperty(HEIGHT_VARIABLE);
965
+ root.removeAttribute(KEYBOARD_ATTRIBUTE);
966
+ };
967
+ }
968
+
969
+ // src/client/index.tsx
970
+ var import_jsx_runtime2 = require("react/jsx-runtime");
971
+ var inject = ["slots", "locale", "configForms", "layout"];
972
+ var PLUGIN_ID = "@1outres/dsh-plugin-mobile";
973
+ var SETTINGS_SLOT = "settings.section";
974
+ function apply(ctx) {
975
+ ctx.effect(() => ctx.locale.register(NAMESPACE, { en, zh, ja }), "mobile: dictionaries");
976
+ const t = ctx.locale.bind(NAMESPACE);
977
+ ctx.effect(() => {
978
+ const tag = document.createElement("style");
979
+ tag.dataset.plugin = PLUGIN_ID;
980
+ tag.dataset.pluginCss = `${PLUGIN_ID}/mobile.css`;
981
+ tag.textContent = MOBILE_CSS;
982
+ document.head.append(tag);
983
+ return () => {
984
+ tag.remove();
985
+ };
986
+ }, "mobile: stylesheet");
987
+ ctx.effect(() => installMeta(document), "mobile: viewport metadata");
988
+ const form = ctx.configForms.get(NAMESPACE);
989
+ let config = normalizeConfig(form.getSnapshot().value);
990
+ const page = createPageStore({
991
+ drawer: createDrawerSource(document, observeMutations),
992
+ touch: mediaSource((query) => window.matchMedia(query), TOUCH_QUERY)
993
+ });
994
+ ctx.effect(() => () => page.dispose(), "mobile: page state");
995
+ ctx.effect(() => {
996
+ const root = document.documentElement;
997
+ let stopViewport;
998
+ const sync = () => {
999
+ const state = page.getSnapshot();
1000
+ root.toggleAttribute(MOBILE_ATTRIBUTE, state.mobile);
1001
+ root.toggleAttribute(TOUCH_ATTRIBUTE, state.touch);
1002
+ root.toggleAttribute(NO_PULL_ATTRIBUTE, state.mobile && config.blockPullToRefresh);
1003
+ const fit = state.mobile && config.fitKeyboard;
1004
+ if (fit && stopViewport === void 0) {
1005
+ stopViewport = trackViewport(window, root);
1006
+ } else if (!fit && stopViewport !== void 0) {
1007
+ stopViewport();
1008
+ stopViewport = void 0;
1009
+ }
1010
+ };
1011
+ const offPage = page.subscribe(sync);
1012
+ const offForm = form.subscribe(() => {
1013
+ config = normalizeConfig(form.getSnapshot().value);
1014
+ sync();
1015
+ });
1016
+ sync();
1017
+ return () => {
1018
+ offPage();
1019
+ offForm();
1020
+ stopViewport?.();
1021
+ root.removeAttribute(MOBILE_ATTRIBUTE);
1022
+ root.removeAttribute(TOUCH_ATTRIBUTE);
1023
+ root.removeAttribute(NO_PULL_ATTRIBUTE);
1024
+ };
1025
+ }, "mobile: page layout");
1026
+ const toggle = () => {
1027
+ try {
1028
+ ctx.layout.toggleSidebar();
1029
+ } catch (error) {
1030
+ console.warn("mobile: could not toggle the sidebar", error);
1031
+ }
1032
+ };
1033
+ ctx.effect(
1034
+ () => installEnterNewline(window, () => {
1035
+ const state = page.getSnapshot();
1036
+ return state.mobile && state.touch && config.enterKey === "newline";
1037
+ }),
1038
+ "mobile: Enter adds a line"
1039
+ );
1040
+ ctx.effect(
1041
+ () => installFocusGuard(document, () => {
1042
+ const state = page.getSnapshot();
1043
+ return state.mobile && state.touch && !config.autoFocusComposer;
1044
+ }),
1045
+ "mobile: keyboard stays closed until the composer is tapped"
1046
+ );
1047
+ ctx.effect(
1048
+ () => installSwipe(document, {
1049
+ enabled: () => page.getSnapshot().mobile && config.swipe,
1050
+ expanded: () => isExpanded(findFrame(document)),
1051
+ blocked: (target) => swipeBlocked(
1052
+ target,
1053
+ document,
1054
+ (element) => getComputedStyle(element).overflowX
1055
+ ),
1056
+ toggle
1057
+ }),
1058
+ "mobile: drawer swipe"
1059
+ );
1060
+ const Page = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SettingsPage, { form, page, t });
1061
+ ctx.effect(
1062
+ () => ctx.configForms.whileServed(
1063
+ [NAMESPACE],
1064
+ () => ctx.slots.inject(
1065
+ SETTINGS_SLOT,
1066
+ () => ctx.slots.register(
1067
+ {
1068
+ name: SETTINGS_SLOT,
1069
+ id: "mobile",
1070
+ order: 62,
1071
+ label: () => t("nav"),
1072
+ locale: NAMESPACE
1073
+ },
1074
+ Page
1075
+ )
1076
+ )
1077
+ ),
1078
+ "mobile: settings section"
1079
+ );
1080
+ }
1081
+
1082
+ return module.exports;
1083
+ }
1084
+ });