datocms-plugin-sdk 2.0.13 → 2.0.16

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 (50) hide show
  1. package/dist/cjs/connect.js +4 -0
  2. package/dist/cjs/connect.js.map +1 -1
  3. package/dist/cjs/hooks/mainNavigationTabs.js +3 -1
  4. package/dist/cjs/hooks/mainNavigationTabs.js.map +1 -1
  5. package/dist/cjs/hooks/renderInspector.js +11 -0
  6. package/dist/cjs/hooks/renderInspector.js.map +1 -0
  7. package/dist/cjs/hooks/renderInspectorPanel.js +11 -0
  8. package/dist/cjs/hooks/renderInspectorPanel.js.map +1 -0
  9. package/dist/cjs/icon.js.map +1 -1
  10. package/dist/cjs/index.js +2 -0
  11. package/dist/cjs/index.js.map +1 -1
  12. package/dist/cjs/manifest.js +127 -3
  13. package/dist/cjs/manifest.js.map +1 -1
  14. package/dist/esm/connect.d.ts +3 -1
  15. package/dist/esm/connect.js +4 -0
  16. package/dist/esm/connect.js.map +1 -1
  17. package/dist/esm/hooks/mainNavigationTabs.d.ts +4 -0
  18. package/dist/esm/hooks/mainNavigationTabs.js +3 -1
  19. package/dist/esm/hooks/mainNavigationTabs.js.map +1 -1
  20. package/dist/esm/hooks/onBeforeItemUpsert.d.ts +9 -2
  21. package/dist/esm/hooks/renderInspector.d.ts +145 -0
  22. package/dist/esm/hooks/renderInspector.js +8 -0
  23. package/dist/esm/hooks/renderInspector.js.map +1 -0
  24. package/dist/esm/hooks/renderInspectorPanel.d.ts +20 -0
  25. package/dist/esm/hooks/renderInspectorPanel.js +8 -0
  26. package/dist/esm/hooks/renderInspectorPanel.js.map +1 -0
  27. package/dist/esm/icon.d.ts +39 -1
  28. package/dist/esm/icon.js.map +1 -1
  29. package/dist/esm/index.d.ts +2 -0
  30. package/dist/esm/index.js +2 -0
  31. package/dist/esm/index.js.map +1 -1
  32. package/dist/esm/manifest.js +127 -3
  33. package/dist/esm/manifest.js.map +1 -1
  34. package/dist/types/connect.d.ts +3 -1
  35. package/dist/types/hooks/mainNavigationTabs.d.ts +4 -0
  36. package/dist/types/hooks/onBeforeItemUpsert.d.ts +9 -2
  37. package/dist/types/hooks/renderInspector.d.ts +145 -0
  38. package/dist/types/hooks/renderInspectorPanel.d.ts +20 -0
  39. package/dist/types/icon.d.ts +39 -1
  40. package/dist/types/index.d.ts +2 -0
  41. package/manifest.json +127 -3
  42. package/package.json +2 -2
  43. package/src/connect.ts +12 -0
  44. package/src/hooks/mainNavigationTabs.ts +12 -4
  45. package/src/hooks/onBeforeItemUpsert.ts +9 -2
  46. package/src/hooks/renderInspector.ts +173 -0
  47. package/src/hooks/renderInspectorPanel.ts +38 -0
  48. package/src/icon.ts +46 -3
  49. package/src/index.ts +2 -0
  50. package/src/manifest.ts +137 -3
@@ -0,0 +1,173 @@
1
+ import { ImposedSizePluginFrameCtx } from '../ctx/pluginFrame';
2
+ import { fullScreenRenderModeBootstrapper } from '../utils';
3
+
4
+ /**
5
+ * Defines the different modes in which an inspector can be displayed
6
+ */
7
+ export type InspectorMode =
8
+ | {
9
+ /** Display a list of records in the inspector */
10
+ type: 'itemList';
11
+ }
12
+ | {
13
+ /** Display a single record editor in the inspector */
14
+ type: 'itemEditor';
15
+ /** The ID of the record to edit */
16
+ itemId: string;
17
+ /** Optional field path to highlight/focus within the record editor */
18
+ fieldPath?: string;
19
+ }
20
+ | {
21
+ /** Display a custom panel in the inspector */
22
+ type: 'customPanel';
23
+ /** ID of the inspector panel to render */
24
+ panelId: string;
25
+ /**
26
+ * An arbitrary configuration object that will be passed as the `parameters`
27
+ * property of the second argument of the `renderInspectorPanel` function
28
+ */
29
+ parameters?: Record<string, unknown>;
30
+ };
31
+
32
+ /**
33
+ * Options for configuring inspector mode changes
34
+ */
35
+ export type SetInspectorModeOptions = {
36
+ /**
37
+ * When true, the mode change will be ignored if there are unsaved changes
38
+ * in the current inspector. Useful for "low intent" mode changes that
39
+ * shouldn't interrupt active editing sessions.
40
+ * @default false
41
+ */
42
+ ignoreIfUnsavedChanges?: boolean;
43
+ };
44
+
45
+ export type RenderInspectorHook = {
46
+ /**
47
+ * This function will be called when the plugin needs to render a specific
48
+ * inspector. Inspectors provide a side panel interface for displaying and
49
+ * interacting with content alongside a custom interface.
50
+ *
51
+ * @tag inspector
52
+ *
53
+ * @example
54
+ *
55
+ * ```js
56
+ * connect({
57
+ * renderInspector(inspectorId, ctx) {
58
+ * render(
59
+ * <div>
60
+ * <h1>Inspector: {inspectorId}</h1>
61
+ * <button onClick={() => ctx.setInspectorMode({
62
+ * type: 'itemEditor',
63
+ * itemId: 'some-item-id'
64
+ * })}>
65
+ * Show Item Editor
66
+ * </button>
67
+ * </div>
68
+ * );
69
+ * }
70
+ * });
71
+ * ```
72
+ */
73
+ renderInspector: (inspectorId: string, ctx: RenderInspectorCtx) => void;
74
+ };
75
+
76
+ export type RenderInspectorCtx = ImposedSizePluginFrameCtx<
77
+ 'renderInspector',
78
+ {
79
+ /** The ID of the inspector that needs to be rendered */
80
+ inspectorId: string;
81
+
82
+ /** Current page location */
83
+ location: {
84
+ pathname: string;
85
+ search: string;
86
+ hash: string;
87
+ };
88
+ },
89
+ {
90
+ /**
91
+ * Changes the current display mode of the inspector. This allows the plugin
92
+ * to dynamically switch between showing a record list, record editor, or custom
93
+ * panel within the inspector interface.
94
+ *
95
+ * @param mode - The inspector mode to switch to
96
+ * @param options - Optional configuration for the mode change
97
+ * @param options.ignoreIfUnsavedChanges - When true, the mode change request will be
98
+ * ignored if the current inspector is in itemEditor mode and has unsaved changes.
99
+ * This allows for "low intent" mode changes that shouldn't interrupt active editing.
100
+ * Default is false, meaning mode changes will proceed regardless of unsaved changes.
101
+ *
102
+ * @example
103
+ *
104
+ * ```js
105
+ * // Switch to record editor mode
106
+ * await ctx.setInspectorMode({
107
+ * type: 'itemEditor',
108
+ * itemId: 'item-123',
109
+ * fieldPath: 'title'
110
+ * });
111
+ *
112
+ * // Switch to record list mode
113
+ * await ctx.setInspectorMode({ type: 'itemList' });
114
+ * await ctx.setInspectorItemListData({
115
+ * title: 'Related Records',
116
+ * itemIds: ['item-1', 'item-2', 'item-3']
117
+ * });
118
+ *
119
+ * // Switch to custom panel mode
120
+ * await ctx.setInspectorMode({
121
+ * type: 'customPanel',
122
+ * panelId: 'my-custom-panel',
123
+ * parameters: { filter: 'active' }
124
+ * });
125
+ *
126
+ * // Low intent mode change - won't interrupt editing with unsaved changes
127
+ * await ctx.setInspectorMode(
128
+ * { type: 'itemList' },
129
+ * { ignoreIfUnsavedChanges: true }
130
+ * );
131
+ * ```
132
+ */
133
+ setInspectorMode: (
134
+ mode: InspectorMode,
135
+ options?: SetInspectorModeOptions,
136
+ ) => Promise<void>;
137
+
138
+ /**
139
+ * Sets the data for the item list inspector mode.
140
+ *
141
+ * @example
142
+ *
143
+ * ```js
144
+ * // Set the item list data
145
+ * await ctx.setInspectorItemListData({
146
+ * title: 'Related Records',
147
+ * itemIds: ['item-1', 'item-2', 'item-3']
148
+ * });
149
+ *
150
+ * // Switch to item list mode
151
+ * await ctx.setInspectorMode({ type: 'itemList' });
152
+ * ```
153
+ */
154
+ setInspectorItemListData: (data: {
155
+ /** The title to show in the inspector header */
156
+ title: string;
157
+ /** Array of record IDs to display in the list */
158
+ itemIds: string[];
159
+ }) => Promise<void>;
160
+ }
161
+ >;
162
+
163
+ export const renderInspectorBootstrapper =
164
+ fullScreenRenderModeBootstrapper<RenderInspectorCtx>(
165
+ 'renderInspector',
166
+ (configuration, ctx) => {
167
+ if (!configuration.renderInspector) {
168
+ return;
169
+ }
170
+
171
+ configuration.renderInspector(ctx.inspectorId, ctx);
172
+ },
173
+ );
@@ -0,0 +1,38 @@
1
+ import { ImposedSizePluginFrameCtx } from '../ctx/pluginFrame';
2
+ import { fullScreenRenderModeBootstrapper } from '../utils';
3
+
4
+ export type RenderInspectorPanelHook = {
5
+ /**
6
+ * This function will be called when an inspector needs to render a specific
7
+ * panel (see the `renderInspector` and `setInspectorMode` functions)
8
+ *
9
+ * @tag inspector
10
+ */
11
+ renderInspectorPanel: (panelId: string, ctx: RenderInspectorPanelCtx) => void;
12
+ };
13
+
14
+ export type RenderInspectorPanelCtx = ImposedSizePluginFrameCtx<
15
+ 'renderInspectorPanel',
16
+ {
17
+ /** The ID of the inspector panel that needs to be rendered */
18
+ panelId: string;
19
+
20
+ /**
21
+ * The arbitrary `parameters` of the modal declared in the `setInspectorMode`
22
+ * function
23
+ */
24
+ parameters: Record<string, unknown>;
25
+ }
26
+ >;
27
+
28
+ export const renderInspectorPanelBootstrapper =
29
+ fullScreenRenderModeBootstrapper<RenderInspectorPanelCtx>(
30
+ 'renderInspectorPanel',
31
+ (configuration, ctx) => {
32
+ if (!configuration.renderInspectorPanel) {
33
+ return;
34
+ }
35
+
36
+ configuration.renderInspectorPanel(ctx.panelId, ctx);
37
+ },
38
+ );
package/src/icon.ts CHANGED
@@ -1,8 +1,44 @@
1
1
  import { isRecord, isString } from './guardUtils.js';
2
2
 
3
- export type Icon =
4
- | AwesomeFontIconIdentifier
5
- | { type: 'svg'; viewBox: string; content: string };
3
+ export type Icon = AwesomeFontIconIdentifier | SvgDefinition;
4
+
5
+ /**
6
+ * Defines a custom SVG icon for use in DatoCMS plugins.
7
+ *
8
+ * To create an SVG definition from an existing SVG file:
9
+ * 1. Grab the `viewBox` attribute from your SVG element (e.g., "0 0 24 24")
10
+ * 2. Grab everything between the `<svg>` tags as the content (all the paths, circles, etc.)
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * // From this SVG:
15
+ * // <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
16
+ * // <path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>
17
+ * // </svg>
18
+ *
19
+ * const starIcon: SvgDefinition = {
20
+ * type: 'svg',
21
+ * viewBox: '0 0 24 24',
22
+ * content: '<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>'
23
+ * };
24
+ * ```
25
+ */
26
+ export type SvgDefinition = {
27
+ /** Always set to 'svg' to indicate this is a custom SVG icon */
28
+ type: 'svg';
29
+
30
+ /**
31
+ * The viewBox attribute from your SVG element (e.g., "0 0 24 24").
32
+ * This defines the coordinate system and aspect ratio of the SVG.
33
+ */
34
+ viewBox: string;
35
+
36
+ /**
37
+ * The inner content of your SVG element — everything between the opening and closing <svg> tags.
38
+ * This includes all paths, circles, rectangles, and other SVG elements that make up your icon.
39
+ */
40
+ content: string;
41
+ };
6
42
 
7
43
  export function isIcon(value: unknown): value is Icon {
8
44
  return (
@@ -14,6 +50,13 @@ export function isIcon(value: unknown): value is Icon {
14
50
  );
15
51
  }
16
52
 
53
+ /**
54
+ * Font Awesome icon identifier for use in DatoCMS plugins.
55
+ *
56
+ * Use Font Awesome icons for consistent visual styling across the admin interface.
57
+ * This is the recommended approach for most plugin icons, with custom SVGs reserved
58
+ * primarily for brand/company logos where Font Awesome doesn't have an appropriate match.
59
+ */
17
60
  export type AwesomeFontIconIdentifier =
18
61
  | '0'
19
62
  | '00'
package/src/index.ts CHANGED
@@ -53,6 +53,8 @@ export * from './hooks/renderItemFormSidebarPanel';
53
53
  export * from './hooks/renderManualFieldExtensionConfigScreen';
54
54
  export * from './hooks/renderModal';
55
55
  export * from './hooks/renderPage';
56
+ export * from './hooks/renderInspector';
57
+ export * from './hooks/renderInspectorPanel';
56
58
  export * from './hooks/renderUploadSidebar';
57
59
  export * from './hooks/renderUploadSidebarPanel';
58
60
  export * from './hooks/schemaItemTypeDropdownActions';
package/src/manifest.ts CHANGED
@@ -1231,6 +1231,140 @@ export const manifest: Manifest = {
1231
1231
  lineNumber: 14,
1232
1232
  },
1233
1233
  },
1234
+ renderInspectorPanel: {
1235
+ name: 'renderInspectorPanel',
1236
+ comment: {
1237
+ markdownText:
1238
+ 'This function will be called when an inspector needs to render a specific\npanel (see the `renderInspector` and `setInspectorMode` functions).',
1239
+ tag: 'inspector',
1240
+ },
1241
+ nonCtxArguments: [
1242
+ {
1243
+ name: 'panelId',
1244
+ typeName: 'string',
1245
+ },
1246
+ ],
1247
+ ctxArgument: {
1248
+ type: 'ImposedSizePluginFrameCtx',
1249
+ additionalProperties: [
1250
+ {
1251
+ items: {
1252
+ panelId: {
1253
+ comment: {
1254
+ markdownText:
1255
+ 'The ID of the inspector panel that needs to be rendered.',
1256
+ },
1257
+ location: {
1258
+ filePath: 'src/hooks/renderInspectorPanel.ts',
1259
+ lineNumber: 18,
1260
+ },
1261
+ type: 'string',
1262
+ },
1263
+ parameters: {
1264
+ comment: {
1265
+ markdownText:
1266
+ 'The arbitrary `parameters` of the modal declared in the `setInspectorMode`\nfunction.',
1267
+ },
1268
+ location: {
1269
+ filePath: 'src/hooks/renderInspectorPanel.ts',
1270
+ lineNumber: 24,
1271
+ },
1272
+ type: 'Record<string, unknown>',
1273
+ },
1274
+ },
1275
+ },
1276
+ ],
1277
+ additionalMethods: [],
1278
+ },
1279
+ returnType: 'void',
1280
+ location: {
1281
+ filePath: 'src/hooks/renderInspectorPanel.ts',
1282
+ lineNumber: 11,
1283
+ },
1284
+ },
1285
+ renderInspector: {
1286
+ name: 'renderInspector',
1287
+ comment: {
1288
+ markdownText:
1289
+ 'This function will be called when the plugin needs to render a specific\ninspector. Inspectors provide a side panel interface for displaying and\ninteracting with content alongside a custom interface.',
1290
+ tag: 'inspector',
1291
+ example:
1292
+ "connect({\n renderInspector(inspectorId, ctx) {\n render(\n <div>\n <h1>Inspector: {inspectorId}</h1>\n <button onClick={() => ctx.setInspectorMode({\n type: 'itemEditor',\n itemId: 'some-item-id'\n })}>\n Show Item Editor\n </button>\n </div>\n );\n }\n});",
1293
+ },
1294
+ nonCtxArguments: [
1295
+ {
1296
+ name: 'inspectorId',
1297
+ typeName: 'string',
1298
+ },
1299
+ ],
1300
+ ctxArgument: {
1301
+ type: 'ImposedSizePluginFrameCtx',
1302
+ additionalProperties: [
1303
+ {
1304
+ items: {
1305
+ inspectorId: {
1306
+ comment: {
1307
+ markdownText:
1308
+ 'The ID of the inspector that needs to be rendered.',
1309
+ },
1310
+ location: {
1311
+ filePath: 'src/hooks/renderInspector.ts',
1312
+ lineNumber: 80,
1313
+ },
1314
+ type: 'string',
1315
+ },
1316
+ location: {
1317
+ comment: {
1318
+ markdownText: 'Current page location.',
1319
+ },
1320
+ location: {
1321
+ filePath: 'src/hooks/renderInspector.ts',
1322
+ lineNumber: 83,
1323
+ },
1324
+ type: '{\n pathname: string;\n search: string;\n hash: string;\n }',
1325
+ },
1326
+ },
1327
+ },
1328
+ ],
1329
+ additionalMethods: [
1330
+ {
1331
+ items: {
1332
+ setInspectorMode: {
1333
+ comment: {
1334
+ markdownText:
1335
+ 'Changes the current display mode of the inspector. This allows the plugin\nto dynamically switch between showing a record list, record editor, or custom\npanel within the inspector interface.',
1336
+ example:
1337
+ "// Switch to record editor mode\nawait ctx.setInspectorMode({\n type: 'itemEditor',\n itemId: 'item-123',\n fieldPath: 'title'\n});\n\n// Switch to record list mode\nawait ctx.setInspectorMode({ type: 'itemList' });\nawait ctx.setInspectorItemListData({\n title: 'Related Records',\n itemIds: ['item-1', 'item-2', 'item-3']\n});\n\n// Switch to custom panel mode\nawait ctx.setInspectorMode({\n type: 'customPanel',\n panelId: 'my-custom-panel',\n parameters: { filter: 'active' }\n});\n\n// Low intent mode change - won't interrupt editing with unsaved changes\nawait ctx.setInspectorMode(\n { type: 'itemList' },\n { ignoreIfUnsavedChanges: true }\n);",
1338
+ },
1339
+ location: {
1340
+ filePath: 'src/hooks/renderInspector.ts',
1341
+ lineNumber: 133,
1342
+ },
1343
+ type: '(\n mode: InspectorMode,\n options?: SetInspectorModeOptions,\n ) => Promise<void>',
1344
+ },
1345
+ setInspectorItemListData: {
1346
+ comment: {
1347
+ markdownText:
1348
+ 'Sets the data for the item list inspector mode.',
1349
+ example:
1350
+ "// Set the item list data\nawait ctx.setInspectorItemListData({\n title: 'Related Records',\n itemIds: ['item-1', 'item-2', 'item-3']\n});\n\n// Switch to item list mode\nawait ctx.setInspectorMode({ type: 'itemList' });",
1351
+ },
1352
+ location: {
1353
+ filePath: 'src/hooks/renderInspector.ts',
1354
+ lineNumber: 154,
1355
+ },
1356
+ type: '(data: {\n /** The title to show in the inspector header */\n title: string;\n /** Array of record IDs to display in the list */\n itemIds: string[];\n }) => Promise<void>',
1357
+ },
1358
+ },
1359
+ },
1360
+ ],
1361
+ },
1362
+ returnType: 'void',
1363
+ location: {
1364
+ filePath: 'src/hooks/renderInspector.ts',
1365
+ lineNumber: 73,
1366
+ },
1367
+ },
1234
1368
  renderFieldExtension: {
1235
1369
  name: 'renderFieldExtension',
1236
1370
  comment: {
@@ -1745,7 +1879,7 @@ export const manifest: Manifest = {
1745
1879
  name: 'onBeforeItemUpsert',
1746
1880
  comment: {
1747
1881
  markdownText:
1748
- 'This function will be called before saving a new version of a record. You\ncan stop the action by returning `false`.',
1882
+ 'This hook is called when the user attempts to save a record. You can use it to block record saving.\n\nIf you return `false`, the record will NOT be saved. A small on-page error will say "A plugin blocked the action".\nHowever, for better UX, consider also using `ctx.alert()` to better explain to the user why their save was blocked.\n\nIf you return `true`, the save will proceed as normal.\n\nThis hook runs BEFORE serverside validation. You can use it to do your own additional validation before returning.\nClientside validations are not affected by this hook, since those occur on individual fields\' `onBlur()` events.',
1749
1883
  tag: 'beforeHooks',
1750
1884
  },
1751
1885
  nonCtxArguments: [
@@ -1773,7 +1907,7 @@ export const manifest: Manifest = {
1773
1907
  },
1774
1908
  location: {
1775
1909
  filePath: 'src/hooks/onBeforeItemUpsert.ts',
1776
- lineNumber: 40,
1910
+ lineNumber: 47,
1777
1911
  },
1778
1912
  type: '(path: string, locale?: string) => Promise<void>',
1779
1913
  },
@@ -1784,7 +1918,7 @@ export const manifest: Manifest = {
1784
1918
  returnType: 'MaybePromise<boolean>',
1785
1919
  location: {
1786
1920
  filePath: 'src/hooks/onBeforeItemUpsert.ts',
1787
- lineNumber: 15,
1921
+ lineNumber: 22,
1788
1922
  },
1789
1923
  },
1790
1924
  manualFieldExtensions: {