@dxos/plugin-deck 0.8.2-main.12df754 → 0.8.2-main.30e4dbb
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/dist/lib/browser/{check-app-scheme-SEYECDHI.mjs → check-app-scheme-O7JPE4TM.mjs} +2 -3
- package/dist/lib/browser/check-app-scheme-O7JPE4TM.mjs.map +7 -0
- package/dist/lib/browser/chunk-E7TOEOGO.mjs +157 -0
- package/dist/lib/browser/chunk-E7TOEOGO.mjs.map +7 -0
- package/dist/lib/browser/chunk-HWEH5OJ7.mjs +24 -0
- package/dist/lib/browser/{chunk-6HJZL3WT.mjs → chunk-PGSJT5PG.mjs} +2 -2
- package/dist/lib/browser/{chunk-VP6FCWFV.mjs → chunk-ZAL26IIZ.mjs} +104 -80
- package/dist/lib/browser/chunk-ZAL26IIZ.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +3 -3
- package/dist/lib/browser/{intent-resolver-6AK45PT5.mjs → intent-resolver-NO6L67KF.mjs} +33 -26
- package/dist/lib/browser/intent-resolver-NO6L67KF.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/{react-root-KA2IL5RA.mjs → react-root-SK3KDPHZ.mjs} +5 -5
- package/dist/lib/browser/{react-surface-LIPGYEYN.mjs → react-surface-LSSPY2BU.mjs} +5 -5
- package/dist/lib/browser/{settings-6NU7CF2B.mjs → settings-C7LX2GXF.mjs} +2 -2
- package/dist/lib/browser/{state-Z6UY2Z3M.mjs → state-AX74YEJD.mjs} +2 -2
- package/dist/lib/browser/{tools-VDVQTJMD.mjs → tools-7W7KZRAX.mjs} +7 -7
- package/dist/lib/browser/tools-7W7KZRAX.mjs.map +7 -0
- package/dist/lib/browser/types.mjs +1 -1
- package/dist/lib/browser/{url-handler-3CARFXQK.mjs → url-handler-AF5SYROZ.mjs} +2 -2
- package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
- package/dist/types/src/capabilities/check-app-scheme.d.ts.map +1 -1
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -1
- package/dist/types/src/capabilities/tools.d.ts.map +1 -1
- package/dist/types/src/capabilities/url-handler.d.ts.map +1 -1
- package/dist/types/src/components/DeckLayout/Banner.d.ts.map +1 -1
- package/dist/types/src/components/DeckLayout/DeckLayout.d.ts.map +1 -1
- package/dist/types/src/components/DeckLayout/Popover.d.ts.map +1 -1
- package/dist/types/src/components/DeckLayout/StatusBar.d.ts.map +1 -1
- package/dist/types/src/components/DeckLayout/Toast.d.ts.map +1 -1
- package/dist/types/src/components/DeckSettings/DeckSettings.d.ts.map +1 -1
- package/dist/types/src/components/Plank/Plank.d.ts.map +1 -1
- package/dist/types/src/components/Plank/PlankControls.d.ts.map +1 -1
- package/dist/types/src/components/Plank/PlankError.d.ts.map +1 -1
- package/dist/types/src/components/Plank/PlankHeading.d.ts.map +1 -1
- package/dist/types/src/components/Sidebar/ComplementarySidebar.d.ts.map +1 -1
- package/dist/types/src/components/Sidebar/SidebarButton.d.ts.map +1 -1
- package/dist/types/src/hooks/useNodeActionExpander.d.ts.map +1 -1
- package/dist/types/src/layout.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +104 -104
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/types/src/util/layoutAppliesTopbar.d.ts.map +1 -1
- package/dist/types/src/util/overscroll.d.ts.map +1 -1
- package/dist/types/src/util/set-active.d.ts.map +1 -1
- package/dist/types/src/util/useCompanions.d.ts.map +1 -1
- package/dist/types/src/util/useHoistStatusbar.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +31 -29
- package/src/capabilities/check-app-scheme.ts +3 -5
- package/src/capabilities/intent-resolver.ts +85 -77
- package/src/capabilities/tools.ts +4 -3
- package/src/components/DeckLayout/Popover.tsx +71 -43
- package/src/components/Plank/Plank.stories.tsx +1 -1
- package/src/components/Plank/Plank.tsx +3 -1
- package/src/components/Plank/PlankControls.tsx +14 -28
- package/src/components/Plank/PlankError.tsx +2 -6
- package/src/components/Plank/PlankHeading.tsx +12 -4
- package/src/components/Sidebar/SidebarButton.tsx +3 -0
- package/src/types.ts +72 -71
- package/dist/lib/browser/check-app-scheme-SEYECDHI.mjs.map +0 -7
- package/dist/lib/browser/chunk-4QSEGMY3.mjs +0 -24
- package/dist/lib/browser/chunk-VP6FCWFV.mjs.map +0 -7
- package/dist/lib/browser/chunk-ZMJMCN7O.mjs +0 -157
- package/dist/lib/browser/chunk-ZMJMCN7O.mjs.map +0 -7
- package/dist/lib/browser/intent-resolver-6AK45PT5.mjs.map +0 -7
- package/dist/lib/browser/tools-VDVQTJMD.mjs.map +0 -7
- /package/dist/lib/browser/{chunk-4QSEGMY3.mjs.map → chunk-HWEH5OJ7.mjs.map} +0 -0
- /package/dist/lib/browser/{chunk-6HJZL3WT.mjs.map → chunk-PGSJT5PG.mjs.map} +0 -0
- /package/dist/lib/browser/{react-root-KA2IL5RA.mjs.map → react-root-SK3KDPHZ.mjs.map} +0 -0
- /package/dist/lib/browser/{react-surface-LIPGYEYN.mjs.map → react-surface-LSSPY2BU.mjs.map} +0 -0
- /package/dist/lib/browser/{settings-6NU7CF2B.mjs.map → settings-C7LX2GXF.mjs.map} +0 -0
- /package/dist/lib/browser/{state-Z6UY2Z3M.mjs.map → state-AX74YEJD.mjs.map} +0 -0
- /package/dist/lib/browser/{url-handler-3CARFXQK.mjs.map → url-handler-AF5SYROZ.mjs.map} +0 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { batch } from '@preact/signals-core';
|
|
6
|
-
import { pipe } from 'effect';
|
|
6
|
+
import { Schema, Effect, pipe } from 'effect';
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
9
|
Capabilities,
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
createIntent,
|
|
16
16
|
chain,
|
|
17
17
|
} from '@dxos/app-framework';
|
|
18
|
-
import { getTypename
|
|
18
|
+
import { getTypename } from '@dxos/echo-schema';
|
|
19
19
|
import { invariant } from '@dxos/invariant';
|
|
20
20
|
import { isLiveObject } from '@dxos/live-object';
|
|
21
21
|
import { log } from '@dxos/log';
|
|
@@ -67,10 +67,10 @@ export default (context: PluginsContext) =>
|
|
|
67
67
|
}),
|
|
68
68
|
createResolver({
|
|
69
69
|
intent: LayoutAction.UpdateLayout,
|
|
70
|
-
// TODO(wittjosiah): This should be able to just be `
|
|
70
|
+
// TODO(wittjosiah): This should be able to just be `Schema.is(LayoutAction.UpdateSidebar.fields.input)`
|
|
71
71
|
// but the filter is not being applied correctly.
|
|
72
|
-
filter: (data): data is
|
|
73
|
-
|
|
72
|
+
filter: (data): data is Schema.Schema.Type<typeof LayoutAction.UpdateSidebar.fields.input> =>
|
|
73
|
+
Schema.is(LayoutAction.UpdateSidebar.fields.input)(data),
|
|
74
74
|
resolve: ({ options }) => {
|
|
75
75
|
const layout = context.requestCapability(DeckCapabilities.MutableDeckState);
|
|
76
76
|
const next = options?.state ?? layout.sidebarState;
|
|
@@ -81,10 +81,10 @@ export default (context: PluginsContext) =>
|
|
|
81
81
|
}),
|
|
82
82
|
createResolver({
|
|
83
83
|
intent: LayoutAction.UpdateLayout,
|
|
84
|
-
// TODO(wittjosiah): This should be able to just be `
|
|
84
|
+
// TODO(wittjosiah): This should be able to just be `Schema.is(LayoutAction.UpdateComplementary.fields.input)`
|
|
85
85
|
// but the filter is not being applied correctly.
|
|
86
|
-
filter: (data): data is
|
|
87
|
-
|
|
86
|
+
filter: (data): data is Schema.Schema.Type<typeof LayoutAction.UpdateComplementary.fields.input> =>
|
|
87
|
+
Schema.is(LayoutAction.UpdateComplementary.fields.input)(data),
|
|
88
88
|
resolve: ({ subject, options }) => {
|
|
89
89
|
const layout = context.requestCapability(DeckCapabilities.MutableDeckState);
|
|
90
90
|
|
|
@@ -100,10 +100,10 @@ export default (context: PluginsContext) =>
|
|
|
100
100
|
}),
|
|
101
101
|
createResolver({
|
|
102
102
|
intent: LayoutAction.UpdateLayout,
|
|
103
|
-
// TODO(wittjosiah): This should be able to just be `
|
|
103
|
+
// TODO(wittjosiah): This should be able to just be `Schema.is(LayoutAction.UpdateDialog.fields.input)`
|
|
104
104
|
// but the filter is not being applied correctly.
|
|
105
|
-
filter: (data): data is
|
|
106
|
-
|
|
105
|
+
filter: (data): data is Schema.Schema.Type<typeof LayoutAction.UpdateDialog.fields.input> =>
|
|
106
|
+
Schema.is(LayoutAction.UpdateDialog.fields.input)(data),
|
|
107
107
|
resolve: ({ subject, options }) => {
|
|
108
108
|
const layout = context.requestCapability(DeckCapabilities.MutableDeckState);
|
|
109
109
|
layout.dialogOpen = options.state ?? Boolean(subject);
|
|
@@ -114,10 +114,10 @@ export default (context: PluginsContext) =>
|
|
|
114
114
|
}),
|
|
115
115
|
createResolver({
|
|
116
116
|
intent: LayoutAction.UpdateLayout,
|
|
117
|
-
// TODO(wittjosiah): This should be able to just be `
|
|
117
|
+
// TODO(wittjosiah): This should be able to just be `Schema.is(LayoutAction.UpdatePopover.fields.input)`
|
|
118
118
|
// but the filter is not being applied correctly.
|
|
119
|
-
filter: (data): data is
|
|
120
|
-
|
|
119
|
+
filter: (data): data is Schema.Schema.Type<typeof LayoutAction.UpdatePopover.fields.input> =>
|
|
120
|
+
Schema.is(LayoutAction.UpdatePopover.fields.input)(data),
|
|
121
121
|
resolve: ({ subject, options }) => {
|
|
122
122
|
const layout = context.requestCapability(DeckCapabilities.MutableDeckState);
|
|
123
123
|
layout.popoverOpen = options.state ?? Boolean(subject);
|
|
@@ -133,10 +133,10 @@ export default (context: PluginsContext) =>
|
|
|
133
133
|
}),
|
|
134
134
|
createResolver({
|
|
135
135
|
intent: LayoutAction.UpdateLayout,
|
|
136
|
-
// TODO(wittjosiah): This should be able to just be `
|
|
136
|
+
// TODO(wittjosiah): This should be able to just be `Schema.is(LayoutAction.AddToast.fields.input)`
|
|
137
137
|
// but the filter is not being applied correctly.
|
|
138
|
-
filter: (data): data is
|
|
139
|
-
|
|
138
|
+
filter: (data): data is Schema.Schema.Type<typeof LayoutAction.AddToast.fields.input> =>
|
|
139
|
+
Schema.is(LayoutAction.AddToast.fields.input)(data),
|
|
140
140
|
resolve: ({ subject }) => {
|
|
141
141
|
const layout = context.requestCapability(DeckCapabilities.MutableDeckState);
|
|
142
142
|
layout.toasts.push(subject);
|
|
@@ -144,10 +144,10 @@ export default (context: PluginsContext) =>
|
|
|
144
144
|
}),
|
|
145
145
|
createResolver({
|
|
146
146
|
intent: LayoutAction.UpdateLayout,
|
|
147
|
-
// TODO(wittjosiah): This should be able to just be `
|
|
147
|
+
// TODO(wittjosiah): This should be able to just be `Schema.is(LayoutAction.SetLayoutMode.fields.input)`
|
|
148
148
|
// but the filter is not being applied correctly.
|
|
149
|
-
filter: (data): data is
|
|
150
|
-
if (!
|
|
149
|
+
filter: (data): data is Schema.Schema.Type<typeof LayoutAction.SetLayoutMode.fields.input> => {
|
|
150
|
+
if (!Schema.is(LayoutAction.SetLayoutMode.fields.input)(data)) {
|
|
151
151
|
return false;
|
|
152
152
|
}
|
|
153
153
|
|
|
@@ -202,8 +202,8 @@ export default (context: PluginsContext) =>
|
|
|
202
202
|
}),
|
|
203
203
|
createResolver({
|
|
204
204
|
intent: LayoutAction.UpdateLayout,
|
|
205
|
-
filter: (data): data is
|
|
206
|
-
|
|
205
|
+
filter: (data): data is Schema.Schema.Type<typeof LayoutAction.SwitchWorkspace.fields.input> =>
|
|
206
|
+
Schema.is(LayoutAction.SwitchWorkspace.fields.input)(data),
|
|
207
207
|
resolve: ({ subject }) => {
|
|
208
208
|
const state = context.requestCapability(DeckCapabilities.MutableDeckState);
|
|
209
209
|
batch(() => {
|
|
@@ -228,8 +228,8 @@ export default (context: PluginsContext) =>
|
|
|
228
228
|
}),
|
|
229
229
|
createResolver({
|
|
230
230
|
intent: LayoutAction.UpdateLayout,
|
|
231
|
-
filter: (data): data is
|
|
232
|
-
|
|
231
|
+
filter: (data): data is Schema.Schema.Type<typeof LayoutAction.RevertWorkspace.fields.input> =>
|
|
232
|
+
Schema.is(LayoutAction.RevertWorkspace.fields.input)(data),
|
|
233
233
|
resolve: () => {
|
|
234
234
|
const state = context.requestCapability(DeckCapabilities.MutableDeckState);
|
|
235
235
|
return {
|
|
@@ -239,62 +239,70 @@ export default (context: PluginsContext) =>
|
|
|
239
239
|
}),
|
|
240
240
|
createResolver({
|
|
241
241
|
intent: LayoutAction.UpdateLayout,
|
|
242
|
-
filter: (data): data is
|
|
243
|
-
|
|
244
|
-
resolve: ({ subject, options }) =>
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
242
|
+
filter: (data): data is Schema.Schema.Type<typeof LayoutAction.Open.fields.input> =>
|
|
243
|
+
Schema.is(LayoutAction.Open.fields.input)(data),
|
|
244
|
+
resolve: ({ subject, options }) =>
|
|
245
|
+
Effect.gen(function* () {
|
|
246
|
+
const { graph } = context.requestCapability(Capabilities.AppGraph);
|
|
247
|
+
const state = context.requestCapability(DeckCapabilities.MutableDeckState);
|
|
248
|
+
const attention = context.requestCapability(AttentionCapabilities.Attention);
|
|
249
|
+
const settings = context
|
|
250
|
+
.requestCapabilities(Capabilities.SettingsStore)[0]
|
|
251
|
+
?.getStore<DeckSettingsProps>(DECK_PLUGIN)?.value;
|
|
251
252
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
openEntry(acc, entryId, {
|
|
259
|
-
key: options?.key,
|
|
260
|
-
positioning: options?.positioning ?? settings?.newPlankPositioning,
|
|
261
|
-
pivotId: options?.pivotId,
|
|
262
|
-
variant: options?.variant,
|
|
263
|
-
}),
|
|
264
|
-
state.deck.active,
|
|
265
|
-
);
|
|
253
|
+
if (options?.workspace && state.activeDeck !== options?.workspace) {
|
|
254
|
+
const { dispatch } = context.requestCapability(Capabilities.IntentDispatcher);
|
|
255
|
+
yield* dispatch(
|
|
256
|
+
createIntent(LayoutAction.SwitchWorkspace, { part: 'workspace', subject: options.workspace }),
|
|
257
|
+
);
|
|
258
|
+
}
|
|
266
259
|
|
|
267
|
-
|
|
268
|
-
|
|
260
|
+
const previouslyOpenIds = new Set<string>(state.deck.solo ? [state.deck.solo] : state.deck.active);
|
|
261
|
+
batch(() => {
|
|
262
|
+
const next = state.deck.solo
|
|
263
|
+
? (subject as string[]).map((id) => createEntryId(id, options?.variant))
|
|
264
|
+
: subject.reduce(
|
|
265
|
+
(acc, entryId) =>
|
|
266
|
+
openEntry(acc, entryId, {
|
|
267
|
+
key: options?.key,
|
|
268
|
+
positioning: options?.positioning ?? settings?.newPlankPositioning,
|
|
269
|
+
pivotId: options?.pivotId,
|
|
270
|
+
variant: options?.variant,
|
|
271
|
+
}),
|
|
272
|
+
state.deck.active,
|
|
273
|
+
);
|
|
269
274
|
|
|
270
|
-
|
|
271
|
-
|
|
275
|
+
return setActive({ next, state, attention });
|
|
276
|
+
});
|
|
272
277
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
278
|
+
const ids = state.deck.solo ? [state.deck.solo] : state.deck.active;
|
|
279
|
+
const newlyOpen = ids.filter((i) => !previouslyOpenIds.has(i));
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
intents: [
|
|
283
|
+
...(options?.scrollIntoView !== false
|
|
284
|
+
? [createIntent(LayoutAction.ScrollIntoView, { part: 'current', subject: newlyOpen[0] ?? subject[0] })]
|
|
285
|
+
: []),
|
|
286
|
+
createIntent(LayoutAction.Expose, { part: 'navigation', subject: newlyOpen[0] ?? subject[0] }),
|
|
287
|
+
...newlyOpen.map((subjectId) => {
|
|
288
|
+
const active = graph?.findNode(subjectId)?.data;
|
|
289
|
+
const typename = isLiveObject(active) ? getTypename(active) : undefined;
|
|
290
|
+
return createIntent(ObservabilityAction.SendEvent, {
|
|
291
|
+
name: 'navigation.activate',
|
|
292
|
+
properties: {
|
|
293
|
+
subjectId,
|
|
294
|
+
typename,
|
|
295
|
+
},
|
|
296
|
+
});
|
|
297
|
+
}),
|
|
298
|
+
],
|
|
299
|
+
};
|
|
300
|
+
}),
|
|
293
301
|
}),
|
|
294
302
|
createResolver({
|
|
295
303
|
intent: LayoutAction.UpdateLayout,
|
|
296
|
-
filter: (data): data is
|
|
297
|
-
|
|
304
|
+
filter: (data): data is Schema.Schema.Type<typeof LayoutAction.Close.fields.input> =>
|
|
305
|
+
Schema.is(LayoutAction.Close.fields.input)(data),
|
|
298
306
|
resolve: ({ subject }) => {
|
|
299
307
|
const state = context.requestCapability(DeckCapabilities.MutableDeckState);
|
|
300
308
|
const attention = context.requestCapability(AttentionCapabilities.Attention);
|
|
@@ -316,8 +324,8 @@ export default (context: PluginsContext) =>
|
|
|
316
324
|
}),
|
|
317
325
|
createResolver({
|
|
318
326
|
intent: LayoutAction.UpdateLayout,
|
|
319
|
-
filter: (data): data is
|
|
320
|
-
|
|
327
|
+
filter: (data): data is Schema.Schema.Type<typeof LayoutAction.Set.fields.input> =>
|
|
328
|
+
Schema.is(LayoutAction.Set.fields.input)(data),
|
|
321
329
|
resolve: ({ subject }) => {
|
|
322
330
|
const state = context.requestCapability(DeckCapabilities.MutableDeckState);
|
|
323
331
|
const attention = context.requestCapability(AttentionCapabilities.Attention);
|
|
@@ -329,8 +337,8 @@ export default (context: PluginsContext) =>
|
|
|
329
337
|
}),
|
|
330
338
|
createResolver({
|
|
331
339
|
intent: LayoutAction.UpdateLayout,
|
|
332
|
-
filter: (data): data is
|
|
333
|
-
|
|
340
|
+
filter: (data): data is Schema.Schema.Type<typeof LayoutAction.ScrollIntoView.fields.input> =>
|
|
341
|
+
Schema.is(LayoutAction.ScrollIntoView.fields.input)(data),
|
|
334
342
|
resolve: ({ subject }) => {
|
|
335
343
|
const layout = context.requestCapability(DeckCapabilities.MutableDeckState);
|
|
336
344
|
layout.scrollIntoView = subject;
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { Schema } from 'effect';
|
|
6
|
+
|
|
5
7
|
import {
|
|
6
8
|
contributes,
|
|
7
9
|
createIntent,
|
|
@@ -10,7 +12,6 @@ import {
|
|
|
10
12
|
type PromiseIntentDispatcher,
|
|
11
13
|
} from '@dxos/app-framework';
|
|
12
14
|
import { defineTool, ToolResult } from '@dxos/artifact';
|
|
13
|
-
import { S } from '@dxos/echo-schema';
|
|
14
15
|
import { invariant } from '@dxos/invariant';
|
|
15
16
|
|
|
16
17
|
import { meta } from '../meta';
|
|
@@ -35,8 +36,8 @@ export default () =>
|
|
|
35
36
|
`,
|
|
36
37
|
caption: 'Showing item...',
|
|
37
38
|
// TODO(wittjosiah): Refactor Layout/Navigation/Deck actions so that they can be used directly.
|
|
38
|
-
schema:
|
|
39
|
-
id:
|
|
39
|
+
schema: Schema.Struct({
|
|
40
|
+
id: Schema.String.annotations({
|
|
40
41
|
description: 'The ID of the item to show.',
|
|
41
42
|
}),
|
|
42
43
|
}),
|
|
@@ -2,72 +2,100 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import { createContext } from '@radix-ui/react-context';
|
|
6
|
+
import React, { type PropsWithChildren, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
6
7
|
|
|
7
8
|
import { Surface, useCapability } from '@dxos/app-framework';
|
|
8
|
-
import { Popover } from '@dxos/react-ui';
|
|
9
|
+
import { Popover, type PopoverContentInteractOutsideEvent } from '@dxos/react-ui';
|
|
9
10
|
|
|
10
11
|
import { DeckCapabilities } from '../../capabilities';
|
|
11
12
|
|
|
12
13
|
export type DeckPopoverRootProps = PropsWithChildren<{}>;
|
|
13
14
|
|
|
15
|
+
const DEBOUNCE_DELAY = 40;
|
|
16
|
+
|
|
17
|
+
type DeckPopoverContextValue = {
|
|
18
|
+
setOpen: (open: boolean) => void;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const [DeckPopoverProvider, useDeckPopoverContext] = createContext<DeckPopoverContextValue>('DeckPopover');
|
|
22
|
+
|
|
14
23
|
export const PopoverRoot = ({ children }: DeckPopoverRootProps) => {
|
|
15
|
-
const
|
|
24
|
+
const layout = useCapability(DeckCapabilities.MutableDeckState);
|
|
16
25
|
const virtualRef = useRef<HTMLButtonElement | null>(null);
|
|
17
26
|
const [virtualIter, setVirtualIter] = useState(0);
|
|
27
|
+
const [open, setOpen] = useState(false);
|
|
28
|
+
const debounceRef = useRef<NodeJS.Timeout | null>(null);
|
|
18
29
|
|
|
19
|
-
// TODO(thure): This is a workaround for the
|
|
20
|
-
// the anchor further down the tree
|
|
21
|
-
const [delayedPopoverVisibility, setDelayedPopoverVisibility] = useState(false);
|
|
30
|
+
// TODO(thure): This is a workaround for the race condition between displaying a Popover and either rendering
|
|
31
|
+
// the anchor further down the tree or measuring the virtual trigger’s client rect.
|
|
22
32
|
useEffect(() => {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
(nextOpen: boolean) => {
|
|
28
|
-
if (nextOpen && (context.popoverAnchor || context.popoverAnchorId)) {
|
|
29
|
-
context.popoverOpen = true;
|
|
30
|
-
} else {
|
|
31
|
-
context.popoverOpen = false;
|
|
32
|
-
context.popoverAnchor = undefined;
|
|
33
|
-
context.popoverAnchorId = undefined;
|
|
34
|
-
context.popoverSide = undefined;
|
|
33
|
+
setOpen(false);
|
|
34
|
+
if (layout.popoverOpen) {
|
|
35
|
+
if (debounceRef.current) {
|
|
36
|
+
clearTimeout(debounceRef.current);
|
|
35
37
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}, [context.popoverAnchor]);
|
|
38
|
+
if (layout.popoverAnchor && virtualRef.current !== layout.popoverAnchor) {
|
|
39
|
+
virtualRef.current = layout.popoverAnchor ?? null;
|
|
40
|
+
setVirtualIter((iter) => iter + 1);
|
|
41
|
+
}
|
|
42
|
+
debounceRef.current = setTimeout(() => setOpen(true), DEBOUNCE_DELAY);
|
|
43
|
+
}
|
|
44
|
+
}, [layout.popoverOpen, layout.popoverAnchorId, layout.popoverAnchor, layout.popoverContent]);
|
|
44
45
|
|
|
45
46
|
return (
|
|
46
|
-
<
|
|
47
|
-
modal
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
{children}
|
|
53
|
-
</Popover.Root>
|
|
47
|
+
<DeckPopoverProvider setOpen={setOpen}>
|
|
48
|
+
<Popover.Root modal={false} open={open}>
|
|
49
|
+
{layout.popoverAnchor && <Popover.VirtualTrigger key={virtualIter} virtualRef={virtualRef} />}
|
|
50
|
+
{children}
|
|
51
|
+
</Popover.Root>
|
|
52
|
+
</DeckPopoverProvider>
|
|
54
53
|
);
|
|
55
54
|
};
|
|
56
55
|
|
|
57
56
|
export const PopoverContent = () => {
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
57
|
+
const layout = useCapability(DeckCapabilities.MutableDeckState);
|
|
58
|
+
const { setOpen } = useDeckPopoverContext('PopoverContent');
|
|
59
|
+
|
|
60
|
+
const handleClose = useCallback(
|
|
61
|
+
(event: KeyboardEvent | PopoverContentInteractOutsideEvent) => {
|
|
62
|
+
if (
|
|
63
|
+
// TODO(thure): CodeMirror should not focus itself when it updates.
|
|
64
|
+
event.type === 'dismissableLayer.focusOutside' &&
|
|
65
|
+
(event.currentTarget as HTMLElement | undefined)?.classList.contains('cm-content')
|
|
66
|
+
) {
|
|
67
|
+
event.preventDefault();
|
|
68
|
+
} else {
|
|
69
|
+
setOpen(false);
|
|
70
|
+
layout.popoverOpen = false;
|
|
71
|
+
layout.popoverAnchor = undefined;
|
|
72
|
+
layout.popoverAnchorId = undefined;
|
|
73
|
+
layout.popoverSide = undefined;
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
[setOpen],
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const collisionBoundaries: HTMLElement[] = useMemo(() => {
|
|
80
|
+
const closest = layout.popoverAnchor?.closest('[data-popover-collision-boundary]') as
|
|
81
|
+
| HTMLElement
|
|
82
|
+
| null
|
|
83
|
+
| undefined;
|
|
84
|
+
return closest ? [closest] : [];
|
|
85
|
+
}, [layout.popoverAnchor]);
|
|
65
86
|
|
|
66
87
|
return (
|
|
67
88
|
<Popover.Portal>
|
|
68
|
-
<Popover.Content
|
|
89
|
+
<Popover.Content
|
|
90
|
+
side={layout.popoverSide}
|
|
91
|
+
onInteractOutside={handleClose}
|
|
92
|
+
onEscapeKeyDown={handleClose}
|
|
93
|
+
collisionBoundary={collisionBoundaries}
|
|
94
|
+
sticky='always'
|
|
95
|
+
hideWhenDetached
|
|
96
|
+
>
|
|
69
97
|
<Popover.Viewport>
|
|
70
|
-
<Surface role='popover' data={
|
|
98
|
+
<Surface role='popover' data={layout.popoverContent} limit={1} />
|
|
71
99
|
</Popover.Viewport>
|
|
72
100
|
<Popover.Arrow />
|
|
73
101
|
</Popover.Content>
|
|
@@ -116,7 +116,7 @@ const PlankImpl = memo(
|
|
|
116
116
|
path,
|
|
117
117
|
popoverAnchorId,
|
|
118
118
|
},
|
|
119
|
-
[node, node?.data, path, popoverAnchorId, primary?.data],
|
|
119
|
+
[node, node?.data, path, popoverAnchorId, primary?.data, variant],
|
|
120
120
|
);
|
|
121
121
|
|
|
122
122
|
// TODO(wittjosiah): Change prop to accept a component.
|
|
@@ -131,6 +131,8 @@ const PlankImpl = memo(
|
|
|
131
131
|
part === 'deck' && (companioned === 'companion' ? '!border-separator border-ie' : '!border-separator border-li'),
|
|
132
132
|
part.startsWith('solo-') && 'row-span-2 grid-rows-subgrid min-is-0',
|
|
133
133
|
part === 'solo-companion' && '!border-separator border-is',
|
|
134
|
+
layoutMode === 'solo--fullscreen' &&
|
|
135
|
+
'!transition-[margin-block-start,inline-size] -mbs-[--rail-action] has-[[data-plank-heading]:hover]:mbs-0',
|
|
134
136
|
);
|
|
135
137
|
|
|
136
138
|
return (
|
|
@@ -6,15 +6,7 @@ import React, { forwardRef, useCallback } from 'react';
|
|
|
6
6
|
|
|
7
7
|
import { createIntent, useIntentDispatcher } from '@dxos/app-framework';
|
|
8
8
|
import { invariant } from '@dxos/invariant';
|
|
9
|
-
import {
|
|
10
|
-
Button,
|
|
11
|
-
ButtonGroup,
|
|
12
|
-
type ButtonGroupProps,
|
|
13
|
-
type ButtonProps,
|
|
14
|
-
Icon,
|
|
15
|
-
Tooltip,
|
|
16
|
-
useTranslation,
|
|
17
|
-
} from '@dxos/react-ui';
|
|
9
|
+
import { ButtonGroup, type ButtonGroupProps, type ButtonProps, IconButton, useTranslation } from '@dxos/react-ui';
|
|
18
10
|
|
|
19
11
|
import { DECK_PLUGIN } from '../../meta';
|
|
20
12
|
import { DeckAction, type LayoutMode } from '../../types';
|
|
@@ -39,22 +31,10 @@ export type PlankControlsProps = Omit<ButtonGroupProps, 'onClick'> & {
|
|
|
39
31
|
};
|
|
40
32
|
|
|
41
33
|
const PlankControl = ({ icon, label, ...props }: Omit<ButtonProps, 'children'> & { label: string; icon: string }) => {
|
|
42
|
-
return
|
|
43
|
-
<Tooltip.Root>
|
|
44
|
-
<Tooltip.Trigger asChild>
|
|
45
|
-
<Button variant='ghost' {...props}>
|
|
46
|
-
<span className='sr-only'>{label}</span>
|
|
47
|
-
<Icon icon={icon} size={5} />
|
|
48
|
-
</Button>
|
|
49
|
-
</Tooltip.Trigger>
|
|
50
|
-
<Tooltip.Portal>
|
|
51
|
-
<Tooltip.Content side='bottom'>{label}</Tooltip.Content>
|
|
52
|
-
</Tooltip.Portal>
|
|
53
|
-
</Tooltip.Root>
|
|
54
|
-
);
|
|
34
|
+
return <IconButton iconOnly label={label} icon={icon} size={5} variant='ghost' tooltipSide='bottom' {...props} />;
|
|
55
35
|
};
|
|
56
36
|
|
|
57
|
-
const plankControlSpacing = 'pli-2
|
|
37
|
+
const plankControlSpacing = 'pli-2';
|
|
58
38
|
|
|
59
39
|
type PlankComplimentControlsProps = {
|
|
60
40
|
primary?: string;
|
|
@@ -73,7 +53,7 @@ export const PlankCompanionControls = forwardRef<HTMLDivElement, PlankCompliment
|
|
|
73
53
|
<PlankControl
|
|
74
54
|
label={t('close companion label')}
|
|
75
55
|
variant='ghost'
|
|
76
|
-
icon='ph--
|
|
56
|
+
icon='ph--x--regular'
|
|
77
57
|
onClick={handleCloseCompanion}
|
|
78
58
|
classNames={plankControlSpacing}
|
|
79
59
|
/>
|
|
@@ -114,12 +94,18 @@ export const PlankControls = forwardRef<HTMLDivElement, PlankControlsProps>(
|
|
|
114
94
|
label={t(
|
|
115
95
|
layoutMode === 'solo--fullscreen'
|
|
116
96
|
? 'exit fullscreen label'
|
|
117
|
-
:
|
|
118
|
-
? 'show
|
|
119
|
-
: 'show
|
|
97
|
+
: layoutIsAnySolo
|
|
98
|
+
? 'show deck plank label'
|
|
99
|
+
: 'show solo plank label',
|
|
120
100
|
)}
|
|
121
101
|
classNames={buttonClassNames}
|
|
122
|
-
icon={
|
|
102
|
+
icon={
|
|
103
|
+
layoutMode === 'solo--fullscreen'
|
|
104
|
+
? 'ph--corners-in--regular'
|
|
105
|
+
: layoutIsAnySolo
|
|
106
|
+
? 'ph--arrows-in-line-horizontal--regular'
|
|
107
|
+
: 'ph--arrows-out-line-horizontal--regular'
|
|
108
|
+
}
|
|
123
109
|
onClick={() => onClick?.(layoutMode === 'solo--fullscreen' ? 'solo--fullscreen' : 'solo')}
|
|
124
110
|
/>
|
|
125
111
|
</>
|
|
@@ -6,7 +6,7 @@ import React, { useEffect, useState } from 'react';
|
|
|
6
6
|
|
|
7
7
|
import { type Node } from '@dxos/plugin-graph';
|
|
8
8
|
import { useTranslation } from '@dxos/react-ui';
|
|
9
|
-
import {
|
|
9
|
+
import { descriptionMessage, mx } from '@dxos/react-ui-theme';
|
|
10
10
|
|
|
11
11
|
import { PlankHeading, type PlankHeadingProps } from './PlankHeading';
|
|
12
12
|
import { PlankLoading } from './PlankLoading';
|
|
@@ -19,11 +19,7 @@ export const PlankContentError = ({ error }: { error?: Error }) => {
|
|
|
19
19
|
<div role='none' className='overflow-auto p-8 attention-surface grid place-items-center'>
|
|
20
20
|
<p
|
|
21
21
|
role='alert'
|
|
22
|
-
className={mx(
|
|
23
|
-
descriptionText,
|
|
24
|
-
'break-words border border-dashed border-separator rounded-lg p-8',
|
|
25
|
-
errorString.length < 256 && 'text-lg',
|
|
26
|
-
)}
|
|
22
|
+
className={mx(descriptionMessage, 'break-words rounded-lg p-8', errorString.length < 256 && 'text-lg')}
|
|
27
23
|
>
|
|
28
24
|
{error ? errorString : t('error fallback message')}
|
|
29
25
|
</p>
|
|
@@ -17,6 +17,8 @@ import { PLANK_COMPANION_TYPE, DeckAction, type ResolvedPart, type LayoutMode }
|
|
|
17
17
|
import { useBreakpoints } from '../../util';
|
|
18
18
|
import { soloInlinePadding } from '../fragments';
|
|
19
19
|
|
|
20
|
+
const MAX_COMPANIONS = 5;
|
|
21
|
+
|
|
20
22
|
export type PlankHeadingProps = {
|
|
21
23
|
id: string;
|
|
22
24
|
part: ResolvedPart;
|
|
@@ -92,9 +94,12 @@ export const PlankHeading = memo(
|
|
|
92
94
|
}
|
|
93
95
|
}, [actions, node, variant, graph]);
|
|
94
96
|
|
|
95
|
-
const handleAction = useCallback(
|
|
96
|
-
|
|
97
|
-
|
|
97
|
+
const handleAction = useCallback(
|
|
98
|
+
(action: StackItemSigilAction) => {
|
|
99
|
+
typeof action.data === 'function' && action.data?.({ parent: node, caller: DECK_PLUGIN });
|
|
100
|
+
},
|
|
101
|
+
[node],
|
|
102
|
+
);
|
|
98
103
|
|
|
99
104
|
const handlePlankAction = useCallback(
|
|
100
105
|
(eventType: DeckAction.PartAdjustment) => {
|
|
@@ -143,7 +148,10 @@ export const PlankHeading = memo(
|
|
|
143
148
|
classNames={[
|
|
144
149
|
'plb-1 border-be border-separator items-stretch gap-1 sticky inline-start-12 app-drag min-is-0 contain-layout',
|
|
145
150
|
part === 'solo' ? soloInlinePadding : 'pli-1',
|
|
151
|
+
layoutMode === 'solo--fullscreen' &&
|
|
152
|
+
'opacity-0 border-transparent hover:border-separator hover:opacity-100 transition-[border-color,opacity]',
|
|
146
153
|
]}
|
|
154
|
+
data-plank-heading
|
|
147
155
|
>
|
|
148
156
|
{companions && isCompanionNode ? (
|
|
149
157
|
<div role='none' className='flex-1 min-is-0 overflow-x-auto scrollbar-thin flex gap-1'>
|
|
@@ -152,7 +160,7 @@ export const PlankHeading = memo(
|
|
|
152
160
|
key={id}
|
|
153
161
|
data-id={id}
|
|
154
162
|
icon={icon}
|
|
155
|
-
iconOnly={node?.id !== id}
|
|
163
|
+
iconOnly={companions.length > MAX_COMPANIONS && node?.id !== id}
|
|
156
164
|
label={toLocalizedString(label, t)}
|
|
157
165
|
size={5}
|
|
158
166
|
variant={node?.id === id ? 'primary' : 'default'}
|
|
@@ -50,8 +50,11 @@ export const CloseSidebarButton = () => {
|
|
|
50
50
|
export const ToggleComplementarySidebarButton = ({ inR0, classNames }: ThemedClassName<{ inR0?: boolean }>) => {
|
|
51
51
|
const layoutContext = useCapability(DeckCapabilities.MutableDeckState);
|
|
52
52
|
const { t } = useTranslation(DECK_PLUGIN);
|
|
53
|
+
// TODO(thure): This should have a tooltip but is suppressed because focus is getting set on this twice when the app
|
|
54
|
+
// first mounts, causing even `suppressNextTooltip` not to have the intended effect.
|
|
53
55
|
return (
|
|
54
56
|
<IconButton
|
|
57
|
+
noTooltip
|
|
55
58
|
iconOnly
|
|
56
59
|
onClick={() =>
|
|
57
60
|
(layoutContext.complementarySidebarState =
|