dsh-qr-share 0.1.0 → 0.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/CHANGELOG.md CHANGED
@@ -7,6 +7,58 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.0] — 2026-08-30
11
+
12
+ ### Added
13
+
14
+ - **Test infrastructure** — vitest 2 + @testing-library/react, configured
15
+ for happy-dom. `pnpm test` runs 27 specs covering `config`,
16
+ `client/api` and `client/QrDialog`. `tsconfig.build.json` now
17
+ excludes `src/**/*.test.ts(x)` and `src/test/**` so the tests never
18
+ reach the published `.d.ts` output.
19
+ - **Ambient CSS module declaration** — `src/client/styles.module.css.d.ts`
20
+ gives `tsc` the default-export shape that `lightningcss` produces at
21
+ build time, unblocking `import styles from './styles.module.css'`
22
+ in `QrButton.tsx` and `QrDialog.tsx` without weakening the type
23
+ checker.
24
+
25
+ ### Changed
26
+
27
+ - **Dialog now renders into `document.body` via `createPortal`** —
28
+ the sidebar's `transform`/`filter` would otherwise trap
29
+ `position: fixed` inside the sidebar's box; portalling escapes every
30
+ ancestor's containing block so the backdrop covers the viewport and
31
+ the dialog is centered on screen.
32
+ - **Dialog surface is theme-aware and fully opaque** — background
33
+ resolves to the DSH shell's `--dsw-alias-bg-layer-1` token (alpha = 1),
34
+ with a `currentColor 12%` hairline border and a soft shadow. A
35
+ `--dsh-qr-share-dialog-bg` override hook is exposed for power users.
36
+ - **QR code canvas wrapped in a white card** — 8px padding + `#fff`
37
+ background guarantees the black modules stay scannable against
38
+ either light or dark DSH surface.
39
+ - **Sidebar action is now a labeled icon** — wide mode renders both
40
+ the `HiOutlineQrCode` icon and the `button.label` text; rail mode
41
+ keeps the icon only.
42
+ - **Copy localized** — `button.label` is now "分享" (zh) / "Share"
43
+ (en); `button.tooltip` and `dialog.title` were rewritten in the
44
+ same voice ("扫码把当前会话分享到手机" / "Scan to share"). The host
45
+ handshake's semantics are unchanged.
46
+
47
+ ### Fixed
48
+
49
+ - **QR canvas was blank on first render** — the `useEffect` that
50
+ calls `qrcode.toCanvas` ran once before the portal-mounted `<canvas>`
51
+ was in the DOM. Adding `portalTarget` to the effect's dependency
52
+ list gives the canvas a second pass after the portal resolves.
53
+ - **CSS Module class names were hardcoded** — `QrDialog.tsx` and
54
+ `QrButton.tsx` used the literal `'dsh-qr-share-backdrop'`
55
+ strings, but tsdown's `lightningcss` compiles them under the
56
+ `[hash]_[local]` pattern (`i2jIGq_backdrop`). The two forms never
57
+ matched, so the dialog had no styles at all and fell back to
58
+ `position: static`. Both files now consume the `styles.*` import
59
+ map; the bundle ships the hashed class names in the inline
60
+ `<style data-plugin>` tag.
61
+
10
62
  ## [0.1.0] — 2026-08-29
11
63
 
12
64
  ### Added
package/README.md CHANGED
@@ -99,12 +99,24 @@ layout needs a symlink — see `manual` install below).
99
99
  ## Build
100
100
 
101
101
  ```bash
102
+ # Recommended: pnpm (matches the project lockfile-less layout)
102
103
  pnpm install
103
104
  pnpm build # tsc declarations + tsdown bundles
104
105
  pnpm typecheck # strict check
105
106
  pnpm pack # produce dsh-qr-share-<version>.tgz (used by the install commands above)
107
+
108
+ # Also supported: npm (any Node >= 20, no extra install)
109
+ npm install
110
+ npm run build # same tsc + tsdown pipeline
111
+ npm run typecheck
112
+ npm pack
106
113
  ```
107
114
 
115
+ > For npm publishing we ship [`./npm_publish.sh`](npm_publish.sh) — a
116
+ > single-file mirror of the [core_ts](https://github.com/xiaoguomeiyitian/core_ts)
117
+ > release flow (`npm login` → `npm install` → `npm run build` →
118
+ > `npm pack --dry-run` review → `npm publish --access public`).
119
+
108
120
  Outputs:
109
121
 
110
122
  - `lib/index.js` — Node host half (the `/_qr/share` route)
package/README.zh.md CHANGED
@@ -74,12 +74,21 @@ pnpm add dsh-qr-share
74
74
  ## 构建
75
75
 
76
76
  ```bash
77
+ # 推荐:pnpm(与项目无 lockfile 的设计一致)
77
78
  pnpm install
78
79
  pnpm build # tsc 声明 + tsdown bundle
79
80
  pnpm typecheck # 严格检查
80
81
  pnpm pack # 产出 dsh-qr-share-<version>.tgz
82
+
83
+ # 也支持:npm(任意 Node >= 20, 无需额外安装)
84
+ npm install
85
+ npm run build # 同样的 tsc + tsdown 管线
86
+ npm run typecheck
87
+ npm pack
81
88
  ```
82
89
 
90
+ > 发版用 [`./npm_publish.sh`](npm_publish.sh)——单文件脚本,照搬 [core_ts](https://github.com/xiaoguomeiyitian/core_ts) 的发布流程(`npm login` → `npm install` → `npm run build` → `npm pack --dry-run` 预检 → `npm publish --access public`)。
91
+
83
92
  产物:
84
93
 
85
94
  - `lib/index.js` — Node 宿主半(`/_qr/share` 路由)
@@ -29,7 +29,8 @@ window.__ModuleLoader__.load({
29
29
  //#endregion
30
30
  let react = require("react");
31
31
  react = __toESM(react, 1);
32
- //#region node_modules/.pnpm/react-icons@5.7.0_react@18.3.0/node_modules/react-icons/lib/iconContext.mjs
32
+ let react_dom = require("react-dom");
33
+ //#region node_modules/react-icons/lib/iconContext.mjs
33
34
  var DefaultContext = {
34
35
  color: void 0,
35
36
  size: void 0,
@@ -39,7 +40,7 @@ window.__ModuleLoader__.load({
39
40
  };
40
41
  var IconContext = react.default.createContext && /*#__PURE__*/ react.default.createContext(DefaultContext);
41
42
  //#endregion
42
- //#region node_modules/.pnpm/react-icons@5.7.0_react@18.3.0/node_modules/react-icons/lib/iconBase.mjs
43
+ //#region node_modules/react-icons/lib/iconBase.mjs
43
44
  var _excluded = [
44
45
  "attr",
45
46
  "size",
@@ -143,7 +144,7 @@ window.__ModuleLoader__.load({
143
144
  return IconContext !== void 0 ? /*#__PURE__*/ react.default.createElement(IconContext.Consumer, null, (conf) => elem(conf)) : elem(DefaultContext);
144
145
  }
145
146
  //#endregion
146
- //#region node_modules/.pnpm/react-icons@5.7.0_react@18.3.0/node_modules/react-icons/hi2/index.mjs
147
+ //#region node_modules/react-icons/hi2/index.mjs
147
148
  function HiOutlineQrCode(props) {
148
149
  return GenIcon({
149
150
  "tag": "svg",
@@ -174,14 +175,14 @@ window.__ModuleLoader__.load({
174
175
  })(props);
175
176
  }
176
177
  //#endregion
177
- //#region node_modules/.pnpm/qrcode@1.5.4/node_modules/qrcode/lib/can-promise.js
178
+ //#region node_modules/qrcode/lib/can-promise.js
178
179
  var require_can_promise = /* @__PURE__ */ __commonJSMin(((exports, module) => {
179
180
  module.exports = function() {
180
181
  return typeof Promise === "function" && Promise.prototype && Promise.prototype.then;
181
182
  };
182
183
  }));
183
184
  //#endregion
184
- //#region node_modules/.pnpm/qrcode@1.5.4/node_modules/qrcode/lib/core/utils.js
185
+ //#region node_modules/qrcode/lib/core/utils.js
185
186
  var require_utils$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
186
187
  let toSJISFunction;
187
188
  const CODEWORDS_COUNT = [
@@ -273,7 +274,7 @@ window.__ModuleLoader__.load({
273
274
  };
274
275
  }));
275
276
  //#endregion
276
- //#region node_modules/.pnpm/qrcode@1.5.4/node_modules/qrcode/lib/core/error-correction-level.js
277
+ //#region node_modules/qrcode/lib/core/error-correction-level.js
277
278
  var require_error_correction_level = /* @__PURE__ */ __commonJSMin(((exports) => {
278
279
  exports.L = { bit: 1 };
279
280
  exports.M = { bit: 0 };
@@ -306,7 +307,7 @@ window.__ModuleLoader__.load({
306
307
  };
307
308
  }));
308
309
  //#endregion
309
- //#region node_modules/.pnpm/qrcode@1.5.4/node_modules/qrcode/lib/core/bit-buffer.js
310
+ //#region node_modules/qrcode/lib/core/bit-buffer.js
310
311
  var require_bit_buffer = /* @__PURE__ */ __commonJSMin(((exports, module) => {
311
312
  function BitBuffer() {
312
313
  this.buffer = [];
@@ -333,7 +334,7 @@ window.__ModuleLoader__.load({
333
334
  module.exports = BitBuffer;
334
335
  }));
335
336
  //#endregion
336
- //#region node_modules/.pnpm/qrcode@1.5.4/node_modules/qrcode/lib/core/bit-matrix.js
337
+ //#region node_modules/qrcode/lib/core/bit-matrix.js
337
338
  var require_bit_matrix = /* @__PURE__ */ __commonJSMin(((exports, module) => {
338
339
  /**
339
340
  * Helper class to handle QR Code symbol modules
@@ -394,7 +395,7 @@ window.__ModuleLoader__.load({
394
395
  module.exports = BitMatrix;
395
396
  }));
396
397
  //#endregion
397
- //#region node_modules/.pnpm/qrcode@1.5.4/node_modules/qrcode/lib/core/alignment-pattern.js
398
+ //#region node_modules/qrcode/lib/core/alignment-pattern.js
398
399
  var require_alignment_pattern = /* @__PURE__ */ __commonJSMin(((exports) => {
399
400
  /**
400
401
  * Alignment pattern are fixed reference pattern in defined positions
@@ -462,7 +463,7 @@ window.__ModuleLoader__.load({
462
463
  };
463
464
  }));
464
465
  //#endregion
465
- //#region node_modules/.pnpm/qrcode@1.5.4/node_modules/qrcode/lib/core/finder-pattern.js
466
+ //#region node_modules/qrcode/lib/core/finder-pattern.js
466
467
  var require_finder_pattern = /* @__PURE__ */ __commonJSMin(((exports) => {
467
468
  const getSymbolSize = require_utils$1().getSymbolSize;
468
469
  const FINDER_PATTERN_SIZE = 7;
@@ -483,7 +484,7 @@ window.__ModuleLoader__.load({
483
484
  };
484
485
  }));
485
486
  //#endregion
486
- //#region node_modules/.pnpm/qrcode@1.5.4/node_modules/qrcode/lib/core/mask-pattern.js
487
+ //#region node_modules/qrcode/lib/core/mask-pattern.js
487
488
  var require_mask_pattern = /* @__PURE__ */ __commonJSMin(((exports) => {
488
489
  /**
489
490
  * Data mask pattern reference
@@ -674,7 +675,7 @@ window.__ModuleLoader__.load({
674
675
  };
675
676
  }));
676
677
  //#endregion
677
- //#region node_modules/.pnpm/qrcode@1.5.4/node_modules/qrcode/lib/core/error-correction-code.js
678
+ //#region node_modules/qrcode/lib/core/error-correction-code.js
678
679
  var require_error_correction_code = /* @__PURE__ */ __commonJSMin(((exports) => {
679
680
  const ECLevel = require_error_correction_level();
680
681
  const EC_BLOCKS_TABLE = [
@@ -1037,7 +1038,7 @@ window.__ModuleLoader__.load({
1037
1038
  };
1038
1039
  }));
1039
1040
  //#endregion
1040
- //#region node_modules/.pnpm/qrcode@1.5.4/node_modules/qrcode/lib/core/galois-field.js
1041
+ //#region node_modules/qrcode/lib/core/galois-field.js
1041
1042
  var require_galois_field = /* @__PURE__ */ __commonJSMin(((exports) => {
1042
1043
  const EXP_TABLE = /* @__PURE__ */ new Uint8Array(512);
1043
1044
  const LOG_TABLE = /* @__PURE__ */ new Uint8Array(256);
@@ -1083,7 +1084,7 @@ window.__ModuleLoader__.load({
1083
1084
  };
1084
1085
  }));
1085
1086
  //#endregion
1086
- //#region node_modules/.pnpm/qrcode@1.5.4/node_modules/qrcode/lib/core/polynomial.js
1087
+ //#region node_modules/qrcode/lib/core/polynomial.js
1087
1088
  var require_polynomial = /* @__PURE__ */ __commonJSMin(((exports) => {
1088
1089
  const GF = require_galois_field();
1089
1090
  /**
@@ -1130,7 +1131,7 @@ window.__ModuleLoader__.load({
1130
1131
  };
1131
1132
  }));
1132
1133
  //#endregion
1133
- //#region node_modules/.pnpm/qrcode@1.5.4/node_modules/qrcode/lib/core/reed-solomon-encoder.js
1134
+ //#region node_modules/qrcode/lib/core/reed-solomon-encoder.js
1134
1135
  var require_reed_solomon_encoder = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1135
1136
  const Polynomial = require_polynomial();
1136
1137
  function ReedSolomonEncoder(degree) {
@@ -1170,7 +1171,7 @@ window.__ModuleLoader__.load({
1170
1171
  module.exports = ReedSolomonEncoder;
1171
1172
  }));
1172
1173
  //#endregion
1173
- //#region node_modules/.pnpm/qrcode@1.5.4/node_modules/qrcode/lib/core/version-check.js
1174
+ //#region node_modules/qrcode/lib/core/version-check.js
1174
1175
  var require_version_check = /* @__PURE__ */ __commonJSMin(((exports) => {
1175
1176
  /**
1176
1177
  * Check if QR Code version is valid
@@ -1183,7 +1184,7 @@ window.__ModuleLoader__.load({
1183
1184
  };
1184
1185
  }));
1185
1186
  //#endregion
1186
- //#region node_modules/.pnpm/qrcode@1.5.4/node_modules/qrcode/lib/core/regex.js
1187
+ //#region node_modules/qrcode/lib/core/regex.js
1187
1188
  var require_regex = /* @__PURE__ */ __commonJSMin(((exports) => {
1188
1189
  const numeric = "[0-9]+";
1189
1190
  const alphanumeric = "[A-Z $%*+\\-./:]+";
@@ -1209,7 +1210,7 @@ window.__ModuleLoader__.load({
1209
1210
  };
1210
1211
  }));
1211
1212
  //#endregion
1212
- //#region node_modules/.pnpm/qrcode@1.5.4/node_modules/qrcode/lib/core/mode.js
1213
+ //#region node_modules/qrcode/lib/core/mode.js
1213
1214
  var require_mode = /* @__PURE__ */ __commonJSMin(((exports) => {
1214
1215
  const VersionCheck = require_version_check();
1215
1216
  const Regex = require_regex();
@@ -1366,7 +1367,7 @@ window.__ModuleLoader__.load({
1366
1367
  };
1367
1368
  }));
1368
1369
  //#endregion
1369
- //#region node_modules/.pnpm/qrcode@1.5.4/node_modules/qrcode/lib/core/version.js
1370
+ //#region node_modules/qrcode/lib/core/version.js
1370
1371
  var require_version = /* @__PURE__ */ __commonJSMin(((exports) => {
1371
1372
  const Utils = require_utils$1();
1372
1373
  const ECCode = require_error_correction_code();
@@ -1463,7 +1464,7 @@ window.__ModuleLoader__.load({
1463
1464
  };
1464
1465
  }));
1465
1466
  //#endregion
1466
- //#region node_modules/.pnpm/qrcode@1.5.4/node_modules/qrcode/lib/core/format-info.js
1467
+ //#region node_modules/qrcode/lib/core/format-info.js
1467
1468
  var require_format_info = /* @__PURE__ */ __commonJSMin(((exports) => {
1468
1469
  const Utils = require_utils$1();
1469
1470
  const G15 = 1335;
@@ -1487,7 +1488,7 @@ window.__ModuleLoader__.load({
1487
1488
  };
1488
1489
  }));
1489
1490
  //#endregion
1490
- //#region node_modules/.pnpm/qrcode@1.5.4/node_modules/qrcode/lib/core/numeric-data.js
1491
+ //#region node_modules/qrcode/lib/core/numeric-data.js
1491
1492
  var require_numeric_data = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1492
1493
  const Mode = require_mode();
1493
1494
  function NumericData(data) {
@@ -1520,7 +1521,7 @@ window.__ModuleLoader__.load({
1520
1521
  module.exports = NumericData;
1521
1522
  }));
1522
1523
  //#endregion
1523
- //#region node_modules/.pnpm/qrcode@1.5.4/node_modules/qrcode/lib/core/alphanumeric-data.js
1524
+ //#region node_modules/qrcode/lib/core/alphanumeric-data.js
1524
1525
  var require_alphanumeric_data = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1525
1526
  const Mode = require_mode();
1526
1527
  /**
@@ -1604,7 +1605,7 @@ window.__ModuleLoader__.load({
1604
1605
  module.exports = AlphanumericData;
1605
1606
  }));
1606
1607
  //#endregion
1607
- //#region node_modules/.pnpm/qrcode@1.5.4/node_modules/qrcode/lib/core/byte-data.js
1608
+ //#region node_modules/qrcode/lib/core/byte-data.js
1608
1609
  var require_byte_data = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1609
1610
  const Mode = require_mode();
1610
1611
  function ByteData(data) {
@@ -1627,7 +1628,7 @@ window.__ModuleLoader__.load({
1627
1628
  module.exports = ByteData;
1628
1629
  }));
1629
1630
  //#endregion
1630
- //#region node_modules/.pnpm/qrcode@1.5.4/node_modules/qrcode/lib/core/kanji-data.js
1631
+ //#region node_modules/qrcode/lib/core/kanji-data.js
1631
1632
  var require_kanji_data = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1632
1633
  const Mode = require_mode();
1633
1634
  const Utils = require_utils$1();
@@ -1658,7 +1659,7 @@ window.__ModuleLoader__.load({
1658
1659
  module.exports = KanjiData;
1659
1660
  }));
1660
1661
  //#endregion
1661
- //#region node_modules/.pnpm/dijkstrajs@1.0.3/node_modules/dijkstrajs/dijkstra.js
1662
+ //#region node_modules/dijkstrajs/dijkstra.js
1662
1663
  var require_dijkstra = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1663
1664
  /******************************************************************************
1664
1665
  * Created 2008-08-19.
@@ -1774,7 +1775,7 @@ window.__ModuleLoader__.load({
1774
1775
  if (typeof module !== "undefined") module.exports = dijkstra;
1775
1776
  }));
1776
1777
  //#endregion
1777
- //#region node_modules/.pnpm/qrcode@1.5.4/node_modules/qrcode/lib/core/segments.js
1778
+ //#region node_modules/qrcode/lib/core/segments.js
1778
1779
  var require_segments = /* @__PURE__ */ __commonJSMin(((exports) => {
1779
1780
  const Mode = require_mode();
1780
1781
  const NumericData = require_numeric_data();
@@ -2053,7 +2054,7 @@ window.__ModuleLoader__.load({
2053
2054
  };
2054
2055
  }));
2055
2056
  //#endregion
2056
- //#region node_modules/.pnpm/qrcode@1.5.4/node_modules/qrcode/lib/core/qrcode.js
2057
+ //#region node_modules/qrcode/lib/core/qrcode.js
2057
2058
  var require_qrcode = /* @__PURE__ */ __commonJSMin(((exports) => {
2058
2059
  const Utils = require_utils$1();
2059
2060
  const ECLevel = require_error_correction_level();
@@ -2349,7 +2350,7 @@ window.__ModuleLoader__.load({
2349
2350
  };
2350
2351
  }));
2351
2352
  //#endregion
2352
- //#region node_modules/.pnpm/qrcode@1.5.4/node_modules/qrcode/lib/renderer/utils.js
2353
+ //#region node_modules/qrcode/lib/renderer/utils.js
2353
2354
  var require_utils = /* @__PURE__ */ __commonJSMin(((exports) => {
2354
2355
  function hex2rgba(hex) {
2355
2356
  if (typeof hex === "number") hex = hex.toString();
@@ -2417,7 +2418,7 @@ window.__ModuleLoader__.load({
2417
2418
  };
2418
2419
  }));
2419
2420
  //#endregion
2420
- //#region node_modules/.pnpm/qrcode@1.5.4/node_modules/qrcode/lib/renderer/canvas.js
2421
+ //#region node_modules/qrcode/lib/renderer/canvas.js
2421
2422
  var require_canvas = /* @__PURE__ */ __commonJSMin(((exports) => {
2422
2423
  const Utils = require_utils();
2423
2424
  function clearCanvas(ctx, canvas, size) {
@@ -2466,7 +2467,7 @@ window.__ModuleLoader__.load({
2466
2467
  };
2467
2468
  }));
2468
2469
  //#endregion
2469
- //#region node_modules/.pnpm/qrcode@1.5.4/node_modules/qrcode/lib/renderer/svg-tag.js
2470
+ //#region node_modules/qrcode/lib/renderer/svg-tag.js
2470
2471
  var require_svg_tag = /* @__PURE__ */ __commonJSMin(((exports) => {
2471
2472
  const Utils = require_utils();
2472
2473
  function getColorAttrib(color, attrib) {
@@ -2583,24 +2584,51 @@ window.__ModuleLoader__.load({
2583
2584
  */
2584
2585
  const LOCALE_NS = "qr-share";
2585
2586
  const zh = {
2586
- "button.label": "扫码登录",
2587
- "button.tooltip": "用手机扫码以同一账号登录",
2588
- "dialog.title": "用手机扫码登录",
2587
+ "button.label": "分享",
2588
+ "button.tooltip": "扫码把当前会话分享到手机",
2589
+ "dialog.title": "扫码分享",
2589
2590
  "dialog.hint": "用手机相机扫描下方二维码。手机打开后会自动以当前账号登录,token 与浏览器 cookie 同寿命。",
2590
2591
  "dialog.urlLabel": "链接",
2591
2592
  "dialog.close": "关闭",
2592
2593
  "dialog.error": "二维码生成失败"
2593
2594
  };
2594
2595
  const en = {
2595
- "button.label": "Mobile Login",
2596
- "button.tooltip": "Scan with a phone to log in to this session",
2597
- "dialog.title": "Scan to log in on mobile",
2596
+ "button.label": "Share",
2597
+ "button.tooltip": "Scan to share this session with your phone",
2598
+ "dialog.title": "Scan to share",
2598
2599
  "dialog.hint": "Scan the QR code below with your phone camera. The phone will log in to this session; the token lives as long as the browser cookie.",
2599
2600
  "dialog.urlLabel": "URL",
2600
2601
  "dialog.close": "Close",
2601
2602
  "dialog.error": "Failed to render QR code"
2602
2603
  };
2603
2604
  //#endregion
2605
+ //#region \0dsh-css:/root/ai/dsh-qr-share/src/client/styles.module.css.mjs
2606
+ const css = ".GjS9-q_button{width:100%;color:inherit;font:inherit;cursor:pointer;text-align:left;background:0 0;border:0;border-radius:6px;align-items:center;gap:8px;padding:8px 10px;display:inline-flex}.GjS9-q_button:hover{background:color-mix(in srgb, currentColor 8%, transparent)}.GjS9-q_button:focus-visible{outline-offset:-2px;outline:2px solid}.GjS9-q_buttonLabel{white-space:nowrap;text-overflow:ellipsis;font-size:13px;overflow:hidden}.GjS9-q_backdrop{z-index:9999;background:#00000073;place-items:center;animation:.12s ease-out GjS9-q_dsh-qr-share-fade-in;display:grid;position:fixed;inset:0}.GjS9-q_dialog{background:var(--dsh-qr-share-dialog-bg,var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-overlay,#2c2c2e)));color:var(--dsh-qr-share-dialog-fg,currentColor);border:1px solid color-mix(in srgb, currentColor 12%, transparent);border-radius:12px;flex-direction:column;align-items:center;gap:12px;min-width:320px;max-width:420px;padding:24px;animation:.16s cubic-bezier(.2,.9,.3,1.2) GjS9-q_dsh-qr-share-pop-in;display:flex;box-shadow:0 12px 32px #00000040}.GjS9-q_title{margin:0;font-size:16px;font-weight:600}.GjS9-q_canvas{image-rendering:pixelated;background:#fff;border-radius:8px;padding:8px;display:block}.GjS9-q_err{color:#d23f3f;margin:0;font-size:13px}.GjS9-q_urlBlock{flex-direction:column;align-items:center;gap:4px;width:100%;display:flex}.GjS9-q_urlLabel{text-transform:uppercase;letter-spacing:.05em;opacity:.6;font-size:11px}.GjS9-q_url{word-break:break-all;background:color-mix(in srgb, currentColor 6%, transparent);text-align:center;border-radius:4px;max-width:100%;padding:6px 8px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px}.GjS9-q_hint{opacity:.7;text-align:center;margin:0;font-size:12px}.GjS9-q_close{border:1px solid color-mix(in srgb, currentColor 20%, transparent);color:inherit;font:inherit;cursor:pointer;background:0 0;border-radius:6px;margin-top:4px;padding:8px 16px}.GjS9-q_close:hover{background:color-mix(in srgb, currentColor 8%, transparent)}.GjS9-q_close:focus-visible{outline-offset:-2px;outline:2px solid}@keyframes GjS9-q_dsh-qr-share-fade-in{0%{opacity:0}to{opacity:1}}@keyframes GjS9-q_dsh-qr-share-pop-in{0%{opacity:0;transform:scale(.92)}to{opacity:1;transform:scale(1)}}";
2607
+ const tagId = "dsh-external/dsh-qr-share/styles.module.css";
2608
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
2609
+ const tag = document.createElement("style");
2610
+ tag.dataset.plugin = "dsh-external/dsh-qr-share";
2611
+ tag.dataset.pluginCss = tagId;
2612
+ tag.textContent = css;
2613
+ document.head.appendChild(tag);
2614
+ }
2615
+ var styles_module_css_default = {
2616
+ "title": "GjS9-q_title",
2617
+ "urlBlock": "GjS9-q_urlBlock",
2618
+ "buttonLabel": "GjS9-q_buttonLabel",
2619
+ "err": "GjS9-q_err",
2620
+ "urlLabel": "GjS9-q_urlLabel",
2621
+ "url": "GjS9-q_url",
2622
+ "hint": "GjS9-q_hint",
2623
+ "backdrop": "GjS9-q_backdrop",
2624
+ "dsh-qr-share-pop-in": "GjS9-q_dsh-qr-share-pop-in",
2625
+ "close": "GjS9-q_close",
2626
+ "dialog": "GjS9-q_dialog",
2627
+ "dsh-qr-share-fade-in": "GjS9-q_dsh-qr-share-fade-in",
2628
+ "button": "GjS9-q_button",
2629
+ "canvas": "GjS9-q_canvas"
2630
+ };
2631
+ //#endregion
2604
2632
  //#region src/client/QrDialog.tsx
2605
2633
  /**
2606
2634
  * QR-code modal: a fixed-position overlay with a canvas, the resolved URL
@@ -2613,6 +2641,13 @@ window.__ModuleLoader__.load({
2613
2641
  * exchanges on path `/` (see browser-auth.ts). We enforce that here so a
2614
2642
  * future reverse-proxy sub-path does NOT silently produce a 401 on the
2615
2643
  * phone.
2644
+ *
2645
+ * Portal: the dialog is rendered into `document.body` via createPortal.
2646
+ * The shell's sidebar uses transforms/filters on its ancestors, which
2647
+ * would otherwise trap `position: fixed` inside the sidebar's box and
2648
+ * make the dialog appear *inside* the sidebar column. Portaling escapes
2649
+ * every ancestor's containing block so the backdrop covers the viewport
2650
+ * and the dialog is centered on screen.
2616
2651
  */
2617
2652
  function composeQrUrl(token) {
2618
2653
  const url = new URL("/", window.location.origin);
@@ -2623,7 +2658,12 @@ window.__ModuleLoader__.load({
2623
2658
  const canvasRef = (0, react.useRef)(null);
2624
2659
  const [payload, setPayload] = (0, react.useState)("");
2625
2660
  const [error, setError] = (0, react.useState)(null);
2661
+ const [portalTarget, setPortalTarget] = (0, react.useState)(null);
2662
+ (0, react.useEffect)(() => {
2663
+ setPortalTarget(document.body);
2664
+ }, []);
2626
2665
  (0, react.useEffect)(() => {
2666
+ if (portalTarget === null) return;
2627
2667
  if (token === "") return;
2628
2668
  const url = composeQrUrl(token);
2629
2669
  setPayload(url);
@@ -2639,7 +2679,7 @@ window.__ModuleLoader__.load({
2639
2679
  }).catch((reason) => {
2640
2680
  setError(reason instanceof Error ? reason.message : String(reason));
2641
2681
  });
2642
- }, [token]);
2682
+ }, [token, portalTarget]);
2643
2683
  (0, react.useEffect)(() => {
2644
2684
  const onKey = (e) => {
2645
2685
  if (e.key === "Escape") onClose();
@@ -2652,28 +2692,29 @@ window.__ModuleLoader__.load({
2652
2692
  const onBackdropClick = (e) => {
2653
2693
  if (e.target === e.currentTarget) onClose();
2654
2694
  };
2655
- return (0, react.createElement)("div", {
2656
- className: "dsh-qr-share-backdrop",
2695
+ if (portalTarget === null) return null;
2696
+ return (0, react_dom.createPortal)((0, react.createElement)("div", {
2697
+ className: styles_module_css_default.backdrop,
2657
2698
  role: "presentation",
2658
2699
  onClick: onBackdropClick
2659
2700
  }, (0, react.createElement)("div", {
2660
- className: "dsh-qr-share-dialog",
2701
+ className: styles_module_css_default.dialog,
2661
2702
  role: "dialog",
2662
2703
  "aria-modal": "true",
2663
2704
  "aria-labelledby": "dsh-qr-share-title"
2664
2705
  }, (0, react.createElement)("h2", {
2665
2706
  id: "dsh-qr-share-title",
2666
- className: "dsh-qr-share-title"
2667
- }, t("dialog.title")), error !== null ? (0, react.createElement)("p", { className: "dsh-qr-share-err" }, `${t("dialog.error")}: ${error}`) : (0, react.createElement)("canvas", {
2707
+ className: styles_module_css_default.title
2708
+ }, t("dialog.title")), error !== null ? (0, react.createElement)("p", { className: styles_module_css_default.err }, `${t("dialog.error")}: ${error}`) : (0, react.createElement)("canvas", {
2668
2709
  ref: canvasRef,
2669
- className: "dsh-qr-share-canvas",
2710
+ className: styles_module_css_default.canvas,
2670
2711
  width: 256,
2671
2712
  height: 256
2672
- }), (0, react.createElement)("div", { className: "dsh-qr-share-url-block" }, (0, react.createElement)("span", { className: "dsh-qr-share-url-label" }, t("dialog.urlLabel")), (0, react.createElement)("code", { className: "dsh-qr-share-url" }, payload)), (0, react.createElement)("p", { className: "dsh-qr-share-hint" }, t("dialog.hint")), (0, react.createElement)("button", {
2713
+ }), (0, react.createElement)("div", { className: styles_module_css_default.urlBlock }, (0, react.createElement)("span", { className: styles_module_css_default.urlLabel }, t("dialog.urlLabel")), (0, react.createElement)("code", { className: styles_module_css_default.url }, payload)), (0, react.createElement)("p", { className: styles_module_css_default.hint }, t("dialog.hint")), (0, react.createElement)("button", {
2673
2714
  type: "button",
2674
- className: "dsh-qr-share-close",
2715
+ className: styles_module_css_default.close,
2675
2716
  onClick: onClose
2676
- }, t("dialog.close"))));
2717
+ }, t("dialog.close")))), portalTarget);
2677
2718
  }
2678
2719
  //#endregion
2679
2720
  //#region src/client/QrButton.tsx
@@ -2688,13 +2729,13 @@ window.__ModuleLoader__.load({
2688
2729
  const [open, setOpen] = (0, react.useState)(false);
2689
2730
  return (0, react.createElement)(react.Fragment, null, (0, react.createElement)("button", {
2690
2731
  type: "button",
2691
- className: "dsh-qr-share-button",
2732
+ className: styles_module_css_default.button,
2692
2733
  "aria-label": t("button.label"),
2693
2734
  title: t("button.tooltip"),
2694
2735
  onClick: () => {
2695
2736
  setOpen(true);
2696
2737
  }
2697
- }, (0, react.createElement)(HiOutlineQrCode, { size: wide ? 18 : 20 }), wide ? (0, react.createElement)("span", { className: "dsh-qr-share-button-label" }, t("button.label")) : null), open ? (0, react.createElement)(QrDialog, {
2738
+ }, (0, react.createElement)(HiOutlineQrCode, { size: wide ? 18 : 20 }), wide ? (0, react.createElement)("span", { className: styles_module_css_default.buttonLabel }, t("button.label")) : null), open ? (0, react.createElement)(QrDialog, {
2698
2739
  token: info.token,
2699
2740
  tokenTtlMs: info.tokenTtlMs,
2700
2741
  t,