@notionhq/custom-blocks 0.1.0 → 0.1.2

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 (74) hide show
  1. package/HOST.md +13 -4
  2. package/README.md +31 -6
  3. package/dist/bridge/SandboxBridge.d.ts.map +1 -1
  4. package/dist/bridge/SandboxBridge.js +11 -0
  5. package/dist/bridge/contrast.d.ts +5 -0
  6. package/dist/bridge/contrast.d.ts.map +1 -0
  7. package/dist/bridge/contrast.js +3 -0
  8. package/dist/bridge/dataSources/errors.d.ts +1 -1
  9. package/dist/bridge/dataSources/errors.d.ts.map +1 -1
  10. package/dist/bridge/dataSources/resolveProperty.d.ts +1 -1
  11. package/dist/bridge/dataSources/resolveProperty.d.ts.map +1 -1
  12. package/dist/bridge/hostState.d.ts +3 -0
  13. package/dist/bridge/hostState.d.ts.map +1 -1
  14. package/dist/bridge/messages/contrastModeChanged.d.ts +8 -0
  15. package/dist/bridge/messages/contrastModeChanged.d.ts.map +1 -0
  16. package/dist/bridge/messages/contrastModeChanged.js +7 -0
  17. package/dist/bridge/messages/hostToSandbox.d.ts +4 -0
  18. package/dist/bridge/messages/hostToSandbox.d.ts.map +1 -1
  19. package/dist/bridge/messages/hostToSandbox.js +2 -0
  20. package/dist/bridge/messages/init.d.ts +4 -1
  21. package/dist/bridge/messages/init.d.ts.map +1 -1
  22. package/dist/bridge/messages/init.js +6 -1
  23. package/dist/customBlock.d.ts +4 -0
  24. package/dist/customBlock.d.ts.map +1 -1
  25. package/dist/customBlock.js +5 -0
  26. package/dist/host/createCustomBlockHost.d.ts +2 -0
  27. package/dist/host/createCustomBlockHost.d.ts.map +1 -1
  28. package/dist/host/createCustomBlockHost.js +22 -0
  29. package/dist/host/lifecycle/types.d.ts +2 -0
  30. package/dist/host/lifecycle/types.d.ts.map +1 -1
  31. package/dist/host.d.ts +4 -0
  32. package/dist/host.d.ts.map +1 -1
  33. package/dist/host.js +2 -0
  34. package/dist/index.d.ts +1 -0
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/init.d.ts +2 -0
  37. package/dist/init.d.ts.map +1 -1
  38. package/dist/init.js +1 -0
  39. package/dist/react/NotionTokenScope.d.ts +10 -0
  40. package/dist/react/NotionTokenScope.d.ts.map +1 -0
  41. package/dist/react/NotionTokenScope.js +11 -0
  42. package/dist/react/index.d.ts +2 -1
  43. package/dist/react/index.d.ts.map +1 -1
  44. package/dist/react/index.js +2 -1
  45. package/dist/react/standalonePreview.d.ts.map +1 -1
  46. package/dist/react/standalonePreview.js +2 -0
  47. package/dist/react/useRuntimeState.d.ts +11 -0
  48. package/dist/react/useRuntimeState.d.ts.map +1 -1
  49. package/dist/react/useRuntimeState.js +12 -0
  50. package/dist/styles/nds.css +1 -0
  51. package/dist/version.js +1 -1
  52. package/docs/block-location.md +17 -3
  53. package/docs/lifecycle.md +3 -2
  54. package/package.json +2 -1
  55. package/src/bridge/SandboxBridge.ts +12 -0
  56. package/src/bridge/contrast.ts +6 -0
  57. package/src/bridge/dataSources/errors.ts +1 -1
  58. package/src/bridge/dataSources/resolveProperty.ts +1 -1
  59. package/src/bridge/hostState.ts +3 -0
  60. package/src/bridge/messages/contrastModeChanged.ts +12 -0
  61. package/src/bridge/messages/hostToSandbox.ts +2 -0
  62. package/src/bridge/messages/init.ts +6 -1
  63. package/src/customBlock.ts +13 -1
  64. package/src/host/createCustomBlockHost.ts +27 -0
  65. package/src/host/lifecycle/types.ts +3 -0
  66. package/src/host.ts +4 -0
  67. package/src/index.ts +1 -0
  68. package/src/init.ts +3 -0
  69. package/src/react/NotionTokenScope.tsx +25 -0
  70. package/src/react/index.ts +5 -0
  71. package/src/react/standalonePreview.ts +2 -0
  72. package/src/react/useRuntimeState.ts +17 -0
  73. package/src/styles/nds.css +1 -0
  74. package/src/styles/nds.css.metadata.json +6 -0
@@ -1,3 +1,4 @@
1
+ import type { NotionContrastMode } from "./contrast.js"
1
2
  import type { NotionDataSource } from "./dataSources/dataSource.js"
2
3
  import type {
3
4
  NotionDataSourcePage,
@@ -20,11 +21,13 @@ export type CustomBlockHostState = UninitializedHostState | InitializedHostState
20
21
  export type UninitializedHostState = {
21
22
  status: "uninitialized"
22
23
  theme: NotionTheme
24
+ contrastMode: NotionContrastMode
23
25
  }
24
26
 
25
27
  export type InitializedHostState = {
26
28
  status: "initialized"
27
29
  theme: NotionTheme
30
+ contrastMode: NotionContrastMode
28
31
  blockId: NotionBlockId
29
32
  parent: NotionParent
30
33
  page: CustomBlockPage
@@ -0,0 +1,12 @@
1
+ import * as v from "valibot"
2
+ import { notionContrastModeSchema } from "../contrast.js"
3
+
4
+ /** Message sent by the host when contrast changes after initialization. */
5
+ export const contrastModeChangedMessageSchema = v.object({
6
+ type: v.literal("contrastModeChanged"),
7
+ contrastMode: notionContrastModeSchema,
8
+ })
9
+
10
+ export type ContrastModeChangedMessage = v.InferOutput<
11
+ typeof contrastModeChangedMessageSchema
12
+ >
@@ -1,4 +1,5 @@
1
1
  import * as v from "valibot"
2
+ import { contrastModeChangedMessageSchema } from "./contrastModeChanged.js"
2
3
  import { createPageResultMessageSchema } from "./createPageResult.js"
3
4
  import { currentUserChangedMessageSchema } from "./currentUserChanged.js"
4
5
  import { dataSourcesChangedMessageSchema } from "./dataSourcesChanged.js"
@@ -19,6 +20,7 @@ import { updatePageResultMessageSchema } from "./updatePageResult.js"
19
20
  export const hostToSandboxMessageSchema = v.variant("type", [
20
21
  initMessageSchema,
21
22
  themeChangedMessageSchema,
23
+ contrastModeChangedMessageSchema,
22
24
  parentChangedMessageSchema,
23
25
  pageChangedMessageSchema,
24
26
  currentUserChangedMessageSchema,
@@ -1,4 +1,5 @@
1
1
  import * as v from "valibot"
2
+ import { DEFAULT_CONTRAST_MODE, notionContrastModeSchema } from "../contrast.js"
2
3
  import { notionDataSourceBindingsSchema } from "../dataSources/dataSource.js"
3
4
  import type { CustomBlockErrorCode, CustomBlockErrorInfo } from "../errors.js"
4
5
  import { notionBlockIdSchema } from "../ids.js"
@@ -66,7 +67,9 @@ export class CustomBlockInitializationError
66
67
  /**
67
68
  * Initialization message sent by the host to the sandbox exactly once, in response to the
68
69
  * sandbox's `connect` message. The sandbox echoes `initializationId` in `initResult`. After
69
- * successful initialization, live updates flow through narrower messages.
70
+ * successful initialization, live updates flow through narrower messages
71
+ * (`themeChanged`, `contrastModeChanged`, `parentChanged`, `pageChanged`,
72
+ * `dataSourcesChanged`).
70
73
  */
71
74
  export const initMessageSchema = v.variant("status", [
72
75
  v.object({
@@ -74,6 +77,8 @@ export const initMessageSchema = v.variant("status", [
74
77
  initializationId: v.string(),
75
78
  status: v.literal("success"),
76
79
  theme: notionThemeSchema,
80
+ // TODO(custom-blocks): Make this required when next bumping the protocol version.
81
+ contrastMode: v.optional(notionContrastModeSchema, DEFAULT_CONTRAST_MODE),
77
82
  blockId: notionBlockIdSchema,
78
83
  parent: notionParentSchema,
79
84
  page: customBlockPageSchema,
@@ -1,4 +1,5 @@
1
1
  import { autoResize } from "./autoResize.js"
2
+ import type { NotionContrastMode } from "./bridge/contrast.js"
2
3
  import type { NotionDataSource } from "./bridge/dataSources/dataSource.js"
3
4
  import type {
4
5
  CustomBlockHostState,
@@ -13,10 +14,15 @@ import type { NotionTheme } from "./bridge/theme.js"
13
14
  import type { NotionUser } from "./bridge/users/user.js"
14
15
 
15
16
  export type CustomBlockState =
16
- | { status: "uninitialized"; theme: NotionTheme }
17
+ | {
18
+ status: "uninitialized"
19
+ theme: NotionTheme
20
+ contrastMode: NotionContrastMode
21
+ }
17
22
  | {
18
23
  status: "initialized"
19
24
  theme: NotionTheme
25
+ contrastMode: NotionContrastMode
20
26
  blockId: NotionBlockId
21
27
  parent: NotionParent
22
28
  page: CustomBlockPage
@@ -45,6 +51,10 @@ export const customBlock = {
45
51
  return getInitializedHostState("getTheme").theme
46
52
  },
47
53
 
54
+ getContrastMode(): NotionContrastMode {
55
+ return getInitializedHostState("getContrastMode").contrastMode
56
+ },
57
+
48
58
  getBlockId(): NotionBlockId {
49
59
  return getInitializedHostState("getBlockId").blockId
50
60
  },
@@ -76,12 +86,14 @@ function toPublicState(hostState: CustomBlockHostState): CustomBlockState {
76
86
  lastPublicState = {
77
87
  status: "uninitialized",
78
88
  theme: hostState.theme,
89
+ contrastMode: hostState.contrastMode,
79
90
  }
80
91
  return lastPublicState
81
92
  }
82
93
  lastPublicState = {
83
94
  status: "initialized",
84
95
  theme: hostState.theme,
96
+ contrastMode: hostState.contrastMode,
85
97
  blockId: hostState.blockId,
86
98
  parent: hostState.parent,
87
99
  page: hostState.page,
@@ -1,7 +1,12 @@
1
1
  import * as v from "valibot"
2
+ import {
3
+ DEFAULT_CONTRAST_MODE,
4
+ type NotionContrastMode,
5
+ } from "../bridge/contrast.js"
2
6
  import type { NotionDataSourceId } from "../bridge/ids.js"
3
7
  import type { CustomBlockManifest } from "../bridge/manifest.js"
4
8
  import type { ConnectMessage } from "../bridge/messages/connect.js"
9
+ import type { ContrastModeChangedMessage } from "../bridge/messages/contrastModeChanged.js"
5
10
  import type { CreatePageMessage } from "../bridge/messages/createPage.js"
6
11
  import type { CreatePageResultMessage } from "../bridge/messages/createPageResult.js"
7
12
  import type { DataSourcesChangedMessage } from "../bridge/messages/dataSourcesChanged.js"
@@ -71,6 +76,7 @@ export type CustomBlockHostHandle = {
71
76
  stop: () => void
72
77
  post: (message: HostToSandboxMessage) => void
73
78
  setTheme: (theme: NotionTheme) => void
79
+ setContrastMode: (contrastMode: NotionContrastMode) => void
74
80
  setParent: (parent: NotionParent) => void
75
81
  setPage: (page: CustomBlockPage) => void
76
82
  setDataSources: (dataSources: CustomBlockHostDataSourcesPayload) => void
@@ -108,6 +114,7 @@ export function createCustomBlockHost(
108
114
  )
109
115
 
110
116
  let theme = initialState.theme
117
+ let contrastMode = initialState.contrastMode ?? DEFAULT_CONTRAST_MODE
111
118
  let parent = initialState.parent
112
119
  let page = initialState.page
113
120
  let dataSources = initialState.dataSources
@@ -119,6 +126,7 @@ export function createCustomBlockHost(
119
126
  let activeInitializationId: string | undefined
120
127
  let connectedSandbox: ConnectSuccessMessage | undefined
121
128
  let pendingThemeChange = false
129
+ let pendingContrastModeChange = false
122
130
  let pendingParentChange = false
123
131
  let pendingPageChange = false
124
132
  let pendingDataSourcesChange: CustomBlockHostDataSourcesPayload | undefined
@@ -201,6 +209,7 @@ export function createCustomBlockHost(
201
209
  initializationId: connect.initializationId,
202
210
  status: "success",
203
211
  theme,
212
+ contrastMode,
204
213
  blockId: initialState.blockId,
205
214
  parent,
206
215
  page,
@@ -376,6 +385,10 @@ export function createCustomBlockHost(
376
385
  pendingThemeChange = false
377
386
  post({ type: "themeChanged", theme })
378
387
  }
388
+ if (pendingContrastModeChange) {
389
+ pendingContrastModeChange = false
390
+ post({ type: "contrastModeChanged", contrastMode })
391
+ }
379
392
  if (pendingParentChange) {
380
393
  pendingParentChange = false
381
394
  post({ type: "parentChanged", parent })
@@ -527,6 +540,20 @@ export function createCustomBlockHost(
527
540
  const message: ThemeChangedMessage = { type: "themeChanged", theme }
528
541
  post(message)
529
542
  },
543
+ setContrastMode(nextContrastMode) {
544
+ contrastMode = nextContrastMode
545
+ if (initStatus !== "success") {
546
+ if (initStatus === "waitingForInitResult") {
547
+ pendingContrastModeChange = true
548
+ }
549
+ return
550
+ }
551
+ const message: ContrastModeChangedMessage = {
552
+ type: "contrastModeChanged",
553
+ contrastMode,
554
+ }
555
+ post(message)
556
+ },
530
557
  setParent(nextParent) {
531
558
  parent = nextParent
532
559
  if (initStatus !== "success") {
@@ -1,3 +1,4 @@
1
+ import type { NotionContrastMode } from "../../bridge/contrast.js"
1
2
  import type { NotionDataSourceBindings } from "../../bridge/dataSources/dataSource.js"
2
3
  import type { NotionBlockId } from "../../bridge/ids.js"
3
4
  import type { CustomBlockManifest } from "../../bridge/manifest.js"
@@ -28,6 +29,8 @@ export type CustomBlockHostInitialDataSources =
28
29
 
29
30
  export type CustomBlockHostInitialState = {
30
31
  theme: NotionTheme
32
+ // TODO(custom-blocks): Make this required when next bumping the protocol version.
33
+ contrastMode?: NotionContrastMode
31
34
  blockId: NotionBlockId
32
35
  parent: NotionParent
33
36
  page: CustomBlockPage
package/src/host.ts CHANGED
@@ -10,6 +10,8 @@
10
10
  * surface lives in the default `@notionhq/custom-blocks` entrypoint.
11
11
  */
12
12
 
13
+ export type { NotionContrastMode } from "./bridge/contrast.js"
14
+ export { notionContrastModeSchema } from "./bridge/contrast.js"
13
15
  export type {
14
16
  NotionDataSourceBinding,
15
17
  NotionDataSourceBindings,
@@ -40,6 +42,8 @@ export {
40
42
  export { readIncomingType } from "./bridge/incomingType.js"
41
43
  export type { ConnectMessage } from "./bridge/messages/connect.js"
42
44
  export { connectMessageSchema } from "./bridge/messages/connect.js"
45
+ export type { ContrastModeChangedMessage } from "./bridge/messages/contrastModeChanged.js"
46
+ export { contrastModeChangedMessageSchema } from "./bridge/messages/contrastModeChanged.js"
43
47
  export type { CreatePageMessage } from "./bridge/messages/createPage.js"
44
48
  export { createPageMessageSchema } from "./bridge/messages/createPage.js"
45
49
  export type {
package/src/index.ts CHANGED
@@ -7,6 +7,7 @@
7
7
  * should import hooks and components from `@notionhq/custom-blocks/react`.
8
8
  */
9
9
 
10
+ export type { NotionContrastMode } from "./bridge/contrast.js"
10
11
  export type {
11
12
  NotionCollectionSchema,
12
13
  NotionDataSource,
package/src/init.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { NotionContrastMode } from "./bridge/contrast.js"
1
2
  import type { NotionDataSource } from "./bridge/dataSources/dataSource.js"
2
3
  import type { NotionBlockId } from "./bridge/ids.js"
3
4
  import { loadManifest } from "./bridge/loadManifest.js"
@@ -14,6 +15,7 @@ import { unreachable } from "./utils.js"
14
15
  */
15
16
  export type CustomBlockInitPayload = {
16
17
  theme: NotionTheme
18
+ contrastMode: NotionContrastMode
17
19
  blockId: NotionBlockId
18
20
  parent: NotionParent
19
21
  page: CustomBlockPage
@@ -103,6 +105,7 @@ async function initializeCustomBlock(
103
105
  case "initialized":
104
106
  return {
105
107
  theme: hostState.theme,
108
+ contrastMode: hostState.contrastMode,
106
109
  blockId: hostState.blockId,
107
110
  parent: hostState.parent,
108
111
  page: hostState.page,
@@ -0,0 +1,25 @@
1
+ import type { ReactNode } from "react"
2
+ import { useContrastMode, useTheme } from "./useRuntimeState.js"
3
+
4
+ export type NotionTokenScopeProps = {
5
+ children: ReactNode
6
+ }
7
+
8
+ /**
9
+ * Makes Notion design tokens available to descendants and keeps their
10
+ * host-controlled appearance in sync.
11
+ */
12
+ export function NotionTokenScope({ children }: NotionTokenScopeProps) {
13
+ const theme = useTheme()
14
+ const contrastMode = useContrastMode()
15
+
16
+ return (
17
+ <div
18
+ className="nds"
19
+ data-display-mode={theme}
20
+ data-contrast-mode={contrastMode}
21
+ >
22
+ {children}
23
+ </div>
24
+ )
25
+ }
@@ -11,6 +11,10 @@ export {
11
11
  NotionCustomBlock,
12
12
  type NotionCustomBlockProps,
13
13
  } from "./NotionCustomBlock.js"
14
+ export {
15
+ NotionTokenScope,
16
+ type NotionTokenScopeProps,
17
+ } from "./NotionTokenScope.js"
14
18
  export { useCustomBlockAutoResize } from "./useCustomBlockAutoResize.js"
15
19
  export {
16
20
  type UseCustomBlockInitResult,
@@ -19,6 +23,7 @@ export {
19
23
  export { useDataSource } from "./useDataSource.js"
20
24
  export {
21
25
  useBlockId,
26
+ useContrastMode,
22
27
  useCurrentUser,
23
28
  useManifest,
24
29
  usePage,
@@ -1,4 +1,5 @@
1
1
  import * as v from "valibot"
2
+ import { DEFAULT_CONTRAST_MODE } from "../bridge/contrast.js"
2
3
  import { notionBlockIdSchema, notionPageIdSchema } from "../bridge/ids.js"
3
4
  import { customBlockHost } from "../bridge/sandboxClient.js"
4
5
 
@@ -11,6 +12,7 @@ export function seedStandalonePreviewState() {
11
12
  initializationId: "standalone-preview",
12
13
  status: "success",
13
14
  theme: "light",
15
+ contrastMode: DEFAULT_CONTRAST_MODE,
14
16
  blockId: previewBlockId,
15
17
  parent: { type: "page_id", page_id: previewPageId },
16
18
  page: { id: previewPageId, parent: { type: "workspace", workspace: true } },
@@ -1,4 +1,5 @@
1
1
  import { useSyncExternalStore } from "react"
2
+ import type { NotionContrastMode } from "../bridge/contrast.js"
2
3
  import type { NotionBlockId } from "../bridge/ids.js"
3
4
  import type { CustomBlockManifest } from "../bridge/manifest.js"
4
5
  import type { CustomBlockPage } from "../bridge/pages/page.js"
@@ -57,6 +58,22 @@ export function useTheme(): NotionTheme {
57
58
  return useSyncExternalStore(customBlock.subscribe, customBlock.getTheme)
58
59
  }
59
60
 
61
+ /**
62
+ * Returns the host's contrast preference. Re-renders on every protocol-v3
63
+ * `contrastModeChanged` message from the host.
64
+ *
65
+ * Throws if called before `initCustomBlock` has resolved.
66
+ *
67
+ * @example
68
+ * const contrastMode = useContrastMode()
69
+ */
70
+ export function useContrastMode(): NotionContrastMode {
71
+ return useSyncExternalStore(
72
+ customBlock.subscribe,
73
+ customBlock.getContrastMode,
74
+ )
75
+ }
76
+
60
77
  /**
61
78
  * Returns the viewing user's Notion profile. Re-renders whenever the host sends a
62
79
  * `currentUserChanged` message.