carbon-react 153.8.0 → 154.1.0

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 (73) hide show
  1. package/esm/components/text-editor/__internal__/helpers.js +1 -1
  2. package/esm/components/text-editor/__internal__/plugins/EditorRef/editor-ref.plugin.d.ts +5 -0
  3. package/esm/components/text-editor/__internal__/plugins/EditorRef/editor-ref.plugin.js +17 -0
  4. package/esm/components/text-editor/__internal__/plugins/EditorRef/index.d.ts +1 -0
  5. package/esm/components/text-editor/__internal__/plugins/EditorRef/index.js +1 -0
  6. package/esm/components/text-editor/__internal__/plugins/Toolbar/toolbar.component.js +0 -1
  7. package/esm/components/text-editor/text-editor.component.d.ts +5 -1
  8. package/esm/components/text-editor/text-editor.component.js +19 -11
  9. package/esm/components/vertical-menu/index.d.ts +1 -0
  10. package/esm/components/vertical-menu/index.js +2 -1
  11. package/esm/components/vertical-menu/responsive-vertical-menu/__internal__/depth.context.d.ts +16 -0
  12. package/esm/components/vertical-menu/responsive-vertical-menu/__internal__/depth.context.js +47 -0
  13. package/esm/components/vertical-menu/responsive-vertical-menu/__internal__/focus.context.d.ts +24 -0
  14. package/esm/components/vertical-menu/responsive-vertical-menu/__internal__/focus.context.js +206 -0
  15. package/esm/components/vertical-menu/responsive-vertical-menu/index.d.ts +7 -0
  16. package/esm/components/vertical-menu/responsive-vertical-menu/index.js +4 -0
  17. package/esm/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu-divider/index.d.ts +2 -0
  18. package/esm/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu-divider/index.js +1 -0
  19. package/esm/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu-divider/responsive-vertical-menu-divider.component.d.ts +6 -0
  20. package/esm/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu-divider/responsive-vertical-menu-divider.component.js +19 -0
  21. package/esm/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu-divider/responsive-vertical-menu-divider.style.d.ts +8 -0
  22. package/esm/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu-divider/responsive-vertical-menu-divider.style.js +13 -0
  23. package/esm/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu-item/index.d.ts +2 -0
  24. package/esm/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu-item/index.js +1 -0
  25. package/esm/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu-item/responsive-vertical-menu-item.component.d.ts +23 -0
  26. package/esm/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu-item/responsive-vertical-menu-item.component.js +307 -0
  27. package/esm/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu-item/responsive-vertical-menu-item.style.d.ts +31 -0
  28. package/esm/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu-item/responsive-vertical-menu-item.style.js +217 -0
  29. package/esm/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu.component.d.ts +14 -0
  30. package/esm/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu.component.js +186 -0
  31. package/esm/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu.context.d.ts +28 -0
  32. package/esm/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu.context.js +32 -0
  33. package/esm/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu.style.d.ts +20 -0
  34. package/esm/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu.style.js +95 -0
  35. package/lib/components/text-editor/__internal__/helpers.js +1 -1
  36. package/lib/components/text-editor/__internal__/plugins/EditorRef/editor-ref.plugin.d.ts +5 -0
  37. package/lib/components/text-editor/__internal__/plugins/EditorRef/editor-ref.plugin.js +24 -0
  38. package/lib/components/text-editor/__internal__/plugins/EditorRef/index.d.ts +1 -0
  39. package/lib/components/text-editor/__internal__/plugins/EditorRef/index.js +13 -0
  40. package/lib/components/text-editor/__internal__/plugins/EditorRef/package.json +6 -0
  41. package/lib/components/text-editor/__internal__/plugins/Toolbar/toolbar.component.js +0 -1
  42. package/lib/components/text-editor/text-editor.component.d.ts +5 -1
  43. package/lib/components/text-editor/text-editor.component.js +19 -11
  44. package/lib/components/vertical-menu/index.d.ts +1 -0
  45. package/lib/components/vertical-menu/index.js +19 -0
  46. package/lib/components/vertical-menu/responsive-vertical-menu/__internal__/depth.context.d.ts +16 -0
  47. package/lib/components/vertical-menu/responsive-vertical-menu/__internal__/depth.context.js +58 -0
  48. package/lib/components/vertical-menu/responsive-vertical-menu/__internal__/focus.context.d.ts +24 -0
  49. package/lib/components/vertical-menu/responsive-vertical-menu/__internal__/focus.context.js +217 -0
  50. package/lib/components/vertical-menu/responsive-vertical-menu/index.d.ts +7 -0
  51. package/lib/components/vertical-menu/responsive-vertical-menu/index.js +33 -0
  52. package/lib/components/vertical-menu/responsive-vertical-menu/package.json +6 -0
  53. package/lib/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu-divider/index.d.ts +2 -0
  54. package/lib/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu-divider/index.js +13 -0
  55. package/lib/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu-divider/package.json +6 -0
  56. package/lib/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu-divider/responsive-vertical-menu-divider.component.d.ts +6 -0
  57. package/lib/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu-divider/responsive-vertical-menu-divider.component.js +27 -0
  58. package/lib/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu-divider/responsive-vertical-menu-divider.style.d.ts +8 -0
  59. package/lib/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu-divider/responsive-vertical-menu-divider.style.js +20 -0
  60. package/lib/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu-item/index.d.ts +2 -0
  61. package/lib/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu-item/index.js +13 -0
  62. package/lib/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu-item/package.json +6 -0
  63. package/lib/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu-item/responsive-vertical-menu-item.component.d.ts +23 -0
  64. package/lib/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu-item/responsive-vertical-menu-item.component.js +316 -0
  65. package/lib/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu-item/responsive-vertical-menu-item.style.d.ts +31 -0
  66. package/lib/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu-item/responsive-vertical-menu-item.style.js +226 -0
  67. package/lib/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu.component.d.ts +14 -0
  68. package/lib/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu.component.js +196 -0
  69. package/lib/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu.context.d.ts +28 -0
  70. package/lib/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu.context.js +42 -0
  71. package/lib/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu.style.d.ts +20 -0
  72. package/lib/components/vertical-menu/responsive-vertical-menu/responsive-vertical-menu.style.js +104 -0
  73. package/package.json +1 -1
@@ -14,7 +14,7 @@ import { markdownNodes, theme } from "./constants";
14
14
  const SerializeLexical = editor => {
15
15
  let htmlString;
16
16
  let json;
17
- editor.update(() => {
17
+ editor.read(() => {
18
18
  // Get the current editor state
19
19
  const editorState = editor.getEditorState();
20
20
  // Serialize the editor state to JSON
@@ -0,0 +1,5 @@
1
+ import { LexicalEditor } from "lexical";
2
+ declare const EditorRefPlugin: ({ setEditorRef, }: {
3
+ setEditorRef: (editor: LexicalEditor) => void;
4
+ }) => null;
5
+ export default EditorRefPlugin;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * This plugin allows retrieval of a reference to the current editor. It's useful
3
+ * for testing purposes, where tests might need to directly interact with the editor to
4
+ * emulate e.g. blurring.
5
+ */
6
+ import { useEffect } from "react";
7
+ import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
8
+ const EditorRefPlugin = ({
9
+ setEditorRef
10
+ }) => {
11
+ const [editor] = useLexicalComposerContext();
12
+ useEffect(() => {
13
+ setEditorRef(editor);
14
+ }, [editor, setEditorRef]);
15
+ return null;
16
+ };
17
+ export default EditorRefPlugin;
@@ -0,0 +1 @@
1
+ export { default } from "./editor-ref.plugin";
@@ -0,0 +1 @@
1
+ export { default } from "./editor-ref.plugin";
@@ -85,7 +85,6 @@ const Toolbar = ({
85
85
  "aria-label": locale.textEditor.toolbarAriaLabel(),
86
86
  "data-role": `${namespace}-toolbar`,
87
87
  id: `${namespace}-toolbar`,
88
- onFocus: e => e.stopPropagation(),
89
88
  ref: toolbarRef
90
89
  }, /*#__PURE__*/React.createElement(FormattingButtons, {
91
90
  "data-role": `${namespace}-formatting-buttons`
@@ -23,10 +23,14 @@ export interface TextEditorProps extends MarginProps, TagProps {
23
23
  labelText: string;
24
24
  /** The identifier for the Text Editor. This allows for the using of multiple Text Editors on a screen */
25
25
  namespace?: string;
26
+ /** The callback to fire when the editor loses focus */
27
+ onBlur?: (ev: React.FocusEvent<HTMLElement>) => void;
26
28
  /** The callback to fire when the Cancel button within the editor is pressed */
27
29
  onCancel?: () => void;
28
30
  /** The callback to fire when a change is registered within the editor */
29
31
  onChange?: (value: string, formattedValues: EditorFormattedValues) => void;
32
+ /** The callback to fire when the editor gains focus */
33
+ onFocus?: (ev: React.FocusEvent<HTMLElement>) => void;
30
34
  /** The callback to fire when a link is added into the editor */
31
35
  onLinkAdded?: (link: string, state: string) => void;
32
36
  /** The callback to fire when the Save button within the editor is pressed */
@@ -46,5 +50,5 @@ export interface TextEditorProps extends MarginProps, TagProps {
46
50
  /** The initial value of the editor, as a HTML string, or JSON */
47
51
  value?: string | undefined;
48
52
  }
49
- export declare const TextEditor: ({ characterLimit, error, footer, header, inputHint, isOptional, labelText, namespace, onCancel, onChange, onLinkAdded, onSave, placeholder, previews, readOnly, required, rows, warning, value, ...rest }: TextEditorProps) => React.JSX.Element;
53
+ export declare const TextEditor: ({ characterLimit, error, footer, header, inputHint, isOptional, labelText, namespace, onBlur, onCancel, onChange, onFocus, onLinkAdded, onSave, placeholder, previews, readOnly, required, rows, warning, value, ...rest }: TextEditorProps) => React.JSX.Element;
50
54
  export default TextEditor;
@@ -13,7 +13,6 @@ import { $getRoot } from "lexical";
13
13
  import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
14
14
  import { SerializeLexical, validateUrl } from "./__internal__/helpers";
15
15
  import Label from "../../__internal__/label";
16
- import useDebounce from "../../hooks/__internal__/useDebounce";
17
16
  import useLocale from "../../hooks/__internal__/useLocale";
18
17
  import Logger from "../../__internal__/utils/logger";
19
18
  import { COMPONENT_PREFIX, markdownNodes, theme } from "./__internal__/constants";
@@ -34,8 +33,10 @@ export const TextEditor = ({
34
33
  isOptional = false,
35
34
  labelText,
36
35
  namespace = COMPONENT_PREFIX,
36
+ onBlur,
37
37
  onCancel,
38
38
  onChange,
39
+ onFocus,
39
40
  onLinkAdded,
40
41
  onSave,
41
42
  placeholder,
@@ -74,7 +75,6 @@ export const TextEditor = ({
74
75
  return cleanup;
75
76
  }, [contentEditorRef]);
76
77
  const [cancelTrigger, setCancelTrigger] = useState(false);
77
- const debounceWaitTime = 500;
78
78
  const initialConfig = useMemo(() => {
79
79
  return {
80
80
  namespace,
@@ -88,7 +88,7 @@ export const TextEditor = ({
88
88
 
89
89
  // OnChangePlugin is tested separately
90
90
  /* istanbul ignore next */
91
- const handleChange = useDebounce(newState => {
91
+ const handleChange = useCallback(newState => {
92
92
  const currentTextContent = newState.read(() => $getRoot().getTextContent());
93
93
  if (onChange) {
94
94
  const formattedValues = editorRef.current ? SerializeLexical(editorRef.current) : {};
@@ -101,11 +101,10 @@ export const TextEditor = ({
101
101
  // If the character limit has been exceeded, show the character limit warning
102
102
  setCharacterLimitWarning(currentDiff < 0 ? locale.textEditor.characterLimit(Math.abs(currentDiff)) : undefined);
103
103
  }
104
- }, debounceWaitTime);
104
+ }, [characterLimit, locale.textEditor, onChange]);
105
105
  const handleCancel = useCallback(() => {
106
- const editor = editorRef.current;
107
106
  /* istanbul ignore next */
108
- const isEditable = editor?.isEditable() || false;
107
+ const isEditable = editorRef.current?.isEditable() || false;
109
108
  /* istanbul ignore if */
110
109
  if (!isEditable) return;
111
110
 
@@ -118,13 +117,12 @@ export const TextEditor = ({
118
117
 
119
118
  // Reset the value of the editor when the cancel trigger is updated (implements reset on cancel)
120
119
  useEffect(() => {
121
- const editor = editorRef.current;
122
120
  const safeValue = value || createEmpty();
123
121
 
124
122
  /* istanbul ignore else */
125
- if (editor) {
126
- const newEditorState = editor.parseEditorState(safeValue);
127
- editor.setEditorState(newEditorState);
123
+ if (editorRef.current) {
124
+ const newEditorState = editorRef.current.parseEditorState(safeValue);
125
+ editorRef.current.setEditorState(newEditorState);
128
126
  }
129
127
  }, [cancelTrigger, value]);
130
128
  const toolbarProps = useMemo(() => ({
@@ -133,7 +131,17 @@ export const TextEditor = ({
133
131
  onSave
134
132
  }), [handleCancel, namespace, onCancel, onSave]);
135
133
  return /*#__PURE__*/React.createElement(StyledTextEditorWrapper, _extends({
136
- "data-role": `${namespace}-editor-wrapper`
134
+ "data-role": `${namespace}-editor-wrapper`,
135
+ onBlur: ev => {
136
+ if (!ev.currentTarget.contains(ev.relatedTarget)) {
137
+ onBlur?.(ev);
138
+ }
139
+ },
140
+ onFocus: ev => {
141
+ if (!ev.currentTarget.contains(ev.relatedTarget)) {
142
+ onFocus?.(ev);
143
+ }
144
+ }
137
145
  }, filterStyledSystemMarginProps(rest), tagComponent("text-editor", rest)), /*#__PURE__*/React.createElement(TextEditorContext.Provider, {
138
146
  value: {
139
147
  onLinkAdded
@@ -6,3 +6,4 @@ export { default as VerticalMenuFullScreen } from "./vertical-menu-full-screen/v
6
6
  export type { VerticalMenuFullScreenProps } from "./vertical-menu-full-screen/vertical-menu-full-screen.component";
7
7
  export { default as VerticalMenuTrigger } from "./vertical-menu-trigger/vertical-menu-trigger.component";
8
8
  export type { VerticalMenuTriggerProps } from "./vertical-menu-trigger/vertical-menu-trigger.component";
9
+ export { ResponsiveVerticalMenu, ResponsiveVerticalMenuItem, ResponsiveVerticalMenuProvider, } from "./responsive-vertical-menu";
@@ -1,4 +1,5 @@
1
1
  export { default as VerticalMenu } from "./vertical-menu.component";
2
2
  export { default as VerticalMenuItem } from "./vertical-menu-item/vertical-menu-item.component";
3
3
  export { default as VerticalMenuFullScreen } from "./vertical-menu-full-screen/vertical-menu-full-screen.component";
4
- export { default as VerticalMenuTrigger } from "./vertical-menu-trigger/vertical-menu-trigger.component";
4
+ export { default as VerticalMenuTrigger } from "./vertical-menu-trigger/vertical-menu-trigger.component";
5
+ export { ResponsiveVerticalMenu, ResponsiveVerticalMenuItem, ResponsiveVerticalMenuProvider } from "./responsive-vertical-menu";
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Provides context which can be used to dynamcally track the depth of a component
3
+ * in the component tree. This is useful for components that need to know their depth
4
+ * in the tree for styling or layout purposes.
5
+ */
6
+ import React, { ReactNode } from "react";
7
+ declare const DepthContext: React.Context<number>;
8
+ export declare const useDepth: () => number;
9
+ export declare const DepthProvider: ({ children, value, }: {
10
+ children: ReactNode;
11
+ value?: number | undefined;
12
+ }) => React.JSX.Element;
13
+ export declare const IncreaseDepth: ({ children }: {
14
+ children: ReactNode;
15
+ }) => React.JSX.Element;
16
+ export default DepthContext;
@@ -0,0 +1,47 @@
1
+ /* eslint-disable no-console */
2
+ /**
3
+ * Provides context which can be used to dynamcally track the depth of a component
4
+ * in the component tree. This is useful for components that need to know their depth
5
+ * in the tree for styling or layout purposes.
6
+ */
7
+ import React, { createContext, useContext } from "react";
8
+
9
+ // Context to hold the current depth
10
+ const DepthContext = /*#__PURE__*/createContext(0);
11
+
12
+ // Hook to use the depth context
13
+ export const useDepth = () => {
14
+ // Get the current context value
15
+ const context = useContext(DepthContext);
16
+
17
+ // If context is undefined, it means the hook is being used outside of a DepthProvider
18
+ /* istanbul ignore next */
19
+ if (context === undefined) {
20
+ console.error("useDepth must be used within a DepthProvider. Please ensure you are using the correct context.");
21
+ // Return a default value to avoid breakages
22
+ return -1;
23
+ }
24
+
25
+ // Return the current depth value
26
+ return context;
27
+ };
28
+ export const DepthProvider = ({
29
+ children,
30
+ value = 0
31
+ }) => {
32
+ // Provide the current depth value to the context
33
+ return /*#__PURE__*/React.createElement(DepthContext.Provider, {
34
+ value: value
35
+ }, children);
36
+ };
37
+ export const IncreaseDepth = ({
38
+ children
39
+ }) => {
40
+ const currentDepth = useDepth();
41
+
42
+ // Increase the depth by 1 for the children
43
+ return /*#__PURE__*/React.createElement(DepthContext.Provider, {
44
+ value: currentDepth + 1
45
+ }, children);
46
+ };
47
+ export default DepthContext;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Provides context for managing focus within a menu component.
3
+ * This context allows for tracking expanded items, focusing on specific items,
4
+ * and moving focus in various directions (next, previous, parent, first child, last child).
5
+ */
6
+ import React, { ReactNode, RefObject } from "react";
7
+ interface MenuFocusContextType {
8
+ expandedItems: string[];
9
+ expandItem: (id: string, expand: boolean) => void;
10
+ focusedItemId: string | null;
11
+ getRegisteredItems: () => Array<{
12
+ id: string;
13
+ ref: RefObject<HTMLElement>;
14
+ parentId?: string;
15
+ }>;
16
+ focusItem: (id: string) => void;
17
+ moveFocus: (direction: "next" | "prev" | "parent" | "firstChild" | "lastChild") => void;
18
+ registerMenuItem: (id: string, ref: RefObject<HTMLElement>, parentId?: string) => void;
19
+ }
20
+ export declare const useMenuFocus: () => MenuFocusContextType;
21
+ export declare const MenuFocusProvider: ({ children }: {
22
+ children: ReactNode;
23
+ }) => React.JSX.Element;
24
+ export {};
@@ -0,0 +1,206 @@
1
+ /* eslint-disable no-console */
2
+ /**
3
+ * Provides context for managing focus within a menu component.
4
+ * This context allows for tracking expanded items, focusing on specific items,
5
+ * and moving focus in various directions (next, previous, parent, first child, last child).
6
+ */
7
+ import React, { createContext, useCallback, useContext, useRef, useState } from "react";
8
+ // Create a context for menu focus
9
+ const MenuFocusContext = /*#__PURE__*/createContext(undefined);
10
+
11
+ // Custom hook to use the MenuFocusContext
12
+ export const useMenuFocus = () => {
13
+ // Get the current context value
14
+ const context = useContext(MenuFocusContext);
15
+
16
+ // If context is undefined, it means the hook is being used outside of a MenuFocusProvider
17
+ /* istanbul ignore next */
18
+ if (!context) {
19
+ console.error("useMenuFocus must be used within a MenuFocusProvider");
20
+ // Return a default value to avoid breakages
21
+ return {
22
+ expandedItems: [],
23
+ expandItem: () => {},
24
+ focusedItemId: null,
25
+ getRegisteredItems: () => [],
26
+ focusItem: () => {},
27
+ moveFocus: () => {},
28
+ registerMenuItem: () => {}
29
+ };
30
+ }
31
+
32
+ // Return the current context value
33
+ return context;
34
+ };
35
+ export const MenuFocusProvider = ({
36
+ children
37
+ }) => {
38
+ // State to track expanded items
39
+ const [expandedItems, setExpandedItems] = useState([]);
40
+ // State to track the currently focused item
41
+ const [focusedItemId, setFocusedItemId] = useState(null);
42
+
43
+ // Ref to hold registered menu items
44
+ const menuItemsRef = useRef(new Map());
45
+
46
+ // Function to register a menu item
47
+ // This function takes an id, a ref to the item, and an optional parentId
48
+ // and stores the item in the menuItemsRef map.
49
+ // If a parentId is provided, it also updates the parent's childIds array
50
+ // to include the new item.
51
+ const registerMenuItem = useCallback((id, ref, parentId) => {
52
+ menuItemsRef.current.set(id, {
53
+ ref,
54
+ parentId,
55
+ childIds: []
56
+ });
57
+ if (parentId) {
58
+ const parentItem = menuItemsRef.current.get(parentId);
59
+ parentItem?.childIds.push(id);
60
+ }
61
+ }, []);
62
+
63
+ // Function to get all registered menu items
64
+ // This function returns an array of objects, each containing the id,
65
+ // ref, parentId, and childIds of the registered items.
66
+ const getRegisteredItems = useCallback(() => {
67
+ const items = Array.from(menuItemsRef.current.entries()).map(([id, {
68
+ ref,
69
+ parentId,
70
+ childIds
71
+ }]) => ({
72
+ id,
73
+ ref,
74
+ parentId,
75
+ childIds
76
+ }));
77
+ return items;
78
+ }, []);
79
+
80
+ // Function to focus on a specific menu item
81
+ // This function takes an id and focuses the corresponding item
82
+ // by calling the focus method on its ref.
83
+ // It also updates the focusedItemId state to the new id.
84
+ const focusItem = useCallback(id => {
85
+ const item = menuItemsRef.current.get(id);
86
+ /* istanbul ignore else */
87
+ if (item?.ref?.current) {
88
+ item.ref.current.focus();
89
+ setFocusedItemId(id);
90
+ }
91
+ }, []);
92
+
93
+ // Function to expand or collapse a menu item
94
+ // This function takes an id and a boolean value (expand)
95
+ // and updates the expandedItems state accordingly.
96
+ // If expand is true, it adds the id to the expandedItems array;
97
+ // otherwise, it removes the id from the array.
98
+ const expandItem = useCallback((id, expand) => {
99
+ if (expand) {
100
+ setExpandedItems(prev => [...prev, id]);
101
+ } else {
102
+ setExpandedItems(prev => prev.filter(itemId => itemId !== id));
103
+ }
104
+ }, []);
105
+
106
+ // Function to move focus in a specific direction
107
+ // This function takes a direction (next, prev, parent, firstChild, lastChild)
108
+ // and moves the focus accordingly.
109
+ // It uses the current focusedItemId to determine the current item
110
+ // and then finds the next item based on the direction.
111
+ // It also handles expanding/collapsing items as needed.
112
+ const moveFocus = useCallback(direction => {
113
+ /* istanbul ignore if */
114
+ if (!focusedItemId) return;
115
+
116
+ // Get the current item based on the focusedItemId
117
+ const currentItem = menuItemsRef.current.get(focusedItemId);
118
+
119
+ // If the current item is not found, return early
120
+ /* istanbul ignore if */
121
+ if (!currentItem) return;
122
+ let allItems = [];
123
+ let visibleItems = [];
124
+ let currentIndex = -1;
125
+ switch (direction) {
126
+ // Move focus to the parent item
127
+ case "parent":
128
+ /* istanbul ignore else */
129
+ if (currentItem.parentId) {
130
+ focusItem(currentItem.parentId);
131
+ }
132
+ break;
133
+
134
+ // Move focus to the first child
135
+ // If the current item has children and is not already expanded,
136
+ // expand it and focus on the first child
137
+ case "firstChild":
138
+ /* istanbul ignore else */
139
+ if (currentItem.childIds.length > 0) {
140
+ if (!expandedItems.includes(focusedItemId)) {
141
+ expandItem(focusedItemId, true);
142
+ }
143
+ focusItem(currentItem.childIds[0]);
144
+ }
145
+ break;
146
+
147
+ // Move focus to the last child
148
+ // Moving backwards through the menu works slightly differently:
149
+ // If the current item has children, get the last child. Before focusing on it,
150
+ // check if the last child is expanded. If it is, focus on its last child.
151
+ // If the last child is not expanded (i.e. it's just a link), focus on it directly.
152
+ case "lastChild":
153
+ /* istanbul ignore else */
154
+ if (currentItem.childIds.length > 0) {
155
+ const lastChild = currentItem.childIds[currentItem.childIds.length - 1];
156
+ /* istanbul ignore else */
157
+ if (lastChild) {
158
+ /* istanbul ignore if */
159
+ if (expandedItems.includes(lastChild)) {
160
+ const lastChildItem = getRegisteredItems().find(item => item.id === lastChild);
161
+ if (lastChildItem) {
162
+ focusItem(lastChildItem.childIds[lastChildItem.childIds.length - 1]);
163
+ }
164
+ } else {
165
+ focusItem(lastChild);
166
+ }
167
+ }
168
+ }
169
+ break;
170
+
171
+ // Move focus to the next or previous item
172
+ // This case handles both next and previous focus movement.
173
+ // Whilst this functionality is not currently used in the menu,
174
+ // it is included for completeness/future-proofing.
175
+ /* istanbul ignore next */
176
+ default:
177
+ allItems = Array.from(menuItemsRef.current.keys());
178
+ visibleItems = allItems.filter(id => {
179
+ const item = menuItemsRef.current.get(id);
180
+ if (!item || !item.parentId) return true;
181
+ const parentVisible = expandedItems.includes(item.parentId);
182
+ return parentVisible;
183
+ });
184
+ currentIndex = visibleItems.indexOf(focusedItemId);
185
+ if (currentIndex !== -1) {
186
+ const nextIndex = direction === "next" ? (currentIndex + 1) % visibleItems.length : (currentIndex - 1 + visibleItems.length) % visibleItems.length;
187
+ focusItem(visibleItems[nextIndex]);
188
+ }
189
+ break;
190
+ }
191
+ }, [focusedItemId, focusItem, expandedItems, expandItem, getRegisteredItems]);
192
+ const value = {
193
+ expandedItems,
194
+ expandItem,
195
+ focusedItemId,
196
+ focusItem,
197
+ getRegisteredItems,
198
+ registerMenuItem,
199
+ moveFocus
200
+ };
201
+
202
+ // Provide the current context value to the MenuFocusContext
203
+ return /*#__PURE__*/React.createElement(MenuFocusContext.Provider, {
204
+ value: value
205
+ }, children);
206
+ };
@@ -0,0 +1,7 @@
1
+ export { ResponsiveVerticalMenuProvider } from "./responsive-vertical-menu.context";
2
+ export { ResponsiveVerticalMenu } from "./responsive-vertical-menu.component";
3
+ export type { ResponsiveVerticalMenuProps } from "./responsive-vertical-menu.component";
4
+ export { ResponsiveVerticalMenuItem } from "./responsive-vertical-menu-item";
5
+ export type { ResponsiveVerticalMenuItemProps } from "./responsive-vertical-menu-item";
6
+ export { ResponsiveVerticalMenuDivider } from "./responsive-vertical-menu-divider";
7
+ export type { ResponsiveVerticalMenuDividerProps } from "./responsive-vertical-menu-divider";
@@ -0,0 +1,4 @@
1
+ export { ResponsiveVerticalMenuProvider } from "./responsive-vertical-menu.context";
2
+ export { ResponsiveVerticalMenu } from "./responsive-vertical-menu.component";
3
+ export { ResponsiveVerticalMenuItem } from "./responsive-vertical-menu-item";
4
+ export { ResponsiveVerticalMenuDivider } from "./responsive-vertical-menu-divider";
@@ -0,0 +1,2 @@
1
+ export { default as ResponsiveVerticalMenuDivider } from "./responsive-vertical-menu-divider.component";
2
+ export type { ResponsiveVerticalMenuDividerProps } from "./responsive-vertical-menu-divider.component";
@@ -0,0 +1 @@
1
+ export { default as ResponsiveVerticalMenuDivider } from "./responsive-vertical-menu-divider.component";
@@ -0,0 +1,6 @@
1
+ import React from "react";
2
+ import { MarginProps } from "styled-system";
3
+ export interface ResponsiveVerticalMenuDividerProps extends MarginProps {
4
+ }
5
+ export declare const ResponsiveVerticalMenuDivider: (props: ResponsiveVerticalMenuDividerProps) => React.JSX.Element;
6
+ export default ResponsiveVerticalMenuDivider;
@@ -0,0 +1,19 @@
1
+ import React from "react";
2
+ import { StyledResponsiveVerticalMenuDivider, StyledHr } from "./responsive-vertical-menu-divider.style";
3
+ import { filterStyledSystemMarginProps } from "../../../../style/utils";
4
+ import { useDepth } from "../__internal__/depth.context";
5
+ import { useResponsiveVerticalMenu } from "../responsive-vertical-menu.context";
6
+ export const ResponsiveVerticalMenuDivider = props => {
7
+ const depth = useDepth();
8
+ const {
9
+ responsiveMode
10
+ } = useResponsiveVerticalMenu();
11
+ return /*#__PURE__*/React.createElement(StyledResponsiveVerticalMenuDivider, {
12
+ "data-component": "responsive-vertical-menu-divider",
13
+ "data-element": "responsive-vertical-menu-divider",
14
+ "data-role": "responsive-vertical-menu-divider",
15
+ depth: depth,
16
+ responsive: responsiveMode
17
+ }, /*#__PURE__*/React.createElement(StyledHr, filterStyledSystemMarginProps(props)));
18
+ };
19
+ export default ResponsiveVerticalMenuDivider;
@@ -0,0 +1,8 @@
1
+ import { MarginProps } from "styled-system";
2
+ interface StyledResponsiveVerticalMenuDividerProps {
3
+ depth: number;
4
+ responsive?: boolean;
5
+ }
6
+ export declare const StyledResponsiveVerticalMenuDivider: import("styled-components").StyledComponent<"div", any, StyledResponsiveVerticalMenuDividerProps, never>;
7
+ export declare const StyledHr: import("styled-components").StyledComponent<"hr", any, MarginProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>>, never>;
8
+ export {};
@@ -0,0 +1,13 @@
1
+ import styled from "styled-components";
2
+ import { margin } from "styled-system";
3
+ export const StyledResponsiveVerticalMenuDivider = styled.div`
4
+ width: ${({
5
+ depth,
6
+ responsive
7
+ }) => depth > 0 && responsive ? "88%" : "100%"};
8
+ `;
9
+ export const StyledHr = styled.hr`
10
+ border-color: #ffffff33;
11
+ border-bottom: 1px;
12
+ ${margin}
13
+ `;
@@ -0,0 +1,2 @@
1
+ export { default as ResponsiveVerticalMenuItem } from "./responsive-vertical-menu-item.component";
2
+ export type { ResponsiveVerticalMenuItemProps } from "./responsive-vertical-menu-item.component";
@@ -0,0 +1 @@
1
+ export { default as ResponsiveVerticalMenuItem } from "./responsive-vertical-menu-item.component";
@@ -0,0 +1,23 @@
1
+ import React, { ReactNode } from "react";
2
+ import { MarginProps, PaddingProps } from "styled-system";
3
+ import { IconType } from "../../../icon";
4
+ interface BaseItemProps extends MarginProps, PaddingProps {
5
+ /** The content of the menu item. This will render the menu item as a parent menu. */
6
+ children?: ReactNode;
7
+ /** Custom icon to be displayed. Takes precedence over `icon` if both are specified. */
8
+ customIcon?: ReactNode;
9
+ /** The destination URL. Providing this will render the menu item as an anchor link. */
10
+ href?: string;
11
+ /** The Carbon icon to be displayed. Defers to `customIcon` if both are defined. */
12
+ icon?: IconType;
13
+ /** The unique identifier for the menu item. */
14
+ id: string;
15
+ /** The label for the menu item. */
16
+ label: string;
17
+ }
18
+ export interface ResponsiveVerticalMenuItemProps extends Omit<BaseItemProps, "id"> {
19
+ /** The unique identifier for the menu item. */
20
+ id?: string;
21
+ }
22
+ export declare const ResponsiveVerticalMenuItem: ({ children, id, label, ...props }: ResponsiveVerticalMenuItemProps) => React.JSX.Element;
23
+ export default ResponsiveVerticalMenuItem;