@rightkit/document-workspace 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE-APACHE +201 -0
  3. package/LICENSE-MIT +21 -0
  4. package/README.md +74 -2
  5. package/dist/app/App.d.ts +1 -0
  6. package/dist/app/App.js +6 -0
  7. package/dist/app/AppDialogs.d.ts +52 -0
  8. package/dist/app/AppDialogs.js +23 -0
  9. package/dist/app/AppDialogsHost.d.ts +31 -0
  10. package/dist/app/AppDialogsHost.js +30 -0
  11. package/dist/app/AppLifecycle.d.ts +19 -0
  12. package/dist/app/AppLifecycle.js +87 -0
  13. package/dist/app/AppProvider.d.ts +317 -0
  14. package/dist/app/AppProvider.js +217 -0
  15. package/dist/app/AppRoot.d.ts +8 -0
  16. package/dist/app/AppRoot.js +159 -0
  17. package/dist/app/AppShell.d.ts +2 -0
  18. package/dist/app/AppShell.js +81 -0
  19. package/dist/app/CommandPalette.d.ts +13 -0
  20. package/dist/app/CommandPalette.js +47 -0
  21. package/dist/app/DocActionBar.d.ts +36 -0
  22. package/dist/app/DocActionBar.js +300 -0
  23. package/dist/app/DocumentContextMenu.d.ts +29 -0
  24. package/dist/app/DocumentContextMenu.js +122 -0
  25. package/dist/app/DocumentPane.d.ts +52 -0
  26. package/dist/app/DocumentPane.js +30 -0
  27. package/dist/app/DocumentRouterTable.d.ts +58 -0
  28. package/dist/app/DocumentRouterTable.js +143 -0
  29. package/dist/app/ErrorBoundary.d.ts +12 -0
  30. package/dist/app/ErrorBoundary.js +16 -0
  31. package/dist/app/FindBar.d.ts +12 -0
  32. package/dist/app/FindBar.js +199 -0
  33. package/dist/app/LiveTitle.d.ts +14 -0
  34. package/dist/app/LiveTitle.js +14 -0
  35. package/dist/app/OpeningProgress.d.ts +13 -0
  36. package/dist/app/OpeningProgress.js +21 -0
  37. package/dist/app/PrintPreviewWindow.d.ts +2 -0
  38. package/dist/app/PrintPreviewWindow.js +57 -0
  39. package/dist/app/ShortcutHelp.d.ts +3 -0
  40. package/dist/app/ShortcutHelp.js +99 -0
  41. package/dist/app/Sidebar.d.ts +36 -0
  42. package/dist/app/Sidebar.js +121 -0
  43. package/dist/app/ToastViewport.d.ts +1 -0
  44. package/dist/app/ToastViewport.js +31 -0
  45. package/dist/app/Toolbar.d.ts +26 -0
  46. package/dist/app/Toolbar.js +63 -0
  47. package/dist/app/activeFormatSavers.d.ts +7 -0
  48. package/dist/app/activeFormatSavers.js +4 -0
  49. package/dist/app/clipboardPaths.d.ts +1 -0
  50. package/dist/app/clipboardPaths.js +31 -0
  51. package/dist/app/documentDrafts.d.ts +17 -0
  52. package/dist/app/documentDrafts.js +104 -0
  53. package/dist/app/draftResolution.d.ts +5 -0
  54. package/dist/app/draftResolution.js +25 -0
  55. package/dist/app/markdownPaletteActions.d.ts +8 -0
  56. package/dist/app/markdownPaletteActions.js +107 -0
  57. package/dist/app/resourceCensusDev.d.ts +41 -0
  58. package/dist/app/resourceCensusDev.js +518 -0
  59. package/dist/app/setupRecovery.d.ts +34 -0
  60. package/dist/app/setupRecovery.js +70 -0
  61. package/dist/app/shell/DocStatsPill.d.ts +18 -0
  62. package/dist/app/shell/DocStatsPill.js +32 -0
  63. package/dist/app/shell/DocumentTools.d.ts +9 -0
  64. package/dist/app/shell/DocumentTools.js +73 -0
  65. package/dist/app/shell/InfoInspector.d.ts +9 -0
  66. package/dist/app/shell/InfoInspector.js +67 -0
  67. package/dist/app/shell/StartPage.d.ts +9 -0
  68. package/dist/app/shell/StartPage.js +49 -0
  69. package/dist/app/shell/readEdit.d.ts +6 -0
  70. package/dist/app/shell/readEdit.js +6 -0
  71. package/dist/app/sidebar/SidebarDocumentMenu.d.ts +20 -0
  72. package/dist/app/sidebar/SidebarDocumentMenu.js +56 -0
  73. package/dist/app/sidebar/SidebarExpandedPanel.d.ts +9 -0
  74. package/dist/app/sidebar/SidebarExpandedPanel.js +78 -0
  75. package/dist/app/sidebar/SidebarIcons.d.ts +3 -0
  76. package/dist/app/sidebar/SidebarIcons.js +18 -0
  77. package/dist/app/sidebar/SidebarMenus.d.ts +19 -0
  78. package/dist/app/sidebar/SidebarMenus.js +64 -0
  79. package/dist/app/sidebar/SidebarPathUtils.d.ts +10 -0
  80. package/dist/app/sidebar/SidebarPathUtils.js +55 -0
  81. package/dist/app/sidebar/SidebarPrimitives.d.ts +12 -0
  82. package/dist/app/sidebar/SidebarPrimitives.js +45 -0
  83. package/dist/app/sidebar/SidebarRail.d.ts +5 -0
  84. package/dist/app/sidebar/SidebarRail.js +37 -0
  85. package/dist/app/sidebar/explorer/ExplorerRowMenu.d.ts +21 -0
  86. package/dist/app/sidebar/explorer/ExplorerRowMenu.js +37 -0
  87. package/dist/app/sidebar/explorer/ExplorerSections.d.ts +16 -0
  88. package/dist/app/sidebar/explorer/ExplorerSections.js +259 -0
  89. package/dist/app/sidebar/explorer/ExplorerTree.d.ts +46 -0
  90. package/dist/app/sidebar/explorer/ExplorerTree.js +387 -0
  91. package/dist/app/sidebar/explorer/explorerState.d.ts +37 -0
  92. package/dist/app/sidebar/explorer/explorerState.js +135 -0
  93. package/dist/app/tabMountPolicy.d.ts +45 -0
  94. package/dist/app/tabMountPolicy.js +104 -0
  95. package/dist/app/tabWarmOrder.d.ts +7 -0
  96. package/dist/app/tabWarmOrder.js +48 -0
  97. package/dist/app/tabs/TabFuseCurves.d.ts +3 -0
  98. package/dist/app/tabs/TabFuseCurves.js +15 -0
  99. package/dist/app/tabs/TabOverview.d.ts +24 -0
  100. package/dist/app/tabs/TabOverview.js +121 -0
  101. package/dist/app/tabs/TabStrip.d.ts +42 -0
  102. package/dist/app/tabs/TabStrip.js +380 -0
  103. package/dist/app/tabs/tabIds.d.ts +5 -0
  104. package/dist/app/tabs/tabIds.js +7 -0
  105. package/dist/app/tabs/useTabDirty.d.ts +3 -0
  106. package/dist/app/tabs/useTabDirty.js +11 -0
  107. package/dist/app/useAppChromeState.d.ts +21 -0
  108. package/dist/app/useAppChromeState.js +48 -0
  109. package/dist/app/useAppCommands.d.ts +66 -0
  110. package/dist/app/useAppCommands.js +71 -0
  111. package/dist/app/useAppDocumentCore.d.ts +210 -0
  112. package/dist/app/useAppDocumentCore.js +40 -0
  113. package/dist/app/useAppEffects.d.ts +18 -0
  114. package/dist/app/useAppEffects.js +52 -0
  115. package/dist/app/useAppPaneSearch.d.ts +37 -0
  116. package/dist/app/useAppPaneSearch.js +74 -0
  117. package/dist/app/useAppShellSetup.d.ts +40 -0
  118. package/dist/app/useAppShellSetup.js +52 -0
  119. package/dist/app/useAppSlots.d.ts +28 -0
  120. package/dist/app/useAppSlots.js +40 -0
  121. package/dist/app/useAppTabChrome.d.ts +51 -0
  122. package/dist/app/useAppTabChrome.js +66 -0
  123. package/dist/app/useAutoSave.d.ts +34 -0
  124. package/dist/app/useAutoSave.js +116 -0
  125. package/dist/app/useCloseAll.d.ts +52 -0
  126. package/dist/app/useCloseAll.js +295 -0
  127. package/dist/app/useClosedTabEviction.d.ts +10 -0
  128. package/dist/app/useClosedTabEviction.js +38 -0
  129. package/dist/app/useCommandPalette.d.ts +44 -0
  130. package/dist/app/useCommandPalette.js +254 -0
  131. package/dist/app/useCrossDocSearch.d.ts +7 -0
  132. package/dist/app/useCrossDocSearch.js +94 -0
  133. package/dist/app/useDistractionFree.d.ts +21 -0
  134. package/dist/app/useDistractionFree.js +73 -0
  135. package/dist/app/useDocumentCreation.d.ts +18 -0
  136. package/dist/app/useDocumentCreation.js +63 -0
  137. package/dist/app/useDocumentDirty.d.ts +31 -0
  138. package/dist/app/useDocumentDirty.js +46 -0
  139. package/dist/app/useDocumentLoader.d.ts +55 -0
  140. package/dist/app/useDocumentLoader.js +267 -0
  141. package/dist/app/useDocumentOutputActions.d.ts +6 -0
  142. package/dist/app/useDocumentOutputActions.js +45 -0
  143. package/dist/app/useDocumentRuntimeEffects.d.ts +36 -0
  144. package/dist/app/useDocumentRuntimeEffects.js +458 -0
  145. package/dist/app/useDocumentSaver.d.ts +58 -0
  146. package/dist/app/useDocumentSaver.js +511 -0
  147. package/dist/app/useDocumentTabs.d.ts +41 -0
  148. package/dist/app/useDocumentTabs.js +207 -0
  149. package/dist/app/useDocumentWatcher.d.ts +83 -0
  150. package/dist/app/useDocumentWatcher.js +393 -0
  151. package/dist/app/useDocumentZoom.d.ts +24 -0
  152. package/dist/app/useDocumentZoom.js +104 -0
  153. package/dist/app/useDocuments.d.ts +169 -0
  154. package/dist/app/useDocuments.js +455 -0
  155. package/dist/app/useDraftConfirmGate.d.ts +27 -0
  156. package/dist/app/useDraftConfirmGate.js +47 -0
  157. package/dist/app/useFileDragOverlay.d.ts +12 -0
  158. package/dist/app/useFileDragOverlay.js +49 -0
  159. package/dist/app/useFormatApis.d.ts +13 -0
  160. package/dist/app/useFormatApis.js +62 -0
  161. package/dist/app/useFormatDirtyState.d.ts +29 -0
  162. package/dist/app/useFormatDirtyState.js +81 -0
  163. package/dist/app/useGlobalShortcuts.d.ts +47 -0
  164. package/dist/app/useGlobalShortcuts.js +359 -0
  165. package/dist/app/useHtmlSession.d.ts +68 -0
  166. package/dist/app/useHtmlSession.js +463 -0
  167. package/dist/app/useHtmlSessionProps.d.ts +33 -0
  168. package/dist/app/useHtmlSessionProps.js +57 -0
  169. package/dist/app/useLibrary.d.ts +21 -0
  170. package/dist/app/useLibrary.js +81 -0
  171. package/dist/app/useOpenFromClipboard.d.ts +7 -0
  172. package/dist/app/useOpenFromClipboard.js +44 -0
  173. package/dist/app/usePaneLiveness.d.ts +29 -0
  174. package/dist/app/usePaneLiveness.js +199 -0
  175. package/dist/app/usePendingSavePaths.d.ts +19 -0
  176. package/dist/app/usePendingSavePaths.js +67 -0
  177. package/dist/app/useReadyShow.d.ts +12 -0
  178. package/dist/app/useReadyShow.js +142 -0
  179. package/dist/app/useSearchController.d.ts +22 -0
  180. package/dist/app/useSearchController.js +114 -0
  181. package/dist/app/useSetupRecovery.d.ts +41 -0
  182. package/dist/app/useSetupRecovery.js +153 -0
  183. package/dist/app/useTabChrome.d.ts +40 -0
  184. package/dist/app/useTabChrome.js +115 -0
  185. package/dist/app/useViewState.d.ts +27 -0
  186. package/dist/app/useViewState.js +160 -0
  187. package/dist/appName.d.ts +2 -0
  188. package/dist/appName.js +8 -0
  189. package/dist/documents/DocumentRouter.d.ts +102 -0
  190. package/dist/documents/DocumentRouter.js +153 -0
  191. package/dist/documents/FileTypeIcon.d.ts +6 -0
  192. package/dist/documents/FileTypeIcon.js +93 -0
  193. package/dist/documents/ImageDocument.d.ts +14 -0
  194. package/dist/documents/ImageDocument.js +61 -0
  195. package/dist/documents/capabilities.d.ts +17 -0
  196. package/dist/documents/capabilities.js +18 -0
  197. package/dist/documents/html/HtmlDocument.d.ts +15 -0
  198. package/dist/documents/html/HtmlDocument.js +584 -0
  199. package/dist/documents/html/HtmlDocumentLayout.d.ts +87 -0
  200. package/dist/documents/html/HtmlDocumentLayout.js +25 -0
  201. package/dist/documents/html/HtmlDocumentProps.d.ts +60 -0
  202. package/dist/documents/html/HtmlDocumentProps.js +1 -0
  203. package/dist/documents/html/HtmlDocumentSource.d.ts +34 -0
  204. package/dist/documents/html/HtmlDocumentSource.js +28 -0
  205. package/dist/documents/markdown/MarkdownSplitPane.d.ts +115 -0
  206. package/dist/documents/markdown/MarkdownSplitPane.js +352 -0
  207. package/dist/documents/mdx/MdxSplitPane.d.ts +32 -0
  208. package/dist/documents/mdx/MdxSplitPane.js +60 -0
  209. package/dist/documents/txt/LinkDialog.d.ts +8 -0
  210. package/dist/documents/txt/LinkDialog.js +34 -0
  211. package/dist/documents/txt/MarkdownSourceToolbar.d.ts +8 -0
  212. package/dist/documents/txt/MarkdownSourceToolbar.js +25 -0
  213. package/dist/documents/txt/SourceEditorToolbar.d.ts +12 -0
  214. package/dist/documents/txt/SourceEditorToolbar.js +4 -0
  215. package/dist/documents/txt/TxtEditor.d.ts +39 -0
  216. package/dist/documents/txt/TxtEditor.js +186 -0
  217. package/dist/documents/txt/sourceEditorTypes.d.ts +49 -0
  218. package/dist/documents/txt/sourceEditorTypes.js +1 -0
  219. package/dist/documents/txt/sourceExtensions.d.ts +38 -0
  220. package/dist/documents/txt/sourceExtensions.js +444 -0
  221. package/dist/documents/txt/sourceFormatting.d.ts +18 -0
  222. package/dist/documents/txt/sourceFormatting.js +183 -0
  223. package/dist/documents/txt/sourceTheme.d.ts +5 -0
  224. package/dist/documents/txt/sourceTheme.js +220 -0
  225. package/dist/documents/txt/useCodeMirrorSourceEditor.d.ts +69 -0
  226. package/dist/documents/txt/useCodeMirrorSourceEditor.js +546 -0
  227. package/dist/documents/txt/useSourceSearch.d.ts +39 -0
  228. package/dist/documents/txt/useSourceSearch.js +122 -0
  229. package/dist/documents/txt/useTextEditorCommands.d.ts +26 -0
  230. package/dist/documents/txt/useTextEditorCommands.js +151 -0
  231. package/dist/documents/web/LivePreviewPane.d.ts +11 -0
  232. package/dist/documents/web/LivePreviewPane.js +58 -0
  233. package/dist/env.d.ts +66 -0
  234. package/dist/index.d.ts +121 -0
  235. package/dist/index.js +123 -0
  236. package/dist/settings/AppearanceMenu.d.ts +9 -0
  237. package/dist/settings/AppearanceMenu.js +54 -0
  238. package/dist/settings/SettingsPanel.d.ts +7 -0
  239. package/dist/settings/SettingsPanel.js +104 -0
  240. package/dist/settings/publishedSections.d.ts +22 -0
  241. package/dist/settings/publishedSections.js +79 -0
  242. package/dist/settings/sections/AboutSection.d.ts +1 -0
  243. package/dist/settings/sections/AboutSection.js +13 -0
  244. package/dist/settings/sections/DiagnosticsSection.d.ts +1 -0
  245. package/dist/settings/sections/DiagnosticsSection.js +42 -0
  246. package/dist/settings/sections/PrivacySection.d.ts +3 -0
  247. package/dist/settings/sections/PrivacySection.js +51 -0
  248. package/dist/settings/sections/UpdateLicenseBlock.d.ts +10 -0
  249. package/dist/settings/sections/UpdateLicenseBlock.js +158 -0
  250. package/dist/state/explorer.d.ts +32 -0
  251. package/dist/state/explorer.js +111 -0
  252. package/dist/state/legal.d.ts +51 -0
  253. package/dist/state/legal.js +130 -0
  254. package/dist/state/library.d.ts +33 -0
  255. package/dist/state/library.js +110 -0
  256. package/dist/state/updateLicense.d.ts +31 -0
  257. package/dist/state/updateLicense.js +340 -0
  258. package/dist/styles/explorer.css +219 -0
  259. package/dist/styles/index.css +1477 -0
  260. package/dist/styles/tabs.css +467 -0
  261. package/dist/styles.css +6 -0
  262. package/package.json +88 -3
@@ -0,0 +1,380 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Horizontal open-document tab strip for the fused titlebar (Part A of the
3
+ // tabs+explorer redesign — see docs/design/viewright-tabs-explorer-brief.md and the
4
+ // visual spec docs/design/viewright-tabs-explorer.html). `docs` (App's openDocs order)
5
+ // IS the tab order — the strip is a view of that state, never a second source of truth.
6
+ // The active tab fuses into the page with the concave bottom-corner curves from
7
+ // TabFuseCurves; inactive tabs sit on the chrome band with quiet hairline separators.
8
+ // Styles: src/styles/tabs.css (imported below so the strip carries its own CSS).
9
+ import { tabDomId } from "./tabIds.js";
10
+ import { memo, useCallback, useEffect, useRef, useState, } from "react";
11
+ import { CaretLeft } from "@phosphor-icons/react/dist/icons/CaretLeft";
12
+ import { CaretRight } from "@phosphor-icons/react/dist/icons/CaretRight";
13
+ import { Cards } from "@phosphor-icons/react/dist/icons/Cards";
14
+ import { X } from "@phosphor-icons/react/dist/icons/X";
15
+ import { isUntitled } from "@rightkit/document-core/documents/types";
16
+ import { FileTypeIcon } from "../../documents/FileTypeIcon.js";
17
+ import { shortcutLabel, isMac } from "@rightkit/document-core/platform";
18
+ import { LiveTitle } from "../LiveTitle.js";
19
+ import { useTabDirty } from "./useTabDirty.js";
20
+ import { DocumentMenu } from "../sidebar/SidebarDocumentMenu.js";
21
+ import { TabFuseCurves } from "./TabFuseCurves.js";
22
+ import { TabOverview } from "./TabOverview.js";
23
+ /** Pointer distance (px) before a tab press becomes a drag-reorder. */
24
+ const DRAG_THRESHOLD = 4;
25
+ // Memoized for the same reason Sidebar/Toolbar are: App re-renders far more often than
26
+ // the strip's inputs change (every keystroke). All props arrive pre-stabilized via the
27
+ // integrator's useMemo.
28
+ export const TabStrip = memo(TabStripImpl);
29
+ function TabStripImpl({ docs, activePath, sidebarCollapsed, onActivate, onRequestClose, onCloseAll, onCloseOthers, onCloseRight, onReorderTabs, onTogglePin, pinnedPaths, openLocalPaths, titleFor, isDirty, dirtyPaths, subscribeDirty, subscribeEdits, getEditSnapshot, computeTitle, overviewOpen, onOverviewOpenChange, onClosePaths, }) {
30
+ const listRef = useRef(null);
31
+ const overviewBtnRef = useRef(null);
32
+ const pointerDrag = useRef(null);
33
+ const suppressClickPath = useRef(null);
34
+ // Path of the tab that last held DOM focus — lets a removed focused tab hand focus
35
+ // to its replacement instead of stranding focus on <body>.
36
+ const lastFocusedTab = useRef(null);
37
+ const [focusPath, setFocusPath] = useState(null);
38
+ const [draggingPath, setDraggingPath] = useState(null);
39
+ const [dropTarget, setDropTarget] = useState(null);
40
+ const [docMenu, setDocMenu] = useState(null);
41
+ const mac = isMac();
42
+ const tabElFor = useCallback((path) => {
43
+ const list = listRef.current;
44
+ if (!list)
45
+ return null;
46
+ // dataset compare — paths can hold quotes/backslashes that defeat attribute selectors.
47
+ for (const el of list.querySelectorAll("[data-vr-tab]")) {
48
+ if (el.dataset.vrTabPath === path)
49
+ return el;
50
+ }
51
+ return null;
52
+ }, []);
53
+ // Roving tabindex: the tabbable tab is the last-focused one while it's still open,
54
+ // else the active tab, else the first — so the strip is always one Tab stop.
55
+ const rovingPath = focusPath && docs.some((d) => d.path === focusPath)
56
+ ? focusPath
57
+ : activePath && docs.some((d) => d.path === activePath)
58
+ ? activePath
59
+ : docs[0]?.path;
60
+ // The selected tab owns the roving slot whenever activation happened off-strip
61
+ // (Ctrl+Tab, palette, jumpToTab) — Tab into the strip lands on the selected tab.
62
+ useEffect(() => {
63
+ const focusInStrip = !!listRef.current?.contains(document.activeElement);
64
+ setFocusPath((current) => focusInStrip && current && docs.some((d) => d.path === current) ? current : activePath);
65
+ }, [activePath, docs]);
66
+ // Focused tab closed → move focus to the replacement (the new active tab, else the
67
+ // first) — but only when focus was stranded on <body>, never stealing it back.
68
+ useEffect(() => {
69
+ const focused = lastFocusedTab.current;
70
+ if (!focused)
71
+ return;
72
+ if (docs.some((d) => d.path === focused))
73
+ return;
74
+ lastFocusedTab.current = null;
75
+ if (document.activeElement && document.activeElement !== document.body)
76
+ return;
77
+ const replacement = activePath && docs.some((d) => d.path === activePath) ? activePath : docs[0]?.path;
78
+ if (replacement)
79
+ tabElFor(replacement)?.focus();
80
+ }, [docs, activePath, tabElFor]);
81
+ // Keep the selected tab in view (activations via Ctrl+Tab / menu / palette too).
82
+ // biome-ignore lint/correctness/useExhaustiveDependencies: docs is the trigger so a reordered active tab re-scrolls into view
83
+ useEffect(() => {
84
+ if (!activePath)
85
+ return;
86
+ const scroller = listRef.current;
87
+ const reveal = () => revealTab(scroller, tabElFor(activePath));
88
+ reveal();
89
+ // Width changes (window resize, sidebar toggle, launch-time font/layout settle) can push
90
+ // the active tab under the "+" without an activation — re-reveal on every resize.
91
+ if (!scroller || typeof ResizeObserver === "undefined")
92
+ return;
93
+ const observer = new ResizeObserver(reveal);
94
+ observer.observe(scroller);
95
+ return () => observer.disconnect();
96
+ }, [activePath, docs, tabElFor]);
97
+ // ── Pointer drag-reorder (pointer events, NOT HTML5 DnD) ──────────────────
98
+ // Same state machine as the sidebar's vertical list, hit-testing horizontally:
99
+ // the insertion point is the first sibling tab whose left half contains clientX —
100
+ // "before" it — else "after" the last tab.
101
+ const dropTargetAt = useCallback((clientX, fromPath) => {
102
+ const list = listRef.current;
103
+ if (!list)
104
+ return null;
105
+ const els = [...list.querySelectorAll("[data-vr-tab]")]
106
+ .map((el) => ({ path: el.dataset.vrTabPath, box: el.getBoundingClientRect() }))
107
+ .filter((x) => Boolean(x.path) && x.path !== fromPath);
108
+ for (const { path, box } of els) {
109
+ if (clientX < box.left + box.width / 2) {
110
+ return { toPath: path, position: "before" };
111
+ }
112
+ }
113
+ const last = els[els.length - 1];
114
+ return last ? { toPath: last.path, position: "after" } : null;
115
+ }, []);
116
+ const beginPointerTabDrag = useCallback((path, event) => {
117
+ if (event.button !== 0)
118
+ return;
119
+ if (event.target?.closest?.("[data-vr-tab-action]"))
120
+ return;
121
+ const element = event.currentTarget;
122
+ try {
123
+ element.setPointerCapture?.(event.pointerId);
124
+ }
125
+ catch {
126
+ /* jsdom / synthetic pointers may lack capture */
127
+ }
128
+ pointerDrag.current = {
129
+ element,
130
+ path,
131
+ pointerId: event.pointerId,
132
+ startX: event.clientX,
133
+ startY: event.clientY,
134
+ moved: false,
135
+ };
136
+ }, []);
137
+ const consumeSuppressedClick = useCallback((path, event) => {
138
+ if (suppressClickPath.current !== path)
139
+ return false;
140
+ event.preventDefault();
141
+ event.stopPropagation();
142
+ suppressClickPath.current = null;
143
+ return true;
144
+ }, []);
145
+ useEffect(() => {
146
+ const onMove = (event) => {
147
+ const drag = pointerDrag.current;
148
+ if (!drag || event.pointerId !== drag.pointerId)
149
+ return;
150
+ if (!drag.moved &&
151
+ Math.hypot(event.clientX - drag.startX, event.clientY - drag.startY) <= DRAG_THRESHOLD) {
152
+ return;
153
+ }
154
+ if (!drag.moved) {
155
+ drag.moved = true;
156
+ setDraggingPath(drag.path);
157
+ document.body.style.userSelect = "none";
158
+ }
159
+ const target = dropTargetAt(event.clientX, drag.path);
160
+ setDropTarget((current) => current?.toPath === target?.toPath && current?.position === target?.position
161
+ ? current
162
+ : target);
163
+ event.preventDefault();
164
+ };
165
+ const onUp = (event) => {
166
+ const drag = pointerDrag.current;
167
+ pointerDrag.current = null;
168
+ if (!drag)
169
+ return;
170
+ try {
171
+ drag.element.releasePointerCapture?.(drag.pointerId);
172
+ }
173
+ catch {
174
+ /* jsdom */
175
+ }
176
+ document.body.style.userSelect = "";
177
+ setDropTarget(null);
178
+ setDraggingPath(null);
179
+ if (!drag.moved)
180
+ return;
181
+ event.preventDefault();
182
+ // Only swallow the click when the drag lands on a REAL reorder target — a jitter
183
+ // past the threshold with no target should still let the tab ACTIVATE (mirrors
184
+ // Sidebar's fix for the "tab activation is laggy" report). The horizontal model
185
+ // always resolves SOME before/after target, so a drop that leaves the tab where it
186
+ // already sits (before its current right neighbor / after its current left one)
187
+ // counts as a no-op too — no reorder call, no click suppression.
188
+ const target = dropTargetAt(event.clientX, drag.path);
189
+ if (target) {
190
+ const fromIndex = docs.findIndex((d) => d.path === drag.path);
191
+ const toIndex = docs.findIndex((d) => d.path === target.toPath);
192
+ const isNoOp = (target.position === "before" && toIndex === fromIndex + 1) ||
193
+ (target.position === "after" && toIndex === fromIndex - 1);
194
+ if (!isNoOp) {
195
+ suppressClickPath.current = drag.path;
196
+ window.setTimeout(() => {
197
+ if (suppressClickPath.current === drag.path)
198
+ suppressClickPath.current = null;
199
+ }, 0);
200
+ onReorderTabs(drag.path, target.toPath, target.position);
201
+ }
202
+ }
203
+ };
204
+ const onCancel = (event) => {
205
+ const drag = pointerDrag.current;
206
+ if (!drag || event.pointerId !== drag.pointerId)
207
+ return;
208
+ pointerDrag.current = null;
209
+ try {
210
+ drag.element.releasePointerCapture?.(drag.pointerId);
211
+ }
212
+ catch {
213
+ /* jsdom */
214
+ }
215
+ document.body.style.userSelect = "";
216
+ setDropTarget(null);
217
+ setDraggingPath(null);
218
+ };
219
+ window.addEventListener("pointermove", onMove);
220
+ window.addEventListener("pointerup", onUp);
221
+ window.addEventListener("pointercancel", onCancel);
222
+ return () => {
223
+ window.removeEventListener("pointermove", onMove);
224
+ window.removeEventListener("pointerup", onUp);
225
+ window.removeEventListener("pointercancel", onCancel);
226
+ document.body.style.userSelect = "";
227
+ };
228
+ }, [docs, dropTargetAt, onReorderTabs]);
229
+ // Vertical wheel/trackpad delta scrolls the strip horizontally (trackpads also send
230
+ // deltaX — both fold into scrollLeft). No preventDefault: React's wheel listener is
231
+ // passive and the header never scrolls vertically anyway.
232
+ const onWheel = useCallback((event) => {
233
+ const el = listRef.current;
234
+ if (!el)
235
+ return;
236
+ const unit = event.deltaMode === 1 ? 16 : 1; // DOM_DELTA_LINE → ~16px per line
237
+ const delta = (event.deltaY + event.deltaX) * unit;
238
+ if (delta)
239
+ el.scrollLeft += delta;
240
+ }, []);
241
+ const onScroll = useCallback(() => syncScrolledEdge(listRef.current), []);
242
+ // ── Keyboard: roving tabindex + automatic activation (ARIA tabs pattern) ──
243
+ const onListKeyDown = useCallback((event) => {
244
+ const tabEl = event.target?.closest?.("[data-vr-tab]");
245
+ if (!tabEl)
246
+ return;
247
+ const path = tabEl.dataset.vrTabPath;
248
+ if (!path)
249
+ return;
250
+ const index = docs.findIndex((d) => d.path === path);
251
+ if (index < 0)
252
+ return;
253
+ let nextIndex = null;
254
+ switch (event.key) {
255
+ case "ArrowRight":
256
+ nextIndex = (index + 1) % docs.length;
257
+ break;
258
+ case "ArrowLeft":
259
+ nextIndex = (index - 1 + docs.length) % docs.length;
260
+ break;
261
+ case "Home":
262
+ nextIndex = 0;
263
+ break;
264
+ case "End":
265
+ nextIndex = docs.length - 1;
266
+ break;
267
+ case "Delete":
268
+ case "Backspace":
269
+ event.preventDefault();
270
+ onRequestClose(path);
271
+ return;
272
+ case "Enter":
273
+ case " ":
274
+ // A real <button> inside the tab (the ×) handles its own Enter/Space — don't
275
+ // double-fire activate + close for the same keypress.
276
+ if (event.target.tagName === "BUTTON")
277
+ return;
278
+ event.preventDefault();
279
+ onActivate(path);
280
+ return;
281
+ default:
282
+ return;
283
+ }
284
+ event.preventDefault();
285
+ const next = docs[nextIndex];
286
+ if (!next)
287
+ return;
288
+ // Automatic activation: focus moves and the tab is selected in the same step.
289
+ tabElFor(next.path)?.focus();
290
+ onActivate(next.path);
291
+ }, [docs, onActivate, onRequestClose, tabElFor]);
292
+ const onTabFocused = useCallback((path) => {
293
+ lastFocusedTab.current = path;
294
+ setFocusPath(path);
295
+ }, []);
296
+ const onTabBlurred = useCallback((relatedTarget) => {
297
+ const node = relatedTarget;
298
+ if (!node)
299
+ return; // focus went nowhere/nowhere visible — the docs-change effect decides
300
+ if (listRef.current?.contains(node))
301
+ return; // moved to a sibling tab / the × inside it
302
+ lastFocusedTab.current = null;
303
+ }, []);
304
+ const openDocMenu = useCallback((doc, event) => {
305
+ event.preventDefault();
306
+ onOverviewOpenChange(false);
307
+ setDocMenu({ doc, x: event.clientX, y: event.clientY });
308
+ }, [onOverviewOpenChange]);
309
+ // Bound to the menu's doc so the same menu instance works for any tab.
310
+ const closeMenuOthers = useCallback(() => {
311
+ if (docMenu)
312
+ onCloseOthers(docMenu.doc.path);
313
+ }, [docMenu, onCloseOthers]);
314
+ const closeMenuRight = useCallback(() => {
315
+ if (docMenu)
316
+ onCloseRight(docMenu.doc.path);
317
+ }, [docMenu, onCloseRight]);
318
+ return (_jsxs("div", { className: "vr-tabstrip", "data-tauri-drag-region": true, children: [sidebarCollapsed && mac && (_jsx("div", { className: "vr-tabstrip-lead-lights", "data-tauri-drag-region": true })), _jsx("div", { className: "vr-tabstrip-ov", "data-tauri-drag-region": true, children: _jsxs("button", { ref: overviewBtnRef, type: "button", className: "vr-ov-btn vr-press vr-focus", onClick: () => onOverviewOpenChange(!overviewOpen), title: `Show all tabs (${shortcutLabel("mod+shift+a")})`, "aria-label": "Show all tabs", "aria-haspopup": "dialog", "aria-expanded": overviewOpen, children: [_jsx(Cards, { size: 15, weight: "regular" }), docs.length > 3 && _jsx("span", { className: "vr-ov-count", children: docs.length })] }) }), _jsx("button", { type: "button", className: "vr-tab-arrow vr-tab-arrow-start vr-press vr-focus", onClick: () => scrollStrip(listRef.current, -1), title: "Scroll tabs left", "aria-label": "Scroll tabs left", tabIndex: -1, children: _jsx(CaretLeft, { size: 13, weight: "bold" }) }), _jsx("div", { ref: listRef, className: "vr-tabs", "data-tauri-drag-region": true, onWheel: onWheel, onScroll: onScroll, onKeyDown: onListKeyDown, children: _jsx("div", { role: "tablist", "aria-label": "Open documents", "aria-orientation": "horizontal", className: "vr-tabs-list", children: docs.map((doc) => (_jsx(Tab, { doc: doc, title: titleFor(doc), titleNode: isUntitled(doc.path) ? (_jsx(LiveTitle, { doc: doc, compute: computeTitle, subscribeEdits: subscribeEdits, getEditSnapshot: getEditSnapshot })) : undefined, isDirty: isDirty, dirtyHint: dirtyPaths.has(doc.path), subscribeDirty: subscribeDirty, active: doc.path === activePath, tabbable: doc.path === rovingPath, dragging: draggingPath === doc.path, dropPosition: dropTarget?.toPath === doc.path ? dropTarget.position : undefined, onActivate: onActivate, onRequestClose: onRequestClose, onPointerDragStart: beginPointerTabDrag, onConsumeSuppressedClick: consumeSuppressedClick, onContextMenu: openDocMenu, onFocused: onTabFocused, onBlurred: onTabBlurred }, doc.tabKey ?? doc.path))) }) }), _jsx("button", { type: "button", className: "vr-tab-arrow vr-tab-arrow-end vr-press vr-focus", onClick: () => scrollStrip(listRef.current, 1), title: "Scroll tabs right", "aria-label": "Scroll tabs right", tabIndex: -1, children: _jsx(CaretRight, { size: 13, weight: "bold" }) }), _jsx("div", { className: "vr-tabstrip-fill", "data-tauri-drag-region": true }), overviewOpen && (_jsx(TabOverview, { docs: docs, activePath: activePath, titleFor: titleFor, isDirty: isDirty, dirtyPaths: dirtyPaths, subscribeDirty: subscribeDirty, subscribeEdits: subscribeEdits, getEditSnapshot: getEditSnapshot, computeTitle: computeTitle, onActivate: onActivate, onClosePaths: onClosePaths, onClose: () => onOverviewOpenChange(false), returnFocusRef: overviewBtnRef })), docMenu && (_jsx(DocumentMenu, { menu: docMenu, openLocalPaths: openLocalPaths, pinned: pinnedPaths.has(docMenu.doc.path), onClose: () => setDocMenu(null), onActivate: onActivate, onCloseDoc: onRequestClose, onCloseAll: onCloseAll, onTogglePin: onTogglePin, titleFor: titleFor, onCloseOthers: closeMenuOthers, onCloseRight: closeMenuRight }))] }));
319
+ }
320
+ const Tab = memo(TabImpl);
321
+ // One tab: format icon tile + name (masked fade, no ellipsis) + dirty dot + close ×.
322
+ // Document-keyed id (tabDomId) pairs with the document pane's aria-labelledby, so the pane's
323
+ // label names the selected tab even when tabs reorder.
324
+ function TabImpl({ doc, title, titleNode, isDirty, dirtyHint, subscribeDirty, active, tabbable, dragging, dropPosition, onActivate, onRequestClose, onPointerDragStart, onConsumeSuppressedClick, onContextMenu, onFocused, onBlurred, }) {
325
+ const dirty = useTabDirty(doc, dirtyHint, isDirty, subscribeDirty);
326
+ const accessibleTitle = `${title}${dirty ? " — Unsaved edits" : ""}`;
327
+ const tooltip = doc.format === "web" ? (doc.web?.url ?? doc.path) : doc.path;
328
+ return (_jsxs("div", { role: "tab", id: tabDomId(doc), "aria-selected": active, "aria-controls": "vr-doc-pane", "aria-label": accessibleTitle, tabIndex: tabbable ? 0 : -1, title: tooltip, "data-vr-tab": true, "data-vr-tab-path": doc.path, "data-vr-tab-active": active || undefined, className: `vr-tab${active ? " vr-tab-active" : ""}${dirty ? " vr-tab-dirty" : ""}${dragging ? " vr-tab-dragging" : ""}`, onPointerDown: (event) => onPointerDragStart(doc.path, event), onClick: (event) => {
329
+ if (event.target?.closest?.("[data-vr-tab-action]")) {
330
+ return;
331
+ }
332
+ if (onConsumeSuppressedClick(doc.path, event))
333
+ return;
334
+ onActivate(doc.path);
335
+ }, onAuxClick: (event) => {
336
+ // Middle-click closes — anywhere on the tab, including over the ×.
337
+ if (event.button !== 1)
338
+ return;
339
+ event.preventDefault();
340
+ onRequestClose(doc.path);
341
+ }, onContextMenu: (event) => onContextMenu(doc, event), onFocus: () => onFocused(doc.path), onBlur: (event) => onBlurred(event.relatedTarget), children: [active && _jsx(TabFuseCurves, { size: 12 }), _jsx("span", { className: "vr-tab-ficon", "aria-hidden": "true", children: _jsx(FileTypeIcon, { format: doc.format, path: doc.path, size: 16 }) }), _jsx("span", { className: "vr-tab-name", children: titleNode ?? title }), _jsx("span", { className: "vr-tab-dot", "data-vr-tab-dirty-indicator": dirty || undefined }), _jsx("button", { type: "button", "data-vr-tab-action": true, className: "vr-tab-x vr-press vr-focus", "aria-label": `Close ${title}`, title: "Close", onClick: (event) => {
342
+ event.stopPropagation();
343
+ onRequestClose(doc.path);
344
+ }, children: _jsx(X, { size: 12, weight: "bold" }) }), dropPosition && (_jsx("span", { className: "vr-tab-dropbar", "data-pos": dropPosition, "aria-hidden": "true" }))] }));
345
+ }
346
+ /** Marks which ends of the scroller have tabs hidden past them, so CSS fades those edges
347
+ * instead of slicing a tab. Set directly on the element: scrolling must not re-render. */
348
+ function syncScrolledEdge(el) {
349
+ if (!el)
350
+ return;
351
+ if (el.scrollLeft > 2)
352
+ el.dataset.vrScrolled = "";
353
+ else
354
+ delete el.dataset.vrScrolled;
355
+ if (el.scrollLeft + el.clientWidth < el.scrollWidth - 2)
356
+ el.dataset.vrMoreEnd = "";
357
+ else
358
+ delete el.dataset.vrMoreEnd;
359
+ }
360
+ /** Scroll only the strip (never ancestors, unlike scrollIntoView) so `tab` sits clear of
361
+ * both edge fades. */
362
+ const REVEAL_PAD_START = 28;
363
+ const REVEAL_PAD_END = 28;
364
+ function revealTab(scroller, tab) {
365
+ if (!scroller || !tab)
366
+ return;
367
+ const s = scroller.getBoundingClientRect();
368
+ const t = tab.getBoundingClientRect();
369
+ if (t.left < s.left + REVEAL_PAD_START)
370
+ scroller.scrollLeft -= s.left + REVEAL_PAD_START - t.left;
371
+ else if (t.right > s.right - REVEAL_PAD_END)
372
+ scroller.scrollLeft += t.right - (s.right - REVEAL_PAD_END);
373
+ syncScrolledEdge(scroller);
374
+ }
375
+ /** Page the strip by most of its visible width (the ‹ › buttons shown while tabs overflow). */
376
+ function scrollStrip(el, direction) {
377
+ if (!el)
378
+ return;
379
+ el.scrollBy({ left: direction * Math.max(120, el.clientWidth * 0.7), behavior: "smooth" });
380
+ }
@@ -0,0 +1,5 @@
1
+ import type { LoadedDocument } from "@rightkit/document-core/documents/types";
2
+ /** DOM id for a document's tab, keyed by the document's stable identity (tabKey, else path) so it
3
+ * never shifts when tabs reorder. Characters outside [A-Za-z0-9_-] are encoded: ids are used in
4
+ * space-separated aria idref lists. */
5
+ export declare function tabDomId(doc: Pick<LoadedDocument, "tabKey" | "path">): string;
@@ -0,0 +1,7 @@
1
+ /** DOM id for a document's tab, keyed by the document's stable identity (tabKey, else path) so it
2
+ * never shifts when tabs reorder. Characters outside [A-Za-z0-9_-] are encoded: ids are used in
3
+ * space-separated aria idref lists. */
4
+ export function tabDomId(doc) {
5
+ const raw = doc.tabKey ?? doc.path;
6
+ return `vr-tab-${raw.replace(/[^A-Za-z0-9_-]/g, (c) => `_${c.charCodeAt(0).toString(16)}_`)}`;
7
+ }
@@ -0,0 +1,3 @@
1
+ import type { LoadedDocument } from "@rightkit/document-core/documents/types";
2
+ /** Live dirty state for a tab: the caller's hint OR the subscribed per-document dirty signal. */
3
+ export declare function useTabDirty(doc: LoadedDocument, dirtyHint: boolean, isDirty: (doc: LoadedDocument) => boolean, subscribeDirty: (path: string, cb: () => void) => () => void): boolean;
@@ -0,0 +1,11 @@
1
+ import { useEffect, useState } from "react";
2
+ /** Live dirty state for a tab: the caller's hint OR the subscribed per-document dirty signal. */
3
+ export function useTabDirty(doc, dirtyHint, isDirty, subscribeDirty) {
4
+ const [observedDirty, setObservedDirty] = useState(() => isDirty(doc));
5
+ useEffect(() => {
6
+ const sync = () => setObservedDirty(isDirty(doc));
7
+ sync();
8
+ return subscribeDirty(doc.path, sync);
9
+ }, [doc, isDirty, subscribeDirty]);
10
+ return dirtyHint || observedDirty;
11
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Overlay, panel, and dialog flags plus the Info-inspector callbacks. Extracted verbatim from useAppState;
3
+ * runs right after the document core, matching the original call order.
4
+ */
5
+ export declare function useAppChromeState(): {
6
+ settingsOpen: boolean;
7
+ setSettingsOpen: import("react").Dispatch<import("react").SetStateAction<boolean>>;
8
+ paletteOpen: boolean;
9
+ setPaletteOpen: import("react").Dispatch<import("react").SetStateAction<boolean>>;
10
+ helpOpen: boolean;
11
+ setHelpOpen: import("react").Dispatch<import("react").SetStateAction<boolean>>;
12
+ openUrlDialog: boolean;
13
+ setOpenUrlDialog: import("react").Dispatch<import("react").SetStateAction<boolean>>;
14
+ tabOverviewOpen: boolean;
15
+ setTabOverviewOpen: import("react").Dispatch<import("react").SetStateAction<boolean>>;
16
+ sidebarCollapsed: boolean;
17
+ setSidebarCollapsed: import("react").Dispatch<import("react").SetStateAction<boolean>>;
18
+ inspectorOpen: boolean;
19
+ toggleInspector: () => void;
20
+ closeInspector: () => void;
21
+ };
@@ -0,0 +1,48 @@
1
+ import { useCallback, useState } from "react";
2
+ import { useMediaQuery } from "@rightkit/react-shell/react";
3
+ function initialSidebarCollapsed(compact) {
4
+ if (typeof window === "undefined")
5
+ return false;
6
+ const pinned = localStorage.getItem("vr.sidebar.pinned");
7
+ return pinned === "0" || compact;
8
+ }
9
+ /**
10
+ * Overlay, panel, and dialog flags plus the Info-inspector callbacks. Extracted verbatim from useAppState;
11
+ * runs right after the document core, matching the original call order.
12
+ */
13
+ export function useAppChromeState() {
14
+ const compactViewport = useMediaQuery("(max-width: 640px)");
15
+ const [settingsOpen, setSettingsOpen] = useState(false);
16
+ const [paletteOpen, setPaletteOpen] = useState(false);
17
+ const [helpOpen, setHelpOpen] = useState(false);
18
+ const [openUrlDialog, setOpenUrlDialog] = useState(false);
19
+ // Titlebar tab-overview card grid (⌘⇧A / the strip's cards button).
20
+ const [tabOverviewOpen, setTabOverviewOpen] = useState(false);
21
+ const [sidebarCollapsed, setSidebarCollapsed] = useState(() => initialSidebarCollapsed(compactViewport));
22
+ // Wave 3 Info inspector — one global open flag (per-document persistence is not required). The
23
+ // toggle lives in the right toolbar capsule (DocumentTools); Escape inside the panel routes here,
24
+ // and closeInspector returns focus to that toggle button.
25
+ const [inspectorOpen, setInspectorOpen] = useState(false);
26
+ const closeInspector = useCallback(() => {
27
+ setInspectorOpen(false);
28
+ document.getElementById("vr-inspector-toggle")?.focus();
29
+ }, []);
30
+ const toggleInspector = useCallback(() => setInspectorOpen((o) => !o), []);
31
+ return {
32
+ settingsOpen,
33
+ setSettingsOpen,
34
+ paletteOpen,
35
+ setPaletteOpen,
36
+ helpOpen,
37
+ setHelpOpen,
38
+ openUrlDialog,
39
+ setOpenUrlDialog,
40
+ tabOverviewOpen,
41
+ setTabOverviewOpen,
42
+ sidebarCollapsed,
43
+ setSidebarCollapsed,
44
+ inspectorOpen,
45
+ toggleInspector,
46
+ closeInspector,
47
+ };
48
+ }
@@ -0,0 +1,66 @@
1
+ import type { Dispatch, SetStateAction } from "react";
2
+ import type { LoadedDocument } from "@rightkit/document-core/documents/types";
3
+ import type { createBlockDraftRegistry } from "@rightkit/document-core/app/blockDrafts";
4
+ import type { useDocuments } from "./useDocuments.js";
5
+ import type { useFormatApis } from "./useFormatApis.js";
6
+ import type { useLibrary } from "./useLibrary.js";
7
+ import type { useOpenFromClipboard } from "./useOpenFromClipboard.js";
8
+ import type { useSettings } from "@rightkit/document-core/settings/settings";
9
+ import type { useViewState } from "./useViewState.js";
10
+ import type { useAppTabChrome } from "./useAppTabChrome.js";
11
+ import type { useAppPaneSearch } from "./useAppPaneSearch.js";
12
+ type Docs = ReturnType<typeof useDocuments>;
13
+ type Formats = ReturnType<typeof useFormatApis>;
14
+ type Setting = ReturnType<typeof useSettings>;
15
+ type Tab = ReturnType<typeof useAppTabChrome>;
16
+ type Pane = ReturnType<typeof useAppPaneSearch>;
17
+ type Setter = Dispatch<SetStateAction<boolean>>;
18
+ export interface AppCommandsInput {
19
+ docs: Docs;
20
+ openDocs: LoadedDocument[];
21
+ recents: ReturnType<typeof useLibrary>["recents"];
22
+ active: LoadedDocument | null;
23
+ activeEditable: boolean;
24
+ activeTextLike: boolean;
25
+ activeText: string;
26
+ copyContents: (() => void) | null;
27
+ settings: Setting["settings"];
28
+ update: Setting["update"];
29
+ onChangeView: ReturnType<typeof useViewState>["requestViewChange"];
30
+ docxMenu: Formats["docxMenu"];
31
+ markdownExport: Formats["markdownExport"];
32
+ handlePrint: Formats["handlePrint"];
33
+ openFromClipboard: ReturnType<typeof useOpenFromClipboard>;
34
+ cycleTab: Tab["cycleTab"];
35
+ jumpToTab: Tab["jumpToTab"];
36
+ blockDrafts: ReturnType<typeof createBlockDraftRegistry>;
37
+ searchProvider: Pane["searchProvider"];
38
+ canOpenFind: Pane["canOpenFind"];
39
+ findOpen: Pane["findOpen"];
40
+ openFind: Pane["openFind"];
41
+ settingsOpen: boolean;
42
+ setSettingsOpen: Setter;
43
+ paletteOpen: boolean;
44
+ setPaletteOpen: Setter;
45
+ helpOpen: boolean;
46
+ setHelpOpen: Setter;
47
+ openUrlDialog: boolean;
48
+ setOpenUrlDialog: Setter;
49
+ setTabOverviewOpen: Setter;
50
+ }
51
+ /**
52
+ * Command palette, distraction-free mode, and global keyboard shortcuts. Extracted verbatim from
53
+ * useAppState; the three hooks run in the original order (palette → distraction-free → shortcuts).
54
+ */
55
+ export declare function useAppCommands({ docs, openDocs, recents, active, activeEditable, activeTextLike, activeText, copyContents, settings, update, onChangeView, docxMenu, markdownExport, handlePrint, openFromClipboard, cycleTab, jumpToTab, blockDrafts, searchProvider, canOpenFind, findOpen, openFind, settingsOpen, setSettingsOpen, paletteOpen, setPaletteOpen, helpOpen, setHelpOpen, openUrlDialog, setOpenUrlDialog, setTabOverviewOpen, }: AppCommandsInput): {
56
+ paletteItems: import("./CommandPalette.js").PaletteItem[];
57
+ distractionFree: boolean;
58
+ toggleDistractionFree: () => void;
59
+ fullscreenConfirm: {
60
+ onDone: () => Promise<void>;
61
+ onDiscard: () => Promise<void>;
62
+ onStay: () => void;
63
+ error: string | null;
64
+ } | null;
65
+ };
66
+ export {};
@@ -0,0 +1,71 @@
1
+ import { useCommandPalette } from "./useCommandPalette.js";
2
+ import { useDistractionFree } from "./useDistractionFree.js";
3
+ import { useGlobalShortcuts } from "./useGlobalShortcuts.js";
4
+ import { buildCopyAsMarkdown, buildCopyRendered, buildExportMarkdown } from "./markdownPaletteActions.js";
5
+ /**
6
+ * Command palette, distraction-free mode, and global keyboard shortcuts. Extracted verbatim from
7
+ * useAppState; the three hooks run in the original order (palette → distraction-free → shortcuts).
8
+ */
9
+ export function useAppCommands({ docs, openDocs, recents, active, activeEditable, activeTextLike, activeText, copyContents, settings, update, onChangeView, docxMenu, markdownExport, handlePrint, openFromClipboard, cycleTab, jumpToTab, blockDrafts, searchProvider, canOpenFind, findOpen, openFind, settingsOpen, setSettingsOpen, paletteOpen, setPaletteOpen, helpOpen, setHelpOpen, openUrlDialog, setOpenUrlDialog, setTabOverviewOpen, }) {
10
+ const paletteItems = useCommandPalette({
11
+ openDocs,
12
+ recents,
13
+ active,
14
+ activeEditable,
15
+ titleFor: docs.titleFor,
16
+ activate: docs.activate,
17
+ openRecent: docs.openRecent,
18
+ newDocument: docs.newDocument,
19
+ onOpenDialog: docs.onOpenDialog,
20
+ onOpenUrl: () => setOpenUrlDialog(true),
21
+ onSave: docs.onSave,
22
+ onSaveAs: docs.onSaveAs,
23
+ onPrint: handlePrint,
24
+ docxMenu,
25
+ onChangeView,
26
+ settings,
27
+ update,
28
+ openSettings: () => setSettingsOpen(true),
29
+ reopenClosed: docs.reopenLastClosed,
30
+ showHelp: () => setHelpOpen(true),
31
+ copyContents,
32
+ duplicate: activeTextLike && active ? () => docs.duplicateAsDraft(activeText) : null,
33
+ exportMarkdown: buildExportMarkdown(active, markdownExport, activeText),
34
+ copyRendered: buildCopyRendered(active, markdownExport, activeText),
35
+ copyAsMarkdown: buildCopyAsMarkdown(active),
36
+ });
37
+ const { distractionFree, toggleDistractionFree, fullscreenConfirm } = useDistractionFree({
38
+ active,
39
+ blockDrafts,
40
+ overlayOpen: settingsOpen || paletteOpen || helpOpen || openUrlDialog,
41
+ });
42
+ useGlobalShortcuts({
43
+ active,
44
+ activeEditable,
45
+ searchProvider,
46
+ canOpenFind,
47
+ docxMenu,
48
+ newDocument: docs.newDocument,
49
+ onOpenDialog: docs.onOpenDialog,
50
+ onPrint: handlePrint,
51
+ onSave: docs.onSave,
52
+ onSaveAs: docs.onSaveAs,
53
+ requestClose: docs.requestClose,
54
+ togglePalette: () => setPaletteOpen((o) => !o),
55
+ openFind,
56
+ onHtmlSaveShortcut: docs.onHtmlSaveShortcut,
57
+ reloadActiveFromDisk: (doc) => docs.reload(doc.path),
58
+ openFromClipboard,
59
+ cycleTab,
60
+ jumpToTab,
61
+ reopenClosed: docs.reopenLastClosed,
62
+ resetZoom: () => update({ fontSizePx: 16, sourceFontSizePx: 12 }),
63
+ toggleWordWrap: () => update({ wordWrap: !settings.wordWrap }),
64
+ toggleSidebar: () => window.dispatchEvent(new CustomEvent("viewright:toggle-sidebar")),
65
+ toggleTabOverview: () => setTabOverviewOpen((o) => !o),
66
+ showHelp: () => setHelpOpen(true),
67
+ findOpen,
68
+ toggleDistractionFree,
69
+ });
70
+ return { paletteItems, distractionFree, toggleDistractionFree, fullscreenConfirm };
71
+ }