"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = NavigableToolbar;
var _components = require("@wordpress/components");
var _element = require("@wordpress/element");
var _data = require("@wordpress/data");
var _deprecated = _interopRequireDefault(require("@wordpress/deprecated"));
var _dom = require("@wordpress/dom");
var _keyboardShortcuts = require("@wordpress/keyboard-shortcuts");
var _keycodes = require("@wordpress/keycodes");
var _store = require("../../store");
var _lockUnlock = require("../../lock-unlock");
var _jsxRuntime = require("react/jsx-runtime");
/**
 * WordPress dependencies
 */

/**
 * Internal dependencies
 */

function hasOnlyToolbarItem(elements) {
  const dataProp = 'toolbarItem';
  return !elements.some(element => !(dataProp in element.dataset));
}
function getAllFocusableToolbarItemsIn(container) {
  return Array.from(container.querySelectorAll('[data-toolbar-item]:not([disabled])'));
}
function hasFocusWithin(container) {
  return container.contains(container.ownerDocument.activeElement);
}
function focusFirstTabbableIn(container) {
  const [firstTabbable] = _dom.focus.tabbable.find(container);
  if (firstTabbable) {
    firstTabbable.focus({
      // When focusing newly mounted toolbars,
      // the position of the popover is often not right on the first render
      // This prevents the layout shifts when focusing the dialogs.
      preventScroll: true
    });
  }
}
function useIsAccessibleToolbar(toolbarRef) {
  /*
   * By default, we'll assume the starting accessible state of the Toolbar
   * is true, as it seems to be the most common case.
   *
   * Transitioning from an (initial) false to true state causes the
   * <Toolbar /> component to mount twice, which is causing undesired
   * side-effects. These side-effects appear to only affect certain
   * E2E tests.
   *
   * This was initial discovered in this pull-request:
   * https://github.com/WordPress/gutenberg/pull/23425
   */
  const initialAccessibleToolbarState = true;

  // By default, it's gonna render NavigableMenu. If all the tabbable elements
  // inside the toolbar are ToolbarItem components (or derived components like
  // ToolbarButton), then we can wrap them with the accessible Toolbar
  // component.
  const [isAccessibleToolbar, setIsAccessibleToolbar] = (0, _element.useState)(initialAccessibleToolbarState);
  const determineIsAccessibleToolbar = (0, _element.useCallback)(() => {
    const tabbables = _dom.focus.tabbable.find(toolbarRef.current);
    const onlyToolbarItem = hasOnlyToolbarItem(tabbables);
    if (!onlyToolbarItem) {
      (0, _deprecated.default)('Using custom components as toolbar controls', {
        since: '5.6',
        alternative: 'ToolbarItem, ToolbarButton or ToolbarDropdownMenu components',
        link: 'https://developer.wordpress.org/block-editor/components/toolbar-button/#inside-blockcontrols'
      });
    }
    setIsAccessibleToolbar(onlyToolbarItem);
  }, [toolbarRef]);
  (0, _element.useLayoutEffect)(() => {
    // Toolbar buttons may be rendered asynchronously, so we use
    // MutationObserver to check if the toolbar subtree has been modified.
    const observer = new window.MutationObserver(determineIsAccessibleToolbar);
    observer.observe(toolbarRef.current, {
      childList: true,
      subtree: true
    });
    return () => observer.disconnect();
  }, [determineIsAccessibleToolbar, isAccessibleToolbar, toolbarRef]);
  return isAccessibleToolbar;
}
function useToolbarFocus({
  toolbarRef,
  focusOnMount,
  isAccessibleToolbar,
  defaultIndex,
  onIndexChange,
  shouldUseKeyboardFocusShortcut,
  focusEditorOnEscape
}) {
  // Make sure we don't use modified versions of this prop.
  const [initialFocusOnMount] = (0, _element.useState)(focusOnMount);
  const [initialIndex] = (0, _element.useState)(defaultIndex);
  const focusToolbar = (0, _element.useCallback)(() => {
    focusFirstTabbableIn(toolbarRef.current);
  }, [toolbarRef]);
  const focusToolbarViaShortcut = () => {
    if (shouldUseKeyboardFocusShortcut) {
      focusToolbar();
    }
  };

  // Focus on toolbar when pressing alt+F10 when the toolbar is visible.
  (0, _keyboardShortcuts.useShortcut)('core/block-editor/focus-toolbar', focusToolbarViaShortcut);
  (0, _element.useEffect)(() => {
    if (initialFocusOnMount) {
      focusToolbar();
    }
  }, [isAccessibleToolbar, initialFocusOnMount, focusToolbar]);
  (0, _element.useEffect)(() => {
    // Store ref so we have access on useEffect cleanup: https://legacy.reactjs.org/blog/2020/08/10/react-v17-rc.html#effect-cleanup-timing
    const navigableToolbarRef = toolbarRef.current;
    // If initialIndex is passed, we focus on that toolbar item when the
    // toolbar gets mounted and initial focus is not forced.
    // We have to wait for the next browser paint because block controls aren't
    // rendered right away when the toolbar gets mounted.
    let raf = 0;

    // If the toolbar already had focus before the render, we don't want to move it.
    // https://github.com/WordPress/gutenberg/issues/58511
    if (!initialFocusOnMount && !hasFocusWithin(navigableToolbarRef)) {
      raf = window.requestAnimationFrame(() => {
        const items = getAllFocusableToolbarItemsIn(navigableToolbarRef);
        const index = initialIndex || 0;
        if (items[index] && hasFocusWithin(navigableToolbarRef)) {
          items[index].focus({
            // When focusing newly mounted toolbars,
            // the position of the popover is often not right on the first render
            // This prevents the layout shifts when focusing the dialogs.
            preventScroll: true
          });
        }
      });
    }
    return () => {
      window.cancelAnimationFrame(raf);
      if (!onIndexChange || !navigableToolbarRef) {
        return;
      }
      // When the toolbar element is unmounted and onIndexChange is passed, we
      // pass the focused toolbar item index so it can be hydrated later.
      const items = getAllFocusableToolbarItemsIn(navigableToolbarRef);
      const index = items.findIndex(item => item.tabIndex === 0);
      onIndexChange(index);
    };
  }, [initialIndex, initialFocusOnMount, onIndexChange, toolbarRef]);
  const {
    getLastFocus
  } = (0, _lockUnlock.unlock)((0, _data.useSelect)(_store.store));
  /**
   * Handles returning focus to the block editor canvas when pressing escape.
   */
  (0, _element.useEffect)(() => {
    const navigableToolbarRef = toolbarRef.current;
    if (focusEditorOnEscape) {
      const handleKeyDown = event => {
        const lastFocus = getLastFocus();
        if (event.keyCode === _keycodes.ESCAPE && lastFocus?.current) {
          // Focus the last focused element when pressing escape.
          event.preventDefault();
          lastFocus.current.focus();
        }
      };
      navigableToolbarRef.addEventListener('keydown', handleKeyDown);
      return () => {
        navigableToolbarRef.removeEventListener('keydown', handleKeyDown);
      };
    }
  }, [focusEditorOnEscape, getLastFocus, toolbarRef]);
}
function NavigableToolbar({
  children,
  focusOnMount,
  focusEditorOnEscape = false,
  shouldUseKeyboardFocusShortcut = true,
  __experimentalInitialIndex: initialIndex,
  __experimentalOnIndexChange: onIndexChange,
  orientation = 'horizontal',
  ...props
}) {
  const toolbarRef = (0, _element.useRef)();
  const isAccessibleToolbar = useIsAccessibleToolbar(toolbarRef);
  useToolbarFocus({
    toolbarRef,
    focusOnMount,
    defaultIndex: initialIndex,
    onIndexChange,
    isAccessibleToolbar,
    shouldUseKeyboardFocusShortcut,
    focusEditorOnEscape
  });
  if (isAccessibleToolbar) {
    return /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Toolbar, {
      label: props['aria-label'],
      ref: toolbarRef,
      orientation: orientation,
      ...props,
      children: children
    });
  }
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.NavigableMenu, {
    orientation: orientation,
    role: "toolbar",
    ref: toolbarRef,
    ...props,
    children: children
  });
}
//# sourceMappingURL=index.js.map