{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "external-hooks-utils", "title": "external hooks utils", "description": "utils external hook", "files": [ { "path": "packages/external-hooks/src/utils/dynamicCSS.ts", "content": "const APPEND_ORDER = 'data-rc-order';\nconst APPEND_PRIORITY = 'data-rc-priority';\nconst MARK_KEY = 'rc-util-key';\n\nconst containerCache = new Map();\n\nexport type ContainerType = Element | ShadowRoot;\nexport type Prepend = boolean | 'queue';\nexport type AppendType = 'prependQueue' | 'append' | 'prepend';\n\nexport default function contains(root: Node | null | undefined, n?: Node) {\n if (!root) {\n return false;\n }\n\n // Use native if support\n if (root.contains) {\n return root.contains(n as any);\n }\n\n // `document.contains` not support with IE11\n let node = n;\n while (node) {\n if (node === root) {\n return true;\n }\n // @ts-ignore\n node = node.parentNode;\n }\n\n return false;\n}\n\ninterface Options {\n attachTo?: ContainerType;\n csp?: { nonce?: string };\n prepend?: Prepend;\n /**\n * Config the `priority` of `prependQueue`. Default is `0`.\n * It's useful if you need to insert style before other style.\n */\n priority?: number;\n mark?: string;\n}\n\nfunction getMark({ mark }: Options = {}) {\n if (mark) {\n return mark.startsWith('data-') ? mark : `data-${mark}`;\n }\n return MARK_KEY;\n}\n\nfunction getContainer(option: Options) {\n if (option.attachTo) {\n return option.attachTo;\n }\n\n const head = document.querySelector('head');\n return head || document.body;\n}\n\nfunction getOrder(prepend?: Prepend): AppendType {\n if (prepend === 'queue') {\n return 'prependQueue';\n }\n\n return prepend ? 'prepend' : 'append';\n}\n\n/**\n * Find style which inject by rc-util\n */\nfunction findStyles(container: ContainerType) {\n return [...(containerCache.get(container) || container).children].filter(\n (node) => node.tagName === 'STYLE',\n ) as HTMLStyleElement[];\n}\n\nexport function injectCSS(css: string, option: Options = {}) {\n const { csp, prepend, priority = 0 } = option;\n const mergedOrder = getOrder(prepend);\n const isPrependQueue = mergedOrder === 'prependQueue';\n\n const styleNode = document.createElement('style');\n styleNode.setAttribute(APPEND_ORDER, mergedOrder);\n\n if (isPrependQueue && priority) {\n styleNode.setAttribute(APPEND_PRIORITY, `${priority}`);\n }\n\n if (csp?.nonce) {\n styleNode.nonce = csp?.nonce;\n }\n styleNode.innerHTML = css;\n\n const container = getContainer(option);\n const { firstChild } = container as any;\n\n if (prepend) {\n // If is queue `prepend`, it will prepend first style and then append rest style\n if (isPrependQueue) {\n const existStyle = findStyles(container).filter((node) => {\n // Ignore style which not injected by rc-util with prepend\n // @ts-ignore\n if (!['prepend', 'prependQueue'].includes(node.getAttribute(APPEND_ORDER))) {\n return false;\n }\n\n // Ignore style which priority less then new style\n const nodePriority = Number(node.getAttribute(APPEND_PRIORITY) || 0);\n return priority >= nodePriority;\n });\n\n if (existStyle.length > 0) {\n // @ts-ignore\n container.insertBefore(styleNode, existStyle.at(-1).nextSibling);\n\n return styleNode;\n }\n }\n\n // Use `insertBefore` as `prepend`\n firstChild.before(styleNode);\n } else {\n container.append(styleNode);\n }\n\n return styleNode;\n}\n\nfunction findExistNode(key: string, option: Options = {}) {\n const container = getContainer(option);\n\n return findStyles(container).find((node) => node.getAttribute(getMark(option)) === key);\n}\n\nexport function removeCSS(key: string, option: Options = {}) {\n const existNode = findExistNode(key, option);\n if (existNode) {\n // const container = getContainer(option);\n existNode.remove();\n }\n}\n\n/**\n * qiankun will inject `appendChild` to insert into other\n */\nfunction syncRealContainer(container: ContainerType, option: Options) {\n const cachedRealContainer = containerCache.get(container);\n\n // Find real container when not cached or cached container removed\n if (!cachedRealContainer || !contains(document, cachedRealContainer)) {\n const placeholderStyle = injectCSS('', option);\n const { parentNode } = placeholderStyle;\n // @ts-ignore\n containerCache.set(container, parentNode);\n placeholderStyle.remove();\n }\n}\n\n/**\n * manually clear container cache to avoid global cache in unit testes\n */\nexport function clearContainerCache() {\n containerCache.clear();\n}\n\nexport function updateCSS(css: string, key: string, option: Options = {}) {\n const container = getContainer(option);\n\n // Sync real parent\n syncRealContainer(container, option);\n\n const existNode = findExistNode(key, option);\n\n if (existNode) {\n if (option.csp?.nonce && existNode.nonce !== option.csp?.nonce) {\n existNode.nonce = option.csp?.nonce;\n }\n\n if (existNode.innerHTML !== css) {\n existNode.innerHTML = css;\n }\n\n return existNode;\n }\n\n const newNode = injectCSS(css, option);\n newNode.setAttribute(getMark(option), key);\n return newNode;\n}\n", "type": "registry:file", "target": "src/hooks/external-hooks/utils/dynamicCSS.ts" }, { "path": "packages/external-hooks/src/utils/getScrollBarSize.ts", "content": "import { removeCSS, updateCSS } from './dynamicCSS';\n\ntype ScrollBarSize = { width: number; height: number };\n\ntype ExtendCSSStyleDeclaration = CSSStyleDeclaration & {\n scrollbarColor?: string;\n scrollbarWidth?: string;\n};\n\nlet cached: ScrollBarSize;\n\nfunction measureScrollbarSize(ele?: HTMLElement): ScrollBarSize {\n const randomId = `rc-scrollbar-measure-${Math.random().toString(36).substring(7)}`;\n const measureEle = document.createElement('div');\n measureEle.id = randomId;\n\n // Create Style\n const measureStyle: ExtendCSSStyleDeclaration = measureEle.style;\n measureStyle.position = 'absolute';\n measureStyle.left = '0';\n measureStyle.top = '0';\n measureStyle.width = '100px';\n measureStyle.height = '100px';\n measureStyle.overflow = 'scroll';\n\n // Clone Style if needed\n let fallbackWidth: number | undefined;\n let fallbackHeight: number | undefined;\n if (ele) {\n const targetStyle: ExtendCSSStyleDeclaration = getComputedStyle(ele);\n measureStyle.scrollbarColor = targetStyle.scrollbarColor;\n measureStyle.scrollbarWidth = targetStyle.scrollbarWidth;\n\n // Set Webkit style\n const webkitScrollbarStyle = getComputedStyle(ele, '::-webkit-scrollbar');\n const width = parseInt(webkitScrollbarStyle.width, 10);\n const height = parseInt(webkitScrollbarStyle.height, 10);\n\n // Try wrap to handle CSP case\n try {\n const widthStyle = width ? `width: ${webkitScrollbarStyle.width};` : '';\n const heightStyle = height ? `height: ${webkitScrollbarStyle.height};` : '';\n\n updateCSS(\n `\n#${randomId}::-webkit-scrollbar {\n${widthStyle}\n${heightStyle}\n}`,\n randomId,\n );\n } catch (e) {\n // Can't wrap, just log error\n console.error(e);\n\n // Get from style directly\n fallbackWidth = width;\n fallbackHeight = height;\n }\n }\n\n document.body.appendChild(measureEle);\n\n // Measure. Get fallback style if provided\n const scrollWidth =\n ele && fallbackWidth && !isNaN(fallbackWidth)\n ? fallbackWidth\n : measureEle.offsetWidth - measureEle.clientWidth;\n const scrollHeight =\n ele && fallbackHeight && !isNaN(fallbackHeight)\n ? fallbackHeight\n : measureEle.offsetHeight - measureEle.clientHeight;\n\n // Clean up\n document.body.removeChild(measureEle);\n removeCSS(randomId);\n\n return {\n width: scrollWidth,\n height: scrollHeight,\n };\n}\n\nexport default function getScrollBarSize(fresh?: boolean): number {\n if (typeof document === 'undefined') {\n return 0;\n }\n\n if (fresh || cached === undefined) {\n cached = measureScrollbarSize();\n }\n return cached.width;\n}\n\nexport function getTargetScrollBarSize(target: HTMLElement) {\n if (typeof document === 'undefined' || !target || !(target instanceof Element)) {\n return { width: 0, height: 0 };\n }\n\n return measureScrollbarSize(target);\n}\n", "type": "registry:file", "target": "src/hooks/external-hooks/utils/getScrollBarSize.ts" } ], "type": "registry:hook" }