HEX
Server: Apache/2.4.52 (Ubuntu)
System: Linux spn-python 5.15.0-89-generic #99-Ubuntu SMP Mon Oct 30 20:42:41 UTC 2023 x86_64
User: arjun (1000)
PHP: 8.1.2-1ubuntu2.20
Disabled: NONE
Upload Files
File: //home/arjun/projects/buyercall/node_modules/bootstrap-vue-next/src/components/BPopover.vue
<template>
  <span ref="placeholder" />
  <slot name="target" :show="show" :hide="hide" :toggle="toggle" :show-state="showState" />
  <Teleport :to="container" :disabled="!container">
    <div
      v-if="showStateInternal || persistentBoolean"
      :id="id"
      v-bind="$attrs"
      ref="element"
      :class="computedClasses"
      role="tooltip"
      tabindex="-1"
      :style="floatingStyles"
    >
      <div
        ref="arrow"
        :class="`${tooltipBoolean ? 'tooltip' : 'popover'}-arrow`"
        :style="arrowStyle"
        data-popper-arrow
      />
      <div class="overflow-auto" :style="sizeStyles">
        <template v-if="title || $slots.title">
          <div
            v-if="!isHtml"
            class="position-sticky top-0"
            :class="tooltipBoolean ? 'tooltip-inner' : 'popover-header'"
          >
            <slot name="title">
              {{ title }}
            </slot>
          </div>
          <!-- eslint-disable vue/no-v-html -->
          <div
            v-else
            class="position-sticky top-0"
            :class="tooltipBoolean ? 'tooltip-inner' : 'popover-header'"
            v-html="sanitizedTitle"
          />
          <!-- eslint-enable vue/no-v-html -->
        </template>
        <template v-if="(tooltipBoolean && !$slots.title && !title) || !tooltipBoolean">
          <div v-if="!isHtml" :class="tooltipBoolean ? 'tooltip-inner' : 'popover-body'">
            <slot>
              {{ content }}
            </slot>
          </div>
          <!-- eslint-disable vue/no-v-html -->
          <div
            v-else
            :class="tooltipBoolean ? 'tooltip-inner' : 'popover-body'"
            v-html="sanitizedContent"
          />
          <!-- eslint-enable vue/no-v-html -->
        </template>
      </div>
    </div>
  </Teleport>
</template>

<script setup lang="ts">
import {
  type Alignment,
  arrow as arrowMiddleware,
  autoPlacement,
  autoUpdate,
  type Boundary,
  flip,
  hide as hideMiddleware,
  inline as inlineMiddleware,
  type Middleware,
  offset as offsetMiddleware,
  type Placement as OriginalPlacement,
  type RootBoundary,
  shift,
  size as sizeMiddleware,
  useFloating,
} from '@floating-ui/vue'
import {
  BvTriggerableEvent,
  getTransitionDelay,
  IS_BROWSER,
  resolveBootstrapPlacement,
} from '../utils'
import {DefaultAllowlist, sanitizeHtml} from '../utils/sanitizer'
import {onClickOutside, useMouseInElement, useToNumber} from '@vueuse/core'
import {
  type ComponentPublicInstance,
  computed,
  type CSSProperties,
  type MaybeRef,
  nextTick,
  onBeforeUnmount,
  onMounted,
  ref,
  toRef,
  unref,
  watch,
  watchEffect,
} from 'vue'
import {useBooleanish, useId} from '../composables'
import type {BPopoverProps} from '../types'
defineOptions({
  inheritAttrs: false,
})

const props = withDefaults(defineProps<BPopoverProps>(), {
  boundary: 'clippingAncestors',
  boundaryPadding: undefined,
  click: false,
  container: undefined,
  content: undefined,
  customClass: '',
  delay: () => ({show: 100, hide: 300}),
  floatingMiddleware: undefined,
  hide: undefined,
  html: false,
  id: undefined,
  inline: false,
  manual: false,
  modelValue: false,
  noAutoClose: false,
  noFade: false,
  noFlip: false,
  noHide: false,
  noShift: false,
  noSize: false,
  noninteractive: false,
  offset: null,
  placement: 'top',
  realtime: false,
  reference: null,
  strategy: 'absolute',
  target: null,
  title: undefined,
  tooltip: false,
  variant: null,
  persistent: false,
})

const emit = defineEmits<{
  'hidden': [value: BvTriggerableEvent]
  'hide': [value: BvTriggerableEvent]
  'hide-prevented': []
  'show': [value: BvTriggerableEvent]
  'show-prevented': []
  'shown': [value: BvTriggerableEvent]
  'update:modelValue': [value: boolean]
}>()

defineSlots<{
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  default?: (props: Record<string, never>) => any
  target?: (props: {
    show: () => void
    hide: (e: Event) => void
    toggle: (e: Event) => void
    showState: boolean
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
  }) => any
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  title?: (props: Record<string, never>) => any
}>()

const modelValueBoolean = useBooleanish(() => props.modelValue)
const showState = ref(modelValueBoolean.value)
const showStateInternal = ref(modelValueBoolean.value)
watchEffect(() => {
  emit('update:modelValue', showState.value)
})

watch(modelValueBoolean, () => {
  if (modelValueBoolean.value === showState.value) return
  modelValueBoolean.value ? show() : hide(new Event('update:modelValue'))
})

const computedId = useId(() => props.id, 'popover')

const clickBoolean = useBooleanish(() => props.click)
const manualBoolean = useBooleanish(() => props.manual)
const noShiftBoolean = useBooleanish(() => props.noShift)
const noSizeBoolean = useBooleanish(() => props.noSize)
const noFlipBoolean = useBooleanish(() => props.noFlip)
const noFadeBoolean = useBooleanish(() => props.noFade)
const noAutoCloseBoolean = useBooleanish(() => props.noAutoClose)
const noHideBoolean = useBooleanish(() => props.noHide)
const realtimeBoolean = useBooleanish(() => props.realtime)
const inlineBoolean = useBooleanish(() => props.inline)
const persistentBoolean = useBooleanish(() => props.persistent)
const tooltipBoolean = useBooleanish(() => props.tooltip)
const noninteractiveBoolean = useBooleanish(() => props.noninteractive)
const isHtml = useBooleanish(() => props.html)

const hidden = ref(false)

const element = ref<HTMLElement | null>(null)
const targetTrigger = ref<HTMLElement | null>(null)
const arrow = ref<HTMLElement | null>(null)
const trigger = ref<HTMLElement | null>(null)
const placeholder = ref<HTMLElement | null>(null)

const sanitizedTitle = computed(() =>
  props.title ? sanitizeHtml(props.title, DefaultAllowlist) : ''
)

const sanitizedContent = computed(() =>
  props.content ? sanitizeHtml(props.content, DefaultAllowlist) : ''
)
const isAutoPlacement = toRef(() => props.placement.startsWith('auto'))
const offsetNumber = useToNumber(() => props.offset ?? NaN)

const boundary = computed<Boundary | undefined>(() =>
  props.boundary === 'document' || props.boundary === 'viewport' ? undefined : props.boundary
)
const rootBoundary = computed<RootBoundary | undefined>(() =>
  props.boundary === 'document' || props.boundary === 'viewport' ? props.boundary : undefined
)

const sizeStyles = ref<CSSProperties>({})
const floatingMiddleware = computed<Middleware[]>(() => {
  if (props.floatingMiddleware !== undefined) {
    return props.floatingMiddleware
  }
  const off = props.offset !== null ? offsetNumber.value : tooltipBoolean.value ? 6 : 8
  const arr: Middleware[] = [offsetMiddleware(off)]
  if (noFlipBoolean.value === false && !isAutoPlacement.value) {
    arr.push(
      flip({
        boundary: boundary.value,
        rootBoundary: rootBoundary.value,
        padding: props.boundaryPadding,
      })
    )
  }
  if (isAutoPlacement.value) {
    arr.push(
      autoPlacement({
        alignment: (props.placement.split('-')[1] as Alignment) || undefined,
        boundary: boundary.value,
        rootBoundary: rootBoundary.value,
        padding: props.boundaryPadding,
      })
    )
  }
  if (noShiftBoolean.value === false) {
    arr.push(
      shift({
        boundary: boundary.value,
        rootBoundary: rootBoundary.value,
        padding: props.boundaryPadding,
      })
    )
  }
  if (noHideBoolean.value === false) {
    arr.push(
      hideMiddleware({
        boundary: boundary.value,
        rootBoundary: rootBoundary.value,
        padding: props.boundaryPadding,
      })
    )
  }
  if (inlineBoolean.value === true) {
    arr.push(inlineMiddleware())
  }
  arr.push(arrowMiddleware({element: arrow, padding: 10}))
  if (noSizeBoolean.value === false) {
    arr.push(
      sizeMiddleware({
        boundary: boundary.value,
        rootBoundary: rootBoundary.value,
        padding: props.boundaryPadding,
        apply({availableWidth, availableHeight}) {
          sizeStyles.value = {
            maxHeight: availableHeight ? `${availableHeight}px` : undefined,
            maxWidth: availableWidth ? `${availableWidth}px` : undefined,
          }
        },
      })
    )
  }
  return arr
})

const placementRef = toRef(() =>
  isAutoPlacement.value ? undefined : (props.placement as OriginalPlacement)
)

const {floatingStyles, middlewareData, placement, update} = useFloating(targetTrigger, element, {
  placement: placementRef,
  middleware: floatingMiddleware,
  strategy: toRef(() => props.strategy),
  whileElementsMounted: (...args) => {
    const cleanup = autoUpdate(...args, {animationFrame: realtimeBoolean.value})
    // Important! Always return the cleanup function.
    return cleanup
  },
})

const arrowStyle = ref<CSSProperties>({position: 'absolute'})

watch(middlewareData, () => {
  if (noHideBoolean.value === false) {
    if (middlewareData.value.hide?.referenceHidden) {
      hidden.value = true
    } else {
      hidden.value = false
    }
  }
  if (middlewareData.value.arrow) {
    const {x, y} = middlewareData.value.arrow
    arrowStyle.value = {
      position: 'absolute',
      top: y ? `${y}px` : '',
      left: x ? `${x}px` : '',
    }
  }
})

const computedClasses = computed(() => {
  const type = tooltipBoolean.value ? 'tooltip' : 'popover'
  return [
    type,
    `b-${type}`,
    {
      [`b-${type}-${props.variant}`]: props.variant !== null,
      show: showState.value && !hidden.value,
      ['pe-none']: !showState.value,
      fade: !noFadeBoolean.value,
      ['d-none']: !showState.value && noFadeBoolean.value,
      [`${props.customClass}`]: props.customClass !== undefined,
      [`bs-${type}-${resolveBootstrapPlacement(placement.value)}`]: placement.value !== undefined,
    },
  ]
})

const {isOutside} = useMouseInElement(element)
const {isOutside: triggerIsOutside} = useMouseInElement(trigger)

const toggle = (e: Event) => {
  const event = e ?? new Event('click')
  showState.value ? hide(event) : show()
}

const buildTriggerableEvent = (
  type: string,
  opts: Partial<BvTriggerableEvent> = {}
): BvTriggerableEvent =>
  new BvTriggerableEvent(type, {
    cancelable: false,
    target: element.value || null,
    relatedTarget: null,
    trigger: null,
    ...opts,
    componentId: computedId.value,
  })

let showTimeout: ReturnType<typeof setTimeout> | undefined
const show = () => {
  const event = buildTriggerableEvent('show', {cancelable: true})
  emit('show', event)
  if (event.defaultPrevented) {
    emit('show-prevented')
    return
  }
  showStateInternal.value = true
  nextTick(() => {
    update()
    showTimeout = setTimeout(
      () => {
        update()
        showState.value = true
        nextTick(() => {
          emit('shown', buildTriggerableEvent('shown'))
        })
      },
      typeof props.delay === 'number' ? props.delay : props.delay?.show || 0
    )
  })
}

const hide = (e: Event) => {
  const event = buildTriggerableEvent('hide', {cancelable: true})
  emit('hide', event)
  if (event.defaultPrevented) {
    emit('hide-prevented')
    return
  }
  if (showTimeout) {
    clearTimeout(showTimeout)
    showTimeout = undefined
  }
  const delay = typeof props.delay === 'number' ? props.delay : props.delay?.hide || 0
  setTimeout(() => {
    if (
      e?.type === 'click' ||
      e?.type === 'forceHide' ||
      (e?.type === 'update:modelValue' && manualBoolean.value) ||
      (!noninteractiveBoolean.value &&
        isOutside.value &&
        triggerIsOutside.value &&
        !element.value?.contains(document?.activeElement) &&
        !trigger.value?.contains(document?.activeElement)) ||
      (noninteractiveBoolean.value && triggerIsOutside.value)
    ) {
      showState.value = false
      nextTick(() => {
        setTimeout(
          () => {
            showStateInternal.value = false
          },
          element.value ? getTransitionDelay(element.value) : 150
        )
        emit('hidden', buildTriggerableEvent('hidden'))
      })
    } else {
      setTimeout(
        () => {
          hide(e)
        },
        delay < 50 ? 50 : delay
      )
    }
  }, delay)
}

defineExpose({
  hide,
  show,
  toggle,
})

const getElement = (
  target: MaybeRef<
    string | ComponentPublicInstance<HTMLElement> | HTMLSpanElement | HTMLElement | null
  >
): HTMLElement | undefined => {
  const element = unref(target)
  if (!element) return undefined
  if (typeof element === 'string') {
    const idElement = document.getElementById(element)
    return idElement ? idElement : undefined
  }
  if ((element as ComponentPublicInstance<HTMLElement>).$el) {
    return (element as ComponentPublicInstance<HTMLElement>).$el as HTMLElement
  }
  return element
}

const bind = () => {
  // TODO: is this the best way to bind the events?
  // we place a span and get the next element sibling fo rthe listeners
  if (props.target) {
    const elem = getElement(props.target)
    if (elem) {
      trigger.value = elem
    } else {
      // eslint-disable-next-line no-console
      console.warn('Target element not found', props.target)
    }
  } else {
    trigger.value = placeholder.value?.nextElementSibling as HTMLElement
  }
  if (props.reference) {
    const elem = getElement(props.reference)
    if (elem) {
      targetTrigger.value = elem
    } else {
      // eslint-disable-next-line no-console
      console.warn('Reference element not found', props.reference)
    }
  } else {
    targetTrigger.value = trigger.value
  }
  if (!trigger.value || manualBoolean.value) {
    return
  }
  if (!IS_BROWSER) return
  trigger.value.addEventListener('forceHide', hide)
  if (clickBoolean.value) {
    trigger.value.addEventListener('click', toggle)
    return
  }
  trigger.value.addEventListener('pointerenter', show)
  trigger.value.addEventListener('pointerleave', hide)
  trigger.value.addEventListener('focus', show)
  trigger.value.addEventListener('blur', hide)
}

const unbind = () => {
  if (trigger.value) {
    trigger.value.removeEventListener('forceHide', hide)
    trigger.value.removeEventListener('click', toggle)
    trigger.value.removeEventListener('pointerenter', show)
    trigger.value.removeEventListener('pointerleave', hide)
    trigger.value.removeEventListener('focus', show)
    trigger.value.removeEventListener('blur', hide)
  }
}

onClickOutside(
  element,
  () => {
    if (showState.value && clickBoolean.value && !noAutoCloseBoolean.value && !manualBoolean.value)
      hide(new Event('clickOutside'))
  },
  {ignore: [trigger]}
)

watch([() => props.click, () => props.target, () => props.reference], () => {
  unbind()
  bind()
  // update()
})

onMounted(bind)

onBeforeUnmount(unbind)
</script>