/* panel-splitter.tsx generated by @compiled/babel-plugin v0.38.1 */
import _extends from "@babel/runtime/helpers/extends";
import "./panel-splitter.compiled.css";
import * as React from 'react';
import { ax, ix } from "@compiled/react/runtime";
import { forwardRef, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
import { bind } from 'bind-event-listener';
import { createPortal } from 'react-dom';
import invariant from 'tiny-invariant';
import noop from '@atlaskit/ds-lib/noop';
import { useId } from '@atlaskit/ds-lib/use-id';
import useStableRef from '@atlaskit/ds-lib/use-stable-ref';
import { useOpenLayerObserver } from '@atlaskit/layering/experimental/open-layer-observer';
import { fg } from '@atlaskit/platform-feature-flags';
import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';
import { draggable } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
import { blockDraggingToIFrames } from '@atlaskit/pragmatic-drag-and-drop/element/block-dragging-to-iframes';
import { disableNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/disable-native-drag-preview';
import { preventUnhandled } from '@atlaskit/pragmatic-drag-and-drop/prevent-unhandled';
import Tooltip from '@atlaskit/tooltip';
import TooltipContainer from '@atlaskit/tooltip/TooltipContainer';
import VisuallyHidden from '@atlaskit/visually-hidden';
import { useIsFhsEnabled } from '../../fhs-rollout/use-is-fhs-enabled';
import { contentInsetBlockStart } from '../constants';
import { OnDoubleClickContext, PanelSplitterContext } from './context';
import { convertResizeBoundToPixels } from './convert-resize-bound-to-pixels';
import { getPercentageWithinPixelBounds } from './get-percentage-within-pixel-bounds';
import { getPixelWidth, getWidthFromDragLocation } from './get-width';
import { createKeyboardResizeManager } from './keyboard-resize-manager';
const containerStyles = {
  root: "_12ji1r31 _1qu2glyw _12y31o36 _1e0cglyw _kqswstnw _u7coidpf _152tidpf _1pbykb7n _181n1ule",
  positionEnd: "_rjxpftgi",
  positionStart: "_1e02zeo2"
};
const grabAreaStyles = {
  root: "_1bsbl52n _4t3i1osq _kqswstnw _1q51ze3t _y4tize3t _85i5ze3t _bozgze3t _syaz1j28 _bfhk1j28 _k8m01ylx _d0aluf7j _vp7g6x5g _30l36x5g _1gglglyw _9h8h12zz",
  fullHeightSidebar: "_d0al11mm",
  oldTransition: "_1oec1yx9 _1lww5cps _1gavfnf5 _1dr21ylx _1s5z1ylx _1cg4fnf5 _le1bfnf5",
  newTransition: "_1oec6ebc _1lww188d _6fl41ytf"
};
const lineStyles = {
  root: "_kqswstnw _1e0c1ule _1bsb1l7b _4t3i1osq _syaz1kw7 _bfhk1r31 _1e021v6z"
};
const tooltipStyles = {
  root: "_ahbq196n"
};
const panelSplitterDragDataSymbol = Symbol('panel-splitter-drag-data');
function signPanelSplitterDragData(data) {
  return {
    ...data,
    [panelSplitterDragDataSymbol]: true
  };
}
const PanelSplitterTooltip = /*#__PURE__*/forwardRef(({
  children,
  className,
  ...props
}, ref) => {
  const style = useMemo(() => {
    if (!props.style || !props.style.transform) {
      return props.style;
    }
    const [translateX, translateY] = props.style.transform.matchAll(/\d+px/g);
    if (!translateY) {
      // If we can't extract the translateY value we bail out and return the original style
      return props.style;
    }

    /**
     * Adjusts the translate Y to keep the tooltip within the main content area,
     * so that it does not appear over the banner or top navigation.
     */
    const newTranslateY = `max(calc(${contentInsetBlockStart} + ${"var(--ds-space-100, 8px)"}), ${translateY})`;
    const newTransform = `translate3d(${translateX}, ${newTranslateY}, 0)`;
    return {
      ...props.style,
      transform: newTransform
    };
  }, [props.style]);
  return /*#__PURE__*/React.createElement(TooltipContainer, _extends({}, props, {
    ref: ref
    // Must be statically passed
    // eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop, @atlaskit/design-system/no-unsafe-style-overrides
    ,
    className: ax([tooltipStyles.root, className])
    // eslint-disable-next-line @atlaskit/design-system/no-unsafe-style-overrides
    ,

    // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop
    style: style
  }), children);
});

/**
 * A wrapper component that renders a tooltip if the tooltipContent or shortcut is provided.
 */
const MaybeTooltip = ({
  tooltipContent,
  shortcut,
  children
}) => {
  const isFhsEnabled = useIsFhsEnabled();
  if (tooltipContent && isFhsEnabled) {
    return /*#__PURE__*/React.createElement(Tooltip, {
      content: tooltipContent,
      shortcut: shortcut,
      position: fg('platform_dst_nav4_side_nav_resize_tooltip_feedback') ? 'mouse-y' : 'mouse',
      mousePosition: "right",
      isScreenReaderAnnouncementDisabled: true,
      component: fg('platform_dst_nav4_side_nav_resize_tooltip_feedback') ? PanelSplitterTooltip : undefined,
      UNSAFE_shouldAlwaysFadeIn: fg('platform_dst_nav4_side_nav_resize_tooltip_feedback'),
      UNSAFE_shouldRenderToParent: fg('platform_dst_nav4_side_nav_resize_tooltip_feedback')
    }, children);
  }
  return children;
};
export function isPanelSplitterDragData(data) {
  return data[panelSplitterDragDataSymbol] === true;
}
function getTextDirection(element) {
  const {
    direction
  } = window.getComputedStyle(element);
  return direction === 'rtl' ? 'rtl' : 'ltr';
}
const PortaledPanelSplitter = ({
  label,
  onResizeStart,
  onResizeEnd,
  testId,
  panelId,
  panelWidth,
  onCompleteResize,
  getResizeBounds,
  panel,
  portal,
  resizingCssVar,
  position,
  tooltipContent,
  shortcut
}) => {
  const isFhsEnabled = useIsFhsEnabled();
  const splitterRef = useRef(null);
  const labelId = useId();
  // Separate state used for the input range width to remove the UI's dependency on the "persisted" layout state value being updated
  const [rangeInputValue, setRangeInputValue] = useState(panelWidth);

  /**
   * We are using state to store the resize bounds _converted into pixel units_, so that we can update them when the window is resized.
   * Otherwise they can become out of sync with the actual viewport size, and cause a broken experience (inaccurate width percentage)
   * when the user tries to resize using the keyboard.
   */
  const [rangeInputBounds, setRangeInputBounds] = useState({
    // Using placeholder values here for the initial render. These will be calculated and updated when the range input is focused.
    min: 200,
    max: 500
  });
  const openLayerObserver = useOpenLayerObserver();

  /**
   * This is a temporary workaround to enable the `SideNavPanelSplitter` component
   * to collapse the side nav on double click, without exposing the `onDoubleClick` prop on `PanelSplitter`.
   * Once `PanelSplitter` supports an `onDoubleClick` prop directly, this context can be removed.
   */
  const onDoubleClick = useContext(OnDoubleClickContext);

  // Storing the initial `clientX` on `mousedown` events, to workaround a bug caused by some browser extensions
  // where the `dragstart` event incorrectly returns `0` for the `clientX` location.
  const initialClientXRef = useRef(null);
  useEffect(() => {
    const splitter = splitterRef.current;
    invariant(splitter, 'Splitter ref must be set');
    return combine(blockDraggingToIFrames({
      element: splitter
    }),
    /**
     * Capturing the initial `clientX` from the mousedown, before the drag starts.
     *
     * ⚠️ Note: We are not using pragmatic-drag-and-drop's `onDragStart` for this as some browser
     * extensions can cause the client location (e.g. clientX) to incorrectly return 0 during the `dragstart` event.
     *
     * We are also not using pragmatic-drag-and-drop's `onDrag` here as it is throttled, which means
     * fast mouse movements can cause the real first drag event to be missed, causing a different clientX
     * to be captured.
     *
     * I also tried only binding an event listener inside pragmatic-drag-and-drop's `onDragStart`, which seemd to work
     * but did not feel as robust, and might have timing issues as it happens slightly later.
     */
    fg('platform-dst-panel-splitter-drag-start-client-x') ? bind(splitter, {
      type: 'mousedown',
      listener: event => {
        initialClientXRef.current = event.clientX;
      }
    }) : noop, draggable({
      element: splitter,
      onGenerateDragPreview: ({
        nativeSetDragImage
      }) => {
        // We will be moving the line to indicate a drag. We can disable the native drag preview
        disableNativeDragPreview({
          nativeSetDragImage
        });
        // We don't want any native drop animation for when the user does not drop on a drop target. We want the drag to finish immediately
        preventUnhandled.start();
      },
      getInitialData() {
        const initialWidth = getPixelWidth(panel);

        /**
         * The drag calculations require the actual computed width of the element
         * For example, if the panel has loaded with a width of 2000px, but the max bound is 1000px and is visually 1000px (due to the CSS `clamp()`),
         * the drag calculations should use the actual width of 1000px, not the width in state of 2000px.
         */
        return signPanelSplitterDragData({
          panelId,
          initialWidth,
          resizingWidth: `${initialWidth}px`,
          resizeBounds: getResizeBounds(),
          // Only computing text direction when we need it, just as the drag is starting.
          // Recomputing text direction on each new drag in case the text direction
          // has changed. This is unlikely, but being safe.
          direction: getTextDirection(panel)
        });
      },
      onDragStart({
        source
      }) {
        /**
         * ⚠️ Note: We are not using the client locations (e.g. clientX) during `onDragStart`
         * because some browser extensions can cause the event properties to incorrectly return 0.
         */
        invariant(isPanelSplitterDragData(source.data));
        onResizeStart === null || onResizeStart === void 0 ? void 0 : onResizeStart({
          initialWidth: source.data.initialWidth
        });

        // Close any open layers when the user starts resizing
        openLayerObserver === null || openLayerObserver === void 0 ? void 0 : openLayerObserver.closeLayers();
      },
      onDrag({
        location,
        source
      }) {
        var _initialClientXRef$cu;
        /**
         * ⚠️ Note: We are not using the location.initial.input client locations because some browser extensions
         * can cause the client locations (e.g. clientX) in the `dragstart` event to incorrectly return 0.
         */
        invariant(isPanelSplitterDragData(source.data));
        const {
          initialWidth,
          resizeBounds,
          direction
        } = source.data;
        if (fg('platform-dst-panel-splitter-drag-start-client-x')) {
          invariant(initialClientXRef.current !== null, 'initialClientX must be set');
        }

        /**
         * How wide the element would be if there were no width constraints,
         * based on the pointer's position.
         */
        const targetWidth = getWidthFromDragLocation({
          initialWidth,
          location,
          // The fallback of 0 won't be used due to the invariant, however we require one to satisfy the type.
          initialClientX: (_initialClientXRef$cu = initialClientXRef.current) !== null && _initialClientXRef$cu !== void 0 ? _initialClientXRef$cu : 0,
          direction,
          position
        });
        const resizingWidth = `clamp(${resizeBounds.min}, ${targetWidth}px, ${resizeBounds.max})`;
        panel.style.setProperty(resizingCssVar, resizingWidth);
        source.data.resizingWidth = resizingWidth;
      },
      onDrop({
        source
      }) {
        invariant(isPanelSplitterDragData(source.data));
        preventUnhandled.stop();
        const finalWidth = getPixelWidth(panel);
        onCompleteResize(finalWidth);
        onResizeEnd === null || onResizeEnd === void 0 ? void 0 : onResizeEnd({
          initialWidth: source.data.initialWidth,
          finalWidth
        });
        panel.style.removeProperty(resizingCssVar);
      }
    }));
  }, [onCompleteResize, onResizeStart, onResizeEnd, panel, resizingCssVar, panelWidth, position, openLayerObserver, panelId, getResizeBounds]);
  const onResizeStartStableRef = useStableRef(onResizeStart);
  const onResizeEndStableRef = useStableRef(onResizeEnd);
  const [keyboardResizeManager] = useState(() => createKeyboardResizeManager({
    onResizeStart: (...args) => {
      var _onResizeStartStableR;
      // Close any open layers when the user starts resizing with keyboard
      openLayerObserver === null || openLayerObserver === void 0 ? void 0 : openLayerObserver.closeLayers();
      (_onResizeStartStableR = onResizeStartStableRef.current) === null || _onResizeStartStableR === void 0 ? void 0 : _onResizeStartStableR.call(onResizeStartStableRef, ...args);
    },
    onResizeEnd: (...args) => {
      var _onResizeEndStableRef;
      return (_onResizeEndStableRef = onResizeEndStableRef.current) === null || _onResizeEndStableRef === void 0 ? void 0 : _onResizeEndStableRef.call(onResizeEndStableRef, ...args);
    }
  }));
  const handleSliderInputChange = useCallback(event => {
    const value = parseInt(event.target.value);
    setRangeInputValue(value);

    /**
     * We are intentionally _not_ debouncing `onCompleteResize` as we want it to be called anytime the user changes the range input value.
     * It is an internal-only callback - it's provided by `PanelSplitterProvider`, and used by layout slots to update their layout width state.
     */
    onCompleteResize(value);
    keyboardResizeManager.onResize({
      initialWidth: panelWidth,
      finalWidth: value
    });
  }, [onCompleteResize, panelWidth, keyboardResizeManager]);
  const resizeEventListenerCleanupFn = useRef(null);
  const handleSliderFocus = useCallback(() => {
    var _resizeEventListenerC;
    /**
     * We are only updating the range input's properties when the element is focused, as a performance optimisation.
     * It isn't necessary to recalculate these values until the element is going to be used.
     */

    // Clean up any left over listeners in case `focus` was triggered again without `blur`.
    (_resizeEventListenerC = resizeEventListenerCleanupFn.current) === null || _resizeEventListenerC === void 0 ? void 0 : _resizeEventListenerC.call(resizeEventListenerCleanupFn);

    // Update the range input value to match the current panel width.
    setRangeInputValue(panelWidth);
    const resizeBounds = getResizeBounds();

    // Update the range input bounds to ensure they are accurate (in case the viewport was previously resized)
    setRangeInputBounds({
      min: convertResizeBoundToPixels(resizeBounds.min),
      max: convertResizeBoundToPixels(resizeBounds.max)
    });

    /**
     * _While the slider element is focused_, add a window resize event listener to update the range input bounds
     * when the window is resized. This is needed to ensure the min and max widths accurately reflect the viewport
     * size - in case they are provided in vw units.
     *
     * This listener is cleaned up when the element is blurred.
     */
    resizeEventListenerCleanupFn.current = bind(window, {
      type: 'resize',
      listener: function updateState() {
        const resizeBounds = getResizeBounds();
        setRangeInputBounds({
          min: convertResizeBoundToPixels(resizeBounds.min),
          max: convertResizeBoundToPixels(resizeBounds.max)
        });
      }
    });
  }, [panelWidth, getResizeBounds]);
  const handleSliderBlur = useCallback(() => {
    var _resizeEventListenerC2;
    // Remove the resize event listener when the range input is blurred.
    (_resizeEventListenerC2 = resizeEventListenerCleanupFn.current) === null || _resizeEventListenerC2 === void 0 ? void 0 : _resizeEventListenerC2.call(resizeEventListenerCleanupFn);
  }, []);
  useEffect(() => {
    return function cleanup() {
      var _resizeEventListenerC3;
      // Cleanup the resize event listener when the component is unmounted.
      (_resizeEventListenerC3 = resizeEventListenerCleanupFn.current) === null || _resizeEventListenerC3 === void 0 ? void 0 : _resizeEventListenerC3.call(resizeEventListenerCleanupFn);
    };
  }, []);
  const ariaValueText = useMemo(() => `${getPercentageWithinPixelBounds({
    currentWidth: rangeInputValue,
    resizeBounds: rangeInputBounds
  })}% width`, [rangeInputValue, rangeInputBounds]);
  return /*#__PURE__*/createPortal( /*#__PURE__*/React.createElement("div", {
    "data-testid": testId ? `${testId}-container` : undefined,
    className: ax([containerStyles.root, position === 'start' && containerStyles.positionStart, position === 'end' && containerStyles.positionEnd])
  }, /*#__PURE__*/React.createElement(MaybeTooltip, {
    tooltipContent: tooltipContent,
    shortcut: shortcut
  }, /*#__PURE__*/React.createElement("div", {
    ref: splitterRef,
    "data-testid": testId,
    onDoubleClick: onDoubleClick,
    className: ax([grabAreaStyles.root, isFhsEnabled && grabAreaStyles.fullHeightSidebar, fg('platform_dst_nav4_side_nav_resize_tooltip_feedback') ? grabAreaStyles.newTransition : grabAreaStyles.oldTransition])
  }, /*#__PURE__*/React.createElement(VisuallyHidden, null, /*#__PURE__*/React.createElement("input", {
    type: "range",
    value: rangeInputValue,
    step: 20,
    min: rangeInputBounds.min,
    max: rangeInputBounds.max,
    "aria-valuetext": ariaValueText,
    "aria-labelledby": labelId,
    onChange: handleSliderInputChange,
    onFocus: handleSliderFocus,
    onBlur: handleSliderBlur
  }), /*#__PURE__*/React.createElement("span", {
    id: labelId
  }, label)), /*#__PURE__*/React.createElement("span", {
    className: ax([lineStyles.root])
  })))), portal);
};

/**
 * _PanelSplitter_
 *
 * A component that allows the user to resize a layout area.
 * It can be used within layout areas like `Panel`, and `Aside`.
 * For the `SideNav`, use the `SideNavPanelSplitter` component instead, as it provides additional functionality
 * such as double clicking to collapse the side nav.
 *
 * Example usage:
 * ```tsx
 * <Aside>
 *   <!-- other side nav content -->
 *   <PanelSplitter label="Resize Side Nav" />
 * </Aside>
 * ```
 */
export const PanelSplitter = ({
  label,
  onResizeStart,
  onResizeEnd,
  testId,
  tooltipContent
}) => {
  const [panel, setPanel] = useState(null);
  const [portal, setPortal] = useState(null);
  const context = useContext(PanelSplitterContext);
  invariant(context, 'Panel splitter context not set');
  const {
    panelRef,
    portalRef,
    isEnabled,
    panelId,
    panelWidth,
    onCompleteResize,
    getResizeBounds,
    resizingCssVar,
    position,
    shortcut
  } = context;

  /**
   * **Explanation**
   *
   * _How React Suspense works_
   *
   * When a component is suspended:
   *
   * - it is _not_ "unmounted" and effects are _not_ cleaned up
   * - refs to react managed elements are cleared
   * - react adds `display:none !important` as an inline style to hide the element
   * - state is preserved
   *
   * When a suspended component is resumed:
   * - `display:none !important` inline style is removed
   * - it will re-render (without the refs set)
   * - refs will be set
   * - `useEffect` will _not_ re-run
   *
   * [More details](https://x.com/alexandereardon/status/1944617494569992440)
   *
   * _What does this mean for the panel splitter?_
   *
   * - The panel splitter renders into content into a react managed element (eg managed by Aside)
   * - When UI is suspended the portal ref is cleared
   * - After a suspense, we will get a render with cleared refs
   * - 🔥 This is problematic if we want to portal into that ref during the render
   *
   * _Approach: initial render_
   *
   * - We cannot portal the panel splitter until after the refs are initially set
   * - Effects run after refs being set. In an effect we set the ref values (which are populated),
   *   trigger a re-render and then portal into the refs.
   *
   * _Approach: suspense_
   *
   * - We put the refs into react `state` after every render
   * - The refs in `state` will not be cleared by suspending as effects are not run, or cleaned up,
   *   when components are suspended / resumed.
   * - After the post suspense render, we can use the `portalRef` (which was captured in an effect)
   *   to continue to portal into.
   *
   * _Why not store `portalRef` in `state` in `<PanelSplitterProvider>`?_
   *
   * - The `portalRef` is set high in the tree, so a re-render will cause a
   *   large amount of components to re-render (eg the whole sidebar)
   * - After a suspense, we would need to do _another_ large re-render to propagate the new refs
   * - With our current approach, any changes to `portalRef` will cause a re-render low in the tree
   *   (just in the `<PanelSplitter>`)
   */

  // We want this effect to run after each render (see above)
  // eslint-disable-next-line react-hooks/exhaustive-deps
  useEffect(() => {
    // Don't need to do anything
    if (!isEnabled) {
      return;
    }

    // It is safe to access refs in useEffect, even with Suspense

    const panelEl = panelRef.current;
    const portalEl = portalRef.current;
    invariant(panelEl, 'panelRef not set');
    invariant(portalEl, 'portal element not set');

    // Doing our own check, as this results in fewer re-renders than react
    // (which also does it's own checks to see if a re-render should occur)
    if (panelEl === panel && portalEl === portal) {
      return;
    }

    // If refs don't change, react won't cause infinite loops
    setPanel(panelEl);
    setPortal(portalEl);
  });
  if (!isEnabled) {
    return null;
  }
  if (!portal || !panel) {
    return null;
  }
  return /*#__PURE__*/React.createElement(PortaledPanelSplitter, {
    label: label,
    onResizeStart: onResizeStart,
    onResizeEnd: onResizeEnd,
    testId: testId,
    panelId: panelId,
    panel: panel,
    portal: portal,
    panelWidth: panelWidth,
    onCompleteResize: onCompleteResize,
    getResizeBounds: getResizeBounds,
    resizingCssVar: resizingCssVar,
    position: position,
    tooltipContent: tooltipContent,
    shortcut: shortcut
  });
};