@legendapp/spark 0.0.1-next.2 → 0.0.1-next.3

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 (399) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -4
  3. package/ai-codex.ts +1 -0
  4. package/ai.ts +1 -0
  5. package/app-documents.ts +1 -0
  6. package/config-plugin.d.cts +2 -0
  7. package/contracts.ts +2 -0
  8. package/diagnostics.ts +1 -0
  9. package/expo-config.d.cts +1 -0
  10. package/metro.cjs +4 -1
  11. package/metro.d.cts +12 -0
  12. package/native.cjs +3 -1
  13. package/native.d.cts +6 -0
  14. package/package.json +82 -14
  15. package/schema.json +276 -58
  16. package/settings-observable.ts +1 -0
  17. package/settings-window.ts +1 -0
  18. package/shortcuts-commands.ts +1 -0
  19. package/shortcuts-keyboard.ts +3 -0
  20. package/ui-classnames.ts +1 -0
  21. package/ui-glass.ts +1 -0
  22. package/ui-search.ts +1 -0
  23. package/ui-sidebar.ts +1 -0
  24. package/ui-split-view.ts +1 -0
  25. package/ui-symbol.ts +1 -0
  26. package/vendor/node_modules/@legendapp/spark-ai/package.json +19 -0
  27. package/vendor/node_modules/@legendapp/spark-ai/src/availability.ts +17 -0
  28. package/vendor/node_modules/@legendapp/spark-ai/src/index.ts +15 -0
  29. package/vendor/node_modules/@legendapp/spark-ai/src/invocations.ts +34 -0
  30. package/vendor/node_modules/@legendapp/spark-ai/src/output.ts +62 -0
  31. package/vendor/node_modules/@legendapp/spark-ai/src/run.ts +12 -0
  32. package/vendor/node_modules/@legendapp/spark-ai/src/types.ts +16 -0
  33. package/vendor/node_modules/@legendapp/spark-audio/macos/RNSparkAudio.h +2 -1
  34. package/vendor/node_modules/@legendapp/spark-audio/macos/RNSparkAudio.mm +126 -24
  35. package/vendor/node_modules/@legendapp/spark-audio/package.json +5 -2
  36. package/vendor/node_modules/@legendapp/spark-audio/src/NativeSparkAudio.ts +5 -1
  37. package/vendor/node_modules/@legendapp/spark-audio/src/desktop.ts +36 -32
  38. package/vendor/node_modules/@legendapp/spark-audio/src/events.ts +11 -0
  39. package/vendor/node_modules/@legendapp/spark-audio/src/hooks.ts +65 -0
  40. package/vendor/node_modules/@legendapp/spark-audio/src/index.web.ts +92 -33
  41. package/vendor/node_modules/@legendapp/spark-audio/src/media-session-unavailable.ts +6 -4
  42. package/vendor/node_modules/@legendapp/spark-audio/src/media-session.ts +52 -19
  43. package/vendor/node_modules/@legendapp/spark-audio/src/media-session.web.ts +18 -10
  44. package/vendor/node_modules/@legendapp/spark-audio/src/media-types.ts +40 -13
  45. package/vendor/node_modules/@legendapp/spark-audio/src/mobile.ts +40 -32
  46. package/vendor/node_modules/@legendapp/spark-audio/src/player.ts +97 -0
  47. package/vendor/node_modules/@legendapp/spark-audio/src/status-listeners.ts +21 -15
  48. package/vendor/node_modules/@legendapp/spark-audio/src/types.ts +9 -4
  49. package/vendor/node_modules/@legendapp/spark-audio/windows/SparkAudio/SparkAudio.h +67 -20
  50. package/vendor/node_modules/@legendapp/spark-auth-session/package.json +4 -3
  51. package/vendor/node_modules/@legendapp/spark-auth-session/src/core.ts +60 -42
  52. package/vendor/node_modules/@legendapp/spark-auth-session/src/index.ts +28 -12
  53. package/vendor/node_modules/@legendapp/spark-auth-session/src/mobile.ts +97 -41
  54. package/vendor/node_modules/@legendapp/spark-auth-session/src/types.ts +20 -9
  55. package/vendor/node_modules/@legendapp/spark-cli/dist/add-desktop.js +50 -7
  56. package/vendor/node_modules/@legendapp/spark-cli/dist/build-mode.js +1 -1
  57. package/vendor/node_modules/@legendapp/spark-cli/dist/build.js +83 -48
  58. package/vendor/node_modules/@legendapp/spark-cli/dist/cli-arguments.js +60 -0
  59. package/vendor/node_modules/@legendapp/spark-cli/dist/commands.js +7 -2
  60. package/vendor/node_modules/@legendapp/spark-cli/dist/create.js +31 -5
  61. package/vendor/node_modules/@legendapp/spark-cli/dist/credentials.js +69 -23
  62. package/vendor/node_modules/@legendapp/spark-cli/dist/dev-command.js +12 -3
  63. package/vendor/node_modules/@legendapp/spark-cli/dist/dev.js +12 -9
  64. package/vendor/node_modules/@legendapp/spark-cli/dist/examples.js +1 -1
  65. package/vendor/node_modules/@legendapp/spark-cli/dist/expo-dev-patch.cjs +4 -2
  66. package/vendor/node_modules/@legendapp/spark-cli/dist/expo-metro.cjs +1 -1
  67. package/vendor/node_modules/@legendapp/spark-cli/dist/expo-node.js +3 -3
  68. package/vendor/node_modules/@legendapp/spark-cli/dist/index.js +10 -38
  69. package/vendor/node_modules/@legendapp/spark-cli/dist/local.js +1 -1
  70. package/vendor/node_modules/@legendapp/spark-cli/dist/metro.cjs +26 -5
  71. package/vendor/node_modules/@legendapp/spark-cli/dist/native-metadata.js +26 -0
  72. package/vendor/node_modules/@legendapp/spark-cli/dist/native-patch-preflight.js +84 -0
  73. package/vendor/node_modules/@legendapp/spark-cli/dist/native-preparation.js +88 -0
  74. package/vendor/node_modules/@legendapp/spark-cli/dist/notary-profiles.js +75 -0
  75. package/vendor/node_modules/@legendapp/spark-cli/dist/package.js +4 -2
  76. package/vendor/node_modules/@legendapp/spark-cli/dist/platform.js +16 -2
  77. package/vendor/node_modules/@legendapp/spark-cli/dist/project.js +66 -13
  78. package/vendor/node_modules/@legendapp/spark-cli/dist/release.js +84 -4
  79. package/vendor/node_modules/@legendapp/spark-cli/dist/release.json +23 -18
  80. package/vendor/node_modules/@legendapp/spark-cli/dist/runner-install.js +145 -31
  81. package/vendor/node_modules/@legendapp/spark-cli/dist/signing.js +4 -3
  82. package/vendor/node_modules/@legendapp/spark-cli/dist/updates.js +41 -4
  83. package/vendor/node_modules/@legendapp/spark-cli/dist/windows.js +4 -1
  84. package/vendor/node_modules/@legendapp/spark-cli/package.json +7 -6
  85. package/vendor/node_modules/@legendapp/spark-cli/templates/blank-typescript/App.tsx +6 -12
  86. package/vendor/node_modules/@legendapp/spark-cli/templates/blank-typescript/desktop.config.json +5 -3
  87. package/vendor/node_modules/@legendapp/spark-cli/templates/blank-typescript/package.json +11 -9
  88. package/vendor/node_modules/@legendapp/spark-cli/templates/diff-lite/Git.macos.tsx +2 -2
  89. package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/Integration.macos.tsx +23 -21
  90. package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/Integration.windows.tsx +23 -21
  91. package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/io.desktop.ts +11 -8
  92. package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/io.macos.ts +3 -3
  93. package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/Lifecycle.desktop.tsx +8 -14
  94. package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/OpenFiles.desktop.tsx +5 -10
  95. package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/io.desktop.ts +11 -8
  96. package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/io.macos.ts +3 -3
  97. package/vendor/node_modules/@legendapp/spark-cli/templates/music-lite/assets.desktop.ts +2 -2
  98. package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/Activation.desktop.tsx +2 -2
  99. package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/Desktop.desktop.ts +37 -15
  100. package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/session.ts +5 -6
  101. package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/window-session.ts +5 -4
  102. package/vendor/node_modules/@legendapp/spark-cli/templates/universal/desktop.config.json +5 -3
  103. package/vendor/node_modules/@legendapp/spark-cli/templates/universal/metro.config.js +1 -1
  104. package/vendor/node_modules/@legendapp/spark-cli/templates/universal/package.json +13 -11
  105. package/vendor/node_modules/@legendapp/spark-cli/templates/universal/react-native.config.js +1 -1
  106. package/vendor/node_modules/@legendapp/spark-cli/templates/windows/package.json +13 -11
  107. package/vendor/node_modules/@legendapp/spark-cli/templates/windows/react-native.config.js +1 -1
  108. package/vendor/node_modules/@legendapp/spark-clipboard/macos/RNDesktopClipboard.mm +10 -16
  109. package/vendor/node_modules/@legendapp/spark-clipboard/package.json +2 -2
  110. package/vendor/node_modules/@legendapp/spark-clipboard/src/NativeDesktopClipboard.ts +1 -1
  111. package/vendor/node_modules/@legendapp/spark-clipboard/src/desktop.ts +55 -31
  112. package/vendor/node_modules/@legendapp/spark-clipboard/src/expo.ts +21 -11
  113. package/vendor/node_modules/@legendapp/spark-clipboard/src/options.ts +8 -0
  114. package/vendor/node_modules/@legendapp/spark-clipboard/src/types.ts +10 -0
  115. package/vendor/node_modules/@legendapp/spark-clipboard/windows/SparkClipboard/SparkClipboard.h +38 -15
  116. package/vendor/node_modules/@legendapp/spark-clipboard/windows/SparkClipboard/SparkClipboard.vcxproj +2 -0
  117. package/vendor/node_modules/@legendapp/spark-codex/RNCodex.podspec +22 -0
  118. package/vendor/node_modules/@legendapp/spark-codex/cpp/HybridCodexAppServer.hpp +23 -0
  119. package/vendor/node_modules/@legendapp/spark-codex/cpp/HybridCodexAppServer.mm +1529 -0
  120. package/vendor/node_modules/@legendapp/spark-codex/nitro.json +21 -0
  121. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/.gitattributes +1 -0
  122. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex+autolinking.rb +62 -0
  123. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex-Swift-Cxx-Bridge.cpp +17 -0
  124. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex-Swift-Cxx-Bridge.hpp +27 -0
  125. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex-Swift-Cxx-Umbrella.hpp +38 -0
  126. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodexAutolinking.mm +35 -0
  127. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodexAutolinking.swift +16 -0
  128. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/CodexAvailability.hpp +95 -0
  129. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/CodexRunResult.hpp +99 -0
  130. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/HybridCodexAppServerSpec.cpp +24 -0
  131. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/HybridCodexAppServerSpec.hpp +71 -0
  132. package/vendor/node_modules/@legendapp/spark-codex/package.json +35 -0
  133. package/vendor/node_modules/@legendapp/spark-codex/react-native.config.js +11 -0
  134. package/vendor/node_modules/@legendapp/spark-codex/src/Codex.nitro.ts +30 -0
  135. package/vendor/node_modules/@legendapp/spark-codex/src/index.ts +65 -0
  136. package/vendor/node_modules/@legendapp/spark-codex/src/native.ts +6 -0
  137. package/vendor/node_modules/@legendapp/spark-commands/package.json +21 -0
  138. package/vendor/node_modules/@legendapp/spark-commands/src/bindings.ts +90 -0
  139. package/vendor/node_modules/@legendapp/spark-commands/src/index.tsx +400 -0
  140. package/vendor/node_modules/@legendapp/spark-commands/src/router.ts +138 -0
  141. package/vendor/node_modules/@legendapp/spark-commands/src/storage.ts +20 -0
  142. package/vendor/node_modules/@legendapp/spark-context-menu/macos/RNContextMenu.mm +7 -8
  143. package/vendor/node_modules/@legendapp/spark-context-menu/package.json +4 -3
  144. package/vendor/node_modules/@legendapp/spark-context-menu/src/NativeContextMenu.ts +1 -1
  145. package/vendor/node_modules/@legendapp/spark-context-menu/src/index.ts +26 -26
  146. package/vendor/node_modules/@legendapp/spark-context-menu/windows/SparkContextMenu/SparkContextMenu.h +3 -2
  147. package/vendor/node_modules/@legendapp/spark-desktop-app/RNDesktopApp.podspec +2 -1
  148. package/vendor/node_modules/@legendapp/spark-desktop-app/cpp/SparkBinaryJSI.hpp +89 -0
  149. package/vendor/node_modules/@legendapp/spark-desktop-app/cpp/SparkBinaryWindows.hpp +22 -0
  150. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/RNDesktopApp.mm +13 -7
  151. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkBinaryJSI.h +48 -0
  152. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkDesktop.h +4 -0
  153. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkDesktop.mm +33 -33
  154. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkDesktopError.h +20 -0
  155. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkLifecycle.h +26 -0
  156. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkLifecycle.mm +49 -0
  157. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkQuitCoordinator.h +9 -0
  158. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkQuitCoordinator.mm +70 -0
  159. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkWindow.mm +9 -6
  160. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/recent-documents/RNRecentDocumentController.mm +55 -0
  161. package/vendor/node_modules/@legendapp/spark-desktop-app/package.json +5 -3
  162. package/vendor/node_modules/@legendapp/spark-desktop-app/src/NativeDesktopApp.ts +1 -1
  163. package/vendor/node_modules/@legendapp/spark-desktop-app/src/api.ts +86 -40
  164. package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/accelerator.ts +40 -0
  165. package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/index.ts +56 -0
  166. package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/menu.ts +114 -0
  167. package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/native-buffer.ts +22 -0
  168. package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/path.ts +24 -0
  169. package/vendor/node_modules/@legendapp/spark-desktop-app/src/events.ts +75 -0
  170. package/vendor/node_modules/@legendapp/spark-desktop-app/src/index.ts +2 -0
  171. package/vendor/node_modules/@legendapp/spark-desktop-app/src/index.windows.ts +2 -0
  172. package/vendor/node_modules/@legendapp/spark-desktop-app/src/open-requests.ts +43 -0
  173. package/vendor/node_modules/@legendapp/spark-desktop-app/src/restoration.ts +11 -0
  174. package/vendor/node_modules/@legendapp/spark-desktop-app/src/transport.ts +10 -0
  175. package/vendor/node_modules/@legendapp/spark-desktop-config/app.plugin.cjs +10 -5
  176. package/vendor/node_modules/@legendapp/spark-desktop-config/app.plugin.d.cts +3 -0
  177. package/vendor/node_modules/@legendapp/spark-desktop-config/autolinking.cjs +35 -0
  178. package/vendor/node_modules/@legendapp/spark-desktop-config/config.cjs +16 -3
  179. package/vendor/node_modules/@legendapp/spark-desktop-config/config.d.cts +89 -9
  180. package/vendor/node_modules/@legendapp/spark-desktop-config/expo.d.cts +3 -0
  181. package/vendor/node_modules/@legendapp/spark-desktop-config/identity.cjs +3 -2
  182. package/vendor/node_modules/@legendapp/spark-desktop-config/lifecycle.cjs +20 -0
  183. package/vendor/node_modules/@legendapp/spark-desktop-config/package.json +6 -5
  184. package/vendor/node_modules/@legendapp/spark-desktop-config/schema.json +276 -58
  185. package/vendor/node_modules/@legendapp/spark-desktop-config/updates.cjs +1 -0
  186. package/vendor/node_modules/@legendapp/spark-desktop-config/validation.cjs +35 -0
  187. package/vendor/node_modules/@legendapp/spark-desktop-config/windows.plugin.cjs +2 -1
  188. package/vendor/node_modules/@legendapp/spark-desktop-host/AppDelegate.mm +129 -11
  189. package/vendor/node_modules/@legendapp/spark-desktop-host/package.json +2 -2
  190. package/vendor/node_modules/@legendapp/spark-desktop-host/windows/application.inc +277 -121
  191. package/vendor/node_modules/@legendapp/spark-desktop-links/common/AuthLoopback.h +5 -7
  192. package/vendor/node_modules/@legendapp/spark-desktop-links/macos/RNDesktopLinks.mm +10 -9
  193. package/vendor/node_modules/@legendapp/spark-desktop-links/package.json +2 -2
  194. package/vendor/node_modules/@legendapp/spark-desktop-links/src/NativeDesktopLinks.ts +1 -1
  195. package/vendor/node_modules/@legendapp/spark-desktop-links/src/api.ts +27 -33
  196. package/vendor/node_modules/@legendapp/spark-desktop-links/src/expo.ts +3 -4
  197. package/vendor/node_modules/@legendapp/spark-desktop-links/src/native.ts +9 -0
  198. package/vendor/node_modules/@legendapp/spark-desktop-links/windows/SparkLinks/SparkLinks.h +22 -22
  199. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/RNDesktopShortcuts.podspec +1 -0
  200. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/macos/RNDesktopShortcuts.mm +1 -1
  201. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/macos/keyboard-manager/RNKeyboardManager.h +9 -0
  202. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/macos/keyboard-manager/RNKeyboardManager.mm +307 -0
  203. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/package.json +8 -5
  204. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/NativeDesktopShortcuts.ts +1 -1
  205. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/api.ts +39 -14
  206. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/keyboard-manager/NativeKeyboardManager.ts +12 -0
  207. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/keyboard-manager/codes.ts +108 -0
  208. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/keyboard-manager/index.ts +99 -0
  209. package/vendor/node_modules/@legendapp/spark-desktop-windows/RNDesktopWindows.podspec +2 -0
  210. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/RNDesktopWindows.mm +229 -103
  211. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/SparkWindowGeometry.h +63 -0
  212. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/LegendWindowRegistry.h +20 -0
  213. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/LegendWindowRegistry.mm +27 -0
  214. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/RNWindowManager.h +12 -0
  215. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/RNWindowManager.mm +3130 -0
  216. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/RNWindowManagerStartup.mm +31 -0
  217. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/SidebarStartupFallbacks.mm +14 -0
  218. package/vendor/node_modules/@legendapp/spark-desktop-windows/package.json +10 -6
  219. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/NativeDesktopWindowManager.ts +1 -1
  220. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/api.ts +144 -70
  221. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/diagnostics.ts +17 -0
  222. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/index.ts +3 -1
  223. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/index.windows.ts +3 -1
  224. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/macos-adapter.ts +53 -0
  225. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/macos.ts +51 -0
  226. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/transport.ts +41 -0
  227. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/types.ts +140 -0
  228. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/validation.ts +162 -0
  229. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/window-manager/NativeWindowManager.ts +28 -0
  230. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/WindowProvider.tsx +20 -0
  231. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/createWindowsNavigator.tsx +78 -0
  232. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/index.ts +6 -0
  233. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/primaryWindowLifecycle.ts +34 -0
  234. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/types.ts +11 -0
  235. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/usePrimaryWindowLifecycle.ts +18 -0
  236. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/useWindowFocusEffect.ts +17 -0
  237. package/vendor/node_modules/@legendapp/spark-documents/package.json +22 -0
  238. package/vendor/node_modules/@legendapp/spark-documents/src/controller.ts +97 -0
  239. package/vendor/node_modules/@legendapp/spark-documents/src/documentTransition.ts +13 -0
  240. package/vendor/node_modules/@legendapp/spark-documents/src/hooks.ts +139 -0
  241. package/vendor/node_modules/@legendapp/spark-documents/src/index.tsx +50 -0
  242. package/vendor/node_modules/@legendapp/spark-documents/src/reload.ts +39 -0
  243. package/vendor/node_modules/@legendapp/spark-documents/src/requests.ts +15 -0
  244. package/vendor/node_modules/@legendapp/spark-drag-drop/package.json +4 -3
  245. package/vendor/node_modules/@legendapp/spark-drag-drop/src/DesktopDragViewNativeComponent.ts +1 -1
  246. package/vendor/node_modules/@legendapp/spark-drag-drop/src/contracts.ts +73 -18
  247. package/vendor/node_modules/@legendapp/spark-drag-drop/src/index.tsx +58 -15
  248. package/vendor/node_modules/@legendapp/spark-drag-drop/windows/SparkDragDrop/SparkDragDrop.h +14 -8
  249. package/vendor/node_modules/@legendapp/spark-file-dialog/ios/RNFileDialog.mm +34 -121
  250. package/vendor/node_modules/@legendapp/spark-file-dialog/package.json +12 -5
  251. package/vendor/node_modules/@legendapp/spark-file-dialog/src/NativeFileDialog.ts +1 -5
  252. package/vendor/node_modules/@legendapp/spark-file-dialog/src/index.ts +19 -84
  253. package/vendor/node_modules/@legendapp/spark-file-dialog/src/native.ts +12 -0
  254. package/vendor/node_modules/@legendapp/spark-file-dialog/src/options.ts +34 -14
  255. package/vendor/node_modules/@legendapp/spark-file-dialog/src/types.ts +26 -0
  256. package/vendor/node_modules/@legendapp/spark-file-dialog/windows/SparkFileDialog/SparkFileDialog.h +42 -58
  257. package/vendor/node_modules/@legendapp/spark-file-system/RNDesktopFileSystem.podspec +1 -0
  258. package/vendor/node_modules/@legendapp/spark-file-system/macos/RNDesktopFileSystem.mm +32 -21
  259. package/vendor/node_modules/@legendapp/spark-file-system/macos/SparkFileMutations.h +24 -0
  260. package/vendor/node_modules/@legendapp/spark-file-system/macos/SparkFileMutations.mm +215 -0
  261. package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScanner.h +9 -0
  262. package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScanner.mm +82 -0
  263. package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScannerCore.h +47 -0
  264. package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScannerCore.mm +306 -0
  265. package/vendor/node_modules/@legendapp/spark-file-system/package.json +8 -6
  266. package/vendor/node_modules/@legendapp/spark-file-system/src/NativeDesktopFileSystem.ts +1 -1
  267. package/vendor/node_modules/@legendapp/spark-file-system/src/file-scanner/NativeFileScanner.ts +11 -0
  268. package/vendor/node_modules/@legendapp/spark-file-system/src/file-scanner/index.ts +93 -0
  269. package/vendor/node_modules/@legendapp/spark-file-system/src/handles.ts +48 -14
  270. package/vendor/node_modules/@legendapp/spark-file-system/src/index.ts +134 -45
  271. package/vendor/node_modules/@legendapp/spark-file-system/src/path.ts +1 -9
  272. package/vendor/node_modules/@legendapp/spark-file-system/windows/SparkFileSystem/SparkFileSystem.h +102 -26
  273. package/vendor/node_modules/@legendapp/spark-file-system/windows/SparkFileSystem/SparkFileSystem.vcxproj +2 -0
  274. package/vendor/node_modules/@legendapp/spark-global-shortcuts/RNDesktopGlobalShortcuts.podspec +1 -0
  275. package/vendor/node_modules/@legendapp/spark-global-shortcuts/macos/RNDesktopGlobalShortcuts.mm +7 -4
  276. package/vendor/node_modules/@legendapp/spark-global-shortcuts/package.json +4 -3
  277. package/vendor/node_modules/@legendapp/spark-global-shortcuts/src/NativeDesktopGlobalShortcuts.ts +1 -1
  278. package/vendor/node_modules/@legendapp/spark-global-shortcuts/src/index.ts +39 -10
  279. package/vendor/node_modules/@legendapp/spark-global-shortcuts/windows/SparkGlobalShortcuts/SparkGlobalShortcuts.h +5 -4
  280. package/vendor/node_modules/@legendapp/spark-message-dialog/macos/RNDesktopMessageDialog.mm +2 -2
  281. package/vendor/node_modules/@legendapp/spark-message-dialog/package.json +2 -2
  282. package/vendor/node_modules/@legendapp/spark-message-dialog/src/NativeDesktopMessageDialog.ts +1 -1
  283. package/vendor/node_modules/@legendapp/spark-message-dialog/src/index.ts +48 -11
  284. package/vendor/node_modules/@legendapp/spark-native-menu/ios/RNNativeMenu.mm +141 -609
  285. package/vendor/node_modules/@legendapp/spark-native-menu/package.json +9 -4
  286. package/vendor/node_modules/@legendapp/spark-native-menu/src/NativeMenu.ts +2 -7
  287. package/vendor/node_modules/@legendapp/spark-native-menu/src/api.ts +76 -121
  288. package/vendor/node_modules/@legendapp/spark-native-menu/src/hooks.ts +97 -0
  289. package/vendor/node_modules/@legendapp/spark-native-menu/src/windows-menus.ts +43 -37
  290. package/vendor/node_modules/@legendapp/spark-notifications/macos/RNDesktopNotifications.mm +75 -30
  291. package/vendor/node_modules/@legendapp/spark-notifications/package.json +2 -2
  292. package/vendor/node_modules/@legendapp/spark-notifications/src/NativeDesktopNotifications.ts +1 -1
  293. package/vendor/node_modules/@legendapp/spark-notifications/src/index.ts +130 -42
  294. package/vendor/node_modules/@legendapp/spark-notifications/windows/SparkNotifications/SparkNotifications.h +46 -8
  295. package/vendor/node_modules/@legendapp/spark-processes/macos/RNDesktopProcesses.mm +94 -21
  296. package/vendor/node_modules/@legendapp/spark-processes/package.json +4 -3
  297. package/vendor/node_modules/@legendapp/spark-processes/src/NativeDesktopProcesses.ts +1 -1
  298. package/vendor/node_modules/@legendapp/spark-processes/src/index.ts +124 -28
  299. package/vendor/node_modules/@legendapp/spark-processes/src/types.ts +34 -0
  300. package/vendor/node_modules/@legendapp/spark-processes/windows/SparkProcesses/SparkProcesses.h +83 -34
  301. package/vendor/node_modules/@legendapp/spark-processes/windows/SparkProcesses/SparkProcesses.vcxproj +2 -0
  302. package/vendor/node_modules/@legendapp/spark-secure-storage/macos/RNDesktopSecureStorage.mm +49 -40
  303. package/vendor/node_modules/@legendapp/spark-secure-storage/package.json +2 -2
  304. package/vendor/node_modules/@legendapp/spark-secure-storage/src/NativeDesktopSecureStorage.ts +1 -1
  305. package/vendor/node_modules/@legendapp/spark-secure-storage/src/desktop.ts +16 -14
  306. package/vendor/node_modules/@legendapp/spark-secure-storage/src/expo.ts +0 -1
  307. package/vendor/node_modules/@legendapp/spark-secure-storage/src/options.ts +3 -2
  308. package/vendor/node_modules/@legendapp/spark-secure-storage/src/unavailable.ts +6 -3
  309. package/vendor/node_modules/@legendapp/spark-settings/package.json +10 -3
  310. package/vendor/node_modules/@legendapp/spark-settings/src/fileStorage.ts +11 -0
  311. package/vendor/node_modules/@legendapp/spark-settings/src/index.ts +7 -9
  312. package/vendor/node_modules/@legendapp/spark-settings/src/storage/index.ts +142 -0
  313. package/vendor/node_modules/@legendapp/spark-settings/src/store.ts +63 -21
  314. package/vendor/node_modules/@legendapp/spark-settings-ui/package.json +20 -0
  315. package/vendor/node_modules/@legendapp/spark-settings-ui/src/index.tsx +436 -0
  316. package/vendor/node_modules/@legendapp/spark-settings-ui/src/options.ts +10 -0
  317. package/vendor/node_modules/@legendapp/spark-sqlite/package.json +3 -3
  318. package/vendor/node_modules/@legendapp/spark-sqlite/src/database.ts +182 -0
  319. package/vendor/node_modules/@legendapp/spark-sqlite/src/index.ts +19 -6
  320. package/vendor/node_modules/@legendapp/spark-sqlite/src/types.ts +22 -0
  321. package/vendor/node_modules/@legendapp/spark-system/macos/RNDesktopSystem.mm +36 -11
  322. package/vendor/node_modules/@legendapp/spark-system/package.json +2 -2
  323. package/vendor/node_modules/@legendapp/spark-system/src/NativeDesktopSystem.ts +1 -1
  324. package/vendor/node_modules/@legendapp/spark-system/src/index.ts +89 -32
  325. package/vendor/node_modules/@legendapp/spark-system/windows/SparkSystem/SparkSystem.h +8 -4
  326. package/vendor/node_modules/@legendapp/spark-tray/macos/RNDesktopTray.mm +20 -8
  327. package/vendor/node_modules/@legendapp/spark-tray/package.json +2 -2
  328. package/vendor/node_modules/@legendapp/spark-tray/src/NativeDesktopTray.ts +1 -1
  329. package/vendor/node_modules/@legendapp/spark-tray/src/index.ts +91 -34
  330. package/vendor/node_modules/@legendapp/spark-tray/windows/SparkTray/SparkTray.h +45 -15
  331. package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkButton.mm +2 -1
  332. package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkTextInput.mm +10 -2
  333. package/vendor/node_modules/@legendapp/spark-ui/macos/appkit-split-view/RNSidebarSplitViewComponent.h +15 -0
  334. package/vendor/node_modules/@legendapp/spark-ui/macos/appkit-split-view/RNSidebarSplitViewComponent.mm +1182 -0
  335. package/vendor/node_modules/@legendapp/spark-ui/macos/glass-effect-view/RNGlassEffectView.mm +72 -0
  336. package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSegmentedControl.h +15 -0
  337. package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSegmentedControl.mm +194 -0
  338. package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSelect.h +15 -0
  339. package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSelect.mm +180 -0
  340. package/vendor/node_modules/@legendapp/spark-ui/macos/sf-symbol/RNSFSymbol.h +15 -0
  341. package/vendor/node_modules/@legendapp/spark-ui/macos/sf-symbol/RNSFSymbol.mm +155 -0
  342. package/vendor/node_modules/@legendapp/spark-ui/macos/sidebar/RNSidebar.h +18 -0
  343. package/vendor/node_modules/@legendapp/spark-ui/macos/sidebar/RNSidebar.mm +645 -0
  344. package/vendor/node_modules/@legendapp/spark-ui/macos/text-input-search/RNTextInputSearch.h +15 -0
  345. package/vendor/node_modules/@legendapp/spark-ui/macos/text-input-search/RNTextInputSearch.mm +164 -0
  346. package/vendor/node_modules/@legendapp/spark-ui/package.json +16 -4
  347. package/vendor/node_modules/@legendapp/spark-ui/src/SparkSelectNativeComponent.ts +3 -1
  348. package/vendor/node_modules/@legendapp/spark-ui/src/SparkTextInputNativeComponent.ts +6 -2
  349. package/vendor/node_modules/@legendapp/spark-ui/src/appkit-split-view/SidebarSplitViewNativeComponent.ts +31 -0
  350. package/vendor/node_modules/@legendapp/spark-ui/src/appkit-split-view/index.tsx +77 -0
  351. package/vendor/node_modules/@legendapp/spark-ui/src/availability.ts +10 -0
  352. package/vendor/node_modules/@legendapp/spark-ui/src/classnames/index.ts +8 -0
  353. package/vendor/node_modules/@legendapp/spark-ui/src/control.ts +42 -0
  354. package/vendor/node_modules/@legendapp/spark-ui/src/desktop.tsx +56 -0
  355. package/vendor/node_modules/@legendapp/spark-ui/src/glass-effect-view/index.tsx +15 -0
  356. package/vendor/node_modules/@legendapp/spark-ui/src/index.android.tsx +35 -21
  357. package/vendor/node_modules/@legendapp/spark-ui/src/index.ios.tsx +55 -26
  358. package/vendor/node_modules/@legendapp/spark-ui/src/index.tsx +1 -27
  359. package/vendor/node_modules/@legendapp/spark-ui/src/index.web.tsx +45 -14
  360. package/vendor/node_modules/@legendapp/spark-ui/src/index.windows.tsx +1 -42
  361. package/vendor/node_modules/@legendapp/spark-ui/src/mobile-input.tsx +11 -0
  362. package/vendor/node_modules/@legendapp/spark-ui/src/native-select/NativeSegmentedControlNativeComponent.ts +17 -0
  363. package/vendor/node_modules/@legendapp/spark-ui/src/native-select/NativeSelectNativeComponent.ts +17 -0
  364. package/vendor/node_modules/@legendapp/spark-ui/src/select.ts +5 -3
  365. package/vendor/node_modules/@legendapp/spark-ui/src/sf-symbol/SFSymbolNativeComponent.ts +13 -0
  366. package/vendor/node_modules/@legendapp/spark-ui/src/sf-symbol/index.tsx +27 -0
  367. package/vendor/node_modules/@legendapp/spark-ui/src/sidebar/SidebarItemNativeComponent.ts +25 -0
  368. package/vendor/node_modules/@legendapp/spark-ui/src/sidebar/SidebarNativeComponent.ts +24 -0
  369. package/vendor/node_modules/@legendapp/spark-ui/src/sidebar/index.tsx +104 -0
  370. package/vendor/node_modules/@legendapp/spark-ui/src/specialized.ts +22 -0
  371. package/vendor/node_modules/@legendapp/spark-ui/src/text-input-search/TextInputSearchNativeComponent.ts +33 -0
  372. package/vendor/node_modules/@legendapp/spark-ui/src/text-input-search/index.tsx +34 -0
  373. package/vendor/node_modules/@legendapp/spark-ui/src/types.ts +30 -19
  374. package/vendor/node_modules/@legendapp/spark-ui/uniwind.ts +3 -1
  375. package/vendor/node_modules/@legendapp/spark-ui/uniwind.web.ts +3 -1
  376. package/vendor/node_modules/@legendapp/spark-ui/windows/SparkUI/SparkUI.h +16 -11
  377. package/vendor/node_modules/@legendapp/spark-updates/macos/RNDesktopUpdates.mm +21 -8
  378. package/vendor/node_modules/@legendapp/spark-updates/package.json +2 -2
  379. package/vendor/node_modules/@legendapp/spark-updates/src/NativeDesktopUpdates.ts +3 -2
  380. package/vendor/node_modules/@legendapp/spark-updates/src/index.ts +71 -15
  381. package/vendor/node_modules/@legendapp/spark-webview/package.json +2 -2
  382. package/vendor/node_modules/@legendapp/spark-webview/src/WebView.tsx +11 -0
  383. package/vendor/node_modules/@legendapp/spark-webview/src/adapter.ts +55 -0
  384. package/vendor/node_modules/@legendapp/spark-webview/src/index.ts +2 -2
  385. package/vendor/node_modules/@legendapp/spark-webview/src/types.ts +32 -0
  386. package/vendor/node_modules/@legendapp/spark-webview/src/view-props.ts +28 -0
  387. package/vendor/node_modules/@legendapp/spark-window-options/index.cjs +51 -14
  388. package/vendor/node_modules/@legendapp/spark-window-options/index.d.ts +21 -8
  389. package/vendor/node_modules/@legendapp/spark-window-options/package.json +1 -1
  390. package/windows-macos.ts +1 -0
  391. package/expo-metro.cjs +0 -1
  392. package/message-dialog.ts +0 -1
  393. package/universal.cjs +0 -1
  394. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/accelerator.ts +0 -22
  395. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows-options.ts +0 -11
  396. package/vendor/node_modules/@legendapp/spark-file-dialog/src/index.windows.ts +0 -15
  397. package/vendor/node_modules/@legendapp/spark-global-shortcuts/src/accelerator.ts +0 -22
  398. package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkSelect.h +0 -4
  399. package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkSelect.mm +0 -53
@@ -1,7 +1,20 @@
1
- import { open } from "@op-engineering/op-sqlite";
2
- import { getDirectory } from "@legendapp/spark-file-system";
3
- export type { DB, QueryResult } from "@op-engineering/op-sqlite";
4
- export async function openDatabase(name: string) {
5
- if (!/^[a-zA-Z0-9_-]+\.sqlite$/.test(name)) throw new Error("Database name must be a simple .sqlite filename");
6
- return open({ name, location: await getDirectory("data") });
1
+ import { SparkError } from "@legendapp/spark-desktop-app/src/contracts";
2
+ import { createDatabase } from "./database";
3
+ import type { Database, DatabaseOptions } from "./types";
4
+ export type { Database, DatabaseOptions, IntegerMode, SqlExecutor, SqlRow, SqlValue, RunResult } from "./types";
5
+
6
+ /**
7
+ * Open a project-scoped database. Backends deliver INTEGER values as JS numbers, so
8
+ * `integers: "text"` reads values outside 2^53−1 as text instead of rejecting them;
9
+ * exact digits beyond the double's precision still require `CAST(col AS TEXT)`.
10
+ */
11
+ export async function openDatabase(name: string, options: DatabaseOptions = {}): Promise<Database> {
12
+ if (!/^[a-zA-Z0-9_-]+\.sqlite$/.test(name)) throw new SparkError("E_INVALID_ARGUMENT", "Database name must be a simple .sqlite filename");
13
+ if (!options || typeof options !== "object" || Array.isArray(options)) throw new SparkError("E_INVALID_ARGUMENT", "Expected database options");
14
+ for (const key of Object.keys(options)) if (key !== "readOnly" && key !== "integers") throw new SparkError("E_UNSUPPORTED_OPTION", `Unsupported database option: ${key}`);
15
+ if (options.readOnly !== undefined && typeof options.readOnly !== "boolean") throw new SparkError("E_INVALID_ARGUMENT", "readOnly must be a boolean");
16
+ if (options.integers !== undefined && options.integers !== "number" && options.integers !== "text") throw new SparkError("E_INVALID_ARGUMENT", "integers must be number or text");
17
+ const [{ open }, { getDirectory }] = await Promise.all([import("@op-engineering/op-sqlite"), import("@legendapp/spark-file-system")]);
18
+ const native = open({ name, location: await getDirectory("data"), readOnly: options.readOnly });
19
+ return createDatabase({ execute: (sql, params) => native.execute(sql, params as import("@op-engineering/op-sqlite").Scalar[]), close: () => native.close() }, options.integers ?? "number");
7
20
  }
@@ -0,0 +1,22 @@
1
+ export type SqlValue = null | string | number | Uint8Array;
2
+ export type SqlRow = Record<string, SqlValue>;
3
+ export type IntegerMode = "number" | "text";
4
+ export type RunResult = {
5
+ changes: number;
6
+ /** Connection's last insert row ID; null when the backend omits it. Text in `integers: "text"` mode. */
7
+ lastInsertRowId: number | string | null;
8
+ };
9
+ export interface SqlExecutor {
10
+ run(sql: string, params?: readonly SqlValue[]): Promise<RunResult>;
11
+ getAll(sql: string, params?: readonly SqlValue[]): Promise<SqlRow[]>;
12
+ getFirst(sql: string, params?: readonly SqlValue[]): Promise<SqlRow | null>;
13
+ }
14
+ export interface Database extends SqlExecutor {
15
+ /** Row mode chosen at open. Text rows carry integers beyond 2^53−1 as strings. */
16
+ readonly integers: IntegerMode;
17
+ /** Use tx inside the callback. Direct database operations reject while a transaction is active. */
18
+ transaction<T>(operation: (tx: SqlExecutor) => Promise<T>): Promise<T>;
19
+ /** Waits for already queued work. Repeated calls share completion. */
20
+ close(): Promise<void>;
21
+ }
22
+ export type DatabaseOptions = { readOnly?: boolean; integers?: IntegerMode };
@@ -23,6 +23,7 @@ static NSDictionary *Power(void) {
23
23
  static void PowerChanged(void *context) { SparkEmit(@{ @"type": @"powerChanged" }); }
24
24
  @interface RNDesktopSystem ()
25
25
  @property NSMutableArray *observers;
26
+ @property NSMutableSet *subscriptions;
26
27
  @property NSMutableSet *assertions;
27
28
  @property NSMutableSet *attention;
28
29
  @property CFRunLoopSourceRef powerSource;
@@ -32,7 +33,7 @@ static void PowerChanged(void *context) { SparkEmit(@{ @"type": @"powerChanged"
32
33
  @implementation RNDesktopSystem
33
34
  RCT_EXPORT_MODULE(NativeDesktopSystem)
34
35
  + (BOOL)requiresMainQueueSetup { return YES; }
35
- - (instancetype)init { if (self = [super init]) { _observers = [NSMutableArray new]; _assertions = [NSMutableSet new]; _attention = [NSMutableSet new]; } return self; }
36
+ - (instancetype)init { if (self = [super init]) { _observers = [NSMutableArray new]; _subscriptions = [NSMutableSet new]; _assertions = [NSMutableSet new]; _attention = [NSMutableSet new]; } return self; }
36
37
  - (void)observe {
37
38
  if (self.observing) return; self.observing = YES;
38
39
  NSArray *definitions = @[
@@ -51,18 +52,45 @@ RCT_EXPORT_MODULE(NativeDesktopSystem)
51
52
  self.powerSource = IOPSNotificationCreateRunLoopSource(PowerChanged, NULL);
52
53
  if (self.powerSource) CFRunLoopAddSource(CFRunLoopGetMain(), self.powerSource, kCFRunLoopCommonModes);
53
54
  }
54
- - (void)dockAction:(NSMenuItem *)sender { SparkEmit(@{ @"type": @"dockAction", @"id": sender.representedObject, @"owner": self.dockOwner ?: @"" }); }
55
+ - (void)stopObserving {
56
+ for (NSArray *entry in self.observers) [entry[0] removeObserver:entry[1]];
57
+ [self.observers removeAllObjects]; self.observing = NO;
58
+ if (self.powerSource) { CFRunLoopRemoveSource(CFRunLoopGetMain(), self.powerSource, kCFRunLoopCommonModes); CFRelease(self.powerSource); self.powerSource = NULL; }
59
+ }
60
+ - (NSMenu *)menu:(NSArray *)items {
61
+ NSMenu *menu = [NSMenu new]; menu.autoenablesItems = NO;
62
+ for (NSDictionary *item in items) {
63
+ if ([item[@"separator"] boolValue]) { [menu addItem:NSMenuItem.separatorItem]; continue; }
64
+ NSMenuItem *entry = [[NSMenuItem alloc] initWithTitle:item[@"title"] action:@selector(dockAction:) keyEquivalent:@""];
65
+ entry.target = self; entry.representedObject = @{ @"id": item[@"id"], @"owner": self.dockOwner };
66
+ entry.enabled = !item[@"enabled"] || [item[@"enabled"] boolValue];
67
+ entry.state = [item[@"checked"] boolValue] ? NSControlStateValueOn : NSControlStateValueOff;
68
+ if (item[@"items"]) entry.submenu = [self menu:item[@"items"]];
69
+ [menu addItem:entry];
70
+ }
71
+ return menu;
72
+ }
73
+ - (void)dockAction:(NSMenuItem *)sender { SparkEmit(@{ @"type": @"dockAction", @"id": sender.representedObject[@"id"], @"owner": sender.representedObject[@"owner"] }); }
55
74
  - (void)call:(NSString *)method args:(NSString *)json resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject {
56
75
  dispatch_async(dispatch_get_main_queue(), ^{
57
76
  NSDictionary *args = SparkArgs(json);
58
77
  if ([method isEqual:@"info"]) {
59
78
  NSMutableDictionary *value = [Power() mutableCopy];
60
- [value addEntriesFromDictionary:@{ @"osVersion": NSProcessInfo.processInfo.operatingSystemVersionString, @"architecture": @"arm64", @"locale": NSLocale.currentLocale.localeIdentifier,
79
+ [value addEntriesFromDictionary:@{ @"osVersion": NSProcessInfo.processInfo.operatingSystemVersionString, @"architecture":
80
+ #if defined(__arm64__)
81
+ @"arm64",
82
+ #elif defined(__x86_64__)
83
+ @"x64",
84
+ #else
85
+ @"unknown",
86
+ #endif
87
+ @"locale": NSLocale.currentLocale.localeIdentifier,
61
88
  @"dark": @([[NSApp.effectiveAppearance bestMatchFromAppearancesWithNames:@[NSAppearanceNameAqua, NSAppearanceNameDarkAqua]] isEqual:NSAppearanceNameDarkAqua]),
62
89
  @"idleSeconds": @(CGEventSourceSecondsSinceLastEventType(kCGEventSourceStateCombinedSessionState, kCGAnyInputEventType)) }];
63
90
  resolve(SparkJSON(value)); return;
64
91
  }
65
- if ([method isEqual:@"observe"]) [self observe];
92
+ if ([method isEqual:@"observe"]) { [self.subscriptions addObject:args[@"id"]]; [self observe]; }
93
+ else if ([method isEqual:@"unobserve"]) { [self.subscriptions removeObject:args[@"id"]]; if (!self.subscriptions.count) [self stopObserving]; }
66
94
  else if ([method isEqual:@"loginStatus"] || [method isEqual:@"login"]) {
67
95
  BOOL available = [SparkContext()[@"runtime"][@"mode"] isEqual:@"release"];
68
96
  if ([method isEqual:@"loginStatus"]) {
@@ -80,11 +108,9 @@ RCT_EXPORT_MODULE(NativeDesktopSystem)
80
108
  else if ([method isEqual:@"cancelAttention"]) { if ([self.attention containsObject:args[@"id"]]) [NSApp cancelUserAttentionRequest:[args[@"id"] integerValue]]; [self.attention removeObject:args[@"id"]]; }
81
109
  else if ([method isEqual:@"clearDockMenu"]) { if ([self.dockOwner isEqual:args[@"owner"]]) { SparkDockMenu = nil; self.dockOwner = nil; } }
82
110
  else if ([method isEqual:@"dockMenu"]) {
83
- if (self.dockOwner) { reject(@"E_DOCK_MENU_EXISTS", @"Remove the existing Dock menu before replacing it", nil); return; }
111
+ if (self.dockOwner) { reject(@"E_ALREADY_EXISTS", @"Remove the existing Dock menu before replacing it", nil); return; }
84
112
  self.dockOwner = args[@"owner"];
85
- NSMenu *menu = [NSMenu new]; menu.autoenablesItems = NO;
86
- for (NSDictionary *item in args[@"items"]) { NSMenuItem *entry = [[NSMenuItem alloc] initWithTitle:item[@"title"] action:@selector(dockAction:) keyEquivalent:@""]; entry.target = self; entry.representedObject = item[@"id"]; entry.enabled = !item[@"enabled"] || [item[@"enabled"] boolValue]; entry.state = [item[@"checked"] boolValue] ? NSControlStateValueOn : NSControlStateValueOff; [menu addItem:entry]; }
87
- SparkDockMenu = menu;
113
+ SparkDockMenu = [self menu:args[@"items"]];
88
114
  }
89
115
  else if ([method isEqual:@"preventSleep"]) {
90
116
  IOPMAssertionID assertion; CFStringRef kind = [args[@"kind"] isEqual:@"system"] ? kIOPMAssertionTypePreventUserIdleSystemSleep : kIOPMAssertionTypePreventUserIdleDisplaySleep;
@@ -92,14 +118,13 @@ RCT_EXPORT_MODULE(NativeDesktopSystem)
92
118
  if (status != kIOReturnSuccess) { reject(@"E_POWER", @"Could not create sleep assertion", nil); return; }
93
119
  [self.assertions addObject:@(assertion)]; resolve(SparkJSON(@(assertion))); return;
94
120
  }
95
- else if ([method isEqual:@"allowSleep"]) { if ([self.assertions containsObject:args[@"id"]]) IOPMAssertionRelease([args[@"id"] unsignedIntValue]); [self.assertions removeObject:args[@"id"]]; }
121
+ else if ([method isEqual:@"allowSleep"]) { if ([self.assertions containsObject:args[@"id"]] && IOPMAssertionRelease([args[@"id"] unsignedIntValue]) != kIOReturnSuccess) { reject(@"E_NATIVE", @"Could not release sleep assertion", nil); return; } [self.assertions removeObject:args[@"id"]]; }
96
122
  else { SparkInvalid(reject, @"Unknown system operation"); return; }
97
123
  resolve(@"null");
98
124
  });
99
125
  }
100
126
  - (void)invalidate { dispatch_async(dispatch_get_main_queue(), ^{
101
- for (NSArray *entry in self.observers) [entry[0] removeObserver:entry[1]]; [self.observers removeAllObjects];
102
- if (self.powerSource) { CFRunLoopRemoveSource(CFRunLoopGetMain(), self.powerSource, kCFRunLoopCommonModes); CFRelease(self.powerSource); self.powerSource = NULL; }
127
+ [self stopObserving]; [self.subscriptions removeAllObjects];
103
128
  for (NSNumber *value in self.assertions) IOPMAssertionRelease(value.unsignedIntValue); [self.assertions removeAllObjects];
104
129
  for (NSNumber *value in self.attention) [NSApp cancelUserAttentionRequest:value.integerValue]; [self.attention removeAllObjects];
105
130
  SparkDockMenu = nil; NSApp.dockTile.badgeLabel = nil;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@legendapp/spark-system",
3
- "version": "0.0.1-next.2",
3
+ "version": "0.0.1-next.3",
4
4
  "license": "MIT",
5
5
  "main": "src/index.ts",
6
6
  "react-native": "src/index.ts",
@@ -40,7 +40,7 @@
40
40
  ]
41
41
  },
42
42
  "dependencies": {
43
- "@legendapp/spark-desktop-app": "0.0.1-next.2"
43
+ "@legendapp/spark-desktop-app": "0.0.1-next.3"
44
44
  },
45
45
  "private": true
46
46
  }
@@ -1,3 +1,3 @@
1
1
  import { TurboModuleRegistry, type TurboModule } from "react-native";
2
2
  export interface Spec extends TurboModule { call(method: string, args: string): Promise<string>; }
3
- export default TurboModuleRegistry.getEnforcing<Spec>("NativeDesktopSystem");
3
+ export default TurboModuleRegistry.get<Spec>("NativeDesktopSystem");
@@ -1,35 +1,92 @@
1
+ import { Platform } from "react-native";
1
2
  import Native from "./NativeDesktopSystem";
2
- import { onDesktopEvent } from "@legendapp/spark-desktop-app";
3
- export type SystemInfo = { osVersion: string; architecture: string; locale: string; dark: boolean; idleSeconds: number; onBattery: boolean; batteryLevel: number | null };
3
+ import { onDesktopEvent } from "@legendapp/spark-desktop-app/src/events";
4
+ import { SparkError, asyncRegistration, invokeNative, parseNativeResult, type Availability, type AsyncRegistration } from "@legendapp/spark-desktop-app/src/contracts";
5
+ import { menuItems, selectableMenuIds, type MenuItem } from "@legendapp/spark-desktop-app/src/contracts/menu";
6
+ export type { AsyncRegistration } from "@legendapp/spark-desktop-app/src/contracts";
7
+ type SupportedDockLeaf = Exclude<MenuItem, { type: "submenu" | "role" | "slider" }> extends infer Item
8
+ ? Item extends unknown ? Omit<Item, "target" | "placement" | "icon" | "shortcut" | "hidden"> : never
9
+ : never;
10
+ export type DockMenuItem = SupportedDockLeaf | (Omit<Extract<MenuItem, { type: "submenu" }>, "items" | "target" | "placement" | "icon" | "shortcut" | "hidden"> & { items: readonly DockMenuItem[] });
11
+ type SupportedTaskbarLeaf = Extract<MenuItem, { type: "action" | "checkbox" }> extends infer Item
12
+ ? Item extends unknown ? Omit<Item, "target" | "placement" | "icon" | "shortcut" | "hidden" | "disabled"> & { disabled?: false } : never
13
+ : never;
14
+ export type TaskbarMenuItem = SupportedTaskbarLeaf;
15
+ type LauncherMenuOptions = { items: readonly MenuItem[]; onAction: (event: { type: "action"; itemId: string }) => void };
16
+ export interface DockMenuOptions { items: readonly DockMenuItem[]; onAction: (event: { type: "action"; itemId: string }) => void }
17
+ export interface TaskbarMenuOptions { items: readonly TaskbarMenuItem[]; onAction: (event: { type: "action"; itemId: string }) => void }
18
+ export interface SystemInfo { osVersion: string; architecture: string; locale: string; dark: boolean; idleSeconds: number; onBattery: boolean; batteryLevel: number | null }
4
19
  export type SystemEvent = { type: "sleep" | "wake" | "lock" | "unlock" | "powerChanged" | "appearanceChanged" | "displaysChanged" };
5
- export type DockMenuItem = { id: string; title: string; enabled?: boolean; checked?: boolean };
6
- async function call<T = void>(method: string, args: object = {}): Promise<T> { return JSON.parse(await Native.call(method, JSON.stringify(args))); }
7
- export const getSystemInfo = () => call<SystemInfo>("info");
8
- export const getLoginItemStatus = () => call<"enabled" | "disabled" | "requiresApproval" | "notFound" | "unavailable">("loginStatus");
9
- export const setLaunchAtLogin = (enabled: boolean) => call("login", { enabled });
10
- export const setDockBadge = (label: string) => call("badge", { label });
11
- export async function requestAttention(critical = false) {
12
- const id = await call<number>("attention", { critical });
13
- return { remove: () => call("cancelAttention", { id }) };
14
- }
15
- export async function preventSleep(reason: string, kind: "display" | "system" = "display") {
16
- if (!reason.trim()) throw new Error("A reason is required");
17
- const id = await call<number>("preventSleep", { reason, kind }); let removed = false;
18
- return { async remove() { if (!removed) { await call("allowSleep", { id }); removed = true; } } };
19
- }
20
- let dockSequence = 0;
21
- export async function setDockMenu(items: DockMenuItem[], handler: (id: string) => void) {
22
- const owner = `dock-${Date.now()}-${++dockSequence}`;
23
- const ids = new Set<string>();
24
- for (const item of items) { if (!item.id || !item.title || ids.has(item.id)) throw new Error("Dock items need unique ids and titles"); ids.add(item.id); }
25
- const subscription = onDesktopEvent(event => { if (event.type === "dockAction" && event.owner === owner && typeof event.id === "string") handler(event.id); });
26
- try { await call("dockMenu", { items, owner }); } catch (error) { subscription.remove(); throw error; }
27
- let removed = false;
28
- return { async remove() { if (!removed) { await call("clearDockMenu", { owner }); subscription.remove(); removed = true; } } };
29
- }
30
- export async function onSystemEvent(handler: (event: SystemEvent) => void) {
31
- const types = ["sleep", "wake", "lock", "unlock", "powerChanged", "appearanceChanged", "displaysChanged"];
32
- const subscription = onDesktopEvent(event => { if (types.includes(event.type)) handler(event as SystemEvent); });
33
- try { await call("observe"); } catch (error) { subscription.remove(); throw error; }
34
- return subscription;
20
+ export type LoginItemStatus = "enabled" | "disabled" | "requiresApproval" | "notFound" | "unavailable";
21
+ export interface AttentionOptions { kind?: "informational" | "critical" }
22
+ export interface PreventSleepOptions { reason: string; kind?: "display" | "system" }
23
+ let sequence = 0;
24
+ const token = () => `system-${Date.now()}-${++sequence}`;
25
+ export function getSystemAvailability(): Availability {
26
+ if (Platform.OS !== "macos" && Platform.OS !== "windows") return { available: false, reason: "unsupported-platform" };
27
+ return Native ? { available: true } : { available: false, reason: "missing-module" };
28
+ }
29
+ async function call<T>(method: string, args: object, validate: (value: unknown) => value is T): Promise<T> {
30
+ const available = getSystemAvailability();
31
+ if (!available.available) throw new SparkError(available.reason === "unsupported-platform" ? "E_UNSUPPORTED_PLATFORM" : "E_MODULE_UNAVAILABLE", "System APIs are unavailable");
32
+ return parseNativeResult(await invokeNative(() => Native!.call(method, JSON.stringify(args))), validate);
33
+ }
34
+ async function command(method: string, args: object = {}): Promise<void> { await call(method, args, (value): value is null => value === null); }
35
+ function options(value: object, keys: readonly string[]) {
36
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new SparkError("E_INVALID_ARGUMENT", "Expected options");
37
+ for (const key of Object.keys(value)) if (!keys.includes(key)) throw new SparkError("E_UNSUPPORTED_OPTION", `Unsupported system option: ${key}`);
38
+ }
39
+ function isInfo(value: unknown): value is SystemInfo {
40
+ if (!value || typeof value !== "object") return false;
41
+ const info = value as SystemInfo;
42
+ return [info.osVersion, info.architecture, info.locale].every(value => typeof value === "string" && value.length > 0) && typeof info.dark === "boolean" && typeof info.onBattery === "boolean" && Number.isFinite(info.idleSeconds) && info.idleSeconds >= 0 && (info.batteryLevel === null || (typeof info.batteryLevel === "number" && Number.isFinite(info.batteryLevel) && info.batteryLevel >= 0 && info.batteryLevel <= 1));
43
+ }
44
+ export function getSystemInfo(): Promise<SystemInfo> { return call("info", {}, isInfo); }
45
+ export function getLoginItemStatus(): Promise<LoginItemStatus> { return call("loginStatus", {}, (value): value is LoginItemStatus => typeof value === "string" && ["enabled", "disabled", "requiresApproval", "notFound", "unavailable"].includes(value)); }
46
+ export async function setLaunchAtLogin(enabled: boolean): Promise<void> {
47
+ if (typeof enabled !== "boolean") throw new SparkError("E_INVALID_ARGUMENT", "Expected enabled boolean");
48
+ await command("login", { enabled });
49
+ }
50
+ /** Dock badge on macOS, taskbar overlay on Windows; empty string clears it. */
51
+ export async function setAppBadge(label: string): Promise<void> {
52
+ if (typeof label !== "string" || label.includes("\0")) throw new SparkError("E_INVALID_ARGUMENT", "Expected badge text without NUL");
53
+ await command("badge", { label });
54
+ }
55
+ export async function requestAttention(value: AttentionOptions = {}): Promise<AsyncRegistration> {
56
+ options(value, ["kind"]);
57
+ if (value.kind !== undefined && value.kind !== "informational" && value.kind !== "critical") throw new SparkError("E_INVALID_ARGUMENT", "Unknown attention kind");
58
+ // AppKit can return a negative request token when no attention is necessary.
59
+ const id = await call("attention", { critical: value.kind === "critical" }, (value): value is number => typeof value === "number" && Number.isSafeInteger(value));
60
+ return asyncRegistration(() => {}, () => command("cancelAttention", { id }));
61
+ }
62
+ export async function preventSleep(value: PreventSleepOptions): Promise<AsyncRegistration> {
63
+ options(value, ["reason", "kind"]);
64
+ if (typeof value.reason !== "string" || !value.reason.trim() || value.reason.includes("\0")) throw new SparkError("E_INVALID_ARGUMENT", "A reason is required");
65
+ if (value.kind !== undefined && value.kind !== "display" && value.kind !== "system") throw new SparkError("E_INVALID_ARGUMENT", "Unknown sleep prevention kind");
66
+ const id = await call("preventSleep", { reason: value.reason, kind: value.kind ?? "display" }, (value): value is number => typeof value === "number" && Number.isSafeInteger(value) && value >= 0);
67
+ return asyncRegistration(() => {}, () => command("allowSleep", { id }));
68
+ }
69
+ async function createLauncherMenu(value: LauncherMenuOptions, platform: "macos" | "windows"): Promise<AsyncRegistration> {
70
+ if (Platform.OS !== platform) throw new SparkError("E_UNSUPPORTED_PLATFORM", `${platform === "macos" ? "Dock" : "Taskbar"} menus require ${platform}`);
71
+ options(value, ["items", "onAction"]);
72
+ if (typeof value.onAction !== "function") throw new SparkError("E_INVALID_ARGUMENT", "Expected onAction callback");
73
+ // Jump Lists cannot render native submenus, shortcuts, icons or disabled tasks.
74
+ const items = menuItems(value.items, { types: platform === "macos" ? ["action", "checkbox", "separator", "submenu"] : ["action", "checkbox"] }, platform);
75
+ const ids = selectableMenuIds(items), owner = token(), onAction = value.onAction;
76
+ let stopped = false, ready = false;
77
+ const subscription = onDesktopEvent(event => {
78
+ if (ready && !stopped && event.type === "dockAction" && event.owner === owner && typeof event.id === "string" && ids.has(event.id)) onAction({ type: "action", itemId: event.id });
79
+ }, { types: ["dockAction"], target: { field: "owner", value: owner } });
80
+ try { await command("dockMenu", { items, owner }); ready = true; } catch (error) { subscription.remove(); throw error; }
81
+ return asyncRegistration(() => { stopped = true; subscription.remove(); }, () => command("clearDockMenu", { owner }));
82
+ }
83
+ export function createDockMenu(options: DockMenuOptions): Promise<AsyncRegistration> { return createLauncherMenu(options, "macos"); }
84
+ export function createTaskbarMenu(options: TaskbarMenuOptions): Promise<AsyncRegistration> { return createLauncherMenu(options, "windows"); }
85
+ export async function onSystemEvent(handler: (event: SystemEvent) => void): Promise<AsyncRegistration> {
86
+ if (typeof handler !== "function") throw new SparkError("E_INVALID_ARGUMENT", "Expected system event handler");
87
+ const types: readonly SystemEvent["type"][] = ["sleep", "wake", "lock", "unlock", "powerChanged", "appearanceChanged", "displaysChanged"];
88
+ const id = token(); let stopped = false;
89
+ const subscription = onDesktopEvent(event => { if (!stopped && types.includes(event.type as SystemEvent["type"])) handler({ type: event.type as SystemEvent["type"] }); }, { types });
90
+ try { await command("observe", { id }); } catch (error) { subscription.remove(); throw error; }
91
+ return asyncRegistration(() => { stopped = true; subscription.remove(); }, () => command("unobserve", { id }));
35
92
  }
@@ -2,6 +2,7 @@
2
2
  #include "NativeModules.h"
3
3
  #include <algorithm>
4
4
  #include <map>
5
+ #include <set>
5
6
  #include <memory>
6
7
  #include <filesystem>
7
8
  #include <powrprof.h>
@@ -38,6 +39,7 @@ inline std::wstring AppId() {
38
39
  struct SystemState {
39
40
  React::ReactContext context;
40
41
  HWND hwnd = nullptr; bool observing = false;
42
+ std::set<std::string> subscriptions;
41
43
  uint64_t sequence = 0;
42
44
  std::map<uint64_t, std::pair<handle, POWER_REQUEST_TYPE>> requests;
43
45
  std::map<uint64_t, bool> attention;
@@ -110,7 +112,7 @@ struct SystemState {
110
112
  Json::JsonObject result; auto version = std::stoull(std::wstring(Windows::System::Profile::AnalyticsInfo::VersionInfo().DeviceFamilyVersion()));
111
113
  auto number = [&](wchar_t const *key, double value) { result.SetNamedValue(key, Json::JsonValue::CreateNumberValue(value)); };
112
114
  result.SetNamedValue(L"osVersion", Json::JsonValue::CreateStringValue(std::to_wstring(version >> 48) + L"." + std::to_wstring((version >> 32) & 0xffff) + L"." + std::to_wstring((version >> 16) & 0xffff)));
113
- SYSTEM_INFO info{}; GetNativeSystemInfo(&info); result.SetNamedValue(L"architecture", Json::JsonValue::CreateStringValue(info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_ARM64 ? L"arm64" : info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 ? L"x64" : L"x86"));
115
+ SYSTEM_INFO info{}; ::GetSystemInfo(&info); result.SetNamedValue(L"architecture", Json::JsonValue::CreateStringValue(info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_ARM64 ? L"arm64" : info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 ? L"x64" : L"x86"));
114
116
  wchar_t locale[LOCALE_NAME_MAX_LENGTH]{}; GetUserDefaultLocaleName(locale, LOCALE_NAME_MAX_LENGTH); result.SetNamedValue(L"locale", Json::JsonValue::CreateStringValue(locale));
115
117
  Windows::UI::ViewManagement::UISettings settings; auto color = settings.GetColorValue(Windows::UI::ViewManagement::UIColorType::Background); result.SetNamedValue(L"dark", Json::JsonValue::CreateBooleanValue(color.R + color.G + color.B < 384));
116
118
  LASTINPUTINFO input{sizeof(LASTINPUTINFO)}; if (!GetLastInputInfo(&input)) throw_last_error(); number(L"idleSeconds", static_cast<DWORD>(GetTickCount() - input.dwTime) / 1000.0);
@@ -130,7 +132,8 @@ struct SparkSystem {
130
132
  try {
131
133
  auto args = Json::JsonObject::Parse(to_hstring(encoded));
132
134
  if (method == "info") { promise.Resolve(to_string(SystemState::Info().Stringify())); return; }
133
- if (method == "observe") { current->EnsureWindow(); current->observing = true; }
135
+ if (method == "observe") { current->EnsureWindow(); current->subscriptions.insert(to_string(args.GetNamedString(L"id"))); current->observing = true; }
136
+ else if (method == "unobserve") { current->subscriptions.erase(to_string(args.GetNamedString(L"id"))); current->observing = !current->subscriptions.empty(); if (!current->observing && current->hwnd) { WTSUnRegisterSessionNotification(current->hwnd); DestroyWindow(current->hwnd); current->hwnd = nullptr; } }
134
137
  else if (method == "loginStatus") { promise.Resolve("\"unavailable\""); return; }
135
138
  else if (method == "login") { promise.Reject(React::ReactError{"E_UNAVAILABLE", "Login startup requires a standalone distribution app"}); return; }
136
139
  else if (method == "badge") { auto label = args.GetNamedString(L"label"); HICON icon = label.empty() ? nullptr : SystemState::Badge(std::wstring(label)); auto result = SystemState::Taskbar()->SetOverlayIcon(MainWindow(), icon, label.c_str()); if (icon) DestroyIcon(icon); check_hresult(result); current->badge = !label.empty(); }
@@ -141,8 +144,9 @@ struct SparkSystem {
141
144
  handle request{PowerCreateRequest(&description)}; if (!request || request.get() == INVALID_HANDLE_VALUE) { request.detach(); throw_last_error(); } auto kind = args.GetNamedString(L"kind") == L"system" ? PowerRequestSystemRequired : PowerRequestDisplayRequired;
142
145
  if (!PowerSetRequest(request.get(), kind)) throw_last_error(); if (kind == PowerRequestDisplayRequired && !PowerSetRequest(request.get(), PowerRequestSystemRequired)) throw_last_error();
143
146
  auto id = ++current->sequence; current->requests.emplace(id, std::make_pair(std::move(request), kind)); promise.Resolve(std::to_string(id)); return;
144
- } else if (method == "allowSleep") { auto found = current->requests.find(static_cast<uint64_t>(args.GetNamedNumber(L"id"))); if (found != current->requests.end()) { if (!PowerClearRequest(found->second.first.get(), found->second.second)) throw_last_error(); if (found->second.second == PowerRequestDisplayRequired) PowerClearRequest(found->second.first.get(), PowerRequestSystemRequired); current->requests.erase(found); } }
145
- else if (method == "dockMenu") { if (!current->dockOwner.empty()) { promise.Reject(React::ReactError{"E_DOCK_MENU_EXISTS", "Remove the existing taskbar menu before replacing it"}); return; } current->DockMenu(args); }
147
+ } // Closing the dedicated handle releases every request held by this registration.
148
+ else if (method == "allowSleep") { current->requests.erase(static_cast<uint64_t>(args.GetNamedNumber(L"id"))); }
149
+ else if (method == "dockMenu") { if (!current->dockOwner.empty()) { promise.Reject(React::ReactError{"E_ALREADY_EXISTS", "Remove the existing taskbar menu before replacing it"}); return; } current->DockMenu(args); }
146
150
  else if (method == "clearDockMenu") { if (current->dockOwner == std::wstring(args.GetNamedString(L"owner"))) current->ClearDock(); }
147
151
  else throw hresult_invalid_argument(L"Unknown system operation");
148
152
  promise.Resolve("null");
@@ -3,14 +3,15 @@
3
3
 
4
4
  @interface SparkTrayItem : NSObject
5
5
  @property NSString *identifier;
6
+ @property NSString *instanceId;
6
7
  @property NSStatusItem *item;
7
8
  @property NSDictionary *options;
8
9
  - (void)clicked:(id)sender;
9
10
  - (void)selected:(NSMenuItem *)sender;
10
11
  @end
11
12
  @implementation SparkTrayItem
12
- - (void)clicked:(id)sender { SparkEmit(@{ @"type": @"trayClick", @"trayId": self.identifier }); }
13
- - (void)selected:(NSMenuItem *)sender { SparkEmit(@{ @"type": @"trayAction", @"trayId": self.identifier, @"itemId": sender.representedObject }); }
13
+ - (void)clicked:(id)sender { SparkEmit(@{ @"type": @"trayClick", @"trayId": self.identifier, @"instanceId": self.instanceId }); }
14
+ - (void)selected:(NSMenuItem *)sender { SparkEmit(@{ @"type": @"trayAction", @"trayId": self.identifier, @"instanceId": self.instanceId, @"itemId": sender.representedObject }); }
14
15
  @end
15
16
  static NSMenu *Menu(NSArray *items, SparkTrayItem *owner) {
16
17
  NSMenu *menu = [NSMenu new]; menu.autoenablesItems = NO;
@@ -36,25 +37,36 @@ RCT_EXPORT_MODULE(NativeDesktopTray)
36
37
  NSDictionary *args = SparkArgs(json); NSString *key = args[@"id"];
37
38
  if (![key isKindOfClass:NSString.class] || !key.length) { SparkInvalid(reject, @"Tray needs an id"); return; }
38
39
  SparkTrayItem *owner = self.items[key];
40
+ if (owner && ![owner.instanceId isEqual:args[@"instanceId"]]) { if ([method isEqual:@"remove"]) resolve(@"null"); else reject([method isEqual:@"create"] ? @"E_ALREADY_EXISTS" : @"E_NOT_FOUND", @"Tray belongs to a different instance", nil); return; }
39
41
  if ([method isEqual:@"remove"]) { if (owner) [NSStatusBar.systemStatusBar removeStatusItem:owner.item]; [self.items removeObjectForKey:key]; resolve(@"null"); return; }
40
42
  if (![method isEqual:@"create"] && ![method isEqual:@"update"]) { SparkInvalid(reject, @"Unknown tray operation"); return; }
41
- if ([method isEqual:@"create"] && owner) { reject(@"E_TRAY_EXISTS", @"Tray id already exists", nil); return; }
43
+ if ([method isEqual:@"create"] && owner) { reject(@"E_ALREADY_EXISTS", @"Tray id already exists", nil); return; }
42
44
  if ([method isEqual:@"update"] && !owner) { reject(@"E_NOT_FOUND", @"Tray was removed", nil); return; }
43
45
  NSMutableDictionary *options = [owner.options mutableCopy] ?: [NSMutableDictionary new]; [options addEntriesFromDictionary:args];
44
- NSImage *image = nil;
45
- if ([options[@"symbol"] length]) {
46
+ BOOL imageChanged = !owner || args[@"symbol"] != nil || args[@"imagePath"] != nil;
47
+ BOOL menuChanged = !owner || args[@"menu"] != nil;
48
+ NSImage *image = imageChanged ? nil : owner.item.button.image;
49
+ if (imageChanged && [options[@"symbol"] length]) {
46
50
  image = [NSImage imageWithSystemSymbolName:options[@"symbol"] accessibilityDescription:options[@"tooltip"] ?: options[@"title"]];
47
51
  if (!image) { SparkInvalid(reject, @"Unknown SF Symbol name"); return; }
48
52
  [image setTemplate:YES];
49
53
  }
54
+ if (imageChanged && [options[@"imagePath"] length]) {
55
+ image = [[NSImage alloc] initWithContentsOfFile:options[@"imagePath"]];
56
+ if (!image) { reject(@"E_INVALID_DATA", @"Tray image could not be decoded", nil); return; }
57
+ NSSize size = image.size; CGFloat height = MAX(1, NSStatusBar.systemStatusBar.thickness - 4);
58
+ image.size = NSMakeSize(size.height > 0 ? height * size.width / size.height : height, height);
59
+ }
50
60
  if (![options[@"title"] length] && !image) { SparkInvalid(reject, @"Tray needs a title or symbol"); return; }
51
- if (!owner) { owner = [SparkTrayItem new]; owner.identifier = key; owner.item = [NSStatusBar.systemStatusBar statusItemWithLength:NSVariableStatusItemLength]; self.items[key] = owner; }
61
+ if (!owner) { owner = [SparkTrayItem new]; owner.identifier = key; owner.instanceId = args[@"instanceId"]; owner.item = [NSStatusBar.systemStatusBar statusItemWithLength:NSVariableStatusItemLength]; self.items[key] = owner; }
62
+ NSMenu *menu = menuChanged && [options[@"menu"] count] ? Menu(options[@"menu"], owner) : nil;
52
63
  owner.options = options;
53
- owner.item.button.title = options[@"title"] ?: @""; owner.item.button.image = image;
64
+ owner.item.button.title = options[@"title"] ?: @"";
65
+ if (imageChanged) owner.item.button.image = image;
54
66
  owner.item.button.imagePosition = NSImageLeft; owner.item.button.toolTip = options[@"tooltip"];
55
67
  owner.item.button.target = owner; owner.item.button.action = @selector(clicked:);
56
68
  owner.item.button.accessibilityIdentifier = [@"spark.tray." stringByAppendingString:key];
57
- owner.item.menu = [options[@"menu"] count] ? Menu(options[@"menu"], owner) : nil;
69
+ if (menuChanged) owner.item.menu = menu;
58
70
  resolve(@"null");
59
71
  });
60
72
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@legendapp/spark-tray",
3
- "version": "0.0.1-next.2",
3
+ "version": "0.0.1-next.3",
4
4
  "license": "MIT",
5
5
  "main": "src/index.ts",
6
6
  "react-native": "src/index.ts",
@@ -40,7 +40,7 @@
40
40
  ]
41
41
  },
42
42
  "dependencies": {
43
- "@legendapp/spark-desktop-app": "0.0.1-next.2"
43
+ "@legendapp/spark-desktop-app": "0.0.1-next.3"
44
44
  },
45
45
  "private": true
46
46
  }
@@ -1,4 +1,4 @@
1
1
  import type { TurboModule } from "react-native";
2
2
  import { TurboModuleRegistry } from "react-native";
3
3
  export interface Spec extends TurboModule { call(method: string, args: string): Promise<string>; }
4
- export default TurboModuleRegistry.getEnforcing<Spec>("NativeDesktopTray");
4
+ export default TurboModuleRegistry.get<Spec>("NativeDesktopTray");
@@ -1,46 +1,103 @@
1
+ import { nativePath } from "@legendapp/spark-desktop-app/src/contracts/path";
2
+ import { Platform } from "react-native";
1
3
  import Native from "./NativeDesktopTray";
2
- import { onDesktopEvent } from "@legendapp/spark-desktop-app";
3
-
4
- export type TrayMenuItem = { id: string; title: string; enabled?: boolean; checked?: boolean; items?: TrayMenuItem[] } | { separator: true };
5
- export type TrayOptions = { id: string; title?: string; /** SF Symbol name. */ symbol?: string; tooltip?: string; menu?: TrayMenuItem[] };
6
- export type TrayAction = { trayId: string; itemId?: string; type: "trayClick" | "trayAction" };
7
- async function call<T = void>(method: string, args: object): Promise<T> { return JSON.parse(await Native.call(method, JSON.stringify(args))) as T; }
8
- function validate(options: Partial<TrayOptions>) {
9
- if (options.id !== undefined && !/^[a-zA-Z0-9_-]{1,100}$/.test(options.id)) throw new Error("Invalid tray id");
10
- if (options.title !== undefined && typeof options.title !== "string") throw new Error("Tray title must be a string");
11
- const ids = new Set<string>();
12
- function menu(items: TrayMenuItem[], depth: number) {
13
- if (depth > 5) throw new Error("Tray menus support at most five levels");
14
- for (const item of items) {
15
- if ("separator" in item) continue;
16
- if (!item.id || ids.has(item.id) || !item.title) throw new Error("Tray menu items need unique ids and titles");
17
- ids.add(item.id);
18
- if (item.items) menu(item.items, depth + 1);
4
+ import { onDesktopEvent } from "@legendapp/spark-desktop-app/src/events";
5
+ import { SparkError, asyncRegistration, invokeNative, parseNativeResult, type Availability, type AsyncRegistration } from "@legendapp/spark-desktop-app/src/contracts";
6
+ import { menuItems, selectableMenuIds, type MenuItem as SharedMenuItem, type MenuWireItem } from "@legendapp/spark-desktop-app/src/contracts/menu";
7
+ type TrayEntry = { id: string; label: string; disabled?: boolean; hidden?: boolean };
8
+ type TrayLeaf = { type: "separator" } | (TrayEntry & { type: "action" }) | (TrayEntry & { type: "checkbox"; checked: boolean });
9
+ export type MenuItem = TrayLeaf | (TrayEntry & { type: "submenu"; items: readonly MenuItem[] });
10
+ export type MenuAction = Extract<import("@legendapp/spark-desktop-app/src/contracts/menu").MenuAction, { type: "action" }>;
11
+ export type { MenuIcon } from "@legendapp/spark-desktop-app/src/contracts/menu";
12
+ export type { AsyncRegistration } from "@legendapp/spark-desktop-app/src/contracts";
13
+ export type TrayAction = { type: "click" } | { type: "action"; itemId: string };
14
+ export interface TrayUpdate {
15
+ /** Visible text on macOS; accessible tooltip fallback on Windows. */
16
+ title?: string;
17
+ /** Local image file; PNG is supported on both desktop hosts. Null clears it. */
18
+ image?: { path: string } | null;
19
+ tooltip?: string;
20
+ /** Replaces the entire menu; [] removes it. */
21
+ menu?: readonly MenuItem[];
22
+ /** Replaces macOS presentation options; null clears the symbol. */
23
+ macos?: { symbol?: string | null };
24
+ }
25
+ export interface TrayOptions extends TrayUpdate { id: string; onAction?: (action: TrayAction) => void }
26
+ export interface Tray extends AsyncRegistration { readonly id: string; update(changes: TrayUpdate): Promise<void> }
27
+ interface Wire { id: string; instanceId: string; imagePath: string; title: string; tooltip?: string; symbol: string; menu: MenuWireItem[] }
28
+ export function getTrayAvailability(): Availability {
29
+ if (Platform.OS !== "macos" && Platform.OS !== "windows") return { available: false, reason: "unsupported-platform" };
30
+ return Native ? { available: true } : { available: false, reason: "missing-module" };
31
+ }
32
+ async function call(method: string, args: object): Promise<void> {
33
+ const available = getTrayAvailability();
34
+ if (!available.available) throw new SparkError(available.reason === "unsupported-platform" ? "E_UNSUPPORTED_PLATFORM" : "E_MODULE_UNAVAILABLE", "Tray is unavailable");
35
+ parseNativeResult(await invokeNative(() => Native!.call(method, JSON.stringify(args))), (value): value is null => value === null);
36
+ }
37
+ function validate(options: TrayUpdate, creating = false): Partial<Wire> {
38
+ if (!options || typeof options !== "object" || Array.isArray(options)) throw new SparkError("E_INVALID_ARGUMENT", "Expected tray options");
39
+ for (const key of Object.keys(options)) if (!["title", "tooltip", "image", "menu", "macos", ...(creating ? ["id", "onAction"] : [])].includes(key)) throw new SparkError("E_UNSUPPORTED_OPTION", `Unsupported tray option: ${key}`);
40
+ const result: Partial<Wire> = {};
41
+ for (const key of ["title", "tooltip"] as const) if (options[key] !== undefined) {
42
+ if (typeof options[key] !== "string" || options[key].includes("\0")) throw new SparkError("E_INVALID_ARGUMENT", `Tray ${key} must be a string without NUL`);
43
+ result[key] = options[key];
44
+ }
45
+ if (options.image !== undefined) {
46
+ if (options.image === null) result.imagePath = "";
47
+ else {
48
+ if (!options.image || typeof options.image !== "object" || Array.isArray(options.image) || Object.keys(options.image).some(key => key !== "path")) throw new SparkError("E_INVALID_ARGUMENT", "Expected an image path or null");
49
+ result.imagePath = nativePath(options.image.path, Platform.OS);
19
50
  }
20
51
  }
21
- if (options.menu) menu(options.menu, 1);
52
+ if (options.macos !== undefined && Platform.OS === "macos") {
53
+ if (!options.macos || typeof options.macos !== "object" || Array.isArray(options.macos) || Object.keys(options.macos).some(key => key !== "symbol")) throw new SparkError("E_INVALID_ARGUMENT", "Expected macos tray options");
54
+ const symbol = options.macos.symbol;
55
+ if (symbol != null && (typeof symbol !== "string" || !symbol.trim() || symbol.includes("\0"))) throw new SparkError("E_INVALID_ARGUMENT", "Expected an SF Symbol name or null");
56
+ result.symbol = symbol ?? "";
57
+ }
58
+ if (options.menu !== undefined) result.menu = menuItems(options.menu, { types: ["action", "checkbox", "separator", "submenu"] }, Platform.OS === "windows" ? "windows" : "macos");
59
+ return result;
60
+ }
61
+ function presentation(options: Wire) {
62
+ if (options.imagePath && options.symbol) throw new SparkError("E_INVALID_ARGUMENT", "Choose a tray image or macOS symbol");
63
+ if (!options.title.trim() && !options.symbol && !options.imagePath) throw new SparkError("E_INVALID_ARGUMENT", "Tray needs a title, image or macOS symbol");
22
64
  }
23
- export async function createTray(options: TrayOptions, onAction: (action: TrayAction) => void = () => {}) {
24
- validate(options);
25
- if (!options.id || (!options.title && !options.symbol)) throw new Error("Tray needs an id and a title or symbol");
26
- let removed = false;
65
+ let sequence = 0;
66
+ export async function createTray(options: TrayOptions): Promise<Tray> {
67
+ const changes = validate(options, true);
68
+ if (typeof options.id !== "string" || !/^[a-zA-Z0-9_-]{1,100}$/.test(options.id)) throw new SparkError("E_INVALID_ARGUMENT", "Invalid tray id");
69
+ if (options.onAction !== undefined && typeof options.onAction !== "function") throw new SparkError("E_INVALID_ARGUMENT", "Expected onAction callback");
70
+ const id = options.id, onAction = options.onAction;
71
+ const instanceId = `tray-${Date.now()}-${++sequence}-${Math.random().toString(36).slice(2)}`;
72
+ let current: Wire = { id, instanceId, imagePath: "", title: "", symbol: "", menu: [], ...changes };
73
+ presentation(current);
74
+ const available = getTrayAvailability();
75
+ if (!available.available) throw new SparkError(available.reason === "unsupported-platform" ? "E_UNSUPPORTED_PLATFORM" : "E_MODULE_UNAVAILABLE", "Tray is unavailable");
76
+ let stopped = false, ready = false;
77
+ let selected = selectableMenuIds(current.menu);
27
78
  let queue: Promise<unknown> = Promise.resolve();
28
79
  const sub = onDesktopEvent(event => {
29
- if (!removed && event.trayId === options.id && (event.type === "trayClick" || event.type === "trayAction")) onAction(event as TrayAction);
30
- });
31
- try { await call("create", options); } catch (error) { sub.remove(); throw error; }
80
+ if (!ready || stopped || event.trayId !== id || event.instanceId !== instanceId) return;
81
+ if (event.type === "trayClick") onAction?.({ type: "click" });
82
+ else if (event.type === "trayAction" && typeof event.itemId === "string" && selected.has(event.itemId)) onAction?.({ type: "action", itemId: event.itemId });
83
+ }, { types: ["trayClick", "trayAction"], target: { field: "trayId", value: id } });
84
+ try { await call("create", current); ready = true; } catch (error) { sub.remove(); throw error; }
85
+ const registration = asyncRegistration(() => { stopped = true; sub.remove(); }, async () => { await queue; await call("remove", { id, instanceId }); });
32
86
  return {
33
- async update(changes: Omit<Partial<TrayOptions>, "id">) {
34
- if (removed) throw new Error("Tray was removed");
35
- validate(changes);
36
- const next = queue.then(() => call("update", { ...changes, id: options.id }));
87
+ id,
88
+ async update(changes) {
89
+ if (stopped) throw new SparkError("E_CLOSED", "Tray was removed");
90
+ const snapshot = validate(changes);
91
+ const next = queue.then(async () => {
92
+ const options = { ...current, ...snapshot };
93
+ presentation(options);
94
+ await call("update", { id, instanceId, ...snapshot });
95
+ current = options;
96
+ if (snapshot.menu !== undefined) selected = selectableMenuIds(options.menu);
97
+ });
37
98
  queue = next.catch(() => {});
38
99
  await next;
39
100
  },
40
- async remove() {
41
- if (removed) return;
42
- removed = true; sub.remove();
43
- await queue; await call("remove", { id: options.id });
44
- },
101
+ remove: registration.remove,
45
102
  };
46
103
  }