ncblock 0.0.1

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 (113) hide show
  1. package/README.md +286 -0
  2. package/dist/bridge/context.d.ts +44 -0
  3. package/dist/bridge/context.d.ts.map +1 -0
  4. package/dist/bridge/context.js +42 -0
  5. package/dist/bridge/dataSources/dataSource.d.ts +732 -0
  6. package/dist/bridge/dataSources/dataSource.d.ts.map +1 -0
  7. package/dist/bridge/dataSources/dataSource.js +37 -0
  8. package/dist/bridge/dataSources/dataSourcePage.d.ts +117 -0
  9. package/dist/bridge/dataSources/dataSourcePage.d.ts.map +1 -0
  10. package/dist/bridge/dataSources/dataSourcePage.js +13 -0
  11. package/dist/bridge/dataSources/dataSourceValue.d.ts +67 -0
  12. package/dist/bridge/dataSources/dataSourceValue.d.ts.map +1 -0
  13. package/dist/bridge/dataSources/dataSourceValue.js +14 -0
  14. package/dist/bridge/dataSources/dateValue.d.ts +158 -0
  15. package/dist/bridge/dataSources/dateValue.d.ts.map +1 -0
  16. package/dist/bridge/dataSources/dateValue.js +59 -0
  17. package/dist/bridge/dataSources/propertySchema.d.ts +191 -0
  18. package/dist/bridge/dataSources/propertySchema.d.ts.map +1 -0
  19. package/dist/bridge/dataSources/propertySchema.js +148 -0
  20. package/dist/bridge/dataSources/recordPointer.d.ts +10 -0
  21. package/dist/bridge/dataSources/recordPointer.d.ts.map +1 -0
  22. package/dist/bridge/dataSources/recordPointer.js +8 -0
  23. package/dist/bridge/ids.d.ts +21 -0
  24. package/dist/bridge/ids.d.ts.map +1 -0
  25. package/dist/bridge/ids.js +3 -0
  26. package/dist/bridge/incomingType.d.ts +10 -0
  27. package/dist/bridge/incomingType.d.ts.map +1 -0
  28. package/dist/bridge/incomingType.js +17 -0
  29. package/dist/bridge/manifest.d.ts +66 -0
  30. package/dist/bridge/manifest.d.ts.map +1 -0
  31. package/dist/bridge/manifest.js +40 -0
  32. package/dist/bridge/messages/contextChanged.d.ts +20 -0
  33. package/dist/bridge/messages/contextChanged.d.ts.map +1 -0
  34. package/dist/bridge/messages/contextChanged.js +10 -0
  35. package/dist/bridge/messages/createPage.d.ts +233 -0
  36. package/dist/bridge/messages/createPage.d.ts.map +1 -0
  37. package/dist/bridge/messages/createPage.js +45 -0
  38. package/dist/bridge/messages/createPageResult.d.ts +198 -0
  39. package/dist/bridge/messages/createPageResult.d.ts.map +1 -0
  40. package/dist/bridge/messages/createPageResult.js +20 -0
  41. package/dist/bridge/messages/dataSourcesChanged.d.ts +158 -0
  42. package/dist/bridge/messages/dataSourcesChanged.d.ts.map +1 -0
  43. package/dist/bridge/messages/dataSourcesChanged.js +13 -0
  44. package/dist/bridge/messages/getPage.d.ts +203 -0
  45. package/dist/bridge/messages/getPage.d.ts.map +1 -0
  46. package/dist/bridge/messages/getPage.js +24 -0
  47. package/dist/bridge/messages/hostToSandbox.d.ts +974 -0
  48. package/dist/bridge/messages/hostToSandbox.d.ts.map +1 -0
  49. package/dist/bridge/messages/hostToSandbox.js +24 -0
  50. package/dist/bridge/messages/init.d.ts +169 -0
  51. package/dist/bridge/messages/init.d.ts.map +1 -0
  52. package/dist/bridge/messages/init.js +17 -0
  53. package/dist/bridge/messages/invalidHostMessage.d.ts +15 -0
  54. package/dist/bridge/messages/invalidHostMessage.d.ts.map +1 -0
  55. package/dist/bridge/messages/invalidHostMessage.js +13 -0
  56. package/dist/bridge/messages/invalidSandboxMessage.d.ts +15 -0
  57. package/dist/bridge/messages/invalidSandboxMessage.d.ts.map +1 -0
  58. package/dist/bridge/messages/invalidSandboxMessage.js +13 -0
  59. package/dist/bridge/messages/queryDataSource.d.ts +13 -0
  60. package/dist/bridge/messages/queryDataSource.d.ts.map +1 -0
  61. package/dist/bridge/messages/queryDataSource.js +11 -0
  62. package/dist/bridge/messages/queryDataSourceResult.d.ts +77 -0
  63. package/dist/bridge/messages/queryDataSourceResult.d.ts.map +1 -0
  64. package/dist/bridge/messages/queryDataSourceResult.js +13 -0
  65. package/dist/bridge/messages/ready.d.ts +41 -0
  66. package/dist/bridge/messages/ready.d.ts.map +1 -0
  67. package/dist/bridge/messages/ready.js +22 -0
  68. package/dist/bridge/messages/resize.d.ts +12 -0
  69. package/dist/bridge/messages/resize.d.ts.map +1 -0
  70. package/dist/bridge/messages/resize.js +10 -0
  71. package/dist/bridge/messages/sandboxToHost.d.ts +389 -0
  72. package/dist/bridge/messages/sandboxToHost.d.ts.map +1 -0
  73. package/dist/bridge/messages/sandboxToHost.js +21 -0
  74. package/dist/bridge/messages/themeChanged.d.ts +11 -0
  75. package/dist/bridge/messages/themeChanged.d.ts.map +1 -0
  76. package/dist/bridge/messages/themeChanged.js +10 -0
  77. package/dist/bridge/messages/updatePage.d.ts +171 -0
  78. package/dist/bridge/messages/updatePage.d.ts.map +1 -0
  79. package/dist/bridge/messages/updatePage.js +14 -0
  80. package/dist/bridge/messages/updatePageResult.d.ts +197 -0
  81. package/dist/bridge/messages/updatePageResult.d.ts.map +1 -0
  82. package/dist/bridge/messages/updatePageResult.js +19 -0
  83. package/dist/bridge/pages/page.d.ts +651 -0
  84. package/dist/bridge/pages/page.d.ts.map +1 -0
  85. package/dist/bridge/pages/page.js +229 -0
  86. package/dist/bridge/pendingRequests.d.ts +14 -0
  87. package/dist/bridge/pendingRequests.d.ts.map +1 -0
  88. package/dist/bridge/pendingRequests.js +27 -0
  89. package/dist/bridge/theme.d.ts +4 -0
  90. package/dist/bridge/theme.d.ts.map +1 -0
  91. package/dist/bridge/theme.js +2 -0
  92. package/dist/host.d.ts +52 -0
  93. package/dist/host.d.ts.map +1 -0
  94. package/dist/host.js +32 -0
  95. package/dist/index.d.ts +27 -0
  96. package/dist/index.d.ts.map +1 -0
  97. package/dist/index.js +14 -0
  98. package/dist/notion.d.ts +113 -0
  99. package/dist/notion.d.ts.map +1 -0
  100. package/dist/notion.js +773 -0
  101. package/dist/pages.d.ts +23 -0
  102. package/dist/pages.d.ts.map +1 -0
  103. package/dist/pages.js +30 -0
  104. package/dist/react.d.ts +171 -0
  105. package/dist/react.d.ts.map +1 -0
  106. package/dist/react.js +284 -0
  107. package/dist/types.d.ts +124 -0
  108. package/dist/types.d.ts.map +1 -0
  109. package/dist/types.js +1 -0
  110. package/dist/utils.d.ts +9 -0
  111. package/dist/utils.d.ts.map +1 -0
  112. package/dist/utils.js +10 -0
  113. package/package.json +48 -0
@@ -0,0 +1,23 @@
1
+ import type { CreatePageInput, CreatePageResult, GetPageResult, NotionPageId, UpdatePageInput, UpdatePageResult } from "./types";
2
+ /**
3
+ * Page-related SDK APIs exposed under `sdk.pages.*`.
4
+ */
5
+ export declare const pages: {
6
+ /**
7
+ * Creates a new Notion page.
8
+ */
9
+ create(input: CreatePageInput): Promise<CreatePageResult>;
10
+ /**
11
+ * Fetches a page by id.
12
+ */
13
+ get(pageId: NotionPageId): Promise<GetPageResult>;
14
+ /**
15
+ * Updates an existing page.
16
+ */
17
+ update(input: UpdatePageInput): Promise<UpdatePageResult>;
18
+ /**
19
+ * Soft-delete a page by archiving it.
20
+ */
21
+ delete(pageId: NotionPageId): Promise<UpdatePageResult>;
22
+ };
23
+ //# sourceMappingURL=pages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pages.d.ts","sourceRoot":"","sources":["../pages.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACX,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,eAAe,EACf,gBAAgB,EAChB,MAAM,SAAS,CAAA;AAEhB;;GAEG;AACH,eAAO,MAAM,KAAK;IACjB;;OAEG;kBACW,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAIzD;;OAEG;gBACS,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC;IAIjD;;OAEG;kBACW,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAIzD;;OAEG;mBACY,YAAY,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAGvD,CAAA"}
package/dist/pages.js ADDED
@@ -0,0 +1,30 @@
1
+ import { createPage, getPage, updatePage } from "./notion";
2
+ /**
3
+ * Page-related SDK APIs exposed under `sdk.pages.*`.
4
+ */
5
+ export const pages = {
6
+ /**
7
+ * Creates a new Notion page.
8
+ */
9
+ create(input) {
10
+ return createPage(input);
11
+ },
12
+ /**
13
+ * Fetches a page by id.
14
+ */
15
+ get(pageId) {
16
+ return getPage(pageId);
17
+ },
18
+ /**
19
+ * Updates an existing page.
20
+ */
21
+ update(input) {
22
+ return updatePage(input);
23
+ },
24
+ /**
25
+ * Soft-delete a page by archiving it.
26
+ */
27
+ delete(pageId) {
28
+ return updatePage({ pageId, archived: true });
29
+ },
30
+ };
@@ -0,0 +1,171 @@
1
+ import { type ReactNode } from "react";
2
+ import type { NotionCustomBlockContext } from "./bridge/context";
3
+ import type { NotionDataSource } from "./bridge/dataSources/dataSource";
4
+ import type { NotionTheme } from "./bridge/theme";
5
+ import { type CustomBlockInitial, type InitCustomBlockOptions } from "./notion";
6
+ import type { UseDataSourceResult } from "./types";
7
+ /**
8
+ * Discriminated state returned by {@link useCustomBlockInit}.
9
+ *
10
+ * Branch on `isLoaded`/`error`:
11
+ * - `{ isLoaded: false, error: undefined }` — handshake in progress.
12
+ * - `{ isLoaded: false, error: Error }` — handshake failed (most commonly a
13
+ * `TimeoutError` because the host never sent `init`).
14
+ * - `{ isLoaded: true, initial }` — handshake complete; safe to render
15
+ * children that call `useTheme`, `useCustomBlockContext`, etc.
16
+ */
17
+ export type UseCustomBlockInitResult = {
18
+ isLoaded: false;
19
+ error: undefined;
20
+ } | {
21
+ isLoaded: false;
22
+ error: Error;
23
+ } | {
24
+ isLoaded: true;
25
+ error: undefined;
26
+ initial: CustomBlockInitial;
27
+ };
28
+ /**
29
+ * React wrapper around {@link initCustomBlock}. Kicks off the SDK ↔ host
30
+ * handshake on mount and returns a discriminated state object so the rest of
31
+ * the tree can render inside the `isLoaded === true` branch (where every
32
+ * other SDK hook is guaranteed to return a populated value).
33
+ *
34
+ * Idempotent — multiple components can call this; they share the same
35
+ * underlying handshake promise.
36
+ *
37
+ * @example
38
+ * function Root() {
39
+ * const init = useCustomBlockInit()
40
+ * if (init.error) return <p role="alert">Init failed: {init.error.message}</p>
41
+ * if (!init.isLoaded) return null
42
+ * return <App />
43
+ * }
44
+ */
45
+ export declare function useCustomBlockInit(opts?: InitCustomBlockOptions): UseCustomBlockInitResult;
46
+ /**
47
+ * Props accepted by {@link NotionCustomBlock}.
48
+ */
49
+ export type NotionCustomBlockProps = InitCustomBlockOptions & {
50
+ children: ReactNode;
51
+ /**
52
+ * Rendered while the SDK ↔ host handshake is in progress. Defaults to
53
+ * `null` (nothing).
54
+ */
55
+ fallback?: ReactNode;
56
+ /**
57
+ * Rendered when the handshake fails. Either a node, or a function that
58
+ * receives the `Error`. When omitted, a small inline `<p role="alert">` is
59
+ * rendered with the error message — replace it for production templates.
60
+ */
61
+ errorFallback?: ReactNode | ((error: Error) => ReactNode);
62
+ /**
63
+ * Whether the provider should automatically post resize messages so the
64
+ * host iframe matches the content height of `#root`. Defaults to `true`.
65
+ * Pass `false` when you want to use the default block size and are ok
66
+ * with scrollbars within the Notion client.
67
+ *
68
+ * @default true
69
+ */
70
+ autoResize?: boolean;
71
+ };
72
+ /**
73
+ * Top-level wrapper that runs the SDK ↔ host handshake and gates `children`
74
+ * until it resolves. Passes `timeoutMs` straight through to
75
+ * {@link initCustomBlock}.
76
+ *
77
+ * Templates that prefer not to write a `Root` gating component (or top-level
78
+ * `await`) can mount their app entirely inside this provider:
79
+ *
80
+ * ```tsx
81
+ * ReactDOM.createRoot(root).render(
82
+ * <NotionCustomBlock>
83
+ * <App />
84
+ * </NotionCustomBlock>,
85
+ * )
86
+ * ```
87
+ *
88
+ * Inside `children`, every SDK hook is guaranteed to return a populated
89
+ * value. Outside the provider (or during the loading window), they throw.
90
+ */
91
+ export declare function NotionCustomBlock({ children, timeoutMs, fallback, errorFallback, autoResize, }: NotionCustomBlockProps): import("react/jsx-runtime").JSX.Element;
92
+ /**
93
+ * Returns the block's location in the document tree. Re-renders when the host sends
94
+ * `contextChanged` (e.g. the block moves into a different container or its enclosing
95
+ * page changes).
96
+ *
97
+ * Throws if called before `initCustomBlock` has resolved — `await` it before mounting.
98
+ *
99
+ * @example
100
+ * const ctx = useCustomBlockContext()
101
+ * console.log(ctx.customBlockId, ctx.parent.type)
102
+ */
103
+ export declare function useCustomBlockContext(): NotionCustomBlockContext;
104
+ /**
105
+ * Returns the host's current theme. Re-renders on every `themeChanged` message from the host.
106
+ *
107
+ * Throws if called before `initCustomBlock` has resolved — `await` it before mounting.
108
+ *
109
+ * @example
110
+ * const theme = useTheme()
111
+ */
112
+ export declare function useTheme(): NotionTheme;
113
+ /**
114
+ * Returns the raw data-source definitions — semantic keys plus optional `collectionPointer`,
115
+ * `collectionSchema`, `propertyIdsByKey`, and derived `propertySchemasById`. Most templates should use
116
+ * `useDataSource(key)` instead — this hook is for views that render the configuration
117
+ * itself (debug panels, schema-driven UIs).
118
+ *
119
+ * Throws if called before `initCustomBlock` has resolved.
120
+ */
121
+ export declare function useDataSourceDefinitions(): NotionDataSource[];
122
+ /**
123
+ * Reads from the data source mapped to the given semantic `key`.
124
+ *
125
+ * The first render kicks off a query for the first `initialLimit` items. Calling
126
+ * `fetchMore()` re-requests a larger prefix (the bridge does not expose cursors yet);
127
+ * page growth tracks `initialLimit`. The hook automatically resets to `initialLimit`
128
+ * when the underlying data source definition changes.
129
+ *
130
+ * @param key - The semantic data-source key the block is wired to (e.g. `"people"`).
131
+ * @param initialLimit - Page size for the first query, and the increment used by
132
+ * `fetchMore`. Defaults to 20.
133
+ *
134
+ * @example
135
+ * const { items, isLoading, hasMore, fetchMore, error } = useDataSource("default")
136
+ */
137
+ export declare function useDataSource(key: string, initialLimit?: number): UseDataSourceResult;
138
+ /**
139
+ * Measures the sandbox's `#root` element and posts `resize` messages so the host iframe
140
+ * matches the block's content height. Uses `Math.ceil(scrollHeight)` and dedupes
141
+ * unchanged values.
142
+ *
143
+ * `<NotionCustomBlock>` calls this hook for you by default — only reach for it directly
144
+ * when you need to drive `enabled` yourself (e.g. behind a debug toggle). In that case,
145
+ * pass `autoResize={false}` to the provider to avoid running it twice. For full-bleed
146
+ * views that should fill their slot, pass `autoResize={false}` and skip the hook.
147
+ *
148
+ * @param args.enabled - When `false`, suspends measurement. Useful for conditional
149
+ * measurement (e.g. while a debug toggle is off). Defaults to `true`.
150
+ *
151
+ * @example
152
+ * <NotionCustomBlock autoResize={false}>
153
+ * <App />
154
+ * </NotionCustomBlock>
155
+ *
156
+ * function App() {
157
+ * const [enabled, setEnabled] = useState(true)
158
+ * useCustomBlockAutoResize({ enabled })
159
+ * return <div>…</div>
160
+ * }
161
+ */
162
+ export declare function useCustomBlockAutoResize(args?: {
163
+ /**
164
+ * Whether or not the hook is enabled. To disable this behavior, pass `false`. This is
165
+ * provided as an argument to allow for conditional disabling of the hook.
166
+ *
167
+ * @default true
168
+ */
169
+ enabled?: boolean;
170
+ }): void;
171
+ //# sourceMappingURL=react.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../react.tsx"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,SAAS,EAKd,MAAM,OAAO,CAAA;AACd,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAA;AAChE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAA;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AACjD,OAAO,EAEN,KAAK,kBAAkB,EAGvB,KAAK,sBAAsB,EAO3B,MAAM,UAAU,CAAA;AACjB,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAA;AAsBlD;;;;;;;;;GASG;AACH,MAAM,MAAM,wBAAwB,GACjC;IAAE,QAAQ,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,SAAS,CAAA;CAAE,GACrC;IAAE,QAAQ,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,KAAK,CAAA;CAAE,GACjC;IAAE,QAAQ,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,kBAAkB,CAAA;CAAE,CAAA;AAEpE;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CACjC,IAAI,CAAC,EAAE,sBAAsB,GAC3B,wBAAwB,CA8B1B;AAED;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG,sBAAsB,GAAG;IAC7D,QAAQ,EAAE,SAAS,CAAA;IACnB;;;OAGG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB;;;;OAIG;IACH,aAAa,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,SAAS,CAAC,CAAA;IACzD;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;CACpB,CAAA;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,iBAAiB,CAAC,EACjC,QAAQ,EACR,SAAS,EACT,QAAe,EACf,aAAa,EACb,UAAiB,GACjB,EAAE,sBAAsB,2CAyDxB;AAYD;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,IAAI,wBAAwB,CAIhE;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,IAAI,WAAW,CAItC;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,IAAI,gBAAgB,EAAE,CAI7D;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAC5B,GAAG,EAAE,MAAM,EACX,YAAY,GAAE,MAAwC,GACpD,mBAAmB,CAiDrB;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,wBAAwB,CACvC,IAAI,GAAE;IACL;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CACZ,GACJ,IAAI,CAmCN"}
package/dist/react.js ADDED
@@ -0,0 +1,284 @@
1
+ import { jsxs as _jsxs, Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useState, useSyncExternalStore, } from "react";
3
+ import { getCustomBlockHostState, getDataSourceQueryView, initCustomBlock, NotInIframeError, postCustomBlockResize, queryCustomBlockDataSource, setMockCustomBlockState, subscribeToCustomBlockHost, } from "./notion";
4
+ const DEFAULT_DATA_SOURCE_QUERY_LIMIT = 20;
5
+ function useCustomBlockHost() {
6
+ return useSyncExternalStore(subscribeToCustomBlockHost, getCustomBlockHostState);
7
+ }
8
+ function assertInitialized(host, hookName) {
9
+ if (host.status !== "initialized") {
10
+ throw new Error(`${hookName} called before \`initCustomBlock\` resolved. Await it before mounting your tree.`);
11
+ }
12
+ }
13
+ /**
14
+ * React wrapper around {@link initCustomBlock}. Kicks off the SDK ↔ host
15
+ * handshake on mount and returns a discriminated state object so the rest of
16
+ * the tree can render inside the `isLoaded === true` branch (where every
17
+ * other SDK hook is guaranteed to return a populated value).
18
+ *
19
+ * Idempotent — multiple components can call this; they share the same
20
+ * underlying handshake promise.
21
+ *
22
+ * @example
23
+ * function Root() {
24
+ * const init = useCustomBlockInit()
25
+ * if (init.error) return <p role="alert">Init failed: {init.error.message}</p>
26
+ * if (!init.isLoaded) return null
27
+ * return <App />
28
+ * }
29
+ */
30
+ export function useCustomBlockInit(opts) {
31
+ const [state, setState] = useState({
32
+ isLoaded: false,
33
+ error: undefined,
34
+ });
35
+ useEffect(() => {
36
+ let cancelled = false;
37
+ initCustomBlock(opts).then(initial => {
38
+ if (!cancelled) {
39
+ setState({ isLoaded: true, error: undefined, initial });
40
+ }
41
+ }, err => {
42
+ if (!cancelled) {
43
+ setState({
44
+ isLoaded: false,
45
+ error: err instanceof Error ? err : new Error(String(err)),
46
+ });
47
+ }
48
+ });
49
+ return () => {
50
+ cancelled = true;
51
+ };
52
+ // `initCustomBlock` caches its result, so options after the first call
53
+ // are ignored — re-running on opts changes would be misleading.
54
+ // eslint-disable-next-line react-hooks/exhaustive-deps
55
+ }, []);
56
+ return state;
57
+ }
58
+ /**
59
+ * Top-level wrapper that runs the SDK ↔ host handshake and gates `children`
60
+ * until it resolves. Passes `timeoutMs` straight through to
61
+ * {@link initCustomBlock}.
62
+ *
63
+ * Templates that prefer not to write a `Root` gating component (or top-level
64
+ * `await`) can mount their app entirely inside this provider:
65
+ *
66
+ * ```tsx
67
+ * ReactDOM.createRoot(root).render(
68
+ * <NotionCustomBlock>
69
+ * <App />
70
+ * </NotionCustomBlock>,
71
+ * )
72
+ * ```
73
+ *
74
+ * Inside `children`, every SDK hook is guaranteed to return a populated
75
+ * value. Outside the provider (or during the loading window), they throw.
76
+ */
77
+ export function NotionCustomBlock({ children, timeoutMs, fallback = null, errorFallback, autoResize = true, }) {
78
+ const init = useCustomBlockInit({ timeoutMs });
79
+ useCustomBlockAutoResize({ enabled: autoResize });
80
+ const isStandalone = init.error instanceof NotInIframeError;
81
+ const host = useCustomBlockHost();
82
+ useEffect(() => {
83
+ if (!isStandalone) {
84
+ return;
85
+ }
86
+ console.warn(`[notion-custom-sdk] ${init.error?.message}`);
87
+ setMockCustomBlockState({
88
+ type: "init",
89
+ theme: "light",
90
+ context: {
91
+ customBlockId: "",
92
+ parent: { id: "", type: "" },
93
+ page: { id: "" },
94
+ },
95
+ dataSources: { bindings: {} },
96
+ });
97
+ }, [isStandalone, init.error]);
98
+ if (init.error && !isStandalone) {
99
+ if (errorFallback === undefined) {
100
+ return (_jsxs("p", { role: "alert", children: ["Notion custom view init failed: ", init.error.message] }));
101
+ }
102
+ return (_jsx(_Fragment, { children: typeof errorFallback === "function"
103
+ ? errorFallback(init.error)
104
+ : errorFallback }));
105
+ }
106
+ if (isStandalone) {
107
+ // Wait for the effect to seed placeholder host state; otherwise hooks
108
+ // in `children` would throw.
109
+ if (host.status !== "initialized") {
110
+ return _jsx(_Fragment, { children: fallback });
111
+ }
112
+ return (_jsxs(_Fragment, { children: [_jsx("div", { role: "status", style: STANDALONE_BANNER_STYLE, children: "Notion host not detected \u2014 running in standalone preview. SDK hooks return placeholder values until embedded in Notion." }), children] }));
113
+ }
114
+ if (!init.isLoaded) {
115
+ return _jsx(_Fragment, { children: fallback });
116
+ }
117
+ return _jsx(_Fragment, { children: children });
118
+ }
119
+ const STANDALONE_BANNER_STYLE = {
120
+ padding: "8px 12px",
121
+ background: "#fff8e1",
122
+ color: "#5d4200",
123
+ borderBottom: "1px solid #f0d77b",
124
+ fontSize: 13,
125
+ fontFamily: "system-ui, sans-serif",
126
+ lineHeight: 1.4,
127
+ };
128
+ /**
129
+ * Returns the block's location in the document tree. Re-renders when the host sends
130
+ * `contextChanged` (e.g. the block moves into a different container or its enclosing
131
+ * page changes).
132
+ *
133
+ * Throws if called before `initCustomBlock` has resolved — `await` it before mounting.
134
+ *
135
+ * @example
136
+ * const ctx = useCustomBlockContext()
137
+ * console.log(ctx.customBlockId, ctx.parent.type)
138
+ */
139
+ export function useCustomBlockContext() {
140
+ const host = useCustomBlockHost();
141
+ assertInitialized(host, "useCustomBlockContext");
142
+ return host.context;
143
+ }
144
+ /**
145
+ * Returns the host's current theme. Re-renders on every `themeChanged` message from the host.
146
+ *
147
+ * Throws if called before `initCustomBlock` has resolved — `await` it before mounting.
148
+ *
149
+ * @example
150
+ * const theme = useTheme()
151
+ */
152
+ export function useTheme() {
153
+ const host = useCustomBlockHost();
154
+ assertInitialized(host, "useTheme");
155
+ return host.theme;
156
+ }
157
+ /**
158
+ * Returns the raw data-source definitions — semantic keys plus optional `collectionPointer`,
159
+ * `collectionSchema`, `propertyIdsByKey`, and derived `propertySchemasById`. Most templates should use
160
+ * `useDataSource(key)` instead — this hook is for views that render the configuration
161
+ * itself (debug panels, schema-driven UIs).
162
+ *
163
+ * Throws if called before `initCustomBlock` has resolved.
164
+ */
165
+ export function useDataSourceDefinitions() {
166
+ const host = useCustomBlockHost();
167
+ assertInitialized(host, "useDataSourceDefinitions");
168
+ return host.dataSources;
169
+ }
170
+ /**
171
+ * Reads from the data source mapped to the given semantic `key`.
172
+ *
173
+ * The first render kicks off a query for the first `initialLimit` items. Calling
174
+ * `fetchMore()` re-requests a larger prefix (the bridge does not expose cursors yet);
175
+ * page growth tracks `initialLimit`. The hook automatically resets to `initialLimit`
176
+ * when the underlying data source definition changes.
177
+ *
178
+ * @param key - The semantic data-source key the block is wired to (e.g. `"people"`).
179
+ * @param initialLimit - Page size for the first query, and the increment used by
180
+ * `fetchMore`. Defaults to 20.
181
+ *
182
+ * @example
183
+ * const { items, isLoading, hasMore, fetchMore, error } = useDataSource("default")
184
+ */
185
+ export function useDataSource(key, initialLimit = DEFAULT_DATA_SOURCE_QUERY_LIMIT) {
186
+ const host = useCustomBlockHost();
187
+ const [limit, setLimit] = useState(initialLimit);
188
+ const matchingDataSource = host.status === "initialized"
189
+ ? host.dataSources.find(dataSource => dataSource.key === key)
190
+ : undefined;
191
+ // Serialize to avoid re-querying when `dataSourcesChanged` rebuilds the array with equal entries.
192
+ const matchingSignature = matchingDataSource
193
+ ? JSON.stringify(matchingDataSource)
194
+ : null;
195
+ useEffect(() => {
196
+ // A new key or source definition means we should restart from the initial page size.
197
+ setLimit(initialLimit);
198
+ }, [matchingSignature, key, initialLimit]);
199
+ const isInitialized = host.status === "initialized";
200
+ useEffect(() => {
201
+ if (!isInitialized) {
202
+ return;
203
+ }
204
+ queryCustomBlockDataSource(key, limit);
205
+ }, [matchingSignature, isInitialized, key, limit]);
206
+ const view = getDataSourceQueryView(host, key);
207
+ const fetchMore = useCallback(() => {
208
+ if (!view.hasMore || view.isLoading) {
209
+ return;
210
+ }
211
+ // The bridge does not expose cursors yet, so "fetch more" means re-requesting a
212
+ // larger prefix of items and letting the host return the expanded result set.
213
+ // Page growth tracks the caller-provided `initialLimit` so templates can opt
214
+ // into larger batches.
215
+ setLimit(currentLimit => currentLimit + initialLimit);
216
+ }, [view.hasMore, view.isLoading, initialLimit]);
217
+ return {
218
+ items: view.items,
219
+ collectionSchema: view.collectionSchema,
220
+ propertySchemasById: view.propertySchemasById,
221
+ propertySchemasByKey: view.propertySchemasByKey,
222
+ isLoading: view.isLoading,
223
+ hasMore: view.hasMore,
224
+ fetchMore,
225
+ error: view.error,
226
+ };
227
+ }
228
+ /**
229
+ * Measures the sandbox's `#root` element and posts `resize` messages so the host iframe
230
+ * matches the block's content height. Uses `Math.ceil(scrollHeight)` and dedupes
231
+ * unchanged values.
232
+ *
233
+ * `<NotionCustomBlock>` calls this hook for you by default — only reach for it directly
234
+ * when you need to drive `enabled` yourself (e.g. behind a debug toggle). In that case,
235
+ * pass `autoResize={false}` to the provider to avoid running it twice. For full-bleed
236
+ * views that should fill their slot, pass `autoResize={false}` and skip the hook.
237
+ *
238
+ * @param args.enabled - When `false`, suspends measurement. Useful for conditional
239
+ * measurement (e.g. while a debug toggle is off). Defaults to `true`.
240
+ *
241
+ * @example
242
+ * <NotionCustomBlock autoResize={false}>
243
+ * <App />
244
+ * </NotionCustomBlock>
245
+ *
246
+ * function App() {
247
+ * const [enabled, setEnabled] = useState(true)
248
+ * useCustomBlockAutoResize({ enabled })
249
+ * return <div>…</div>
250
+ * }
251
+ */
252
+ export function useCustomBlockAutoResize(args = {}) {
253
+ const { enabled = true } = args;
254
+ useEffect(() => {
255
+ if (!enabled) {
256
+ return;
257
+ }
258
+ if (typeof window === "undefined") {
259
+ return;
260
+ }
261
+ const target = document.getElementById("root");
262
+ if (!(target instanceof HTMLElement)) {
263
+ return;
264
+ }
265
+ let lastHeight = -1;
266
+ const post = () => {
267
+ const next = Math.ceil(target.scrollHeight);
268
+ if (next === lastHeight) {
269
+ return;
270
+ }
271
+ lastHeight = next;
272
+ postCustomBlockResize(next);
273
+ };
274
+ post();
275
+ if (typeof ResizeObserver === "undefined") {
276
+ return;
277
+ }
278
+ const observer = new ResizeObserver(post);
279
+ observer.observe(target);
280
+ return () => {
281
+ observer.disconnect();
282
+ };
283
+ }, [enabled]);
284
+ }
@@ -0,0 +1,124 @@
1
+ import type { NotionCollectionSchema } from "./bridge/dataSources/dataSource";
2
+ import type { NotionDataSourcePage, NotionDataSourcePageUpdateInput, NotionDataSourcePageUpdateResult } from "./bridge/dataSources/dataSourcePage";
3
+ import type { NotionPropertySchema } from "./bridge/dataSources/propertySchema";
4
+ import type { NotionDataSourceId } from "./bridge/ids";
5
+ import type { NotionCreatePagePosition } from "./bridge/messages/createPage";
6
+ import type { NotionPage, NotionPageCover, NotionPageIcon, NotionPageId, NotionPagePropertyInputMap, NotionPagePropertyWriteMap } from "./bridge/pages/page";
7
+ export type { NotionDataSourceId, NotionSpaceId } from "./bridge/ids";
8
+ export type { NotionPageId } from "./bridge/pages/page";
9
+ export type { NotionDataSourcePageUpdateInput, NotionDataSourcePageUpdateResult, };
10
+ /**
11
+ * Return shape of `useDataSource`.
12
+ *
13
+ * - `items` — the rows the host has returned so far. Empty until the first response arrives.
14
+ * - `isLoading` — `true` while a query is in flight.
15
+ * - `hasMore` — `true` if the host indicated more rows are available beyond the current page.
16
+ * - `fetchMore` — re-requests a larger prefix of items (no-op while loading or when
17
+ * `hasMore` is `false`). The page grows by `initialLimit` per call.
18
+ * - `error` — a human-readable error string if the most recent query failed.
19
+ */
20
+ export type UseDataSourceResult = {
21
+ items: NotionDataSourcePage[];
22
+ /**
23
+ * Collection/data-source schema for the bound Notion data source, including raw property
24
+ * schemas. Undefined when the semantic data-source key has not been bound.
25
+ */
26
+ collectionSchema?: NotionCollectionSchema;
27
+ /**
28
+ * Per-property schemas keyed by raw property ID, including the four synthetic built-ins
29
+ * (`created_time`, `last_edited_time`, `created_by`, `last_edited_by`).
30
+ */
31
+ propertySchemasById: {
32
+ [propertyId: string]: NotionPropertySchema;
33
+ };
34
+ /**
35
+ * Per-property schemas keyed by user-defined keys from the data source's `propertyIdsByKey`.
36
+ * Built-ins are NOT included here. Keys mapped to `undefined` indicate a declared-but-unbound
37
+ * slot.
38
+ */
39
+ propertySchemasByKey: {
40
+ [key: string]: NotionPropertySchema | undefined;
41
+ };
42
+ isLoading: boolean;
43
+ hasMore: boolean;
44
+ fetchMore: () => void;
45
+ error?: string;
46
+ };
47
+ /**
48
+ * Parent reference accepted by `sdk.pages.create`. Mirrors Notion's public `POST /v1/pages`
49
+ * parent shape; see https://developers.notion.com/reference/data-source.
50
+ *
51
+ * - `page_id`: Create a child page under the given Notion page.
52
+ * - `data_source_id`: Create a row inside the given Notion data source (aka the internal
53
+ * collection). The public API's `data_source_id` is the same UUID as the `collectionPointer.id`
54
+ * exposed in `dataSources`, so either value works here.
55
+ * - `data_source_key`: Create a row inside the data source that the custom block was configured with
56
+ * under this semantic key. The SDK resolves the key to a `data_source_id` locally before
57
+ * sending the request to the host.
58
+ */
59
+ export type CreatePageParent = {
60
+ type: "page_id";
61
+ page_id: NotionPageId;
62
+ } | {
63
+ type: "data_source_id";
64
+ data_source_id: NotionDataSourceId;
65
+ } | {
66
+ type: "data_source_key";
67
+ key: string;
68
+ };
69
+ /**
70
+ * Input accepted by `sdk.pages.create`. Mirrors the Notion public API `POST /v1/pages` API.
71
+ */
72
+ export type CreatePageInput = {
73
+ parent: CreatePageParent;
74
+ properties: NotionPagePropertyInputMap;
75
+ icon?: NotionPageIcon;
76
+ cover?: NotionPageCover;
77
+ position?: NotionCreatePagePosition;
78
+ };
79
+ /**
80
+ * The result of a `sdk.pages.create` API call.
81
+ */
82
+ export type CreatePageResult = {
83
+ status: "success";
84
+ /** The newly created page. */
85
+ page: NotionPage;
86
+ } | {
87
+ status: "error";
88
+ error: string;
89
+ };
90
+ /**
91
+ * Input accepted by `sdk.pages.update`.
92
+ *
93
+ * `properties` is keyed by raw Notion property ID. To update a row with configured
94
+ * custom-block property keys, use the `update` helper on pages returned from
95
+ * `useDataSource`.
96
+ */
97
+ export type UpdatePageInput = {
98
+ pageId: NotionPageId;
99
+ properties?: NotionPagePropertyWriteMap;
100
+ icon?: NotionPageIcon;
101
+ cover?: NotionPageCover;
102
+ archived?: boolean;
103
+ };
104
+ /**
105
+ * Result of `sdk.pages.get`.
106
+ */
107
+ export type GetPageResult = {
108
+ status: "success";
109
+ page: NotionPage;
110
+ } | {
111
+ status: "error";
112
+ error: string;
113
+ };
114
+ /**
115
+ * Result of `sdk.pages.update` / `sdk.pages.delete`.
116
+ */
117
+ export type UpdatePageResult = {
118
+ status: "success";
119
+ page: NotionPage;
120
+ } | {
121
+ status: "error";
122
+ error: string;
123
+ };
124
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Internal helpers shared across the SDK.
3
+ */
4
+ /**
5
+ * Throws an error when an unexpected value is encountered. This is used to ensure all code paths
6
+ * are covered when using discriminated unions.
7
+ */
8
+ export declare function unreachable(value: never): never;
9
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,KAAK,CAI/C"}
package/dist/utils.js ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Internal helpers shared across the SDK.
3
+ */
4
+ /**
5
+ * Throws an error when an unexpected value is encountered. This is used to ensure all code paths
6
+ * are covered when using discriminated unions.
7
+ */
8
+ export function unreachable(value) {
9
+ throw new Error(`[notion-custom-sdk] Unexpected value encountered: ${JSON.stringify(value)}`);
10
+ }