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

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 (422) 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/camera.ts +13 -0
  7. package/config-plugin.d.cts +2 -0
  8. package/contracts.ts +2 -0
  9. package/diagnostics.ts +1 -0
  10. package/expo-config.d.cts +1 -0
  11. package/metro.cjs +4 -1
  12. package/metro.d.cts +12 -0
  13. package/native.cjs +3 -1
  14. package/native.d.cts +6 -0
  15. package/package.json +93 -15
  16. package/patches/camera/react-native-nitro-image.patch +320 -0
  17. package/patches/camera/react-native-nitro-modules.patch +50 -0
  18. package/patches/camera/react-native-vision-camera.patch +2662 -0
  19. package/patches/camera/upstream.json +22 -0
  20. package/patches/dependencies.json +8 -0
  21. package/patches/react-native-enriched-markdown@1.0.2.patch +8684 -0
  22. package/postinstall.cjs +1 -0
  23. package/schema.json +276 -58
  24. package/settings-observable.ts +1 -0
  25. package/settings-window.ts +1 -0
  26. package/shortcuts-commands.ts +1 -0
  27. package/shortcuts-keyboard.ts +3 -0
  28. package/ui-classnames.ts +1 -0
  29. package/ui-glass.ts +1 -0
  30. package/ui-search.ts +1 -0
  31. package/ui-sidebar.ts +1 -0
  32. package/ui-split-view.ts +1 -0
  33. package/ui-swipe.ts +1 -0
  34. package/ui-symbol.ts +1 -0
  35. package/vendor/node_modules/@legendapp/spark-ai/package.json +19 -0
  36. package/vendor/node_modules/@legendapp/spark-ai/src/availability.ts +17 -0
  37. package/vendor/node_modules/@legendapp/spark-ai/src/index.ts +15 -0
  38. package/vendor/node_modules/@legendapp/spark-ai/src/invocations.ts +34 -0
  39. package/vendor/node_modules/@legendapp/spark-ai/src/output.ts +62 -0
  40. package/vendor/node_modules/@legendapp/spark-ai/src/run.ts +12 -0
  41. package/vendor/node_modules/@legendapp/spark-ai/src/types.ts +16 -0
  42. package/vendor/node_modules/@legendapp/spark-audio/macos/RNSparkAudio.h +2 -1
  43. package/vendor/node_modules/@legendapp/spark-audio/macos/RNSparkAudio.mm +126 -24
  44. package/vendor/node_modules/@legendapp/spark-audio/package.json +5 -2
  45. package/vendor/node_modules/@legendapp/spark-audio/src/NativeSparkAudio.ts +5 -1
  46. package/vendor/node_modules/@legendapp/spark-audio/src/desktop.ts +36 -32
  47. package/vendor/node_modules/@legendapp/spark-audio/src/events.ts +11 -0
  48. package/vendor/node_modules/@legendapp/spark-audio/src/hooks.ts +65 -0
  49. package/vendor/node_modules/@legendapp/spark-audio/src/index.web.ts +92 -33
  50. package/vendor/node_modules/@legendapp/spark-audio/src/media-session-unavailable.ts +6 -4
  51. package/vendor/node_modules/@legendapp/spark-audio/src/media-session.ts +52 -19
  52. package/vendor/node_modules/@legendapp/spark-audio/src/media-session.web.ts +18 -10
  53. package/vendor/node_modules/@legendapp/spark-audio/src/media-types.ts +40 -13
  54. package/vendor/node_modules/@legendapp/spark-audio/src/mobile.ts +40 -32
  55. package/vendor/node_modules/@legendapp/spark-audio/src/player.ts +97 -0
  56. package/vendor/node_modules/@legendapp/spark-audio/src/status-listeners.ts +21 -15
  57. package/vendor/node_modules/@legendapp/spark-audio/src/types.ts +9 -4
  58. package/vendor/node_modules/@legendapp/spark-audio/windows/SparkAudio/SparkAudio.h +67 -20
  59. package/vendor/node_modules/@legendapp/spark-auth-session/package.json +4 -3
  60. package/vendor/node_modules/@legendapp/spark-auth-session/src/core.ts +60 -42
  61. package/vendor/node_modules/@legendapp/spark-auth-session/src/index.ts +28 -12
  62. package/vendor/node_modules/@legendapp/spark-auth-session/src/mobile.ts +97 -41
  63. package/vendor/node_modules/@legendapp/spark-auth-session/src/types.ts +20 -9
  64. package/vendor/node_modules/@legendapp/spark-cli/dist/add-desktop.js +50 -7
  65. package/vendor/node_modules/@legendapp/spark-cli/dist/build-mode.js +1 -1
  66. package/vendor/node_modules/@legendapp/spark-cli/dist/build.js +145 -59
  67. package/vendor/node_modules/@legendapp/spark-cli/dist/cli-arguments.js +61 -0
  68. package/vendor/node_modules/@legendapp/spark-cli/dist/commands.js +10 -4
  69. package/vendor/node_modules/@legendapp/spark-cli/dist/create.js +37 -8
  70. package/vendor/node_modules/@legendapp/spark-cli/dist/credentials.js +86 -25
  71. package/vendor/node_modules/@legendapp/spark-cli/dist/dependency-pins.js +132 -0
  72. package/vendor/node_modules/@legendapp/spark-cli/dist/dev-command.js +13 -4
  73. package/vendor/node_modules/@legendapp/spark-cli/dist/dev.js +13 -10
  74. package/vendor/node_modules/@legendapp/spark-cli/dist/errors.js +100 -0
  75. package/vendor/node_modules/@legendapp/spark-cli/dist/examples.js +2 -2
  76. package/vendor/node_modules/@legendapp/spark-cli/dist/expo-dev-patch.cjs +4 -2
  77. package/vendor/node_modules/@legendapp/spark-cli/dist/expo-metro.cjs +1 -1
  78. package/vendor/node_modules/@legendapp/spark-cli/dist/expo-native.cjs +5 -1
  79. package/vendor/node_modules/@legendapp/spark-cli/dist/expo-node.js +3 -3
  80. package/vendor/node_modules/@legendapp/spark-cli/dist/helpers.js +1 -1
  81. package/vendor/node_modules/@legendapp/spark-cli/dist/index.js +52 -44
  82. package/vendor/node_modules/@legendapp/spark-cli/dist/json.js +16 -0
  83. package/vendor/node_modules/@legendapp/spark-cli/dist/local.js +1 -1
  84. package/vendor/node_modules/@legendapp/spark-cli/dist/metro.cjs +26 -5
  85. package/vendor/node_modules/@legendapp/spark-cli/dist/native-metadata.js +26 -0
  86. package/vendor/node_modules/@legendapp/spark-cli/dist/native-patch-preflight.js +86 -0
  87. package/vendor/node_modules/@legendapp/spark-cli/dist/native-preparation.js +88 -0
  88. package/vendor/node_modules/@legendapp/spark-cli/dist/notary-profiles.js +75 -0
  89. package/vendor/node_modules/@legendapp/spark-cli/dist/package.js +4 -2
  90. package/vendor/node_modules/@legendapp/spark-cli/dist/platform.js +16 -2
  91. package/vendor/node_modules/@legendapp/spark-cli/dist/process.js +7 -0
  92. package/vendor/node_modules/@legendapp/spark-cli/dist/project.js +90 -32
  93. package/vendor/node_modules/@legendapp/spark-cli/dist/release.js +86 -5
  94. package/vendor/node_modules/@legendapp/spark-cli/dist/release.json +23 -18
  95. package/vendor/node_modules/@legendapp/spark-cli/dist/runner-install.js +145 -31
  96. package/vendor/node_modules/@legendapp/spark-cli/dist/signing.js +4 -3
  97. package/vendor/node_modules/@legendapp/spark-cli/dist/universal.cjs +2 -1
  98. package/vendor/node_modules/@legendapp/spark-cli/dist/updates.js +41 -4
  99. package/vendor/node_modules/@legendapp/spark-cli/dist/windows.js +5 -9
  100. package/vendor/node_modules/@legendapp/spark-cli/package.json +7 -6
  101. package/vendor/node_modules/@legendapp/spark-cli/templates/blank-typescript/App.tsx +6 -12
  102. package/vendor/node_modules/@legendapp/spark-cli/templates/blank-typescript/desktop.config.json +5 -3
  103. package/vendor/node_modules/@legendapp/spark-cli/templates/blank-typescript/package.json +12 -10
  104. package/vendor/node_modules/@legendapp/spark-cli/templates/diff-lite/Git.macos.tsx +2 -2
  105. package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/Integration.macos.tsx +23 -21
  106. package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/Integration.windows.tsx +23 -21
  107. package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/io.desktop.ts +11 -8
  108. package/vendor/node_modules/@legendapp/spark-cli/templates/document-editor/io.macos.ts +3 -3
  109. package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/Lifecycle.desktop.tsx +8 -14
  110. package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/OpenFiles.desktop.tsx +5 -10
  111. package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/io.desktop.ts +11 -8
  112. package/vendor/node_modules/@legendapp/spark-cli/templates/example-shared/io.macos.ts +3 -3
  113. package/vendor/node_modules/@legendapp/spark-cli/templates/music-lite/assets.desktop.ts +2 -2
  114. package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/Activation.desktop.tsx +2 -2
  115. package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/Desktop.desktop.ts +37 -15
  116. package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/session.ts +5 -6
  117. package/vendor/node_modules/@legendapp/spark-cli/templates/notes-lite/window-session.ts +5 -4
  118. package/vendor/node_modules/@legendapp/spark-cli/templates/universal/desktop.config.json +5 -3
  119. package/vendor/node_modules/@legendapp/spark-cli/templates/universal/metro.config.js +1 -1
  120. package/vendor/node_modules/@legendapp/spark-cli/templates/universal/package.json +14 -12
  121. package/vendor/node_modules/@legendapp/spark-cli/templates/universal/react-native.config.js +1 -1
  122. package/vendor/node_modules/@legendapp/spark-cli/templates/windows/package.json +14 -12
  123. package/vendor/node_modules/@legendapp/spark-cli/templates/windows/react-native.config.js +1 -1
  124. package/vendor/node_modules/@legendapp/spark-clipboard/macos/RNDesktopClipboard.mm +10 -16
  125. package/vendor/node_modules/@legendapp/spark-clipboard/package.json +2 -2
  126. package/vendor/node_modules/@legendapp/spark-clipboard/src/NativeDesktopClipboard.ts +1 -1
  127. package/vendor/node_modules/@legendapp/spark-clipboard/src/desktop.ts +55 -31
  128. package/vendor/node_modules/@legendapp/spark-clipboard/src/expo.ts +21 -11
  129. package/vendor/node_modules/@legendapp/spark-clipboard/src/options.ts +8 -0
  130. package/vendor/node_modules/@legendapp/spark-clipboard/src/types.ts +10 -0
  131. package/vendor/node_modules/@legendapp/spark-clipboard/windows/SparkClipboard/SparkClipboard.h +38 -15
  132. package/vendor/node_modules/@legendapp/spark-clipboard/windows/SparkClipboard/SparkClipboard.vcxproj +2 -0
  133. package/vendor/node_modules/@legendapp/spark-codex/RNCodex.podspec +22 -0
  134. package/vendor/node_modules/@legendapp/spark-codex/cpp/HybridCodexAppServer.hpp +23 -0
  135. package/vendor/node_modules/@legendapp/spark-codex/cpp/HybridCodexAppServer.mm +1529 -0
  136. package/vendor/node_modules/@legendapp/spark-codex/nitro.json +21 -0
  137. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/.gitattributes +1 -0
  138. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex+autolinking.rb +62 -0
  139. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex-Swift-Cxx-Bridge.cpp +17 -0
  140. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex-Swift-Cxx-Bridge.hpp +27 -0
  141. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodex-Swift-Cxx-Umbrella.hpp +38 -0
  142. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodexAutolinking.mm +35 -0
  143. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/ios/RNCodexAutolinking.swift +16 -0
  144. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/CodexAvailability.hpp +95 -0
  145. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/CodexRunResult.hpp +99 -0
  146. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/HybridCodexAppServerSpec.cpp +24 -0
  147. package/vendor/node_modules/@legendapp/spark-codex/nitrogen/generated/shared/c++/HybridCodexAppServerSpec.hpp +71 -0
  148. package/vendor/node_modules/@legendapp/spark-codex/package.json +35 -0
  149. package/vendor/node_modules/@legendapp/spark-codex/react-native.config.js +11 -0
  150. package/vendor/node_modules/@legendapp/spark-codex/src/Codex.nitro.ts +30 -0
  151. package/vendor/node_modules/@legendapp/spark-codex/src/index.ts +65 -0
  152. package/vendor/node_modules/@legendapp/spark-codex/src/native.ts +6 -0
  153. package/vendor/node_modules/@legendapp/spark-commands/package.json +21 -0
  154. package/vendor/node_modules/@legendapp/spark-commands/src/bindings.ts +90 -0
  155. package/vendor/node_modules/@legendapp/spark-commands/src/index.tsx +400 -0
  156. package/vendor/node_modules/@legendapp/spark-commands/src/router.ts +138 -0
  157. package/vendor/node_modules/@legendapp/spark-commands/src/storage.ts +20 -0
  158. package/vendor/node_modules/@legendapp/spark-context-menu/macos/RNContextMenu.mm +7 -8
  159. package/vendor/node_modules/@legendapp/spark-context-menu/package.json +4 -3
  160. package/vendor/node_modules/@legendapp/spark-context-menu/src/NativeContextMenu.ts +1 -1
  161. package/vendor/node_modules/@legendapp/spark-context-menu/src/index.ts +26 -26
  162. package/vendor/node_modules/@legendapp/spark-context-menu/windows/SparkContextMenu/SparkContextMenu.h +3 -2
  163. package/vendor/node_modules/@legendapp/spark-desktop-app/RNDesktopApp.podspec +2 -1
  164. package/vendor/node_modules/@legendapp/spark-desktop-app/cpp/SparkBinaryJSI.hpp +89 -0
  165. package/vendor/node_modules/@legendapp/spark-desktop-app/cpp/SparkBinaryWindows.hpp +22 -0
  166. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/RNDesktopApp.mm +13 -7
  167. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkBinaryJSI.h +48 -0
  168. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkDesktop.h +4 -0
  169. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkDesktop.mm +33 -33
  170. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkDesktopError.h +20 -0
  171. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkLifecycle.h +26 -0
  172. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkLifecycle.mm +49 -0
  173. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkQuitCoordinator.h +9 -0
  174. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkQuitCoordinator.mm +70 -0
  175. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/SparkWindow.mm +9 -6
  176. package/vendor/node_modules/@legendapp/spark-desktop-app/macos/recent-documents/RNRecentDocumentController.mm +55 -0
  177. package/vendor/node_modules/@legendapp/spark-desktop-app/package.json +5 -3
  178. package/vendor/node_modules/@legendapp/spark-desktop-app/src/NativeDesktopApp.ts +1 -1
  179. package/vendor/node_modules/@legendapp/spark-desktop-app/src/api.ts +86 -40
  180. package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/accelerator.ts +40 -0
  181. package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/index.ts +56 -0
  182. package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/menu.ts +116 -0
  183. package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/native-buffer.ts +22 -0
  184. package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/path.ts +24 -0
  185. package/vendor/node_modules/@legendapp/spark-desktop-app/src/events.ts +75 -0
  186. package/vendor/node_modules/@legendapp/spark-desktop-app/src/index.ts +2 -0
  187. package/vendor/node_modules/@legendapp/spark-desktop-app/src/index.windows.ts +2 -0
  188. package/vendor/node_modules/@legendapp/spark-desktop-app/src/open-requests.ts +43 -0
  189. package/vendor/node_modules/@legendapp/spark-desktop-app/src/restoration.ts +11 -0
  190. package/vendor/node_modules/@legendapp/spark-desktop-app/src/transport.ts +10 -0
  191. package/vendor/node_modules/@legendapp/spark-desktop-config/app.plugin.cjs +10 -5
  192. package/vendor/node_modules/@legendapp/spark-desktop-config/app.plugin.d.cts +3 -0
  193. package/vendor/node_modules/@legendapp/spark-desktop-config/autolinking.cjs +35 -0
  194. package/vendor/node_modules/@legendapp/spark-desktop-config/config.cjs +18 -3
  195. package/vendor/node_modules/@legendapp/spark-desktop-config/config.d.cts +89 -9
  196. package/vendor/node_modules/@legendapp/spark-desktop-config/expo.cjs +1 -0
  197. package/vendor/node_modules/@legendapp/spark-desktop-config/expo.d.cts +3 -0
  198. package/vendor/node_modules/@legendapp/spark-desktop-config/identity.cjs +3 -2
  199. package/vendor/node_modules/@legendapp/spark-desktop-config/lifecycle.cjs +20 -0
  200. package/vendor/node_modules/@legendapp/spark-desktop-config/native-patches.cjs +106 -0
  201. package/vendor/node_modules/@legendapp/spark-desktop-config/native-patches.d.cts +1 -0
  202. package/vendor/node_modules/@legendapp/spark-desktop-config/package.json +7 -5
  203. package/vendor/node_modules/@legendapp/spark-desktop-config/schema.json +276 -58
  204. package/vendor/node_modules/@legendapp/spark-desktop-config/updates.cjs +1 -0
  205. package/vendor/node_modules/@legendapp/spark-desktop-config/validation.cjs +35 -0
  206. package/vendor/node_modules/@legendapp/spark-desktop-config/windows.plugin.cjs +2 -1
  207. package/vendor/node_modules/@legendapp/spark-desktop-host/AppDelegate.mm +129 -11
  208. package/vendor/node_modules/@legendapp/spark-desktop-host/package.json +2 -2
  209. package/vendor/node_modules/@legendapp/spark-desktop-host/windows/application.inc +277 -121
  210. package/vendor/node_modules/@legendapp/spark-desktop-links/common/AuthLoopback.h +5 -7
  211. package/vendor/node_modules/@legendapp/spark-desktop-links/macos/RNDesktopLinks.mm +10 -9
  212. package/vendor/node_modules/@legendapp/spark-desktop-links/package.json +2 -2
  213. package/vendor/node_modules/@legendapp/spark-desktop-links/src/NativeDesktopLinks.ts +1 -1
  214. package/vendor/node_modules/@legendapp/spark-desktop-links/src/api.ts +27 -33
  215. package/vendor/node_modules/@legendapp/spark-desktop-links/src/expo.ts +3 -4
  216. package/vendor/node_modules/@legendapp/spark-desktop-links/src/native.ts +9 -0
  217. package/vendor/node_modules/@legendapp/spark-desktop-links/windows/SparkLinks/SparkLinks.h +22 -22
  218. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/RNDesktopShortcuts.podspec +1 -0
  219. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/macos/RNDesktopShortcuts.mm +1 -1
  220. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/macos/keyboard-manager/RNKeyboardManager.h +9 -0
  221. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/macos/keyboard-manager/RNKeyboardManager.mm +307 -0
  222. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/package.json +8 -5
  223. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/NativeDesktopShortcuts.ts +1 -1
  224. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/api.ts +39 -14
  225. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/keyboard-manager/NativeKeyboardManager.ts +12 -0
  226. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/keyboard-manager/codes.ts +108 -0
  227. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/keyboard-manager/index.ts +99 -0
  228. package/vendor/node_modules/@legendapp/spark-desktop-windows/RNDesktopWindows.podspec +2 -0
  229. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/RNDesktopWindows.mm +229 -103
  230. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/SparkWindowGeometry.h +63 -0
  231. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/LegendWindowRegistry.h +20 -0
  232. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/LegendWindowRegistry.mm +27 -0
  233. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/RNWindowManager.h +12 -0
  234. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/RNWindowManager.mm +3169 -0
  235. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/RNWindowManagerStartup.mm +31 -0
  236. package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/SidebarStartupFallbacks.mm +14 -0
  237. package/vendor/node_modules/@legendapp/spark-desktop-windows/package.json +10 -6
  238. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/NativeDesktopWindowManager.ts +1 -1
  239. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/api.ts +146 -70
  240. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/diagnostics.ts +17 -0
  241. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/index.ts +3 -1
  242. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/index.windows.ts +3 -1
  243. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/macos-adapter.ts +53 -0
  244. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/macos.ts +51 -0
  245. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/transport.ts +41 -0
  246. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/types.ts +140 -0
  247. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/validation.ts +162 -0
  248. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/window-manager/NativeWindowManager.ts +28 -0
  249. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/WindowProvider.tsx +20 -0
  250. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/createWindowsNavigator.tsx +78 -0
  251. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/index.ts +6 -0
  252. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/primaryWindowLifecycle.ts +34 -0
  253. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/types.ts +11 -0
  254. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/usePrimaryWindowLifecycle.ts +18 -0
  255. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows/useWindowFocusEffect.ts +17 -0
  256. package/vendor/node_modules/@legendapp/spark-documents/package.json +22 -0
  257. package/vendor/node_modules/@legendapp/spark-documents/src/controller.ts +97 -0
  258. package/vendor/node_modules/@legendapp/spark-documents/src/documentTransition.ts +13 -0
  259. package/vendor/node_modules/@legendapp/spark-documents/src/hooks.ts +139 -0
  260. package/vendor/node_modules/@legendapp/spark-documents/src/index.tsx +50 -0
  261. package/vendor/node_modules/@legendapp/spark-documents/src/reload.ts +39 -0
  262. package/vendor/node_modules/@legendapp/spark-documents/src/requests.ts +15 -0
  263. package/vendor/node_modules/@legendapp/spark-drag-drop/package.json +4 -3
  264. package/vendor/node_modules/@legendapp/spark-drag-drop/src/DesktopDragViewNativeComponent.ts +1 -1
  265. package/vendor/node_modules/@legendapp/spark-drag-drop/src/contracts.ts +73 -18
  266. package/vendor/node_modules/@legendapp/spark-drag-drop/src/index.tsx +58 -15
  267. package/vendor/node_modules/@legendapp/spark-drag-drop/windows/SparkDragDrop/SparkDragDrop.h +14 -8
  268. package/vendor/node_modules/@legendapp/spark-file-dialog/ios/RNFileDialog.mm +34 -121
  269. package/vendor/node_modules/@legendapp/spark-file-dialog/package.json +12 -5
  270. package/vendor/node_modules/@legendapp/spark-file-dialog/src/NativeFileDialog.ts +1 -5
  271. package/vendor/node_modules/@legendapp/spark-file-dialog/src/index.ts +19 -84
  272. package/vendor/node_modules/@legendapp/spark-file-dialog/src/native.ts +12 -0
  273. package/vendor/node_modules/@legendapp/spark-file-dialog/src/options.ts +34 -14
  274. package/vendor/node_modules/@legendapp/spark-file-dialog/src/types.ts +26 -0
  275. package/vendor/node_modules/@legendapp/spark-file-dialog/windows/SparkFileDialog/SparkFileDialog.h +42 -58
  276. package/vendor/node_modules/@legendapp/spark-file-system/RNDesktopFileSystem.podspec +1 -0
  277. package/vendor/node_modules/@legendapp/spark-file-system/macos/RNDesktopFileSystem.mm +32 -21
  278. package/vendor/node_modules/@legendapp/spark-file-system/macos/SparkFileMutations.h +24 -0
  279. package/vendor/node_modules/@legendapp/spark-file-system/macos/SparkFileMutations.mm +215 -0
  280. package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScanner.h +9 -0
  281. package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScanner.mm +82 -0
  282. package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScannerCore.h +47 -0
  283. package/vendor/node_modules/@legendapp/spark-file-system/macos/file-scanner/RNFileScannerCore.mm +306 -0
  284. package/vendor/node_modules/@legendapp/spark-file-system/package.json +8 -6
  285. package/vendor/node_modules/@legendapp/spark-file-system/src/NativeDesktopFileSystem.ts +1 -1
  286. package/vendor/node_modules/@legendapp/spark-file-system/src/file-scanner/NativeFileScanner.ts +11 -0
  287. package/vendor/node_modules/@legendapp/spark-file-system/src/file-scanner/index.ts +93 -0
  288. package/vendor/node_modules/@legendapp/spark-file-system/src/handles.ts +48 -14
  289. package/vendor/node_modules/@legendapp/spark-file-system/src/index.ts +134 -45
  290. package/vendor/node_modules/@legendapp/spark-file-system/src/path.ts +1 -9
  291. package/vendor/node_modules/@legendapp/spark-file-system/windows/SparkFileSystem/SparkFileSystem.h +102 -26
  292. package/vendor/node_modules/@legendapp/spark-file-system/windows/SparkFileSystem/SparkFileSystem.vcxproj +2 -0
  293. package/vendor/node_modules/@legendapp/spark-global-shortcuts/RNDesktopGlobalShortcuts.podspec +1 -0
  294. package/vendor/node_modules/@legendapp/spark-global-shortcuts/macos/RNDesktopGlobalShortcuts.mm +7 -4
  295. package/vendor/node_modules/@legendapp/spark-global-shortcuts/package.json +4 -3
  296. package/vendor/node_modules/@legendapp/spark-global-shortcuts/src/NativeDesktopGlobalShortcuts.ts +1 -1
  297. package/vendor/node_modules/@legendapp/spark-global-shortcuts/src/index.ts +39 -10
  298. package/vendor/node_modules/@legendapp/spark-global-shortcuts/windows/SparkGlobalShortcuts/SparkGlobalShortcuts.h +5 -4
  299. package/vendor/node_modules/@legendapp/spark-message-dialog/macos/RNDesktopMessageDialog.mm +2 -2
  300. package/vendor/node_modules/@legendapp/spark-message-dialog/package.json +2 -2
  301. package/vendor/node_modules/@legendapp/spark-message-dialog/src/NativeDesktopMessageDialog.ts +1 -1
  302. package/vendor/node_modules/@legendapp/spark-message-dialog/src/index.ts +48 -11
  303. package/vendor/node_modules/@legendapp/spark-native-menu/ios/RNNativeMenu.mm +141 -609
  304. package/vendor/node_modules/@legendapp/spark-native-menu/package.json +9 -4
  305. package/vendor/node_modules/@legendapp/spark-native-menu/src/NativeMenu.ts +2 -7
  306. package/vendor/node_modules/@legendapp/spark-native-menu/src/api.ts +76 -121
  307. package/vendor/node_modules/@legendapp/spark-native-menu/src/hooks.ts +97 -0
  308. package/vendor/node_modules/@legendapp/spark-native-menu/src/windows-menus.ts +43 -37
  309. package/vendor/node_modules/@legendapp/spark-notifications/macos/RNDesktopNotifications.mm +75 -30
  310. package/vendor/node_modules/@legendapp/spark-notifications/package.json +2 -2
  311. package/vendor/node_modules/@legendapp/spark-notifications/src/NativeDesktopNotifications.ts +1 -1
  312. package/vendor/node_modules/@legendapp/spark-notifications/src/index.ts +130 -42
  313. package/vendor/node_modules/@legendapp/spark-notifications/windows/SparkNotifications/SparkNotifications.h +51 -8
  314. package/vendor/node_modules/@legendapp/spark-processes/macos/RNDesktopProcesses.mm +94 -21
  315. package/vendor/node_modules/@legendapp/spark-processes/package.json +4 -3
  316. package/vendor/node_modules/@legendapp/spark-processes/src/NativeDesktopProcesses.ts +1 -1
  317. package/vendor/node_modules/@legendapp/spark-processes/src/index.ts +124 -28
  318. package/vendor/node_modules/@legendapp/spark-processes/src/types.ts +34 -0
  319. package/vendor/node_modules/@legendapp/spark-processes/windows/SparkProcesses/SparkProcesses.h +83 -34
  320. package/vendor/node_modules/@legendapp/spark-processes/windows/SparkProcesses/SparkProcesses.vcxproj +2 -0
  321. package/vendor/node_modules/@legendapp/spark-secure-storage/macos/RNDesktopSecureStorage.mm +49 -40
  322. package/vendor/node_modules/@legendapp/spark-secure-storage/package.json +2 -2
  323. package/vendor/node_modules/@legendapp/spark-secure-storage/src/NativeDesktopSecureStorage.ts +1 -1
  324. package/vendor/node_modules/@legendapp/spark-secure-storage/src/desktop.ts +16 -14
  325. package/vendor/node_modules/@legendapp/spark-secure-storage/src/expo.ts +0 -1
  326. package/vendor/node_modules/@legendapp/spark-secure-storage/src/options.ts +3 -2
  327. package/vendor/node_modules/@legendapp/spark-secure-storage/src/unavailable.ts +6 -3
  328. package/vendor/node_modules/@legendapp/spark-settings/package.json +10 -3
  329. package/vendor/node_modules/@legendapp/spark-settings/src/fileStorage.ts +11 -0
  330. package/vendor/node_modules/@legendapp/spark-settings/src/index.ts +7 -9
  331. package/vendor/node_modules/@legendapp/spark-settings/src/storage/index.ts +142 -0
  332. package/vendor/node_modules/@legendapp/spark-settings/src/store.ts +63 -21
  333. package/vendor/node_modules/@legendapp/spark-settings-ui/package.json +20 -0
  334. package/vendor/node_modules/@legendapp/spark-settings-ui/src/index.tsx +436 -0
  335. package/vendor/node_modules/@legendapp/spark-settings-ui/src/options.ts +10 -0
  336. package/vendor/node_modules/@legendapp/spark-sqlite/package.json +3 -3
  337. package/vendor/node_modules/@legendapp/spark-sqlite/src/database.ts +182 -0
  338. package/vendor/node_modules/@legendapp/spark-sqlite/src/index.ts +19 -6
  339. package/vendor/node_modules/@legendapp/spark-sqlite/src/types.ts +22 -0
  340. package/vendor/node_modules/@legendapp/spark-system/macos/RNDesktopSystem.mm +36 -11
  341. package/vendor/node_modules/@legendapp/spark-system/package.json +2 -2
  342. package/vendor/node_modules/@legendapp/spark-system/src/NativeDesktopSystem.ts +1 -1
  343. package/vendor/node_modules/@legendapp/spark-system/src/index.ts +89 -32
  344. package/vendor/node_modules/@legendapp/spark-system/windows/SparkSystem/SparkSystem.h +8 -4
  345. package/vendor/node_modules/@legendapp/spark-tray/macos/RNDesktopTray.mm +20 -8
  346. package/vendor/node_modules/@legendapp/spark-tray/package.json +2 -2
  347. package/vendor/node_modules/@legendapp/spark-tray/src/NativeDesktopTray.ts +1 -1
  348. package/vendor/node_modules/@legendapp/spark-tray/src/index.ts +91 -34
  349. package/vendor/node_modules/@legendapp/spark-tray/windows/SparkTray/SparkTray.h +45 -15
  350. package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkButton.mm +2 -1
  351. package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkTextInput.mm +10 -2
  352. package/vendor/node_modules/@legendapp/spark-ui/macos/appkit-split-view/RNSidebarSplitViewComponent.h +15 -0
  353. package/vendor/node_modules/@legendapp/spark-ui/macos/appkit-split-view/RNSidebarSplitViewComponent.mm +1419 -0
  354. package/vendor/node_modules/@legendapp/spark-ui/macos/glass-effect-view/RNGlassEffectView.mm +72 -0
  355. package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSegmentedControl.h +15 -0
  356. package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSegmentedControl.mm +194 -0
  357. package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSelect.h +15 -0
  358. package/vendor/node_modules/@legendapp/spark-ui/macos/native-select/RNNativeSelect.mm +180 -0
  359. package/vendor/node_modules/@legendapp/spark-ui/macos/sf-symbol/RNSFSymbol.h +15 -0
  360. package/vendor/node_modules/@legendapp/spark-ui/macos/sf-symbol/RNSFSymbol.mm +155 -0
  361. package/vendor/node_modules/@legendapp/spark-ui/macos/sidebar/RNSidebar.h +18 -0
  362. package/vendor/node_modules/@legendapp/spark-ui/macos/sidebar/RNSidebar.mm +645 -0
  363. package/vendor/node_modules/@legendapp/spark-ui/macos/swipe-actions/RNSwipeActions.h +10 -0
  364. package/vendor/node_modules/@legendapp/spark-ui/macos/swipe-actions/RNSwipeActions.mm +422 -0
  365. package/vendor/node_modules/@legendapp/spark-ui/macos/text-input-search/RNTextInputSearch.h +15 -0
  366. package/vendor/node_modules/@legendapp/spark-ui/macos/text-input-search/RNTextInputSearch.mm +164 -0
  367. package/vendor/node_modules/@legendapp/spark-ui/package.json +17 -4
  368. package/vendor/node_modules/@legendapp/spark-ui/src/SparkSelectNativeComponent.ts +3 -1
  369. package/vendor/node_modules/@legendapp/spark-ui/src/SparkTextInputNativeComponent.ts +6 -2
  370. package/vendor/node_modules/@legendapp/spark-ui/src/appkit-split-view/SidebarSplitViewNativeComponent.ts +37 -0
  371. package/vendor/node_modules/@legendapp/spark-ui/src/appkit-split-view/index.tsx +86 -0
  372. package/vendor/node_modules/@legendapp/spark-ui/src/availability.ts +10 -0
  373. package/vendor/node_modules/@legendapp/spark-ui/src/classnames/index.ts +8 -0
  374. package/vendor/node_modules/@legendapp/spark-ui/src/control.ts +42 -0
  375. package/vendor/node_modules/@legendapp/spark-ui/src/desktop.tsx +56 -0
  376. package/vendor/node_modules/@legendapp/spark-ui/src/glass-effect-view/index.tsx +15 -0
  377. package/vendor/node_modules/@legendapp/spark-ui/src/index.android.tsx +35 -21
  378. package/vendor/node_modules/@legendapp/spark-ui/src/index.ios.tsx +55 -26
  379. package/vendor/node_modules/@legendapp/spark-ui/src/index.tsx +1 -27
  380. package/vendor/node_modules/@legendapp/spark-ui/src/index.web.tsx +45 -14
  381. package/vendor/node_modules/@legendapp/spark-ui/src/index.windows.tsx +1 -42
  382. package/vendor/node_modules/@legendapp/spark-ui/src/mobile-input.tsx +11 -0
  383. package/vendor/node_modules/@legendapp/spark-ui/src/native-select/NativeSegmentedControlNativeComponent.ts +17 -0
  384. package/vendor/node_modules/@legendapp/spark-ui/src/native-select/NativeSelectNativeComponent.ts +17 -0
  385. package/vendor/node_modules/@legendapp/spark-ui/src/select.ts +5 -3
  386. package/vendor/node_modules/@legendapp/spark-ui/src/sf-symbol/SFSymbolNativeComponent.ts +13 -0
  387. package/vendor/node_modules/@legendapp/spark-ui/src/sf-symbol/index.tsx +27 -0
  388. package/vendor/node_modules/@legendapp/spark-ui/src/sidebar/SidebarItemNativeComponent.ts +25 -0
  389. package/vendor/node_modules/@legendapp/spark-ui/src/sidebar/SidebarNativeComponent.ts +24 -0
  390. package/vendor/node_modules/@legendapp/spark-ui/src/sidebar/index.tsx +104 -0
  391. package/vendor/node_modules/@legendapp/spark-ui/src/specialized.ts +22 -0
  392. package/vendor/node_modules/@legendapp/spark-ui/src/swipe-actions/SwipeActionsNativeComponent.ts +11 -0
  393. package/vendor/node_modules/@legendapp/spark-ui/src/swipe-actions/index.tsx +41 -0
  394. package/vendor/node_modules/@legendapp/spark-ui/src/text-input-search/TextInputSearchNativeComponent.ts +33 -0
  395. package/vendor/node_modules/@legendapp/spark-ui/src/text-input-search/index.tsx +34 -0
  396. package/vendor/node_modules/@legendapp/spark-ui/src/types.ts +30 -19
  397. package/vendor/node_modules/@legendapp/spark-ui/uniwind.ts +3 -1
  398. package/vendor/node_modules/@legendapp/spark-ui/uniwind.web.ts +3 -1
  399. package/vendor/node_modules/@legendapp/spark-ui/windows/SparkUI/SparkUI.h +16 -11
  400. package/vendor/node_modules/@legendapp/spark-updates/macos/RNDesktopUpdates.mm +21 -8
  401. package/vendor/node_modules/@legendapp/spark-updates/package.json +2 -2
  402. package/vendor/node_modules/@legendapp/spark-updates/src/NativeDesktopUpdates.ts +3 -2
  403. package/vendor/node_modules/@legendapp/spark-updates/src/index.ts +71 -15
  404. package/vendor/node_modules/@legendapp/spark-webview/package.json +2 -2
  405. package/vendor/node_modules/@legendapp/spark-webview/src/WebView.tsx +11 -0
  406. package/vendor/node_modules/@legendapp/spark-webview/src/adapter.ts +55 -0
  407. package/vendor/node_modules/@legendapp/spark-webview/src/index.ts +2 -2
  408. package/vendor/node_modules/@legendapp/spark-webview/src/types.ts +32 -0
  409. package/vendor/node_modules/@legendapp/spark-webview/src/view-props.ts +28 -0
  410. package/vendor/node_modules/@legendapp/spark-window-options/index.cjs +51 -14
  411. package/vendor/node_modules/@legendapp/spark-window-options/index.d.ts +21 -8
  412. package/vendor/node_modules/@legendapp/spark-window-options/package.json +1 -1
  413. package/windows-macos.ts +1 -0
  414. package/expo-metro.cjs +0 -1
  415. package/message-dialog.ts +0 -1
  416. package/universal.cjs +0 -1
  417. package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/src/accelerator.ts +0 -22
  418. package/vendor/node_modules/@legendapp/spark-desktop-windows/src/windows-options.ts +0 -11
  419. package/vendor/node_modules/@legendapp/spark-file-dialog/src/index.windows.ts +0 -15
  420. package/vendor/node_modules/@legendapp/spark-global-shortcuts/src/accelerator.ts +0 -22
  421. package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkSelect.h +0 -4
  422. package/vendor/node_modules/@legendapp/spark-ui/macos/RNSparkSelect.mm +0 -53
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@legendapp/spark-native-menu",
3
- "version": "0.0.1-next.2",
3
+ "version": "0.0.1-next.4",
4
4
  "main": "src/index",
5
5
  "react-native": "src/index",
6
6
  "codegenConfig": {
@@ -13,7 +13,8 @@
13
13
  "ios": {
14
14
  "modulesProvider": {
15
15
  "NativeMenu": "RNNativeMenu"
16
- }
16
+ },
17
+ "componentProvider": {}
17
18
  }
18
19
  },
19
20
  "peerDependencies": {
@@ -24,7 +25,8 @@
24
25
  "files": [
25
26
  "src",
26
27
  "ios",
27
- "*.podspec"
28
+ "*.podspec",
29
+ "macos"
28
30
  ],
29
31
  "spark": {
30
32
  "sdk": true,
@@ -37,5 +39,8 @@
37
39
  ],
38
40
  "requires": []
39
41
  },
40
- "private": true
42
+ "private": true,
43
+ "dependencies": {
44
+ "@legendapp/spark-desktop-app": "0.0.1-next.4"
45
+ }
41
46
  }
@@ -1,13 +1,8 @@
1
1
  import type { TurboModule } from "react-native";
2
2
  import { TurboModuleRegistry } from "react-native";
3
-
4
3
  export interface Spec extends TurboModule {
5
- configureMenus(ownerId: string, menusJson: string): void;
6
- updateMenuItems(ownerId: string, patchesJson: string): void;
7
- clearMenus(ownerId: string): void;
8
- clearAllMenus(): void;
4
+ publish(menusJson: string): Promise<void>;
9
5
  addListener(eventName: string): void;
10
6
  removeListeners(count: number): void;
11
7
  }
12
-
13
- export default TurboModuleRegistry.getEnforcing<Spec>("NativeMenu");
8
+ export default TurboModuleRegistry.get<Spec>("NativeMenu");
@@ -1,128 +1,83 @@
1
1
  import { NativeEventEmitter, Platform } from "react-native";
2
- import { useEffect } from "react";
3
2
  import NativeMenu from "./NativeMenu";
4
- import { composeWindowsMenus, patchWindowsMenus } from "./windows-menus";
5
-
6
- export type NativeMenuShortcut = {
7
- key: string;
8
- modifiers?: number;
9
- };
10
-
11
- export type NativeMenuItemPlacement = {
12
- before?: string;
13
- after?: string;
14
- };
15
-
16
- export const commandModifier = 1 << 20;
17
- export const optionModifier = 1 << 19;
18
- export const shiftModifier = 1 << 17;
19
- export const settingsTargetTitles = ["Settings...", "Settings…", "Preferences...", "Preferences…"];
20
- export const openTargetTitles = ["Open", "Open...", "Open…"];
21
- export const saveTargetTitles = ["Save", "Save...", "Save…"];
22
- export const saveAsTargetTitles = ["Save As", "Save As...", "Save As…"];
23
-
24
- export type NativeMenuItem = {
25
- id: string;
26
- title?: string;
27
- targetTitle?: string;
28
- targetTitles?: string[];
29
- /** Nested native system-menu paths are macOS-only. Windows accepts one item title. */
30
- targetPath?: string[];
31
- enabled?: boolean;
32
- checked?: boolean;
33
- hidden?: boolean;
34
- placement?: NativeMenuItemPlacement;
35
- separator?: boolean;
36
- shortcut?: NativeMenuShortcut | null;
37
- payload?: Record<string, unknown>;
38
- };
39
-
40
- export type NativeMenuConfig = {
41
- id: string;
42
- title: string;
43
- systemMenu?: "app";
44
- placement?: {
45
- before?: string;
46
- after?: string;
47
- };
48
- items: NativeMenuItem[];
49
- };
50
-
51
- export type NativeMenuItemPatch = Omit<Partial<NativeMenuItem>, "separator"> & {
52
- id: string;
53
- };
54
-
55
- export type NativeMenuAction = {
56
- ownerId: string;
57
- menuId: string;
58
- itemId: string;
59
- payload?: Record<string, unknown>;
60
- };
61
-
62
- export type NativeMenuActionHandlers = Record<string, (action: NativeMenuAction) => void>;
63
-
64
- export type UseNativeMenuOptions = {
65
- handlers?: NativeMenuActionHandlers;
66
- menus: NativeMenuConfig[];
67
- onAction?: (action: NativeMenuAction) => void;
68
- ownerId: string;
69
- };
70
-
71
- const windowsOwners = new Map<string, NativeMenuConfig[]>();
72
- function publishWindowsMenus(next: Map<string, NativeMenuConfig[]>) {
73
- // Composition can reject unsupported targets. Preserve the last good owners
74
- // and native menu when a proposed contribution is invalid.
75
- const composed = composeWindowsMenus(next);
76
- NativeMenu.configureMenus("spark.windows.menus", JSON.stringify(composed));
77
- windowsOwners.clear(); for (const [owner, menus] of next) windowsOwners.set(owner, menus);
78
- }
79
- export function configureMenus(ownerId: string, menus: NativeMenuConfig[]) {
80
- if (Platform.OS === "windows") { const next = new Map(windowsOwners); next.set(ownerId, JSON.parse(JSON.stringify(menus))); publishWindowsMenus(next); }
81
- else if (Platform.OS === "macos") NativeMenu.configureMenus(ownerId, JSON.stringify(menus));
3
+ import { SparkError, asyncRegistration, invokeNative, type Availability, type AsyncRegistration } from "@legendapp/spark-desktop-app/src/contracts";
4
+ import { menuItems, selectableMenuIds, type MenuItem as SharedMenuItem, type MenuItemTree, type MenuWireItem, type MenuAction as SharedMenuAction } from "@legendapp/spark-desktop-app/src/contracts/menu";
5
+ import { composeWindowsMenus } from "./windows-menus";
6
+ type NativeMenuLeaf = Extract<SharedMenuItem, { type: "action" | "checkbox" | "separator" | "role" }>;
7
+ export type MenuItem = MenuItemTree<NativeMenuLeaf>;
8
+ export type MenuAction = Extract<SharedMenuAction, { type: "action" }>;
9
+ export type { MenuIcon, MenuRole, MenuTarget, MenuPlacement, MenuLocation } from "@legendapp/spark-desktop-app/src/contracts/menu";
10
+ export type { AsyncRegistration } from "@legendapp/spark-desktop-app/src/contracts";
11
+ export type MenuRootItem = Extract<MenuItem, { type: "submenu" }>;
12
+ export interface MenuUpdate { items: readonly MenuRootItem[] }
13
+ export interface MenuOptions extends MenuUpdate { id: string; onAction?: (action: MenuAction) => void }
14
+ export interface Menu extends AsyncRegistration { readonly id: string; update(options: MenuUpdate): Promise<void> }
15
+ interface Owner { id: string; token: string; items: MenuWireItem[] }
16
+ let owners = new Map<string, Owner>();
17
+ let sequence = 0, queue: Promise<unknown> = Promise.resolve();
18
+ function serial<T>(operation: () => Promise<T>): Promise<T> { const result = queue.then(operation); queue = result.catch(() => {}); return result; }
19
+ export function getMenuAvailability(): Availability {
20
+ if (Platform.OS !== "macos" && Platform.OS !== "windows") return { available: false, reason: "unsupported-platform" };
21
+ return NativeMenu ? { available: true } : { available: false, reason: "missing-module" };
82
22
  }
83
- export function updateMenuItems(ownerId: string, patches: NativeMenuItemPatch[]) {
84
- if (Platform.OS === "windows") {
85
- const menus = windowsOwners.get(ownerId); if (menus) { const next = new Map(windowsOwners); next.set(ownerId, patchWindowsMenus(menus, patches)); publishWindowsMenus(next); }
86
- } else if (Platform.OS === "macos") NativeMenu.updateMenuItems(ownerId, JSON.stringify(patches));
23
+ function available() {
24
+ const availability = getMenuAvailability();
25
+ if (!availability.available) throw new SparkError(availability.reason === "unsupported-platform" ? "E_UNSUPPORTED_PLATFORM" : "E_MODULE_UNAVAILABLE", "Application menus are unavailable");
87
26
  }
88
- export function clearMenus(ownerId: string) {
89
- if (Platform.OS === "windows") { const next = new Map(windowsOwners); next.delete(ownerId); publishWindowsMenus(next); }
90
- else if (Platform.OS === "macos") NativeMenu.clearMenus(ownerId);
27
+ function snapshot(value: MenuUpdate, token: string, creating = false): MenuWireItem[] {
28
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new SparkError("E_INVALID_ARGUMENT", "Expected menu options");
29
+ for (const key of Object.keys(value)) if (!["items", ...(creating ? ["id", "onAction"] : [])].includes(key)) throw new SparkError("E_UNSUPPORTED_OPTION", `Unsupported menu option: ${key}`);
30
+ const items = menuItems(value.items, { types: Platform.OS === "macos" ? ["action", "checkbox", "separator", "submenu", "role"] : ["action", "checkbox", "separator", "submenu"], shortcuts: true, targeting: true, icons: Platform.OS === "macos" ? ["symbol", "image"] : [] }, Platform.OS === "windows" ? "windows" : "macos");
31
+ if (items.some(item => !item.items)) throw new SparkError("E_INVALID_ARGUMENT", "Application menu roots must be submenus");
32
+ function annotate(items: MenuWireItem[]) { for (const item of items) { item._sparkOwner = token; if (item.items) annotate(item.items); } }
33
+ annotate(items); return items;
91
34
  }
92
- export function clearAllMenus() {
93
- if (Platform.OS === "windows") { publishWindowsMenus(new Map()); }
94
- else if (Platform.OS === "macos") NativeMenu.clearAllMenus();
35
+ async function publish(next: Map<string, Owner>): Promise<void> {
36
+ const menus = Platform.OS === "windows"
37
+ ? composeWindowsMenus(new Map([...next].map(([id, owner]) => [id, owner.items])))
38
+ : [...next.values()].flatMap(owner => owner.items);
39
+ const result = await invokeNative(() => NativeMenu!.publish(JSON.stringify(menus)));
40
+ if (result !== undefined && result !== null) throw new SparkError("E_INVALID_DATA", "Native menu publication returned an unexpected result");
41
+ owners = next;
95
42
  }
96
-
97
- export function addNativeMenuActionListener(listener: (action: NativeMenuAction) => void) {
98
- if ((Platform.OS !== "macos" && Platform.OS !== "windows")) {
99
- return { remove() {} };
100
- }
101
-
102
- const emitter = new NativeEventEmitter(NativeMenu as never);
103
- return emitter.addListener("NativeMenuAction", listener);
104
- }
105
-
106
- export function useNativeMenu({ handlers, menus, onAction, ownerId }: UseNativeMenuOptions) {
107
- useEffect(() => {
108
- configureMenus(ownerId, menus);
109
-
110
- const subscription = addNativeMenuActionListener((action) => {
111
- if (action.ownerId === ownerId) {
112
- const handler = handlers?.[action.itemId];
113
- if (handler) {
114
- handler(action);
115
- } else {
116
- onAction?.(action);
117
- }
118
- }
119
- });
120
-
121
- return () => {
122
- subscription.remove();
123
- clearMenus(ownerId);
124
- };
125
- }, [handlers, menus, onAction, ownerId]);
43
+ export async function createMenu(options: MenuOptions): Promise<Menu> {
44
+ available();
45
+ const token = `menu-${Date.now()}-${++sequence}`;
46
+ const items = snapshot(options, token, true);
47
+ if (typeof options.id !== "string" || !/^[a-zA-Z0-9_-]{1,100}$/.test(options.id)) throw new SparkError("E_INVALID_ARGUMENT", "Expected a menu owner id");
48
+ if (options.onAction !== undefined && typeof options.onAction !== "function") throw new SparkError("E_INVALID_ARGUMENT", "Expected onAction callback");
49
+ const id = options.id, onAction = options.onAction;
50
+ let stopped = false, ready = false, ids = selectableMenuIds(items);
51
+ const subscription = new NativeEventEmitter(NativeMenu as never).addListener("NativeMenuAction", event => {
52
+ if (ready && !stopped && event && event.ownerId === token && typeof event.itemId === "string" && ids.has(event.itemId)) {
53
+ // All owners share this emitter; one owner's throw must not swallow the other owners' actions.
54
+ try { onAction?.({ type: "action", itemId: event.itemId }); }
55
+ catch (cause) { console.error(new SparkError("E_NATIVE", "Menu action handler failed", { cause })); }
56
+ }
57
+ });
58
+ try { await serial(async () => {
59
+ if (owners.has(id)) throw new SparkError("E_ALREADY_EXISTS", `Menu owner ${id} already exists`);
60
+ const next = new Map(owners); next.set(id, { id, token, items }); await publish(next); ready = true;
61
+ }); } catch (error) { subscription.remove(); throw error; }
62
+ const registration = asyncRegistration(() => { stopped = true; subscription.remove(); }, () => serial(async () => {
63
+ if (owners.get(id)?.token !== token) return;
64
+ const next = new Map(owners); next.delete(id); await publish(next);
65
+ }));
66
+ return {
67
+ id,
68
+ async update(options) {
69
+ if (stopped) throw new SparkError("E_CLOSED", "Menu was removed");
70
+ const items = snapshot(options, token);
71
+ await serial(async () => {
72
+ // Callers commonly rebuild identical item literals each render; republishing the whole
73
+ // bar would rebuild native menus for no visible change. Compare the wire trees first.
74
+ const existing = owners.get(id);
75
+ if (existing?.token === token && JSON.stringify(existing.items) === JSON.stringify(items)) return;
76
+ const next = new Map(owners); next.delete(id); next.set(id, { id, token, items });
77
+ await publish(next); ids = selectableMenuIds(items);
78
+ });
79
+ },
80
+ remove: registration.remove,
81
+ };
126
82
  }
127
-
128
- export { default as NativeMenu } from "./NativeMenu";
83
+ export { useMenu, type MenuState, type UseMenuOptions } from "./hooks";
@@ -0,0 +1,97 @@
1
+ import { useEffect, useLayoutEffect, useRef, useState } from "react";
2
+ import { createMenu, type Menu, type MenuOptions, type MenuAction } from "./api";
3
+ export interface UseMenuOptions extends MenuOptions { onError?: (error: unknown) => void; onCleanupError?: (error: unknown, menu: Menu) => void }
4
+ export type MenuState = { status: "loading" } | { status: "ready"; menu: Menu } | { status: "error"; error: unknown };
5
+ interface Lifetime { menu?: Menu; removing?: Promise<void>; blocker?: Lifetime; items: MenuOptions["items"]; disposed: boolean; completion: Promise<void> }
6
+ const lifetimes = new Map<string, Lifetime>();
7
+ /** Callback changes do not recreate native menus. Structural updates use the same handle. */
8
+ export function useMenu(options: UseMenuOptions): MenuState {
9
+ const [state, setState] = useState<MenuState>({ status: "loading" });
10
+ const callbacks = useRef(options);
11
+ useLayoutEffect(() => { callbacks.current = options; });
12
+ const current = useRef<Lifetime | undefined>(undefined);
13
+ useEffect(() => {
14
+ const previous = lifetimes.get(options.id);
15
+ let end!: () => void;
16
+ const ended = new Promise<void>(resolve => { end = resolve; });
17
+ const owner: Lifetime = { items: options.items, disposed: false, completion: Promise.resolve(), blocker: previous?.disposed ? previous.blocker ?? previous : undefined };
18
+ current.current = owner; setState({ status: "loading" });
19
+ const report = (error: unknown) => {
20
+ if (!owner.disposed) setState({ status: "error", error });
21
+ try {
22
+ if (callbacks.current.onError) callbacks.current.onError(error);
23
+ else console.error("Menu lifecycle failed", error);
24
+ } catch (callbackError) { console.error(callbackError); }
25
+ };
26
+ const remove = (target: Lifetime) => {
27
+ const menu = target.menu;
28
+ if (!menu) return Promise.resolve();
29
+ if (!target.removing) target.removing = menu.remove().then(() => {
30
+ if (target.menu === menu) target.menu = undefined;
31
+ if (lifetimes.get(options.id) === target) lifetimes.delete(options.id);
32
+ }).catch(error => {
33
+ if (!owner.disposed) setState({ status: "error", error });
34
+ try {
35
+ if (callbacks.current.onCleanupError) callbacks.current.onCleanupError(error, menu);
36
+ else {
37
+ console.error("Menu cleanup failed; menu handle remains retryable", error, menu);
38
+ report(error);
39
+ }
40
+ } catch (callbackError) { console.error(callbackError); }
41
+ throw error;
42
+ }).finally(() => { target.removing = undefined; });
43
+ return target.removing;
44
+ };
45
+ if (!previous || previous.disposed) lifetimes.set(options.id, owner);
46
+ owner.completion = Promise.resolve().then(async () => {
47
+ // A remount waits for allocation/disposal, then retries a retained failed removal.
48
+ if (previous?.disposed) {
49
+ await previous.completion.catch(() => {});
50
+ const blocked = owner.blocker;
51
+ if (blocked) {
52
+ await blocked.completion.catch(() => {});
53
+ try { if (blocked.menu) await remove(blocked); }
54
+ catch { return; }
55
+ owner.blocker = undefined;
56
+ }
57
+ }
58
+ if (owner.disposed) {
59
+ if (lifetimes.get(options.id) === owner) lifetimes.delete(options.id);
60
+ return;
61
+ }
62
+ const initial = owner.items;
63
+ let menu: Menu;
64
+ try {
65
+ menu = await createMenu({ id: options.id, items: initial, onAction: (event: MenuAction) => { if (!owner.disposed) callbacks.current.onAction?.(event); } });
66
+ owner.menu = menu;
67
+ } catch (error) {
68
+ report(error);
69
+ if (lifetimes.get(options.id) === owner) lifetimes.delete(options.id);
70
+ return;
71
+ }
72
+ try {
73
+ if (!owner.disposed && owner.items !== initial) await menu.update({ items: owner.items });
74
+ if (!owner.disposed) setState({ status: "ready", menu });
75
+ } catch (error) { report(error); }
76
+ await ended;
77
+ if (owner.disposed) await remove(owner).catch(() => {});
78
+ }).catch(report);
79
+ return () => { owner.disposed = true; end(); };
80
+ }, [options.id]);
81
+ useEffect(() => {
82
+ const owner = current.current;
83
+ if (!owner || owner.disposed || owner.items === options.items) return;
84
+ owner.items = options.items;
85
+ if (owner.menu) void owner.menu.update({ items: options.items }).then(() => {
86
+ // Reusing the previous state keeps an identical-content update from cascading renders.
87
+ if (!owner.disposed) setState(state => state.status === "ready" && state.menu === owner.menu ? state : { status: "ready", menu: owner.menu! });
88
+ }).catch(error => {
89
+ if (!owner.disposed) setState({ status: "error", error });
90
+ try {
91
+ if (callbacks.current.onError) callbacks.current.onError(error);
92
+ else console.error("Menu update failed", error);
93
+ } catch (callbackError) { console.error(callbackError); }
94
+ });
95
+ }, [options.id, options.items]);
96
+ return state;
97
+ }
@@ -1,43 +1,49 @@
1
- import type { NativeMenuConfig, NativeMenuItem, NativeMenuItemPatch } from "./api";
2
- type Item = NativeMenuItem & { _sparkOwner: string; _sparkMenu: string };
3
- type Menu = Omit<NativeMenuConfig, "items"> & { items: Item[] };
4
- const titleKey = (title = "") => title.replaceAll("…", "...").trim().toLowerCase();
5
- function insertion<T extends { title?: string }>(items: T[], placement?: { before?: string; after?: string }) {
6
- for (const position of ["before", "after"] as const) {
7
- const title = placement?.[position];
8
- if (title) { const index = items.findIndex(item => titleKey(item.title) === titleKey(title)); if (index >= 0) return index + (position === "after" ? 1 : 0); }
1
+ import { SparkError } from "@legendapp/spark-desktop-app/src/contracts";
2
+ import type { MenuTarget, MenuWireItem } from "@legendapp/spark-desktop-app/src/contracts/menu";
3
+ function matches(item: MenuWireItem, target: MenuTarget): boolean {
4
+ return "id" in target ? (item._sparkIdentity ?? item.id) === target.id : "role" in target ? item.role === target.role : !!item.target && "menu" in item.target && item.target.menu === target.menu;
5
+ }
6
+ function insertion(items: MenuWireItem[], placement?: MenuWireItem["placement"]): number {
7
+ if (!placement) return items.length;
8
+ const target = placement.before ?? placement.after!;
9
+ const index = items.findIndex(item => matches(item, target));
10
+ if (index < 0) throw new SparkError("E_NOT_FOUND", "Menu placement target was not found");
11
+ return index + (placement.after ? 1 : 0);
12
+ }
13
+ function locate(items: MenuWireItem[], target: MenuTarget, recursive: boolean): { items: MenuWireItem[]; item: MenuWireItem } | undefined {
14
+ for (const item of items) {
15
+ if (matches(item, target)) return { items, item };
16
+ if (recursive && item.items) { const found = locate(item.items, target, true); if (found) return found; }
9
17
  }
10
- return items.length;
11
18
  }
12
- /** Recompute owners' contributions so removing a binding restores the original item. */
13
- export type MenuDiagnostic = { code: "E_MENU_TARGET_NOT_FOUND"; ownerId: string; menuId: string; itemId: string; targets: string[] };
14
- export function composeWindowsMenus(owners: ReadonlyMap<string, NativeMenuConfig[]>, diagnose: (diagnostic: MenuDiagnostic) => void = diagnostic => console.warn("Windows menu target not found", diagnostic)): Menu[] {
15
- const result: Menu[] = [];
16
- for (const [owner, configs] of owners) for (const config of configs) {
17
- let menu = result.find(menu => config.systemMenu === "app" ? menu.systemMenu === "app" : menu.title === config.title);
18
- if (!menu) {
19
- menu = { ...config, items: [] };
20
- result.splice(config.systemMenu === "app" ? 0 : insertion(result, config.placement), 0, menu);
21
- }
22
- for (const input of config.items) {
23
- // Public menu contributions are flat on Windows; AppKit system submenus
24
- // have no counterpart. Reject before publishing rather than dropping them.
25
- if ((input.targetPath?.length ?? 0) > 1) throw Object.assign(new Error(`Windows menu ${config.id}/${input.id} does not support nested targetPath: ${input.targetPath!.join(" > ")}`), { code: "E_MENU_TARGET_UNSUPPORTED" });
26
- const candidates = input.targetPath?.length ? input.targetPath : [input.targetTitle, ...(input.targetTitles ?? [])].filter((title): title is string => !!title);
27
- const targets = !!(input.targetTitle || input.targetTitles?.length || input.targetPath?.length);
28
- const target = candidates.map(title => menu.items.find(item => titleKey(item.title) === titleKey(title))).find(Boolean);
29
- if (targets && !target) { diagnose({ code: "E_MENU_TARGET_NOT_FOUND", ownerId: owner, menuId: config.id, itemId: input.id, targets: candidates }); continue; }
30
- const identity = input.targetPath?.length && target ? { _sparkOwner: target._sparkOwner, _sparkMenu: target._sparkMenu, id: target.id, payload: target.payload } : { _sparkOwner: owner, _sparkMenu: config.id, id: input.id };
31
- const item: Item = { ...target, ...input, ...identity };
32
- if (target) {
33
- const index = menu.items.indexOf(target);
34
- menu.items.splice(index, 1);
35
- menu.items.splice(input.placement ? insertion(menu.items, input.placement) : index, 0, item);
36
- } else menu.items.splice(insertion(menu.items, input.placement), 0, item);
19
+ /** Compose fresh snapshots by stable IDs/semantic targets; failed proposals never mutate live owners. */
20
+ export function composeWindowsMenus(owners: ReadonlyMap<string, readonly MenuWireItem[]>): MenuWireItem[] {
21
+ const result: MenuWireItem[] = [];
22
+ function merge(into: MenuWireItem[], inputs: readonly MenuWireItem[], root: boolean) {
23
+ for (const input of inputs) {
24
+ const found = input.target || input.id ? locate(into, input.target ?? { id: input.id! }, !!input.target && !root) : undefined;
25
+ const target = found?.item, siblings = found?.items ?? into;
26
+ if (input.target && !target && !(root && "menu" in input.target)) throw new SparkError("E_NOT_FOUND", `Menu target for ${input.id} was not found`);
27
+ if (target && input.items) {
28
+ if (!target.items) throw new SparkError("E_INVALID_ARGUMENT", "Submenu target is not a submenu");
29
+ const { items: children, id, target: identity, placement, ...presentation } = input;
30
+ Object.assign(target, presentation);
31
+ merge(target.items, children, false);
32
+ if (placement) {
33
+ const old = siblings.indexOf(target), position = insertion(siblings, placement);
34
+ siblings.splice(old, 1); siblings.splice(position - (old < position ? 1 : 0), 0, target);
35
+ }
36
+ } else {
37
+ // Overlays keep the owner identity and the role AppKit would retain, so later placements still resolve semantic anchors.
38
+ const item = { ...input, ...(target ? { _sparkIdentity: target._sparkIdentity ?? target.id } : {}), ...(target?.role && !input.role ? { role: target.role } : {}), ...(input.items ? { items: [] as MenuWireItem[] } : {}) };
39
+ if (input.items) merge(item.items!, input.items, false);
40
+ const index = target ? siblings.indexOf(target) : -1;
41
+ const position = input.placement ? insertion(siblings, input.placement) : index < 0 ? siblings.length : index;
42
+ if (index >= 0) siblings.splice(index, 1);
43
+ siblings.splice(position - (index >= 0 && index < position ? 1 : 0), 0, item);
44
+ }
37
45
  }
38
46
  }
47
+ for (const items of owners.values()) merge(result, items, true);
39
48
  return result;
40
49
  }
41
- export function patchWindowsMenus(menus: NativeMenuConfig[], patches: NativeMenuItemPatch[]): NativeMenuConfig[] {
42
- return menus.map(menu => ({ ...menu, items: menu.items.map(item => patches.reduce((item, patch) => patch.id === item.id ? { ...item, ...patch } : item, item)) }));
43
- }
@@ -16,25 +16,41 @@ static NSString *Identifier(NSString *value) { return [NSString stringWithFormat
16
16
  static BOOL Owns(UNNotificationRequest *request) { return [request.content.userInfo[NamespaceKey] isEqual:SparkNamespace()]; }
17
17
  @interface SparkNotificationCenter : NSObject <UNUserNotificationCenterDelegate>
18
18
  @property NSMutableArray *responses;
19
+ @property NSMutableSet<UNNotificationCategory *> *categories;
20
+ @property BOOL categoriesReady;
21
+ @property NSMutableArray<void (^)(void)> *pendingCategories;
19
22
  @property (weak) id<UNUserNotificationCenterDelegate> previous;
20
23
  + (instancetype)shared;
24
+ - (void)whenCategoriesReady:(void (^)(void))operation;
21
25
  @end
22
26
  @implementation SparkNotificationCenter
23
27
  + (instancetype)shared {
24
28
  static SparkNotificationCenter *instance;
25
29
  static dispatch_once_t once;
26
30
  dispatch_once(&once, ^{
27
- instance = [SparkNotificationCenter new]; instance.responses = [NSMutableArray new];
31
+ instance = [SparkNotificationCenter new]; instance.responses = [NSMutableArray new]; instance.categories = [NSMutableSet new];
32
+ instance.pendingCategories = [NSMutableArray new];
28
33
  UNUserNotificationCenter *center = UNUserNotificationCenter.currentNotificationCenter;
29
34
  instance.previous = center.delegate; center.delegate = instance;
35
+ // Merge the host categories before publishing any Spark notification.
30
36
  [center getNotificationCategoriesWithCompletionHandler:^(NSSet<UNNotificationCategory *> *categories) {
31
- NSMutableSet *combined = [categories mutableCopy];
32
- [combined addObject:[UNNotificationCategory categoryWithIdentifier:@"spark.desktop.default" actions:@[] intentIdentifiers:@[] options:UNNotificationCategoryOptionCustomDismissAction]];
33
- [center setNotificationCategories:combined];
37
+ dispatch_async(dispatch_get_main_queue(), ^{
38
+ [instance.categories addObjectsFromArray:categories.allObjects];
39
+ [instance.categories addObject:[UNNotificationCategory categoryWithIdentifier:@"spark.desktop.default" actions:@[] intentIdentifiers:@[] options:UNNotificationCategoryOptionCustomDismissAction]];
40
+ [center setNotificationCategories:[instance.categories copy]];
41
+ instance.categoriesReady = YES;
42
+ NSArray<void (^)(void)> *pending = [instance.pendingCategories copy];
43
+ [instance.pendingCategories removeAllObjects];
44
+ for (void (^operation)(void) in pending) operation();
45
+ });
34
46
  }];
35
47
  });
36
48
  return instance;
37
49
  }
50
+ - (void)whenCategoriesReady:(void (^)(void))operation {
51
+ if (self.categoriesReady) operation();
52
+ else [self.pendingCategories addObject:[operation copy]];
53
+ }
38
54
  + (void)load {
39
55
  // Install before launch finishes, including when a notification launches the app.
40
56
  [NSNotificationCenter.defaultCenter addObserverForName:NSApplicationWillFinishLaunchingNotification object:nil queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification *note) { [self shared]; }];
@@ -51,9 +67,12 @@ static BOOL Owns(UNNotificationRequest *request) { return [request.content.userI
51
67
  return;
52
68
  }
53
69
  NSDictionary *info = response.notification.request.content.userInfo;
70
+ NSString *action = @"open";
71
+ if ([response.actionIdentifier isEqual:UNNotificationDismissActionIdentifier]) action = @"dismiss";
72
+ else if ([response.actionIdentifier hasPrefix:@"spark.action."]) action = [response.actionIdentifier substringFromIndex:@"spark.action.".length];
54
73
  NSDictionary *event = @{ @"type": @"notificationResponse", @"id": NSUUID.UUID.UUIDString,
55
74
  @"notificationId": info[@"sparkId"] ?: @"", @"data": info[@"sparkData"] ?: @{},
56
- @"action": [response.actionIdentifier isEqual:UNNotificationDismissActionIdentifier] ? @"dismiss" : @"open" };
75
+ @"action": action };
57
76
  dispatch_async(dispatch_get_main_queue(), ^{
58
77
  [self.responses addObject:event]; if (self.responses.count > 100) [self.responses removeObjectAtIndex:0];
59
78
  SparkEmit(event); completion();
@@ -74,34 +93,60 @@ RCT_EXPORT_MODULE(NativeDesktopNotifications)
74
93
  if ([method isEqual:@"permission"]) read();
75
94
  else [center requestAuthorizationWithOptions:UNAuthorizationOptionAlert | UNAuthorizationOptionSound completionHandler:^(BOOL granted, NSError *error) { if (error) SparkReject(reject, error); else read(); }];
76
95
  } else if ([method isEqual:@"show"]) {
77
- if (![args[@"id"] isKindOfClass:NSString.class] || ![args[@"title"] isKindOfClass:NSString.class]) { SparkInvalid(reject, @"Notification needs an id and title"); return; }
78
- UNMutableNotificationContent *content = [UNMutableNotificationContent new];
79
- content.categoryIdentifier = @"spark.desktop.default";
80
- content.title = args[@"title"]; content.body = args[@"body"] ?: @""; content.subtitle = args[@"subtitle"] ?: @"";
81
- if ([args[@"sound"] boolValue]) content.sound = UNNotificationSound.defaultSound;
82
- content.userInfo = @{ NamespaceKey: SparkNamespace(), @"sparkId": args[@"id"], @"sparkData": args[@"data"] ?: @{} };
83
- UNNotificationTrigger *trigger = args[@"delay"] ? [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:[args[@"delay"] doubleValue] repeats:NO] : nil;
84
- UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:Identifier(args[@"id"]) content:content trigger:trigger];
85
- [center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings *settings) {
86
- if (settings.authorizationStatus != UNAuthorizationStatusAuthorized && settings.authorizationStatus != UNAuthorizationStatusProvisional) { reject(@"E_NOTIFICATION_PERMISSION", @"Request notification permission from a user action before showing notifications", nil); return; }
87
- [center addNotificationRequest:request withCompletionHandler:^(NSError *error) { if (error) SparkReject(reject, error); else resolve(@"null"); }];
96
+ [delegate whenCategoriesReady:^{
97
+ if (![args[@"id"] isKindOfClass:NSString.class] || ![args[@"title"] isKindOfClass:NSString.class]) { SparkInvalid(reject, @"Notification needs an id and title"); return; }
98
+ UNMutableNotificationContent *content = [UNMutableNotificationContent new];
99
+ NSArray *actions = [args[@"actions"] isKindOfClass:NSArray.class] ? args[@"actions"] : nil;
100
+ content.title = args[@"title"]; content.body = args[@"body"] ?: @""; content.subtitle = args[@"subtitle"] ?: @"";
101
+ // JS normalizes sound to "none", "default", or a tone name. macOS user
102
+ // notification sounds must ship in the app bundle or Library/Sounds, so
103
+ // tones present the platform default here; Windows maps tones to system sounds.
104
+ NSString *sound = [args[@"sound"] isKindOfClass:NSString.class] ? args[@"sound"] : nil;
105
+ if (sound.length > 0 && ![sound isEqual:@"none"]) content.sound = UNNotificationSound.defaultSound;
106
+ content.categoryIdentifier = @"spark.desktop.default";
107
+ if (actions.count > 0) {
108
+ // Categories are app-global; namespace identifiers so Spark registrations
109
+ // never collide with a host app's own UNNotificationCategory set.
110
+ NSMutableArray<UNNotificationAction *> *categoryActions = [NSMutableArray new];
111
+ NSMutableString *signature = [NSMutableString stringWithString:@"spark.actions."];
112
+ for (NSDictionary *action in actions) {
113
+ if (![action[@"id"] isKindOfClass:NSString.class] || ![action[@"label"] isKindOfClass:NSString.class]) { SparkInvalid(reject, @"Notification actions need an id and label"); return; }
114
+ [categoryActions addObject:[UNNotificationAction actionWithIdentifier:[@"spark.action." stringByAppendingString:action[@"id"]] title:action[@"label"] options:UNNotificationActionOptionNone]];
115
+ [signature appendFormat:@"%@:%@|", action[@"id"], action[@"label"]];
116
+ }
117
+ content.categoryIdentifier = signature;
118
+ UNNotificationCategory *category = [UNNotificationCategory categoryWithIdentifier:signature actions:categoryActions intentIdentifiers:@[] options:UNNotificationCategoryOptionCustomDismissAction];
119
+ if (![delegate.categories containsObject:category]) {
120
+ [delegate.categories addObject:category];
121
+ [center setNotificationCategories:[delegate.categories copy]];
122
+ }
123
+ }
124
+ content.userInfo = @{ NamespaceKey: SparkNamespace(), @"sparkId": args[@"id"], @"sparkData": args[@"data"] ?: @{} };
125
+ UNNotificationTrigger *trigger = args[@"delay"] ? [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:[args[@"delay"] doubleValue] repeats:NO] : nil;
126
+ UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:Identifier(args[@"id"]) content:content trigger:trigger];
127
+ [center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings *settings) {
128
+ if (settings.authorizationStatus != UNAuthorizationStatusAuthorized && settings.authorizationStatus != UNAuthorizationStatusProvisional) { reject(@"E_PERMISSION_DENIED", @"Request notification permission from a user action before showing notifications", nil); return; }
129
+ [center addNotificationRequest:request withCompletionHandler:^(NSError *error) { if (error) SparkReject(reject, error); else resolve(@"null"); }];
130
+ }];
88
131
  }];
89
- } else if ([method isEqual:@"cancel"]) {
90
- [center removePendingNotificationRequestsWithIdentifiers:@[Identifier(args[@"id"])]];
91
- [center removeDeliveredNotificationsWithIdentifiers:@[Identifier(args[@"id"])]]; resolve(@"null");
92
- } else if ([method isEqual:@"pending"] || [method isEqual:@"delivered"] || [method isEqual:@"clear"]) {
93
- BOOL clear = [method isEqual:@"clear"];
94
- if ([method isEqual:@"pending"] || clear) [center getPendingNotificationRequestsWithCompletionHandler:^(NSArray<UNNotificationRequest *> *requests) {
95
- NSMutableArray *ids = [NSMutableArray new]; NSMutableArray *nativeIds = [NSMutableArray new];
132
+ } else if ([method isEqual:@"cancel"] || [method isEqual:@"dismiss"]) {
133
+ NSArray *ids = @[Identifier(args[@"id"])];
134
+ if ([method isEqual:@"cancel"]) [center removePendingNotificationRequestsWithIdentifiers:ids];
135
+ else [center removeDeliveredNotificationsWithIdentifiers:ids];
136
+ resolve(@"null");
137
+ } else if ([method isEqual:@"pending"] || [method isEqual:@"cancelAll"]) {
138
+ [center getPendingNotificationRequestsWithCompletionHandler:^(NSArray<UNNotificationRequest *> *requests) {
139
+ NSMutableArray *ids = [NSMutableArray new], *nativeIds = [NSMutableArray new];
96
140
  for (UNNotificationRequest *request in requests) if (Owns(request)) { [ids addObject:request.content.userInfo[@"sparkId"]]; [nativeIds addObject:request.identifier]; }
97
- if (clear) [center removePendingNotificationRequestsWithIdentifiers:nativeIds]; else resolve(SparkJSON(ids));
98
- if (clear) [center getDeliveredNotificationsWithCompletionHandler:^(NSArray<UNNotification *> *notifications) {
99
- NSMutableArray *delivered = [NSMutableArray new]; for (UNNotification *notification in notifications) if (Owns(notification.request)) [delivered addObject:notification.request.identifier];
100
- [center removeDeliveredNotificationsWithIdentifiers:delivered]; resolve(@"null");
101
- }];
141
+ if ([method isEqual:@"cancelAll"]) { [center removePendingNotificationRequestsWithIdentifiers:nativeIds]; resolve(@"null"); }
142
+ else resolve(SparkJSON(ids));
102
143
  }];
103
- else [center getDeliveredNotificationsWithCompletionHandler:^(NSArray<UNNotification *> *notifications) {
104
- NSMutableArray *ids = [NSMutableArray new]; for (UNNotification *notification in notifications) if (Owns(notification.request)) [ids addObject:notification.request.content.userInfo[@"sparkId"]]; resolve(SparkJSON(ids));
144
+ } else if ([method isEqual:@"delivered"] || [method isEqual:@"dismissAll"]) {
145
+ [center getDeliveredNotificationsWithCompletionHandler:^(NSArray<UNNotification *> *notifications) {
146
+ NSMutableArray *ids = [NSMutableArray new], *nativeIds = [NSMutableArray new];
147
+ for (UNNotification *notification in notifications) if (Owns(notification.request)) { [ids addObject:notification.request.content.userInfo[@"sparkId"]]; [nativeIds addObject:notification.request.identifier]; }
148
+ if ([method isEqual:@"dismissAll"]) { [center removeDeliveredNotificationsWithIdentifiers:nativeIds]; resolve(@"null"); }
149
+ else resolve(SparkJSON(ids));
105
150
  }];
106
151
  } else if ([method isEqual:@"responses"]) resolve(SparkJSON([delegate.responses copy]));
107
152
  else SparkInvalid(reject, @"Unknown notification operation");