"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.TabList = void 0;
var Ariakit = _interopRequireWildcard(require("@ariakit/react"));
var _warning = _interopRequireDefault(require("@wordpress/warning"));
var _element = require("@wordpress/element");
var _context = require("./context");
var _styles = require("./styles");
var _clsx = _interopRequireDefault(require("clsx"));
var _jsxRuntime = require("react/jsx-runtime");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
/**
 * External dependencies
 */

/**
 * WordPress dependencies
 */

/**
 * Internal dependencies
 */

// TODO: move these into a separate utility file, for use in other components
// such as ToggleGroupControl.

/**
 * Any function.
 */

/**
 * Creates a stable callback function that has access to the latest state and
 * can be used within event handlers and effect callbacks. Throws when used in
 * the render phase.
 *
 * @example
 *
 * ```tsx
 * function Component(props) {
 *   const onClick = useEvent(props.onClick);
 *   React.useEffect(() => {}, [onClick]);
 * }
 * ```
 */
function useEvent(callback) {
  const ref = (0, _element.useRef)(() => {
    throw new Error('Cannot call an event handler while rendering.');
  });
  (0, _element.useInsertionEffect)(() => {
    ref.current = callback;
  });
  return (0, _element.useCallback)((...args) => ref.current?.(...args), []);
}

/**
 * `useResizeObserver` options.
 */

/**
 * Fires `onResize` when the target element is resized.
 *
 * **The element must not be stored in a ref**, else it won't be observed
 * or updated. Instead, it should be stored in a React state or equivalent.
 *
 * It sets up a `ResizeObserver` that tracks the element under the hood. The
 * target element can be changed dynamically, and the observer will be
 * updated accordingly.
 *
 * By default, `onResize` is called when the observer is set up, in addition
 * to when the element is resized. This behavior can be disabled with the
 * `fireOnObserve` option.
 *
 * @example
 *
 * ```tsx
 * const [ targetElement, setTargetElement ] = useState< HTMLElement | null >();
 *
 * useResizeObserver( targetElement, ( element ) => {
 *   console.log( 'Element resized:', element );
 * } );
 *
 * <div ref={ setTargetElement } />;
 * ```
 */
function useResizeObserver(
/**
 * The target element to observe. It can be changed dynamically.
 */
targetElement,
/**
 * Callback to fire when the element is resized. It will also be
 * called when the observer is set up, unless `fireOnObserve` is
 * set to `false`.
 */
onResize, {
  fireOnObserve = true
} = {}) {
  const onResizeEvent = useEvent(onResize);
  const observedElementRef = (0, _element.useRef)();
  const resizeObserverRef = (0, _element.useRef)();
  (0, _element.useEffect)(() => {
    if (targetElement === observedElementRef.current) {
      return;
    }
    observedElementRef.current = targetElement;

    // Set up a ResizeObserver.
    if (!resizeObserverRef.current) {
      resizeObserverRef.current = new ResizeObserver(() => {
        if (observedElementRef.current) {
          onResizeEvent(observedElementRef.current);
        }
      });
    }
    const {
      current: resizeObserver
    } = resizeObserverRef;

    // Observe new element.
    if (targetElement) {
      if (fireOnObserve) {
        onResizeEvent(targetElement);
      }
      resizeObserver.observe(targetElement);
    }
    return () => {
      // Unobserve previous element.
      if (observedElementRef.current) {
        resizeObserver.unobserve(observedElementRef.current);
      }
    };
  }, [fireOnObserve, onResizeEvent, targetElement]);
}

/**
 * The position and dimensions of an element, relative to its offset parent.
 */

/**
 * An `ElementOffsetRect` object with all values set to zero.
 */
const NULL_ELEMENT_OFFSET_RECT = {
  left: 0,
  top: 0,
  width: 0,
  height: 0
};

/**
 * Returns the position and dimensions of an element, relative to its offset
 * parent. This is useful in contexts where `getBoundingClientRect` is not
 * suitable, such as when the element is transformed.
 *
 * **Note:** the `left` and `right` values are adjusted due to a limitation
 * in the way the browser calculates the offset position of the element,
 * which can cause unwanted scrollbars to appear. This adjustment makes the
 * values potentially inaccurate within a range of 1 pixel.
 */
function getElementOffsetRect(element) {
  return {
    // The adjustments mentioned in the documentation above are necessary
    // because `offsetLeft` and `offsetTop` are rounded to the nearest pixel,
    // which can result in a position mismatch that causes unwanted overflow.
    // For context, see: https://github.com/WordPress/gutenberg/pull/61979
    left: Math.max(element.offsetLeft - 1, 0),
    top: Math.max(element.offsetTop - 1, 0),
    // This is a workaround to obtain these values with a sub-pixel precision,
    // since `offsetWidth` and `offsetHeight` are rounded to the nearest pixel.
    width: parseFloat(getComputedStyle(element).width),
    height: parseFloat(getComputedStyle(element).height)
  };
}

/**
 * Tracks the position and dimensions of an element, relative to its offset
 * parent. The element can be changed dynamically.
 */
function useTrackElementOffsetRect(targetElement) {
  const [indicatorPosition, setIndicatorPosition] = (0, _element.useState)(NULL_ELEMENT_OFFSET_RECT);
  useResizeObserver(targetElement, element => setIndicatorPosition(getElementOffsetRect(element)));
  return indicatorPosition;
}

/**
 * Context object for the `onUpdate` callback of `useOnValueUpdate`.
 */

/**
 * Calls the `onUpdate` callback when the `value` changes.
 */
function useOnValueUpdate(
/**
 * The value to watch for changes.
 */
value,
/**
 * Callback to fire when the value changes.
 */
onUpdate) {
  const previousValueRef = (0, _element.useRef)(value);
  const updateCallbackEvent = useEvent(onUpdate);
  (0, _element.useEffect)(() => {
    if (previousValueRef.current !== value) {
      updateCallbackEvent({
        previousValue: previousValueRef.current
      });
      previousValueRef.current = value;
    }
  }, [updateCallbackEvent, value]);
}
const TabList = exports.TabList = (0, _element.forwardRef)(function TabList({
  children,
  ...otherProps
}, ref) {
  const context = (0, _context.useTabsContext)();
  const selectedId = context?.store.useState('selectedId');
  const indicatorPosition = useTrackElementOffsetRect(context?.store.item(selectedId)?.element);
  const [animationEnabled, setAnimationEnabled] = (0, _element.useState)(false);
  useOnValueUpdate(selectedId, ({
    previousValue
  }) => previousValue && setAnimationEnabled(true));
  if (!context) {
    globalThis.SCRIPT_DEBUG === true ? (0, _warning.default)('`Tabs.TabList` must be wrapped in a `Tabs` component.') : void 0;
    return null;
  }
  const {
    store
  } = context;
  const {
    activeId,
    selectOnMove
  } = store.useState();
  const {
    setActiveId
  } = store;
  const onBlur = () => {
    if (!selectOnMove) {
      return;
    }

    // When automatic tab selection is on, make sure that the active tab is up
    // to date with the selected tab when leaving the tablist. This makes sure
    // that the selected tab will receive keyboard focus when tabbing back into
    // the tablist.
    if (selectedId !== activeId) {
      setActiveId(selectedId);
    }
  };
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(Ariakit.TabList, {
    ref: ref,
    store: store,
    render: /*#__PURE__*/(0, _jsxRuntime.jsx)(_styles.TabListWrapper, {
      onTransitionEnd: event => {
        if (event.pseudoElement === '::after') {
          setAnimationEnabled(false);
        }
      }
    }),
    onBlur: onBlur,
    ...otherProps,
    style: {
      '--indicator-left': `${indicatorPosition.left}px`,
      '--indicator-top': `${indicatorPosition.top}px`,
      '--indicator-width': `${indicatorPosition.width}px`,
      '--indicator-height': `${indicatorPosition.height}px`,
      ...otherProps.style
    },
    className: (0, _clsx.default)(animationEnabled ? 'is-animation-enabled' : '', otherProps.className),
    children: children
  });
});
//# sourceMappingURL=tablist.js.map