dsh-pocket 2.1.4 → 2.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/client/client.js CHANGED
@@ -1231,8 +1231,44 @@ var en = {
1231
1231
  "files": "Files"
1232
1232
  };
1233
1233
 
1234
+ // client/mobile/layout-mode.mjs
1235
+ function resolveLayout({ urlValue, stored, narrowMatch }) {
1236
+ const url = String(urlValue ?? "").trim();
1237
+ if (url === "desktop") return "desktop";
1238
+ if (url === "mobile") return "mobile";
1239
+ if (stored === "desktop" || stored === "mobile") return stored;
1240
+ return narrowMatch ? "mobile" : "desktop";
1241
+ }
1242
+ function persistLayoutFromUrl(urlValue) {
1243
+ if (typeof localStorage === "undefined") return "";
1244
+ const v = String(urlValue ?? "").trim();
1245
+ try {
1246
+ if (v === "desktop" || v === "mobile") localStorage.setItem("dsh-pocket.layout", v);
1247
+ else if (v === "auto" || v === "") localStorage.removeItem("dsh-pocket.layout");
1248
+ } catch {
1249
+ }
1250
+ try {
1251
+ const s = localStorage.getItem("dsh-pocket.layout");
1252
+ return s === "desktop" || s === "mobile" ? s : "";
1253
+ } catch {
1254
+ return "";
1255
+ }
1256
+ }
1257
+
1234
1258
  // client/mobile/mobile-apply.tsx
1235
1259
  function mobileApply(ctx) {
1260
+ const urlValue = new URL(window.location.href).searchParams.get("dsh-layout") ?? "";
1261
+ const narrowMQ = window.matchMedia("(max-width: 1023px)");
1262
+ const stored = persistLayoutFromUrl(urlValue);
1263
+ const layout = resolveLayout({ urlValue, stored, narrowMatch: narrowMQ.matches });
1264
+ document.body?.setAttribute("data-dsh-pocket-layout", layout);
1265
+ if (layout === "desktop") return;
1266
+ let narrow = narrowMQ;
1267
+ if (layout === "mobile") {
1268
+ narrow = { matches: true, addEventListener: () => {
1269
+ }, removeEventListener: () => {
1270
+ } };
1271
+ }
1236
1272
  ctx.effect(() => ctx.locale.register(NS, { zh, en }), "dsh-mobile-nav: dictionaries");
1237
1273
  ctx.effect(() => {
1238
1274
  const tag = document.createElement("style");
@@ -1245,7 +1281,6 @@ function mobileApply(ctx) {
1245
1281
  };
1246
1282
  }, "dsh-mobile-nav: styles");
1247
1283
  ctx.effect(() => {
1248
- const narrow = window.matchMedia("(max-width: 1023px)");
1249
1284
  const viewport = document.querySelector('meta[name="viewport"]');
1250
1285
  const originalViewport = viewport?.content ?? "";
1251
1286
  const themeMeta = document.createElement("meta");
@@ -1277,7 +1312,6 @@ function mobileApply(ctx) {
1277
1312
  };
1278
1313
  }, "dsh-mobile-nav: status bar theme + viewport + zoom guard");
1279
1314
  ctx.effect(() => {
1280
- const narrow = window.matchMedia("(max-width: 1023px)");
1281
1315
  if (!narrow.matches) return () => {
1282
1316
  };
1283
1317
  const onChevronClick = (event) => {
@@ -1289,7 +1323,6 @@ function mobileApply(ctx) {
1289
1323
  return () => document.removeEventListener("click", onChevronClick, true);
1290
1324
  }, "dsh-mobile-nav: aionui explorer close marker");
1291
1325
  ctx.effect(() => {
1292
- const narrow = window.matchMedia("(max-width: 1023px)");
1293
1326
  if (!narrow.matches) return () => {
1294
1327
  };
1295
1328
  const frame = () => document.querySelector('[data-mobile-nav="frame"]');
@@ -1307,7 +1340,6 @@ function mobileApply(ctx) {
1307
1340
  };
1308
1341
  }, "dsh-mobile-nav: explorer availability (issue #48)");
1309
1342
  ctx.effect(() => {
1310
- const narrow = window.matchMedia("(max-width: 1023px)");
1311
1343
  if (!narrow.matches) return () => {
1312
1344
  };
1313
1345
  const frame = () => document.querySelector('[data-mobile-nav="frame"]');
@@ -1332,7 +1364,6 @@ function mobileApply(ctx) {
1332
1364
  };
1333
1365
  }, "dsh-mobile-nav: preview sheet open marker");
1334
1366
  ctx.effect(() => {
1335
- const narrow = window.matchMedia("(max-width: 1023px)");
1336
1367
  if (!narrow.matches) return () => {
1337
1368
  };
1338
1369
  const moveTps = (stats) => {
@@ -1366,7 +1397,6 @@ function mobileApply(ctx) {
1366
1397
  };
1367
1398
  }, "dsh-mobile-nav: stats line marker");
1368
1399
  ctx.effect(() => {
1369
- const narrow = window.matchMedia("(max-width: 1023px)");
1370
1400
  if (!narrow.matches) return () => {
1371
1401
  };
1372
1402
  const cols = ["[data-aionui-explorer-col]", "[data-aionui-preview-col]"];
@@ -0,0 +1,46 @@
1
+ // 布局模式判定(issue #74:宽屏手机/Pad 可选电脑布局)
2
+ //
3
+ // 优先级:URL 参数 (?dsh-layout=desktop|mobile|auto) > localStorage > 默认 auto。
4
+ // - 'auto' 不写 localStorage,仅按 matchMedia 走;
5
+ // - 显式 'desktop' / 'mobile' 同步到 localStorage,下次无 URL 参数也走同样布局。
6
+ //
7
+ // 纯函数,便于单测;mobile-apply.tsx 读 DOM/localStorage 喂进来。
8
+
9
+ /** @typedef {'mobile' | 'desktop'} ForcedLayout */
10
+
11
+ /** 解析输入。 */
12
+ export function resolveLayout({ urlValue, stored, narrowMatch }) {
13
+ const url = String(urlValue ?? '').trim();
14
+ if (url === 'desktop') return 'desktop';
15
+ if (url === 'mobile') return 'mobile';
16
+ // 'auto' / 空 / 非法值 → 用 localStorage / matchMedia
17
+ if (stored === 'desktop' || stored === 'mobile') return stored;
18
+ return narrowMatch ? 'mobile' : 'desktop';
19
+ }
20
+
21
+ /** 同步 localStorage 的副作用(在 mobileApply 入口跑一次)。返回最终存储值。 */
22
+ export function persistLayoutFromUrl(urlValue) {
23
+ if (typeof localStorage === 'undefined') return '';
24
+ const v = String(urlValue ?? '').trim();
25
+ try {
26
+ if (v === 'desktop' || v === 'mobile') localStorage.setItem('dsh-pocket.layout', v);
27
+ else if (v === 'auto' || v === '') localStorage.removeItem('dsh-pocket.layout');
28
+ } catch { /* 隐私模式/无 storage → 静默 */ }
29
+ try {
30
+ const s = localStorage.getItem('dsh-pocket.layout');
31
+ return s === 'desktop' || s === 'mobile' ? s : '';
32
+ } catch {
33
+ return '';
34
+ }
35
+ }
36
+
37
+ /** 读 localStorage 当前的 layout 值('desktop' | 'mobile' | '')。 */
38
+ export function readStoredLayout() {
39
+ if (typeof localStorage === 'undefined') return '';
40
+ try {
41
+ const v = localStorage.getItem('dsh-pocket.layout');
42
+ return v === 'desktop' || v === 'mobile' ? v : '';
43
+ } catch {
44
+ return '';
45
+ }
46
+ }
@@ -6,6 +6,7 @@ import { MobileDrawerFooter } from './MobileDrawerFooter.tsx'
6
6
  import { MOBILE_CSS } from './mobile.css.ts'
7
7
  import { NS, en, zh } from './locales.ts'
8
8
  import type { MobileNavKey } from './locales.ts'
9
+ import { resolveLayout, persistLayoutFromUrl } from './layout-mode.mjs'
9
10
 
10
11
  declare module '@deepseek-ai/dsh-client-ui-slots' {
11
12
  interface LocaleNamespaceMap {
@@ -23,6 +24,22 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
23
24
  * @param ctx - client root context.
24
25
  */
25
26
  export function mobileApply(ctx): void {
27
+ // 布局模式(issue #74):URL 参数 > localStorage > auto(=matchMedia)。
28
+ // desktop 模式(宽屏手机/平板强制电脑布局)下整段 mobile 效果都不挂——
29
+ // 不加 styles、不挂 slots、不跑 effects,直接走 DSH 原生桌面 UI。
30
+ const urlValue = new URL(window.location.href).searchParams.get('dsh-layout') ?? '';
31
+ const narrowMQ = window.matchMedia('(max-width: 1023px)');
32
+ const stored = persistLayoutFromUrl(urlValue);
33
+ const layout = resolveLayout({ urlValue, stored, narrowMatch: narrowMQ.matches });
34
+ document.body?.setAttribute('data-dsh-pocket-layout', layout);
35
+ if (layout === 'desktop') return;
36
+ // 强制 mobile:narrow 永远 true;宽度变化不再切换(用户已显式选 mobile)
37
+ // auto 模式:narrow 是真实的 matchMedia,宽度变化会触发 effect 挂载/卸载
38
+ let narrow: MediaQueryList = narrowMQ;
39
+ if (layout === 'mobile') {
40
+ narrow = { matches: true, addEventListener: () => {}, removeEventListener: () => {} } as MediaQueryList;
41
+ }
42
+
26
43
  ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-mobile-nav: dictionaries')
27
44
 
28
45
  ctx.effect(() => {
@@ -52,7 +69,6 @@ export function mobileApply(ctx): void {
52
69
  // zoom; modern browsers are covered by the stylesheet's
53
70
  // touch-action: manipulation (which keeps pan and pinch zoom).
54
71
  ctx.effect(() => {
55
- const narrow = window.matchMedia('(max-width: 1023px)')
56
72
  const viewport = document.querySelector<HTMLMetaElement>('meta[name="viewport"]')
57
73
  const originalViewport = viewport?.content ?? ''
58
74
  const themeMeta = document.createElement('meta')
@@ -96,7 +112,6 @@ export function mobileApply(ctx): void {
96
112
  // sheet's own collapse chevron is tapped, so closing is symmetric with
97
113
  // opening.
98
114
  ctx.effect(() => {
99
- const narrow = window.matchMedia('(max-width: 1023px)')
100
115
  if (!narrow.matches) return () => {}
101
116
  const onChevronClick = (event: MouseEvent) => {
102
117
  const target = event.target as HTMLElement | null
@@ -115,7 +130,6 @@ export function mobileApply(ctx): void {
115
130
  // `data-mobile-nav-explorer="1|0"` so the stylesheet can hide the entries
116
131
  // on hosts without it (dsh-web-ui installs keep the feature).
117
132
  ctx.effect(() => {
118
- const narrow = window.matchMedia('(max-width: 1023px)')
119
133
  if (!narrow.matches) return () => {}
120
134
  const frame = (): HTMLElement | null => document.querySelector('[data-mobile-nav="frame"]')
121
135
  const check = () => {
@@ -141,7 +155,6 @@ export function mobileApply(ctx): void {
141
155
  // whenever the suite hides the column again (collapse chevron / tab
142
156
  // close), so a restored-but-unwanted sheet never appears.
143
157
  ctx.effect(() => {
144
- const narrow = window.matchMedia('(max-width: 1023px)')
145
158
  if (!narrow.matches) return () => {}
146
159
  const frame = (): HTMLElement | null => document.querySelector('[data-mobile-nav="frame"]')
147
160
  const onTap = (event: MouseEvent) => {
@@ -173,7 +186,6 @@ export function mobileApply(ctx): void {
173
186
  // marked row out as ONE horizontally scrolling line with every metric
174
187
  // reachable.
175
188
  ctx.effect(() => {
176
- const narrow = window.matchMedia('(max-width: 1023px)')
177
189
  if (!narrow.matches) return () => {}
178
190
  // The composer root renders the TPS readout ("TPS 89.4 tok/s") as its
179
191
  // own row BELOW the status strip; fold it into the strip so every
@@ -218,7 +230,6 @@ export function mobileApply(ctx): void {
218
230
  // with the Web Animations API each time a column turns visible, then
219
231
  // leave the resting state to the stylesheet.
220
232
  ctx.effect(() => {
221
- const narrow = window.matchMedia('(max-width: 1023px)')
222
233
  if (!narrow.matches) return () => {}
223
234
  const cols = ['[data-aionui-explorer-col]', '[data-aionui-preview-col]']
224
235
  const seen = new Map<string, boolean>()
package/package.json CHANGED
@@ -80,5 +80,5 @@
80
80
  "access": "public",
81
81
  "registry": "https://registry.npmjs.org/"
82
82
  },
83
- "version": "2.1.4"
83
+ "version": "2.2.0"
84
84
  }