// packages/block-editor/src/components/list-view/block.js
import clsx from "clsx";
import {
  hasBlockSupport,
  switchToBlockType,
  store as blocksStore
} from "@wordpress/blocks";
import {
  __experimentalTreeGridCell as TreeGridCell,
  __experimentalTreeGridItem as TreeGridItem
} from "@wordpress/components";
import { useInstanceId, useDebounce } from "@wordpress/compose";
import { moreVertical } from "@wordpress/icons";
import {
  useCallback,
  useMemo,
  useState,
  useRef,
  memo
} from "@wordpress/element";
import { useDispatch, useSelect } from "@wordpress/data";
import { __ } from "@wordpress/i18n";
import { BACKSPACE, DELETE } from "@wordpress/keycodes";
import isShallowEqual from "@wordpress/is-shallow-equal";
import { __unstableUseShortcutEventMatch as useShortcutEventMatch } from "@wordpress/keyboard-shortcuts";
import { speak } from "@wordpress/a11y";
import ListViewLeaf from "./leaf";
import useListViewScrollIntoView from "./use-list-view-scroll-into-view";
import {
  BlockMoverUpButton,
  BlockMoverDownButton
} from "../block-mover/button";
import ListViewBlockContents from "./block-contents";
import { useListViewContext } from "./context";
import {
  getBlockPositionDescription,
  getBlockPropertiesDescription,
  focusListItem
} from "./utils";
import { store as blockEditorStore } from "../../store";
import useBlockDisplayInformation from "../use-block-display-information";
import { useBlockLock } from "../block-lock";
import AriaReferencedText from "./aria-referenced-text";
import { unlock } from "../../lock-unlock";
import usePasteStyles from "../use-paste-styles";
import { cleanEmptyObject } from "../../hooks/utils";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
function ListViewBlock({
  block: { clientId },
  displacement,
  isAfterDraggedBlocks,
  isDragged,
  isNesting,
  isSelected,
  isBranchSelected,
  selectBlock,
  position,
  level,
  rowCount,
  siblingBlockCount,
  showBlockMovers,
  path,
  isExpanded,
  selectedClientIds,
  isSyncedBranch
}) {
  const cellRef = useRef(null);
  const rowRef = useRef(null);
  const settingsRef = useRef(null);
  const [isHovered, setIsHovered] = useState(false);
  const [settingsAnchorRect, setSettingsAnchorRect] = useState();
  const { isLocked } = useBlockLock(clientId);
  const isFirstSelectedBlock = isSelected && selectedClientIds[0] === clientId;
  const isLastSelectedBlock = isSelected && selectedClientIds[selectedClientIds.length - 1] === clientId;
  const {
    toggleBlockHighlight,
    duplicateBlocks,
    multiSelect,
    replaceBlocks,
    removeBlocks,
    insertAfterBlock,
    insertBeforeBlock,
    setOpenedBlockSettingsMenu,
    updateBlockAttributes
  } = unlock(useDispatch(blockEditorStore));
  const debouncedToggleBlockHighlight = useDebounce(
    toggleBlockHighlight,
    50
  );
  const {
    canInsertBlockType,
    getSelectedBlockClientIds,
    getPreviousBlockClientId,
    getBlockRootClientId,
    getBlockOrder,
    getBlockParents,
    getBlocksByClientId,
    canEditBlock,
    canMoveBlock,
    canRemoveBlocks,
    isGroupable
  } = useSelect(blockEditorStore);
  const { getGroupingBlockName } = useSelect(blocksStore);
  const blockInformation = useBlockDisplayInformation(clientId);
  const pasteStyles = usePasteStyles();
  const { block, blockName, allowRightClickOverrides, isBlockHidden } = useSelect(
    (select) => {
      const { getBlock, getBlockName, getSettings } = select(blockEditorStore);
      const { isBlockHidden: _isBlockHidden } = unlock(
        select(blockEditorStore)
      );
      return {
        block: getBlock(clientId),
        blockName: getBlockName(clientId),
        allowRightClickOverrides: getSettings().allowRightClickOverrides,
        isBlockHidden: _isBlockHidden(clientId)
      };
    },
    [clientId]
  );
  const showBlockActions = (
    // When a block hides its toolbar it also hides the block settings menu,
    // since that menu is part of the toolbar in the editor canvas.
    // List View respects this by also hiding the block settings menu.
    hasBlockSupport(blockName, "__experimentalToolbar", true)
  );
  const instanceId = useInstanceId(ListViewBlock);
  const descriptionId = `list-view-block-select-button__description-${instanceId}`;
  const {
    expand,
    collapse,
    collapseAll,
    BlockSettingsMenu,
    listViewInstanceId,
    expandedState,
    setInsertedBlock,
    treeGridElementRef,
    rootClientId
  } = useListViewContext();
  const isMatch = useShortcutEventMatch();
  function getBlocksToUpdate() {
    const selectedBlockClientIds = getSelectedBlockClientIds();
    const isUpdatingSelectedBlocks = selectedBlockClientIds.includes(clientId);
    const firstBlockClientId = isUpdatingSelectedBlocks ? selectedBlockClientIds[0] : clientId;
    const firstBlockRootClientId = getBlockRootClientId(firstBlockClientId);
    const blocksToUpdate = isUpdatingSelectedBlocks ? selectedBlockClientIds : [clientId];
    return {
      blocksToUpdate,
      firstBlockClientId,
      firstBlockRootClientId,
      selectedBlockClientIds
    };
  }
  async function onKeyDown(event) {
    if (event.defaultPrevented) {
      return;
    }
    if (event.target.closest("[role=dialog]")) {
      return;
    }
    const isDeleteKey = [BACKSPACE, DELETE].includes(event.keyCode);
    if (isMatch("core/block-editor/unselect", event) && selectedClientIds.length > 0) {
      event.stopPropagation();
      event.preventDefault();
      selectBlock(event, void 0);
    } else if (isDeleteKey || isMatch("core/block-editor/remove", event)) {
      const {
        blocksToUpdate: blocksToDelete,
        firstBlockClientId,
        firstBlockRootClientId,
        selectedBlockClientIds
      } = getBlocksToUpdate();
      if (!canRemoveBlocks(blocksToDelete)) {
        return;
      }
      let blockToFocus = getPreviousBlockClientId(firstBlockClientId) ?? // If the previous block is not found (when the first block is deleted),
      // fallback to focus the parent block.
      firstBlockRootClientId;
      removeBlocks(blocksToDelete, false);
      const shouldUpdateSelection = selectedBlockClientIds.length > 0 && getSelectedBlockClientIds().length === 0;
      if (!blockToFocus) {
        blockToFocus = getBlockOrder()[0];
      }
      updateFocusAndSelection(blockToFocus, shouldUpdateSelection);
    } else if (isMatch("core/block-editor/paste-styles", event)) {
      event.preventDefault();
      const { blocksToUpdate } = getBlocksToUpdate();
      const blocks = getBlocksByClientId(blocksToUpdate);
      pasteStyles(blocks);
    } else if (isMatch("core/block-editor/duplicate", event)) {
      event.preventDefault();
      const { blocksToUpdate, firstBlockRootClientId } = getBlocksToUpdate();
      const canDuplicate = getBlocksByClientId(blocksToUpdate).every(
        (blockToUpdate) => {
          return !!blockToUpdate && hasBlockSupport(
            blockToUpdate.name,
            "multiple",
            true
          ) && canInsertBlockType(
            blockToUpdate.name,
            firstBlockRootClientId
          );
        }
      );
      if (canDuplicate) {
        const updatedBlocks = await duplicateBlocks(
          blocksToUpdate,
          false
        );
        if (updatedBlocks?.length) {
          updateFocusAndSelection(updatedBlocks[0], false);
        }
      }
    } else if (isMatch("core/block-editor/insert-before", event)) {
      event.preventDefault();
      const { blocksToUpdate } = getBlocksToUpdate();
      await insertBeforeBlock(blocksToUpdate[0]);
      const newlySelectedBlocks = getSelectedBlockClientIds();
      setOpenedBlockSettingsMenu(void 0);
      updateFocusAndSelection(newlySelectedBlocks[0], false);
    } else if (isMatch("core/block-editor/insert-after", event)) {
      event.preventDefault();
      const { blocksToUpdate } = getBlocksToUpdate();
      await insertAfterBlock(blocksToUpdate.at(-1));
      const newlySelectedBlocks = getSelectedBlockClientIds();
      setOpenedBlockSettingsMenu(void 0);
      updateFocusAndSelection(newlySelectedBlocks[0], false);
    } else if (isMatch("core/block-editor/select-all", event)) {
      event.preventDefault();
      const { firstBlockRootClientId, selectedBlockClientIds } = getBlocksToUpdate();
      const blockClientIds = getBlockOrder(firstBlockRootClientId);
      if (!blockClientIds.length) {
        return;
      }
      if (isShallowEqual(selectedBlockClientIds, blockClientIds)) {
        if (firstBlockRootClientId && firstBlockRootClientId !== rootClientId) {
          updateFocusAndSelection(firstBlockRootClientId, true);
          return;
        }
      }
      multiSelect(
        blockClientIds[0],
        blockClientIds[blockClientIds.length - 1],
        null
      );
    } else if (isMatch("core/block-editor/collapse-list-view", event)) {
      event.preventDefault();
      const { firstBlockClientId } = getBlocksToUpdate();
      const blockParents = getBlockParents(firstBlockClientId, false);
      collapseAll();
      expand(blockParents);
    } else if (isMatch("core/block-editor/group", event)) {
      const { blocksToUpdate } = getBlocksToUpdate();
      if (blocksToUpdate.length > 1 && isGroupable(blocksToUpdate)) {
        event.preventDefault();
        const blocks = getBlocksByClientId(blocksToUpdate);
        const groupingBlockName = getGroupingBlockName();
        const newBlocks = switchToBlockType(
          blocks,
          groupingBlockName
        );
        replaceBlocks(blocksToUpdate, newBlocks);
        speak(__("Selected blocks are grouped."));
        const newlySelectedBlocks = getSelectedBlockClientIds();
        setOpenedBlockSettingsMenu(void 0);
        updateFocusAndSelection(newlySelectedBlocks[0], false);
      }
    } else if (isMatch("core/block-editor/toggle-block-visibility", event)) {
      event.preventDefault();
      const { blocksToUpdate } = getBlocksToUpdate();
      const blocks = getBlocksByClientId(blocksToUpdate);
      const canToggleVisibility = blocks.every(
        (blockToUpdate) => hasBlockSupport(blockToUpdate.name, "visibility", true)
      );
      if (!canToggleVisibility) {
        return;
      }
      const hasHiddenBlock = blocks.some(
        (blockToUpdate) => blockToUpdate.attributes.metadata?.blockVisibility === false
      );
      const attributesByClientId = Object.fromEntries(
        blocks.map(({ clientId: mapClientId, attributes }) => [
          mapClientId,
          {
            metadata: cleanEmptyObject({
              ...attributes?.metadata,
              blockVisibility: hasHiddenBlock ? void 0 : false
            })
          }
        ])
      );
      updateBlockAttributes(blocksToUpdate, attributesByClientId, {
        uniqueByBlock: true
      });
    }
  }
  const onMouseEnter = useCallback(() => {
    setIsHovered(true);
    debouncedToggleBlockHighlight(clientId, true);
  }, [clientId, setIsHovered, debouncedToggleBlockHighlight]);
  const onMouseLeave = useCallback(() => {
    setIsHovered(false);
    debouncedToggleBlockHighlight(clientId, false);
  }, [clientId, setIsHovered, debouncedToggleBlockHighlight]);
  const selectEditorBlock = useCallback(
    (event) => {
      selectBlock(event, clientId);
      event.preventDefault();
    },
    [clientId, selectBlock]
  );
  const updateFocusAndSelection = useCallback(
    (focusClientId, shouldSelectBlock) => {
      if (shouldSelectBlock) {
        selectBlock(void 0, focusClientId, null, null);
      }
      focusListItem(focusClientId, treeGridElementRef?.current);
    },
    [selectBlock, treeGridElementRef]
  );
  const toggleExpanded = useCallback(
    (event) => {
      event.preventDefault();
      event.stopPropagation();
      if (isExpanded === true) {
        collapse(clientId);
      } else if (isExpanded === false) {
        expand(clientId);
      }
    },
    [clientId, expand, collapse, isExpanded]
  );
  const onContextMenu = useCallback(
    (event) => {
      if (showBlockActions && allowRightClickOverrides) {
        settingsRef.current?.click();
        setSettingsAnchorRect(
          new window.DOMRect(event.clientX, event.clientY, 0, 0)
        );
        event.preventDefault();
      }
    },
    [allowRightClickOverrides, settingsRef, showBlockActions]
  );
  const onMouseDown = useCallback(
    (event) => {
      if (allowRightClickOverrides && event.button === 2) {
        event.preventDefault();
      }
    },
    [allowRightClickOverrides]
  );
  const settingsPopoverAnchor = useMemo(() => {
    const { ownerDocument } = rowRef?.current || {};
    if (!settingsAnchorRect || !ownerDocument) {
      return void 0;
    }
    return {
      ownerDocument,
      getBoundingClientRect() {
        return settingsAnchorRect;
      }
    };
  }, [settingsAnchorRect]);
  const clearSettingsAnchorRect = useCallback(() => {
    setSettingsAnchorRect(void 0);
  }, [setSettingsAnchorRect]);
  useListViewScrollIntoView({
    isSelected,
    rowItemRef: rowRef,
    selectedClientIds
  });
  if (!block) {
    return null;
  }
  const blockPositionDescription = getBlockPositionDescription(
    position,
    siblingBlockCount,
    level
  );
  const blockPropertiesDescription = getBlockPropertiesDescription(
    blockInformation,
    isLocked
  );
  const blockVisibilityDescription = isBlockHidden ? __("Block is hidden.") : null;
  const hasSiblings = siblingBlockCount > 0;
  const hasRenderedMovers = showBlockMovers && hasSiblings;
  const moverCellClassName = clsx(
    "block-editor-list-view-block__mover-cell",
    { "is-visible": isHovered || isSelected }
  );
  const listViewBlockSettingsClassName = clsx(
    "block-editor-list-view-block__menu-cell",
    { "is-visible": isHovered || isFirstSelectedBlock }
  );
  let colSpan;
  if (hasRenderedMovers) {
    colSpan = 2;
  } else if (!showBlockActions) {
    colSpan = 3;
  }
  const classes = clsx({
    "is-selected": isSelected,
    "is-first-selected": isFirstSelectedBlock,
    "is-last-selected": isLastSelectedBlock,
    "is-branch-selected": isBranchSelected,
    "is-synced-branch": isSyncedBranch,
    "is-dragging": isDragged,
    "has-single-cell": !showBlockActions,
    "is-synced": blockInformation?.isSynced,
    "is-draggable": canMoveBlock,
    "is-displacement-normal": displacement === "normal",
    "is-displacement-up": displacement === "up",
    "is-displacement-down": displacement === "down",
    "is-after-dragged-blocks": isAfterDraggedBlocks,
    "is-nesting": isNesting
  });
  const dropdownClientIds = selectedClientIds.includes(clientId) ? selectedClientIds : [clientId];
  const currentlyEditingBlockInCanvas = isSelected && selectedClientIds.length === 1;
  return /* @__PURE__ */ jsxs(
    ListViewLeaf,
    {
      className: classes,
      isDragged,
      onKeyDown,
      onMouseEnter,
      onMouseLeave,
      onFocus: onMouseEnter,
      onBlur: onMouseLeave,
      level,
      position,
      rowCount,
      path,
      id: `list-view-${listViewInstanceId}-block-${clientId}`,
      "data-block": clientId,
      "data-expanded": canEditBlock ? isExpanded : void 0,
      ref: rowRef,
      children: [
        /* @__PURE__ */ jsx(
          TreeGridCell,
          {
            className: "block-editor-list-view-block__contents-cell",
            colSpan,
            ref: cellRef,
            "aria-selected": !!isSelected,
            children: ({ ref, tabIndex, onFocus }) => /* @__PURE__ */ jsxs("div", { className: "block-editor-list-view-block__contents-container", children: [
              /* @__PURE__ */ jsx(
                ListViewBlockContents,
                {
                  block,
                  onClick: selectEditorBlock,
                  onContextMenu,
                  onMouseDown,
                  onToggleExpanded: toggleExpanded,
                  isSelected,
                  position,
                  siblingBlockCount,
                  level,
                  ref,
                  tabIndex: currentlyEditingBlockInCanvas ? 0 : tabIndex,
                  onFocus,
                  isExpanded: canEditBlock ? isExpanded : void 0,
                  selectedClientIds,
                  ariaDescribedBy: descriptionId
                }
              ),
              /* @__PURE__ */ jsx(AriaReferencedText, { id: descriptionId, children: [
                blockPositionDescription,
                blockPropertiesDescription,
                blockVisibilityDescription
              ].filter(Boolean).join(" ") })
            ] })
          }
        ),
        hasRenderedMovers && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(
          TreeGridCell,
          {
            className: moverCellClassName,
            withoutGridItem: true,
            children: [
              /* @__PURE__ */ jsx(TreeGridItem, { children: ({ ref, tabIndex, onFocus }) => /* @__PURE__ */ jsx(
                BlockMoverUpButton,
                {
                  orientation: "vertical",
                  clientIds: [clientId],
                  ref,
                  tabIndex,
                  onFocus
                }
              ) }),
              /* @__PURE__ */ jsx(TreeGridItem, { children: ({ ref, tabIndex, onFocus }) => /* @__PURE__ */ jsx(
                BlockMoverDownButton,
                {
                  orientation: "vertical",
                  clientIds: [clientId],
                  ref,
                  tabIndex,
                  onFocus
                }
              ) })
            ]
          }
        ) }),
        showBlockActions && BlockSettingsMenu && /* @__PURE__ */ jsx(
          TreeGridCell,
          {
            className: listViewBlockSettingsClassName,
            "aria-selected": !!isSelected,
            ref: settingsRef,
            children: ({ ref, tabIndex, onFocus }) => /* @__PURE__ */ jsx(
              BlockSettingsMenu,
              {
                clientIds: dropdownClientIds,
                block,
                icon: moreVertical,
                label: __("Options"),
                popoverProps: {
                  anchor: settingsPopoverAnchor
                  // Used to position the settings at the cursor on right-click.
                },
                toggleProps: {
                  ref,
                  className: "block-editor-list-view-block__menu",
                  tabIndex,
                  onClick: clearSettingsAnchorRect,
                  onFocus,
                  size: "small"
                },
                disableOpenOnArrowDown: true,
                expand,
                expandedState,
                setInsertedBlock,
                __experimentalSelectBlock: updateFocusAndSelection
              }
            )
          }
        )
      ]
    }
  );
}
var block_default = memo(ListViewBlock);
export {
  block_default as default
};
//# sourceMappingURL=block.js.map
