@dxos/react-ui-menu 0.8.4-main.21d9917 → 0.8.4-main.2244d791bb

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-menu",
3
- "version": "0.8.4-main.21d9917",
3
+ "version": "0.8.4-main.2244d791bb",
4
4
  "description": "Components for facilitating reactive and consistent menus.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -31,36 +31,36 @@
31
31
  "dependencies": {
32
32
  "@radix-ui/react-context": "1.1.1",
33
33
  "@radix-ui/react-use-controllable-state": "1.1.0",
34
- "@dxos/app-graph": "0.8.4-main.21d9917",
35
- "@dxos/effect": "0.8.4-main.21d9917",
36
- "@dxos/invariant": "0.8.4-main.21d9917",
37
- "@dxos/keyboard": "0.8.4-main.21d9917",
38
- "@dxos/util": "0.8.4-main.21d9917",
39
- "@dxos/react-ui-attention": "0.8.4-main.21d9917",
40
- "@dxos/ui-types": "0.8.4-main.21d9917"
34
+ "@dxos/app-graph": "0.8.4-main.2244d791bb",
35
+ "@dxos/effect": "0.8.4-main.2244d791bb",
36
+ "@dxos/invariant": "0.8.4-main.2244d791bb",
37
+ "@dxos/keyboard": "0.8.4-main.2244d791bb",
38
+ "@dxos/ui-types": "0.8.4-main.2244d791bb",
39
+ "@dxos/util": "0.8.4-main.2244d791bb",
40
+ "@dxos/react-ui-attention": "0.8.4-main.2244d791bb"
41
41
  },
42
42
  "devDependencies": {
43
- "@effect-atom/atom-react": "^0.4.6",
44
- "@effect/platform": "0.93.6",
43
+ "@effect-atom/atom-react": "^0.5.0",
44
+ "@effect/platform": "0.94.4",
45
45
  "@types/react": "~19.2.7",
46
46
  "@types/react-dom": "~19.2.3",
47
- "effect": "3.19.11",
47
+ "effect": "3.19.16",
48
48
  "react": "~19.2.3",
49
49
  "react-dom": "~19.2.3",
50
50
  "vite": "7.1.9",
51
- "@dxos/random": "0.8.4-main.21d9917",
52
- "@dxos/react-ui": "0.8.4-main.21d9917",
53
- "@dxos/storybook-utils": "0.8.4-main.21d9917",
54
- "@dxos/ui-theme": "0.8.4-main.21d9917"
51
+ "@dxos/random": "0.8.4-main.2244d791bb",
52
+ "@dxos/storybook-utils": "0.8.4-main.2244d791bb",
53
+ "@dxos/react-ui": "0.8.4-main.2244d791bb",
54
+ "@dxos/ui-theme": "0.8.4-main.2244d791bb"
55
55
  },
56
56
  "peerDependencies": {
57
- "@effect-atom/atom-react": "^0.4.6",
58
- "@effect/platform": "0.93.6",
59
- "effect": "3.19.11",
57
+ "@effect-atom/atom-react": "^0.5.0",
58
+ "@effect/platform": "0.94.4",
59
+ "effect": "3.19.16",
60
60
  "react": "~19.2.3",
61
61
  "react-dom": "~19.2.3",
62
- "@dxos/ui-theme": "0.8.4-main.21d9917",
63
- "@dxos/react-ui": "0.8.4-main.21d9917"
62
+ "@dxos/react-ui": "0.8.4-main.2244d791bb",
63
+ "@dxos/ui-theme": "0.8.4-main.2244d791bb"
64
64
  },
65
65
  "publishConfig": {
66
66
  "access": "public"
@@ -30,6 +30,7 @@ const MenuProvider = ({
30
30
  useGroupItems = menuContextDefaults.useGroupItems,
31
31
  iconSize = menuContextDefaults.iconSize,
32
32
  attendableId,
33
+ alwaysActive,
33
34
  onAction,
34
35
  }: MenuProviderProps) => {
35
36
  const { scope } = useMenuScope(undefined);
@@ -38,6 +39,7 @@ const MenuProvider = ({
38
39
  useGroupItems={useGroupItems}
39
40
  iconSize={iconSize}
40
41
  attendableId={attendableId}
42
+ alwaysActive={alwaysActive}
41
43
  onAction={onAction}
42
44
  scope={scope}
43
45
  >
@@ -21,7 +21,7 @@ faker.seed(1234);
21
21
  const meta = {
22
22
  title: 'ui/react-ui-menu/ToolbarMenu',
23
23
  component: ToolbarMenu,
24
- decorators: [withTheme, withRegistry],
24
+ decorators: [withTheme(), withRegistry],
25
25
  parameters: {
26
26
  translations,
27
27
  },
@@ -6,7 +6,7 @@ import React, { Fragment, useCallback } from 'react';
6
6
 
7
7
  import { Icon, Toolbar as NaturalToolbar, type ToolbarRootProps, useTranslation } from '@dxos/react-ui';
8
8
  import { useAttention } from '@dxos/react-ui-attention';
9
- import { mx, textBlockWidth, toolbarInactive, toolbarLayout } from '@dxos/ui-theme';
9
+ import { mx, textBlockWidth, toolbarLayout } from '@dxos/ui-theme';
10
10
  import { type MenuActionProperties } from '@dxos/ui-types';
11
11
 
12
12
  import { translationKey } from '../translations';
@@ -187,7 +187,7 @@ export const ToolbarMenu = ({
187
187
  ...props
188
188
  }: MenuScopedProps<ToolbarMenuProps>) => {
189
189
  const items = useMenuItems(undefined, undefined, 'ToolbarMenu', __menuScope);
190
- const { attendableId } = useMenu('ToolbarMenu', __menuScope);
190
+ const { attendableId, alwaysActive } = useMenu('ToolbarMenu', __menuScope);
191
191
  const { hasAttention } = useAttention(attendableId);
192
192
  const InnerRoot = textBlockWidthProp ? 'div' : Fragment;
193
193
  const innerRootProps = textBlockWidthProp
@@ -197,7 +197,8 @@ export const ToolbarMenu = ({
197
197
  return (
198
198
  <NaturalToolbar.Root
199
199
  {...props}
200
- classNames={[attendableId && !hasAttention && toolbarInactive, classNames]}
200
+ classNames={[attendableId, classNames]}
201
+ disabled={!alwaysActive && !hasAttention}
201
202
  layoutManaged={textBlockWidthProp}
202
203
  >
203
204
  <InnerRoot {...innerRootProps}>
@@ -9,33 +9,6 @@ import { Graph, Node } from '@dxos/app-graph';
9
9
 
10
10
  import { type MenuItem, type MenuItemGroup } from '../types';
11
11
 
12
- const edgesArrayToRecord = (edges: Graph.Edge[]): Record<string, Graph.Edges> => {
13
- return Object.fromEntries(
14
- Object.entries(
15
- edges.reduce((acc: Record<string, { inbound: string[]; outbound: string[] }>, { source, target }) => {
16
- if (!acc[source]) {
17
- acc[source] = { inbound: [], outbound: [] };
18
- }
19
- if (!acc[target]) {
20
- acc[target] = { inbound: [], outbound: [] };
21
- }
22
-
23
- const sourceEdges = acc[source];
24
- if (!sourceEdges.outbound.includes(target)) {
25
- sourceEdges.outbound.push(target);
26
- }
27
-
28
- const targetEdges = acc[target];
29
- if (!targetEdges.inbound.includes(source)) {
30
- targetEdges.inbound.push(source);
31
- }
32
-
33
- return acc;
34
- }, {}),
35
- ),
36
- );
37
- };
38
-
39
12
  export type ActionGraphNodes = Node.NodeArg<any>[];
40
13
  export type ActionGraphEdges = Graph.Edge[];
41
14
  export type ActionGraphProps = {
@@ -51,24 +24,16 @@ export const useMenuActions = (props: Atom.Atom<ActionGraphProps>): MenuActions
51
24
  const registry = useContext(RegistryContext);
52
25
  const menuGraphProps = useAtomValue(props);
53
26
 
54
- // Create graph once, then update it when props change.
55
- const graphRef = useRef<Graph.WritableGraph | null>(null);
56
- if (!graphRef.current) {
57
- graphRef.current = Graph.make({
58
- registry,
59
- nodes: menuGraphProps.nodes as Node.Node[],
60
- edges: edgesArrayToRecord(menuGraphProps.edges),
61
- });
27
+ // Create a new graph whenever props change to preserve correct order.
28
+ // (Graph.addEdges appends rather than replaces, which breaks ordering on updates.)
29
+ const graphRef = useRef<{ graph: Graph.WritableGraph; props: ActionGraphProps } | null>(null);
30
+ if (!graphRef.current || graphRef.current.props !== menuGraphProps) {
31
+ const newGraph = Graph.make({ registry });
32
+ newGraph.pipe(Graph.addNodes(menuGraphProps.nodes as Node.NodeArg<any>[]), Graph.addEdges(menuGraphProps.edges));
33
+ graphRef.current = { graph: newGraph, props: menuGraphProps };
62
34
  }
63
- const graph = graphRef.current;
64
35
 
65
- // Update graph nodes synchronously when props change (before render completes).
66
- // Using a ref to track the previous value to detect changes.
67
- const prevPropsRef = useRef(menuGraphProps);
68
- if (prevPropsRef.current !== menuGraphProps) {
69
- prevPropsRef.current = menuGraphProps;
70
- graph.pipe(Graph.addNodes(menuGraphProps.nodes), Graph.addEdges(menuGraphProps.edges));
71
- }
36
+ const graph = graphRef.current.graph;
72
37
 
73
38
  const useGroupItems = useCallback(
74
39
  (sourceNode?: MenuItemGroup) => {
package/src/types.ts CHANGED
@@ -33,6 +33,8 @@ export type MenuContextValue = {
33
33
  useGroupItems: MenuItemsResolver;
34
34
  iconSize: IconButtonProps['size'];
35
35
  attendableId?: string;
36
+ /** If true, the menu is always active regardless of attention state. */
37
+ alwaysActive?: boolean;
36
38
  /** Optional action executor. If provided, will be used instead of default execution. */
37
39
  onAction?: ActionExecutor;
38
40
  };