@notionhq/custom-blocks 0.0.58

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 (298) hide show
  1. package/HOST.md +171 -0
  2. package/README.md +57 -0
  3. package/bin/cli/attach.js +180 -0
  4. package/bin/cli/cli.js +248 -0
  5. package/bin/cli/create.js +106 -0
  6. package/bin/cli/datasources.js +255 -0
  7. package/bin/cli/deploy.js +109 -0
  8. package/bin/cli/ids.js +13 -0
  9. package/bin/cli/ntn.js +95 -0
  10. package/bin/cli/pullData.js +76 -0
  11. package/bin/cli/pullManifest.js +158 -0
  12. package/bin/cli/target.js +95 -0
  13. package/bin/src/bridge/dataSources/propertySchema.js +148 -0
  14. package/bin/src/bridge/manifest.js +40 -0
  15. package/dist/autoResize.d.ts +4 -0
  16. package/dist/autoResize.d.ts.map +1 -0
  17. package/dist/autoResize.js +71 -0
  18. package/dist/bridge/SandboxBridge.d.ts +92 -0
  19. package/dist/bridge/SandboxBridge.d.ts.map +1 -0
  20. package/dist/bridge/SandboxBridge.js +718 -0
  21. package/dist/bridge/dataSources/dataSource.d.ts +732 -0
  22. package/dist/bridge/dataSources/dataSource.d.ts.map +1 -0
  23. package/dist/bridge/dataSources/dataSource.js +37 -0
  24. package/dist/bridge/dataSources/dataSourcePage.d.ts +118 -0
  25. package/dist/bridge/dataSources/dataSourcePage.d.ts.map +1 -0
  26. package/dist/bridge/dataSources/dataSourcePage.js +14 -0
  27. package/dist/bridge/dataSources/dataSourceValue.d.ts +67 -0
  28. package/dist/bridge/dataSources/dataSourceValue.d.ts.map +1 -0
  29. package/dist/bridge/dataSources/dataSourceValue.js +14 -0
  30. package/dist/bridge/dataSources/dateValue.d.ts +158 -0
  31. package/dist/bridge/dataSources/dateValue.d.ts.map +1 -0
  32. package/dist/bridge/dataSources/dateValue.js +59 -0
  33. package/dist/bridge/dataSources/errors.d.ts +6 -0
  34. package/dist/bridge/dataSources/errors.d.ts.map +1 -0
  35. package/dist/bridge/dataSources/errors.js +4 -0
  36. package/dist/bridge/dataSources/propertySchema.d.ts +191 -0
  37. package/dist/bridge/dataSources/propertySchema.d.ts.map +1 -0
  38. package/dist/bridge/dataSources/propertySchema.js +148 -0
  39. package/dist/bridge/dataSources/recordPointer.d.ts +10 -0
  40. package/dist/bridge/dataSources/recordPointer.d.ts.map +1 -0
  41. package/dist/bridge/dataSources/recordPointer.js +8 -0
  42. package/dist/bridge/dataSources/resolve.d.ts +22 -0
  43. package/dist/bridge/dataSources/resolve.d.ts.map +1 -0
  44. package/dist/bridge/dataSources/resolve.js +65 -0
  45. package/dist/bridge/dataSources/resolveProperty.d.ts +41 -0
  46. package/dist/bridge/dataSources/resolveProperty.d.ts.map +1 -0
  47. package/dist/bridge/dataSources/resolveProperty.js +73 -0
  48. package/dist/bridge/errors.d.ts +12 -0
  49. package/dist/bridge/errors.d.ts.map +1 -0
  50. package/dist/bridge/errors.js +6 -0
  51. package/dist/bridge/hostState.d.ts +65 -0
  52. package/dist/bridge/hostState.d.ts.map +1 -0
  53. package/dist/bridge/hostState.js +65 -0
  54. package/dist/bridge/ids.d.ts +39 -0
  55. package/dist/bridge/ids.d.ts.map +1 -0
  56. package/dist/bridge/ids.js +5 -0
  57. package/dist/bridge/incomingType.d.ts +10 -0
  58. package/dist/bridge/incomingType.d.ts.map +1 -0
  59. package/dist/bridge/incomingType.js +17 -0
  60. package/dist/bridge/loadManifest.d.ts +25 -0
  61. package/dist/bridge/loadManifest.d.ts.map +1 -0
  62. package/dist/bridge/loadManifest.js +68 -0
  63. package/dist/bridge/manifest.d.ts +66 -0
  64. package/dist/bridge/manifest.d.ts.map +1 -0
  65. package/dist/bridge/manifest.js +40 -0
  66. package/dist/bridge/messages/createPage.d.ts +225 -0
  67. package/dist/bridge/messages/createPage.d.ts.map +1 -0
  68. package/dist/bridge/messages/createPage.js +46 -0
  69. package/dist/bridge/messages/createPageResult.d.ts +211 -0
  70. package/dist/bridge/messages/createPageResult.d.ts.map +1 -0
  71. package/dist/bridge/messages/createPageResult.js +28 -0
  72. package/dist/bridge/messages/currentUserChanged.d.ts +19 -0
  73. package/dist/bridge/messages/currentUserChanged.d.ts.map +1 -0
  74. package/dist/bridge/messages/currentUserChanged.js +9 -0
  75. package/dist/bridge/messages/dataSourcesChanged.d.ts +158 -0
  76. package/dist/bridge/messages/dataSourcesChanged.d.ts.map +1 -0
  77. package/dist/bridge/messages/dataSourcesChanged.js +13 -0
  78. package/dist/bridge/messages/getPage.d.ts +216 -0
  79. package/dist/bridge/messages/getPage.d.ts.map +1 -0
  80. package/dist/bridge/messages/getPage.js +33 -0
  81. package/dist/bridge/messages/getUser.d.ts +45 -0
  82. package/dist/bridge/messages/getUser.d.ts.map +1 -0
  83. package/dist/bridge/messages/getUser.js +32 -0
  84. package/dist/bridge/messages/hostToSandbox.d.ts +1089 -0
  85. package/dist/bridge/messages/hostToSandbox.d.ts.map +1 -0
  86. package/dist/bridge/messages/hostToSandbox.js +32 -0
  87. package/dist/bridge/messages/init.d.ts +209 -0
  88. package/dist/bridge/messages/init.d.ts.map +1 -0
  89. package/dist/bridge/messages/init.js +47 -0
  90. package/dist/bridge/messages/invalidHostMessage.d.ts +15 -0
  91. package/dist/bridge/messages/invalidHostMessage.d.ts.map +1 -0
  92. package/dist/bridge/messages/invalidHostMessage.js +13 -0
  93. package/dist/bridge/messages/invalidSandboxMessage.d.ts +15 -0
  94. package/dist/bridge/messages/invalidSandboxMessage.d.ts.map +1 -0
  95. package/dist/bridge/messages/invalidSandboxMessage.js +13 -0
  96. package/dist/bridge/messages/listUsers.d.ts +53 -0
  97. package/dist/bridge/messages/listUsers.d.ts.map +1 -0
  98. package/dist/bridge/messages/listUsers.js +33 -0
  99. package/dist/bridge/messages/pageChanged.d.ts +12 -0
  100. package/dist/bridge/messages/pageChanged.d.ts.map +1 -0
  101. package/dist/bridge/messages/pageChanged.js +9 -0
  102. package/dist/bridge/messages/parentChanged.d.ts +22 -0
  103. package/dist/bridge/messages/parentChanged.d.ts.map +1 -0
  104. package/dist/bridge/messages/parentChanged.js +9 -0
  105. package/dist/bridge/messages/queryDataSource.d.ts +20 -0
  106. package/dist/bridge/messages/queryDataSource.d.ts.map +1 -0
  107. package/dist/bridge/messages/queryDataSource.js +19 -0
  108. package/dist/bridge/messages/queryDataSourceResult.d.ts +90 -0
  109. package/dist/bridge/messages/queryDataSourceResult.d.ts.map +1 -0
  110. package/dist/bridge/messages/queryDataSourceResult.js +21 -0
  111. package/dist/bridge/messages/ready.d.ts +45 -0
  112. package/dist/bridge/messages/ready.d.ts.map +1 -0
  113. package/dist/bridge/messages/ready.js +37 -0
  114. package/dist/bridge/messages/resize.d.ts +12 -0
  115. package/dist/bridge/messages/resize.d.ts.map +1 -0
  116. package/dist/bridge/messages/resize.js +10 -0
  117. package/dist/bridge/messages/sandboxToHost.d.ts +410 -0
  118. package/dist/bridge/messages/sandboxToHost.d.ts.map +1 -0
  119. package/dist/bridge/messages/sandboxToHost.js +25 -0
  120. package/dist/bridge/messages/themeChanged.d.ts +11 -0
  121. package/dist/bridge/messages/themeChanged.d.ts.map +1 -0
  122. package/dist/bridge/messages/themeChanged.js +10 -0
  123. package/dist/bridge/messages/updatePage.d.ts +171 -0
  124. package/dist/bridge/messages/updatePage.d.ts.map +1 -0
  125. package/dist/bridge/messages/updatePage.js +15 -0
  126. package/dist/bridge/messages/updatePageResult.d.ts +210 -0
  127. package/dist/bridge/messages/updatePageResult.d.ts.map +1 -0
  128. package/dist/bridge/messages/updatePageResult.js +27 -0
  129. package/dist/bridge/pages/page.d.ts +600 -0
  130. package/dist/bridge/pages/page.d.ts.map +1 -0
  131. package/dist/bridge/pages/page.js +217 -0
  132. package/dist/bridge/parent.d.ts +25 -0
  133. package/dist/bridge/parent.d.ts.map +1 -0
  134. package/dist/bridge/parent.js +11 -0
  135. package/dist/bridge/pendingRequests.d.ts +14 -0
  136. package/dist/bridge/pendingRequests.d.ts.map +1 -0
  137. package/dist/bridge/pendingRequests.js +27 -0
  138. package/dist/bridge/sandboxClient.d.ts +63 -0
  139. package/dist/bridge/sandboxClient.d.ts.map +1 -0
  140. package/dist/bridge/sandboxClient.js +92 -0
  141. package/dist/bridge/theme.d.ts +4 -0
  142. package/dist/bridge/theme.d.ts.map +1 -0
  143. package/dist/bridge/theme.js +2 -0
  144. package/dist/bridge/users/user.d.ts +36 -0
  145. package/dist/bridge/users/user.d.ts.map +1 -0
  146. package/dist/bridge/users/user.js +19 -0
  147. package/dist/customBlock.d.ts +36 -0
  148. package/dist/customBlock.d.ts.map +1 -0
  149. package/dist/customBlock.js +65 -0
  150. package/dist/host/createCustomBlockHost.d.ts +34 -0
  151. package/dist/host/createCustomBlockHost.d.ts.map +1 -0
  152. package/dist/host/createCustomBlockHost.js +278 -0
  153. package/dist/host/lifecycle/initErrors.d.ts +10 -0
  154. package/dist/host/lifecycle/initErrors.d.ts.map +1 -0
  155. package/dist/host/lifecycle/initErrors.js +123 -0
  156. package/dist/host/lifecycle/ready.d.ts +8 -0
  157. package/dist/host/lifecycle/ready.d.ts.map +1 -0
  158. package/dist/host/lifecycle/ready.js +24 -0
  159. package/dist/host/lifecycle/types.d.ts +24 -0
  160. package/dist/host/lifecycle/types.d.ts.map +1 -0
  161. package/dist/host/lifecycle/types.js +1 -0
  162. package/dist/host/messages/invalidSandboxMessage.d.ts +7 -0
  163. package/dist/host/messages/invalidSandboxMessage.d.ts.map +1 -0
  164. package/dist/host/messages/invalidSandboxMessage.js +24 -0
  165. package/dist/host/messages/types.d.ts +60 -0
  166. package/dist/host/messages/types.d.ts.map +1 -0
  167. package/dist/host/messages/types.js +1 -0
  168. package/dist/host/queries/querySubscriptions.d.ts +20 -0
  169. package/dist/host/queries/querySubscriptions.d.ts.map +1 -0
  170. package/dist/host/queries/querySubscriptions.js +29 -0
  171. package/dist/host/queries/types.d.ts +9 -0
  172. package/dist/host/queries/types.d.ts.map +1 -0
  173. package/dist/host/queries/types.js +1 -0
  174. package/dist/host.d.ts +73 -0
  175. package/dist/host.d.ts.map +1 -0
  176. package/dist/host.js +42 -0
  177. package/dist/index.d.ts +37 -0
  178. package/dist/index.d.ts.map +1 -0
  179. package/dist/index.js +14 -0
  180. package/dist/init.d.ts +53 -0
  181. package/dist/init.d.ts.map +1 -0
  182. package/dist/init.js +87 -0
  183. package/dist/react/DebugMessageLog.d.ts +2 -0
  184. package/dist/react/DebugMessageLog.d.ts.map +1 -0
  185. package/dist/react/DebugMessageLog.js +22 -0
  186. package/dist/react/NotionCustomBlock.css +110 -0
  187. package/dist/react/NotionCustomBlock.d.ts +50 -0
  188. package/dist/react/NotionCustomBlock.d.ts.map +1 -0
  189. package/dist/react/NotionCustomBlock.js +81 -0
  190. package/dist/react/index.d.ts +14 -0
  191. package/dist/react/index.d.ts.map +1 -0
  192. package/dist/react/index.js +13 -0
  193. package/dist/react/standalonePreview.d.ts +2 -0
  194. package/dist/react/standalonePreview.d.ts.map +1 -0
  195. package/dist/react/standalonePreview.js +26 -0
  196. package/dist/react/useCustomBlockAutoResize.d.ts +30 -0
  197. package/dist/react/useCustomBlockAutoResize.d.ts.map +1 -0
  198. package/dist/react/useCustomBlockAutoResize.js +35 -0
  199. package/dist/react/useCustomBlockInit.d.ts +43 -0
  200. package/dist/react/useCustomBlockInit.d.ts.map +1 -0
  201. package/dist/react/useCustomBlockInit.js +60 -0
  202. package/dist/react/useDataSource.d.ts +13 -0
  203. package/dist/react/useDataSource.d.ts.map +1 -0
  204. package/dist/react/useDataSource.js +37 -0
  205. package/dist/react/useHostState.d.ts +2 -0
  206. package/dist/react/useHostState.d.ts.map +1 -0
  207. package/dist/react/useHostState.js +5 -0
  208. package/dist/react/useRuntimeState.d.ts +66 -0
  209. package/dist/react/useRuntimeState.d.ts.map +1 -0
  210. package/dist/react/useRuntimeState.js +73 -0
  211. package/dist/types.d.ts +154 -0
  212. package/dist/types.d.ts.map +1 -0
  213. package/dist/types.js +1 -0
  214. package/dist/utils.d.ts +9 -0
  215. package/dist/utils.d.ts.map +1 -0
  216. package/dist/utils.js +10 -0
  217. package/dist/version.d.ts +9 -0
  218. package/dist/version.d.ts.map +1 -0
  219. package/dist/version.js +7 -0
  220. package/docs/block-location.md +83 -0
  221. package/docs/data-sources.md +186 -0
  222. package/docs/lifecycle.md +172 -0
  223. package/docs/manifest.md +42 -0
  224. package/docs/pages.md +143 -0
  225. package/docs/users.md +92 -0
  226. package/package.json +69 -0
  227. package/src/autoResize.ts +84 -0
  228. package/src/bridge/SandboxBridge.ts +921 -0
  229. package/src/bridge/dataSources/dataSource.ts +63 -0
  230. package/src/bridge/dataSources/dataSourcePage.ts +71 -0
  231. package/src/bridge/dataSources/dataSourceValue.ts +19 -0
  232. package/src/bridge/dataSources/dateValue.ts +96 -0
  233. package/src/bridge/dataSources/errors.ts +14 -0
  234. package/src/bridge/dataSources/propertySchema.ts +186 -0
  235. package/src/bridge/dataSources/recordPointer.ts +13 -0
  236. package/src/bridge/dataSources/resolve.ts +99 -0
  237. package/src/bridge/dataSources/resolveProperty.ts +130 -0
  238. package/src/bridge/errors.ts +13 -0
  239. package/src/bridge/hostState.ts +154 -0
  240. package/src/bridge/ids.ts +58 -0
  241. package/src/bridge/incomingType.ts +19 -0
  242. package/src/bridge/loadManifest.ts +89 -0
  243. package/src/bridge/manifest.ts +53 -0
  244. package/src/bridge/messages/createPage.ts +67 -0
  245. package/src/bridge/messages/createPageResult.ts +52 -0
  246. package/src/bridge/messages/currentUserChanged.ts +14 -0
  247. package/src/bridge/messages/dataSourcesChanged.ts +18 -0
  248. package/src/bridge/messages/getPage.ts +52 -0
  249. package/src/bridge/messages/getUser.ts +48 -0
  250. package/src/bridge/messages/hostToSandbox.ts +37 -0
  251. package/src/bridge/messages/init.ts +81 -0
  252. package/src/bridge/messages/invalidHostMessage.ts +16 -0
  253. package/src/bridge/messages/invalidSandboxMessage.ts +18 -0
  254. package/src/bridge/messages/listUsers.ts +49 -0
  255. package/src/bridge/messages/pageChanged.ts +12 -0
  256. package/src/bridge/messages/parentChanged.ts +14 -0
  257. package/src/bridge/messages/queryDataSource.ts +24 -0
  258. package/src/bridge/messages/queryDataSourceResult.ts +38 -0
  259. package/src/bridge/messages/ready.ts +41 -0
  260. package/src/bridge/messages/resize.ts +13 -0
  261. package/src/bridge/messages/sandboxToHost.ts +30 -0
  262. package/src/bridge/messages/themeChanged.ts +15 -0
  263. package/src/bridge/messages/updatePage.ts +22 -0
  264. package/src/bridge/messages/updatePageResult.ts +49 -0
  265. package/src/bridge/pages/page.ts +281 -0
  266. package/src/bridge/parent.ts +27 -0
  267. package/src/bridge/pendingRequests.ts +28 -0
  268. package/src/bridge/sandboxClient.ts +137 -0
  269. package/src/bridge/theme.ts +5 -0
  270. package/src/bridge/users/user.ts +31 -0
  271. package/src/css.d.ts +1 -0
  272. package/src/customBlock.ts +102 -0
  273. package/src/host/createCustomBlockHost.ts +392 -0
  274. package/src/host/lifecycle/initErrors.ts +132 -0
  275. package/src/host/lifecycle/ready.ts +36 -0
  276. package/src/host/lifecycle/types.ts +28 -0
  277. package/src/host/messages/invalidSandboxMessage.ts +38 -0
  278. package/src/host/messages/types.ts +67 -0
  279. package/src/host/queries/querySubscriptions.ts +52 -0
  280. package/src/host/queries/types.ts +16 -0
  281. package/src/host.ts +167 -0
  282. package/src/index.ts +115 -0
  283. package/src/init.ts +143 -0
  284. package/src/react/DebugMessageLog.tsx +30 -0
  285. package/src/react/NotionCustomBlock.css +110 -0
  286. package/src/react/NotionCustomBlock.tsx +151 -0
  287. package/src/react/index.ts +28 -0
  288. package/src/react/standalonePreview.ts +28 -0
  289. package/src/react/useCustomBlockAutoResize.ts +46 -0
  290. package/src/react/useCustomBlockInit.ts +91 -0
  291. package/src/react/useDataSource.ts +47 -0
  292. package/src/react/useHostState.ts +9 -0
  293. package/src/react/useRuntimeState.ts +85 -0
  294. package/src/types.ts +175 -0
  295. package/src/utils.ts +13 -0
  296. package/src/version.ts +8 -0
  297. package/vite-plugin/index.d.ts +46 -0
  298. package/vite-plugin/index.js +115 -0
@@ -0,0 +1,151 @@
1
+ import { type ReactNode, useEffect, useState } from "react"
2
+ import { type InitCustomBlockOptions, NotInIframeError } from "../index.js"
3
+ import { DebugMessageLog } from "./DebugMessageLog.js"
4
+ import { seedStandalonePreviewState } from "./standalonePreview.js"
5
+ import { useCustomBlockAutoResize } from "./useCustomBlockAutoResize.js"
6
+ import { useCustomBlockInit } from "./useCustomBlockInit.js"
7
+ import { useCustomBlockHost } from "./useHostState.js"
8
+ import "./NotionCustomBlock.css"
9
+
10
+ /**
11
+ * Props accepted by {@link NotionCustomBlock}.
12
+ */
13
+ export type NotionCustomBlockProps = InitCustomBlockOptions & {
14
+ children: ReactNode
15
+ /**
16
+ * Rendered while the SDK ↔ host handshake is in progress. Defaults to
17
+ * `null` (nothing).
18
+ */
19
+ fallback?: ReactNode
20
+ /**
21
+ * Rendered when the handshake fails. Either a node, or a function that
22
+ * receives the `Error`. When omitted, a friendly default error card is
23
+ * rendered with developer details — replace it for production templates.
24
+ */
25
+ errorFallback?: ReactNode | ((error: Error) => ReactNode)
26
+ /**
27
+ * Whether the provider should automatically post resize messages so the
28
+ * host iframe matches the content height of `#root`. Defaults to `true`.
29
+ * Pass `false` when you want to use the default block size and are ok
30
+ * with scrollbars within the Notion client.
31
+ *
32
+ * @default true
33
+ */
34
+ autoResize?: boolean
35
+ }
36
+
37
+ /**
38
+ * Top-level wrapper that runs the SDK ↔ host handshake and gates `children`
39
+ * until it resolves. Passes `timeoutMs` straight through to
40
+ * {@link initCustomBlock}.
41
+ *
42
+ * Templates that prefer not to write a `Root` gating component (or top-level
43
+ * `await`) can mount their app entirely inside this provider:
44
+ *
45
+ * ```tsx
46
+ * ReactDOM.createRoot(root).render(
47
+ * <NotionCustomBlock>
48
+ * <App />
49
+ * </NotionCustomBlock>,
50
+ * )
51
+ * ```
52
+ *
53
+ * Inside `children`, every SDK hook is guaranteed to return a populated
54
+ * value. Outside the provider (or during the loading window), they throw.
55
+ */
56
+ export function NotionCustomBlock({
57
+ children,
58
+ timeoutMs,
59
+ fallback = null,
60
+ errorFallback,
61
+ autoResize = true,
62
+ }: NotionCustomBlockProps) {
63
+ const init = useCustomBlockInit({ timeoutMs })
64
+ useCustomBlockAutoResize({ enabled: autoResize })
65
+ const isStandalone = init.error instanceof NotInIframeError
66
+ const host = useCustomBlockHost()
67
+ const [debugOpen, setDebugOpen] = useState(false)
68
+
69
+ useEffect(() => {
70
+ const onKeyDown = (e: KeyboardEvent) => {
71
+ if (e.key === "\\") {
72
+ setDebugOpen(prev => !prev)
73
+ }
74
+ }
75
+ window.addEventListener("keydown", onKeyDown)
76
+ return () => window.removeEventListener("keydown", onKeyDown)
77
+ }, [])
78
+
79
+ useEffect(() => {
80
+ if (!isStandalone) {
81
+ return
82
+ }
83
+ console.warn(`[notion-custom-sdk] ${init.error?.message}`)
84
+ seedStandalonePreviewState()
85
+ }, [isStandalone, init.error])
86
+
87
+ if (debugOpen) {
88
+ return <DebugMessageLog />
89
+ }
90
+ if (init.error && !isStandalone) {
91
+ if (errorFallback === undefined) {
92
+ return <DefaultInitErrorFallback error={init.error} />
93
+ }
94
+ return (
95
+ <>
96
+ {typeof errorFallback === "function"
97
+ ? errorFallback(init.error)
98
+ : errorFallback}
99
+ </>
100
+ )
101
+ }
102
+ if (isStandalone) {
103
+ // Wait for the effect to seed placeholder host state; otherwise hooks
104
+ // in `children` would throw.
105
+ if (host.status !== "initialized") {
106
+ return <>{fallback}</>
107
+ }
108
+ return (
109
+ <>
110
+ <div role="status" className="ncblock-standalone-banner">
111
+ Notion host not detected — running in standalone preview. SDK hooks
112
+ return placeholder values until embedded in Notion.
113
+ </div>
114
+ {children}
115
+ </>
116
+ )
117
+ }
118
+ if (!init.isLoaded) {
119
+ return <>{fallback}</>
120
+ }
121
+ return <>{children}</>
122
+ }
123
+
124
+ // TODO(custom-blocks): Align the styling of this with the host-side error states.
125
+ function DefaultInitErrorFallback({ error }: { error: Error }) {
126
+ useEffect(() => {
127
+ console.warn(
128
+ `[notion-custom-sdk] Custom block init failed: ${error.message}`,
129
+ )
130
+ }, [error])
131
+
132
+ return (
133
+ <div role="alert" className="ncblock-default-init-error">
134
+ <div className="ncblock-default-init-error__card">
135
+ <div className="ncblock-default-init-error__eyebrow">Custom block</div>
136
+ <h2 className="ncblock-default-init-error__title">
137
+ Couldn&apos;t connect to Notion
138
+ </h2>
139
+ <p className="ncblock-default-init-error__body">
140
+ This custom block loaded, but the setup handshake didn&apos;t finish.
141
+ Try again from Notion. If this keeps happening, check the browser
142
+ console for SDK details.
143
+ </p>
144
+ <details className="ncblock-default-init-error__details">
145
+ <summary>Developer details</summary>
146
+ <pre className="ncblock-default-init-error__pre">{error.message}</pre>
147
+ </details>
148
+ </div>
149
+ </div>
150
+ )
151
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * React entrypoint for `@notionhq/custom-blocks/react`.
3
+ *
4
+ * Exposes only the React surface — components and hooks. Framework-neutral
5
+ * values and types (clients, manifest/data-source types, etc.) live on the
6
+ * `@notionhq/custom-blocks` entrypoint and are intentionally NOT re-exported
7
+ * here, so the two public surfaces stay disjoint.
8
+ */
9
+
10
+ export {
11
+ NotionCustomBlock,
12
+ type NotionCustomBlockProps,
13
+ } from "./NotionCustomBlock.js"
14
+ export { useCustomBlockAutoResize } from "./useCustomBlockAutoResize.js"
15
+ export {
16
+ type CustomBlockInitFailure,
17
+ type UseCustomBlockInitResult,
18
+ useCustomBlockInit,
19
+ } from "./useCustomBlockInit.js"
20
+ export { useDataSource } from "./useDataSource.js"
21
+ export {
22
+ useBlockId,
23
+ useCurrentUser,
24
+ useManifest,
25
+ usePage,
26
+ useParent,
27
+ useTheme,
28
+ } from "./useRuntimeState.js"
@@ -0,0 +1,28 @@
1
+ import * as v from "valibot"
2
+ import { notionBlockIdSchema, notionPageIdSchema } from "../bridge/ids.js"
3
+ import { customBlockHost } from "../bridge/sandboxClient.js"
4
+
5
+ const previewBlockId = v.parse(notionBlockIdSchema, "")
6
+ const previewPageId = v.parse(notionPageIdSchema, "")
7
+
8
+ export function seedStandalonePreviewState() {
9
+ customBlockHost.setMockState({
10
+ type: "init",
11
+ status: "success",
12
+ theme: "light",
13
+ blockId: previewBlockId,
14
+ parent: { type: "page_id", page_id: previewPageId },
15
+ page: { id: previewPageId },
16
+ dataSources: { bindings: {} },
17
+ currentUser: {
18
+ object: "user",
19
+ id: "",
20
+ name: "Preview User",
21
+ avatar_url: null,
22
+ type: "person",
23
+ person: {
24
+ email: "preview@example.com",
25
+ },
26
+ },
27
+ })
28
+ }
@@ -0,0 +1,46 @@
1
+ import { useEffect } from "react"
2
+ import { customBlock } from "../index.js"
3
+
4
+ /**
5
+ * Measures the sandbox's `#root` element and posts `resize` messages so the host iframe
6
+ * matches the block's border-box height. Unchanged values are deduped.
7
+ *
8
+ * `<NotionCustomBlock>` calls this hook for you by default — only reach for it directly
9
+ * when you need to drive `enabled` yourself (e.g. behind a debug toggle). In that case,
10
+ * pass `autoResize={false}` to the provider to avoid running it twice. For full-bleed
11
+ * views that should fill their slot, pass `autoResize={false}` and skip the hook.
12
+ *
13
+ * @example
14
+ * <NotionCustomBlock autoResize={false}>
15
+ * <App />
16
+ * </NotionCustomBlock>
17
+ *
18
+ * function App() {
19
+ * const [enabled, setEnabled] = useState(true)
20
+ * useCustomBlockAutoResize({ enabled })
21
+ * return <div>…</div>
22
+ * }
23
+ */
24
+ export function useCustomBlockAutoResize(
25
+ args: {
26
+ /**
27
+ * Whether or not the hook is enabled. To disable this behavior, pass `false`. This is
28
+ * provided as an argument to allow for conditional disabling of the hook.
29
+ *
30
+ * @default true
31
+ */
32
+ enabled?: boolean
33
+ } = {},
34
+ ): void {
35
+ const { enabled = true } = args
36
+ useEffect(() => {
37
+ if (!enabled) {
38
+ return
39
+ }
40
+ if (typeof window === "undefined") {
41
+ return
42
+ }
43
+ const target = document.getElementById("root")
44
+ return customBlock.autoResize({ target })
45
+ }, [enabled])
46
+ }
@@ -0,0 +1,91 @@
1
+ import { useEffect, useState } from "react"
2
+ import { CustomBlockInitError } from "../bridge/messages/init.js"
3
+ import {
4
+ type CustomBlockInitial,
5
+ type InitCustomBlockOptions,
6
+ initCustomBlock,
7
+ NotInIframeError,
8
+ } from "../init.js"
9
+
10
+ export type CustomBlockInitFailure = CustomBlockInitError | NotInIframeError
11
+
12
+ /**
13
+ * Discriminated state returned by {@link useCustomBlockInit}.
14
+ *
15
+ * Branch on `isLoaded`/`error`:
16
+ * - `{ isLoaded: false, error: undefined }` — handshake in progress.
17
+ * - `{ isLoaded: false, error: CustomBlockInitFailure }` — handshake failed (most commonly a
18
+ * `CustomBlockInitError` with code `init_timeout` because the host never sent `init`).
19
+ * - `{ isLoaded: true, initial }` — handshake complete; safe to render
20
+ * children that call `useTheme`, `useBlockId`, etc.
21
+ */
22
+ export type UseCustomBlockInitResult =
23
+ | { isLoaded: false; error: undefined }
24
+ | { isLoaded: false; error: CustomBlockInitFailure }
25
+ | { isLoaded: true; error: undefined; initial: CustomBlockInitial }
26
+
27
+ /**
28
+ * React wrapper around {@link initCustomBlock}. Kicks off the SDK ↔ host
29
+ * handshake on mount and returns a discriminated state object so the rest of
30
+ * the tree can render inside the `isLoaded === true` branch (where every
31
+ * other SDK hook is guaranteed to return a populated value).
32
+ *
33
+ * Idempotent — multiple components can call this; they share the same
34
+ * underlying handshake promise.
35
+ *
36
+ * @example
37
+ * function Root() {
38
+ * const init = useCustomBlockInit()
39
+ * if (init.error) return <p role="alert">Init failed: {init.error.message}</p>
40
+ * if (!init.isLoaded) return null
41
+ * return <App />
42
+ * }
43
+ */
44
+ export function useCustomBlockInit(
45
+ opts?: InitCustomBlockOptions,
46
+ ): UseCustomBlockInitResult {
47
+ const [state, setState] = useState<UseCustomBlockInitResult>({
48
+ isLoaded: false,
49
+ error: undefined,
50
+ })
51
+ useEffect(() => {
52
+ let cancelled = false
53
+ initCustomBlock(opts).then(
54
+ initial => {
55
+ if (!cancelled) {
56
+ setState({ isLoaded: true, error: undefined, initial })
57
+ }
58
+ },
59
+ err => {
60
+ if (!cancelled) {
61
+ setState({
62
+ isLoaded: false,
63
+ error: normalizeInitError(err),
64
+ })
65
+ }
66
+ },
67
+ )
68
+ return () => {
69
+ cancelled = true
70
+ }
71
+ // `initCustomBlock` caches its result, so options after the first call
72
+ // are ignored — re-running on opts changes would be misleading.
73
+ // eslint-disable-next-line react-hooks/exhaustive-deps
74
+ }, [])
75
+ return state
76
+ }
77
+
78
+ function normalizeInitError(error: unknown): CustomBlockInitFailure {
79
+ if (
80
+ error instanceof CustomBlockInitError ||
81
+ error instanceof NotInIframeError
82
+ ) {
83
+ return error
84
+ }
85
+ const message = error instanceof Error ? error.message : String(error)
86
+ return new CustomBlockInitError({
87
+ code: "unknown_error",
88
+ message,
89
+ isRetryable: false,
90
+ })
91
+ }
@@ -0,0 +1,47 @@
1
+ import { useEffect } from "react"
2
+ import { customBlockDataSources } from "../bridge/sandboxClient.js"
3
+ import type { UseDataSourceOptions, UseDataSourceResult } from "../types.js"
4
+ import { useCustomBlockHost } from "./useHostState.js"
5
+
6
+ /**
7
+ * Reads from the data source mapped to the given semantic `key`.
8
+ *
9
+ * @param key - The semantic data-source key the block is wired to (e.g. `"people"`).
10
+ * @param options - Optional live snapshot options. `limit` defaults to 20 and
11
+ * is capped at 999.
12
+ *
13
+ * @example
14
+ * const { items, isLoading, hasMore, error } = useDataSource("default", { limit: 25 })
15
+ */
16
+ export function useDataSource(
17
+ key: string,
18
+ options?: UseDataSourceOptions,
19
+ ): UseDataSourceResult {
20
+ const host = useCustomBlockHost()
21
+ const limit = options?.limit
22
+ const matchingDataSource =
23
+ host.status === "initialized"
24
+ ? host.dataSources.find(dataSource => dataSource.key === key)
25
+ : undefined
26
+
27
+ const isInitialized = host.status === "initialized"
28
+ useEffect(() => {
29
+ if (!isInitialized) {
30
+ return
31
+ }
32
+
33
+ customBlockDataSources.query(key, { limit })
34
+ }, [matchingDataSource, isInitialized, key, limit])
35
+
36
+ const view = customBlockDataSources.getView(host, key)
37
+
38
+ return {
39
+ items: view.items,
40
+ collectionSchema: view.collectionSchema,
41
+ propertySchemasById: view.propertySchemasById,
42
+ propertySchemasByKey: view.propertySchemasByKey,
43
+ isLoading: view.isLoading,
44
+ hasMore: view.hasMore,
45
+ error: view.error,
46
+ }
47
+ }
@@ -0,0 +1,9 @@
1
+ import { useSyncExternalStore } from "react"
2
+ import { customBlockHost } from "../bridge/sandboxClient.js"
3
+
4
+ export function useCustomBlockHost() {
5
+ return useSyncExternalStore(
6
+ customBlockHost.subscribe,
7
+ customBlockHost.getState,
8
+ )
9
+ }
@@ -0,0 +1,85 @@
1
+ import { useSyncExternalStore } from "react"
2
+ import type { NotionBlockId } from "../bridge/ids.js"
3
+ import type { CustomBlockManifest } from "../bridge/manifest.js"
4
+ import type { CustomBlockPage } from "../bridge/pages/page.js"
5
+ import type { NotionParent } from "../bridge/parent.js"
6
+ import type { NotionTheme } from "../bridge/theme.js"
7
+ import type { NotionUser } from "../bridge/users/user.js"
8
+ import { customBlock } from "../customBlock.js"
9
+
10
+ /**
11
+ * Returns this custom block's ID. This is stable throughout the block's lifetime.
12
+ *
13
+ * Throws if called before `initCustomBlock` has resolved.
14
+ *
15
+ * @example
16
+ * const blockId = useBlockId()
17
+ */
18
+ export function useBlockId(): NotionBlockId {
19
+ return useSyncExternalStore(customBlock.subscribe, customBlock.getBlockId)
20
+ }
21
+
22
+ /**
23
+ * Returns the block's parent in the document tree. Re-renders when the host sends
24
+ * a parent location update (e.g. the block moves into a different container).
25
+ *
26
+ * Throws if called before `initCustomBlock` has resolved.
27
+ *
28
+ * @example
29
+ * const parent = useParent()
30
+ */
31
+ export function useParent(): NotionParent {
32
+ return useSyncExternalStore(customBlock.subscribe, customBlock.getParent)
33
+ }
34
+
35
+ /**
36
+ * Returns the nearest enclosing page ancestor. Re-renders when the host sends
37
+ * a page location update.
38
+ *
39
+ * Throws if called before `initCustomBlock` has resolved.
40
+ *
41
+ * @example
42
+ * const page = usePage()
43
+ */
44
+ export function usePage(): CustomBlockPage {
45
+ return useSyncExternalStore(customBlock.subscribe, customBlock.getPage)
46
+ }
47
+
48
+ /**
49
+ * Returns the host's current theme. Re-renders on every `themeChanged` message from the host.
50
+ *
51
+ * Throws if called before `initCustomBlock` has resolved.
52
+ *
53
+ * @example
54
+ * const theme = useTheme()
55
+ */
56
+ export function useTheme(): NotionTheme {
57
+ return useSyncExternalStore(customBlock.subscribe, customBlock.getTheme)
58
+ }
59
+
60
+ /**
61
+ * Returns the viewing user's Notion profile. Re-renders whenever the host sends a
62
+ * `currentUserChanged` message.
63
+ *
64
+ * Throws if called before `initCustomBlock` has resolved. `await` it before mounting.
65
+ *
66
+ * @example
67
+ * const me = useCurrentUser()
68
+ * console.log(me.id, me.name, me.person.email)
69
+ */
70
+ export function useCurrentUser(): NotionUser {
71
+ return useSyncExternalStore(customBlock.subscribe, customBlock.getCurrentUser)
72
+ }
73
+
74
+ /**
75
+ * Returns the author-declared manifest loaded from `custom_blocks.json` — the semantic
76
+ * data-source keys plus their declared `name`, `description`, and property declarations.
77
+ * `null` when the block ships no manifest.
78
+ *
79
+ * This is the configuration the block declared, not host-resolved bindings: use
80
+ * `useDataSource(key)` for rows and resolved schema. Handy for enumerating the declared
81
+ * data-source keys (e.g. picking a default key, or rendering a key switcher).
82
+ */
83
+ export function useManifest(): CustomBlockManifest | null {
84
+ return useSyncExternalStore(customBlock.subscribe, customBlock.getManifest)
85
+ }
package/src/types.ts ADDED
@@ -0,0 +1,175 @@
1
+ import type { NotionCollectionSchema } from "./bridge/dataSources/dataSource.js"
2
+ import type {
3
+ NotionDataSourcePage,
4
+ NotionDataSourcePageUpdateInput,
5
+ NotionDataSourcePageUpdateResult,
6
+ } from "./bridge/dataSources/dataSourcePage.js"
7
+ import type { NotionPropertySchema } from "./bridge/dataSources/propertySchema.js"
8
+ import type { CustomBlockErrorInfo } from "./bridge/errors.js"
9
+ import type { NotionDataSourceId } from "./bridge/ids.js"
10
+ import type { NotionCreatePagePosition } from "./bridge/messages/createPage.js"
11
+ import type { CustomBlockCreatePageErrorInfo } from "./bridge/messages/createPageResult.js"
12
+ import type { CustomBlockGetPageErrorInfo } from "./bridge/messages/getPage.js"
13
+ import type { CustomBlockGetUserErrorInfo } from "./bridge/messages/getUser.js"
14
+ import type { CustomBlockListUsersErrorInfo } from "./bridge/messages/listUsers.js"
15
+ import type { CustomBlockQueryDataSourceErrorInfo } from "./bridge/messages/queryDataSourceResult.js"
16
+ import type { CustomBlockUpdatePageErrorInfo } from "./bridge/messages/updatePageResult.js"
17
+ import type {
18
+ NotionPage,
19
+ NotionPageCover,
20
+ NotionPageIcon,
21
+ NotionPageId,
22
+ NotionPagePropertyInputMap,
23
+ NotionPagePropertyWriteMap,
24
+ } from "./bridge/pages/page.js"
25
+ import type { NotionUser, NotionUserList } from "./bridge/users/user.js"
26
+
27
+ export type { NotionDataSourceId, NotionSpaceId } from "./bridge/ids.js"
28
+ export type { NotionPageId } from "./bridge/pages/page.js"
29
+ export type {
30
+ NotionUser,
31
+ NotionUserId,
32
+ NotionUserList,
33
+ } from "./bridge/users/user.js"
34
+ export type {
35
+ CustomBlockCreatePageErrorInfo,
36
+ CustomBlockErrorInfo,
37
+ CustomBlockGetPageErrorInfo,
38
+ CustomBlockGetUserErrorInfo,
39
+ CustomBlockListUsersErrorInfo,
40
+ CustomBlockQueryDataSourceErrorInfo,
41
+ CustomBlockUpdatePageErrorInfo,
42
+ NotionDataSourcePageUpdateInput,
43
+ NotionDataSourcePageUpdateResult,
44
+ }
45
+
46
+ /**
47
+ * Return shape of `useDataSource`.
48
+ *
49
+ * - `items` — the rows the host has returned so far. Empty until the first response arrives.
50
+ * - `isLoading` — `true` while a query is in flight.
51
+ * - `hasMore` — `true` if the host indicated more rows are available beyond the current page.
52
+ * - `error` — structured error information if the most recent query failed.
53
+ */
54
+ export type UseDataSourceResult = {
55
+ items: NotionDataSourcePage[]
56
+ /**
57
+ * Collection/data-source schema for the bound Notion data source, including raw property
58
+ * schemas. Undefined when the semantic data-source key has not been bound.
59
+ */
60
+ collectionSchema?: NotionCollectionSchema
61
+ /**
62
+ * Per-property schemas keyed by raw property ID, including the four synthetic built-ins
63
+ * (`created_time`, `last_edited_time`, `created_by`, `last_edited_by`).
64
+ */
65
+ propertySchemasById: { [propertyId: string]: NotionPropertySchema }
66
+ /**
67
+ * Per-property schemas keyed by user-defined keys from the data source's `propertyIdsByKey`.
68
+ * Built-ins are NOT included here. Keys mapped to `undefined` indicate a declared-but-unbound
69
+ * slot.
70
+ */
71
+ propertySchemasByKey: { [key: string]: NotionPropertySchema | undefined }
72
+ isLoading: boolean
73
+ hasMore: boolean
74
+ error?: CustomBlockQueryDataSourceErrorInfo
75
+ }
76
+
77
+ export type UseDataSourceOptions = {
78
+ /**
79
+ * Maximum number of rows to request from the host. Defaults to 20.
80
+ */
81
+ limit?: number
82
+ }
83
+
84
+ /**
85
+ * Parent reference accepted by `sdk.pages.create`. Mirrors Notion's public `POST /v1/pages`
86
+ * parent shape; see https://developers.notion.com/reference/data-source.
87
+ *
88
+ * - `page_id`: Create a child page under the given Notion page.
89
+ * - `data_source_id`: Create a row inside the given Notion data source (aka the internal
90
+ * collection). The public API's `data_source_id` is the same UUID as the `collectionPointer.id`
91
+ * exposed in `dataSources`, so either value works here.
92
+ * - `data_source_key`: Create a row inside the data source that the custom block was configured with
93
+ * under this semantic key. The SDK resolves the key to a `data_source_id` locally before
94
+ * sending the request to the host.
95
+ */
96
+ export type CreatePageParent =
97
+ | { type: "page_id"; page_id: NotionPageId }
98
+ | { type: "data_source_id"; data_source_id: NotionDataSourceId }
99
+ | { type: "data_source_key"; key: string }
100
+
101
+ /**
102
+ * Input accepted by `sdk.pages.create`. Mirrors the Notion public API `POST /v1/pages` API.
103
+ */
104
+ export type CreatePageInput = {
105
+ parent: CreatePageParent
106
+ properties: NotionPagePropertyInputMap
107
+ icon?: NotionPageIcon
108
+ cover?: NotionPageCover
109
+ position?: NotionCreatePagePosition
110
+ }
111
+
112
+ /**
113
+ * The result of a `sdk.pages.create` API call.
114
+ */
115
+ export type CreatePageResult =
116
+ | {
117
+ status: "success"
118
+ /** The newly created page. */
119
+ page: NotionPage
120
+ }
121
+ | { status: "error"; error: CustomBlockCreatePageErrorInfo }
122
+
123
+ /**
124
+ * Input accepted by `sdk.pages.update`.
125
+ *
126
+ * `properties` is keyed by raw Notion property ID. To update a row with configured
127
+ * custom-block property keys, use the `update` helper on pages returned from
128
+ * `useDataSource`.
129
+ */
130
+ export type UpdatePageInput = {
131
+ pageId: NotionPageId
132
+ properties?: NotionPagePropertyWriteMap
133
+ icon?: NotionPageIcon
134
+ cover?: NotionPageCover
135
+ archived?: boolean
136
+ }
137
+
138
+ /**
139
+ * Result of `sdk.pages.get`.
140
+ */
141
+ export type GetPageResult =
142
+ | {
143
+ status: "success"
144
+ page: NotionPage
145
+ }
146
+ | { status: "error"; error: CustomBlockGetPageErrorInfo }
147
+
148
+ /**
149
+ * Result of `sdk.pages.update` / `sdk.pages.delete`.
150
+ */
151
+ export type UpdatePageResult =
152
+ | {
153
+ status: "success"
154
+ page: NotionPage
155
+ }
156
+ | { status: "error"; error: CustomBlockUpdatePageErrorInfo }
157
+
158
+ export type ListUsersInput = {
159
+ startCursor?: string
160
+ pageSize?: number
161
+ }
162
+
163
+ export type ListUsersResult =
164
+ | {
165
+ status: "success"
166
+ list: NotionUserList
167
+ }
168
+ | { status: "error"; error: CustomBlockListUsersErrorInfo }
169
+
170
+ export type GetUserResult =
171
+ | {
172
+ status: "success"
173
+ user: NotionUser
174
+ }
175
+ | { status: "error"; error: CustomBlockGetUserErrorInfo }
package/src/utils.ts ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Internal helpers shared across the SDK.
3
+ */
4
+
5
+ /**
6
+ * Throws an error when an unexpected value is encountered. This is used to ensure all code paths
7
+ * are covered when using discriminated unions.
8
+ */
9
+ export function unreachable(value: never): never {
10
+ throw new Error(
11
+ `[notion-custom-sdk] Unexpected value encountered: ${JSON.stringify(value)}`,
12
+ )
13
+ }
package/src/version.ts ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Runtime SDK package version. Sent over the bridge for host-side analytics only. Business logic
3
+ * should compare against the bridge protocol version instead of this value.
4
+ *
5
+ * This checked-in value intentionally stays generic for local development. The SDK publish build
6
+ * overwrites the compiled runtime module with package.json's semver before publishing.
7
+ */
8
+ export const NCBLOCK_SDK_VERSION: string = "0.0.0"