UNPKG

bootstrap-vue-next

Version:

Seamless integration of Vue 3, Bootstrap 5, and TypeScript for modern, type-safe UI development

1 lines 13.7 kB
{"version":3,"file":"utils-BfbvBFtw.mjs","sources":["../src/directives/utils.ts"],"sourcesContent":["import type {ComponentInternalInstance, Directive, DirectiveBinding, Ref, VNode} from 'vue'\nimport type {BPopoverProps} from '../types/ComponentProps'\nimport {\n bind,\n type ElementWithPopper,\n resolveActiveStatus,\n resolveContent,\n resolveDirectiveProps,\n unbind,\n} from '../utils/floatingUi'\nimport {defaultsKey} from '../utils/keys'\n\ninterface _ComponentInternalInstance extends ComponentInternalInstance {\n provides?: Record<string, unknown>\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n setupState?: any\n}\n\ninterface _VNode extends VNode {\n ctx?: _ComponentInternalInstance | null\n ssContent?: VNode | null\n}\n\n/**\n * Represents per-instance state for directives using UID namespacing\n */\nexport interface DirectiveInstanceState {\n binding: string // JSON.stringify cache for change detection\n destroying: boolean // Flag to prevent race conditions during cleanup\n}\n\n/**\n * Gets the component instance UID from a directive binding\n * @throws Error if binding.instance is not available\n */\nexport function getDirectiveUid(binding: DirectiveBinding): number {\n if (!binding.instance) {\n throw new Error('[Bootstrap-Vue-Next] Directive binding.instance is not available')\n }\n return binding.instance.$.uid\n}\n\n/**\n * Initializes UID-namespaced storage on an element for a directive\n * @param el - The HTML element\n * @param propertyName - The property name (e.g., '$__tooltip', '$__popover')\n * @param uid - The component instance UID\n * @param binding - The directive binding value to cache\n * @returns The initialized instance state\n */\nexport function initDirectiveInstance(\n el: HTMLElement & Record<string, unknown>,\n propertyName: string,\n uid: number,\n binding: DirectiveBinding\n): DirectiveInstanceState {\n // Initialize UID namespace for this directive\n const elWithProps = el as Record<string, unknown>\n elWithProps[propertyName] = elWithProps[propertyName] ?? Object.create(null)\n\n // Store per-instance state with JSON cache for change detection\n const state: DirectiveInstanceState = {\n binding: JSON.stringify([binding.modifiers, binding.value]),\n destroying: false,\n }\n\n ;(elWithProps[propertyName] as Record<string, unknown>)[uid] = state\n return state\n}\n\n/**\n * Gets the instance state for a directive, if it exists\n * @param el - The HTML element\n * @param propertyName - The property name (e.g., '$__tooltip', '$__popover')\n * @param uid - The component instance UID\n * @returns The instance state or undefined if not found\n */\nexport function getDirectiveInstance(\n el: HTMLElement & Record<string, unknown>,\n propertyName: string,\n uid: number\n): DirectiveInstanceState | undefined {\n const elWithProps = el as Record<string, unknown>\n return (elWithProps[propertyName] as Record<string, unknown> | undefined)?.[uid] as\n | DirectiveInstanceState\n | undefined\n}\n\n/**\n * Checks if the directive binding has changed for this instance\n * @param instance - The directive instance state\n * @param binding - The current directive binding\n * @returns true if the binding has changed, false otherwise\n */\nexport function hasBindingChanged(\n instance: DirectiveInstanceState,\n binding: DirectiveBinding\n): boolean {\n const newBinding = JSON.stringify([binding.modifiers, binding.value])\n return instance.binding !== newBinding\n}\n\n/**\n * Updates the cached binding value for a directive instance\n * @param instance - The directive instance state\n * @param binding - The new directive binding\n */\nexport function updateBindingCache(\n instance: DirectiveInstanceState,\n binding: DirectiveBinding\n): void {\n instance.binding = JSON.stringify([binding.modifiers, binding.value])\n}\n\n/**\n * Cleans up a directive instance\n * @param el - The HTML element\n * @param propertyName - The property name (e.g., '$__tooltip', '$__popover')\n * @param uid - The component instance UID\n */\nexport function cleanupDirectiveInstance(\n el: HTMLElement & Record<string, unknown>,\n propertyName: string,\n uid: number\n): void {\n const elWithProps = el as Record<string, unknown>\n const instance = (elWithProps[propertyName] as Record<string, unknown> | undefined)?.[uid] as\n | DirectiveInstanceState\n | undefined\n if (instance) {\n instance.destroying = true\n delete (elWithProps[propertyName] as Record<string, unknown>)[uid]\n }\n}\n\n// taken from vuetify https://github.com/vuetifyjs/vuetify/blob/master/packages/vuetify/src/composables/directiveComponent.ts\n\nexport function findProvides(binding: DirectiveBinding, vnode: _VNode): Record<string, unknown> {\n const provides =\n (vnode.ctx === binding.instance!.$\n ? findComponentParent(vnode, binding.instance!.$)?.provides\n : vnode.ctx?.provides) ?? binding.instance!.$.provides\n\n return provides\n}\n\nexport function findComponentParent(\n vnode: VNode,\n root: ComponentInternalInstance\n): _ComponentInternalInstance | null {\n // Walk the tree from root until we find the child vnode\n const stack = new Set<VNode>()\n const walk = (children: _VNode[]): boolean => {\n for (const child of children) {\n if (!child) continue\n\n if (child === vnode || (child.el && vnode.el && child.el === vnode.el)) {\n return true\n }\n\n stack.add(child)\n let result\n if (child.suspense) {\n result = walk([child.ssContent!])\n } else if (Array.isArray(child.children)) {\n result = walk(child.children as VNode[])\n } else if (child.component?.vnode) {\n result = walk([child.component?.subTree])\n }\n if (result) {\n return result\n }\n stack.delete(child)\n }\n\n return false\n }\n if (!walk([root.subTree])) {\n // eslint-disable-next-line no-console\n console.error('Could not find original vnode, will not inherit provides')\n return root\n }\n\n // Return the first component parent\n const result = Array.from(stack).reverse()\n for (const child of result) {\n if (child.component) {\n return child.component\n }\n }\n return root\n}\n\n/**\n * Creates a floating UI directive (tooltip or popover) with UID-namespaced state management\n * @param propertyName - The property name for storing state (e.g., '$__tooltip', '$__popover')\n * @param componentDefaultsKey - The key for accessing component defaults (e.g., 'BTooltip', 'BPopover')\n * @param buildProps - Optional function to customize the props passed to bind()\n * @returns A Vue directive object\n */\nexport function createFloatingDirective(\n propertyName: string,\n componentDefaultsKey: string,\n buildProps?: (\n text: {title?: string; body?: string},\n defaults: unknown,\n binding: Readonly<DirectiveBinding>,\n el: Readonly<HTMLElement>\n ) => BPopoverProps\n): Directive<ElementWithPopper> {\n return {\n mounted(el, binding, vnode) {\n const uid = getDirectiveUid(binding)\n const defaultsMap = (findProvides(binding, vnode) as Record<symbol, Ref>)[defaultsKey]\n ?.value as Record<string, unknown> | undefined\n const isActive = resolveActiveStatus(binding.value)\n if (!isActive) return\n\n const text = resolveContent(binding.value, el)\n\n if (!text.body && !text.title) return\n\n // Initialize per-instance state with UID namespacing\n initDirectiveInstance(el, propertyName, uid, binding)\n\n const props = buildProps\n ? buildProps(text, defaultsMap?.[componentDefaultsKey], binding, el)\n : {\n ...(defaultsMap?.[componentDefaultsKey] || undefined),\n ...resolveDirectiveProps(binding, el),\n ...text,\n }\n\n bind(el, binding, props)\n },\n\n updated(el, binding, vnode) {\n const uid = getDirectiveUid(binding)\n let instance = getDirectiveInstance(el, propertyName, uid)\n\n const defaultsMap = (findProvides(binding, vnode) as Record<symbol, Ref>)[defaultsKey]\n ?.value as Record<string, unknown> | undefined\n\n const isActive = resolveActiveStatus(binding.value)\n\n // If inactive, clean up existing instance if present\n if (!isActive) {\n if (instance && el.$__element) {\n unbind(el)\n cleanupDirectiveInstance(el, propertyName, uid)\n }\n return\n }\n\n const text = resolveContent(binding.value, el)\n\n if (!text.body && !text.title) {\n // Clean up if no content\n if (instance && el.$__element) {\n unbind(el)\n cleanupDirectiveInstance(el, propertyName, uid)\n }\n return\n }\n\n // If instance doesn't exist, this is a transition from inactive/no-content to active\n // Initialize the instance now (similar to mounted)\n if (!instance) {\n instance = initDirectiveInstance(el, propertyName, uid, binding)\n\n const props = buildProps\n ? buildProps(text, defaultsMap?.[componentDefaultsKey], binding, el)\n : {\n ...(defaultsMap?.[componentDefaultsKey] || undefined),\n ...resolveDirectiveProps(binding, el),\n ...text,\n }\n\n bind(el, binding, props)\n return\n }\n\n // Check if binding changed for THIS instance\n if (!hasBindingChanged(instance, binding)) return\n\n // Prevent race conditions during update\n if (instance.destroying) return\n\n unbind(el)\n\n const props = buildProps\n ? buildProps(text, defaultsMap?.[componentDefaultsKey], binding, el)\n : {\n ...(defaultsMap?.[componentDefaultsKey] || undefined),\n ...resolveDirectiveProps(binding, el),\n ...text,\n }\n\n bind(el, binding, props)\n\n // Update THIS instance's cache\n updateBindingCache(instance, binding)\n },\n\n beforeUnmount(el, binding) {\n const uid = getDirectiveUid(binding)\n const instance = getDirectiveInstance(el, propertyName, uid)\n\n if (!instance) return\n\n unbind(el)\n cleanupDirectiveInstance(el, propertyName, uid)\n },\n }\n}\n"],"names":["result","props"],"mappings":"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"}