@notionhq/custom-blocks 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/HOST.md +29 -18
- package/README.md +56 -15
- package/dist/bridge/SandboxBridge.d.ts.map +1 -1
- package/dist/bridge/SandboxBridge.js +11 -0
- package/dist/bridge/contrast.d.ts +5 -0
- package/dist/bridge/contrast.d.ts.map +1 -0
- package/dist/bridge/contrast.js +3 -0
- package/dist/bridge/hostState.d.ts +3 -0
- package/dist/bridge/hostState.d.ts.map +1 -1
- package/dist/bridge/loadManifest.d.ts +9 -8
- package/dist/bridge/loadManifest.d.ts.map +1 -1
- package/dist/bridge/loadManifest.js +6 -14
- package/dist/bridge/manifest.d.ts +3 -8
- package/dist/bridge/manifest.d.ts.map +1 -1
- package/dist/bridge/manifest.js +0 -8
- package/dist/bridge/messages/contrastModeChanged.d.ts +8 -0
- package/dist/bridge/messages/contrastModeChanged.d.ts.map +1 -0
- package/dist/bridge/messages/contrastModeChanged.js +7 -0
- package/dist/bridge/messages/hostToSandbox.d.ts +4 -0
- package/dist/bridge/messages/hostToSandbox.d.ts.map +1 -1
- package/dist/bridge/messages/hostToSandbox.js +2 -0
- package/dist/bridge/messages/init.d.ts +4 -1
- package/dist/bridge/messages/init.d.ts.map +1 -1
- package/dist/bridge/messages/init.js +6 -1
- package/dist/customBlock.d.ts +4 -0
- package/dist/customBlock.d.ts.map +1 -1
- package/dist/customBlock.js +5 -0
- package/dist/host/createCustomBlockHost.d.ts +2 -0
- package/dist/host/createCustomBlockHost.d.ts.map +1 -1
- package/dist/host/createCustomBlockHost.js +22 -0
- package/dist/host/lifecycle/types.d.ts +2 -0
- package/dist/host/lifecycle/types.d.ts.map +1 -1
- package/dist/host.d.ts +4 -0
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/init.d.ts +7 -6
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +20 -10
- package/dist/react/NotionTokenScope.d.ts +10 -0
- package/dist/react/NotionTokenScope.d.ts.map +1 -0
- package/dist/react/NotionTokenScope.js +11 -0
- package/dist/react/index.d.ts +2 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +2 -1
- package/dist/react/standalonePreview.d.ts.map +1 -1
- package/dist/react/standalonePreview.js +2 -0
- package/dist/react/useRuntimeState.d.ts +11 -0
- package/dist/react/useRuntimeState.d.ts.map +1 -1
- package/dist/react/useRuntimeState.js +12 -0
- package/dist/styles/nds.css +1 -0
- package/dist/version.js +1 -1
- package/docs/block-location.md +17 -3
- package/docs/deployment.md +41 -0
- package/docs/lifecycle.md +3 -2
- package/docs/vite-plugin.md +5 -29
- package/package.json +2 -1
- package/src/bridge/SandboxBridge.ts +12 -0
- package/src/bridge/contrast.ts +6 -0
- package/src/bridge/hostState.ts +3 -0
- package/src/bridge/loadManifest.ts +8 -17
- package/src/bridge/manifest.ts +3 -9
- package/src/bridge/messages/contrastModeChanged.ts +12 -0
- package/src/bridge/messages/hostToSandbox.ts +2 -0
- package/src/bridge/messages/init.ts +6 -1
- package/src/customBlock.ts +13 -1
- package/src/host/createCustomBlockHost.ts +27 -0
- package/src/host/lifecycle/types.ts +3 -0
- package/src/host.ts +4 -0
- package/src/index.ts +1 -0
- package/src/init.ts +22 -10
- package/src/react/NotionTokenScope.tsx +25 -0
- package/src/react/index.ts +5 -0
- package/src/react/standalonePreview.ts +2 -0
- package/src/react/useRuntimeState.ts +17 -0
- package/src/styles/nds.css +1 -0
- package/src/styles/nds.css.metadata.json +6 -0
- package/vite-plugin/index.d.ts +2 -2
- package/vite-plugin/index.js +8 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@notionhq/custom-blocks",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"import": "./dist/index.js",
|
|
19
19
|
"types": "./dist/index.d.ts"
|
|
20
20
|
},
|
|
21
|
+
"./nds.css": "./dist/styles/nds.css",
|
|
21
22
|
"./react": {
|
|
22
23
|
"import": "./dist/react/index.js",
|
|
23
24
|
"types": "./dist/react/index.d.ts"
|
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
} from "../types.js"
|
|
15
15
|
import { unreachable } from "../utils.js"
|
|
16
16
|
import { CUSTOM_BLOCKS_SDK_VERSION } from "../version.js"
|
|
17
|
+
import { DEFAULT_CONTRAST_MODE } from "./contrast.js"
|
|
17
18
|
import type {
|
|
18
19
|
NotionDataSource,
|
|
19
20
|
NotionDataSourceBindings,
|
|
@@ -79,6 +80,7 @@ export class SandboxBridge {
|
|
|
79
80
|
private hostState: CustomBlockHostState = {
|
|
80
81
|
status: "uninitialized",
|
|
81
82
|
theme: "light",
|
|
83
|
+
contrastMode: DEFAULT_CONTRAST_MODE,
|
|
82
84
|
}
|
|
83
85
|
private listeners = new Set<() => void>()
|
|
84
86
|
private messageLog: MessageLogEntry[] = []
|
|
@@ -301,6 +303,15 @@ export class SandboxBridge {
|
|
|
301
303
|
return
|
|
302
304
|
}
|
|
303
305
|
|
|
306
|
+
case "contrastModeChanged": {
|
|
307
|
+
this.hostState = {
|
|
308
|
+
...hostState,
|
|
309
|
+
contrastMode: message.contrastMode,
|
|
310
|
+
}
|
|
311
|
+
this.notify()
|
|
312
|
+
return
|
|
313
|
+
}
|
|
314
|
+
|
|
304
315
|
case "parentChanged": {
|
|
305
316
|
this.hostState = {
|
|
306
317
|
...hostState,
|
|
@@ -538,6 +549,7 @@ export class SandboxBridge {
|
|
|
538
549
|
this.hostState = {
|
|
539
550
|
status: "initialized",
|
|
540
551
|
theme: message.theme,
|
|
552
|
+
contrastMode: message.contrastMode,
|
|
541
553
|
blockId,
|
|
542
554
|
parent,
|
|
543
555
|
page,
|
package/src/bridge/hostState.ts
CHANGED
|
@@ -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
|
|
@@ -4,13 +4,12 @@ import { type CustomBlockManifest, manifestSchema } from "./manifest.js"
|
|
|
4
4
|
|
|
5
5
|
const MANIFEST_URL = "custom_blocks.json"
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
type CustomBlockConnectErrorCode =
|
|
8
8
|
| "manifest_unavailable"
|
|
9
9
|
| "manifest_invalid"
|
|
10
10
|
| (string & {})
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
CustomBlockErrorInfo<CustomBlockConnectErrorCode>
|
|
12
|
+
type CustomBlockConnectError = CustomBlockErrorInfo<CustomBlockConnectErrorCode>
|
|
14
13
|
|
|
15
14
|
export type ManifestLoadResult =
|
|
16
15
|
| {
|
|
@@ -23,21 +22,13 @@ export type ManifestLoadResult =
|
|
|
23
22
|
}
|
|
24
23
|
|
|
25
24
|
/**
|
|
26
|
-
* Attempts to load
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
25
|
+
* Attempts to load the custom block's optional self-hosted manifest, co-located with the bundle at
|
|
26
|
+
* `/custom_blocks.json`. This is to support local development where the block is served from a
|
|
27
|
+
* local dev server.
|
|
28
|
+
*
|
|
29
|
+
* It is up to the host to decide whether to use this manifest or provide its own persisted manifest.
|
|
31
30
|
*/
|
|
32
|
-
export async function
|
|
33
|
-
if (typeof fetch !== "function") {
|
|
34
|
-
const message = `No fetch API available; cannot load ${MANIFEST_URL}.`
|
|
35
|
-
console.warn(`[custom-blocks-sdk] ${message}`)
|
|
36
|
-
return {
|
|
37
|
-
manifest: null,
|
|
38
|
-
error: { code: "manifest_unavailable", message, isRetryable: true },
|
|
39
|
-
}
|
|
40
|
-
}
|
|
31
|
+
export async function attemptToLoadSelfHostedManifest(): Promise<ManifestLoadResult> {
|
|
41
32
|
let response: Response
|
|
42
33
|
try {
|
|
43
34
|
response = await fetch(MANIFEST_URL, { credentials: "omit" })
|
package/src/bridge/manifest.ts
CHANGED
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
import * as v from "valibot"
|
|
2
2
|
import { notionPropertyTypeSchema } from "./dataSources/propertySchema.js"
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* User-authored manifest declaring the data sources the custom block expects.
|
|
6
|
-
* Lives at `custom_blocks.json` in the project root. The sandbox may send it in
|
|
7
|
-
* `connect`, and the host returns the authoritative manifest in `init`. The
|
|
8
|
-
* `notionCustomBlock` Vite plugin from
|
|
9
|
-
* `@notionhq/custom-blocks/vite` wires the JSON file into the dev server and
|
|
10
|
-
* the build output.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
4
|
/**
|
|
14
5
|
* Decorative icon attached to a manifest data source. Mirrors the
|
|
15
6
|
* `emoji` / `external` icon variants the public Notion API uses, so the host
|
|
@@ -50,4 +41,7 @@ export const manifestSchema = v.object({
|
|
|
50
41
|
dataSources: v.record(v.string(), manifestDataSourceSchema),
|
|
51
42
|
})
|
|
52
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Manifest declaring the data sources required by a custom block.
|
|
46
|
+
*/
|
|
53
47
|
export type CustomBlockManifest = v.InferOutput<typeof manifestSchema>
|
|
@@ -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,
|
package/src/customBlock.ts
CHANGED
|
@@ -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
|
-
| {
|
|
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
package/src/init.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
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
|
-
import {
|
|
4
|
+
import { attemptToLoadSelfHostedManifest } from "./bridge/loadManifest.js"
|
|
4
5
|
import { CustomBlockInitializationError } from "./bridge/messages/init.js"
|
|
5
6
|
import type { CustomBlockPage } from "./bridge/pages/page.js"
|
|
6
7
|
import type { NotionParent } from "./bridge/parent.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
|
|
@@ -61,26 +63,35 @@ const NOT_IN_IFRAME_MESSAGE =
|
|
|
61
63
|
let initPromise: Promise<CustomBlockInitPayload> | undefined
|
|
62
64
|
|
|
63
65
|
/**
|
|
64
|
-
*
|
|
65
|
-
* `connect`, then awaits the host's `init` message. Resolves after the sandbox
|
|
66
|
-
* applies the payload and returns `initResult.success`.
|
|
66
|
+
* Initializes the custom block by running the SDK <-> host handshake.
|
|
67
67
|
*
|
|
68
|
-
*
|
|
68
|
+
* Resolves with the block's initial context (theme, block location, current
|
|
69
|
+
* user, data sources) once the host has initialized the block. Rejects with a
|
|
70
|
+
* `CustomBlockInitializationError` if initialization fails.
|
|
69
71
|
*
|
|
70
72
|
* Idempotent: subsequent calls return the same promise as the first and ignore any new options.
|
|
71
|
-
* Mount your React tree (or call any SDK hook
|
|
72
|
-
* returned promise resolves.
|
|
73
|
+
* Mount your React tree (or call any SDK hook) only after the returned promise resolves.
|
|
73
74
|
*/
|
|
74
75
|
export function initCustomBlock(
|
|
75
76
|
opts: InitCustomBlockOptions = {},
|
|
76
77
|
): Promise<CustomBlockInitPayload> {
|
|
77
78
|
if (initPromise === undefined) {
|
|
78
|
-
initPromise =
|
|
79
|
+
initPromise = performHandshake(opts)
|
|
79
80
|
}
|
|
80
81
|
return initPromise
|
|
81
82
|
}
|
|
82
83
|
|
|
83
|
-
|
|
84
|
+
/**
|
|
85
|
+
* Performs the host <-> sandbox SDK handshake:
|
|
86
|
+
* 1. Attempts to load an optional self-hosted `custom_blocks.json`
|
|
87
|
+
* 2. Sends `connect` with the manifest
|
|
88
|
+
* 3. Awaits the host's `init` message
|
|
89
|
+
* 4. Applies the `init` payload and acknowledges with `initResult` (fire-and-forget)
|
|
90
|
+
*
|
|
91
|
+
* Resolves with the `init` payload after the process above. Rejects with a
|
|
92
|
+
* `CustomBlockInitializationError` if any step fails.
|
|
93
|
+
*/
|
|
94
|
+
async function performHandshake(
|
|
84
95
|
opts: InitCustomBlockOptions,
|
|
85
96
|
): Promise<CustomBlockInitPayload> {
|
|
86
97
|
try {
|
|
@@ -92,7 +103,7 @@ async function initializeCustomBlock(
|
|
|
92
103
|
}
|
|
93
104
|
|
|
94
105
|
// Load the manifest and send it to the host.
|
|
95
|
-
const manifestResult = await
|
|
106
|
+
const manifestResult = await attemptToLoadSelfHostedManifest()
|
|
96
107
|
customBlockHost.sendConnect(manifestResult)
|
|
97
108
|
|
|
98
109
|
const timeoutMs = opts.timeoutMs ?? DEFAULT_INIT_TIMEOUT_MS
|
|
@@ -103,6 +114,7 @@ async function initializeCustomBlock(
|
|
|
103
114
|
case "initialized":
|
|
104
115
|
return {
|
|
105
116
|
theme: hostState.theme,
|
|
117
|
+
contrastMode: hostState.contrastMode,
|
|
106
118
|
blockId: hostState.blockId,
|
|
107
119
|
parent: hostState.parent,
|
|
108
120
|
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
|
+
}
|
package/src/react/index.ts
CHANGED
|
@@ -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.
|