import { emptyRenderFn, ref, computed, onBeforeUnmount, onMounted, inject, withDirectives, Ripple, h, tabsKey, stopAndPrevent, isKeyCode, shouldIgnoreKey, QIcon, hMergeSlot, getCurrentInstance, isDeepEqual, createComponent, watch, onDeactivated, onActivated, hSlot, provide, hDir } from './__federation_expose_Modules-a9c6a6fc.js';
import { uid } from './uid-8cc7ec44.js';
import { QResizeObserver } from './QResizeObserver-41bc340b.js';
import { useTick, useTimeout } from './use-timeout-6c040b87.js';
import { rtlHasScrollBug } from './rtl-cff1c3fe.js';
import { usePanelChildProps, usePanelProps, usePanelEmits, usePanel } from './use-panel-471006fb.js';
import { useDarkProps, useDark } from './use-dark-b88930fe.js';

let id = 0;

const useTabEmits = [ 'click', 'keydown' ];

const useTabProps = {
  icon: String,
  label: [ Number, String ],

  alert: [ Boolean, String ],
  alertIcon: String,

  name: {
    type: [ Number, String ],
    default: () => `t_${ id++ }`
  },

  noCaps: Boolean,

  tabindex: [ String, Number ],
  disable: Boolean,

  contentClass: String,

  ripple: {
    type: [ Boolean, Object ],
    default: true
  }
};

function useTab (props, slots, emit, routeData) {
  const $tabs = inject(tabsKey, emptyRenderFn);
  if ($tabs === emptyRenderFn) {
    console.error('QTab/QRouteTab component needs to be child of QTabs');
    return emptyRenderFn
  }

  const { proxy } = getCurrentInstance();

  const blurTargetRef = ref(null);
  const rootRef = ref(null);
  const tabIndicatorRef = ref(null);

  const ripple = computed(() => (
    props.disable === true || props.ripple === false
      ? false
      : Object.assign(
        { keyCodes: [ 13, 32 ], early: true },
        props.ripple === true ? {} : props.ripple
      )
  ));

  const isActive = computed(() => $tabs.currentModel.value === props.name);

  const classes = computed(() =>
    'q-tab relative-position self-stretch flex flex-center text-center'
    + (
      isActive.value === true
        ? (
            ' q-tab--active'
            + ($tabs.tabProps.value.activeClass ? ' ' + $tabs.tabProps.value.activeClass : '')
            + ($tabs.tabProps.value.activeColor ? ` text-${ $tabs.tabProps.value.activeColor }` : '')
            + ($tabs.tabProps.value.activeBgColor ? ` bg-${ $tabs.tabProps.value.activeBgColor }` : '')
          )
        : ' q-tab--inactive'
    )
    + (props.icon && props.label && $tabs.tabProps.value.inlineLabel === false ? ' q-tab--full' : '')
    + (props.noCaps === true || $tabs.tabProps.value.noCaps === true ? ' q-tab--no-caps' : '')
    + (props.disable === true ? ' disabled' : ' q-focusable q-hoverable cursor-pointer')
    + (routeData !== void 0 ? routeData.linkClass.value : '')
  );

  const innerClass = computed(() =>
    'q-tab__content self-stretch flex-center relative-position q-anchor--skip non-selectable '
    + ($tabs.tabProps.value.inlineLabel === true ? 'row no-wrap q-tab__content--inline' : 'column')
    + (props.contentClass !== void 0 ? ` ${ props.contentClass }` : '')
  );

  const tabIndex = computed(() => (
    (
      props.disable === true
      || $tabs.hasFocus.value === true
      || (isActive.value === false && $tabs.hasActiveTab.value === true)
    )
      ? -1
      : props.tabindex || 0
  ));

  function onClick (e, keyboard) {
    if (keyboard !== true && blurTargetRef.value !== null) {
      blurTargetRef.value.focus();
    }

    if (props.disable === true) {
      // we should hinder native navigation though
      if (routeData !== void 0 && routeData.hasRouterLink.value === true) {
        stopAndPrevent(e);
      }
      return
    }

    // do we have a QTab?
    if (routeData === void 0) {
      $tabs.updateModel({ name: props.name });
      emit('click', e);
      return
    }

    if (routeData.hasRouterLink.value === true) {
      const go = (opts = {}) => {
        // if requiring to go to another route, then we
        // let the QTabs route watcher do its job,
        // otherwise directly select this
        let hardError;
        const reqId = opts.to === void 0 || isDeepEqual(opts.to, props.to) === true
          ? ($tabs.avoidRouteWatcher = uid())
          : null;

        return routeData.navigateToRouterLink(e, { ...opts, returnRouterError: true })
          .catch(err => { hardError = err; })
          .then(softError => {
            if (reqId === $tabs.avoidRouteWatcher) {
              $tabs.avoidRouteWatcher = false;

              // if we don't have any hard errors or any soft errors, except for
              // when navigating to the same route (on all other soft errors,
              // like when navigation was aborted in a nav guard, we don't activate this tab)
              if (
                hardError === void 0 && (
                  softError === void 0
                  || softError.message.startsWith('Avoided redundant navigation') === true
                )
              ) {
                $tabs.updateModel({ name: props.name });
              }
            }

            if (opts.returnRouterError === true) {
              return hardError !== void 0 ? Promise.reject(hardError) : softError
            }
          })
      };

      emit('click', e, go);
      e.defaultPrevented !== true && go();

      return
    }

    emit('click', e);
  }

  function onKeydown (e) {
    if (isKeyCode(e, [ 13, 32 ])) {
      onClick(e, true);
    }
    else if (
      shouldIgnoreKey(e) !== true
      && e.keyCode >= 35
      && e.keyCode <= 40
      && e.altKey !== true
      && e.metaKey !== true
    ) {
      $tabs.onKbdNavigate(e.keyCode, proxy.$el) === true && stopAndPrevent(e);
    }

    emit('keydown', e);
  }

  function getContent () {
    const
      narrow = $tabs.tabProps.value.narrowIndicator,
      content = [],
      indicator = h('div', {
        ref: tabIndicatorRef,
        class: [
          'q-tab__indicator',
          $tabs.tabProps.value.indicatorClass
        ]
      });

    props.icon !== void 0 && content.push(
      h(QIcon, {
        class: 'q-tab__icon',
        name: props.icon
      })
    );

    props.label !== void 0 && content.push(
      h('div', { class: 'q-tab__label' }, props.label)
    );

    props.alert !== false && content.push(
      props.alertIcon !== void 0
        ? h(QIcon, {
          class: 'q-tab__alert-icon',
          color: props.alert !== true
            ? props.alert
            : void 0,
          name: props.alertIcon
        })
        : h('div', {
          class: 'q-tab__alert'
            + (props.alert !== true ? ` text-${ props.alert }` : '')
        })
    );

    narrow === true && content.push(indicator);

    const node = [
      h('div', { class: 'q-focus-helper', tabindex: -1, ref: blurTargetRef }),
      h('div', { class: innerClass.value }, hMergeSlot(slots.default, content))
    ];

    narrow === false && node.push(indicator);

    return node
  }

  const tabData = {
    name: computed(() => props.name),
    rootRef,
    tabIndicatorRef,
    routeData
  };

  onBeforeUnmount(() => {
    $tabs.unregisterTab(tabData);
  });

  onMounted(() => {
    $tabs.registerTab(tabData);
  });

  function renderTab (tag, customData) {
    const data = {
      ref: rootRef,
      class: classes.value,
      tabindex: tabIndex.value,
      role: 'tab',
      'aria-selected': isActive.value === true ? 'true' : 'false',
      'aria-disabled': props.disable === true ? 'true' : void 0,
      onClick,
      onKeydown,
      ...customData
    };

    return withDirectives(
      h(tag, data, getContent()),
      [ [ Ripple, ripple.value ] ]
    )
  }

  return { renderTab, $tabs }
}

const QTab = createComponent({
  name: 'QTab',

  props: useTabProps,

  emits: useTabEmits,

  setup (props, { slots, emit }) {
    const { renderTab } = useTab(props, slots, emit);
    return () => renderTab('div')
  }
});

function getIndicatorClass (color, top, vertical) {
  const pos = vertical === true
    ? [ 'left', 'right' ]
    : [ 'top', 'bottom' ];

  return `absolute-${ top === true ? pos[ 0 ] : pos[ 1 ] }${ color ? ` text-${ color }` : '' }`
}

const alignValues = [ 'left', 'center', 'right', 'justify' ];

const QTabs = createComponent({
  name: 'QTabs',

  props: {
    modelValue: [ Number, String ],

    align: {
      type: String,
      default: 'center',
      validator: v => alignValues.includes(v)
    },
    breakpoint: {
      type: [ String, Number ],
      default: 600
    },

    vertical: Boolean,
    shrink: Boolean,
    stretch: Boolean,

    activeClass: String,
    activeColor: String,
    activeBgColor: String,
    indicatorColor: String,
    leftIcon: String,
    rightIcon: String,

    outsideArrows: Boolean,
    mobileArrows: Boolean,

    switchIndicator: Boolean,

    narrowIndicator: Boolean,
    inlineLabel: Boolean,
    noCaps: Boolean,

    dense: Boolean,

    contentClass: String,

    'onUpdate:modelValue': [ Function, Array ]
  },

  setup (props, { slots, emit }) {
    const { proxy } = getCurrentInstance();
    const { $q } = proxy;

    const { registerTick: registerScrollTick } = useTick();
    const { registerTick: registerUpdateArrowsTick } = useTick();
    const { registerTick: registerAnimateTick } = useTick();

    const { registerTimeout: registerFocusTimeout, removeTimeout: removeFocusTimeout } = useTimeout();
    const { registerTimeout: registerScrollToTabTimeout, removeTimeout: removeScrollToTabTimeout } = useTimeout();

    const rootRef = ref(null);
    const contentRef = ref(null);

    const currentModel = ref(props.modelValue);
    const scrollable = ref(false);
    const leftArrow = ref(true);
    const rightArrow = ref(false);
    const justify = ref(false);

    const tabDataList = [];
    const tabDataListLen = ref(0);
    const hasFocus = ref(false);

    let animateTimer = null, scrollTimer = null, unwatchRoute;

    const tabProps = computed(() => ({
      activeClass: props.activeClass,
      activeColor: props.activeColor,
      activeBgColor: props.activeBgColor,
      indicatorClass: getIndicatorClass(
        props.indicatorColor,
        props.switchIndicator,
        props.vertical
      ),
      narrowIndicator: props.narrowIndicator,
      inlineLabel: props.inlineLabel,
      noCaps: props.noCaps
    }));

    const hasActiveTab = computed(() => {
      const len = tabDataListLen.value;
      const val = currentModel.value;

      for (let i = 0; i < len; i++) {
        if (tabDataList[ i ].name.value === val) {
          return true
        }
      }

      return false
    });

    const alignClass = computed(() => {
      const align = scrollable.value === true
        ? 'left'
        : (justify.value === true ? 'justify' : props.align);

      return `q-tabs__content--align-${ align }`
    });

    const classes = computed(() =>
      'q-tabs row no-wrap items-center'
      + ` q-tabs--${ scrollable.value === true ? '' : 'not-' }scrollable`
      + ` q-tabs--${ props.vertical === true ? 'vertical' : 'horizontal' }`
      + ` q-tabs__arrows--${ props.outsideArrows === true ? 'outside' : 'inside' }`
      + ` q-tabs--mobile-with${ props.mobileArrows === true ? '' : 'out' }-arrows`
      + (props.dense === true ? ' q-tabs--dense' : '')
      + (props.shrink === true ? ' col-shrink' : '')
      + (props.stretch === true ? ' self-stretch' : '')
    );

    const innerClass = computed(() =>
      'q-tabs__content scroll--mobile row no-wrap items-center self-stretch hide-scrollbar relative-position '
      + alignClass.value
      + (props.contentClass !== void 0 ? ` ${ props.contentClass }` : '')
    );

    const domProps = computed(() => (
      props.vertical === true
        ? { container: 'height', content: 'offsetHeight', scroll: 'scrollHeight' }
        : { container: 'width', content: 'offsetWidth', scroll: 'scrollWidth' }
    ));

    const isRTL = computed(() => props.vertical !== true && $q.lang.rtl === true);
    const rtlPosCorrection = computed(() => rtlHasScrollBug === false && isRTL.value === true);

    watch(isRTL, updateArrows);

    watch(() => props.modelValue, name => {
      updateModel({ name, setCurrent: true, skipEmit: true });
    });

    watch(() => props.outsideArrows, recalculateScroll);

    function updateModel ({ name, setCurrent, skipEmit }) {
      if (currentModel.value !== name) {
        if (skipEmit !== true && props[ 'onUpdate:modelValue' ] !== void 0) {
          emit('update:modelValue', name);
        }

        if (
          setCurrent === true
          || props[ 'onUpdate:modelValue' ] === void 0
        ) {
          animate(currentModel.value, name);
          currentModel.value = name;
        }
      }
    }

    function recalculateScroll () {
      registerScrollTick(() => {
        updateContainer({
          width: rootRef.value.offsetWidth,
          height: rootRef.value.offsetHeight
        });
      });
    }

    function updateContainer (domSize) {
      // it can be called faster than component being initialized
      // so we need to protect against that case
      // (one example of such case is the docs release notes page)
      if (domProps.value === void 0 || contentRef.value === null) { return }

      const
        size = domSize[ domProps.value.container ],
        scrollSize = Math.min(
          contentRef.value[ domProps.value.scroll ],
          Array.prototype.reduce.call(
            contentRef.value.children,
            (acc, el) => acc + (el[ domProps.value.content ] || 0),
            0
          )
        ),
        scroll = size > 0 && scrollSize > size; // when there is no tab, in Chrome, size === 0 and scrollSize === 1

      scrollable.value = scroll;

      // Arrows need to be updated even if the scroll status was already true
      scroll === true && registerUpdateArrowsTick(updateArrows);

      justify.value = size < parseInt(props.breakpoint, 10);
    }

    function animate (oldName, newName) {
      const
        oldTab = oldName !== void 0 && oldName !== null && oldName !== ''
          ? tabDataList.find(tab => tab.name.value === oldName)
          : null,
        newTab = newName !== void 0 && newName !== null && newName !== ''
          ? tabDataList.find(tab => tab.name.value === newName)
          : null;

      if (oldTab && newTab) {
        const
          oldEl = oldTab.tabIndicatorRef.value,
          newEl = newTab.tabIndicatorRef.value;

        if (animateTimer !== null) {
          clearTimeout(animateTimer);
          animateTimer = null;
        }

        oldEl.style.transition = 'none';
        oldEl.style.transform = 'none';
        newEl.style.transition = 'none';
        newEl.style.transform = 'none';

        const
          oldPos = oldEl.getBoundingClientRect(),
          newPos = newEl.getBoundingClientRect();

        newEl.style.transform = props.vertical === true
          ? `translate3d(0,${ oldPos.top - newPos.top }px,0) scale3d(1,${ newPos.height ? oldPos.height / newPos.height : 1 },1)`
          : `translate3d(${ oldPos.left - newPos.left }px,0,0) scale3d(${ newPos.width ? oldPos.width / newPos.width : 1 },1,1)`;

        // allow scope updates to kick in (QRouteTab needs more time)
        registerAnimateTick(() => {
          animateTimer = setTimeout(() => {
            animateTimer = null;
            newEl.style.transition = 'transform .25s cubic-bezier(.4, 0, .2, 1)';
            newEl.style.transform = 'none';
          }, 70);
        });
      }

      if (newTab && scrollable.value === true) {
        scrollToTabEl(newTab.rootRef.value);
      }
    }

    function scrollToTabEl (el) {
      const
        { left, width, top, height } = contentRef.value.getBoundingClientRect(),
        newPos = el.getBoundingClientRect();

      let offset = props.vertical === true ? newPos.top - top : newPos.left - left;

      if (offset < 0) {
        contentRef.value[ props.vertical === true ? 'scrollTop' : 'scrollLeft' ] += Math.floor(offset);
        updateArrows();
        return
      }

      offset += props.vertical === true ? newPos.height - height : newPos.width - width;
      if (offset > 0) {
        contentRef.value[ props.vertical === true ? 'scrollTop' : 'scrollLeft' ] += Math.ceil(offset);
        updateArrows();
      }
    }

    function updateArrows () {
      const content = contentRef.value;
      if (content === null) { return }

      const
        rect = content.getBoundingClientRect(),
        pos = props.vertical === true ? content.scrollTop : Math.abs(content.scrollLeft);

      if (isRTL.value === true) {
        leftArrow.value = Math.ceil(pos + rect.width) < content.scrollWidth - 1;
        rightArrow.value = pos > 0;
      }
      else {
        leftArrow.value = pos > 0;
        rightArrow.value = props.vertical === true
          ? Math.ceil(pos + rect.height) < content.scrollHeight
          : Math.ceil(pos + rect.width) < content.scrollWidth;
      }
    }

    function animScrollTo (value) {
      scrollTimer !== null && clearInterval(scrollTimer);
      scrollTimer = setInterval(() => {
        if (scrollTowards(value) === true) {
          stopAnimScroll();
        }
      }, 5);
    }

    function scrollToStart () {
      animScrollTo(rtlPosCorrection.value === true ? Number.MAX_SAFE_INTEGER : 0);
    }

    function scrollToEnd () {
      animScrollTo(rtlPosCorrection.value === true ? 0 : Number.MAX_SAFE_INTEGER);
    }

    function stopAnimScroll () {
      if (scrollTimer !== null) {
        clearInterval(scrollTimer);
        scrollTimer = null;
      }
    }

    function onKbdNavigate (keyCode, fromEl) {
      const tabs = Array.prototype.filter.call(
        contentRef.value.children,
        el => el === fromEl || (el.matches && el.matches('.q-tab.q-focusable') === true)
      );

      const len = tabs.length;
      if (len === 0) { return }

      if (keyCode === 36) { // Home
        scrollToTabEl(tabs[ 0 ]);
        tabs[ 0 ].focus();
        return true
      }
      if (keyCode === 35) { // End
        scrollToTabEl(tabs[ len - 1 ]);
        tabs[ len - 1 ].focus();
        return true
      }

      const dirPrev = keyCode === (props.vertical === true ? 38 /* ArrowUp */ : 37 /* ArrowLeft */);
      const dirNext = keyCode === (props.vertical === true ? 40 /* ArrowDown */ : 39 /* ArrowRight */);

      const dir = dirPrev === true ? -1 : (dirNext === true ? 1 : void 0);

      if (dir !== void 0) {
        const rtlDir = isRTL.value === true ? -1 : 1;
        const index = tabs.indexOf(fromEl) + dir * rtlDir;

        if (index >= 0 && index < len) {
          scrollToTabEl(tabs[ index ]);
          tabs[ index ].focus({ preventScroll: true });
        }

        return true
      }
    }

    // let's speed up execution of time-sensitive scrollTowards()
    // with a computed variable by directly applying the minimal
    // number of instructions on get/set functions
    const posFn = computed(() => (
      rtlPosCorrection.value === true
        ? { get: content => Math.abs(content.scrollLeft), set: (content, pos) => { content.scrollLeft = -pos; } }
        : (
            props.vertical === true
              ? { get: content => content.scrollTop, set: (content, pos) => { content.scrollTop = pos; } }
              : { get: content => content.scrollLeft, set: (content, pos) => { content.scrollLeft = pos; } }
          )
    ));

    function scrollTowards (value) {
      const
        content = contentRef.value,
        { get, set } = posFn.value;

      let
        done = false,
        pos = get(content);

      const direction = value < pos ? -1 : 1;

      pos += direction * 5;

      if (pos < 0) {
        done = true;
        pos = 0;
      }
      else if (
        (direction === -1 && pos <= value)
        || (direction === 1 && pos >= value)
      ) {
        done = true;
        pos = value;
      }

      set(content, pos);
      updateArrows();

      return done
    }

    function hasQueryIncluded (targetQuery, matchingQuery) {
      for (const key in targetQuery) {
        if (targetQuery[ key ] !== matchingQuery[ key ]) {
          return false
        }
      }

      return true
    }

    // do not use directly; use verifyRouteModel() instead
    function updateActiveRoute () {
      let name = null, bestScore = { matchedLen: 0, queryDiff: 9999, hrefLen: 0 };

      const list = tabDataList.filter(tab => tab.routeData !== void 0 && tab.routeData.hasRouterLink.value === true);
      const { hash: currentHash, query: currentQuery } = proxy.$route;
      const currentQueryLen = Object.keys(currentQuery).length;

      // Vue Router does not keep account of hash & query when matching
      // so we're doing this as well

      for (const tab of list) {
        const exact = tab.routeData.exact.value === true;

        if (tab.routeData[ exact === true ? 'linkIsExactActive' : 'linkIsActive' ].value !== true) {
          // it cannot match anything as it's not active nor exact-active
          continue
        }

        const { hash, query, matched, href } = tab.routeData.resolvedLink.value;
        const queryLen = Object.keys(query).length;

        if (exact === true) {
          if (hash !== currentHash) {
            // it's set to exact but it doesn't matches the hash
            continue
          }

          if (
            queryLen !== currentQueryLen
            || hasQueryIncluded(currentQuery, query) === false
          ) {
            // it's set to exact but it doesn't matches the query
            continue
          }

          // yey, we found the perfect match (route + hash + query)
          name = tab.name.value;
          break
        }

        if (hash !== '' && hash !== currentHash) {
          // it has hash and it doesn't matches
          continue
        }

        if (
          queryLen !== 0
          && hasQueryIncluded(query, currentQuery) === false
        ) {
          // it has query and it doesn't includes the current one
          continue
        }

        const newScore = {
          matchedLen: matched.length,
          queryDiff: currentQueryLen - queryLen,
          hrefLen: href.length - hash.length
        };

        if (newScore.matchedLen > bestScore.matchedLen) {
          // it matches more routes so it's more specific so we set it as current champion
          name = tab.name.value;
          bestScore = newScore;
          continue
        }
        else if (newScore.matchedLen !== bestScore.matchedLen) {
          // it matches less routes than the current champion so we discard it
          continue
        }

        if (newScore.queryDiff < bestScore.queryDiff) {
          // query is closer to the current one so we set it as current champion
          name = tab.name.value;
          bestScore = newScore;
        }
        else if (newScore.queryDiff !== bestScore.queryDiff) {
          // it matches less routes than the current champion so we discard it
          continue
        }

        if (newScore.hrefLen > bestScore.hrefLen) {
          // href is lengthier so it's more specific so we set it as current champion
          name = tab.name.value;
          bestScore = newScore;
        }
      }

      if (
        name === null
        && tabDataList.some(tab => tab.routeData === void 0 && tab.name.value === currentModel.value) === true
      ) {
        // we shouldn't interfere if non-route tab is active
        return
      }

      updateModel({ name, setCurrent: true });
    }

    function onFocusin (e) {
      removeFocusTimeout();

      if (
        hasFocus.value !== true
        && rootRef.value !== null
        && e.target
        && typeof e.target.closest === 'function'
      ) {
        const tab = e.target.closest('.q-tab');

        // if the target is contained by a QTab/QRouteTab
        // (it might be other elements focused, like additional QBtn)
        if (tab && rootRef.value.contains(tab) === true) {
          hasFocus.value = true;
          scrollable.value === true && scrollToTabEl(tab);
        }
      }
    }

    function onFocusout () {
      registerFocusTimeout(() => { hasFocus.value = false; }, 30);
    }

    function verifyRouteModel () {
      if ($tabs.avoidRouteWatcher === false) {
        registerScrollToTabTimeout(updateActiveRoute);
      }
      else {
        removeScrollToTabTimeout();
      }
    }

    function watchRoute () {
      if (unwatchRoute === void 0) {
        const unwatch = watch(() => proxy.$route.fullPath, verifyRouteModel);
        unwatchRoute = () => {
          unwatch();
          unwatchRoute = void 0;
        };
      }
    }

    function registerTab (tabData) {
      tabDataList.push(tabData);
      tabDataListLen.value++;

      recalculateScroll();

      // if it's a QTab or we don't have Vue Router
      if (tabData.routeData === void 0 || proxy.$route === void 0) {
        // we should position to the currently active tab (if any)
        registerScrollToTabTimeout(() => {
          if (scrollable.value === true) {
            const value = currentModel.value;
            const newTab = value !== void 0 && value !== null && value !== ''
              ? tabDataList.find(tab => tab.name.value === value)
              : null;

            newTab && scrollToTabEl(newTab.rootRef.value);
          }
        });
      }
      // else if it's a QRouteTab with a valid link
      else {
        // start watching route
        watchRoute();

        if (tabData.routeData.hasRouterLink.value === true) {
          verifyRouteModel();
        }
      }
    }

    function unregisterTab (tabData) {
      tabDataList.splice(tabDataList.indexOf(tabData), 1);
      tabDataListLen.value--;

      recalculateScroll();

      if (unwatchRoute !== void 0 && tabData.routeData !== void 0) {
        // unwatch route if we don't have any QRouteTabs left
        if (tabDataList.every(tab => tab.routeData === void 0) === true) {
          unwatchRoute();
        }

        // then update model
        verifyRouteModel();
      }
    }

    const $tabs = {
      currentModel,
      tabProps,
      hasFocus,
      hasActiveTab,

      registerTab,
      unregisterTab,

      verifyRouteModel,
      updateModel,
      onKbdNavigate,

      avoidRouteWatcher: false // false | string (uid)
    };

    provide(tabsKey, $tabs);

    function cleanup () {
      animateTimer !== null && clearTimeout(animateTimer);
      stopAnimScroll();
      unwatchRoute !== void 0 && unwatchRoute();
    }

    let hadRouteWatcher;

    onBeforeUnmount(cleanup);

    onDeactivated(() => {
      hadRouteWatcher = unwatchRoute !== void 0;
      cleanup();
    });

    onActivated(() => {
      hadRouteWatcher === true && watchRoute();
      recalculateScroll();
    });

    return () => {
      return h('div', {
        ref: rootRef,
        class: classes.value,
        role: 'tablist',
        onFocusin,
        onFocusout
      }, [
        h(QResizeObserver, { onResize: updateContainer }),

        h('div', {
          ref: contentRef,
          class: innerClass.value,
          onScroll: updateArrows
        }, hSlot(slots.default)),

        h(QIcon, {
          class: 'q-tabs__arrow q-tabs__arrow--left absolute q-tab__icon'
            + (leftArrow.value === true ? '' : ' q-tabs__arrow--faded'),
          name: props.leftIcon || $q.iconSet.tabs[ props.vertical === true ? 'up' : 'left' ],
          onMousedownPassive: scrollToStart,
          onTouchstartPassive: scrollToStart,
          onMouseupPassive: stopAnimScroll,
          onMouseleavePassive: stopAnimScroll,
          onTouchendPassive: stopAnimScroll
        }),

        h(QIcon, {
          class: 'q-tabs__arrow q-tabs__arrow--right absolute q-tab__icon'
            + (rightArrow.value === true ? '' : ' q-tabs__arrow--faded'),
          name: props.rightIcon || $q.iconSet.tabs[ props.vertical === true ? 'down' : 'right' ],
          onMousedownPassive: scrollToEnd,
          onTouchstartPassive: scrollToEnd,
          onMouseupPassive: stopAnimScroll,
          onMouseleavePassive: stopAnimScroll,
          onTouchendPassive: stopAnimScroll
        })
      ])
    }
  }
});

const QTabPanel = createComponent({
  name: 'QTabPanel',

  props: usePanelChildProps,

  setup (_, { slots }) {
    return () => h('div', { class: 'q-tab-panel', role: 'tabpanel' }, hSlot(slots.default))
  }
});

const QTabPanels = createComponent({
  name: 'QTabPanels',

  props: {
    ...usePanelProps,
    ...useDarkProps
  },

  emits: usePanelEmits,

  setup (props, { slots }) {
    const vm = getCurrentInstance();
    const isDark = useDark(props, vm.proxy.$q);

    const { updatePanelsList, getPanelContent, panelDirectives } = usePanel();

    const classes = computed(() =>
      'q-tab-panels q-panel-parent'
      + (isDark.value === true ? ' q-tab-panels--dark q-dark' : '')
    );

    return () => {
      updatePanelsList(slots);

      return hDir(
        'div',
        { class: classes.value },
        getPanelContent(),
        'pan',
        props.swipeable,
        () => panelDirectives.value
      )
    }
  }
});

export { QTab, QTabPanel, QTabPanels, QTabs };
