@wildwinter/app-shell 0.38.0 → 0.40.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.
Files changed (168) hide show
  1. package/LICENSES.md +62 -0
  2. package/README.md +8 -0
  3. package/dist/about.css +17 -38
  4. package/dist/about.css.map +1 -1
  5. package/dist/anchored.css +9 -9
  6. package/dist/anchored.css.map +1 -1
  7. package/dist/app-store.cjs +15 -0
  8. package/dist/app-store.cjs.map +1 -1
  9. package/dist/app-store.d.cts +25 -1
  10. package/dist/app-store.d.ts +25 -1
  11. package/dist/app-store.js +14 -1
  12. package/dist/app-store.js.map +1 -1
  13. package/dist/chunk-2ECJUTSX.js +97 -0
  14. package/dist/chunk-2ECJUTSX.js.map +1 -0
  15. package/dist/chunk-DS4XXAZS.js +56 -0
  16. package/dist/chunk-DS4XXAZS.js.map +1 -0
  17. package/dist/chunk-EHR2KBZC.js +82 -0
  18. package/dist/chunk-EHR2KBZC.js.map +1 -0
  19. package/dist/chunk-IU7EY6BI.js +26 -0
  20. package/dist/chunk-IU7EY6BI.js.map +1 -0
  21. package/dist/chunk-OPZPFOPS.js +66 -0
  22. package/dist/chunk-OPZPFOPS.js.map +1 -0
  23. package/dist/chunk-P4G4ERG6.js +60 -0
  24. package/dist/chunk-P4G4ERG6.js.map +1 -0
  25. package/dist/chunk-P6WLAEKZ.js +71 -0
  26. package/dist/chunk-P6WLAEKZ.js.map +1 -0
  27. package/dist/chunk-R3NXPHRZ.js +534 -0
  28. package/dist/chunk-R3NXPHRZ.js.map +1 -0
  29. package/dist/chunk-SDOKZTAR.js +87 -0
  30. package/dist/chunk-SDOKZTAR.js.map +1 -0
  31. package/dist/chunk-SEWJLUOD.js +70 -0
  32. package/dist/chunk-SEWJLUOD.js.map +1 -0
  33. package/dist/chunk-UG2P777F.js +587 -0
  34. package/dist/chunk-UG2P777F.js.map +1 -0
  35. package/dist/chunk-YRFJLJUH.js +55 -0
  36. package/dist/chunk-YRFJLJUH.js.map +1 -0
  37. package/dist/chunk-YWMSQFXX.js +146 -0
  38. package/dist/chunk-YWMSQFXX.js.map +1 -0
  39. package/dist/comments.css +16 -14
  40. package/dist/comments.css.map +1 -1
  41. package/dist/confirm.css +2 -55
  42. package/dist/confirm.css.map +1 -1
  43. package/dist/context-menu.cjs +250 -0
  44. package/dist/context-menu.cjs.map +1 -0
  45. package/dist/context-menu.css +64 -0
  46. package/dist/context-menu.css.map +1 -0
  47. package/dist/context-menu.d.cts +25 -0
  48. package/dist/context-menu.d.ts +25 -0
  49. package/dist/context-menu.js +5 -0
  50. package/dist/context-menu.js.map +1 -0
  51. package/dist/controls.css +214 -0
  52. package/dist/controls.css.map +1 -0
  53. package/dist/dialog.cjs +222 -0
  54. package/dist/dialog.cjs.map +1 -0
  55. package/dist/dialog.css +56 -0
  56. package/dist/dialog.css.map +1 -0
  57. package/dist/dialog.d.cts +28 -0
  58. package/dist/dialog.d.ts +28 -0
  59. package/dist/dialog.js +5 -0
  60. package/dist/dialog.js.map +1 -0
  61. package/dist/floating.cjs +83 -0
  62. package/dist/floating.cjs.map +1 -0
  63. package/dist/floating.d.cts +25 -0
  64. package/dist/floating.d.ts +25 -0
  65. package/dist/floating.js +4 -0
  66. package/dist/floating.js.map +1 -0
  67. package/dist/identity.css +15 -50
  68. package/dist/identity.css.map +1 -1
  69. package/dist/index.cjs +1553 -260
  70. package/dist/index.cjs.map +1 -1
  71. package/dist/index.d.cts +385 -152
  72. package/dist/index.d.ts +385 -152
  73. package/dist/index.js +264 -823
  74. package/dist/index.js.map +1 -1
  75. package/dist/job.css +24 -1
  76. package/dist/job.css.map +1 -1
  77. package/dist/keys.cjs +155 -0
  78. package/dist/keys.cjs.map +1 -0
  79. package/dist/keys.css +41 -0
  80. package/dist/keys.css.map +1 -0
  81. package/dist/keys.d.cts +57 -0
  82. package/dist/keys.d.ts +57 -0
  83. package/dist/keys.js +3 -0
  84. package/dist/keys.js.map +1 -0
  85. package/dist/link-status.cjs +430 -0
  86. package/dist/link-status.cjs.map +1 -0
  87. package/dist/link-status.css +76 -0
  88. package/dist/link-status.css.map +1 -0
  89. package/dist/link-status.d.cts +53 -0
  90. package/dist/link-status.d.ts +53 -0
  91. package/dist/link-status.js +5 -0
  92. package/dist/link-status.js.map +1 -0
  93. package/dist/menu.cjs +62 -1
  94. package/dist/menu.cjs.map +1 -1
  95. package/dist/menu.d.cts +88 -1
  96. package/dist/menu.d.ts +88 -1
  97. package/dist/menu.js +1 -1
  98. package/dist/notes-editor.css +9 -52
  99. package/dist/notes-editor.css.map +1 -1
  100. package/dist/pane-shell.css +23 -5
  101. package/dist/pane-shell.css.map +1 -1
  102. package/dist/problems.cjs +61 -0
  103. package/dist/problems.cjs.map +1 -0
  104. package/dist/problems.d.cts +57 -0
  105. package/dist/problems.d.ts +57 -0
  106. package/dist/problems.js +3 -0
  107. package/dist/problems.js.map +1 -0
  108. package/dist/property-list-D3VZCJzi.d.cts +162 -0
  109. package/dist/property-list-D3VZCJzi.d.ts +162 -0
  110. package/dist/property-list.cjs +784 -0
  111. package/dist/property-list.cjs.map +1 -0
  112. package/dist/property-list.d.cts +1 -0
  113. package/dist/property-list.d.ts +1 -0
  114. package/dist/property-list.js +4 -0
  115. package/dist/property-list.js.map +1 -0
  116. package/dist/save.css +1 -1
  117. package/dist/save.css.map +1 -1
  118. package/dist/settings.css +62 -18
  119. package/dist/settings.css.map +1 -1
  120. package/dist/stale.css +4 -1
  121. package/dist/stale.css.map +1 -1
  122. package/dist/stepper.css +47 -3
  123. package/dist/stepper.css.map +1 -1
  124. package/dist/toast.css +5 -5
  125. package/dist/toast.css.map +1 -1
  126. package/dist/tokens.css +20 -0
  127. package/dist/tokens.css.map +1 -1
  128. package/dist/tool-window.cjs +63 -0
  129. package/dist/tool-window.cjs.map +1 -1
  130. package/dist/tool-window.css +8 -4
  131. package/dist/tool-window.css.map +1 -1
  132. package/dist/tool-window.d.cts +67 -1
  133. package/dist/tool-window.d.ts +67 -1
  134. package/dist/tool-window.js +63 -1
  135. package/dist/tool-window.js.map +1 -1
  136. package/dist/tooltip.css +3 -3
  137. package/dist/tooltip.css.map +1 -1
  138. package/dist/updater-view.cjs +283 -0
  139. package/dist/updater-view.cjs.map +1 -0
  140. package/dist/updater-view.d.cts +23 -0
  141. package/dist/updater-view.d.ts +23 -0
  142. package/dist/updater-view.js +6 -0
  143. package/dist/updater-view.js.map +1 -0
  144. package/dist/updater.cjs +12 -16
  145. package/dist/updater.cjs.map +1 -1
  146. package/dist/updater.css +44 -0
  147. package/dist/updater.css.map +1 -0
  148. package/dist/updater.js +12 -16
  149. package/dist/updater.js.map +1 -1
  150. package/dist/util.cjs +78 -0
  151. package/dist/util.cjs.map +1 -0
  152. package/dist/util.d.cts +37 -0
  153. package/dist/util.d.ts +37 -0
  154. package/dist/util.js +3 -0
  155. package/dist/util.js.map +1 -0
  156. package/dist/vc.css +4 -3
  157. package/dist/vc.css.map +1 -1
  158. package/dist/welcome.cjs +215 -0
  159. package/dist/welcome.cjs.map +1 -0
  160. package/dist/welcome.css +112 -0
  161. package/dist/welcome.css.map +1 -0
  162. package/dist/welcome.d.cts +55 -0
  163. package/dist/welcome.d.ts +55 -0
  164. package/dist/welcome.js +4 -0
  165. package/dist/welcome.js.map +1 -0
  166. package/package.json +72 -2
  167. package/dist/chunk-QDPFU4WX.js +0 -43
  168. package/dist/chunk-QDPFU4WX.js.map +0 -1
package/dist/job.css CHANGED
@@ -6,7 +6,7 @@
6
6
  padding: 7px 18px;
7
7
  border-bottom: 1px solid var(--line-soft, var(--line));
8
8
  background: var(--surface);
9
- font-size: 0.8rem;
9
+ font-size: 13px;
10
10
  color: var(--muted);
11
11
  }
12
12
  .job-strip[hidden] {
@@ -36,11 +36,34 @@
36
36
  transition: none;
37
37
  }
38
38
  }
39
+ .job-strip.indeterminate .job-fill {
40
+ width: 25%;
41
+ transition: none;
42
+ animation: var(--anim-job-band, none);
43
+ }
44
+ @keyframes job-band {
45
+ from {
46
+ transform: translateX(-100%);
47
+ }
48
+ to {
49
+ transform: translateX(400%);
50
+ }
51
+ }
52
+ @media (prefers-reduced-motion: reduce) {
53
+ .job-strip.indeterminate .job-fill {
54
+ width: 100%;
55
+ opacity: 0.35;
56
+ }
57
+ }
39
58
  .job-count,
40
59
  .job-time {
41
60
  flex: none;
42
61
  font-variant-numeric: tabular-nums;
43
62
  }
63
+ .job-count[hidden],
64
+ .job-cancel[hidden] {
65
+ display: none;
66
+ }
44
67
  .job-time {
45
68
  opacity: 0.8;
46
69
  }
package/dist/job.css.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/job.css"],"sourcesContent":["/* The long-job progress strip. Consumes the app's tokens only, so it wears\n whichever palette the host is in. Quiet: it is a status line, not an alarm. */\n\n.job-strip {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 7px 18px;\n border-bottom: 1px solid var(--line-soft, var(--line));\n background: var(--surface);\n font-size: 0.8rem;\n color: var(--muted);\n}\n.job-strip[hidden] { display: none; }\n\n.job-label { flex: none; }\n\n.job-bar {\n flex: 1;\n min-width: 80px;\n height: 6px;\n background: var(--line-soft, var(--line));\n border-radius: var(--radius-pill, 999px);\n overflow: hidden;\n}\n.job-fill {\n display: block;\n height: 100%;\n width: 0;\n background: var(--accent);\n border-radius: var(--radius-pill, 999px);\n transition: width var(--dur-quick, 120ms) linear;\n}\n@media (prefers-reduced-motion: reduce) {\n .job-fill { transition: none; }\n}\n\n.job-count,\n.job-time {\n flex: none;\n font-variant-numeric: tabular-nums;\n}\n.job-time { opacity: 0.8; }\n\n.job-cancel {\n flex: none;\n font: inherit;\n color: var(--muted);\n background: none;\n border: 1px solid var(--line);\n border-radius: var(--radius-sm, 4px);\n padding: 3px 10px;\n cursor: pointer;\n}\n.job-cancel:hover:not(:disabled) { color: var(--danger); border-color: var(--danger); background: none; }\n.job-cancel:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }\n.job-cancel:disabled { opacity: 0.6; cursor: default; }\n"],"mappings":";AAGA,CAAC;AACC,WAAS;AACT,eAAa;AACb,OAAK;AACL,WAAS,IAAI;AACb,iBAAe,IAAI,MAAM,IAAI,WAAW,EAAE,IAAI;AAC9C,cAAY,IAAI;AAChB,aAAW;AACX,SAAO,IAAI;AACb;AACA,CAVC,SAUS,CAAC;AAAU,WAAS;AAAM;AAEpC,CAAC;AAAY,QAAM;AAAM;AAEzB,CAAC;AACC,QAAM;AACN,aAAW;AACX,UAAQ;AACR,cAAY,IAAI,WAAW,EAAE,IAAI;AACjC,iBAAe,IAAI,aAAa,EAAE;AAClC,YAAU;AACZ;AACA,CAAC;AACC,WAAS;AACT,UAAQ;AACR,SAAO;AACP,cAAY,IAAI;AAChB,iBAAe,IAAI,aAAa,EAAE;AAClC,cAAY,MAAM,IAAI,WAAW,EAAE,OAAO;AAC5C;AACA,QAAO,wBAAyB;AAC9B,GATD;AASa,gBAAY;AAAM;AAChC;AAEA,CAAC;AACD,CAAC;AACC,QAAM;AACN,wBAAsB;AACxB;AACA,CAJC;AAIW,WAAS;AAAK;AAE1B,CAAC;AACC,QAAM;AACN,QAAM;AACN,SAAO,IAAI;AACX,cAAY;AACZ,UAAQ,IAAI,MAAM,IAAI;AACtB,iBAAe,IAAI,WAAW,EAAE;AAChC,WAAS,IAAI;AACb,UAAQ;AACV;AACA,CAVC,UAUU,MAAM,KAAK;AAAa,SAAO,IAAI;AAAW,gBAAc,IAAI;AAAW,cAAY;AAAM;AACxG,CAXC,UAWU;AAAiB,WAAS,IAAI,MAAM,IAAI;AAAW,kBAAgB;AAAK;AACnF,CAZC,UAYU;AAAY,WAAS;AAAK,UAAQ;AAAS;","names":[]}
1
+ {"version":3,"sources":["../src/job.css"],"sourcesContent":["/* The long-job progress strip. Consumes the app's tokens only, so it wears\n whichever palette the host is in. Quiet: it is a status line, not an alarm. */\n\n.job-strip {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 7px 18px;\n border-bottom: 1px solid var(--line-soft, var(--line));\n background: var(--surface);\n font-size: 13px;\n color: var(--muted);\n}\n.job-strip[hidden] { display: none; }\n\n.job-label { flex: none; }\n\n.job-bar {\n flex: 1;\n min-width: 80px;\n height: 6px;\n background: var(--line-soft, var(--line));\n border-radius: var(--radius-pill, 999px);\n overflow: hidden;\n}\n.job-fill {\n display: block;\n height: 100%;\n width: 0;\n background: var(--accent);\n border-radius: var(--radius-pill, 999px);\n transition: width var(--dur-quick, 120ms) linear;\n}\n@media (prefers-reduced-motion: reduce) {\n .job-fill { transition: none; }\n}\n\n/* The indeterminate band: a job that has not said how big it is (total 0, the\n host's \"no idea yet\") or one that never will (a publish, a pull). A quarter\n of the bar sweeps left to right on the family's motion token; the count is\n hidden by the view. Under reduced motion the token is `none`, and the band\n would otherwise sit still at a quarter and read as stuck, so it becomes a\n full, translucent bar: \"busy\", said without movement. */\n.job-strip.indeterminate .job-fill {\n width: 25%;\n transition: none;\n animation: var(--anim-job-band, none);\n}\n@keyframes job-band {\n from { transform: translateX(-100%); }\n to { transform: translateX(400%); }\n}\n@media (prefers-reduced-motion: reduce) {\n .job-strip.indeterminate .job-fill { width: 100%; opacity: 0.35; }\n}\n\n.job-count,\n.job-time {\n flex: none;\n font-variant-numeric: tabular-nums;\n}\n.job-count[hidden],\n.job-cancel[hidden] { display: none; }\n.job-time { opacity: 0.8; }\n\n.job-cancel {\n flex: none;\n font: inherit;\n color: var(--muted);\n background: none;\n border: 1px solid var(--line);\n border-radius: var(--radius-sm, 4px);\n padding: 3px 10px;\n cursor: pointer;\n}\n.job-cancel:hover:not(:disabled) { color: var(--danger); border-color: var(--danger); background: none; }\n.job-cancel:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }\n.job-cancel:disabled { opacity: 0.6; cursor: default; }\n"],"mappings":";AAGA,CAAC;AACC,WAAS;AACT,eAAa;AACb,OAAK;AACL,WAAS,IAAI;AACb,iBAAe,IAAI,MAAM,IAAI,WAAW,EAAE,IAAI;AAC9C,cAAY,IAAI;AAChB,aAAW;AACX,SAAO,IAAI;AACb;AACA,CAVC,SAUS,CAAC;AAAU,WAAS;AAAM;AAEpC,CAAC;AAAY,QAAM;AAAM;AAEzB,CAAC;AACC,QAAM;AACN,aAAW;AACX,UAAQ;AACR,cAAY,IAAI,WAAW,EAAE,IAAI;AACjC,iBAAe,IAAI,aAAa,EAAE;AAClC,YAAU;AACZ;AACA,CAAC;AACC,WAAS;AACT,UAAQ;AACR,SAAO;AACP,cAAY,IAAI;AAChB,iBAAe,IAAI,aAAa,EAAE;AAClC,cAAY,MAAM,IAAI,WAAW,EAAE,OAAO;AAC5C;AACA,QAAO,wBAAyB;AAC9B,GATD;AASa,gBAAY;AAAM;AAChC;AAQA,CAxCC,SAwCS,CAAC,cAAc,CAlBxB;AAmBC,SAAO;AACP,cAAY;AACZ,aAAW,IAAI,eAAe,EAAE;AAClC;AACA,WAAW;AACT;AAAO,eAAW,WAAW;AAAQ;AACrC;AAAK,eAAW,WAAW;AAAO;AACpC;AACA,QAAO,wBAAyB;AAC9B,GAlDD,SAkDW,CAVD,cAUgB,CA5B1B;AA4BsC,WAAO;AAAM,aAAS;AAAM;AACnE;AAEA,CAAC;AACD,CAAC;AACC,QAAM;AACN,wBAAsB;AACxB;AACA,CALC,SAKS,CAAC;AACX,CAAC,UAAU,CAAC;AAAU,WAAS;AAAM;AACrC,CANC;AAMW,WAAS;AAAK;AAE1B,CAHC;AAIC,QAAM;AACN,QAAM;AACN,SAAO,IAAI;AACX,cAAY;AACZ,UAAQ,IAAI,MAAM,IAAI;AACtB,iBAAe,IAAI,WAAW,EAAE;AAChC,WAAS,IAAI;AACb,UAAQ;AACV;AACA,CAbC,UAaU,MAAM,KAAK;AAAa,SAAO,IAAI;AAAW,gBAAc,IAAI;AAAW,cAAY;AAAM;AACxG,CAdC,UAcU;AAAiB,WAAS,IAAI,MAAM,IAAI;AAAW,kBAAgB;AAAK;AACnF,CAfC,UAeU;AAAY,WAAS;AAAK,UAAQ;AAAS;","names":[]}
package/dist/keys.cjs ADDED
@@ -0,0 +1,155 @@
1
+ 'use strict';
2
+
3
+ // src/keys.ts
4
+ var override;
5
+ function setKeyPlatform(platform) {
6
+ override = platform;
7
+ }
8
+ function keyPlatform() {
9
+ if (override) return override;
10
+ if (typeof navigator !== "undefined") {
11
+ const s = `${navigator.platform ?? ""} ${navigator.userAgent ?? ""}`;
12
+ if (/mac|iphone|ipad/i.test(s)) return "mac";
13
+ if (/win/i.test(s)) return "win";
14
+ if (s.trim()) return "linux";
15
+ }
16
+ const p = typeof process !== "undefined" ? process.platform : void 0;
17
+ return p === "darwin" ? "mac" : p === "win32" ? "win" : "linux";
18
+ }
19
+ var MAC_ORDER = ["ctrl", "alt", "shift", "mod"];
20
+ var OTHER_ORDER = ["mod", "ctrl", "alt", "shift"];
21
+ var MAC_MOD = { ctrl: "\u2303", alt: "\u2325", shift: "\u21E7", mod: "\u2318" };
22
+ var OTHER_MOD = { ctrl: "Ctrl", alt: "Alt", shift: "Shift", mod: "Ctrl" };
23
+ var MODIFIER_ALIASES = {
24
+ mod: "mod",
25
+ cmd: "mod",
26
+ command: "mod",
27
+ meta: "mod",
28
+ super: "mod",
29
+ ctrl: "ctrl",
30
+ control: "ctrl",
31
+ alt: "alt",
32
+ option: "alt",
33
+ opt: "alt",
34
+ shift: "shift"
35
+ };
36
+ var NAMED = {
37
+ enter: { mac: "\u21A9", other: "Enter" },
38
+ return: { mac: "\u21A9", other: "Enter" },
39
+ esc: { mac: "esc", other: "Esc" },
40
+ escape: { mac: "esc", other: "Esc" },
41
+ up: { mac: "\u2191", other: "Up" },
42
+ down: { mac: "\u2193", other: "Down" },
43
+ left: { mac: "\u2190", other: "Left" },
44
+ right: { mac: "\u2192", other: "Right" },
45
+ backspace: { mac: "\u232B", other: "Backspace" },
46
+ delete: { mac: "\u2326", other: "Del" },
47
+ del: { mac: "\u2326", other: "Del" },
48
+ tab: { mac: "\u21E5", other: "Tab" },
49
+ space: { mac: "Space", other: "Space" },
50
+ home: { mac: "\u2196", other: "Home" },
51
+ end: { mac: "\u2198", other: "End" },
52
+ pageup: { mac: "\u21DE", other: "PgUp" },
53
+ pagedown: { mac: "\u21DF", other: "PgDn" }
54
+ };
55
+ var PORTABLE_MODS = /* @__PURE__ */ new Set(["mod", "ctrl", "alt", "shift"]);
56
+ function parse(combo) {
57
+ const parts = combo.split("+");
58
+ const mods = [];
59
+ let key;
60
+ for (let i = 0; i < parts.length; i++) {
61
+ const raw = parts[i] ?? "";
62
+ if (raw === "" && i > 0) {
63
+ key = "+";
64
+ continue;
65
+ }
66
+ const mod = MODIFIER_ALIASES[raw.toLowerCase()];
67
+ if (mod) {
68
+ if (!mods.includes(mod)) mods.push(mod);
69
+ continue;
70
+ }
71
+ key = raw;
72
+ }
73
+ return { mods, key };
74
+ }
75
+ function isKeyCombo(value) {
76
+ const plus = value.length > 2 && value.endsWith("++");
77
+ const parts = (plus ? value.slice(0, -2) : value).split("+");
78
+ let keys = plus ? 1 : 0;
79
+ for (const raw of parts) {
80
+ if (raw === "") return false;
81
+ const word = raw.toLowerCase();
82
+ if (PORTABLE_MODS.has(word)) continue;
83
+ if (raw.length === 1 || word in NAMED) {
84
+ keys++;
85
+ continue;
86
+ }
87
+ return false;
88
+ }
89
+ return keys === 1;
90
+ }
91
+ function keyLegend(key, mac) {
92
+ const named = NAMED[key.toLowerCase()];
93
+ if (named) return mac ? named.mac : named.other;
94
+ return key.length === 1 ? key.toUpperCase() : key;
95
+ }
96
+ function keyLabel(combo, platform = keyPlatform()) {
97
+ const mac = platform === "mac";
98
+ const { mods, key } = parse(combo);
99
+ const order = mac ? MAC_ORDER : OTHER_ORDER;
100
+ const words = order.filter((m) => mods.includes(m)).map((m) => (mac ? MAC_MOD : OTHER_MOD)[m]);
101
+ const seen = /* @__PURE__ */ new Set();
102
+ const modWords = words.filter((w) => seen.has(w) ? false : (seen.add(w), true));
103
+ const legend = key === void 0 ? [] : [keyLegend(key, mac)];
104
+ return mac ? [...modWords, ...legend].join("") : [...modWords, ...legend].join("+");
105
+ }
106
+ function keyLegends(combo, platform = keyPlatform()) {
107
+ if (platform === "mac") return [keyLabel(combo, platform)];
108
+ const { mods, key } = parse(combo);
109
+ const seen = /* @__PURE__ */ new Set();
110
+ const words = OTHER_ORDER.filter((m) => mods.includes(m)).map((m) => OTHER_MOD[m]).filter((w) => seen.has(w) ? false : (seen.add(w), true));
111
+ return key === void 0 ? words : [...words, keyLegend(key, false)];
112
+ }
113
+ function chip(legend) {
114
+ const k = document.createElement("kbd");
115
+ k.className = "shell-kbd";
116
+ k.textContent = legend;
117
+ return k;
118
+ }
119
+ function keyHint(combo, opts = {}) {
120
+ const platform = opts.platform ?? keyPlatform();
121
+ const legends = combo.trim().split(/\s+/).flatMap((c) => keyLegends(c, platform));
122
+ if (legends.length === 1) return chip(legends[0]);
123
+ const group = document.createElement("span");
124
+ group.className = "shell-keys";
125
+ for (const legend of legends) group.append(chip(legend));
126
+ return group;
127
+ }
128
+ function hintBar(items, opts = {}) {
129
+ const bar = document.createElement("div");
130
+ bar.className = "shell-hintbar";
131
+ for (const item of items) {
132
+ const hint = document.createElement("span");
133
+ hint.className = "shell-hint";
134
+ const label = document.createElement("span");
135
+ label.className = "shell-hint-label";
136
+ label.textContent = item.label;
137
+ hint.append(keyHint(item.keys, opts), label);
138
+ bar.append(hint);
139
+ }
140
+ return bar;
141
+ }
142
+ function tipWithKey(text, combo, platform = keyPlatform()) {
143
+ return `${text} (${keyLabel(combo, platform)})`;
144
+ }
145
+
146
+ exports.hintBar = hintBar;
147
+ exports.isKeyCombo = isKeyCombo;
148
+ exports.keyHint = keyHint;
149
+ exports.keyLabel = keyLabel;
150
+ exports.keyLegends = keyLegends;
151
+ exports.keyPlatform = keyPlatform;
152
+ exports.setKeyPlatform = setKeyPlatform;
153
+ exports.tipWithKey = tipWithKey;
154
+ //# sourceMappingURL=keys.cjs.map
155
+ //# sourceMappingURL=keys.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/keys.ts"],"names":[],"mappings":"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","file":"keys.cjs","sourcesContent":["// ---------------------------------------------------------------------------\n// Keyboard hints, platform-true, from ONE helper.\n//\n// \"One casing rule, and platform-true key hints\" (design-language.md section\n// 4, 2026-09-16): a hint writes \"⌘\" on macOS and \"Ctrl\" elsewhere, and \"⌘\"\n// never appears in a hard-coded string. Before this module the family had four\n// notations for one idea (ui-review-2026-09, finding 26): a <kbd> here, \"(⌘1)\"\n// in a tooltip there, a platform fork in one label, and \"↑↓ move · ↵ jump ·\n// esc to close\" typed into a hint bar, which on Windows showed a Mac glyph.\n//\n// The input is the portable spelling every caller already knows from the menu\n// spine: \"Mod+1\", \"Mod+Shift+M\", \"Enter\", \"Esc\", \"Up\". The output is what the\n// platform writes: \"⌘1\" / \"⇧⌘M\" / \"↩\" / \"esc\" on a Mac, in Apple's modifier\n// order and with no joiner; \"Ctrl+1\" / \"Ctrl+Shift+M\" / \"Enter\" / \"Esc\"\n// everywhere else. A hint is DOM (`keyHint`, `hintBar`): a keycap element,\n// never a glyph string, and a tooltip gets its bracketed key from\n// `tipWithKey` so it is right on both platforms.\n// ---------------------------------------------------------------------------\n\nexport type KeyPlatform = \"mac\" | \"win\" | \"linux\";\n\nlet override: KeyPlatform | undefined;\n\n/** Pin the platform every helper here renders for (undefined = detect again).\n * A seam for the tests and for a preview harness; an app leaves it unset. */\nexport function setKeyPlatform(platform?: KeyPlatform): void { override = platform; }\n\n/** The platform the hints render for: the override, else the browser's, else\n * the process's, else \"linux\" (which draws the same as \"win\"). */\nexport function keyPlatform(): KeyPlatform {\n if (override) return override;\n if (typeof navigator !== \"undefined\") {\n const s = `${navigator.platform ?? \"\"} ${navigator.userAgent ?? \"\"}`;\n if (/mac|iphone|ipad/i.test(s)) return \"mac\";\n if (/win/i.test(s)) return \"win\";\n if (s.trim()) return \"linux\";\n }\n const p = typeof process !== \"undefined\" ? process.platform : undefined;\n return p === \"darwin\" ? \"mac\" : p === \"win32\" ? \"win\" : \"linux\";\n}\n\n/** Canonical modifier words, in the order each platform writes them. Apple\n * puts them ⌃ ⌥ ⇧ ⌘ before the key; Windows and Linux write Ctrl+Alt+Shift. */\ntype Modifier = \"ctrl\" | \"alt\" | \"shift\" | \"mod\";\nconst MAC_ORDER: Modifier[] = [\"ctrl\", \"alt\", \"shift\", \"mod\"];\nconst OTHER_ORDER: Modifier[] = [\"mod\", \"ctrl\", \"alt\", \"shift\"];\nconst MAC_MOD: Record<Modifier, string> = { ctrl: \"⌃\", alt: \"⌥\", shift: \"⇧\", mod: \"⌘\" };\nconst OTHER_MOD: Record<Modifier, string> = { ctrl: \"Ctrl\", alt: \"Alt\", shift: \"Shift\", mod: \"Ctrl\" };\n\nconst MODIFIER_ALIASES: Record<string, Modifier> = {\n mod: \"mod\", cmd: \"mod\", command: \"mod\", meta: \"mod\", super: \"mod\",\n ctrl: \"ctrl\", control: \"ctrl\",\n alt: \"alt\", option: \"alt\", opt: \"alt\",\n shift: \"shift\",\n};\n\n/** Named keys: the Mac legend and the spelled-out word the others use. */\nconst NAMED: Record<string, { mac: string; other: string }> = {\n enter: { mac: \"↩\", other: \"Enter\" }, return: { mac: \"↩\", other: \"Enter\" },\n esc: { mac: \"esc\", other: \"Esc\" }, escape: { mac: \"esc\", other: \"Esc\" },\n up: { mac: \"↑\", other: \"Up\" }, down: { mac: \"↓\", other: \"Down\" },\n left: { mac: \"←\", other: \"Left\" }, right: { mac: \"→\", other: \"Right\" },\n backspace: { mac: \"⌫\", other: \"Backspace\" }, delete: { mac: \"⌦\", other: \"Del\" }, del: { mac: \"⌦\", other: \"Del\" },\n tab: { mac: \"⇥\", other: \"Tab\" }, space: { mac: \"Space\", other: \"Space\" },\n home: { mac: \"↖\", other: \"Home\" }, end: { mac: \"↘\", other: \"End\" },\n pageup: { mac: \"⇞\", other: \"PgUp\" }, pagedown: { mac: \"⇟\", other: \"PgDn\" },\n};\n\ninterface Parsed { mods: Modifier[]; key: string | undefined }\n\n/** The portable modifier words, exactly: the spelling the menu spine uses. The\n * aliases above (\"Cmd\", \"Option\") are tolerated by `parse` but they are not\n * portable, which is what `isKeyCombo` keys on. */\nconst PORTABLE_MODS = new Set<string>([\"mod\", \"ctrl\", \"alt\", \"shift\"]);\n\n/** Split \"Mod+Shift+M\" into its modifiers and its key. A literal \"+\" key is\n * written \"Mod++\" (the last empty part). Case is ignored on the words. */\nfunction parse(combo: string): Parsed {\n const parts = combo.split(\"+\");\n const mods: Modifier[] = [];\n let key: string | undefined;\n for (let i = 0; i < parts.length; i++) {\n const raw = parts[i] ?? \"\";\n if (raw === \"\" && i > 0) { key = \"+\"; continue; }\n const mod = MODIFIER_ALIASES[raw.toLowerCase()];\n if (mod) { if (!mods.includes(mod)) mods.push(mod); continue; }\n key = raw;\n }\n return { mods, key };\n}\n\n/**\n * Whether `value` is a combo in the portable spelling (\"Mod+1\", \"Ctrl+Shift+M\",\n * \"Enter\", \"Mod++\") rather than a literal hint someone already wrote out\n * (\"Cmd+1\", \"⌘1\", \"F1 twice\"). The test is strict on purpose: only the words\n * `Mod`, `Ctrl`, `Alt` and `Shift` (any case) count as modifiers, and the one\n * key must be a single character or a named key. A place that took a literal\n * string before (the pane shell's `shortcutHint`) can then take a combo as\n * well without changing what any existing literal draws.\n */\nexport function isKeyCombo(value: string): boolean {\n // \"Mod++\" is the plus key, as `parse` reads it: count it, then read the rest.\n const plus = value.length > 2 && value.endsWith(\"++\");\n const parts = (plus ? value.slice(0, -2) : value).split(\"+\");\n let keys = plus ? 1 : 0;\n for (const raw of parts) {\n if (raw === \"\") return false;\n const word = raw.toLowerCase();\n if (PORTABLE_MODS.has(word)) continue;\n if (raw.length === 1 || word in NAMED) { keys++; continue; }\n return false;\n }\n return keys === 1;\n}\n\nfunction keyLegend(key: string, mac: boolean): string {\n const named = NAMED[key.toLowerCase()];\n if (named) return mac ? named.mac : named.other;\n return key.length === 1 ? key.toUpperCase() : key;\n}\n\n/** The platform's spelling of a combo as one string: \"⇧⌘M\" on a Mac,\n * \"Ctrl+Shift+M\" elsewhere. For a tooltip or a menu label; a visible hint in\n * a window is `keyHint` (a keycap), not this. */\nexport function keyLabel(combo: string, platform: KeyPlatform = keyPlatform()): string {\n const mac = platform === \"mac\";\n const { mods, key } = parse(combo);\n const order = mac ? MAC_ORDER : OTHER_ORDER;\n const words = order.filter((m) => mods.includes(m)).map((m) => (mac ? MAC_MOD : OTHER_MOD)[m]);\n // On Windows \"Mod\" and \"Ctrl\" are both Ctrl; say it once.\n const seen = new Set<string>();\n const modWords = words.filter((w) => (seen.has(w) ? false : (seen.add(w), true)));\n const legend = key === undefined ? [] : [keyLegend(key, mac)];\n return mac ? [...modWords, ...legend].join(\"\") : [...modWords, ...legend].join(\"+\");\n}\n\n/** The legends of a combo as the platform would draw them on keycaps: one\n * entry on a Mac (Apple's glyph string is one legend), one per key elsewhere. */\nexport function keyLegends(combo: string, platform: KeyPlatform = keyPlatform()): string[] {\n if (platform === \"mac\") return [keyLabel(combo, platform)];\n const { mods, key } = parse(combo);\n const seen = new Set<string>();\n const words = OTHER_ORDER.filter((m) => mods.includes(m)).map((m) => OTHER_MOD[m])\n .filter((w) => (seen.has(w) ? false : (seen.add(w), true)));\n return key === undefined ? words : [...words, keyLegend(key, false)];\n}\n\nexport interface KeyHintOptions {\n /** Render for this platform rather than the detected one. */\n platform?: KeyPlatform;\n}\n\nfunction chip(legend: string): HTMLElement {\n const k = document.createElement(\"kbd\");\n k.className = \"shell-kbd\";\n k.textContent = legend;\n return k;\n}\n\n/**\n * A keycap for `combo`: `<kbd class=\"shell-kbd\">` (keys.css). On a Mac one\n * chip carrying the glyph string (\"⇧⌘M\"); elsewhere one chip per key\n * (\"Ctrl\" \"Shift\" \"M\") side by side in a `<span class=\"shell-keys\">`, with no\n * typed joiner: keycaps beside each other read as \"together\". `combo` may\n * also list ALTERNATIVES separated by spaces (\"Up Down\" for a move hint),\n * each drawn as its own keycap.\n */\nexport function keyHint(combo: string, opts: KeyHintOptions = {}): HTMLElement {\n const platform = opts.platform ?? keyPlatform();\n const legends = combo.trim().split(/\\s+/).flatMap((c) => keyLegends(c, platform));\n if (legends.length === 1) return chip(legends[0]!);\n const group = document.createElement(\"span\");\n group.className = \"shell-keys\";\n for (const legend of legends) group.append(chip(legend));\n return group;\n}\n\nexport interface HintBarItem {\n /** A combo, or space-separated alternatives (\"Up Down\"). */\n keys: string;\n /** What the key does, as a sentence-case fragment (\"Move\", \"Jump\"). */\n label: string;\n}\n\n/**\n * The hint bar under a picker or a tool window (\"↑↓ move · ↵ jump · esc\n * close\" as it used to be typed): one `<div class=\"shell-hintbar\">` of\n * `<span class=\"shell-hint\">` items, each a keycap (`keyHint`) and a label,\n * spaced by CSS and separated by nothing typed at all.\n */\nexport function hintBar(items: HintBarItem[], opts: KeyHintOptions = {}): HTMLElement {\n const bar = document.createElement(\"div\");\n bar.className = \"shell-hintbar\";\n for (const item of items) {\n const hint = document.createElement(\"span\");\n hint.className = \"shell-hint\";\n const label = document.createElement(\"span\");\n label.className = \"shell-hint-label\";\n label.textContent = item.label;\n hint.append(keyHint(item.keys, opts), label);\n bar.append(hint);\n }\n return bar;\n}\n\n/** Tooltip text with its key in brackets, platform-true: `tipWithKey(\"Show\n * scenes\", \"Mod+1\")` is \"Show scenes (⌘1)\" on a Mac and \"Show scenes\n * (Ctrl+1)\" elsewhere. For `data-tip`; the pane shell calls it itself when\n * `PaneSideConfig.shortcutHint` is a combo. */\nexport function tipWithKey(text: string, combo: string, platform: KeyPlatform = keyPlatform()): string {\n return `${text} (${keyLabel(combo, platform)})`;\n}\n"]}
package/dist/keys.css ADDED
@@ -0,0 +1,41 @@
1
+ /* src/keys.css */
2
+ .shell-kbd {
3
+ display: inline-flex;
4
+ align-items: center;
5
+ justify-content: center;
6
+ min-width: 18px;
7
+ padding: 0 5px;
8
+ font: 500 11px/1.5 var(--font-ui);
9
+ letter-spacing: 0.01em;
10
+ color: var(--muted);
11
+ background: var(--surface);
12
+ border: 1px solid var(--line);
13
+ border-bottom-width: 2px;
14
+ border-radius: var(--radius-sm);
15
+ white-space: nowrap;
16
+ vertical-align: middle;
17
+ }
18
+ .shell-keys {
19
+ display: inline-flex;
20
+ align-items: center;
21
+ gap: 3px;
22
+ vertical-align: middle;
23
+ }
24
+ .shell-hintbar {
25
+ display: flex;
26
+ flex-wrap: wrap;
27
+ align-items: center;
28
+ gap: 4px 14px;
29
+ font: 11.5px/1.4 var(--font-ui);
30
+ color: var(--muted);
31
+ }
32
+ .shell-hint {
33
+ display: inline-flex;
34
+ align-items: center;
35
+ gap: 5px;
36
+ white-space: nowrap;
37
+ }
38
+ .shell-hint-label {
39
+ color: var(--muted);
40
+ }
41
+ /*# sourceMappingURL=keys.css.map */
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/keys.css"],"sourcesContent":["/* ============================================================================\n Keycaps and hint bars (keys.ts): `keyHint`, `hintBar`. On the chrome scale\n (px off the 14px base, tokens.css). Reads the palette (--surface, --line,\n --muted, --ink, --font-ui). The element `kbd` rule in controls.css is the\n floor for a hand-written keycap; these are the shell's own.\n ============================================================================ */\n\n/* One keycap. The UI face, not mono: Apple's modifier glyphs sit on the text\n baseline in the UI face and float in most monos. A 2px bottom edge is the\n whole of the \"key\" drawing; nothing else lifts or shades. */\n.shell-kbd {\n display: inline-flex; align-items: center; justify-content: center;\n min-width: 18px; padding: 0 5px;\n font: 500 11px/1.5 var(--font-ui); letter-spacing: 0.01em;\n color: var(--muted); background: var(--surface);\n border: 1px solid var(--line); border-bottom-width: 2px; border-radius: var(--radius-sm);\n white-space: nowrap; vertical-align: middle;\n}\n\n/* Several keycaps that mean \"together\" (Windows and Linux spell a chord as\n one cap per key) or \"either\" (a move hint's up and down): side by side,\n with a gap and no typed joiner. */\n.shell-keys { display: inline-flex; align-items: center; gap: 3px; vertical-align: middle; }\n\n/* The hint bar: items spaced by the row's gap, each a keycap then its label. */\n.shell-hintbar {\n display: flex; flex-wrap: wrap; align-items: center; gap: 4px 14px;\n font: 11.5px/1.4 var(--font-ui); color: var(--muted);\n}\n.shell-hint { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; }\n.shell-hint-label { color: var(--muted); }\n"],"mappings":";AAUA,CAAC;AACC,WAAS;AAAa,eAAa;AAAQ,mBAAiB;AAC5D,aAAW;AAAM,WAAS,EAAE;AAC5B,QAAM,IAAI,IAAI,CAAC,IAAI,IAAI;AAAY,kBAAgB;AACnD,SAAO,IAAI;AAAU,cAAY,IAAI;AACrC,UAAQ,IAAI,MAAM,IAAI;AAAS,uBAAqB;AAAK,iBAAe,IAAI;AAC5E,eAAa;AAAQ,kBAAgB;AACvC;AAKA,CAAC;AAAa,WAAS;AAAa,eAAa;AAAQ,OAAK;AAAK,kBAAgB;AAAQ;AAG3F,CAAC;AACC,WAAS;AAAM,aAAW;AAAM,eAAa;AAAQ,OAAK,IAAI;AAC9D,QAAM,MAAM,CAAC,IAAI,IAAI;AAAY,SAAO,IAAI;AAC9C;AACA,CAAC;AAAa,WAAS;AAAa,eAAa;AAAQ,OAAK;AAAK,eAAa;AAAQ;AACxF,CAAC;AAAmB,SAAO,IAAI;AAAU;","names":[]}
@@ -0,0 +1,57 @@
1
+ type KeyPlatform = "mac" | "win" | "linux";
2
+ /** Pin the platform every helper here renders for (undefined = detect again).
3
+ * A seam for the tests and for a preview harness; an app leaves it unset. */
4
+ declare function setKeyPlatform(platform?: KeyPlatform): void;
5
+ /** The platform the hints render for: the override, else the browser's, else
6
+ * the process's, else "linux" (which draws the same as "win"). */
7
+ declare function keyPlatform(): KeyPlatform;
8
+ /**
9
+ * Whether `value` is a combo in the portable spelling ("Mod+1", "Ctrl+Shift+M",
10
+ * "Enter", "Mod++") rather than a literal hint someone already wrote out
11
+ * ("Cmd+1", "⌘1", "F1 twice"). The test is strict on purpose: only the words
12
+ * `Mod`, `Ctrl`, `Alt` and `Shift` (any case) count as modifiers, and the one
13
+ * key must be a single character or a named key. A place that took a literal
14
+ * string before (the pane shell's `shortcutHint`) can then take a combo as
15
+ * well without changing what any existing literal draws.
16
+ */
17
+ declare function isKeyCombo(value: string): boolean;
18
+ /** The platform's spelling of a combo as one string: "⇧⌘M" on a Mac,
19
+ * "Ctrl+Shift+M" elsewhere. For a tooltip or a menu label; a visible hint in
20
+ * a window is `keyHint` (a keycap), not this. */
21
+ declare function keyLabel(combo: string, platform?: KeyPlatform): string;
22
+ /** The legends of a combo as the platform would draw them on keycaps: one
23
+ * entry on a Mac (Apple's glyph string is one legend), one per key elsewhere. */
24
+ declare function keyLegends(combo: string, platform?: KeyPlatform): string[];
25
+ interface KeyHintOptions {
26
+ /** Render for this platform rather than the detected one. */
27
+ platform?: KeyPlatform;
28
+ }
29
+ /**
30
+ * A keycap for `combo`: `<kbd class="shell-kbd">` (keys.css). On a Mac one
31
+ * chip carrying the glyph string ("⇧⌘M"); elsewhere one chip per key
32
+ * ("Ctrl" "Shift" "M") side by side in a `<span class="shell-keys">`, with no
33
+ * typed joiner: keycaps beside each other read as "together". `combo` may
34
+ * also list ALTERNATIVES separated by spaces ("Up Down" for a move hint),
35
+ * each drawn as its own keycap.
36
+ */
37
+ declare function keyHint(combo: string, opts?: KeyHintOptions): HTMLElement;
38
+ interface HintBarItem {
39
+ /** A combo, or space-separated alternatives ("Up Down"). */
40
+ keys: string;
41
+ /** What the key does, as a sentence-case fragment ("Move", "Jump"). */
42
+ label: string;
43
+ }
44
+ /**
45
+ * The hint bar under a picker or a tool window ("↑↓ move · ↵ jump · esc
46
+ * close" as it used to be typed): one `<div class="shell-hintbar">` of
47
+ * `<span class="shell-hint">` items, each a keycap (`keyHint`) and a label,
48
+ * spaced by CSS and separated by nothing typed at all.
49
+ */
50
+ declare function hintBar(items: HintBarItem[], opts?: KeyHintOptions): HTMLElement;
51
+ /** Tooltip text with its key in brackets, platform-true: `tipWithKey("Show
52
+ * scenes", "Mod+1")` is "Show scenes (⌘1)" on a Mac and "Show scenes
53
+ * (Ctrl+1)" elsewhere. For `data-tip`; the pane shell calls it itself when
54
+ * `PaneSideConfig.shortcutHint` is a combo. */
55
+ declare function tipWithKey(text: string, combo: string, platform?: KeyPlatform): string;
56
+
57
+ export { type HintBarItem, type KeyHintOptions, type KeyPlatform, hintBar, isKeyCombo, keyHint, keyLabel, keyLegends, keyPlatform, setKeyPlatform, tipWithKey };
package/dist/keys.d.ts ADDED
@@ -0,0 +1,57 @@
1
+ type KeyPlatform = "mac" | "win" | "linux";
2
+ /** Pin the platform every helper here renders for (undefined = detect again).
3
+ * A seam for the tests and for a preview harness; an app leaves it unset. */
4
+ declare function setKeyPlatform(platform?: KeyPlatform): void;
5
+ /** The platform the hints render for: the override, else the browser's, else
6
+ * the process's, else "linux" (which draws the same as "win"). */
7
+ declare function keyPlatform(): KeyPlatform;
8
+ /**
9
+ * Whether `value` is a combo in the portable spelling ("Mod+1", "Ctrl+Shift+M",
10
+ * "Enter", "Mod++") rather than a literal hint someone already wrote out
11
+ * ("Cmd+1", "⌘1", "F1 twice"). The test is strict on purpose: only the words
12
+ * `Mod`, `Ctrl`, `Alt` and `Shift` (any case) count as modifiers, and the one
13
+ * key must be a single character or a named key. A place that took a literal
14
+ * string before (the pane shell's `shortcutHint`) can then take a combo as
15
+ * well without changing what any existing literal draws.
16
+ */
17
+ declare function isKeyCombo(value: string): boolean;
18
+ /** The platform's spelling of a combo as one string: "⇧⌘M" on a Mac,
19
+ * "Ctrl+Shift+M" elsewhere. For a tooltip or a menu label; a visible hint in
20
+ * a window is `keyHint` (a keycap), not this. */
21
+ declare function keyLabel(combo: string, platform?: KeyPlatform): string;
22
+ /** The legends of a combo as the platform would draw them on keycaps: one
23
+ * entry on a Mac (Apple's glyph string is one legend), one per key elsewhere. */
24
+ declare function keyLegends(combo: string, platform?: KeyPlatform): string[];
25
+ interface KeyHintOptions {
26
+ /** Render for this platform rather than the detected one. */
27
+ platform?: KeyPlatform;
28
+ }
29
+ /**
30
+ * A keycap for `combo`: `<kbd class="shell-kbd">` (keys.css). On a Mac one
31
+ * chip carrying the glyph string ("⇧⌘M"); elsewhere one chip per key
32
+ * ("Ctrl" "Shift" "M") side by side in a `<span class="shell-keys">`, with no
33
+ * typed joiner: keycaps beside each other read as "together". `combo` may
34
+ * also list ALTERNATIVES separated by spaces ("Up Down" for a move hint),
35
+ * each drawn as its own keycap.
36
+ */
37
+ declare function keyHint(combo: string, opts?: KeyHintOptions): HTMLElement;
38
+ interface HintBarItem {
39
+ /** A combo, or space-separated alternatives ("Up Down"). */
40
+ keys: string;
41
+ /** What the key does, as a sentence-case fragment ("Move", "Jump"). */
42
+ label: string;
43
+ }
44
+ /**
45
+ * The hint bar under a picker or a tool window ("↑↓ move · ↵ jump · esc
46
+ * close" as it used to be typed): one `<div class="shell-hintbar">` of
47
+ * `<span class="shell-hint">` items, each a keycap (`keyHint`) and a label,
48
+ * spaced by CSS and separated by nothing typed at all.
49
+ */
50
+ declare function hintBar(items: HintBarItem[], opts?: KeyHintOptions): HTMLElement;
51
+ /** Tooltip text with its key in brackets, platform-true: `tipWithKey("Show
52
+ * scenes", "Mod+1")` is "Show scenes (⌘1)" on a Mac and "Show scenes
53
+ * (Ctrl+1)" elsewhere. For `data-tip`; the pane shell calls it itself when
54
+ * `PaneSideConfig.shortcutHint` is a combo. */
55
+ declare function tipWithKey(text: string, combo: string, platform?: KeyPlatform): string;
56
+
57
+ export { type HintBarItem, type KeyHintOptions, type KeyPlatform, hintBar, isKeyCombo, keyHint, keyLabel, keyLegends, keyPlatform, setKeyPlatform, tipWithKey };
package/dist/keys.js ADDED
@@ -0,0 +1,3 @@
1
+ export { hintBar, isKeyCombo, keyHint, keyLabel, keyLegends, keyPlatform, setKeyPlatform, tipWithKey } from './chunk-YWMSQFXX.js';
2
+ //# sourceMappingURL=keys.js.map
3
+ //# sourceMappingURL=keys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"keys.js"}