@dxos/plugin-deck 0.6.13 → 0.6.14-main.69511f5

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 (53) hide show
  1. package/dist/lib/browser/{chunk-YVHGFQQR.mjs → chunk-GVOGPULO.mjs} +1 -1
  2. package/dist/lib/browser/chunk-GVOGPULO.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +340 -288
  4. package/dist/lib/browser/index.mjs.map +3 -3
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/meta.mjs +1 -1
  7. package/dist/types/src/DeckPlugin.d.ts.map +1 -1
  8. package/dist/types/src/components/DeckLayout/ActiveNode.d.ts +1 -3
  9. package/dist/types/src/components/DeckLayout/ActiveNode.d.ts.map +1 -1
  10. package/dist/types/src/components/DeckLayout/ComplementarySidebar.d.ts +2 -4
  11. package/dist/types/src/components/DeckLayout/ComplementarySidebar.d.ts.map +1 -1
  12. package/dist/types/src/components/DeckLayout/DeckLayout.d.ts +3 -4
  13. package/dist/types/src/components/DeckLayout/DeckLayout.d.ts.map +1 -1
  14. package/dist/types/src/components/DeckLayout/NodePlankHeading.d.ts +9 -7
  15. package/dist/types/src/components/DeckLayout/NodePlankHeading.d.ts.map +1 -1
  16. package/dist/types/src/components/DeckLayout/Plank.d.ts +1 -1
  17. package/dist/types/src/components/DeckLayout/Plank.d.ts.map +1 -1
  18. package/dist/types/src/components/DeckLayout/PlankError.d.ts +1 -2
  19. package/dist/types/src/components/DeckLayout/PlankError.d.ts.map +1 -1
  20. package/dist/types/src/components/DeckLayout/Sidebar.d.ts +2 -3
  21. package/dist/types/src/components/DeckLayout/Sidebar.d.ts.map +1 -1
  22. package/dist/types/src/components/DeckLayout/StatusBar.d.ts +3 -1
  23. package/dist/types/src/components/DeckLayout/StatusBar.d.ts.map +1 -1
  24. package/dist/types/src/components/DeckLayout/Toast.d.ts.map +1 -1
  25. package/dist/types/src/components/LayoutSettings.d.ts.map +1 -1
  26. package/dist/types/src/hooks/useNode.d.ts.map +1 -1
  27. package/dist/types/src/layout.d.ts.map +1 -1
  28. package/dist/types/src/meta.d.ts.map +1 -1
  29. package/dist/types/src/translations.d.ts +5 -3
  30. package/dist/types/src/translations.d.ts.map +1 -1
  31. package/dist/types/src/types.d.ts +1 -1
  32. package/dist/types/src/types.d.ts.map +1 -1
  33. package/dist/types/src/util/overscroll.d.ts +1 -1
  34. package/dist/types/src/util/overscroll.d.ts.map +1 -1
  35. package/package.json +35 -33
  36. package/src/DeckPlugin.tsx +89 -79
  37. package/src/components/DeckLayout/ActiveNode.tsx +4 -1
  38. package/src/components/DeckLayout/ComplementarySidebar.tsx +72 -28
  39. package/src/components/DeckLayout/DeckLayout.tsx +64 -96
  40. package/src/components/DeckLayout/NodePlankHeading.tsx +130 -127
  41. package/src/components/DeckLayout/Plank.tsx +31 -22
  42. package/src/components/DeckLayout/PlankError.tsx +1 -9
  43. package/src/components/DeckLayout/Sidebar.tsx +7 -8
  44. package/src/components/DeckLayout/StatusBar.tsx +12 -3
  45. package/src/components/DeckLayout/Toast.tsx +3 -3
  46. package/src/components/LayoutSettings.tsx +17 -20
  47. package/src/hooks/useNode.ts +5 -1
  48. package/src/layout.ts +1 -0
  49. package/src/meta.ts +3 -1
  50. package/src/translations.ts +7 -3
  51. package/src/types.ts +1 -1
  52. package/src/util/overscroll.ts +5 -5
  53. package/dist/lib/browser/chunk-YVHGFQQR.mjs.map +0 -7
@@ -2,8 +2,7 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import { ArrowsOut, type IconProps } from '@phosphor-icons/react';
6
- import { batch, effect } from '@preact/signals-core';
5
+ import { batch } from '@preact/signals-core';
7
6
  import { setAutoFreeze } from 'immer';
8
7
  import React, { type PropsWithChildren } from 'react';
9
8
 
@@ -33,6 +32,7 @@ import {
33
32
  } from '@dxos/app-framework';
34
33
  import { type UnsubscribeCallback } from '@dxos/async';
35
34
  import { create, getTypename, isReactiveObject } from '@dxos/echo-schema';
35
+ import { scheduledEffect } from '@dxos/echo-signals/core';
36
36
  import { LocalStorageStore } from '@dxos/local-storage';
37
37
  import { log } from '@dxos/log';
38
38
  import { parseAttentionPlugin, type AttentionPluginProvides } from '@dxos/plugin-attention';
@@ -41,7 +41,6 @@ import { createExtension, type Node } from '@dxos/plugin-graph';
41
41
  import { ObservabilityAction } from '@dxos/plugin-observability/meta';
42
42
  import { fullyQualifiedId } from '@dxos/react-client/echo';
43
43
  import { translations as deckTranslations } from '@dxos/react-ui-deck';
44
- import { Mosaic } from '@dxos/react-ui-mosaic';
45
44
 
46
45
  import {
47
46
  DeckLayout,
@@ -71,7 +70,7 @@ const isSocket = !!(globalThis as any).__args;
71
70
  // TODO(mjamesderocher): Can we get this directly from Socket?
72
71
  const appScheme = 'composer://';
73
72
 
74
- // TODO(burdon): Evolve into customizable prefs,.
73
+ // TODO(burdon): Evolve into customizable prefs.
75
74
  const customSlots: DeckLayoutProps['slots'] = {
76
75
  wallpaper: {
77
76
  classNames:
@@ -112,7 +111,7 @@ export const DeckPlugin = ({
112
111
  let handleNavigation: () => Promise<void> | undefined;
113
112
 
114
113
  const settings = new LocalStorageStore<DeckSettingsProps>('dxos.org/settings/layout', {
115
- showFooter: false,
114
+ showHints: false,
116
115
  customSlots: false,
117
116
  flatDeck: false,
118
117
  enableNativeRedirect: false,
@@ -190,6 +189,28 @@ export const DeckPlugin = ({
190
189
  }
191
190
  };
192
191
 
192
+ /**
193
+ * Update the active state and ensure that attention is on an active element.
194
+ */
195
+ const handleSetLocation = (next: LayoutParts) => {
196
+ if (attentionPlugin) {
197
+ const attended = attentionPlugin.provides.attention.attended;
198
+ const [attendedId] = Array.from(attended);
199
+ const ids = (layout.values.layoutMode === 'deck' ? next.main : next.solo)?.map(({ id }) => id) ?? [];
200
+ const isAttendedAvailable = !!attendedId && ids.includes(attendedId);
201
+ if (!isAttendedAvailable) {
202
+ // Allow new plank to render before focusing.
203
+ requestAnimationFrame(() => {
204
+ const nextAttended = layout.values.layoutMode === 'solo' ? next.solo?.[0].id : next.main?.[0]?.id;
205
+ const article = document.querySelector<HTMLElement>(`article[data-attendable-id="${nextAttended}"]`);
206
+ article?.focus();
207
+ });
208
+ }
209
+ }
210
+
211
+ location.values.active = next;
212
+ };
213
+
193
214
  return {
194
215
  meta,
195
216
  ready: async (plugins) => {
@@ -198,19 +219,16 @@ export const DeckPlugin = ({
198
219
  attentionPlugin = resolvePlugin(plugins, parseAttentionPlugin);
199
220
  clientPlugin = resolvePlugin(plugins, parseClientPlugin);
200
221
 
201
- // prettier-ignore
202
222
  layout
203
- .prop({ key: 'layoutMode', storageKey: 'layout-mode', type: LocalStorageStore.enum<LayoutMode>() })
204
- .prop({ key: 'sidebarOpen', storageKey: 'sidebar-open', type: LocalStorageStore.bool() })
205
- .prop({ key: 'complementarySidebarOpen', storageKey: 'complementary-sidebar-open', type: LocalStorageStore.bool() });
223
+ .prop({ key: 'layoutMode', type: LocalStorageStore.enum<LayoutMode>() })
224
+ .prop({ key: 'sidebarOpen', type: LocalStorageStore.bool() })
225
+ .prop({ key: 'complementarySidebarOpen', type: LocalStorageStore.bool() });
206
226
 
207
- // prettier-ignore
208
- deck.prop({ key: 'plankSizing', storageKey: 'plank-sizing', type: LocalStorageStore.json<Record<string, number>>() });
227
+ deck.prop({ key: 'plankSizing', type: LocalStorageStore.json<Record<string, number>>() });
209
228
 
210
- // prettier-ignore
211
229
  location
212
- .prop({ key: 'active', storageKey: 'active', type: LocalStorageStore.json<LayoutParts>() })
213
- .prop({ key: 'closed', storageKey: 'closed', type: LocalStorageStore.json<string[]>() });
230
+ .prop({ key: 'active', type: LocalStorageStore.json<LayoutParts>() })
231
+ .prop({ key: 'closed', type: LocalStorageStore.json<string[]>() });
214
232
 
215
233
  unsubscriptionCallbacks.push(
216
234
  clientPlugin?.provides.client.shell.onReset(() => {
@@ -220,15 +238,14 @@ export const DeckPlugin = ({
220
238
  }),
221
239
  );
222
240
 
223
- // prettier-ignore
224
241
  settings
225
- .prop({ key: 'showFooter', storageKey: 'show-footer', type: LocalStorageStore.bool() })
226
- .prop({ key: 'customSlots', storageKey: 'customSlots', type: LocalStorageStore.bool() })
227
- .prop({ key: 'flatDeck', storageKey: 'flatDeck', type: LocalStorageStore.bool() })
228
- .prop({ key: 'enableNativeRedirect', storageKey: 'enable-native-redirect', type: LocalStorageStore.bool() })
229
- .prop({ key: 'disableDeck', storageKey: 'disable-deck', type: LocalStorageStore.bool() }) // Deprecated.
230
- .prop({ key: 'newPlankPositioning', storageKey: 'newPlankPositioning', type: LocalStorageStore.enum<NewPlankPositioning>() })
231
- .prop({ key: 'overscroll', storageKey: 'overscroll', type: LocalStorageStore.enum<Overscroll>() });
242
+ .prop({ key: 'showHints', type: LocalStorageStore.bool() })
243
+ .prop({ key: 'customSlots', type: LocalStorageStore.bool() })
244
+ .prop({ key: 'flatDeck', type: LocalStorageStore.bool() })
245
+ .prop({ key: 'enableNativeRedirect', type: LocalStorageStore.bool() })
246
+ .prop({ key: 'disableDeck', type: LocalStorageStore.bool() }) // Deprecated.
247
+ .prop({ key: 'newPlankPositioning', type: LocalStorageStore.enum<NewPlankPositioning>() })
248
+ .prop({ key: 'overscroll', type: LocalStorageStore.enum<Overscroll>() });
232
249
 
233
250
  if (!isSocket && settings.values.enableNativeRedirect) {
234
251
  checkAppScheme(appScheme);
@@ -237,7 +254,7 @@ export const DeckPlugin = ({
237
254
  handleNavigation = async () => {
238
255
  const pathname = window.location.pathname;
239
256
  if (pathname === '/reset') {
240
- location.values.active = { sidebar: [{ id: NAV_ID }] };
257
+ handleSetLocation({ sidebar: [{ id: NAV_ID }] });
241
258
  location.values.closed = [];
242
259
  layout.values.layoutMode = 'solo';
243
260
  window.location.pathname = '/';
@@ -250,7 +267,7 @@ export const DeckPlugin = ({
250
267
  }
251
268
 
252
269
  const startingLayout = removePart(location.values.active, 'solo');
253
- location.values.active = mergeLayoutParts(layoutFromUri, startingLayout);
270
+ handleSetLocation(mergeLayoutParts(layoutFromUri, startingLayout));
254
271
  layout.values.layoutMode = 'solo';
255
272
  };
256
273
 
@@ -258,23 +275,13 @@ export const DeckPlugin = ({
258
275
  window.addEventListener('popstate', handleNavigation);
259
276
 
260
277
  unsubscriptionCallbacks.push(
261
- effect(() => {
262
- const selectedPath = soloPartToUri(location.values.active);
263
- // TODO(thure): In some browsers, this only preserves the most recent state change, even though this is not `history.replace`…
264
- history.pushState(null, '', `/${selectedPath}${window.location.search}`);
265
- }),
266
- );
267
-
268
- unsubscriptionCallbacks.push(
269
- effect(() => {
270
- const soloId = location.values.active.solo?.[0].id;
271
- if (layout.values.layoutMode === 'solo' && soloId && layout.values.scrollIntoView !== soloId) {
272
- void intentPlugin?.provides.intent.dispatch({
273
- action: LayoutAction.SCROLL_INTO_VIEW,
274
- data: { id: soloId },
275
- });
276
- }
277
- }),
278
+ scheduledEffect(
279
+ () => ({ selectedPath: soloPartToUri(location.values.active) }),
280
+ ({ selectedPath }) => {
281
+ // TODO(thure): In some browsers, this only preserves the most recent state change, even though this is not `history.replace`…
282
+ history.pushState(null, '', `/${selectedPath}${window.location.search}`);
283
+ },
284
+ ),
278
285
  );
279
286
 
280
287
  layoutModeHistory.values.push(`${layout.values.layoutMode}`);
@@ -310,8 +317,7 @@ export const DeckPlugin = ({
310
317
  },
311
318
  properties: {
312
319
  label: ['toggle fullscreen label', { ns: DECK_PLUGIN }],
313
- icon: (props: IconProps) => <ArrowsOut {...props} />,
314
- iconSymbol: 'ph--arrows-out--regular',
320
+ icon: 'ph--arrows-out--regular',
315
321
  keyBinding: {
316
322
  macos: 'ctrl+meta+f',
317
323
  windows: 'shift+ctrl+f',
@@ -329,31 +335,27 @@ export const DeckPlugin = ({
329
335
  ),
330
336
  root: () => {
331
337
  return (
332
- <Mosaic.Root>
333
- <DeckLayout
334
- attention={attentionPlugin?.provides.attention ?? { attended: new Set() }}
335
- layoutParts={location.values.active}
336
- showHintsFooter={settings.values.showFooter}
337
- overscroll={settings.values.overscroll}
338
- flatDeck={settings.values.flatDeck}
339
- slots={settings.values.customSlots ? customSlots : undefined}
340
- toasts={layout.values.toasts}
341
- onDismissToast={(id) => {
342
- const index = layout.values.toasts.findIndex((toast) => toast.id === id);
343
- if (index !== -1) {
344
- // Allow time for the toast to animate out.
345
- // TODO(burdon): Factor out and unregister timeout.
346
- setTimeout(() => {
347
- if (layout.values.toasts[index].id === currentUndoId) {
348
- currentUndoId = undefined;
349
- }
350
- layout.values.toasts.splice(index, 1);
351
- }, 1_000);
352
- }
353
- }}
354
- />
355
- <Mosaic.DragOverlay />
356
- </Mosaic.Root>
338
+ <DeckLayout
339
+ layoutParts={location.values.active}
340
+ showHints={settings.values.showHints}
341
+ overscroll={settings.values.overscroll}
342
+ flatDeck={settings.values.flatDeck}
343
+ slots={settings.values.customSlots ? customSlots : undefined}
344
+ toasts={layout.values.toasts}
345
+ onDismissToast={(id) => {
346
+ const index = layout.values.toasts.findIndex((toast) => toast.id === id);
347
+ if (index !== -1) {
348
+ // Allow time for the toast to animate out.
349
+ // TODO(burdon): Factor out and unregister timeout.
350
+ setTimeout(() => {
351
+ if (layout.values.toasts[index].id === currentUndoId) {
352
+ currentUndoId = undefined;
353
+ }
354
+ layout.values.toasts.splice(index, 1);
355
+ }, 1_000);
356
+ }
357
+ }}
358
+ />
357
359
  );
358
360
  },
359
361
  surface: {
@@ -472,7 +474,7 @@ export const DeckPlugin = ({
472
474
  }
473
475
  });
474
476
 
475
- location.values.active = newLayout;
477
+ handleSetLocation(newLayout);
476
478
  });
477
479
 
478
480
  const ids = openIds(location.values.active);
@@ -522,10 +524,12 @@ export const DeckPlugin = ({
522
524
  const layoutEntry = { id: data.id };
523
525
  const effectivePart = getEffectivePart(data.part, layout.values.layoutMode);
524
526
 
525
- location.values.active = openEntry(location.values.active, effectivePart, layoutEntry, {
526
- positioning: data.positioning ?? settings.values.newPlankPositioning,
527
- pivotId: data.pivotId,
528
- });
527
+ handleSetLocation(
528
+ openEntry(location.values.active, effectivePart, layoutEntry, {
529
+ positioning: data.positioning ?? settings.values.newPlankPositioning,
530
+ pivotId: data.pivotId,
531
+ }),
532
+ );
529
533
 
530
534
  const intents = [];
531
535
  if (data.scrollIntoView && layout.values.layoutMode === 'deck') {
@@ -561,7 +565,11 @@ export const DeckPlugin = ({
561
565
  }
562
566
  });
563
567
 
564
- location.values.active = newLayout;
568
+ handleSetLocation(newLayout);
569
+ // TODO(wittjosiah): This needs to also set the closed state.
570
+ // The closed state should be the existing closed state plus the newly closed ids.
571
+ // The closed state should also be updated when opening entries to remove the id from closed.
572
+ // When SET is called the closed ids should also be calculated and set.
565
573
  return { data: true };
566
574
  });
567
575
  }
@@ -570,7 +578,7 @@ export const DeckPlugin = ({
570
578
  case NavigationAction.SET: {
571
579
  return batch(() => {
572
580
  if (isLayoutParts(intent.data?.activeParts)) {
573
- location.values.active = intent.data!.activeParts;
581
+ handleSetLocation(intent.data!.activeParts);
574
582
  }
575
583
  return { data: true };
576
584
  });
@@ -581,10 +589,12 @@ export const DeckPlugin = ({
581
589
  if (isLayoutAdjustment(intent.data)) {
582
590
  const adjustment = intent.data;
583
591
  if (adjustment.type === 'increment-end' || adjustment.type === 'increment-start') {
584
- location.values.active = incrementPlank(location.values.active, {
585
- type: adjustment.type,
586
- layoutCoordinate: adjustment.layoutCoordinate,
587
- });
592
+ handleSetLocation(
593
+ incrementPlank(location.values.active, {
594
+ type: adjustment.type,
595
+ layoutCoordinate: adjustment.layoutCoordinate,
596
+ }),
597
+ );
588
598
  }
589
599
 
590
600
  if (adjustment.type === 'solo') {
@@ -6,10 +6,13 @@ import React from 'react';
6
6
 
7
7
  import { Surface } from '@dxos/app-framework';
8
8
  import { useGraph } from '@dxos/plugin-graph';
9
+ import { useAttended } from '@dxos/react-ui-attention';
9
10
 
10
11
  import { useNode, useNodeActionExpander } from '../../hooks';
11
12
 
12
- export const ActiveNode = ({ id }: { id?: string }) => {
13
+ // TODO(burdon): Factor out to effect in plugin set document title.
14
+ export const ActiveNode = () => {
15
+ const [id] = useAttended();
13
16
  const { graph } = useGraph();
14
17
  const activeNode = useNode(graph, id);
15
18
  useNodeActionExpander(activeNode);
@@ -2,12 +2,18 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import React from 'react';
5
+ import React, { useMemo } from 'react';
6
6
 
7
- import { type LayoutParts, SLUG_PATH_SEPARATOR, Surface } from '@dxos/app-framework';
7
+ import {
8
+ type LayoutCoordinate,
9
+ NavigationAction,
10
+ SLUG_PATH_SEPARATOR,
11
+ Surface,
12
+ useIntentDispatcher,
13
+ } from '@dxos/app-framework';
8
14
  import { useGraph } from '@dxos/plugin-graph';
9
15
  import { Main } from '@dxos/react-ui';
10
- import { createAttendableAttributes } from '@dxos/react-ui-attention';
16
+ import { useAttended } from '@dxos/react-ui-attention';
11
17
  import { deckGrid } from '@dxos/react-ui-deck';
12
18
  import { mx } from '@dxos/react-ui-theme';
13
19
 
@@ -15,44 +21,82 @@ import { NodePlankHeading } from './NodePlankHeading';
15
21
  import { PlankContentError } from './PlankError';
16
22
  import { PlankLoading } from './PlankLoading';
17
23
  import { useNode, useNodeActionExpander } from '../../hooks';
24
+ import { DECK_PLUGIN } from '../../meta';
18
25
  import { useLayout } from '../LayoutContext';
19
26
 
20
27
  export type ComplementarySidebarProps = {
21
- id?: string;
22
- layoutParts: LayoutParts;
28
+ panel?: string;
23
29
  flatDeck?: boolean;
24
30
  };
25
31
 
26
- export const ComplementarySidebar = ({ id, layoutParts, flatDeck }: ComplementarySidebarProps) => {
32
+ type Panel = { id: string; icon: string };
33
+
34
+ // TODO(burdon): Should be provided by plugins.
35
+ const panels: Panel[] = [
36
+ { id: 'settings', icon: 'ph--gear--regular' },
37
+ { id: 'comments', icon: 'ph--chat-text--regular' },
38
+ { id: 'automation', icon: 'ph--atom--regular' },
39
+ { id: 'debug', icon: 'ph--bug--regular' },
40
+ ];
41
+
42
+ const getPanel = (id?: string): Panel['id'] => {
43
+ const panel = panels.find((p) => p.id === id) ?? panels[0];
44
+ return panel.id;
45
+ };
46
+
47
+ export const ComplementarySidebar = ({ panel, flatDeck }: ComplementarySidebarProps) => {
27
48
  const { popoverAnchorId } = useLayout();
49
+ const attended = useAttended();
50
+ const part = getPanel(panel);
51
+ const id = attended[0] ? `${attended[0]}${SLUG_PATH_SEPARATOR}${part}` : undefined;
28
52
  const { graph } = useGraph();
29
53
  const node = useNode(graph, id);
30
- // const complementaryAvailable = useMemo(() => id === NAV_ID || !!node, [id, node]);
31
- const complementaryAttrs = createAttendableAttributes(id?.split(SLUG_PATH_SEPARATOR)[0] ?? 'never');
32
-
54
+ const dispatch = useIntentDispatcher();
33
55
  useNodeActionExpander(node);
34
56
 
57
+ const actions = useMemo(
58
+ () =>
59
+ panels.map(({ id, icon }) => ({
60
+ id: `complementary-${id}`,
61
+ data: () => {
62
+ void dispatch({ action: NavigationAction.OPEN, data: { activeParts: { complementary: id } } });
63
+ },
64
+ properties: {
65
+ label: [`open ${id} label`, { ns: DECK_PLUGIN }],
66
+ icon,
67
+ menuItemType: 'toggle',
68
+ isChecked: part === id,
69
+ },
70
+ })),
71
+ [part],
72
+ );
73
+
74
+ // TODO(wittjosiah): Ensure that id is always defined.
75
+ const coordinate: LayoutCoordinate = useMemo(() => ({ entryId: id ?? 'unknown', part: 'complementary' }), [id]);
76
+
77
+ // TODO(burdon): Debug panel doesn't change when switching even though id has chagned.
35
78
  return (
36
- <Main.ComplementarySidebar {...complementaryAttrs}>
37
- {node ? (
38
- <div role='none' className={mx(deckGrid, 'grid-cols-1 bs-full')}>
39
- <NodePlankHeading
40
- node={node}
41
- id={id}
42
- layoutParts={layoutParts}
43
- layoutPart='complementary'
44
- popoverAnchorId={popoverAnchorId}
45
- flatDeck={flatDeck}
46
- />
47
- <Surface
48
- role='article'
49
- data={{ subject: node.data, part: 'complementary', popoverAnchorId }}
50
- limit={1}
51
- fallback={PlankContentError}
52
- placeholder={<PlankLoading />}
53
- />
79
+ <Main.ComplementarySidebar>
80
+ <div role='none' className={mx(deckGrid, 'grid-cols-1 bs-full')}>
81
+ <NodePlankHeading
82
+ coordinate={coordinate}
83
+ node={node}
84
+ popoverAnchorId={popoverAnchorId}
85
+ flatDeck={flatDeck}
86
+ actions={actions}
87
+ />
88
+ <div className='row-span-2 divide-y divide-separator overflow-x-hidden overflow-y-scroll'>
89
+ {node && (
90
+ <Surface
91
+ key={id}
92
+ role={`complementary--${part}`}
93
+ data={{ id, subject: node.properties.object ?? node.properties.space, popoverAnchorId }}
94
+ fallback={PlankContentError}
95
+ placeholder={<PlankLoading />}
96
+ />
97
+ )}
54
98
  </div>
55
- ) : null}
99
+ </div>
56
100
  </Main.ComplementarySidebar>
57
101
  );
58
102
  };