{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "avatar", "type": "registry:ui", "title": "Avatar", "description": "Avatar from @pitsi-ui/native for native projects.", "dependencies": [ "@gorhom/bottom-sheet@^5.2.8", "react-native-gesture-handler@^2.28.0", "react-native-reanimated@^4.1.1", "react-native-safe-area-context@^5.6.0", "react-native-screens", "react-native-svg@^15.12.1", "react-native-worklets", "tailwind-variants@^3.2.2", "uniwind" ], "registryDependencies": [], "files": [ { "path": "registry/native-ui/src/components/avatar/avatar.animation.ts", "content": "import { Easing, FadeIn, useAnimatedStyle, withTiming } from \"react-native-reanimated\";\nimport { useAnimationSettings } from \"../../helpers/internal/contexts\";\nimport { useCombinedAnimationDisabledState } from \"../../helpers/internal/hooks\";\nimport type { AnimationRootDisableAll } from \"../../helpers/internal/types\";\nimport {\n getAnimationState,\n getAnimationValueMergedConfig,\n getAnimationValueProperty,\n getIsAnimationDisabledValue,\n} from \"../../helpers/internal/utils\";\nimport * as AvatarPrimitives from \"../../primitives/avatar\";\nimport type { AvatarFallbackAnimation, AvatarImageAnimation } from \"./avatar\";\n\n/**\n * Animation hook for Avatar root component\n * Handles root-level animation configuration and provides context for child components\n */\nexport function useAvatarRootAnimation(options: {\n animation: AnimationRootDisableAll | undefined;\n}) {\n const { animation } = options;\n\n const isAllAnimationsDisabled = useCombinedAnimationDisabledState(animation);\n\n return {\n isAllAnimationsDisabled,\n };\n}\n\n/**\n * Animation hook for Avatar Image component\n * Handles opacity animation for the avatar image based on loading status\n */\nexport function useAvatarImageAnimation(options: { animation: AvatarImageAnimation | undefined }) {\n const { animation } = options;\n\n // Read from global animation context (always available in compound parts)\n const { isAllAnimationsDisabled } = useAnimationSettings();\n\n const { status } = AvatarPrimitives.useRootContext();\n\n const { animationConfig, isAnimationDisabled } = getAnimationState(animation);\n\n const isAnimationDisabledValue = getIsAnimationDisabledValue({\n isAnimationDisabled,\n isAllAnimationsDisabled,\n });\n\n // Opacity animation\n const opacityValue = getAnimationValueProperty({\n animationValue: animationConfig?.opacity,\n property: \"value\",\n defaultValue: [0, 1] as [number, number],\n });\n const opacityTimingConfig = getAnimationValueMergedConfig({\n animationValue: animationConfig?.opacity,\n property: \"timingConfig\",\n defaultValue: { duration: 200, easing: Easing.in(Easing.ease) },\n });\n\n const rImageStyle = useAnimatedStyle(() => {\n const isLoaded = status === \"loaded\";\n const targetOpacity = isLoaded ? opacityValue[1] : opacityValue[0];\n\n if (isAnimationDisabledValue) {\n return {\n opacity: targetOpacity,\n };\n }\n\n return {\n opacity: withTiming(targetOpacity, opacityTimingConfig),\n };\n });\n\n return {\n rImageStyle,\n };\n}\n\n/**\n * Animation hook for Avatar Fallback component\n * Handles entering animation for the avatar fallback\n */\nexport function useAvatarFallbackAnimation(options: {\n animation: AvatarFallbackAnimation | undefined;\n delayMs?: number;\n}) {\n const { animation, delayMs } = options;\n\n // Read from global animation context (always available in compound parts)\n const { isAllAnimationsDisabled } = useAnimationSettings();\n\n const { animationConfig, isAnimationDisabled } = getAnimationState(animation);\n\n const isAnimationDisabledValue = getIsAnimationDisabledValue({\n isAnimationDisabled,\n isAllAnimationsDisabled,\n });\n\n // Entering animation\n const enteringValue = getAnimationValueProperty({\n animationValue: animationConfig?.entering,\n property: \"value\",\n defaultValue: FadeIn.duration(200)\n .easing(Easing.in(Easing.ease))\n .delay(delayMs ?? 0),\n });\n\n return {\n entering: isAnimationDisabledValue ? undefined : enteringValue,\n };\n}\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/components/avatar/avatar.animation.ts" }, { "path": "registry/native-ui/src/components/avatar/avatar.context.ts", "content": "import { createContext } from \"../../helpers/internal/utils\";\nimport type { AvatarContextValue } from \"./avatar\";\n\n/**\n * Avatar context provider and hook\n * Provides size, color, and animation state to child components\n */\nexport const [AvatarProvider, useInnerAvatarContext] = createContext({\n name: \"AvatarContext\",\n});\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/components/avatar/avatar.context.ts" }, { "path": "registry/native-ui/src/components/avatar/avatar.tsx", "content": "import { forwardRef, useMemo } from \"react\";\nimport {\n type ImageSourcePropType,\n type ImageProps as RNImageProps,\n StyleSheet,\n type TextProps,\n type TextStyle,\n type ViewStyle,\n} from \"react-native\";\nimport Animated, {\n type AnimatedProps,\n type EntryOrExitLayoutType,\n type WithTimingConfig,\n} from \"react-native-reanimated\";\nimport { tv } from \"tailwind-variants\";\nimport { useThemeColor } from \"../../helpers/external/hooks\";\nimport { HeroText } from \"../../helpers/internal/components\";\nimport { AnimationSettingsProvider } from \"../../helpers/internal/contexts\";\nimport type {\n Animation,\n AnimationRootDisableAll,\n AnimationValue,\n ElementSlots,\n} from \"../../helpers/internal/types\";\nimport { childrenToString, combineStyles } from \"../../helpers/internal/utils\";\nimport type {\n FallbackProps as PrimitiveFallbackProps,\n FallbackRef as PrimitiveFallbackRef,\n ImageProps as PrimitiveImageProps,\n ImageRef as PrimitiveImageRef,\n RootProps as PrimitiveRootProps,\n RootRef as PrimitiveRootRef,\n} from \"../../primitives/avatar\";\nimport * as AvatarPrimitives from \"../../primitives/avatar\";\nimport type { ImageProps } from \"../../primitives/avatar/avatar.types\";\nimport {\n useAvatarFallbackAnimation,\n useAvatarImageAnimation,\n useAvatarRootAnimation,\n} from \"./avatar.animation\";\nimport { AvatarProvider, useInnerAvatarContext } from \"./avatar.context\";\nimport type { PersonIconProps } from \"./person-icon\";\nimport { PersonIcon } from \"./person-icon\";\n\n/* -------------------------------------------------------------------------------------------------\n * Types\n * -----------------------------------------------------------------------------------------------*/\n/**\n * Available sizes for the Avatar component\n */\nexport type AvatarSize = \"sm\" | \"md\" | \"lg\";\n\n/**\n * Available variants for the Avatar component\n */\nexport type AvatarVariant = \"default\" | \"soft\";\n\n/**\n * Available color variants for the Avatar component\n */\nexport type AvatarColor = \"accent\" | \"default\" | \"success\" | \"warning\" | \"danger\";\n\n/**\n * Props for the Avatar root component\n */\nexport interface AvatarRootProps extends PrimitiveRootProps {\n /** @default 'md' */\n size?: AvatarSize;\n /** @default 'default' */\n variant?: AvatarVariant;\n /** @default 'accent' */\n color?: AvatarColor;\n className?: string;\n animation?: AnimationRootDisableAll;\n}\n\n/**\n * Animation configuration for avatar image component\n */\nexport type AvatarImageAnimation = Animation<{\n opacity?: AnimationValue<{\n value?: [number, number];\n timingConfig?: WithTimingConfig;\n }>;\n}>;\n\n/**\n * Props for the Avatar image component\n */\nexport type AvatarImageProps =\n | (AnimatedProps & {\n className?: string;\n asChild?: false;\n animation?: AvatarImageAnimation;\n isAnimatedStyleActive?: boolean;\n })\n | (PrimitiveImageProps & {\n className?: string;\n asChild: true;\n });\n\n/**\n * Animation configuration for avatar fallback component\n */\nexport type AvatarFallbackAnimation = Animation<{\n entering?: AnimationValue<{\n value?: EntryOrExitLayoutType;\n }>;\n}>;\n\n/**\n * Props for the Avatar fallback component\n */\nexport interface AvatarFallbackProps\n extends Omit, \"entering\"> {\n /** @default 0 */\n delayMs?: number;\n color?: AvatarColor;\n className?: string;\n classNames?: ElementSlots;\n styles?: {\n container?: ViewStyle;\n text?: TextStyle;\n };\n textProps?: TextProps;\n iconProps?: PersonIconProps;\n animation?: AvatarFallbackAnimation;\n}\n\n/**\n * Context value shared between Avatar components\n */\nexport interface AvatarContextValue {\n size: AvatarSize;\n color: AvatarColor;\n}\n\n/** Reference type for the Avatar root component */\nexport type AvatarRootRef = PrimitiveRootRef;\n\n/** Reference type for the Avatar image component */\nexport type AvatarImageRef = PrimitiveImageRef;\n\n/** Reference type for the Avatar fallback component */\nexport type AvatarFallbackRef = PrimitiveFallbackRef;\n\n/* -------------------------------------------------------------------------------------------------\n * Constants\n * -----------------------------------------------------------------------------------------------*/\n/**\n * Display names for Avatar components\n */\nexport const AVATAR_DISPLAY_NAME = {\n ROOT: \"PitsiUINative.Avatar\",\n IMAGE: \"PitsiUINative.Avatar.Image\",\n FALLBACK: \"PitsiUINative.Avatar.Fallback\",\n};\n\n/**\n * Default icon sizes for different avatar sizes\n */\nexport const AVATAR_DEFAULT_ICON_SIZE: Record = {\n sm: 14,\n md: 16,\n lg: 20,\n};\n\n/* -------------------------------------------------------------------------------------------------\n * Styles\n * -----------------------------------------------------------------------------------------------*/\nconst root = tv({\n base: \"items-center justify-center overflow-hidden rounded-full\",\n variants: {\n variant: {\n default: \"bg-default\",\n soft: \"\",\n },\n size: {\n sm: \"size-10\",\n md: \"size-12\",\n lg: \"size-16\",\n },\n color: {\n accent: \"\",\n default: \"\",\n success: \"\",\n warning: \"\",\n danger: \"\",\n },\n },\n compoundVariants: [\n {\n variant: \"soft\",\n color: \"accent\",\n className: \"bg-accent/15\",\n },\n {\n variant: \"soft\",\n color: \"default\",\n className: \"bg-default\",\n },\n {\n variant: \"soft\",\n color: \"success\",\n className: \"bg-success/15\",\n },\n {\n variant: \"soft\",\n color: \"warning\",\n className: \"bg-warning/15\",\n },\n {\n variant: \"soft\",\n color: \"danger\",\n className: \"bg-danger/15\",\n },\n ],\n defaultVariants: {\n variant: \"default\",\n size: \"md\",\n color: \"accent\",\n },\n});\n\nconst image = tv({\n base: \"h-full w-full\",\n});\n\nconst fallback = tv({\n slots: {\n container: \"h-full w-full items-center justify-center rounded-full\",\n text: \"font-medium\",\n },\n variants: {\n size: {\n sm: {\n text: \"text-xs\",\n },\n md: {\n text: \"text-sm\",\n },\n lg: {\n text: \"text-base\",\n },\n },\n color: {\n default: {\n text: \"text-default-foreground\",\n },\n accent: {\n text: \"text-accent\",\n },\n success: {\n text: \"text-success\",\n },\n warning: {\n text: \"text-warning\",\n },\n danger: {\n text: \"text-danger\",\n },\n },\n },\n defaultVariants: {\n size: \"md\",\n color: \"default\",\n },\n});\n\nexport const avatarClassNames = combineStyles({\n root,\n image,\n fallback,\n});\n\nexport const avatarStyleSheet = StyleSheet.create({\n borderCurve: {\n borderCurve: \"continuous\",\n },\n});\n\n/**\n * Export slot types for type-safe classNames props\n */\nexport type AvatarFallbackSlots = keyof ReturnType;\n\n/* -------------------------------------------------------------------------------------------------\n * Avatar.Root\n * -----------------------------------------------------------------------------------------------*/\nconst AnimatedFallback = Animated.createAnimatedComponent(AvatarPrimitives.Fallback);\n\n/**\n * Hook to access Avatar primitive root context\n */\nconst useAvatar = AvatarPrimitives.useRootContext;\n\nconst AvatarRoot = forwardRef((props, ref) => {\n const {\n children,\n size = \"md\",\n variant = \"default\",\n color = \"accent\",\n className,\n style,\n animation,\n ...restProps\n } = props;\n\n const rootClassName = avatarClassNames.root({\n variant,\n size,\n color,\n className,\n });\n\n const { isAllAnimationsDisabled } = useAvatarRootAnimation({\n animation,\n });\n\n const contextValue = useMemo(\n () => ({\n size,\n color,\n }),\n [size, color],\n );\n\n const animationSettingsContextValue = useMemo(\n () => ({\n isAllAnimationsDisabled,\n }),\n [isAllAnimationsDisabled],\n );\n\n return (\n \n \n \n {children}\n \n \n \n );\n});\n\n/* -------------------------------------------------------------------------------------------------\n * Avatar.Image\n * -----------------------------------------------------------------------------------------------*/\nconst AvatarImage = forwardRef((props, ref) => {\n const { className, style: styleProp, source, asChild, ...restProps } = props;\n\n const animation = asChild ? undefined : \"animation\" in props ? props.animation : undefined;\n\n const isAnimatedStyleActive = asChild\n ? true\n : \"isAnimatedStyleActive\" in props\n ? (props.isAnimatedStyleActive ?? true)\n : true;\n\n const { rImageStyle } = useAvatarImageAnimation({\n animation,\n });\n\n const imageClassName = avatarClassNames.image({\n className,\n });\n\n const imageStyle = isAnimatedStyleActive ? [rImageStyle, styleProp] : styleProp;\n\n if (asChild) {\n return (\n )}\n />\n );\n }\n\n return (\n \n \n \n );\n});\n\n/* -------------------------------------------------------------------------------------------------\n * Avatar.Fallback\n * -----------------------------------------------------------------------------------------------*/\nconst DefaultFallbackIcon: React.FC<{\n sizeVariant: AvatarSize;\n colorVariant: AvatarColor;\n iconProps?: PersonIconProps;\n}> = ({ sizeVariant, colorVariant, iconProps }) => {\n const [\n themeColorDefaultForeground,\n themeColorAccent,\n themeColorSuccess,\n themeColorWarning,\n themeColorDanger,\n ] = useThemeColor([\"default-foreground\", \"accent\", \"success\", \"warning\", \"danger\"]);\n\n const iconSize = iconProps?.size ?? AVATAR_DEFAULT_ICON_SIZE[sizeVariant];\n\n const defaultIconColorMap: Record = {\n default: themeColorDefaultForeground,\n accent: themeColorAccent,\n success: themeColorSuccess,\n warning: themeColorWarning,\n danger: themeColorDanger,\n };\n\n const iconColor = iconProps?.color ?? defaultIconColorMap[colorVariant];\n\n return ;\n};\n\nconst AvatarFallback = forwardRef((props, ref) => {\n const { size, color: contextColor } = useInnerAvatarContext();\n\n const {\n children,\n color: colorProp,\n className,\n classNames,\n style,\n styles,\n textProps,\n iconProps,\n delayMs,\n animation,\n ...restProps\n } = props;\n\n const stringifiedChildren = childrenToString(children);\n\n const color = colorProp ?? contextColor;\n\n const { container, text } = avatarClassNames.fallback({\n size,\n color,\n });\n\n const fallbackContainerClassName = container({\n className: [className, classNames?.container],\n });\n\n const fallbackTextClassName = text({\n className: [classNames?.text, textProps?.className],\n });\n\n const { entering } = useAvatarFallbackAnimation({\n animation,\n delayMs,\n });\n\n return (\n \n {children ? (\n stringifiedChildren ? (\n \n {stringifiedChildren}\n \n ) : (\n children\n )\n ) : (\n \n )}\n \n );\n});\n\nAvatarRoot.displayName = AVATAR_DISPLAY_NAME.ROOT;\nAvatarImage.displayName = AVATAR_DISPLAY_NAME.IMAGE;\nAvatarFallback.displayName = AVATAR_DISPLAY_NAME.FALLBACK;\n\n/* -------------------------------------------------------------------------------------------------\n * Compound export\n *\n * @component Avatar - Main container that manages avatar display state.\n * @component Avatar.Image - Optional image component that displays the avatar image.\n * @component Avatar.Fallback - Optional fallback component shown when image fails to load.\n *\n * @see https://pitsiui.com/docs/native/components/avatar\n * -----------------------------------------------------------------------------------------------*/\nconst Avatar = Object.assign(AvatarRoot, {\n /** @optional Displays the avatar image with loading state management */\n Image: AvatarImage,\n /** @optional Shows fallback content when image is unavailable */\n Fallback: AvatarFallback,\n});\n\nexport default Avatar;\nexport { Avatar, useAvatar };\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/components/avatar/avatar.tsx" }, { "path": "registry/native-ui/src/components/avatar/index.ts", "content": "export type {\n AvatarColor,\n AvatarContextValue,\n AvatarFallbackProps,\n AvatarFallbackRef,\n AvatarImageProps,\n AvatarImageRef,\n AvatarRootProps,\n AvatarRootRef,\n AvatarSize,\n} from \"./avatar\";\nexport { Avatar, avatarClassNames, default, useAvatar } from \"./avatar\";\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/components/avatar/index.ts" }, { "path": "registry/native-ui/src/components/avatar/person-icon.tsx", "content": "import type React from \"react\";\nimport Svg, { Path } from \"react-native-svg\";\n\nexport interface PersonIconProps {\n size?: number;\n color?: string;\n}\n\nexport const PersonIcon: React.FC = ({ size = 16, color = \"currentColor\" }) => {\n return (\n \n \n \n );\n};\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/components/avatar/person-icon.tsx" }, { "path": "registry/native-ui/src/components/button/button.tsx", "content": "import { forwardRef, useMemo } from \"react\";\nimport { StyleSheet, type TextProps } from \"react-native\";\nimport { tv } from \"tailwind-variants\";\nimport { useThemeColor } from \"../../helpers/external/hooks\";\nimport { colorKit } from \"../../helpers/external/utils\";\nimport { HeroText } from \"../../helpers/internal/components/hero-text\";\nimport type {\n AnimationRoot,\n AnimationRootDisableAll,\n PressableRef,\n TextRef,\n} from \"../../helpers/internal/types\";\nimport { childrenToString, combineStyles, createContext } from \"../../helpers/internal/utils\";\nimport {\n PressableFeedback,\n type PressableFeedbackHighlightAnimation,\n type PressableFeedbackProps,\n type PressableFeedbackRippleAnimation,\n type PressableFeedbackScaleAnimation,\n} from \"../pressable-feedback\";\n\n/* -------------------------------------------------------------------------------------------------\n * Constants\n * -----------------------------------------------------------------------------------------------*/\nexport const DISPLAY_NAME = {\n BUTTON_ROOT: \"PitsiUINative.Button.Root\",\n BUTTON_LABEL: \"PitsiUINative.Button.Label\",\n};\n\n/* -------------------------------------------------------------------------------------------------\n * Types\n * -----------------------------------------------------------------------------------------------*/\nexport type ButtonSize = \"sm\" | \"md\" | \"lg\";\n\nexport type ButtonVariant =\n | \"primary\"\n | \"secondary\"\n | \"tertiary\"\n | \"outline\"\n | \"ghost\"\n | \"danger\"\n | \"danger-soft\";\n\nexport type ButtonFeedbackVariant = \"scale-highlight\" | \"scale-ripple\" | \"scale\" | \"none\";\n\ntype ButtonRootPropsBase = Omit & {\n /** @default 'primary' */\n variant?: ButtonVariant;\n /** @default 'md' */\n size?: ButtonSize;\n /** @default false */\n isIconOnly?: boolean;\n};\n\nexport type ButtonRootPropsScaleHighlight = ButtonRootPropsBase & {\n /** @default 'scale-highlight' */\n feedbackVariant?: \"scale-highlight\";\n animation?: AnimationRoot<{\n scale?: PressableFeedbackScaleAnimation;\n highlight?: PressableFeedbackHighlightAnimation;\n }>;\n};\n\ntype ButtonRootPropsScaleRipple = ButtonRootPropsBase & {\n feedbackVariant: \"scale-ripple\";\n animation?: AnimationRoot<{\n scale?: PressableFeedbackScaleAnimation;\n ripple?: PressableFeedbackRippleAnimation;\n }>;\n};\n\ntype ButtonRootPropsScale = ButtonRootPropsBase & {\n feedbackVariant: \"scale\";\n animation?: AnimationRoot<{\n scale?: PressableFeedbackScaleAnimation;\n }>;\n};\n\ntype ButtonRootPropsNone = ButtonRootPropsBase & {\n feedbackVariant: \"none\";\n animation?: AnimationRootDisableAll;\n};\n\nexport type ButtonRootProps =\n | ButtonRootPropsScaleHighlight\n | ButtonRootPropsScaleRipple\n | ButtonRootPropsScale\n | ButtonRootPropsNone;\n\nexport interface ButtonLabelProps extends TextProps {\n children?: React.ReactNode;\n className?: string;\n}\n\nexport interface ButtonContextValue {\n size: ButtonSize;\n variant: ButtonVariant;\n isDisabled: boolean;\n}\n\n/* -------------------------------------------------------------------------------------------------\n * Styles\n *\n * @note ANIMATED PROPERTIES (cannot be set via className):\n * `transform` (specifically `scale`) — animated for press feedback. Use the\n * `animation` prop to customize: `\n );\n});\n\n// --------------------------------------------------\n\nconst ToastClose = forwardRef((props, ref) => {\n const { children, iconProps, size = \"sm\", className, onPress, ...restProps } = props;\n const { hide, id } = useToast();\n\n const themeColorMuted = useThemeColor(\"muted\");\n\n /**\n * Handle close button press\n * If hide and id are available from context, use them to hide the toast\n * Otherwise, use the provided onPress handler\n */\n const handlePress = (event: any) => {\n if (hide && id) {\n hide(id);\n }\n if (onPress && typeof onPress === \"function\") {\n onPress(event);\n }\n };\n\n return (\n \n {children ?? (\n \n )}\n \n );\n});\n\n// --------------------------------------------------\n\n/**\n * Default styled toast component for simplified toast.show() API\n * Used internally when showing toasts with string or config object (without component)\n */\nexport function DefaultToast(props: DefaultToastProps) {\n const globalConfig = useToastConfig();\n\n const {\n id,\n variant: localVariant,\n placement: localPlacement,\n isSwipeable: localIsSwipeable,\n animation: localAnimation,\n label,\n description,\n actionLabel,\n onActionPress,\n icon,\n hide,\n show,\n ...toastComponentProps\n } = props;\n\n /**\n * Merge global config with local props, ensuring local props take precedence\n */\n const variant = localVariant ?? globalConfig?.variant ?? \"default\";\n const placement = localPlacement ?? globalConfig?.placement ?? \"top\";\n const isSwipeable = localIsSwipeable ?? globalConfig?.isSwipeable;\n const animation = localAnimation ?? globalConfig?.animation;\n\n const handleActionPress = () => {\n if (onActionPress) {\n onActionPress({ show, hide });\n }\n };\n\n return (\n \n {icon && {icon}}\n \n {label && {label}}\n {description && {description}}\n \n {actionLabel && {actionLabel}}\n \n );\n}\n\n// --------------------------------------------------\n\nToastRoot.displayName = DISPLAY_NAME.TOAST_ROOT;\nToastTitle.displayName = DISPLAY_NAME.TOAST_TITLE;\nToastDescription.displayName = DISPLAY_NAME.TOAST_DESCRIPTION;\nToastAction.displayName = DISPLAY_NAME.TOAST_ACTION;\nToastClose.displayName = DISPLAY_NAME.TOAST_CLOSE;\n\n/**\n * Compound Toast component with sub-components\n *\n * @component Toast - Main toast container that displays notification messages with various variants.\n *\n * @component Toast.Title - Title/heading text of the toast notification.\n *\n * @component Toast.Description - Descriptive text content of the toast.\n *\n * @component Toast.Action - Action button within the toast. Variant is automatically determined\n * based on toast variant but can be overridden.\n *\n * @component Toast.Close - Close button for dismissing the toast. Renders as an icon-only button.\n *\n * Props flow from Toast to sub-components via context (variant).\n *\n * @see Full documentation: https://pitsiui.com/docs/native/components/toast\n */\nconst CompoundToast = Object.assign(ToastRoot, {\n /** Toast title - renders text content */\n Title: ToastTitle,\n /** Toast description - renders descriptive text */\n Description: ToastDescription,\n /** Toast action button - renders action with appropriate variant */\n Action: ToastAction,\n /** Toast close button - renders icon-only close button */\n Close: ToastClose,\n});\n\nexport default CompoundToast;\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/components/toast/toast.tsx" }, { "path": "registry/native-ui/src/helpers/external/hooks/index.ts", "content": "export { useTextComponent } from \"../../../providers/text-component/index\";\nexport * from \"../../internal/hooks/use-combined-animation-disabled-state\";\nexport * from \"./use-bottom-sheet-aware-handlers\";\nexport * from \"./use-is-on-surface\";\nexport * from \"./use-theme-color\";\n", "type": "registry:hook", "target": "@components/pitsi-ui/native-ui/src/helpers/external/hooks/index.ts" }, { "path": "registry/native-ui/src/helpers/external/hooks/use-bottom-sheet-aware-handlers.ts", "content": "import { useCallback } from \"react\";\nimport { type BlurEvent, type FocusEvent, findNodeHandle, TextInput } from \"react-native\";\nimport GorhomBottomSheetPackage from \"../../../optional/gorhom-bottom-sheet\";\n\n/**\n * Return type for the bottom-sheet-aware handlers hook\n */\ninterface UseBottomSheetAwareHandlersReturn {\n /** Focus handler that notifies the bottom sheet about the keyboard target */\n onFocus: (e: FocusEvent) => void;\n /** Blur handler that conditionally clears the keyboard target in the bottom sheet */\n onBlur: (e: BlurEvent) => void;\n}\n\n/**\n * Hook that provides onFocus/onBlur handlers for managing bottom sheet\n * keyboard state when an input is rendered inside a BottomSheet context.\n *\n * Uses `useBottomSheetInternal(true)` (unsafe mode) so it returns `null`\n * instead of throwing when called outside a BottomSheet. When inside a\n * BottomSheet it returns handlers that wire into the keyboard state\n * management logic required by `@gorhom/bottom-sheet`.\n *\n * Pass the returned handlers to your `` or `` component:\n *\n * ```tsx\n * const { onFocus, onBlur } = useBottomSheetAwareHandlers();\n * \n * ```\n *\n * @returns onFocus and onBlur handlers for bottom sheet keyboard management\n */\nexport function useBottomSheetAwareHandlers(): UseBottomSheetAwareHandlersReturn {\n const useBottomSheetInternal = GorhomBottomSheetPackage?.useBottomSheetInternal;\n const bottomSheetContext = useBottomSheetInternal?.(true) ?? null;\n\n const isActive = bottomSheetContext !== null;\n\n /**\n * Handles focus event: notifies the bottom sheet about the keyboard target.\n */\n const onFocus = useCallback(\n (e: FocusEvent) => {\n if (isActive && bottomSheetContext) {\n bottomSheetContext.animatedKeyboardState.set((state: Record) => ({\n ...state,\n target: e.nativeEvent.target,\n }));\n }\n },\n [isActive, bottomSheetContext],\n );\n\n /**\n * Handles blur event: conditionally clears the keyboard target in the\n * bottom sheet state.\n */\n const onBlur = useCallback(\n (e: BlurEvent) => {\n if (isActive && bottomSheetContext) {\n const keyboardState = bottomSheetContext.animatedKeyboardState.get();\n const currentFocusedInput = findNodeHandle(\n TextInput.State.currentlyFocusedInput() as TextInput | null,\n );\n const shouldRemoveCurrentTarget = keyboardState.target === e.nativeEvent.target;\n const shouldIgnoreBlurEvent =\n currentFocusedInput &&\n bottomSheetContext.textInputNodesRef.current.has(currentFocusedInput);\n\n if (shouldRemoveCurrentTarget && !shouldIgnoreBlurEvent) {\n bottomSheetContext.animatedKeyboardState.set((state: Record) => ({\n ...state,\n target: undefined,\n }));\n }\n }\n },\n [isActive, bottomSheetContext],\n );\n\n return { onFocus, onBlur };\n}\n", "type": "registry:hook", "target": "@components/pitsi-ui/native-ui/src/helpers/external/hooks/use-bottom-sheet-aware-handlers.ts" }, { "path": "registry/native-ui/src/helpers/external/hooks/use-is-on-surface.ts", "content": "import { useSurface } from \"../../../components/surface\";\n\nexport const useIsOnSurface = () => {\n const surfaceContext = useSurface();\n return !!(surfaceContext?.variant && surfaceContext.variant !== \"transparent\");\n};\n", "type": "registry:hook", "target": "@components/pitsi-ui/native-ui/src/helpers/external/hooks/use-is-on-surface.ts" }, { "path": "registry/native-ui/src/helpers/external/hooks/use-theme-color.ts", "content": "import { useCSSVariable } from \"uniwind\";\n\n/**\n * Unique brand symbol used to prevent accidental array destructuring of a\n * single theme color value returned from `useThemeColor`.\n */\ndeclare const _colorValueBrand: unique symbol;\n\n/**\n * A resolved theme color string.\n *\n * This type intentionally removes `[Symbol.iterator]` so that TypeScript\n * surfaces a `never`-typed element when the value is array-destructured,\n * making the misuse visible in the IDE immediately.\n *\n * @example\n * // ✅ Correct – single value\n * const color = useThemeColor('muted');\n *\n * @example\n * // ✅ Correct – multiple values\n * const [primary, bg] = useThemeColor(['accent', 'background']);\n *\n * @example\n * // ❌ Wrong – destructuring a single-color result yields `never`\n * const [color] = useThemeColor('muted');\n */\nexport type ThemeColorValue = string & {\n readonly [_colorValueBrand]: undefined;\n /** Removed to prevent accidental array destructuring. */\n readonly [Symbol.iterator]: never;\n};\n\n/**\n * Theme colors as const array for efficient mapping\n * Ordered to match the order in src/styles/theme.css\n */\nconst THEME_COLORS = [\n \"background\",\n \"foreground\",\n \"surface\",\n \"surface-foreground\",\n \"surface-hover\",\n \"overlay\",\n \"overlay-foreground\",\n \"overlay-backdrop\",\n \"muted\",\n \"accent\",\n \"accent-foreground\",\n \"segment\",\n \"segment-foreground\",\n \"border\",\n \"separator\",\n \"focus\",\n \"link\",\n \"default\",\n \"default-foreground\",\n \"success\",\n \"success-foreground\",\n \"warning\",\n \"warning-foreground\",\n \"danger\",\n \"danger-foreground\",\n \"field\",\n \"field-foreground\",\n \"field-placeholder\",\n \"field-border\",\n \"background-secondary\",\n \"background-tertiary\",\n \"background-inverse\",\n \"default-hover\",\n \"accent-hover\",\n \"success-hover\",\n \"warning-hover\",\n \"danger-hover\",\n \"field-hover\",\n \"field-focus\",\n \"field-border-hover\",\n \"field-border-focus\",\n \"accent-soft\",\n \"accent-soft-foreground\",\n \"accent-soft-hover\",\n \"danger-soft\",\n \"danger-soft-foreground\",\n \"danger-soft-hover\",\n \"warning-soft\",\n \"warning-soft-foreground\",\n \"warning-soft-hover\",\n \"success-soft\",\n \"success-soft-foreground\",\n \"success-soft-hover\",\n \"surface-secondary\",\n \"surface-tertiary\",\n \"on-surface\",\n \"on-surface-foreground\",\n \"on-surface-hover\",\n \"on-surface-focus\",\n \"on-surface-secondary\",\n \"on-surface-secondary-foreground\",\n \"on-surface-secondary-hover\",\n \"on-surface-secondary-focus\",\n \"on-surface-tertiary\",\n \"on-surface-tertiary-foreground\",\n \"on-surface-tertiary-hover\",\n \"on-surface-tertiary-focus\",\n \"separator-secondary\",\n \"separator-tertiary\",\n \"border-secondary\",\n \"border-tertiary\",\n] as const;\n\n/**\n * Theme colors type derived from THEME_COLORS array\n */\nexport type ThemeColor = (typeof THEME_COLORS)[number];\n\n/**\n * Helper type to create a tuple of strings with the same length as the input array\n */\ntype CreateStringTuple = TAcc[\"length\"] extends N\n ? TAcc\n : CreateStringTuple;\n\n/**\n * Hook to retrieve theme color values from CSS variables.\n * Supports both single color and multiple colors for efficient batch retrieval.\n *\n * @param themeColor - Single theme color name or array of theme color names\n * @returns `ThemeColorValue` for a single name, or a string tuple/array for multiple names.\n *\n * @example\n * // Single color – returns `ThemeColorValue` (not destructurable)\n * const primaryColor = useThemeColor('accent');\n *\n * @example\n * // Multiple colors – returns a typed string tuple (destructurable)\n * const [primaryColor, backgroundColor] = useThemeColor(['accent', 'background']);\n */\nexport function useThemeColor(themeColor: ThemeColor): ThemeColorValue;\nexport function useThemeColor(\n themeColor: T,\n): CreateStringTuple;\nexport function useThemeColor(themeColor: ThemeColor[]): string[];\nexport function useThemeColor(themeColor: ThemeColor | ThemeColor[]): ThemeColorValue | string[] {\n const isArray = Array.isArray(themeColor);\n const cssVariables = isArray\n ? themeColor.map((color) => `--color-${color}`)\n : [`--color-${themeColor}`];\n\n const resolvedColors = useCSSVariable(cssVariables);\n\n const processedColors: string[] = resolvedColors.map((color) => {\n if (typeof color === \"string\") {\n return color;\n }\n if (typeof color === \"number\") {\n return String(color);\n }\n return \"invalid\";\n });\n\n if (isArray) {\n return processedColors;\n }\n\n /** `cssVariables` always contains one entry when `isArray` is false, so index 0 is always defined. */\n return (processedColors[0] ?? \"invalid\") as ThemeColorValue;\n}\n", "type": "registry:hook", "target": "@components/pitsi-ui/native-ui/src/helpers/external/hooks/use-theme-color.ts" }, { "path": "registry/native-ui/src/helpers/external/utils/cn.ts", "content": "import { type CnOptions, cnMerge } from \"tailwind-variants\";\n\nexport function cn(...args: CnOptions) {\n return cnMerge(args)({\n twMerge: true,\n twMergeConfig: {\n classGroups: {\n opacity: [{ opacity: [\"disabled\"] }],\n },\n },\n });\n}\n", "type": "registry:lib", "target": "@components/pitsi-ui/native-ui/src/helpers/external/utils/cn.ts" }, { "path": "registry/native-ui/src/helpers/external/utils/color-kit/index.ts", "content": "/**\n * Color manipulation utilities adapted from reanimated-color-picker\n *\n * Original source: https://github.com/alabsi91/reanimated-color-picker\n * Author: @alabsi91\n * License: MIT\n *\n * This code has been adapted for use in PitsiUI Native with modifications\n * for TypeScript compatibility and integration with the theme system.\n */\n\nimport type {\n ColorFormats,\n ColorTypes,\n ConversionMethods,\n hslaT,\n hslT,\n hsvaT,\n hsvT,\n hwbaT,\n hwbT,\n rgbaT,\n rgbT,\n SupportedColorFormats,\n} from \"./types\";\n\n// If you find yourself wondering why all of this is within a single function,\n// the reason is that to execute each method on the UI thread, you must include the 'worklet' directive.\n// Functions marked with this directive are transformed by the Reanimated Babel plugin\n// into IIFE — IMMEDIATELY INVOKED FUNCTION EXPRESSION: `const fun = (function{})()`.\n// Due to the presence of numerous methods,\n// this transformation can lead to a slow initial execution.\n// To address this issue, I consolidated them into a single worklet function.\n\nexport const colorKitUI = () => {\n \"worklet\";\n\n const NAMED_COLORS = {\n aliceblue: \"#f0f8ff\",\n antiquewhite: \"#faebd7\",\n aqua: \"#00ffff\",\n aquamarine: \"#7fffd4\",\n azure: \"#f0ffff\",\n beige: \"#f5f5dc\",\n bisque: \"#ffe4c4\",\n black: \"#000000\",\n blanchedalmond: \"#ffebcd\",\n blue: \"#0000ff\",\n blueviolet: \"#8a2be2\",\n brown: \"#a52a2a\",\n burlywood: \"#deb887\",\n cadetblue: \"#5f9ea0\",\n chartreuse: \"#7fff00\",\n chocolate: \"#d2691e\",\n coral: \"#ff7f50\",\n cornflowerblue: \"#6495ed\",\n cornsilk: \"#fff8dc\",\n crimson: \"#dc143c\",\n cyan: \"#00ffff\",\n darkblue: \"#00008b\",\n darkcyan: \"#008b8b\",\n darkgoldenrod: \"#b8860b\",\n darkgray: \"#a9a9a9\",\n darkgreen: \"#006400\",\n darkgrey: \"#a9a9a9\",\n darkkhaki: \"#bdb76b\",\n darkmagenta: \"#8b008b\",\n darkolivegreen: \"#556b2f\",\n darkorange: \"#ff8c00\",\n darkorchid: \"#9932cc\",\n darkred: \"#8b0000\",\n darksalmon: \"#e9967a\",\n darkseagreen: \"#8fbc8f\",\n darkslateblue: \"#483d8b\",\n darkslategrey: \"#2f4f4f\",\n darkturquoise: \"#00ced1\",\n darkviolet: \"#9400d3\",\n deeppink: \"#ff1493\",\n deepskyblue: \"#00bfff\",\n dimgray: \"#696969\",\n dimgrey: \"#696969\",\n dodgerblue: \"#1e90ff\",\n firebrick: \"#b22222\",\n floralwhite: \"#fffaf0\",\n forestgreen: \"#228b22\",\n fuchsia: \"#ff00ff\",\n gainsboro: \"#dcdcdc\",\n ghostwhite: \"#f8f8ff\",\n gold: \"#ffd700\",\n goldenrod: \"#daa520\",\n gray: \"#808080\",\n green: \"#008000\",\n greenyellow: \"#adff2f\",\n grey: \"#808080\",\n honeydew: \"#f0fff0\",\n hotpink: \"#ff69b4\",\n indianred: \"#cd5c5c\",\n indigo: \"#4b0082\",\n ivory: \"#fffff0\",\n khaki: \"#f0e68c\",\n lavender: \"#e6e6fa\",\n lavenderblush: \"#fff0f5\",\n lawngreen: \"#7cfc00\",\n lemonchiffon: \"#fffacd\",\n lightblue: \"#add8e6\",\n lightcoral: \"#f08080\",\n lightcyan: \"#e0ffff\",\n lightgoldenrodyellow: \"#fafad2\",\n lightgray: \"#d3d3d3\",\n lightgreen: \"#90ee90\",\n lightgrey: \"#d3d3d3\",\n lightpink: \"#ffb6c1\",\n lightsalmon: \"#ffa07a\",\n lightseagreen: \"#20b2aa\",\n lightskyblue: \"#87cefa\",\n lightslategrey: \"#778899\",\n lightsteelblue: \"#b0c4de\",\n lightyellow: \"#ffffe0\",\n lime: \"#00ff00\",\n limegreen: \"#32cd32\",\n linen: \"#faf0e6\",\n magenta: \"#ff00ff\",\n maroon: \"#800000\",\n mediumaquamarine: \"#66cdaa\",\n mediumblue: \"#0000cd\",\n mediumorchid: \"#ba55d3\",\n mediumpurple: \"#9370db\",\n mediumseagreen: \"#3cb371\",\n mediumslateblue: \"#7b68ee\",\n mediumspringgreen: \"#00fa9a\",\n mediumturquoise: \"#48d1cc\",\n mediumvioletred: \"#c71585\",\n midnightblue: \"#191970\",\n mintcream: \"#f5fffa\",\n mistyrose: \"#ffe4e1\",\n moccasin: \"#ffe4b5\",\n navajowhite: \"#ffdead\",\n navy: \"#000080\",\n oldlace: \"#fdf5e6\",\n olive: \"#808000\",\n olivedrab: \"#6b8e23\",\n orange: \"#ffa500\",\n orangered: \"#ff4500\",\n orchid: \"#da70d6\",\n palegoldenrod: \"#eee8aa\",\n palegreen: \"#98fb98\",\n paleturquoise: \"#afeeee\",\n palevioletred: \"#db7093\",\n papayawhip: \"#ffefd5\",\n peachpuff: \"#ffdab9\",\n peru: \"#cd853f\",\n pink: \"#ffc0cb\",\n plum: \"#dda0dd\",\n powderblue: \"#b0e0e6\",\n purple: \"#800080\",\n rebeccapurple: \"#663399\",\n red: \"#ff0000\",\n rosybrown: \"#bc8f8f\",\n royalblue: \"#4169e1\",\n saddlebrown: \"#8b4513\",\n salmon: \"#fa8072\",\n sandybrown: \"#f4a460\",\n seagreen: \"#2e8b57\",\n seashell: \"#fff5ee\",\n sienna: \"#a0522d\",\n silver: \"#c0c0c0\",\n skyblue: \"#87ceeb\",\n slateblue: \"#6a5acd\",\n slategray: \"#708090\",\n snow: \"#fffafa\",\n springgreen: \"#00ff7f\",\n steelblue: \"#4682b4\",\n tan: \"#d2b48c\",\n teal: \"#008080\",\n thistle: \"#d8bfd8\",\n tomato: \"#ff6347\",\n turquoise: \"#40e0d0\",\n violet: \"#ee82ee\",\n wheat: \"#f5deb3\",\n white: \"#ffffff\",\n whitesmoke: \"#f5f5f5\",\n yellow: \"#ffff00\",\n yellowgreen: \"#9acd32\",\n };\n\n const COLORS_REGEX: Record = {\n // #rgb\n hex3: /^#([A-Fa-f0-9]{3})$/,\n // #rgba\n hex4: /^#([A-Fa-f0-9]{3}[A-Fa-f0-9]{1})$/,\n // #rrggbb\n hex6: /^#([A-Fa-f0-9]{6})$/,\n // #rrggbbaa\n hex8: /^#([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i,\n\n hsl: [\n // hsl(360deg, 100%, 100%) hsl(360, 100%, 100%) hsl(23.55, 12.22%, 34.56%)\n /^hsl\\s*\\(\\s*([\\d.]+)(?:deg)?\\s*,\\s*([\\d.]+)%\\s*,\\s*([\\d.]+)%\\s*\\)$/i,\n // hsl(360deg 100% 100%) hsl(360 100% 100%) hsl(23.55 12.22% 34.56%)\n /^hsl\\s*\\(\\s*([\\d.]+)(?:deg)?\\s+([\\d.]+)%\\s+([\\d.]+)%\\s*\\)$/i,\n ],\n hsla: [\n // hsla(360deg, 100%, 100%, 1.0) hsla(360, 100%, 100%, 1.0) hsl(360deg, 100%, 100%, 1.0) hsl(360, 100%, 100%, 1.0)\n /^hsla?\\s*\\(\\s*([\\d.]+)(?:deg)?\\s*,\\s*([\\d.]+)%\\s*,\\s*([\\d.]+)%\\s*,\\s*(\\d|\\d\\.\\d+)\\s*\\)$/i,\n // hsla(360deg 100% 100% / 1.0) hsla(360 100% 100% / 1.0) hsl(360deg 100% 100% / 1.0) hsl(360 100% 100% / 1.0)\n /^hsla?\\s*\\(\\s*([\\d.]+)(?:deg)?\\s+([\\d.]+)%\\s+([\\d.]+)%\\s*\\/\\s*(\\d|\\d\\.\\d+)\\s*\\)$/i,\n ],\n\n hsv: [\n // hsv(360deg, 000%, 000%) hsv(360, 100%, 100%)\n /^hsv\\s*\\(\\s*(\\d{1,3})(?:deg)?\\s*,\\s*([\\d.]+)%\\s*,\\s*([\\d.]+)%\\s*\\)$/i,\n // hsv(360deg 000% 000%) hsv(360 100% 100%)\n /^hsv\\s*\\(\\s*(\\d{1,3})(?:deg)?\\s+([\\d.]+)%\\s+([\\d.]+)%\\s*\\)$/i,\n ],\n hsva: [\n // hsva(360deg, 100%, 100%, 1.0) hsva(360, 100%, 100%, 1.0) hsv(360deg, 100%, 100%, 1.0) hsv(360, 100%, 100%, 1.0)\n /^hsva?\\s*\\(\\s*(\\d{1,3})(?:deg)?\\s*,\\s*([\\d.]+)%\\s*,\\s*([\\d.]+)%\\s*,\\s*(\\d|\\d\\.\\d+)\\s*\\)$/i,\n // hsva(360deg 100% 100% / 1.0) hsva(360 100% 100% / 1.0) hsv(360deg 100% 100% / 1.0) hsv(360 100% 100% / 1.0)\n /^hsva?\\s*\\(\\s*(\\d{1,3})(?:deg)?\\s+([\\d.]+)%\\s+([\\d.]+)%\\s*\\/\\s*(\\d|\\d\\.\\d+)\\s*\\)$/i,\n ],\n\n hwb: [\n // hwb(360deg, 100%, 100%) hwb(360, 100%, 100%)\n /^hwb\\s*\\(\\s*(\\d{1,3})(?:deg)?\\s*,\\s*([\\d.]+)%\\s*,\\s*([\\d.]+)%\\s*\\)$/i,\n // hwb(360deg 100% 100%) hwb(360 100% 100%)\n /^hwb\\s*\\(\\s*(\\d{1,3})(?:deg)?\\s+([\\d.]+)%\\s+([\\d.]+)%\\s*\\)$/i,\n ],\n hwba: [\n // hwba(360deg, 100%, 100%, 1.0) hwba(360, 100%, 100%, 1.0) hwb(360deg, 100%, 100%, 1.0) hwb(360, 100%, 100%, 1.0)\n /^hwba?\\s*\\(\\s*(\\d{1,3})(?:deg)?\\s*,\\s*([\\d.]+)%\\s*,\\s*([\\d.]+)%\\s*,\\s*(\\d|\\d\\.\\d+)\\s*\\)$/i,\n // hwb(360deg 100% 100% 1.0) hwb(360 100% 100% / 1.0) hwba(360deg 100% 100% 1.0) hwba(360 100% 100% / 1.0)\n /^hwba?\\s*\\(\\s*(\\d{1,3})(?:deg)?\\s+([\\d.]+)%\\s+([\\d.]+)%\\s*\\/\\s*(\\d|\\d\\.\\d+)\\s*\\)$/i,\n ],\n\n rgb: [\n // rgb(255, 255, 255)\n /^rgb\\s*\\(\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})\\s*\\)$/i,\n // rgb(255 255 255)\n /^rgb\\s*\\(\\s*(\\d{1,3})\\s+(\\d{1,3})\\s+(\\d{1,3})\\s*\\)$/i,\n ],\n rgba: [\n // rgba(255, 255, 255, 1.0) rgb(255, 255, 255, 1.0)\n /^rgba?\\s*\\(\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})\\s*,\\s*([\\d.]+)\\s*\\)$/i,\n // rgba(255 255 255 / 1.0) rgb(255 255 255 / 1.0)\n /^rgba?\\s*\\(\\s*(\\d{1,3})\\s+(\\d{1,3})\\s+(\\d{1,3})\\s*\\/\\s*([\\d.]+)\\s*\\)$/i,\n ],\n };\n\n // * MARK: General utilities\n\n const clamp = (value: number, min: number, max: number) => {\n return Math.max(min, Math.min(value, max));\n };\n\n const clampRGB = (value: number) => {\n return clamp(value, 0, 255);\n };\n\n const clampHue = (value: number) => {\n return clamp(value, 0, 360);\n };\n\n const clamp100 = (value: number) => {\n return clamp(value, 0, 100);\n };\n\n const clampAlpha = (value: number) => {\n return clamp(+value.toFixed(2), 0, 1);\n };\n\n const randomNumber = (min: number, max: number) => {\n return Math.random() * (max - min) + min;\n };\n\n const numberToHexString = (c: number): string => {\n c = clampRGB(Math.round(c));\n const hex = c.toString(16).padStart(2, \"0\");\n return hex;\n };\n\n const calculateHueValue = (p: number, q: number, t: number): number => {\n if (t < 0) t += 1;\n if (t > 1) t -= 1;\n if (t < 1 / 6) return p + (q - p) * 6 * t;\n if (t < 1 / 2) return q;\n if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;\n return p;\n };\n\n /** - Identify the color format of a given `string` or `object` */\n const detectColorFormat = (color: SupportedColorFormats): ColorFormats | null => {\n // color int\n if (typeof color === \"number\") {\n // eslint-disable-next-line no-bitwise\n if (color >>> 0 === color && color >= 0 && color <= 0xffffffff) return \"hex8\";\n return null;\n }\n\n // color string\n if (typeof color === \"string\") {\n color = color.trim().toLowerCase();\n for (const key in COLORS_REGEX) {\n const format = key as ColorFormats;\n const entry = COLORS_REGEX[format];\n if (Array.isArray(entry)) {\n for (let i = 0; i < entry.length; i++) {\n const regex = entry[i];\n if (regex?.test(color)) return format;\n }\n continue;\n }\n if (entry.test(color)) return format;\n }\n }\n\n // color object\n if (typeof color === \"object\") {\n const rgbaKeys = [\"r\", \"g\", \"b\", \"a\"] as (keyof rgbaT)[];\n const isRgbaOb = rgbaKeys.every(\n (k) => Object.hasOwn(color, k) && typeof (color as rgbaT)[k] === \"number\",\n );\n if (isRgbaOb) return \"rgba\";\n\n const rgbKeys = [\"r\", \"g\", \"b\"] as (keyof rgbT)[];\n const isRgbOb = rgbKeys.every(\n (k) => Object.hasOwn(color, k) && typeof (color as rgbT)[k] === \"number\",\n );\n if (isRgbOb) return \"rgb\";\n\n const hslaKeys = [\"h\", \"s\", \"l\", \"a\"] as (keyof hslaT)[];\n const isHslaOb = hslaKeys.every(\n (k) => Object.hasOwn(color, k) && typeof (color as hslaT)[k] === \"number\",\n );\n if (isHslaOb) return \"hsla\";\n\n const hslKeys = [\"h\", \"s\", \"l\"] as (keyof hslT)[];\n const isHslOb = hslKeys.every(\n (k) => Object.hasOwn(color, k) && typeof (color as hslT)[k] === \"number\",\n );\n if (isHslOb) return \"hsl\";\n\n const hsvaKeys = [\"h\", \"s\", \"v\", \"a\"] as (keyof hsvaT)[];\n const isHsvaOb = hsvaKeys.every(\n (k) => Object.hasOwn(color, k) && typeof (color as hsvaT)[k] === \"number\",\n );\n if (isHsvaOb) return \"hsva\";\n\n const hsvKeys = [\"h\", \"s\", \"v\"] as (keyof hsvT)[];\n const isHsvOb = hsvKeys.every(\n (k) => Object.hasOwn(color, k) && typeof (color as hsvT)[k] === \"number\",\n );\n if (isHsvOb) return \"hsv\";\n\n const hwbaKeys = [\"h\", \"w\", \"b\", \"a\"] as (keyof hwbaT)[];\n const isHwbaOb = hwbaKeys.every(\n (k) => Object.hasOwn(color, k) && typeof (color as hwbaT)[k] === \"number\",\n );\n if (isHwbaOb) return \"hwba\";\n\n const hwbKeys = [\"h\", \"w\", \"b\"] as (keyof hwbT)[];\n const isHwbOb = hwbKeys.every(\n (k) => Object.hasOwn(color, k) && typeof (color as hwbT)[k] === \"number\",\n );\n if (isHwbOb) return \"hwb\";\n }\n\n return null;\n };\n\n // * MARK: RGB\n\n /** - Parse `RGB` or `RGBA` color string to an `object` */\n const RGB_string_to_object = (color: string): rgbaT => {\n color = color.trim().toLowerCase();\n const colorType = detectColorFormat(color);\n\n if (!colorType?.includes(\"rgb\")) {\n console.error(\n '[colorKit.getRgbObject] is unable to parse the string into an `RGB` object. As a result, the color \"black\" will be returned instead.',\n );\n return { r: 0, g: 0, b: 0, a: 1 };\n }\n\n let matches: RegExpMatchArray | null = null;\n const entry = COLORS_REGEX[colorType];\n if (Array.isArray(entry)) {\n for (let i = 0; i < entry.length; i++) {\n const regex = entry[i];\n if (regex?.test(color)) {\n matches = color.match(regex);\n }\n }\n } else {\n matches = color.match(entry);\n }\n\n if (!matches || matches.length < 4) {\n console.error(\n '[colorKit.getRgbObject] An error occurred while attempting to destructuring `RGB` values from the given string. As a result, the color \"black\" will be returned instead.',\n );\n return { r: 0, g: 0, b: 0, a: 1 };\n }\n\n const r = parseInt(matches[1] || \"0\", 10),\n g = parseInt(matches[2] || \"0\", 10),\n b = parseInt(matches[3] || \"0\", 10),\n a = parseFloat(matches[4] ?? \"1\");\n\n return {\n r: clampRGB(r),\n g: clampRGB(g),\n b: clampRGB(b),\n a: clampAlpha(a),\n };\n };\n\n /** - Ensure that the `RGB` object values are within the correct range and that it has the alpha channel */\n const normalize_RGB_object = (color: rgbaT | rgbT): rgbaT => {\n return {\n r: clampRGB(color.r),\n g: clampRGB(color.g),\n b: clampRGB(color.b),\n a: clampAlpha((color as rgbaT).a ?? 1),\n };\n };\n\n /** - Convert an `RGB` or `RGBA` color to its corresponding `Hex` color */\n const RGB_to_HEX = (color: string | rgbaT | rgbT): string => {\n const { r, g, b, a } =\n typeof color === \"string\" ? RGB_string_to_object(color) : normalize_RGB_object(color);\n\n const red = numberToHexString(r),\n green = numberToHexString(g),\n blue = numberToHexString(b),\n alpha = a === 1 ? \"\" : numberToHexString(a * 255);\n\n return `#${red + green + blue + alpha}`;\n };\n\n /** - Convert `RGB` or `RGBA` color to an `RGBA` object representation */\n const RGB_to_RGB = (color: string | rgbaT | rgbT): rgbaT => {\n return typeof color === \"string\" ? RGB_string_to_object(color) : normalize_RGB_object(color);\n };\n\n /** - Convert an `RGB` or `RGBA` color to an `HSLA` object representation */\n const RGB_to_HSLA = (color: string | rgbaT | rgbT): hslaT => {\n const rgb =\n typeof color === \"string\" ? RGB_string_to_object(color) : normalize_RGB_object(color),\n r = rgb.r / 255,\n g = rgb.g / 255,\n b = rgb.b / 255,\n a = rgb.a;\n\n const max = Math.max(r, g, b),\n min = Math.min(r, g, b);\n\n let h = 0;\n let s = 0;\n let l = (max + min) / 2;\n\n if (max === min) {\n h = s = 0;\n } else {\n const d = max - min;\n s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\n\n if (max === r) {\n h = (g - b) / d + (g < b ? 6 : 0);\n } else if (max === g) {\n h = (b - r) / d + 2;\n } else if (max === b) {\n h = (r - g) / d + 4;\n }\n\n h /= 6;\n }\n\n h = clampHue(h * 360);\n s = clamp100(s * 100);\n l = clamp100(l * 100);\n\n return { h, s, l, a: clampAlpha(a) };\n };\n\n /** - Convert `RGB` or `RGBA` color to an `HSVA` object representation */\n const RGB_to_HSVA = (color: string | rgbaT | rgbT): hsvaT => {\n const rgb =\n typeof color === \"string\" ? RGB_string_to_object(color) : normalize_RGB_object(color),\n r = rgb.r / 255,\n g = rgb.g / 255,\n b = rgb.b / 255,\n a = rgb.a;\n\n const max = Math.max(r, g, b),\n min = Math.min(r, g, b),\n d = max - min,\n v = max,\n s = max === 0 ? 0 : d / max;\n\n let h = 0;\n\n if (max === min) {\n h = 0;\n } else {\n if (max === r) {\n h = (g - b) / d + (g < b ? 6 : 0);\n } else if (max === g) {\n h = (b - r) / d + 2;\n } else if (max === b) {\n h = (r - g) / d + 4;\n }\n\n h = h / 6;\n }\n\n return {\n h: clampHue(h * 360),\n s: clamp100(s * 100),\n v: clamp100(v * 100),\n a: clampAlpha(a),\n };\n };\n\n /** - Convert `RGB` or `RGBA` color to an `HWBA` object representation */\n const RGB_to_HWBA = (color: string | rgbaT | rgbT): hwbaT => {\n const rgb =\n typeof color === \"string\" ? RGB_string_to_object(color) : normalize_RGB_object(color),\n red = rgb.r / 255,\n green = rgb.g / 255,\n blue = rgb.b / 255,\n a = rgb.a;\n\n const { h } = RGB_to_HSLA(color);\n\n const white = Math.min(red, green, blue) * 100;\n const black = (1 - Math.max(red, green, blue)) * 100;\n\n return {\n h: clampHue(h),\n w: clamp100(white),\n b: clamp100(black),\n a: clampAlpha(a),\n };\n };\n\n /** - Return the `RGB` color as a string, an array, or an object */\n const RGB_types = ({ r, g, b, a }: rgbaT): ColorTypes => {\n return {\n string: (forceAlpha?: boolean) => {\n r = Math.round(r);\n g = Math.round(g);\n b = Math.round(b);\n\n // auto\n if (typeof forceAlpha === \"undefined\") {\n if (typeof a === \"number\" && a !== 1) return `rgba(${r}, ${g}, ${b}, ${a})`;\n return `rgb(${r}, ${g}, ${b})`;\n }\n\n if (forceAlpha) return `rgba(${r}, ${g}, ${b}, ${a ?? 1})`;\n\n return `rgb(${r}, ${g}, ${b})`;\n },\n array: (roundValues = true) => {\n if (roundValues) {\n r = Math.round(r);\n g = Math.round(g);\n b = Math.round(b);\n }\n return [r, g, b, a];\n },\n object: (roundValues = true) => {\n if (roundValues) {\n r = Math.round(r);\n g = Math.round(g);\n b = Math.round(b);\n }\n return { r, g, b, a };\n },\n };\n };\n\n // * MARK: HSL\n\n /** - Parse `HSL` or `HSLA` color string to an `object` */\n const HSL_string_to_object = (color: string): hslaT => {\n color = color.trim().toLowerCase();\n const colorType = detectColorFormat(color);\n\n if (!colorType?.includes(\"hsl\")) {\n console.error(\n '[colorKit.getHslObject] is unable to parse the string into an `HSL` object. As a result, the color \"black\" will be returned instead.',\n );\n return { h: 0, s: 0, l: 0, a: 1 };\n }\n\n let matches: RegExpMatchArray | null = null;\n const entry = COLORS_REGEX[colorType as \"hsl\" | \"hsla\"];\n if (Array.isArray(entry)) {\n for (let i = 0; i < entry.length; i++) {\n const regex = entry[i];\n if (regex?.test(color)) {\n matches = color.match(regex);\n }\n }\n } else {\n matches = color.match(entry);\n }\n\n if (!matches || matches.length < 3) {\n console.error(\n '[colorKit.getHslObject] An error occurred while attempting to destructuring `HSL` values from the given string. As a result, the color \"black\" will be returned instead.',\n );\n return { h: 0, s: 0, l: 0, a: 1 };\n }\n\n const h = parseInt(matches[1] || \"0\", 10),\n s = parseInt(matches[2] || \"0\", 10),\n l = parseInt(matches[3] || \"0\", 10),\n a = parseFloat(matches[4] ?? \"1\");\n\n return {\n h: clampHue(h),\n s: clamp100(s),\n l: clamp100(l),\n a: clampAlpha(a),\n };\n };\n\n /** - Ensure that the `HSL` object values are within the correct range and that it has the alpha channel */\n const normalize_HSL_object = (color: hslaT | hslT): hslaT => {\n return {\n h: clampHue(color.h),\n s: clamp100(color.s),\n l: clamp100(color.l),\n a: clampAlpha((color as hslaT).a ?? 1),\n };\n };\n\n /** - Convert `HSL` or `HSLA` color to an `RGBA` object representation */\n const HSL_to_RGBA = (color: string | hslaT | hslT): rgbaT => {\n const hsla =\n typeof color === \"string\" ? HSL_string_to_object(color) : normalize_HSL_object(color);\n\n const h = hsla.h / 360,\n s = hsla.s / 100,\n l = hsla.l / 100,\n a = hsla.a;\n\n const q = l < 0.5 ? l * (1 + s) : l + s - l * s,\n p = 2 * l - q;\n\n const r = calculateHueValue(p, q, h + 1 / 3),\n g = calculateHueValue(p, q, h),\n b = calculateHueValue(p, q, h - 1 / 3);\n\n return {\n r: clampRGB(r * 255),\n g: clampRGB(g * 255),\n b: clampRGB(b * 255),\n a: clampAlpha(a),\n };\n };\n\n /** - Convert `HSL` or `HSLA` color to `HEX` color */\n const HSL_to_HEX = (color: string | hslaT | hslT): string => {\n const hsla =\n typeof color === \"string\" ? HSL_string_to_object(color) : normalize_HSL_object(color);\n const rgb = HSL_to_RGBA(hsla);\n\n const r = numberToHexString(rgb.r),\n g = numberToHexString(rgb.g),\n b = numberToHexString(rgb.b),\n a = rgb.a === 1 ? \"\" : numberToHexString(rgb.a * 255);\n\n return `#${r + g + b + a}`;\n };\n\n /** - Convert `HSL` or `HSLA` color to an `HSVA` object representation */\n const HSL_to_HSVA = (color: string | hslaT | hslT): hsvaT => {\n const hsla =\n typeof color === \"string\" ? HSL_string_to_object(color) : normalize_HSL_object(color);\n const h = hsla.h;\n\n const s = hsla.s / 100,\n l = hsla.l / 100,\n a = (hsla as hslaT).a ?? 1,\n v = l + s * Math.min(l, 1 - l),\n sNew = v === 0 ? 0 : 2 - (2 * l) / v;\n\n return {\n h: clampHue(h),\n s: clamp100(sNew * 100),\n v: clamp100(v * 100),\n a: clampAlpha(a),\n };\n };\n\n /** - Convert `HSL` or `HSLA` color to an `HWBA` object representation */\n const HSL_to_HWBA = (color: string | hslaT | hslT): hwbaT => {\n const hsva = HSL_to_HSVA(color);\n return HSV_to_HWBA(hsva);\n };\n\n /** - Convert `HSL` or `HSLA` color to an `HSLA` object representation */\n const HSL_to_HSL = (color: string | hslaT | hslT): hslaT => {\n return typeof color === \"string\" ? HSL_string_to_object(color) : normalize_HSL_object(color);\n };\n\n /** - Return the `HSL` color as a string, an array, or an object */\n const HSL_types = ({ h, s, l, a }: hslaT): ColorTypes => {\n return {\n string: (forceAlpha?: boolean) => {\n h = Math.round(h);\n s = Math.round(s);\n l = Math.round(l);\n\n // auto\n if (typeof forceAlpha === \"undefined\") {\n if (typeof a === \"number\" && a !== 1) return `hsla(${h}, ${s}%, ${l}%, ${a})`;\n return `hsl(${h}, ${s}%, ${l}%)`;\n }\n\n if (forceAlpha) return `hsla(${h}, ${s}%, ${l}%, ${a ?? 1})`;\n\n return `hsl(${h}, ${s}%, ${l}%)`;\n },\n array: (roundValues = true) => {\n if (roundValues) {\n h = Math.round(h);\n s = Math.round(s);\n l = Math.round(l);\n }\n return [h, s, l, a];\n },\n object: (roundValues = true) => {\n if (roundValues) {\n h = Math.round(h);\n s = Math.round(s);\n l = Math.round(l);\n }\n return { h, s, l, a };\n },\n };\n };\n\n // * MARK: HWB\n\n /** - Parse `HWB` or `HWBA` color strong to an `object` */\n const HWB_string_to_object = (color: string): hwbaT => {\n color = color.trim().toLowerCase();\n const colorType = detectColorFormat(color);\n\n if (!colorType?.includes(\"hwb\")) {\n console.error(\n '[colorKit.getHwbObject] is unable to parse the string into an `HWB` object. As a result, the color \"black\" will be returned instead.',\n );\n return { h: 0, w: 0, b: 0, a: 1 };\n }\n\n let matches: RegExpMatchArray | null = null;\n const entry = COLORS_REGEX[colorType as \"hwb\" | \"hwba\"];\n if (Array.isArray(entry)) {\n for (let i = 0; i < entry.length; i++) {\n const regex = entry[i];\n if (regex?.test(color)) {\n matches = color.match(regex);\n }\n }\n } else {\n matches = color.match(entry);\n }\n\n if (!matches || matches.length < 4) {\n console.error(\n '[colorKit.getHwbObject] An error occurred while attempting to destructuring `HWB` values from the given string. As a result, the color \"black\" will be returned instead.',\n );\n return { h: 0, w: 0, b: 0, a: 1 };\n }\n\n const h = parseInt(matches[1] || \"0\", 10),\n w = parseInt(matches[2] || \"0\", 10),\n b = parseInt(matches[3] || \"0\", 10),\n a = parseFloat(matches[4] ?? \"1\");\n\n return {\n h: clampHue(h),\n w: clamp100(w),\n b: clamp100(b),\n a: clampAlpha(a),\n };\n };\n\n /** - Ensure that the `HWB` object values are within the correct range and that it has the alpha channel */\n const normalize_HWB_object = (color: hwbaT | hwbT): hwbaT => {\n return {\n h: clampHue(color.h),\n w: clamp100(color.w),\n b: clamp100(color.b),\n a: clampAlpha((color as hwbaT).a ?? 1),\n };\n };\n\n /** - Convert `HWB` or `HWBA` color to an `RGBA` object representation */\n const HWB_to_RGBA = (color: hwbaT | hwbT | string): rgbaT => {\n const hwba =\n typeof color === \"string\" ? HWB_string_to_object(color) : normalize_HWB_object(color);\n\n const h = hwba.h / 360,\n w = hwba.w / 100,\n b = hwba.b / 100,\n a = hwba.a;\n\n if (w + b >= 1) {\n const gray = clampRGB((w * 255) / (w + b));\n return {\n r: gray,\n g: gray,\n b: gray,\n a,\n };\n }\n\n const red = calculateHueValue(0, 1, h + 1 / 3) * (1 - w - b) + w,\n green = calculateHueValue(0, 1, h) * (1 - w - b) + w,\n blue = calculateHueValue(0, 1, h - 1 / 3) * (1 - w - b) + w;\n\n return {\n r: clampRGB(red * 255),\n g: clampRGB(green * 255),\n b: clampRGB(blue * 255),\n a: clampAlpha(a),\n };\n };\n\n /** - Convert `HWB` or `HWBA` color to an `Hex` color */\n const HWB_to_HEX = (color: hwbaT | hwbT | string): string => {\n const rgba = HWB_to_RGBA(color);\n return RGB_to_HEX(rgba);\n };\n\n /** - Convert `HWB` or `HWBA` color to an `HSVA` object representation */\n function HWB_to_HSVA(color: hwbaT | hwbT | string): hsvaT {\n const hwba =\n typeof color === \"string\" ? HWB_string_to_object(color) : normalize_HWB_object(color);\n\n const h = hwba.h % 360,\n w = hwba.w / 100,\n b = hwba.b / 100,\n a = hwba.a;\n\n const v = (1 - b) * 100;\n let s = (1 - w / (v / 100)) * 100;\n s = Number.isNaN(s) ? 0 : s;\n\n return {\n h: clampHue(h),\n s: clamp100(s),\n v: clamp100(v),\n a: clampAlpha(a),\n };\n }\n\n /** - Convert `HWB` or `HWBA` color to an `HSLA` object representation */\n const HWB_to_HSLA = (color: hwbaT | hwbT | string): hslaT => {\n const hsva = HWB_to_HSVA(color);\n return HSV_to_HSLA(hsva);\n };\n\n /** - Convert `HWB` or `HWBA` color to an `HWBA` object representation */\n const HWB_to_HWB = (color: hwbaT | hwbT | string): hwbaT => {\n return typeof color === \"string\" ? HWB_string_to_object(color) : normalize_HWB_object(color);\n };\n\n /** - Return the `HWB` color as a string, an array, or an object */\n const HWB_types = ({ h, w, b, a }: hwbaT): ColorTypes => {\n return {\n string: (forceAlpha?: boolean) => {\n h = Math.round(h);\n w = Math.round(w);\n b = Math.round(b);\n\n // auto\n if (typeof forceAlpha === \"undefined\") {\n if (typeof a === \"number\" && a !== 1) return `hwb(${h} ${w}% ${b}% / ${a})`;\n return `hwb(${h}, ${w}%, ${b}%)`;\n }\n\n if (forceAlpha) return `hwb(${h} ${w}% ${b}% / ${a ?? 1})`;\n\n return `hwb(${h} ${w}% ${b}%)`;\n },\n array: (roundValues = true) => {\n if (roundValues) {\n h = Math.round(h);\n w = Math.round(w);\n b = Math.round(b);\n }\n return [h, w, b, a];\n },\n object: (roundValues = true) => {\n if (roundValues) {\n h = Math.round(h);\n w = Math.round(w);\n b = Math.round(b);\n }\n return { h, w, b, a };\n },\n };\n };\n\n // * MARK: HSV\n\n /** - Parse `HSV` or `HSVA` color string to an `object` */\n const HSV_string_to_object = (color: string): hsvaT => {\n color = color.trim().toLowerCase();\n const colorType = detectColorFormat(color);\n\n if (!colorType?.includes(\"hsv\")) {\n console.error(\n '[colorKit.getHsvObject] is unable to parse the string into an `HSV` object. As a result, the color \"black\" will be returned instead.',\n );\n return { h: 0, s: 0, v: 0, a: 1 };\n }\n\n let matches: RegExpMatchArray | null = null;\n const entry = COLORS_REGEX[colorType as \"hsv\" | \"hsva\"];\n if (Array.isArray(entry)) {\n for (let i = 0; i < entry.length; i++) {\n const regex = entry[i];\n if (regex?.test(color)) {\n matches = color.match(regex);\n }\n }\n } else {\n matches = color.match(entry);\n }\n\n if (!matches || matches.length < 4) {\n console.error(\n '[colorKit.getHsvObject] An error occurred while attempting to destructuring `HSV` values from the given string. As a result, the color \"black\" will be returned instead.',\n );\n return { h: 0, s: 0, v: 0, a: 1 };\n }\n\n const h = parseInt(matches[1] || \"0\", 10),\n s = parseInt(matches[2] || \"0\", 10),\n v = parseInt(matches[3] || \"0\", 10),\n a = parseFloat(matches[4] ?? \"1\");\n\n return {\n h: clampHue(h),\n s: clamp100(s),\n v: clamp100(v),\n a: clampAlpha(a),\n };\n };\n\n /** - Ensure that the `HSV` object values are within the correct range and that it has the alpha channel */\n const normalize_HSV_object = (color: hsvaT | hsvT): hsvaT => {\n return {\n h: clampHue(color.h),\n s: clamp100(color.s),\n v: clamp100(color.v),\n a: clampAlpha((color as hsvaT).a ?? 1),\n };\n };\n\n /** - Convert `HSV` color to an `RGBA` object representation */\n const HSV_to_RGBA = (color: hsvaT | hsvT | string): rgbaT => {\n const hsva =\n typeof color === \"string\" ? HSV_string_to_object(color) : normalize_HSV_object(color);\n\n const h = hsva.h / 360,\n s = hsva.s / 100,\n v = hsva.v / 100,\n a = hsva.a;\n\n const i = Math.floor(h * 6),\n f = h * 6 - i,\n p = v * (1 - s),\n q = v * (1 - f * s),\n t = v * (1 - (1 - f) * s);\n\n let r = 0,\n g = 0,\n b = 0;\n\n if (i % 6 === 0) {\n r = v;\n g = t;\n b = p;\n } else if (i % 6 === 1) {\n r = q;\n g = v;\n b = p;\n } else if (i % 6 === 2) {\n r = p;\n g = v;\n b = t;\n } else if (i % 6 === 3) {\n r = p;\n g = q;\n b = v;\n } else if (i % 6 === 4) {\n r = t;\n g = p;\n b = v;\n } else if (i % 6 === 5) {\n r = v;\n g = p;\n b = q;\n }\n\n return {\n r: clampRGB(r * 255),\n g: clampRGB(g * 255),\n b: clampRGB(b * 255),\n a: clampAlpha(a),\n };\n };\n\n /** - Convert `HSV` color to an `HSLA` object representation */\n const HSV_to_HSLA = (color: hsvaT | hsvT | string): hslaT => {\n const hsva =\n typeof color === \"string\" ? HSV_string_to_object(color) : normalize_HSV_object(color);\n\n const h = hsva.h,\n s = hsva.s / 100,\n v = hsva.v / 100,\n a = hsva.a;\n\n const l = ((2 - s) * v) / 2,\n sl = s * v,\n sln = l !== 0 && l !== 1 ? sl / (l < 0.5 ? l * 2 : 2 - l * 2) : sl;\n\n return {\n h: clampHue(h),\n s: clamp100(sln * 100),\n l: clamp100(l * 100),\n a: clampAlpha(a),\n };\n };\n\n /** - Convert `HSV` color to an `Hex` color */\n const HSV_to_HEX = (color: hsvaT | hsvT | string): string => {\n const rgba = HSV_to_RGBA(color);\n const hex = RGB_to_HEX(rgba);\n return hex;\n };\n\n /** - Convert `HSV` color to an `HWBA` object representation */\n const HSV_to_HWBA = (color: hsvaT | hsvT | string): hwbaT => {\n const { h, s, v, a } =\n typeof color === \"string\" ? HSV_string_to_object(color) : normalize_HSV_object(color);\n\n const w = (1 - s / 100) * v,\n b = (1 - v / 100) * 100;\n\n return {\n h: clampHue(h),\n w: clamp100(w),\n b: clamp100(b),\n a: clampAlpha(a),\n };\n };\n\n /** - Convert `HSV` color to an `HSVA` object representation */\n const HSV_to_HSV = (color: hsvaT | hsvT | string): hsvaT => {\n return typeof color === \"string\" ? HSV_string_to_object(color) : normalize_HSV_object(color);\n };\n\n /** - Return the `HSV` color as a string, an array, or an object */\n const HSV_types = ({ h, s, v, a }: hsvaT): ColorTypes => {\n return {\n string: (forceAlpha?: boolean) => {\n h = Math.round(h);\n s = Math.round(s);\n v = Math.round(v);\n\n // auto\n if (typeof forceAlpha === \"undefined\") {\n if (typeof a === \"number\" && a !== 1) return `hsva(${h}, ${s}%, ${v}%, ${a})`;\n return `hsv(${h}, ${s}%, ${v}%)`;\n }\n\n if (forceAlpha) return `hsva(${h}, ${s}%, ${v}%, ${a ?? 1})`;\n\n return `hsv(${h}, ${s}%, ${v}%)`;\n },\n array: (roundValues = true) => {\n if (roundValues) {\n h = Math.round(h);\n s = Math.round(s);\n v = Math.round(v);\n }\n return [h, s, v, a];\n },\n object: (roundValues = true) => {\n if (roundValues) {\n h = Math.round(h);\n s = Math.round(s);\n v = Math.round(v);\n }\n return { h, s, v, a };\n },\n };\n };\n\n // * MARK: HEX\n\n /** - Convert any `HEX` color to 8-digit `HEX` color (#rrggbbaa) */\n const normalize_HEX = (color: string | number): string => {\n if (typeof color === \"number\") {\n return `#${color.toString(16).padStart(8, \"0\")}`;\n }\n\n color = color.trim().toLowerCase();\n const colorType = detectColorFormat(color);\n\n if (!colorType?.includes(\"hex\")) {\n console.error(\n '[colorKit.normalizeHexColor] is unable to normalize the `HEX` string provided. As a result, the color \"black\" will be returned instead.',\n );\n return \"#000000ff\";\n }\n\n const hex = color.replace(/^#/, \"\").split(\"\");\n\n if (hex.length === 3) return `#${hex.map((x) => x + x).join(\"\")}ff`;\n if (hex.length === 4) return `#${hex.map((x) => x + x).join(\"\")}`;\n if (hex.length === 6) return `#${hex.join(\"\")}ff`;\n\n return color;\n };\n\n /** - Convert any `HEX` color to an `RGBA` object representation */\n const HEX_to_RGBA = (color: string | number): rgbaT => {\n const hex = normalize_HEX(color);\n\n let matches: RegExpMatchArray | null = null;\n const entry = COLORS_REGEX.hex8;\n if (Array.isArray(entry)) {\n for (let i = 0; i < entry.length; i++) {\n const regex = entry[i];\n if (regex?.test(hex)) matches = hex.match(regex);\n }\n } else {\n matches = hex.match(entry);\n }\n\n if (!matches || matches.length < 4) {\n console.error(\n '[colorKit.HEX_RGBA] An error occurred while attempting to destructuring `HEX` values from the given string. As a result, the color \"black\" will be returned instead.',\n );\n return { r: 0, g: 0, b: 0, a: 1 };\n }\n\n const r = parseInt(matches[1] || \"0\", 16),\n g = parseInt(matches[2] || \"0\", 16),\n b = parseInt(matches[3] || \"0\", 16),\n a = parseInt(matches[4] || \"ff\", 16) / 255;\n\n return {\n r: clampRGB(r),\n g: clampRGB(g),\n b: clampRGB(b),\n a: clampAlpha(a),\n };\n };\n\n /** - Convert any `HEX` color to an `HSVA` object representation */\n const HEX_to_HSVA = (color: string | number): hsvaT => {\n const rgb = HEX_to_RGBA(color);\n const hsva = RGB_to_HSVA(rgb);\n return hsva;\n };\n\n /** - Convert any `HEX` color to an `HSLA` object representation */\n const HEX_to_HSLA = (color: string): hslaT => {\n const rgb = HEX_to_RGBA(color);\n return RGB_to_HSLA(rgb);\n };\n\n /** - Convert any `HEX` color to an `HWBA` object representation */\n const HEX_to_HWBA = (color: string): hwbaT => {\n const rgba = HEX_to_RGBA(color);\n return RGB_to_HWBA(rgba);\n };\n\n // * MARK: Color conversions\n\n /** - Convert `HSL`, `HSV`, `HWB`, or `RGB` color to the `HEX` color format. */\n const HEX = (color: SupportedColorFormats): string => {\n // named color\n if (typeof color === \"string\") {\n color = color.trim().toLowerCase();\n\n if (Object.hasOwn(NAMED_COLORS, color)) {\n color = NAMED_COLORS[color as keyof typeof NAMED_COLORS] as string;\n }\n }\n\n const colorType = detectColorFormat(color);\n\n // RGB to HEX\n if (colorType === \"rgb\" || colorType === \"rgba\") {\n return RGB_to_HEX(color as string | rgbT | rgbaT);\n }\n\n // HSL to HEX\n if (colorType === \"hsl\" || colorType === \"hsla\") {\n return HSL_to_HEX(color as string | hslaT | hslT);\n }\n\n // HSV to HEX\n if (colorType === \"hsv\" || colorType === \"hsva\") {\n return HSV_to_HEX(color as string | hsvaT | hsvT);\n }\n\n // HWB to HEX\n if (colorType === \"hwb\" || colorType === \"hwba\") {\n return HWB_to_HEX(color as string | hwbaT | hwbT);\n }\n\n // HEX\n if (colorType?.includes(\"hex\")) {\n return normalize_HEX(color as string | number);\n }\n\n // ! error\n console.error(\n '[colorKit.HEX] An error occurred while attempting to convert the provided parameter into an `HEX` color. As a result, the default color \"black\" will be used instead.',\n );\n\n return \"#000000\";\n };\n\n /** - Convert `HSL`, `HSV`, `HWB`, or `HEX` color to the `RGB` color format. */\n const RGB = (color: SupportedColorFormats): ColorTypes => {\n // named color\n if (typeof color === \"string\") {\n color = color.trim().toLowerCase();\n\n if (Object.hasOwn(NAMED_COLORS, color)) {\n color = NAMED_COLORS[color as keyof typeof NAMED_COLORS] as string;\n }\n }\n\n const colorType = detectColorFormat(color);\n\n // HEX to RGB\n if (colorType?.includes(\"hex\")) {\n const rgb = HEX_to_RGBA(color as string | number);\n return RGB_types(rgb);\n }\n\n // HSL to RGB\n if (colorType === \"hsl\" || colorType === \"hsla\") {\n const rgb = HSL_to_RGBA(color as string | hslaT | hslT);\n return RGB_types(rgb);\n }\n\n // HSV to RGB\n if (colorType === \"hsv\" || colorType === \"hsva\") {\n const rgb = HSV_to_RGBA(color as string | hsvaT | hsvT);\n return RGB_types(rgb);\n }\n\n // HWB to RGB\n if (colorType === \"hwb\" || colorType === \"hwba\") {\n const rgb = HWB_to_RGBA(color as string | hwbaT | hwbT);\n return RGB_types(rgb);\n }\n\n // RGB to normalized RGB\n if (colorType === \"rgb\" || colorType === \"rgba\") {\n const rgba = RGB_to_RGB(color as string | rgbaT | rgbT);\n return RGB_types(rgba);\n }\n\n // ! error\n console.error(\n '[colorKit.RGB] An error occurred while attempting to convert the provided parameter into an `RGB` color. As a result, the default color \"black\" will be used instead.',\n );\n\n return RGB_types({ r: 0, g: 0, b: 0, a: 1 });\n };\n\n /** - Convert `HEX`, `HSV`, `HWB`, or `RGB` color to the `HSL` color format. */\n const HSL = (color: SupportedColorFormats): ColorTypes => {\n // named color\n if (typeof color === \"string\") {\n color = color.trim().toLowerCase();\n\n if (Object.hasOwn(NAMED_COLORS, color)) {\n color = NAMED_COLORS[color as keyof typeof NAMED_COLORS] as string;\n }\n }\n\n const colorType = detectColorFormat(color);\n\n // HEX to HSL\n if (colorType?.includes(\"hex\")) {\n const hsla = HEX_to_HSLA(color as string);\n return HSL_types(hsla);\n }\n\n // RGB to HSL\n if (colorType === \"rgb\" || colorType === \"rgba\") {\n const hsla = RGB_to_HSLA(color as string | rgbaT | rgbT);\n return HSL_types(hsla);\n }\n\n // HSV to HSL\n if (colorType === \"hsv\" || colorType === \"hsva\") {\n const hsla = HSV_to_HSLA(color as string | hsvaT | hsvT);\n return HSL_types(hsla);\n }\n\n // HWB to HSL\n if (colorType === \"hwb\" || colorType === \"hwba\") {\n const hsla = HWB_to_HSLA(color as string | hwbaT | hwbT);\n return HSL_types(hsla);\n }\n\n // HSL to normalized HSL\n if (colorType === \"hsl\" || colorType === \"hsla\") {\n const hsla = HSL_to_HSL(color as string | hslaT | hslT);\n return HSL_types(hsla);\n }\n\n // ! error\n console.error(\n '[colorKit.HSL] An error occurred while attempting to convert the provided parameter into an `HSL` color. As a result, the default color \"black\" will be used instead.',\n );\n\n return HSL_types({ h: 0, s: 0, l: 0, a: 1 });\n };\n\n /** - Convert `HSL`, `HEX`, `HSV`, or `RGB` color to the `HWB` color format. */\n const HWB = (color: SupportedColorFormats): ColorTypes => {\n // named color\n if (typeof color === \"string\") {\n color = color.trim().toLowerCase();\n\n if (Object.hasOwn(NAMED_COLORS, color)) {\n color = NAMED_COLORS[color as keyof typeof NAMED_COLORS] as string;\n }\n }\n\n const colorType = detectColorFormat(color);\n\n // HEX to HWB\n if (colorType?.includes(\"hex\")) {\n const hwba = HEX_to_HWBA(color as string);\n return HWB_types(hwba);\n }\n\n // RGB to HWB\n if (colorType === \"rgb\" || colorType === \"rgba\") {\n const hwba = RGB_to_HWBA(color as string | rgbaT | rgbT);\n return HWB_types(hwba);\n }\n\n // HSL to HWB\n if (colorType === \"hsl\" || colorType === \"hsla\") {\n const hwba = HSL_to_HWBA(color as string | hslaT | hslT);\n return HWB_types(hwba);\n }\n\n // HSV to HWB\n if (colorType === \"hsv\" || colorType === \"hsva\") {\n const hwba = HSV_to_HWBA(color as string | hsvaT | hsvT);\n return HWB_types(hwba);\n }\n\n // HWB to normalized HWB\n if (colorType === \"hwb\" || colorType === \"hwba\") {\n const hwba = HWB_to_HWB(color as string | hwbaT | hwbT);\n return HWB_types(hwba);\n }\n\n // ! error\n console.error(\n '[colorKit.HWB] An error occurred while attempting to convert the provided parameter into an `HWB` color. As a result, the default color \"black\" will be used instead.',\n );\n\n return HWB_types({ h: 0, w: 0, b: 100, a: 1 });\n };\n\n /** - Convert `HSL`, `HEX`, `HWB`, or `RGB` color to the `HSV` color format. */\n const HSV = (color: SupportedColorFormats): ColorTypes => {\n // named color\n if (typeof color === \"string\") {\n color = color.trim().toLowerCase();\n\n if (Object.hasOwn(NAMED_COLORS, color)) {\n color = NAMED_COLORS[color as keyof typeof NAMED_COLORS] as string;\n }\n }\n\n const colorType = detectColorFormat(color);\n\n // HEX to HSV\n if (colorType?.includes(\"hex\")) {\n const hsva = HEX_to_HSVA(color as string);\n return HSV_types(hsva);\n }\n\n // RGB to HSV\n if (colorType === \"rgb\" || colorType === \"rgba\") {\n const hsva = RGB_to_HSVA(color as string | rgbaT | rgbT);\n return HSV_types(hsva);\n }\n\n // HSL to HSV\n if (colorType === \"hsl\" || colorType === \"hsla\") {\n const hsva = HSL_to_HSVA(color as string | hslaT | hslT);\n return HSV_types(hsva);\n }\n\n // HWB to HSV\n if (colorType === \"hwb\" || colorType === \"hwba\") {\n const hsva = HWB_to_HSVA(color as string | hwbaT | hwbT);\n return HSV_types(hsva);\n }\n\n // HSV to normalized HSV\n if (colorType === \"hsv\" || colorType === \"hsva\") {\n const hsva = HSV_to_HSV(color as string | hsvaT | hsvT);\n return HSV_types(hsva);\n }\n\n // ! error\n console.error(\n '[colorKit.HSV] An error occurred while attempting to convert the provided parameter into an `HSV` color. As a result, the default color \"black\" will be used instead.',\n );\n\n return HSV_types({ h: 0, s: 0, v: 0, a: 1 });\n };\n\n // * MARK: Color Information\n\n /** - Identify the color format of a given `string` or `object`, and return `null` for invalid colors. */\n const getFormat = (color: SupportedColorFormats): ColorFormats | \"named\" | null => {\n // color int\n if (typeof color === \"number\") {\n // eslint-disable-next-line no-bitwise\n if (color >>> 0 === color && color >= 0 && color <= 0xffffffff) return \"hex8\";\n return null;\n }\n\n // color string\n if (typeof color === \"string\") {\n color = color.trim().toLowerCase();\n if (Object.hasOwn(NAMED_COLORS, color)) return \"named\";\n\n for (const key in COLORS_REGEX) {\n const format = key as ColorFormats;\n const entry = COLORS_REGEX[format];\n if (Array.isArray(entry)) {\n for (let i = 0; i < entry.length; i++) {\n const regex = entry[i];\n if (regex?.test(color)) return format;\n }\n continue;\n }\n if (entry.test(color)) return format;\n }\n }\n\n // color object\n if (typeof color === \"object\") {\n const rgbaKeys = [\"r\", \"g\", \"b\", \"a\"] as (keyof rgbaT)[];\n const isRgbaOb = rgbaKeys.every(\n (k) => Object.hasOwn(color, k) && typeof (color as rgbaT)[k] === \"number\",\n );\n if (isRgbaOb) return \"rgba\";\n\n const rgbKeys = [\"r\", \"g\", \"b\"] as (keyof rgbT)[];\n const isRgbOb = rgbKeys.every(\n (k) => Object.hasOwn(color, k) && typeof (color as rgbT)[k] === \"number\",\n );\n if (isRgbOb) return \"rgb\";\n\n const hslaKeys = [\"h\", \"s\", \"l\", \"a\"] as (keyof hslaT)[];\n const isHslaOb = hslaKeys.every(\n (k) => Object.hasOwn(color, k) && typeof (color as hslaT)[k] === \"number\",\n );\n if (isHslaOb) return \"hsla\";\n\n const hslKeys = [\"h\", \"s\", \"l\"] as (keyof hslT)[];\n const isHslOb = hslKeys.every(\n (k) => Object.hasOwn(color, k) && typeof (color as hslT)[k] === \"number\",\n );\n if (isHslOb) return \"hsl\";\n\n const hsvaKeys = [\"h\", \"s\", \"v\", \"a\"] as (keyof hsvaT)[];\n const isHsvaOb = hsvaKeys.every(\n (k) => Object.hasOwn(color, k) && typeof (color as hsvaT)[k] === \"number\",\n );\n if (isHsvaOb) return \"hsva\";\n\n const hsvKeys = [\"h\", \"s\", \"v\"] as (keyof hsvT)[];\n const isHsvOb = hsvKeys.every(\n (k) => Object.hasOwn(color, k) && typeof (color as hsvT)[k] === \"number\",\n );\n if (isHsvOb) return \"hsv\";\n\n const hwbaKeys = [\"h\", \"w\", \"b\", \"a\"] as (keyof hwbaT)[];\n const isHwbaOb = hwbaKeys.every(\n (k) => Object.hasOwn(color, k) && typeof (color as hwbaT)[k] === \"number\",\n );\n if (isHwbaOb) return \"hwba\";\n\n const hwbKeys = [\"h\", \"w\", \"b\"] as (keyof hwbT)[];\n const isHwbOb = hwbKeys.every(\n (k) => Object.hasOwn(color, k) && typeof (color as hwbT)[k] === \"number\",\n );\n if (isHwbOb) return \"hwb\";\n }\n\n return null;\n };\n\n /** - Get the `red` channel value of a given color. */\n const getRed = (color: SupportedColorFormats): number => {\n const { r } = RGB(color).object();\n return r;\n };\n\n /** - Get the `green` channel value of a given color. */\n const getGreen = (color: SupportedColorFormats): number => {\n const { g } = RGB(color).object();\n return g;\n };\n\n /** - Get the `blue` channel value of a given color. */\n const getBlue = (color: SupportedColorFormats): number => {\n const { b } = RGB(color).object();\n return b;\n };\n\n /** - Get the `hue` channel value of a given color. */\n const getHue = (color: SupportedColorFormats): number => {\n const { h } = HSL(color).object();\n return h;\n };\n\n /** - Get the `saturation` value of a given color. */\n const getSaturation = (color: SupportedColorFormats): number => {\n const { s } = HSL(color).object();\n return s;\n };\n\n /**\n * - Get color's HSL `luminosity` channel value.\n * - If you want the overall `luminosity` of a color use `getLuminanceWCAG` method.\n */\n const getLuminance = (color: SupportedColorFormats): number => {\n const { l } = HSL(color).object();\n return l;\n };\n\n /** - Get the HSV's `value` (brightness) channel value of a given color. */\n const getBrightness = (color: SupportedColorFormats): number => {\n const { v } = HSV(color).object();\n return v;\n };\n\n /** - Returns the perceived `luminance` of a color, from `0-1` as defined by Web Content Accessibility Guidelines (Version 2.0). */\n const getLuminanceWCAG = (color: SupportedColorFormats): number => {\n const { r, g, b } = RGB(color).object(false);\n const a = [r, g, b].map((v) =>\n v / 255 <= 0.03928 ? v / 255 / 12.92 : ((v / 255 + 0.055) / 1.055) ** 2.4,\n );\n return (a[0] || 0) * 0.2126 + (a[1] || 0) * 0.7152 + (a[2] || 0) * 0.0722;\n };\n\n /** - Returns a boolean indicating whether the color is considered \"dark\" or not */\n const isDark = (color: SupportedColorFormats): boolean => {\n const luminance = getLuminanceWCAG(color);\n return luminance < 0.5;\n };\n\n /** - Returns a boolean indicating whether the color is considered \"light\" or not */\n const isLight = (color: SupportedColorFormats): boolean => {\n const luminance = getLuminanceWCAG(color);\n return luminance >= 0.5;\n };\n\n /**\n * - Check if two colors are similar within a specified tolerance.\n *\n * @example\n * const tolerance = 0;\n * const isEqual = colorKit.areColorsEqual('#f00', 'red', tolerance); // true\n */\n const areColorsEqual = (\n color1: SupportedColorFormats,\n color2: SupportedColorFormats,\n tolerance = 0,\n ): boolean => {\n const rgb1 = RGB(color1).object();\n const rgb2 = RGB(color2).object();\n\n const deltaR = rgb1.r - rgb2.r;\n const deltaG = rgb1.g - rgb2.g;\n const deltaB = rgb1.b - rgb2.b;\n const difference = Math.sqrt(deltaR * deltaR + deltaG * deltaG + deltaB * deltaB);\n\n return difference <= tolerance;\n };\n\n /** - Calculates the contrast ratio between two colors, useful for ensuring accessibility and readability. */\n const contrastRatio = (color1: SupportedColorFormats, color2: SupportedColorFormats): number => {\n const luminance1 = getLuminanceWCAG(color1);\n const luminance2 = getLuminanceWCAG(color2);\n const contrast =\n (Math.max(luminance1, luminance2) + 0.05) / (Math.min(luminance1, luminance2) + 0.05);\n return Math.round(contrast * 100) / 100;\n };\n\n // * MARK: Color Manipulation\n\n const returnColorObject = (color: SupportedColorFormats) => {\n return {\n hex() {\n return HEX(color);\n },\n rgb() {\n return RGB(color);\n },\n hsl() {\n return HSL(color);\n },\n hsv() {\n return HSV(color);\n },\n hwb() {\n return HWB(color);\n },\n };\n };\n\n // * MARK: Red Manuipulation\n /** Set the `red` value of a color to a specific amount. */\n const setRed = (color: SupportedColorFormats, amount: number): ConversionMethods => {\n const { g, b, a } = RGB(color).object();\n const newR = clampRGB(amount);\n const newColor = { r: newR, g, b, a };\n\n return returnColorObject(newColor);\n };\n\n /**\n * Increase the `red` value of a color by the given percentage/amount.\n *\n * @example\n * increaseRed('rgb(100, 100, 100)', 20).hex();\n * increaseRed('rgb(100, 100, 100)', '20%').rgb().string();\n */\n const increaseRed = (\n color: SupportedColorFormats,\n amount: number | string,\n ): ConversionMethods => {\n const { r, g, b, a } = RGB(color).object();\n const red = typeof amount === \"string\" ? r + r * (parseFloat(amount) / 100) : r + amount;\n const newR = clampRGB(red);\n const newColor = { r: newR, g, b, a };\n\n return returnColorObject(newColor);\n };\n\n /**\n * Decrease the `red` value of a color by the given percentage/amount\n *\n * @example\n * decreaseRed('rgb(100, 100, 100)', 20).hex();\n * decreaseRed('rgb(100, 100, 100)', '20%').rgb().string();\n */\n const decreaseRed = (\n color: SupportedColorFormats,\n amount: number | string,\n ): ConversionMethods => {\n const { r, g, b, a } = RGB(color).object();\n const red = typeof amount === \"string\" ? r - r * (parseFloat(amount) / 100) : r - amount;\n const newR = clampRGB(red);\n const newColor = { r: newR, g, b, a };\n\n return returnColorObject(newColor);\n };\n\n // * MARK: Green Manuipulation\n /** - Set the `green` value of a color to a specific amount. */\n const setGreen = (color: SupportedColorFormats, amount: number): ConversionMethods => {\n const { r, b, a } = RGB(color).object();\n const newG = clampRGB(amount);\n const newColor = { r, g: newG, b, a };\n\n return returnColorObject(newColor);\n };\n\n /**\n * Increase the `green` value of a color by the given percentage.\n *\n * @example\n * increaseGreen('rgb(100, 100, 100)', 20).hex();\n * increaseGreen('rgb(100, 100, 100)', '20%').rgb().string();\n */\n const increaseGreen = (\n color: SupportedColorFormats,\n amount: number | string,\n ): ConversionMethods => {\n const { r, g, b, a } = RGB(color).object();\n const green = typeof amount === \"string\" ? g + g * (parseFloat(amount) / 100) : g + amount;\n const newG = clampRGB(green);\n const newColor = { r, g: newG, b, a };\n\n return returnColorObject(newColor);\n };\n\n /**\n * Decrease the `green` value of a color by the given percentage.\n *\n * @example\n * decreaseGreen('rgb(100, 100, 100)', 20).hex();\n * decreaseGreen('rgb(100, 100, 100)', '20%').rgb().string();\n */\n const decreaseGreen = (\n color: SupportedColorFormats,\n amount: number | string,\n ): ConversionMethods => {\n const { r, g, b, a } = RGB(color).object();\n const green = typeof amount === \"string\" ? g - g * (parseFloat(amount) / 100) : g - amount;\n const newG = clampRGB(green);\n const newColor = { r, g: newG, b, a };\n\n return returnColorObject(newColor);\n };\n\n // * MARK: Blue Manuipulation\n /** - Set the `blue` value of a color to a specific amount. */\n const setBlue = (color: SupportedColorFormats, amount: number): ConversionMethods => {\n const { r, g, a } = RGB(color).object();\n const newB = clampRGB(amount);\n const newColor = { r, g, b: newB, a };\n\n return returnColorObject(newColor);\n };\n\n /**\n * Increase the `blue` value of a color by the given percentage.\n *\n * @example\n * increaseBlue('rgb(100, 100, 100)', 20).hex();\n * increaseBlue('rgb(100, 100, 100)', '20%').rgb().string();\n */\n const increaseBlue = (\n color: SupportedColorFormats,\n amount: number | string,\n ): ConversionMethods => {\n const { r, g, b, a } = RGB(color).object();\n const blue = typeof amount === \"string\" ? b + b * (parseFloat(amount) / 100) : b + amount;\n const newB = clampRGB(blue);\n const newColor = { r, g, b: newB, a };\n\n return returnColorObject(newColor);\n };\n\n /**\n * Decrease the `blue` value of a color by the given percentage.\n *\n * @example\n * decreaseBlue('rgb(100, 100, 100)', 20).hex();\n * decreaseBlue('rgb(100, 100, 100)', '20%').rgb().string();\n */\n const decreaseBlue = (\n color: SupportedColorFormats,\n amount: number | string,\n ): ConversionMethods => {\n const { r, g, b, a } = RGB(color).object();\n const blue = typeof amount === \"string\" ? b - b * (parseFloat(amount) / 100) : b - amount;\n const newB = clampRGB(blue);\n const newColor = { r, g, b: newB, a };\n\n return returnColorObject(newColor);\n };\n\n //* MARK: Alpha Manuipulation\n /** - Get the `alpha` value of a given color. */\n const getAlpha = (color: SupportedColorFormats): number => {\n const { a } = RGB(color).object();\n return a;\n };\n\n /** - Set the `alpha` value of a color to a specific amount. */\n const setAlpha = (color: SupportedColorFormats, amount: number): ConversionMethods => {\n const { r, g, b } = RGB(color).object();\n const newA = clampAlpha(amount);\n const newColor = { r, g, b, a: newA };\n\n return returnColorObject(newColor);\n };\n\n /** Increase the `alpha` value of a color by the given percentage. */\n const increaseAlpha = (\n color: SupportedColorFormats,\n amount: number | string,\n ): ConversionMethods => {\n const { r, g, b, a } = RGB(color).object();\n const alpha = typeof amount === \"string\" ? a + a * (parseFloat(amount) / 100) : a + amount;\n const newA = clampAlpha(alpha);\n const newColor = { r, g, b, a: newA };\n\n return returnColorObject(newColor);\n };\n\n /** Decrease the `alpha` value of a color by the given percentage. */\n const decreaseAlpha = (\n color: SupportedColorFormats,\n amount: number | string,\n ): ConversionMethods => {\n const { r, g, b, a } = RGB(color).object();\n const alpha = typeof amount === \"string\" ? a - a * (parseFloat(amount) / 100) : a - amount;\n const newA = clampAlpha(alpha);\n const newColor = { r, g, b, a: newA };\n\n return returnColorObject(newColor);\n };\n\n // * MARK: Hue Manuipulation\n /** - Set the `hue` value of a color to a specific amount. */\n const setHue = (color: SupportedColorFormats, amount: number): ConversionMethods => {\n const { s, l, a } = HSL(color).object();\n const newH = clampHue(amount);\n const newColor = { h: newH, s, l, a };\n\n return returnColorObject(newColor);\n };\n\n /**\n * Increase the `hue` value of a color by the given percentage/amount.\n *\n * @example\n * increaseHue('rgb(100, 100, 100)', 20).hex();\n * increaseHue('rgb(100, 100, 100)', '20%').rgb().string();\n */\n const increaseHue = (\n color: SupportedColorFormats,\n amount: number | string,\n ): ConversionMethods => {\n const { h, s, l, a } = HSL(color).object();\n const hue = typeof amount === \"string\" ? h + h * (parseFloat(amount) / 100) : h + amount;\n const newH = clampHue(hue);\n const newColor = { h: newH, s, l, a };\n\n return returnColorObject(newColor);\n };\n\n /**\n * Decrease the `hue` value of a color by the given percentage/amount.\n *\n * @example\n * decreaseHue('rgb(100, 100, 100)', 20).hex();\n * decreaseHue('rgb(100, 100, 100)', '20%').rgb().string();\n */\n const decreaseHue = (\n color: SupportedColorFormats,\n amount: number | string,\n ): ConversionMethods => {\n const { h, s, l, a } = HSL(color).object();\n const hue = typeof amount === \"string\" ? h - h * (parseFloat(amount) / 100) : h - amount;\n const newH = clampHue(hue);\n const newColor = { h: newH, s, l, a };\n\n return returnColorObject(newColor);\n };\n\n /**\n * - Spin the `hue` channel by a certain percentage/amount.\n *\n * @example\n * spin('red', 20).hex();\n * spin('rgb(255, 0, 0)', '20%').rgb().string();\n */\n const spin = (color: SupportedColorFormats, degree: number | string): ConversionMethods => {\n const { h, s, l, a } = HSL(color).object();\n const spinDegree = typeof degree === \"string\" ? s * (parseFloat(degree) / 100) : degree;\n const newColor = { h: Math.round((h + spinDegree) % 360), s, l, a };\n\n return returnColorObject(newColor);\n };\n\n // * MARK: Saturation Manuipulation\n /** - Set the `saturation` value of a color to a specific amount. */\n const setSaturation = (color: SupportedColorFormats, amount: number): ConversionMethods => {\n const { h, l, a } = HSL(color).object();\n const newS = clamp100(amount);\n const saturatedColor = { h, s: newS, l, a };\n\n return returnColorObject(saturatedColor);\n };\n\n /**\n * - Increase the saturation of the given color by a certain percentage/amount.\n *\n * @example\n * saturate('red', 20).hex();\n * saturate('rgb(255, 0, 0)', '20%').rgb().string();\n */\n const saturate = (color: SupportedColorFormats, amount: number | string): ConversionMethods => {\n const { h, s, l, a } = HSL(color).object();\n const saturation = typeof amount === \"string\" ? s + s * (parseFloat(amount) / 100) : s + amount;\n const newS = clamp100(saturation);\n const saturatedColor = { h, s: newS, l, a };\n\n return returnColorObject(saturatedColor);\n };\n\n /**\n * - Decrease the saturation of the given color by a certain percentage/amount.\n *\n * @example\n * saturate('red', 20).hex();\n * saturate('rgb(255, 0, 0)', '20%').rgb().string();\n */\n const desaturate = (color: SupportedColorFormats, amount: number | string): ConversionMethods => {\n const { h, s, l, a } = HSL(color).object();\n const saturation = typeof amount === \"string\" ? s - s * (parseFloat(amount) / 100) : s - amount;\n const newS = clamp100(saturation);\n const desaturatedColor = { h, s: newS, l, a };\n\n return returnColorObject(desaturatedColor);\n };\n\n // * MARK: Brightness Manuipulation\n /** - Set HSL's `luminosity` channel for a given color to a specific amount. */\n const setLuminance = (color: SupportedColorFormats, amount: number): ConversionMethods => {\n const { h, s, a } = HSL(color).object();\n const newL = clamp100(amount);\n const newColor = { h, s, l: newL, a };\n\n return returnColorObject(newColor);\n };\n\n /**\n * - Increase the brightness of the given color by a certain percentage/amount.\n *\n * @example\n * brighten('red', 20).hex();\n * brighten('rgb(255, 0, 0)', '20%').rgb().string();\n */\n const brighten = (color: SupportedColorFormats, amount: number | string): ConversionMethods => {\n const { h, s, l, a } = HSL(color).object();\n const lum = typeof amount === \"string\" ? l + l * (parseFloat(amount) / 100) : l + amount;\n const newL = clamp100(lum);\n const brightenedColor = { h, s, l: newL, a };\n\n return returnColorObject(brightenedColor);\n };\n\n /**\n * - Decrease the brightness of the given color by a certain percentage/amount.\n *\n * @example\n * darken('red', 20).hex();\n * darken('rgb(255, 0, 0)', '20%').rgb().string();\n */\n const darken = (color: SupportedColorFormats, amount: number | string): ConversionMethods => {\n const { h, s, l, a } = HSL(color).object();\n const lum = typeof amount === \"string\" ? l - l * (parseFloat(amount) / 100) : l - amount;\n const newL = clamp100(lum);\n const darkenedColor = { h, s, l: newL, a };\n\n return returnColorObject(darkenedColor);\n };\n\n /** - Set HSV's `value` (brightness) channel for a given color to a specific amount. */\n const setBrightness = (color: SupportedColorFormats, amount: number): ConversionMethods => {\n const { h, s, a } = HSV(color).object();\n const newV = clamp100(amount);\n const newColor = { h, s, v: newV, a };\n\n return returnColorObject(newColor);\n };\n\n /** Increase HSV's `value` (brightness) channel value of a color by the given percentage/amount. */\n const increaseBrightness = (\n color: SupportedColorFormats,\n amount: number | string,\n ): ConversionMethods => {\n const { h, s, v, a } = HSV(color).object();\n const value = typeof amount === \"string\" ? v + v * (parseFloat(amount) / 100) : v + amount;\n const newV = clamp100(value);\n const newColor = { h, s, v: newV, a };\n\n return returnColorObject(newColor);\n };\n\n /** Decrease HSV's `value` (brightness) channel value of a color by the given percentage/amount. */\n const decreaseBrightness = (\n color: SupportedColorFormats,\n amount: number | string,\n ): ConversionMethods => {\n const { h, s, v, a } = HSV(color).object();\n const value = typeof amount === \"string\" ? v - v * (parseFloat(amount) / 100) : v - amount;\n const newV = clamp100(value);\n const newColor = { h, s, v: newV, a };\n\n return returnColorObject(newColor);\n };\n\n // * MARK: Color Utilities\n\n /**\n * - Blends two colors by a certain amount.\n *\n * @example\n * blend('yellow', 'red', 50).hex(); // #ff8000\n */\n const blend = (\n color1: SupportedColorFormats,\n color2: SupportedColorFormats,\n percentage: number,\n ): ConversionMethods => {\n percentage = percentage / 100;\n\n const rgba1 = RGB(color1).object();\n const rgba2 = RGB(color2).object();\n\n const r = clampRGB(rgba1.r * (1 - percentage) + rgba2.r * percentage),\n g = clampRGB(rgba1.g * (1 - percentage) + rgba2.g * percentage),\n b = clampRGB(rgba1.b * (1 - percentage) + rgba2.b * percentage),\n a = clampAlpha(rgba1.a * (1 - percentage) + rgba2.a * percentage);\n\n const blendedColor = { r, g, b, a };\n\n return returnColorObject(blendedColor);\n };\n\n /** - Invert (negate) a color, black becomes white, white becomes black, blue becomes orange and so on. */\n const invert = (color: SupportedColorFormats): ConversionMethods => {\n const { r, g, b, a } = RGB(color).object();\n const invertedColor = { r: 255 - r, g: 255 - g, b: 255 - b, a };\n return returnColorObject(invertedColor);\n };\n\n /** - Completely desaturate a color into grayscale. */\n const grayscale = (color: SupportedColorFormats): ConversionMethods => {\n const { r, g, b, a } = RGB(color).object();\n const gray = clampRGB(r * 0.3 + g * 0.59 + b * 0.11);\n const grayColor = { r: gray, g: gray, b: gray, a };\n\n return returnColorObject(grayColor);\n };\n\n /** - Generate a random color from `HSL` values. */\n const randomHslColor = ({\n h = [0, 360],\n s = [0, 100],\n l = [0, 100],\n a = [1, 1],\n } = {}): ConversionMethods => {\n const random = {\n h: clampHue(randomNumber(h[0] ?? 0, h[1] ?? 360)),\n s: clamp100(randomNumber(s[0] ?? 0, s[1] ?? 100)),\n l: clamp100(randomNumber(l[0] ?? 0, l[1] ?? 100)),\n a: clampAlpha(randomNumber(a[0] ?? 1, a[1] ?? 1)),\n };\n\n return returnColorObject(random);\n };\n\n /** - Generate a random color from `HSV` values. */\n const randomHsvColor = ({\n h = [0, 360],\n s = [0, 100],\n v = [0, 100],\n a = [1, 1],\n } = {}): ConversionMethods => {\n const random = {\n h: clampHue(randomNumber(h[0] ?? 0, h[1] ?? 360)),\n s: clamp100(randomNumber(s[0] ?? 0, s[1] ?? 100)),\n v: clamp100(randomNumber(v[0] ?? 0, v[1] ?? 100)),\n a: clampAlpha(randomNumber(a[0] ?? 1, a[1] ?? 1)),\n };\n\n return returnColorObject(random);\n };\n\n /** - Generate a random color from `RGB` values. */\n const randomRgbColor = ({\n r = [0, 255],\n g = [0, 255],\n b = [0, 255],\n a = [1, 1],\n } = {}): ConversionMethods => {\n const random = {\n r: clampRGB(randomNumber(r[0] ?? 0, r[1] ?? 255)),\n g: clampRGB(randomNumber(g[0] ?? 0, g[1] ?? 255)),\n b: clampRGB(randomNumber(b[0] ?? 0, b[1] ?? 255)),\n a: clampAlpha(randomNumber(a[0] ?? 1, a[1] ?? 1)),\n };\n\n return returnColorObject(random);\n };\n\n /** - Generate a random color from `HWB` values. */\n const randomHwbColor = ({\n h = [0, 360],\n w = [0, 100],\n b = [0, 100],\n a = [1, 1],\n } = {}): ConversionMethods => {\n const random = {\n h: clampHue(randomNumber(h[0] ?? 0, h[1] ?? 360)),\n w: clamp100(randomNumber(w[0] ?? 0, w[1] ?? 100)),\n b: clamp100(randomNumber(b[0] ?? 0, b[1] ?? 100)),\n a: clampAlpha(randomNumber(a[0] ?? 1, a[1] ?? 1)),\n };\n\n return returnColorObject(random);\n };\n\n /** - Returns the first color with the desired contrast ratio against the second color */\n const adjustContrast = (\n color1: SupportedColorFormats,\n color2: SupportedColorFormats,\n ratio = 4.5,\n ): ConversionMethods => {\n const contrast = contrastRatio(color1, color2);\n const color1RGB = RGB(color1).object();\n const channels = [\"r\", \"g\", \"b\"] as const;\n\n function adjustLuminance(colorRGB: rgbaT, by: number) {\n const r = clampRGB(colorRGB.r + by);\n const g = clampRGB(colorRGB.g + by);\n const b = clampRGB(colorRGB.b + by);\n return { r, g, b, a: colorRGB.a };\n }\n\n let newColor = color1RGB;\n\n //* increase contrast\n if (ratio && contrast < ratio) {\n while (contrastRatio(newColor, color2) < ratio) {\n const adjustBy = isDark(color2) ? 1 : -1; // increase or decrease relative to the background color\n newColor = adjustLuminance(newColor, adjustBy);\n\n // break if the color reached the limit\n if (channels.every((e) => newColor[e] === 0)) break;\n if (channels.every((e) => newColor[e] === 255)) break;\n }\n //* decrease contrast\n } else if (ratio && contrast > ratio) {\n while (contrastRatio(newColor, color2) > ratio) {\n const adjustBy = !isDark(color2) ? 1 : -1; // increase or decrease relative to the background color\n newColor = adjustLuminance(newColor, adjustBy);\n\n // break if the color reached the limit\n if (channels.every((e) => newColor[e] === 0)) break;\n if (channels.every((e) => newColor[e] === 255)) break;\n }\n }\n\n return returnColorObject(newColor);\n };\n\n return {\n // color conversion\n HEX,\n RGB,\n HSL,\n HWB,\n HSV,\n // color information\n getFormat,\n getRed,\n getGreen,\n getBlue,\n getHue,\n getSaturation,\n getBrightness,\n getLuminance,\n getLuminanceWCAG,\n isDark,\n isLight,\n areColorsEqual,\n contrastRatio,\n\n // color manipulation\n setRed,\n increaseRed,\n decreaseRed,\n\n setGreen,\n increaseGreen,\n decreaseGreen,\n\n setBlue,\n increaseBlue,\n decreaseBlue,\n\n getAlpha,\n setAlpha,\n increaseAlpha,\n decreaseAlpha,\n\n setHue,\n increaseHue,\n decreaseHue,\n spin,\n\n setSaturation,\n saturate,\n desaturate,\n\n setLuminance,\n brighten,\n darken,\n setBrightness,\n increaseBrightness,\n decreaseBrightness,\n\n // color utilities\n blend,\n invert,\n grayscale,\n randomHslColor,\n randomHsvColor,\n randomRgbColor,\n randomHwbColor,\n adjustContrast,\n };\n};\n\ntype ColorKit = ReturnType & {\n /** - Initiates the asynchronous execution of a workletized colorKit function on the UI thread. */\n runOnUI: typeof colorKitUI;\n};\n\nconst colorKit = colorKitUI() as ColorKit;\ncolorKit.runOnUI = colorKitUI;\nexport default colorKit;\n", "type": "registry:lib", "target": "@components/pitsi-ui/native-ui/src/helpers/external/utils/color-kit/index.ts" }, { "path": "registry/native-ui/src/helpers/external/utils/color-kit/types.ts", "content": "/**\n * Color manipulation utilities adapted from reanimated-color-picker\n *\n * Original source: https://github.com/alabsi91/reanimated-color-picker\n * Author: @alabsi91\n * License: MIT\n *\n * This code has been adapted for use in PitsiUI Native with modifications\n * for TypeScript compatibility and integration with the theme system.\n */\n\ntype NAMED_COLORS = {\n readonly aliceblue: \"#f0f8ff\";\n readonly antiquewhite: \"#faebd7\";\n readonly aqua: \"#00ffff\";\n readonly aquamarine: \"#7fffd4\";\n readonly azure: \"#f0ffff\";\n readonly beige: \"#f5f5dc\";\n readonly bisque: \"#ffe4c4\";\n readonly black: \"#000000\";\n readonly blanchedalmond: \"#ffebcd\";\n readonly blue: \"#0000ff\";\n readonly blueviolet: \"#8a2be2\";\n readonly brown: \"#a52a2a\";\n readonly burlywood: \"#deb887\";\n readonly cadetblue: \"#5f9ea0\";\n readonly chartreuse: \"#7fff00\";\n readonly chocolate: \"#d2691e\";\n readonly coral: \"#ff7f50\";\n readonly cornflowerblue: \"#6495ed\";\n readonly cornsilk: \"#fff8dc\";\n readonly crimson: \"#dc143c\";\n readonly cyan: \"#00ffff\";\n readonly darkblue: \"#00008b\";\n readonly darkcyan: \"#008b8b\";\n readonly darkgoldenrod: \"#b8860b\";\n readonly darkgray: \"#a9a9a9\";\n readonly darkgreen: \"#006400\";\n readonly darkgrey: \"#a9a9a9\";\n readonly darkkhaki: \"#bdb76b\";\n readonly darkmagenta: \"#8b008b\";\n readonly darkolivegreen: \"#556b2f\";\n readonly darkorange: \"#ff8c00\";\n readonly darkorchid: \"#9932cc\";\n readonly darkred: \"#8b0000\";\n readonly darksalmon: \"#e9967a\";\n readonly darkseagreen: \"#8fbc8f\";\n readonly darkslateblue: \"#483d8b\";\n readonly darkslategrey: \"#2f4f4f\";\n readonly darkturquoise: \"#00ced1\";\n readonly darkviolet: \"#9400d3\";\n readonly deeppink: \"#ff1493\";\n readonly deepskyblue: \"#00bfff\";\n readonly dimgray: \"#696969\";\n readonly dimgrey: \"#696969\";\n readonly dodgerblue: \"#1e90ff\";\n readonly firebrick: \"#b22222\";\n readonly floralwhite: \"#fffaf0\";\n readonly forestgreen: \"#228b22\";\n readonly fuchsia: \"#ff00ff\";\n readonly gainsboro: \"#dcdcdc\";\n readonly ghostwhite: \"#f8f8ff\";\n readonly gold: \"#ffd700\";\n readonly goldenrod: \"#daa520\";\n readonly gray: \"#808080\";\n readonly green: \"#008000\";\n readonly greenyellow: \"#adff2f\";\n readonly grey: \"#808080\";\n readonly honeydew: \"#f0fff0\";\n readonly hotpink: \"#ff69b4\";\n readonly indianred: \"#cd5c5c\";\n readonly indigo: \"#4b0082\";\n readonly ivory: \"#fffff0\";\n readonly khaki: \"#f0e68c\";\n readonly lavender: \"#e6e6fa\";\n readonly lavenderblush: \"#fff0f5\";\n readonly lawngreen: \"#7cfc00\";\n readonly lemonchiffon: \"#fffacd\";\n readonly lightblue: \"#add8e6\";\n readonly lightcoral: \"#f08080\";\n readonly lightcyan: \"#e0ffff\";\n readonly lightgoldenrodyellow: \"#fafad2\";\n readonly lightgray: \"#d3d3d3\";\n readonly lightgreen: \"#90ee90\";\n readonly lightgrey: \"#d3d3d3\";\n readonly lightpink: \"#ffb6c1\";\n readonly lightsalmon: \"#ffa07a\";\n readonly lightseagreen: \"#20b2aa\";\n readonly lightskyblue: \"#87cefa\";\n readonly lightslategrey: \"#778899\";\n readonly lightsteelblue: \"#b0c4de\";\n readonly lightyellow: \"#ffffe0\";\n readonly lime: \"#00ff00\";\n readonly limegreen: \"#32cd32\";\n readonly linen: \"#faf0e6\";\n readonly magenta: \"#ff00ff\";\n readonly maroon: \"#800000\";\n readonly mediumaquamarine: \"#66cdaa\";\n readonly mediumblue: \"#0000cd\";\n readonly mediumorchid: \"#ba55d3\";\n readonly mediumpurple: \"#9370db\";\n readonly mediumseagreen: \"#3cb371\";\n readonly mediumslateblue: \"#7b68ee\";\n readonly mediumspringgreen: \"#00fa9a\";\n readonly mediumturquoise: \"#48d1cc\";\n readonly mediumvioletred: \"#c71585\";\n readonly midnightblue: \"#191970\";\n readonly mintcream: \"#f5fffa\";\n readonly mistyrose: \"#ffe4e1\";\n readonly moccasin: \"#ffe4b5\";\n readonly navajowhite: \"#ffdead\";\n readonly navy: \"#000080\";\n readonly oldlace: \"#fdf5e6\";\n readonly olive: \"#808000\";\n readonly olivedrab: \"#6b8e23\";\n readonly orange: \"#ffa500\";\n readonly orangered: \"#ff4500\";\n readonly orchid: \"#da70d6\";\n readonly palegoldenrod: \"#eee8aa\";\n readonly palegreen: \"#98fb98\";\n readonly paleturquoise: \"#afeeee\";\n readonly palevioletred: \"#db7093\";\n readonly papayawhip: \"#ffefd5\";\n readonly peachpuff: \"#ffdab9\";\n readonly peru: \"#cd853f\";\n readonly pink: \"#ffc0cb\";\n readonly plum: \"#dda0dd\";\n readonly powderblue: \"#b0e0e6\";\n readonly purple: \"#800080\";\n readonly rebeccapurple: \"#663399\";\n readonly red: \"#ff0000\";\n readonly rosybrown: \"#bc8f8f\";\n readonly royalblue: \"#4169e1\";\n readonly saddlebrown: \"#8b4513\";\n readonly salmon: \"#fa8072\";\n readonly sandybrown: \"#f4a460\";\n readonly seagreen: \"#2e8b57\";\n readonly seashell: \"#fff5ee\";\n readonly sienna: \"#a0522d\";\n readonly silver: \"#c0c0c0\";\n readonly skyblue: \"#87ceeb\";\n readonly slateblue: \"#6a5acd\";\n readonly slategray: \"#708090\";\n readonly snow: \"#fffafa\";\n readonly springgreen: \"#00ff7f\";\n readonly steelblue: \"#4682b4\";\n readonly tan: \"#d2b48c\";\n readonly teal: \"#008080\";\n readonly thistle: \"#d8bfd8\";\n readonly tomato: \"#ff6347\";\n readonly turquoise: \"#40e0d0\";\n readonly violet: \"#ee82ee\";\n readonly wheat: \"#f5deb3\";\n readonly white: \"#ffffff\";\n readonly whitesmoke: \"#f5f5f5\";\n readonly yellow: \"#ffff00\";\n readonly yellowgreen: \"#9acd32\";\n};\n\nexport type ColorFormats =\n | \"hex3\"\n | \"hex4\"\n | \"hex6\"\n | \"hex8\"\n | \"hsl\"\n | \"hsla\"\n | \"rgb\"\n | \"rgba\"\n | \"hsva\"\n | \"hsv\"\n | \"hwba\"\n | \"hwb\";\n\nexport type ColorString = keyof NAMED_COLORS | (string & NonNullable);\n\nexport type rgbaT = { r: number; g: number; b: number; a: number };\nexport type rgbT = Omit;\n\nexport type hslaT = { h: number; s: number; l: number; a: number };\nexport type hslT = Omit;\n\nexport type hsvaT = { h: number; s: number; v: number; a: number };\nexport type hsvT = Omit;\n\nexport type hwbaT = { h: number; w: number; b: number; a: number };\nexport type hwbT = Omit;\n\nexport type SupportedColorFormats =\n | ColorString\n | rgbaT\n | rgbT\n | hslaT\n | hslT\n | hsvaT\n | hsvT\n | hwbaT\n | hwbT\n | number;\n\nexport type ColorTypes = {\n object: (roundValues?: boolean) => T;\n string: (alpha?: boolean) => string;\n array: (roundValues?: boolean) => number[];\n};\n\nexport type ConversionMethods = {\n hex: () => string;\n rgb: () => ColorTypes;\n hsl: () => ColorTypes;\n hsv: () => ColorTypes;\n hwb: () => ColorTypes;\n};\n", "type": "registry:lib", "target": "@components/pitsi-ui/native-ui/src/helpers/external/utils/color-kit/types.ts" }, { "path": "registry/native-ui/src/helpers/external/utils/index.ts", "content": "export * from \"./cn\";\nexport { default as colorKit } from \"./color-kit\";\n", "type": "registry:lib", "target": "@components/pitsi-ui/native-ui/src/helpers/external/utils/index.ts" }, { "path": "registry/native-ui/src/helpers/internal/components/animated-check-icon.tsx", "content": "import type React from \"react\";\nimport Animated, {\n Easing,\n useAnimatedProps,\n useDerivedValue,\n withDelay,\n withTiming,\n} from \"react-native-reanimated\";\nimport Svg, { Path } from \"react-native-svg\";\nimport { useThemeColor } from \"../../external/hooks\";\n\nconst AnimatedPath = Animated.createAnimatedComponent(Path);\n\nconst DEFAULT_SIZE = 18;\nconst ENTER_DURATION = 150;\nconst EXIT_DURATION = 150;\n\ninterface CheckIconProps {\n isSelected?: boolean;\n size?: number;\n strokeWidth?: number;\n color?: string;\n enterDuration?: number;\n exitDuration?: number;\n}\n\nexport const AnimatedCheckIcon: React.FC = ({\n isSelected = false,\n size = DEFAULT_SIZE,\n strokeWidth = 2.5,\n color,\n enterDuration = ENTER_DURATION,\n exitDuration = EXIT_DURATION,\n}) => {\n const themeColorForeground = useThemeColor(\"foreground\");\n\n const checkProgress = useDerivedValue(() => {\n if (isSelected) {\n return withDelay(\n 100,\n withTiming(1, {\n duration: enterDuration,\n easing: Easing.out(Easing.ease),\n }),\n );\n } else {\n return withTiming(0, { duration: exitDuration });\n }\n });\n\n const animatedCheckProps = useAnimatedProps(\n () => ({\n strokeDasharray: size,\n strokeDashoffset: size * (1 - checkProgress.value),\n }),\n [checkProgress],\n );\n\n return (\n \n \n \n );\n};\n\nAnimatedCheckIcon.displayName = \"PitsiUINative.AnimatedCheckIcon\";\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/components/animated-check-icon.tsx" }, { "path": "registry/native-ui/src/helpers/internal/components/bottom-sheet-content-container.tsx", "content": "import { useEffect, useRef } from \"react\";\nimport { BackHandler } from \"react-native\";\nimport { useAnimatedReaction } from \"react-native-reanimated\";\nimport { scheduleOnRN } from \"react-native-worklets\";\nimport GorhomBottomSheetPackage from \"../../../optional/gorhom-bottom-sheet\";\nimport type { BottomSheetContentContainerProps } from \"../types/bottom-sheet\";\n\nconst BottomSheetView = GorhomBottomSheetPackage?.BottomSheetView;\nconst useBottomSheet = GorhomBottomSheetPackage?.useBottomSheet;\n\n/**\n * Reusable BottomSheetContentContainer component\n *\n * This component handles the content container for bottom sheets used across\n * BottomSheet, Popover, and Select components. It manages the expand/close\n * behavior based on the provided state and applies consistent styling.\n *\n */\nexport function BottomSheetContentContainer({\n children,\n isOpen,\n progress,\n isDragging,\n isPanActivated,\n isClosingOnSwipe,\n initialIndex,\n contentContainerClassName,\n contentContainerProps,\n onOpenChange,\n enablePanDownToClose,\n}: BottomSheetContentContainerProps) {\n const { close, snapToIndex } = useBottomSheet();\n const prevIsOpenRef = useRef(isOpen);\n\n const closeBottomSheet = () => {\n onOpenChange(false);\n };\n\n useAnimatedReaction(\n () => progress.get(),\n (value) => {\n if (value > 1.5 && !isDragging.get() && !isClosingOnSwipe.get()) {\n isClosingOnSwipe.set(true);\n scheduleOnRN(closeBottomSheet);\n }\n if (value === 2) {\n isPanActivated.set(false);\n }\n },\n );\n\n /**\n * Dismiss the bottom sheet when the Android hardware back button is pressed.\n * Only registers the listener while the sheet is open so that closed\n * instances (Popover, Select, other BottomSheets) don't consume the event.\n */\n useEffect(() => {\n if (!isOpen || !enablePanDownToClose) return;\n\n const backHandler = BackHandler.addEventListener(\"hardwareBackPress\", () => {\n close();\n onOpenChange(false);\n return true;\n });\n\n return () => {\n backHandler.remove();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isOpen, enablePanDownToClose, close, onOpenChange]);\n\n useEffect(() => {\n const wasOpen = prevIsOpenRef.current;\n prevIsOpenRef.current = isOpen;\n\n if (isOpen && !wasOpen) {\n // Only snap to initial index when transitioning from closed to open\n isPanActivated.set(false);\n snapToIndex(initialIndex);\n } else if (!isOpen && wasOpen) {\n // Close when transitioning from open to closed\n close();\n }\n // Note: We intentionally don't include snapToIndex, close, or isPanActivated\n // in the dependency array to prevent re-snapping when content re-renders.\n // We only want to snap when isOpen or initialIndex changes.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n isOpen,\n initialIndex,\n snapToIndex, // Only snap to initial index when transitioning from closed to open\n isPanActivated.set, // Close when transitioning from open to closed\n close,\n ]);\n\n return (\n \n {children}\n \n );\n}\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/components/bottom-sheet-content-container.tsx" }, { "path": "registry/native-ui/src/helpers/internal/components/bottom-sheet-content.tsx", "content": "import type BottomSheet from \"@gorhom/bottom-sheet\";\nimport type { BottomSheetProps } from \"@gorhom/bottom-sheet\";\nimport { forwardRef, useMemo } from \"react\";\nimport type { StyleProp, ViewStyle } from \"react-native\";\nimport type { SharedValue } from \"react-native-reanimated\";\nimport { ReduceMotion } from \"react-native-reanimated\";\nimport { withUniwind } from \"uniwind\";\nimport {\n bottomSheetClassNames,\n useBottomSheetContentAnimation,\n} from \"../../../components/bottom-sheet/bottom-sheet.shared\";\nimport GorhomBottomSheetPackage from \"../../../optional/gorhom-bottom-sheet\";\nimport { BottomSheetIsDraggingProvider } from \"../contexts\";\nimport { useBottomSheetGestureHandlers } from \"../hooks\";\nimport { usePopupBottomSheetContentAnimation } from \"../hooks/use-popup-bottom-sheet-content-animation\";\nimport type { BaseBottomSheetContentProps } from \"../types/bottom-sheet\";\nimport { BottomSheetContentContainer } from \"./bottom-sheet-content-container\";\n\nconst StyledBottomSheet = withUniwind(GorhomBottomSheetPackage?.default);\n\n/**\n * Props for the reusable BottomSheetContent component\n */\nexport interface BottomSheetContentProps\n extends BaseBottomSheetContentProps,\n Partial {\n /**\n * Whether the bottom sheet is open\n */\n isOpen: boolean;\n /**\n * Animation progress shared value (0=idle, 1=open, 2=close)\n */\n progress: SharedValue;\n /**\n * Whether the bottom sheet is dragging\n */\n isDragging: SharedValue;\n /**\n * Callback when the bottom sheet open state changes\n */\n onOpenChange: (open: boolean) => void;\n /**\n * Initial index of the bottom sheet\n */\n index?: number;\n /**\n * Additional style for the background\n */\n backgroundStyle?: StyleProp;\n}\n\n/**\n * Reusable BottomSheetContent component\n *\n * This component provides a reusable bottom sheet content wrapper used across\n * Popover, Select, and other components when using bottom-sheet presentation.\n * It handles animation coordination, styling, and gesture handling.\n *\n * @example\n * ```tsx\n * \n * {children}\n * \n * ```\n */\nexport const BottomSheetContent = forwardRef(\n (\n {\n children,\n index: initialIndex,\n backgroundClassName,\n handleIndicatorClassName,\n contentContainerClassName: contentContainerClassNameProp,\n contentContainerProps,\n animation,\n animationConfigs,\n backgroundStyle,\n isOpen,\n progress,\n isDragging,\n onOpenChange,\n ...restProps\n },\n ref,\n ) => {\n const { isAnimationDisabledValue } = useBottomSheetContentAnimation({\n animation,\n });\n\n const { animatedIndex, isClosingOnSwipe, isPanActivated } = usePopupBottomSheetContentAnimation(\n {\n progress,\n isDragging,\n },\n );\n\n const contentBackgroundClassName = bottomSheetClassNames.contentBackground({\n className: backgroundClassName,\n });\n\n const contentHandleIndicatorClassName = bottomSheetClassNames.contentHandleIndicator({\n className: handleIndicatorClassName,\n });\n\n const contentContainerClassName = bottomSheetClassNames.contentContainer({\n className: contentContainerClassNameProp,\n });\n\n const mergedAnimationConfigs = useMemo(\n () => ({\n ...animationConfigs,\n reduceMotion: isAnimationDisabledValue\n ? ReduceMotion.Always\n : animationConfigs?.reduceMotion,\n }),\n [animationConfigs, isAnimationDisabledValue],\n );\n\n return (\n \n \n \n {children}\n \n \n \n );\n },\n);\n\nBottomSheetContent.displayName = \"PitsiUINative.BottomSheetContent\";\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/components/bottom-sheet-content.tsx" }, { "path": "registry/native-ui/src/helpers/internal/components/check-icon.tsx", "content": "import type React from \"react\";\nimport Svg, { Path } from \"react-native-svg\";\nimport { useThemeColor } from \"../../external/hooks\";\n\ninterface CheckIconProps {\n size?: number;\n color?: string;\n}\n\nexport const CheckIcon: React.FC = ({ size = 16, color }) => {\n const themeColorForeground = useThemeColor(\"foreground\");\n\n return (\n \n \n \n );\n};\n\nCheckIcon.displayName = \"PitsiUINative.CheckIcon\";\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/components/check-icon.tsx" }, { "path": "registry/native-ui/src/helpers/internal/components/chevron-down-icon.tsx", "content": "import type React from \"react\";\nimport Svg, { Path } from \"react-native-svg\";\n\ninterface ChevronDownIconProps {\n size?: number;\n color?: string;\n}\n\n/**\n * Chevron down icon component\n * Reusable SVG icon used in Select and Accordion components\n */\nexport const ChevronDownIcon: React.FC = ({\n size = 16,\n color = \"currentColor\",\n}) => {\n return (\n \n \n \n );\n};\n\nChevronDownIcon.displayName = \"PitsiUINative.ChevronDownIcon\";\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/components/chevron-down-icon.tsx" }, { "path": "registry/native-ui/src/helpers/internal/components/chevron-right-icon.tsx", "content": "import type React from \"react\";\nimport Svg, { Path } from \"react-native-svg\";\n\ninterface ChevronRightIconProps {\n size?: number;\n color?: string;\n}\n\n/**\n * Chevron right icon component\n * Reusable SVG icon used in ListGroup and navigation components.\n * Path derived from chevron-down-icon rotated 90° clockwise.\n */\nexport const ChevronRightIcon: React.FC = ({\n size = 16,\n color = \"currentColor\",\n}) => {\n return (\n \n \n \n );\n};\n\nChevronRightIcon.displayName = \"PitsiUINative.ChevronRightIcon\";\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/components/chevron-right-icon.tsx" }, { "path": "registry/native-ui/src/helpers/internal/components/close-icon.tsx", "content": "import type React from \"react\";\nimport Svg, { Path } from \"react-native-svg\";\nimport { useThemeColor } from \"../../external/hooks\";\n\ninterface CloseIconProps {\n size?: number;\n color?: string;\n}\n\nexport const CloseIcon: React.FC = ({ size = 16, color }) => {\n const themeColorForeground = useThemeColor(\"foreground\");\n\n return (\n \n \n \n );\n};\n\nCloseIcon.displayName = \"PitsiUINative.CloseIcon\";\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/components/close-icon.tsx" }, { "path": "registry/native-ui/src/helpers/internal/components/full-window-overlay.tsx", "content": "import type { ReactNode } from \"react\";\nimport { Platform } from \"react-native\";\nimport ReactNativeScreensPackage from \"../../../optional/react-native-screens\";\n\nconst NativeFullWindowOverlay = ReactNativeScreensPackage?.FullWindowOverlay;\n\n/**\n * Props for the FullWindowOverlay component\n *\n * @description\n * FullWindowOverlay renders content in a separate native window on iOS,\n * which allows overlays (bottom sheets, dialogs, toasts) to appear above\n * native modals and the keyboard. However, this breaks the React Native\n * element inspector because it attaches to the main window.\n *\n * Set `disableFullWindowOverlay={true}` when you need to use the element\n * inspector during development. Note: when disabled, overlay content will\n * not render above native modals. iOS only; has no effect on Android.\n */\nexport interface FullWindowOverlayProps {\n /**\n * When true, uses a regular View instead of FullWindowOverlay on iOS.\n * Enables element inspector but overlay content won't appear above native modals.\n * @default false\n */\n disableFullWindowOverlay: boolean;\n /**\n * Controls whether VoiceOver treats the overlay window as a modal container.\n * When `false`, VoiceOver can still access elements behind the overlay.\n * When `true`, VoiceOver is restricted to elements inside the overlay.\n * @default false\n * @platform ios\n * @unstable This prop maps directly to the native `accessibilityViewIsModal`\n * on the container view and may change in a future react-native-screens release.\n */\n unstable_accessibilityContainerViewIsModal?: boolean;\n /**\n * Content to render inside the overlay\n */\n children: ReactNode;\n}\n\n/**\n * Wrapper for react-native-screens FullWindowOverlay with optional disable prop.\n *\n * @description\n * On iOS, FullWindowOverlay creates a separate native window for overlay content,\n * which breaks the React Native element inspector. Use `disableFullWindowOverlay`\n * when debugging to render content in the main window instead.\n */\nexport function FullWindowOverlay({\n disableFullWindowOverlay,\n unstable_accessibilityContainerViewIsModal = false,\n children,\n}: FullWindowOverlayProps) {\n if (Platform.OS !== \"ios\" || disableFullWindowOverlay || !NativeFullWindowOverlay) {\n return <>{children};\n }\n\n return (\n \n {children}\n \n );\n}\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/components/full-window-overlay.tsx" }, { "path": "registry/native-ui/src/helpers/internal/components/hero-text.tsx", "content": "import React from \"react\";\nimport { Text as RNText, type TextProps as RNTextProps } from \"react-native\";\nimport { useTextComponent } from \"../../external/hooks\";\nimport { cn } from \"../../external/utils\";\n\n/**\n * Props for HeroText component\n */\nexport interface HeroTextProps extends RNTextProps {\n /**\n * Additional CSS classes that will be merged with the default 'font-normal' class\n */\n className?: string;\n}\n\n/**\n * HeroText component that automatically applies global text configuration\n * from PitsiUINativeProvider.\n *\n * This component is distinct from React Native's Text component and includes\n * a default 'font-normal' className that can be extended via the className prop.\n *\n * Global text props that can be configured:\n * - adjustsFontSizeToFit: Auto-scale text to fit constraints\n * - allowFontScaling: Respect Text Size accessibility settings\n * - maxFontSizeMultiplier: Maximum font scale when allowFontScaling is enabled\n * - minimumFontScale: Minimum scale when adjustsFontSizeToFit is enabled (iOS only)\n *\n * @example\n * ```tsx\n * Hello World\n * ```\n *\n * @example\n * With custom className:\n * ```tsx\n * Hello World\n * ```\n *\n * @example\n * Global configuration in PitsiUINativeProvider:\n * ```tsx\n * \n * \n * \n * ```\n */\nexport const HeroText = React.forwardRef((props, ref) => {\n const { className, ...restProps } = props;\n const { textProps } = useTextComponent();\n\n const mergedProps = Object.assign({}, textProps, restProps);\n\n return ;\n});\n\nHeroText.displayName = \"HeroText\";\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/components/hero-text.tsx" }, { "path": "registry/native-ui/src/helpers/internal/components/index.ts", "content": "export * from \"./animated-check-icon\";\nexport * from \"./bottom-sheet-content\";\nexport * from \"./bottom-sheet-content-container\";\nexport * from \"./check-icon\";\nexport * from \"./chevron-down-icon\";\nexport * from \"./chevron-right-icon\";\nexport * from \"./close-icon\";\nexport * from \"./full-window-overlay\";\nexport * from \"./hero-text\";\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/components/index.ts" }, { "path": "registry/native-ui/src/helpers/internal/contexts/animation-settings-context.ts", "content": "import { createContext } from \"../utils\";\n\n/**\n * Context value for global animation settings\n */\nexport interface AnimationSettingsContextValue {\n /**\n * Whether all animations should be disabled (cascading from parent)\n */\n isAllAnimationsDisabled: boolean;\n}\n\nconst [AnimationSettingsProvider, useAnimationSettings] =\n createContext({\n name: \"AnimationSettingsContext\",\n strict: false,\n });\n\nexport { AnimationSettingsProvider, useAnimationSettings };\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/contexts/animation-settings-context.ts" }, { "path": "registry/native-ui/src/helpers/internal/contexts/bottom-sheet-is-dragging-context.ts", "content": "import type { SharedValue } from \"react-native-reanimated\";\nimport { createContext } from \"../utils\";\n\nconst [BottomSheetIsDraggingProvider, useBottomSheetIsDragging] = createContext<{\n isDragging: SharedValue;\n}>({\n name: \"BottomSheetIsDraggingContext\",\n});\n\nexport { BottomSheetIsDraggingProvider, useBottomSheetIsDragging };\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/contexts/bottom-sheet-is-dragging-context.ts" }, { "path": "registry/native-ui/src/helpers/internal/contexts/form-field-context.ts", "content": "import { createContext } from \"../utils\";\n\n/**\n * Combined context value for form field state and layout (shared across form field components).\n *\n * Providers: TextField, SearchField, ControlField, RadioGroup.\n * Consumers: Label, Description, FieldError, Input.\n */\nexport interface FormFieldContextValue {\n /**\n * Whether the form field is required\n */\n isRequired: boolean;\n /**\n * Whether the form field is disabled\n */\n isDisabled: boolean;\n /**\n * Whether the form field is in an invalid state\n */\n isInvalid: boolean;\n /**\n * When true, child components (Label, Description, FieldError) apply\n * additional horizontal padding (`px-1.5`) for consistent field layout.\n *\n * Set to `true` by container components like TextField and SearchField.\n */\n hasFieldPadding: boolean;\n}\n\nconst [FormFieldProvider, useFormField] = createContext({\n name: \"FormFieldContext\",\n strict: false,\n});\n\nexport { FormFieldProvider, useFormField };\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/contexts/form-field-context.ts" }, { "path": "registry/native-ui/src/helpers/internal/contexts/index.ts", "content": "export * from \"./animation-settings-context\";\nexport * from \"./bottom-sheet-is-dragging-context\";\nexport * from \"./form-field-context\";\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/contexts/index.ts" }, { "path": "registry/native-ui/src/helpers/internal/hooks/index.ts", "content": "export * from \"./use-augmented-ref\";\nexport * from \"./use-bottom-sheet-gesture-handlers\";\nexport * from \"./use-combined-animation-disabled-state\";\nexport * from \"./use-controllable-state\";\nexport * from \"./use-dev-info\";\nexport * from \"./use-keyboard-status\";\nexport * from \"./use-popup-bottom-sheet-content-animation\";\nexport * from \"./use-popup-dialog-content-animation\";\nexport * from \"./use-popup-overlay-animation\";\nexport * from \"./use-popup-popover-content-animation\";\nexport * from \"./use-popup-root-animation\";\nexport * from \"./use-relative-position\";\nexport * from \"./use-resolved-style-property\";\n", "type": "registry:hook", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/hooks/index.ts" }, { "path": "registry/native-ui/src/helpers/internal/hooks/use-augmented-ref.ts", "content": "import type * as React from \"react\";\nimport { useImperativeHandle, useRef } from \"react\";\n\ninterface AugmentRefProps {\n ref: React.Ref;\n methods?: Record any>;\n deps?: any[];\n}\n\nexport function useAugmentedRef({ ref, methods, deps = [] }: AugmentRefProps) {\n const augmentedRef = useRef(null);\n\n useImperativeHandle(\n ref,\n () => {\n if (typeof augmentedRef === \"function\" || !augmentedRef?.current) {\n return {} as T;\n }\n return {\n ...augmentedRef.current,\n ...methods,\n };\n },\n // biome-ignore lint/correctness/useExhaustiveDependencies: deps are caller-controlled\n deps,\n );\n return augmentedRef;\n}\n", "type": "registry:hook", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/hooks/use-augmented-ref.ts" }, { "path": "registry/native-ui/src/helpers/internal/hooks/use-bottom-sheet-gesture-handlers.ts", "content": "import type {\n GestureEventHandlerCallbackType,\n GestureEventsHandlersHookType,\n} from \"@gorhom/bottom-sheet\";\nimport GorhomBottomSheetPackage from \"../../../optional/gorhom-bottom-sheet\";\nimport { useBottomSheetIsDragging } from \"../contexts/bottom-sheet-is-dragging-context\";\n\nexport const useBottomSheetGestureHandlers: GestureEventsHandlersHookType = () => {\n const { isDragging } = useBottomSheetIsDragging();\n\n const defaultHandlers = GorhomBottomSheetPackage.useGestureEventsHandlersDefault();\n\n const handleOnStart: GestureEventHandlerCallbackType = (source, payload) => {\n \"worklet\";\n isDragging.set(true);\n defaultHandlers.handleOnStart(source, payload);\n };\n\n const handleOnChange: GestureEventHandlerCallbackType = (source, payload) => {\n \"worklet\";\n defaultHandlers.handleOnChange(source, payload);\n };\n\n const handleOnEnd: GestureEventHandlerCallbackType = (source, payload) => {\n \"worklet\";\n isDragging.set(false);\n defaultHandlers.handleOnEnd(source, payload);\n };\n\n const handleOnFinalize: GestureEventHandlerCallbackType = (source, payload) => {\n \"worklet\";\n isDragging.set(false);\n defaultHandlers.handleOnFinalize(source, payload);\n };\n\n return {\n handleOnStart,\n handleOnChange,\n handleOnEnd,\n handleOnFinalize,\n };\n};\n", "type": "registry:hook", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/hooks/use-bottom-sheet-gesture-handlers.ts" }, { "path": "registry/native-ui/src/helpers/internal/hooks/use-combined-animation-disabled-state.ts", "content": "import { useGlobalAnimationSettings } from \"../../../providers/animation-settings\";\nimport { useAnimationSettings } from \"../contexts/animation-settings-context\";\nimport type { AnimationRoot } from \"../types/animation\";\nimport { getCombinedAnimationDisabledState, getRootAnimationState } from \"../utils/animation\";\n\n/**\n * Hook to combine global, parent, and own animation disabled states\n *\n * @description\n * This hook combines three sources of animation disabled state:\n * 1. Global state from GlobalAnimationSettingsProvider\n * 2. Parent state from AnimationSettingsContext (component tree cascading)\n * 3. Own state from the component's animation prop\n *\n * Priority: Global > Parent > Own (global wins if enabled)\n *\n * @param animation - Root animation configuration for the component\n * @returns Combined isAllAnimationsDisabled value\n *\n * @example\n * ```tsx\n * const isAllAnimationsDisabled = useCombinedAnimationDisabledState(animation);\n * ```\n */\nexport function useCombinedAnimationDisabledState>(\n animation: AnimationRoot | undefined,\n): boolean {\n // Get global animation disabled state\n const { globalIsAllAnimationsDisabled } = useGlobalAnimationSettings();\n\n // Read parent animation disabled state from global context\n const parentAnimationSettingsContext = useAnimationSettings();\n const parentIsAllAnimationsDisabled = parentAnimationSettingsContext?.isAllAnimationsDisabled;\n\n // Get own animation disabled state\n const { isAllAnimationsDisabled: ownIsAllAnimationsDisabled } = getRootAnimationState(animation);\n\n // Combine global, parent, and own disable-all states (global > parent > own)\n return getCombinedAnimationDisabledState({\n globalIsAllAnimationsDisabled,\n parentIsAllAnimationsDisabled,\n ownIsAllAnimationsDisabled,\n });\n}\n", "type": "registry:hook", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/hooks/use-combined-animation-disabled-state.ts" }, { "path": "registry/native-ui/src/helpers/internal/hooks/use-controllable-state.ts", "content": "// This project uses code from WorkOS/Radix Primitives.\n// The code is licensed under the MIT License.\n// https://github.com/radix-ui/primitives/tree/main\n\nimport { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from \"react\";\n\n/**\n * Parameters for the useControllableState hook\n */\ntype UseControllableStateParams = {\n /** The controlled value prop */\n prop?: T | undefined;\n /** The default value for uncontrolled mode */\n defaultProp?: T | undefined;\n /** Callback fired when the value changes */\n onChange?: (state: T) => void;\n};\n\n/**\n * Function type for state setter callbacks\n */\ntype SetStateFn = (prevState?: T) => T;\n\n/**\n * A hook that supports both controlled and uncontrolled state.\n * When a value prop is provided, the component is controlled.\n * When no value prop is provided, the component manages its own state.\n *\n * @param params - Configuration object with prop, defaultProp, and onChange\n * @returns A tuple of [value, setValue] similar to useState\n */\nfunction useControllableState({\n prop,\n defaultProp,\n onChange = () => {},\n}: UseControllableStateParams) {\n const [uncontrolledProp, setUncontrolledProp] = useUncontrolledState({\n defaultProp,\n onChange,\n });\n const isControlled = prop !== undefined;\n const value = isControlled ? prop : uncontrolledProp;\n const handleChange = useCallbackRef(onChange);\n\n /**\n * When the component transitions from controlled (prop !== undefined)\n * back to uncontrolled (prop === undefined), the internal uncontrolled\n * state may hold a stale value from a previous selection. Reset it so\n * the component correctly reflects the \"no value\" state.\n */\n const prevPropRef = useRef(prop);\n useLayoutEffect(() => {\n const wasControlled = prevPropRef.current !== undefined;\n if (wasControlled && prop === undefined) {\n setUncontrolledProp(undefined);\n }\n prevPropRef.current = prop;\n }, [prop, setUncontrolledProp]);\n\n const setValue: React.Dispatch> = useCallback(\n (nextValue) => {\n if (isControlled) {\n const setter = nextValue as SetStateFn;\n const val = typeof nextValue === \"function\" ? setter(prop) : nextValue;\n if (val !== prop) handleChange(val as T);\n } else {\n setUncontrolledProp(nextValue);\n }\n },\n [isControlled, prop, setUncontrolledProp, handleChange],\n );\n\n return [value, setValue] as const;\n}\n\n/**\n * Internal hook for managing uncontrolled state with change callbacks\n */\nfunction useUncontrolledState({\n defaultProp,\n onChange,\n}: Omit, \"prop\">) {\n const uncontrolledState = useState(defaultProp);\n const [value] = uncontrolledState;\n const prevValueRef = useRef(value);\n const handleChange = useCallbackRef(onChange);\n\n useEffect(() => {\n if (prevValueRef.current !== value) {\n handleChange(value as T);\n prevValueRef.current = value;\n }\n }, [value, handleChange]);\n\n return uncontrolledState;\n}\n\n/**\n * A custom hook that converts a callback to a ref to avoid triggering re-renders when passed as a\n * prop or avoid re-executing effects when passed as a dependency\n */\nfunction useCallbackRef any>(callback: T | undefined): T {\n const callbackRef = useRef(callback);\n\n useEffect(() => {\n callbackRef.current = callback;\n });\n\n // https://github.com/facebook/react/issues/19240\n return useMemo(() => ((...args) => callbackRef.current?.(...args)) as T, []);\n}\n\nexport { useControllableState };\n", "type": "registry:hook", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/hooks/use-controllable-state.ts" }, { "path": "registry/native-ui/src/helpers/internal/hooks/use-dev-info.ts", "content": "import { useEffect } from \"react\";\nimport type { DevInfoConfig } from \"../../../providers/pitsi-ui-native/types\";\n\nconst LOG_COLOR = {\n BLUE: \"\\x1b[34m\",\n YELLOW: \"\\x1b[33m\",\n RESET: \"\\x1b[0m\",\n};\n\n/**\n * Hook that displays developer information messages in the console.\n *\n * @description\n * Logs helpful styling principles and best practices during development.\n * Messages are only shown in __DEV__ mode and can be disabled via the\n * devInfo configuration.\n *\n * @param {DevInfoConfig} [devInfo] - Developer information configuration\n */\nexport function useDevInfo(devInfo?: DevInfoConfig): void {\n const { stylingPrinciples = true } = devInfo || {};\n\n useEffect(() => {\n if (__DEV__ && stylingPrinciples) {\n console.info(\n `${LOG_COLOR.BLUE}PitsiUI Native Styling Principles${LOG_COLOR.RESET}\\n` +\n `• className: this is your go-to styling solution. Use Tailwind CSS classes via className prop on all components.\\n` +\n `• StyleSheet precedence: The style prop (StyleSheet API) has precedence over className when both are provided. This allows you to override Tailwind classes when needed.\\n` +\n `• Animated styles: Some style properties are animated using react-native-reanimated and have precedence over className. To identify which styles are animated:\\n` +\n ` - Hover over className in your IDE - TypeScript definitions show which properties are occupied by animated styles\\n` +\n ` - Check component documentation - Each component page includes a link to the component's style source\\n` +\n `• If styles are occupied by animation, modify them via the animation prop on components that support it.\\n` +\n `• To deactivate animated style completely and apply your own styles, use isAnimatedStyleActive prop.\\n` +\n `${LOG_COLOR.YELLOW}💡 To disable this message, set config.devInfo.stylingPrinciples to false${LOG_COLOR.RESET}`,\n );\n }\n }, [stylingPrinciples]);\n}\n", "type": "registry:hook", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/hooks/use-dev-info.ts" }, { "path": "registry/native-ui/src/helpers/internal/hooks/use-keyboard-status.ts", "content": "import { useEffect, useState } from \"react\";\nimport { Keyboard } from \"react-native\";\n\nexport const useKeyboardStatus = () => {\n const [keyboardStatus, setKeyboardStatus] = useState(false);\n\n useEffect(() => {\n const showSubscription = Keyboard.addListener(\"keyboardDidShow\", () => {\n setKeyboardStatus(true);\n });\n const hideSubscription = Keyboard.addListener(\"keyboardDidHide\", () => {\n setKeyboardStatus(false);\n });\n\n return () => {\n showSubscription.remove();\n hideSubscription.remove();\n };\n }, []);\n\n return keyboardStatus;\n};\n", "type": "registry:hook", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/hooks/use-keyboard-status.ts" }, { "path": "registry/native-ui/src/helpers/internal/hooks/use-popup-bottom-sheet-content-animation.ts", "content": "import type { SharedValue } from \"react-native-reanimated\";\nimport {\n Extrapolation,\n interpolate,\n useAnimatedReaction,\n useSharedValue,\n} from \"react-native-reanimated\";\n\n/**\n * Props for usePopupBottomSheetContentAnimation hook\n */\nexport interface UsePopupBottomSheetContentAnimationProps {\n /**\n * Animation progress shared value (0=idle, 1=open, 2=close)\n */\n progress: SharedValue;\n /**\n * Dragging state shared value\n */\n isDragging: SharedValue;\n}\n\n/**\n * Animation hook for popup bottom sheet content components (Popover, Select bottom sheet presentation)\n * Handles synchronization between BottomSheet animatedIndex and popup progress state\n */\nexport function usePopupBottomSheetContentAnimation({\n progress,\n isDragging,\n}: UsePopupBottomSheetContentAnimationProps) {\n const animatedIndex = useSharedValue(-1);\n const isPanActivated = useSharedValue(false);\n const isClosingOnSwipe = useSharedValue(false);\n\n useAnimatedReaction(\n () => isDragging.get(),\n (current, previous) => {\n if (current && !previous) {\n isClosingOnSwipe.set(false);\n }\n if (!isPanActivated.get() && current) {\n isPanActivated.set(true);\n }\n },\n );\n\n useAnimatedReaction(\n () => animatedIndex.get(),\n (current) => {\n if (!isPanActivated.get()) {\n progress.set(interpolate(current, [-1, 0], [0, 1], Extrapolation.CLAMP));\n } else {\n progress.set(interpolate(current, [0, -1], [1, 2], Extrapolation.CLAMP));\n }\n },\n );\n\n return {\n animatedIndex,\n isPanActivated,\n isClosingOnSwipe,\n };\n}\n", "type": "registry:hook", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/hooks/use-popup-bottom-sheet-content-animation.ts" }, { "path": "registry/native-ui/src/helpers/internal/hooks/use-popup-dialog-content-animation.ts", "content": "import { useCallback, useEffect, useMemo } from \"react\";\nimport { Keyboard, useWindowDimensions } from \"react-native\";\nimport { Gesture } from \"react-native-gesture-handler\";\nimport type { SharedValue } from \"react-native-reanimated\";\nimport {\n Easing,\n type EntryOrExitLayoutType,\n Extrapolation,\n interpolate,\n Keyframe,\n useAnimatedStyle,\n useDerivedValue,\n useSharedValue,\n withSpring,\n} from \"react-native-reanimated\";\nimport { scheduleOnRN } from \"react-native-worklets\";\nimport { useAnimationSettings } from \"../contexts/animation-settings-context\";\nimport type { PopupDialogContentAnimation } from \"../types/animation\";\nimport { getAnimationState, getIsAnimationDisabledValue } from \"../utils/animation\";\n\nexport interface UsePopupDialogContentAnimationProps {\n /**\n * Whether the dialog is open\n */\n isOpen: boolean;\n /**\n * Progress shared value (0 = closed, 1 = open, 2 = closing)\n */\n progress: SharedValue;\n /**\n * Whether user is currently dragging\n */\n isDragging: SharedValue;\n /**\n * Gesture release animation running state shared value\n */\n isGestureReleaseAnimationRunning: SharedValue;\n /**\n * Callback when dialog open state changes\n */\n onOpenChange: (open: boolean) => void;\n /**\n * Animation configuration for content\n */\n animation?: PopupDialogContentAnimation;\n /**\n * Whether the dialog content can be swiped to dismiss\n * @default true\n */\n isSwipeable?: boolean;\n}\n\n/**\n * Default entering animation for dialog content\n * Dialog content animates with scale and opacity transitions\n */\nconst DEFAULT_ENTERING_ANIMATION: EntryOrExitLayoutType = new Keyframe({\n 0: {\n transform: [{ scale: 0.96 }],\n opacity: 0,\n },\n 100: {\n transform: [{ scale: 1 }],\n opacity: 1,\n easing: Easing.out(Easing.ease),\n },\n}).duration(200);\n\n/**\n * Default exiting animation for dialog content\n * Mirrors the entering animation - content exits with fade out and scale down\n */\nconst DEFAULT_EXITING_ANIMATION: EntryOrExitLayoutType = new Keyframe({\n 0: {\n transform: [{ scale: 1 }],\n opacity: 1,\n },\n 100: {\n transform: [{ scale: 0.96 }],\n opacity: 0,\n easing: Easing.in(Easing.ease),\n },\n}).duration(150);\n\nexport const usePopupDialogContentAnimation = ({\n isOpen,\n progress,\n isDragging,\n isGestureReleaseAnimationRunning,\n onOpenChange,\n animation,\n isSwipeable = true,\n}: UsePopupDialogContentAnimationProps) => {\n const { height: screenHeight } = useWindowDimensions();\n\n const { isAllAnimationsDisabled } = useAnimationSettings();\n\n const { animationConfig, isAnimationDisabled } = getAnimationState(animation);\n\n const isAnimationDisabledValue = getIsAnimationDisabledValue({\n isAnimationDisabled,\n isAllAnimationsDisabled,\n });\n\n // Get entering animation value with default Keyframe animation\n const enteringValue = animationConfig?.entering ?? DEFAULT_ENTERING_ANIMATION;\n\n // Get exiting animation value with default Keyframe animation\n const exitingValue = animationConfig?.exiting ?? DEFAULT_EXITING_ANIMATION;\n\n const contentY = useSharedValue(0);\n const contentHeight = useSharedValue(0);\n const progressAnchor = useSharedValue(1);\n const contentTranslateYAnchor = useSharedValue(0);\n const contentScaleAnchor = useSharedValue(1);\n const gestureTranslationY = useSharedValue(0);\n\n useEffect(() => {\n if (isOpen) {\n progress.set(1);\n }\n }, [isOpen, progress]);\n\n const dismissKeyboard = useCallback(() => {\n Keyboard.dismiss();\n }, []);\n\n const contentTranslateY = useDerivedValue(() => {\n const maxDragDistance = screenHeight - contentY.get();\n\n if (progress.get() >= 1) {\n return interpolate(progress.get(), [1, 2], [0, maxDragDistance], Extrapolation.CLAMP);\n }\n\n const absoluteGestureTranslationY = Math.abs(gestureTranslationY.get());\n\n return interpolate(\n absoluteGestureTranslationY,\n [0, screenHeight],\n [0, -50],\n Extrapolation.CLAMP,\n );\n });\n\n const contentScale = useDerivedValue(() => {\n return interpolate(progress.get(), [1, 2], [1, 0.95], Extrapolation.CLAMP);\n });\n\n const panGesture = useMemo(\n () =>\n Gesture.Pan()\n .enabled(isSwipeable && isOpen && !isAnimationDisabledValue)\n .onStart(() => isDragging.set(true))\n .onUpdate((event) => {\n if (!isDragging.get()) return;\n\n const maxDragDistance = screenHeight - contentY.get();\n\n gestureTranslationY.set(event.translationY);\n\n if (event.translationY > 0) {\n const progressValue = 1 + event.translationY / maxDragDistance;\n progress.set(Math.max(1, Math.min(progressValue, 2)));\n } else if (event.translationY < 0) {\n const progressValue = 1 - Math.abs(event.translationY) / contentY.get();\n progress.set(Math.max(0, Math.min(progressValue, 1)));\n }\n })\n .onFinalize(() => {\n progressAnchor.set(progress.get());\n contentTranslateYAnchor.set(contentTranslateY.get());\n contentScaleAnchor.set(contentScale.get());\n\n if (progress.get() > 1.1) {\n isGestureReleaseAnimationRunning.set(true);\n scheduleOnRN(dismissKeyboard);\n progress.set(\n withSpring(\n 2,\n {\n mass: 4,\n damping: 120,\n stiffness: 900,\n overshootClamping: false,\n },\n () => {\n isGestureReleaseAnimationRunning.set(false);\n },\n ),\n );\n isDragging.set(false);\n setTimeout(() => {\n progress.set(2);\n scheduleOnRN(onOpenChange, false);\n }, 300);\n setTimeout(() => {\n progress.set(0);\n }, 350);\n } else {\n isGestureReleaseAnimationRunning.set(true);\n progress.set(\n withSpring(1, {}, () => {\n isGestureReleaseAnimationRunning.set(false);\n }),\n );\n isDragging.set(false);\n }\n }),\n [\n contentScale,\n contentScaleAnchor,\n contentTranslateY,\n contentTranslateYAnchor,\n contentY,\n isOpen,\n isDragging,\n isGestureReleaseAnimationRunning,\n isSwipeable,\n onOpenChange,\n progress,\n progressAnchor,\n screenHeight,\n isAnimationDisabledValue,\n gestureTranslationY,\n dismissKeyboard,\n ],\n );\n\n const rDragContainerStyle = useAnimatedStyle(() => {\n if (isGestureReleaseAnimationRunning.get()) {\n return {\n opacity: interpolate(progress.get(), [1, progressAnchor.get(), 1.5, 1.75], [1, 1, 1, 0]),\n transform: [\n {\n translateY: interpolate(\n progress.get(),\n [progressAnchor.get(), 1, progressAnchor.get(), progressAnchor.get() + 0.1, 2],\n [\n contentTranslateYAnchor.get(),\n 0,\n contentTranslateYAnchor.get(),\n contentTranslateYAnchor.get() + 50,\n contentTranslateYAnchor.get() - 150,\n ],\n ),\n },\n {\n scale: interpolate(\n progress.get(),\n [progressAnchor.get(), 1, progressAnchor.get(), 2],\n [contentScaleAnchor.get(), 1, contentScaleAnchor.get(), 0.75],\n ),\n },\n ],\n };\n }\n\n return {\n transform: [\n {\n translateY: contentTranslateY.get(),\n },\n {\n scale: contentScale.get(),\n },\n ],\n };\n });\n\n return {\n contentY,\n contentHeight,\n panGesture,\n rDragContainerStyle,\n entering: isAnimationDisabledValue ? undefined : enteringValue,\n exiting: isAnimationDisabledValue ? undefined : exitingValue,\n };\n};\n", "type": "registry:hook", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/hooks/use-popup-dialog-content-animation.ts" }, { "path": "registry/native-ui/src/helpers/internal/hooks/use-popup-overlay-animation.ts", "content": "import type { SharedValue } from \"react-native-reanimated\";\nimport { FadeIn, FadeOut, interpolate, useAnimatedStyle } from \"react-native-reanimated\";\nimport { useAnimationSettings } from \"../contexts/animation-settings-context\";\nimport type { PopupOverlayAnimation } from \"../types/animation\";\nimport {\n getAnimationState,\n getAnimationValueProperty,\n getIsAnimationDisabledValue,\n} from \"../utils/animation\";\n\n/**\n * Animation hook for popup overlay components (Dialog, Select, BottomSheet, Popover, etc.)\n * Handles both progress-based opacity animation and entering/exiting animations\n */\nexport function usePopupOverlayAnimation(options: {\n /** Animation progress shared value (0=idle, 1=open, 2=close) */\n progress?: SharedValue;\n /** Dragging state shared value */\n isDragging?: SharedValue;\n /** Gesture release animation running state shared value (optional, for components with swipe gestures) */\n isGestureReleaseAnimationRunning?: SharedValue;\n /** Animation configuration for overlay */\n animation?: PopupOverlayAnimation;\n}) {\n const { progress, isDragging, isGestureReleaseAnimationRunning, animation } = options;\n\n const { isAllAnimationsDisabled } = useAnimationSettings();\n\n const { animationConfig, isAnimationDisabled } = getAnimationState(animation);\n\n const isAnimationDisabledValue = getIsAnimationDisabledValue({\n isAnimationDisabled,\n isAllAnimationsDisabled,\n });\n\n // Opacity animation (progress-based, for bottom-sheet/dialog)\n const opacityValue = getAnimationValueProperty({\n animationValue: animationConfig?.opacity,\n property: \"value\",\n defaultValue: [0, 1, 0] as [number, number, number],\n });\n\n const rContainerStyle = useAnimatedStyle(() => {\n if (progress?.get() === undefined) {\n return {};\n }\n\n if (isAnimationDisabledValue) {\n return {\n opacity: progress.get() > 0 ? 1 : 0,\n };\n }\n\n if ((isDragging?.get() || isGestureReleaseAnimationRunning?.get()) && progress.get() <= 1) {\n return {\n opacity: 1,\n };\n }\n\n return {\n opacity: interpolate(progress.get(), [0, 1, 2], opacityValue),\n };\n });\n\n // Entering/exiting animations (for popover presentation)\n const enteringValue =\n animationConfig?.entering ?? (isAnimationDisabledValue ? undefined : FadeIn.duration(200));\n\n const exitingValue =\n animationConfig?.exiting ?? (isAnimationDisabledValue ? undefined : FadeOut.duration(150));\n\n return {\n /** Progress-based animated style (for bottom-sheet/dialog) */\n rContainerStyle,\n /** Entering animation (for popover presentation) */\n entering: isAnimationDisabledValue ? undefined : enteringValue,\n /** Exiting animation (for popover presentation) */\n exiting: isAnimationDisabledValue ? undefined : exitingValue,\n };\n}\n", "type": "registry:hook", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/hooks/use-popup-overlay-animation.ts" }, { "path": "registry/native-ui/src/helpers/internal/hooks/use-popup-popover-content-animation.ts", "content": "import { Easing, type EntryOrExitLayoutType, Keyframe } from \"react-native-reanimated\";\nimport { useAnimationSettings } from \"../contexts/animation-settings-context\";\nimport type { PopupPopoverContentAnimation } from \"../types/animation\";\nimport { getAnimationState, getIsAnimationDisabledValue } from \"../utils/animation\";\n\n/**\n * Placement options for popover/select content\n */\nexport type PopoverContentPlacement = \"top\" | \"bottom\" | \"left\" | \"right\";\n\n/**\n * Props for usePopupPopoverContentAnimation hook\n */\nexport interface UsePopupPopoverContentAnimationProps {\n /**\n * Placement of the popover/select content\n */\n placement: PopoverContentPlacement;\n /**\n * Alignment offset for the popover content\n */\n offset: number;\n /**\n * Animation configuration for content\n */\n animation?: PopupPopoverContentAnimation;\n}\n/**\n * Get default entering animation based on placement\n * Uses Keyframes with translateY/translateX, scale, and opacity transitions\n */\nfunction getDefaultEnteringAnimation(\n placement: PopoverContentPlacement,\n offset: number,\n): EntryOrExitLayoutType {\n const translateDistance = Math.min(offset, 12);\n\n switch (placement) {\n case \"top\":\n // Content comes from below (translateY: translateDistance -> 0)\n return new Keyframe({\n 0: {\n transform: [{ translateY: translateDistance }, { scale: 0.97 }],\n opacity: 0.25,\n },\n 100: {\n transform: [{ translateY: 0 }, { scale: 1 }],\n opacity: 1,\n easing: Easing.out(Easing.ease),\n },\n }).duration(200);\n case \"bottom\":\n // Content comes from above (translateY: -translateDistance -> 0)\n return new Keyframe({\n 0: {\n transform: [{ translateY: -translateDistance }, { scale: 0.97 }],\n opacity: 0.25,\n },\n 100: {\n transform: [{ translateY: 0 }, { scale: 1 }],\n opacity: 1,\n easing: Easing.out(Easing.ease),\n },\n }).duration(200);\n case \"left\":\n // Content comes from right (translateX: translateDistance -> 0)\n return new Keyframe({\n 0: {\n transform: [{ translateX: translateDistance }, { scale: 0.97 }],\n opacity: 0.25,\n },\n 100: {\n transform: [{ translateX: 0 }, { scale: 1 }],\n opacity: 1,\n easing: Easing.out(Easing.ease),\n },\n }).duration(200);\n case \"right\":\n // Content comes from left (translateX: -translateDistance -> 0)\n return new Keyframe({\n 0: {\n transform: [{ translateX: -translateDistance }, { scale: 0.97 }],\n opacity: 0.25,\n },\n 100: {\n transform: [{ translateX: 0 }, { scale: 1 }],\n opacity: 1,\n easing: Easing.out(Easing.ease),\n },\n }).duration(200);\n }\n}\n\n/**\n * Get default exiting animation based on placement\n * Mirrors the entering animation for each placement\n */\nfunction getDefaultExitingAnimation(\n placement: PopoverContentPlacement,\n offset: number,\n): EntryOrExitLayoutType {\n const translateDistance = Math.min(offset, 12);\n\n switch (placement) {\n case \"top\":\n // Content exits downward (translateY: 0 -> translateDistance)\n return new Keyframe({\n 0: {\n transform: [{ translateY: 0 }, { scale: 1 }],\n opacity: 1,\n },\n 100: {\n transform: [{ translateY: translateDistance }, { scale: 0.97 }],\n opacity: 0,\n easing: Easing.out(Easing.ease),\n },\n }).duration(150);\n case \"bottom\":\n // Content exits upward (translateY: 0 -> -translateDistance)\n return new Keyframe({\n 0: {\n transform: [{ translateY: 0 }, { scale: 1 }],\n opacity: 1,\n },\n 100: {\n transform: [{ translateY: -translateDistance }, { scale: 0.97 }],\n opacity: 0,\n easing: Easing.out(Easing.ease),\n },\n }).duration(150);\n case \"left\":\n // Content exits rightward (translateX: 0 -> translateDistance)\n return new Keyframe({\n 0: {\n transform: [{ translateX: 0 }, { scale: 1 }],\n opacity: 1,\n },\n 100: {\n transform: [{ translateX: translateDistance }, { scale: 0.97 }],\n opacity: 0,\n easing: Easing.out(Easing.ease),\n },\n }).duration(150);\n case \"right\":\n // Content exits leftward (translateX: 0 -> -translateDistance)\n return new Keyframe({\n 0: {\n transform: [{ translateX: 0 }, { scale: 1 }],\n opacity: 1,\n },\n 100: {\n transform: [{ translateX: -translateDistance }, { scale: 0.97 }],\n opacity: 0,\n easing: Easing.out(Easing.ease),\n },\n }).duration(150);\n }\n}\n\n/**\n * Animation hook for popover/select content components\n * Returns entering and exiting animations based on configuration and placement\n */\nexport function usePopupPopoverContentAnimation({\n placement,\n offset,\n animation,\n}: UsePopupPopoverContentAnimationProps) {\n const { isAllAnimationsDisabled } = useAnimationSettings();\n\n const { animationConfig, isAnimationDisabled } = getAnimationState(animation);\n\n const isAnimationDisabledValue = getIsAnimationDisabledValue({\n isAnimationDisabled,\n isAllAnimationsDisabled,\n });\n\n // Get entering animation value with default based on placement\n const enteringValue = animationConfig?.entering ?? getDefaultEnteringAnimation(placement, offset);\n\n // Get exiting animation value with default based on placement\n const exitingValue = animationConfig?.exiting ?? getDefaultExitingAnimation(placement, offset);\n\n // Return entering and exiting animations\n // If animations are disabled, return undefined to disable animations\n return {\n entering: isAnimationDisabledValue ? undefined : enteringValue,\n exiting: isAnimationDisabledValue ? undefined : exitingValue,\n };\n}\n", "type": "registry:hook", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/hooks/use-popup-popover-content-animation.ts" }, { "path": "registry/native-ui/src/helpers/internal/hooks/use-popup-root-animation.ts", "content": "import { useSharedValue } from \"react-native-reanimated\";\nimport type { AnimationRootDisableAll } from \"../types/animation\";\nimport { useCombinedAnimationDisabledState } from \"./use-combined-animation-disabled-state\";\n\n/**\n * Root animation hook for popup-like components (Dialog, Select, etc.)\n * Manages component state transitions and animation coordination\n */\nexport function usePopupRootAnimation(options: { animation?: AnimationRootDisableAll }) {\n const isAllAnimationsDisabled = useCombinedAnimationDisabledState(options.animation);\n\n const progress = useSharedValue(0);\n const isDragging = useSharedValue(false);\n const isGestureReleaseAnimationRunning = useSharedValue(false);\n\n return {\n isAllAnimationsDisabled,\n progress,\n isDragging,\n isGestureReleaseAnimationRunning,\n };\n}\n", "type": "registry:hook", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/hooks/use-popup-root-animation.ts" }, { "path": "registry/native-ui/src/helpers/internal/hooks/use-relative-position.ts", "content": "import * as React from \"react\";\nimport { Dimensions, type LayoutRectangle, type ScaledSize } from \"react-native\";\nimport type { Insets } from \"../types\";\n\ntype UseRelativePositionArgs = Omit<\n GetContentStyleArgs,\n \"triggerPosition\" | \"contentLayout\" | \"dimensions\"\n> & {\n triggerPosition: LayoutPosition | null;\n contentLayout: LayoutRectangle | null;\n disablePositioningStyle?: boolean;\n};\n\nexport function useRelativePosition({\n align,\n avoidCollisions,\n triggerPosition,\n contentLayout,\n alignOffset,\n insets,\n offset,\n placement,\n disablePositioningStyle,\n}: UseRelativePositionArgs) {\n const dimensions = Dimensions.get(\"screen\");\n\n return React.useMemo(() => {\n if (disablePositioningStyle) {\n return {};\n }\n if (!triggerPosition || !contentLayout) {\n return {\n position: \"absolute\",\n opacity: 0,\n top: dimensions.height,\n } as const;\n }\n return getContentStyle({\n align,\n avoidCollisions,\n contentLayout,\n placement,\n triggerPosition,\n alignOffset,\n insets,\n offset,\n dimensions,\n });\n }, [\n align,\n avoidCollisions,\n placement,\n alignOffset,\n insets,\n triggerPosition,\n contentLayout,\n dimensions,\n disablePositioningStyle,\n offset,\n ]);\n}\n\nexport interface LayoutPosition {\n pageY: number;\n pageX: number;\n width: number;\n height: number;\n}\n\ninterface GetPositionArgs {\n dimensions: ScaledSize;\n avoidCollisions: boolean;\n triggerPosition: LayoutPosition;\n contentLayout: LayoutRectangle;\n insets?: Insets;\n}\n\ninterface GetSidePositionArgs extends GetPositionArgs {\n placement: \"top\" | \"bottom\" | \"left\" | \"right\";\n offset: number;\n}\n\nfunction getSidePosition({\n placement,\n triggerPosition,\n contentLayout,\n offset,\n insets,\n avoidCollisions,\n dimensions,\n}: GetSidePositionArgs) {\n const insetTop = insets?.top ?? 0;\n const insetBottom = insets?.bottom ?? 0;\n const insetLeft = insets?.left ?? 0;\n const insetRight = insets?.right ?? 0;\n\n // Handle vertical sides (top/bottom)\n if (placement === \"top\" || placement === \"bottom\") {\n const positionTop = triggerPosition?.pageY - offset - contentLayout.height;\n const positionBottom = triggerPosition.pageY + triggerPosition.height + offset;\n\n if (!avoidCollisions) {\n return {\n top: placement === \"top\" ? positionTop : positionBottom,\n };\n }\n\n if (placement === \"top\") {\n return {\n top: Math.min(\n Math.max(insetTop, positionTop),\n dimensions.height - insetBottom - contentLayout.height,\n ),\n };\n }\n\n return {\n top: Math.min(dimensions.height - insetBottom - contentLayout.height, positionBottom),\n };\n }\n\n // Handle horizontal sides (left/right)\n const maxContentWidth = dimensions.width - insetLeft - insetRight;\n const contentWidth = Math.min(contentLayout.width, maxContentWidth);\n\n const positionLeft = triggerPosition.pageX - offset - contentWidth;\n const positionRight = triggerPosition.pageX + triggerPosition.width + offset;\n\n if (!avoidCollisions) {\n return {\n left: placement === \"left\" ? positionLeft : positionRight,\n };\n }\n\n if (placement === \"left\") {\n return {\n left: Math.min(\n Math.max(insetLeft, positionLeft),\n dimensions.width - insetRight - contentWidth,\n ),\n };\n }\n\n // For right placement, ensure content doesn't go beyond left inset\n return {\n left: Math.max(\n insetLeft,\n Math.min(dimensions.width - insetRight - contentWidth, positionRight),\n ),\n };\n}\n\ninterface GetAlignPositionArgs extends GetPositionArgs {\n align: \"start\" | \"center\" | \"end\";\n alignOffset: number;\n placement: \"top\" | \"bottom\" | \"left\" | \"right\";\n}\n\nfunction getAlignPosition({\n align,\n avoidCollisions,\n contentLayout,\n triggerPosition,\n alignOffset,\n insets,\n dimensions,\n placement,\n}: GetAlignPositionArgs) {\n const insetLeft = insets?.left ?? 0;\n const insetRight = insets?.right ?? 0;\n const insetTop = insets?.top ?? 0;\n const insetBottom = insets?.bottom ?? 0;\n\n // For top/bottom sides, align horizontally\n if (placement === \"top\" || placement === \"bottom\") {\n const maxContentWidth = dimensions.width - insetLeft - insetRight;\n const contentWidth = Math.min(contentLayout.width, maxContentWidth);\n\n let left = getHorizontalAlignPosition(\n align,\n triggerPosition.pageX,\n triggerPosition.width,\n contentWidth,\n alignOffset,\n insetLeft,\n insetRight,\n dimensions,\n );\n\n if (avoidCollisions) {\n const doesCollide = left < insetLeft || left + contentWidth > dimensions.width - insetRight;\n if (doesCollide) {\n const spaceLeft = left - insetLeft;\n const spaceRight = dimensions.width - insetRight - (left + contentWidth);\n\n if (spaceLeft > spaceRight && spaceLeft >= contentWidth) {\n left = insetLeft;\n } else if (spaceRight >= contentWidth) {\n left = dimensions.width - insetRight - contentWidth;\n } else {\n const centeredPosition = Math.max(\n insetLeft,\n (dimensions.width - contentWidth - insetRight) / 2,\n );\n left = centeredPosition;\n }\n }\n }\n\n return { left, maxWidth: maxContentWidth };\n }\n\n // For left/right sides, align vertically and constrain width\n const maxContentHeight = dimensions.height - insetTop - insetBottom;\n const maxContentWidth = dimensions.width - insetLeft - insetRight;\n const contentHeight = Math.min(contentLayout.height, maxContentHeight);\n\n let top = getVerticalAlignPosition(\n align,\n triggerPosition.pageY,\n triggerPosition.height,\n contentHeight,\n alignOffset,\n insetTop,\n insetBottom,\n dimensions,\n );\n\n if (avoidCollisions) {\n const doesCollide = top < insetTop || top + contentHeight > dimensions.height - insetBottom;\n if (doesCollide) {\n const spaceTop = top - insetTop;\n const spaceBottom = dimensions.height - insetBottom - (top + contentHeight);\n\n if (spaceTop > spaceBottom && spaceTop >= contentHeight) {\n top = insetTop;\n } else if (spaceBottom >= contentHeight) {\n top = dimensions.height - insetBottom - contentHeight;\n } else {\n const centeredPosition = Math.max(\n insetTop,\n (dimensions.height - contentHeight - insetBottom) / 2,\n );\n top = centeredPosition;\n }\n }\n }\n\n return { top, maxHeight: maxContentHeight, maxWidth: maxContentWidth };\n}\n\nfunction getHorizontalAlignPosition(\n align: \"start\" | \"center\" | \"end\",\n triggerPageX: number,\n triggerWidth: number,\n contentWidth: number,\n alignOffset: number,\n insetLeft: number,\n insetRight: number,\n dimensions: ScaledSize,\n) {\n let left = 0;\n if (align === \"start\") {\n left = triggerPageX;\n }\n if (align === \"center\") {\n left = triggerPageX + triggerWidth / 2 - contentWidth / 2;\n }\n if (align === \"end\") {\n left = triggerPageX + triggerWidth - contentWidth;\n }\n return Math.max(\n insetLeft,\n Math.min(left + alignOffset, dimensions.width - contentWidth - insetRight),\n );\n}\n\nfunction getVerticalAlignPosition(\n align: \"start\" | \"center\" | \"end\",\n triggerPageY: number,\n triggerHeight: number,\n contentHeight: number,\n alignOffset: number,\n insetTop: number,\n insetBottom: number,\n dimensions: ScaledSize,\n) {\n let top = 0;\n if (align === \"start\") {\n top = triggerPageY;\n }\n if (align === \"center\") {\n top = triggerPageY + triggerHeight / 2 - contentHeight / 2;\n }\n if (align === \"end\") {\n top = triggerPageY + triggerHeight - contentHeight;\n }\n return Math.max(\n insetTop,\n Math.min(top + alignOffset, dimensions.height - contentHeight - insetBottom),\n );\n}\n\ntype GetContentStyleArgs = GetPositionArgs & GetSidePositionArgs & GetAlignPositionArgs;\n\nfunction getContentStyle({\n align,\n avoidCollisions,\n contentLayout,\n placement,\n triggerPosition,\n alignOffset,\n insets,\n offset,\n dimensions,\n}: GetContentStyleArgs) {\n return Object.assign(\n { position: \"absolute\" } as const,\n getSidePosition({\n placement,\n triggerPosition,\n contentLayout,\n offset,\n insets,\n avoidCollisions,\n dimensions,\n }),\n getAlignPosition({\n align,\n avoidCollisions,\n triggerPosition,\n contentLayout,\n alignOffset,\n insets,\n dimensions,\n placement,\n }),\n );\n}\n", "type": "registry:hook", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/hooks/use-relative-position.ts" }, { "path": "registry/native-ui/src/helpers/internal/hooks/use-resolved-style-property.ts", "content": "import { useMemo } from \"react\";\nimport type { ImageStyle, StyleProp, TextStyle, ViewStyle } from \"react-native\";\nimport { StyleSheet } from \"react-native\";\nimport { useResolveClassNames } from \"uniwind\";\n\n/**\n * Combined style type from React Native\n */\ntype Style = ViewStyle | TextStyle | ImageStyle;\n\n/**\n * Parameters for single property resolution\n */\ninterface UseResolvedStylePropertyParamsSingle {\n /** The className string to resolve styles from */\n className?: string;\n /** The style prop (can be object, array, or null) */\n style?: StyleProp | StyleProp | StyleProp;\n /** The name of the style property to resolve */\n propertyName: K;\n}\n\n/**\n * Parameters for multiple properties resolution\n */\ninterface UseResolvedStylePropertyParamsMultiple {\n /** The className string to resolve styles from */\n className?: string;\n /** The style prop (can be object, array, or null) */\n style?: StyleProp | StyleProp | StyleProp;\n /** Array of style property names to resolve */\n propertyNames: readonly K[];\n}\n\n/**\n * A hook that resolves specific style properties from both className and style props.\n * The style prop takes precedence over className.\n *\n * This is useful when you need to extract specific style values (like width, height)\n * that might come from either Tailwind classes or inline styles.\n *\n * @param params - Configuration object with className, style, and propertyName(s)\n * @returns The resolved style property value(s) or undefined if not found\n *\n * @example Single property\n * ```tsx\n * const width = useResolvedStyleProperty({\n * className: 'w-10 h-8',\n * style: { width: 50 },\n * propertyName: 'width',\n * });\n * // Returns: 50 (from style, takes precedence)\n * ```\n *\n * @example Multiple properties\n * ```tsx\n * const [width, left] = useResolvedStyleProperty({\n * className: 'w-10 left-2',\n * propertyNames: ['width', 'left'],\n * });\n * // Returns: [40, 8] (from className)\n * ```\n */\nfunction useResolvedStyleProperty(\n params: UseResolvedStylePropertyParamsSingle,\n): Style[K] | undefined;\nfunction useResolvedStyleProperty(\n params: UseResolvedStylePropertyParamsMultiple,\n): (Style[K] | undefined)[];\nfunction useResolvedStyleProperty(\n params: UseResolvedStylePropertyParamsSingle | UseResolvedStylePropertyParamsMultiple,\n): Style[K] | undefined | (Style[K] | undefined)[] {\n const { className, style } = params;\n\n const resolvedClassName = useResolveClassNames(className ?? \"\");\n const resolvedStyle = useMemo(() => (style ? StyleSheet.flatten(style) : undefined), [style]);\n\n return useMemo(() => {\n // Check if we're resolving multiple properties\n if (\"propertyNames\" in params) {\n return params.propertyNames.map((propertyName) => {\n // Style prop takes precedence over className\n if (resolvedStyle && propertyName in resolvedStyle) {\n return resolvedStyle[propertyName];\n }\n\n // Fall back to className-resolved styles\n if (resolvedClassName && propertyName in resolvedClassName) {\n return resolvedClassName[propertyName];\n }\n\n return undefined;\n });\n }\n\n // Single property resolution\n const propertyName = params.propertyName;\n\n // Style prop takes precedence over className\n if (resolvedStyle && propertyName in resolvedStyle) {\n return resolvedStyle[propertyName];\n }\n\n // Fall back to className-resolved styles\n if (resolvedClassName && propertyName in resolvedClassName) {\n return resolvedClassName[propertyName];\n }\n\n return undefined;\n }, [resolvedStyle, resolvedClassName, params]);\n}\n\nexport { useResolvedStyleProperty };\n", "type": "registry:hook", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/hooks/use-resolved-style-property.ts" }, { "path": "registry/native-ui/src/helpers/internal/types/animation.ts", "content": "import type {\n BaseAnimationBuilder,\n EntryOrExitLayoutType,\n LayoutAnimationFunction,\n WithSpringConfig,\n WithTimingConfig,\n} from \"react-native-reanimated\";\n\n/**\n * Universal animation prop type\n * - `true` or `undefined`: Use default animations\n * - `false` or `\"disabled\"`: Disable all animations\n * - `object`: Custom animation configuration\n * - Can include `state?: 'disabled' | boolean | undefined` to disable animations while customizing properties\n */\nexport type Animation = Record> =\n | boolean\n | \"disabled\"\n | (TConfig & { state?: \"disabled\" | boolean });\n\nexport type AnimationDisabled = \"disabled\" | false;\n\n/**\n * Root-level animation prop type with cascading control\n * - `true` or `undefined`: Use default animations\n * - `false` or `\"disabled\"`: Disable only root animations (children can still animate)\n * - `\"disable-all\"`: Disable all animations including children (cascades down)\n * - `object`: Custom animation configuration\n * - Can include `state?: 'disabled' | 'disable-all' | boolean` to disable animations while customizing properties\n */\nexport type AnimationRoot = Record> =\n | boolean\n | \"disabled\"\n | \"disable-all\"\n | (TConfig & { state?: \"disabled\" | \"disable-all\" | boolean });\n\nexport type AnimationRootDisableAll = Extract;\n\n/**\n * Animation value that can be a custom config\n * Used for granular animation control within a component\n */\nexport type AnimationValue = Record> = TConfig;\n\nexport type LayoutTransition =\n | BaseAnimationBuilder\n | LayoutAnimationFunction\n | typeof BaseAnimationBuilder\n | undefined;\n\n/**\n * Spring animation configuration\n */\nexport interface SpringAnimationConfig {\n type: \"spring\";\n config?: WithSpringConfig;\n}\n\n/**\n * Timing animation configuration\n */\nexport interface TimingAnimationConfig {\n type: \"timing\";\n config?: WithTimingConfig;\n}\n\n/**\n * Animation configuration for popup overlay components (Dialog, Select, BottomSheet, Popover, etc.)\n * Supports both progress-based opacity animation and entering/exiting animations\n */\nexport type PopupOverlayAnimation = Animation<{\n /**\n * Opacity animation configuration (progress-based)\n * Takes effect for bottom-sheet/dialog presentation\n * @default [0, 1, 0] - opacity values for [idle, open, close] states\n */\n opacity?: AnimationValue<{\n /**\n * Opacity values [idle, open, close]\n * @default [0, 1, 0]\n */\n value?: [number, number, number];\n }>;\n /**\n * Takes effect for popover presentation\n * @default FadeIn with duration 200ms\n */\n entering?: EntryOrExitLayoutType;\n /**\n * Takes effect for popover presentation\n * @default FadeOut with duration 150ms\n */\n exiting?: EntryOrExitLayoutType;\n}>;\n\n/**\n * Animation configuration for popup dialog content components (Dialog, Select dialog presentation)\n * Supports opacity and scale animations\n */\nexport type PopupDialogContentAnimation = Animation<{\n /**\n * Custom Keyframe animation for entering transition\n * @default Keyframe with scale, and opacity (200ms)\n */\n entering?: EntryOrExitLayoutType;\n /**\n * Custom Keyframe animation for exiting transition\n * @default Keyframe mirroring entering animation (150ms)\n */\n exiting?: EntryOrExitLayoutType;\n}>;\n\n/**\n * Animation configuration for popup popover content components (Popover, Select popover presentation)\n * Supports custom Keyframe animations for entering and exiting transitions\n */\nexport type PopupPopoverContentAnimation = Animation<{\n /**\n * Custom Keyframe animation for entering transition\n * @default Keyframe with translateY/translateX, scale, and opacity (200ms)\n */\n entering?: EntryOrExitLayoutType;\n /**\n * Custom Keyframe animation for exiting transition\n * @default Keyframe mirroring entering animation (150ms)\n */\n exiting?: EntryOrExitLayoutType;\n}>;\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/types/animation.ts" }, { "path": "registry/native-ui/src/helpers/internal/types/bottom-sheet.ts", "content": "import type { BottomSheetViewProps } from \"@gorhom/bottom-sheet/lib/typescript/components/bottomSheetView/types\";\nimport type { ReactNode } from \"react\";\nimport type { SharedValue } from \"react-native-reanimated\";\nimport type { AnimationDisabled } from \"./animation\";\n\n/**\n * State type for bottom sheet content container animation coordination\n */\nexport type BottomSheetContentContainerState = \"idle\" | \"open\" | \"close\";\n\n/**\n * Props for the reusable BottomSheetContentContainer component\n */\nexport interface BottomSheetContentContainerProps {\n /**\n * The content to be rendered inside the container\n */\n children?: ReactNode;\n /**\n * Additional CSS class for the content container\n */\n contentContainerClassName?: string;\n /**\n * Props for the content container\n */\n contentContainerProps?: Omit;\n /**\n * Whether the bottom sheet is open\n */\n isOpen: boolean;\n /**\n * Animation progress shared value (0=idle, 1=open, 2=close)\n */\n progress: SharedValue;\n /**\n * Whether the bottom sheet is dragging\n */\n isDragging: SharedValue;\n /**\n * Whether the bottom sheet is pan activated\n */\n isPanActivated: SharedValue;\n /**\n * Whether the bottom sheet is closing on swipe\n */\n isClosingOnSwipe: SharedValue;\n /**\n * Initial index of the bottom sheet\n */\n initialIndex: number;\n /**\n * Callback when the bottom sheet is opened\n */\n onOpenChange: (open: boolean) => void;\n /**\n * Whether the bottom sheet can be closed by panning down\n */\n enablePanDownToClose: boolean;\n}\n\n/**\n * Base props shared across BottomSheet Content components\n * Used by BottomSheet, Popover, and Select components when using bottom-sheet presentation\n */\nexport interface BaseBottomSheetContentProps {\n /**\n * The bottom sheet content\n */\n children?: ReactNode;\n /**\n * Additional CSS class for the bottom sheet\n */\n className?: string;\n /**\n * Additional CSS class for the container\n */\n containerClassName?: string;\n /**\n * Additional CSS class for the content container\n */\n contentContainerClassName?: string;\n /**\n * Additional CSS class for the background\n */\n backgroundClassName?: string;\n /**\n * Additional CSS class for the handle\n */\n handleClassName?: string;\n /**\n * Additional CSS class for the handle indicator\n */\n handleIndicatorClassName?: string;\n /**\n * Props for the content container\n */\n contentContainerProps?: Omit;\n /**\n * Animation configuration for bottom sheet content\n * - `false` or `\"disabled\"`: Disable all animations\n */\n animation?: AnimationDisabled;\n}\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/types/bottom-sheet.ts" }, { "path": "registry/native-ui/src/helpers/internal/types/index.ts", "content": "export * from \"./animation\";\nexport * from \"./bottom-sheet\";\nexport * from \"./misc\";\nexport * from \"./primitives\";\nexport * from \"./theme\";\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/types/index.ts" }, { "path": "registry/native-ui/src/helpers/internal/types/misc.ts", "content": "type ReactChild =\n | string\n | number\n | bigint\n | React.ReactElement>\n | Iterable\n | React.ReactPortal\n | Promise;\n\nexport type { ReactChild };\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/types/misc.ts" }, { "path": "registry/native-ui/src/helpers/internal/types/primitives.ts", "content": "import type { Pressable, Text, View, ViewStyle } from \"react-native\";\n\n// Base utility types\n\n/**\n * Component props with optional asChild prop for polymorphic components\n * Allows components to render as different elements when asChild is true\n */\ntype ComponentPropsWithAsChild> =\n React.ComponentPropsWithoutRef & { asChild?: boolean };\n\n// Ref types\n\n/**\n * Reference type for React Native View component\n * Used for forwarding refs to View elements\n */\ntype ViewRef = React.ComponentRef;\n\n/**\n * Reference type for React Native Pressable component\n * Used for forwarding refs to Pressable elements\n */\ntype PressableRef = React.ComponentRef;\n\n/**\n * Reference type for React Native Text component\n * Used for forwarding refs to Text elements\n */\ntype TextRef = React.ComponentRef;\n\n// Slottable component props\n\n/**\n * View component props with asChild support for slot composition\n * Enables View components to be used with the Slot pattern\n */\ntype SlottableViewProps = ComponentPropsWithAsChild;\n\n/**\n * Pressable component props with asChild support for slot composition\n * Enables Pressable components to be used with the Slot pattern\n */\ntype SlottablePressableProps = ComponentPropsWithAsChild;\n\n/**\n * Text component props with asChild support for slot composition\n * Enables Text components to be used with the Slot pattern\n */\ntype SlottableTextProps = ComponentPropsWithAsChild;\n\n/**\n * Interface for components that can be force mounted even when normally hidden\n */\ninterface ForceMountable {\n /**\n * Whether to force mount the component in the DOM\n * Useful for animation purposes when component needs to be present but hidden\n */\n forceMount?: true | undefined;\n}\n\n/**\n * Interface for defining spacing/padding from screen edges\n */\ninterface Insets {\n /**\n * Distance from the top edge in pixels\n */\n top?: number;\n /**\n * Distance from the bottom edge in pixels\n */\n bottom?: number;\n /**\n * Distance from the left edge in pixels\n */\n left?: number;\n /**\n * Distance from the right edge in pixels\n */\n right?: number;\n}\n\n/**\n * Props for components that need to be positioned relative to a trigger element\n * Certain props are only available on the native version of the component.\n * @docs For the web version, see the Radix documentation https://www.radix-ui.com/primitives\n */\ninterface PositionedContentProps {\n /**\n * Whether to force mount the component in the DOM\n */\n forceMount?: true | undefined;\n /**\n * Custom styles to apply to the positioned content\n */\n style?: ViewStyle;\n /**\n * Offset along the alignment axis in pixels\n */\n alignOffset?: number;\n /**\n * Screen edge insets to respect when positioning\n */\n insets?: Insets;\n /**\n * Whether to automatically adjust position to avoid screen edges\n * @default true\n */\n avoidCollisions?: boolean;\n /**\n * Alignment relative to the trigger element\n * @default 'start'\n */\n align?: \"start\" | \"center\" | \"end\";\n /**\n * Preferred placement of the trigger element to position against\n * @default 'bottom'\n */\n placement?: \"top\" | \"bottom\" | \"left\" | \"right\";\n /**\n * Offset from the trigger element in pixels\n * @default 0\n */\n offset?: number;\n /**\n * Whether to disable the automatic positioning styles\n * Useful when you want to handle positioning manually\n * @default false\n */\n disablePositioningStyle?: boolean;\n}\n\nexport type {\n ComponentPropsWithAsChild,\n ForceMountable,\n Insets,\n PositionedContentProps,\n PressableRef,\n SlottablePressableProps,\n SlottableTextProps,\n SlottableViewProps,\n TextRef,\n ViewRef,\n};\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/types/primitives.ts" }, { "path": "registry/native-ui/src/helpers/internal/types/theme.ts", "content": "import type { ClassValue } from \"tailwind-variants\";\n\n/**\n * This Typescript utility transform a list of slots into a list of {slot: classes}\n */\ntype ElementSlots = {\n [key in S]?: Exclude;\n};\n\n/**\n * Type helper that preserves the exact type of combined style objects\n * This ensures that VariantProps inference works correctly for each style\n */\ntype CombinedStyles> = {\n [K in keyof T]: T[K];\n};\n\nexport type { CombinedStyles, ElementSlots };\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/types/theme.ts" }, { "path": "registry/native-ui/src/helpers/internal/utils/animation.ts", "content": "import type { Animation, AnimationRoot, AnimationValue } from \"../types/animation\";\n\n/**\n * Check if the entire animation is disabled\n * @param animation - Animation configuration\n * @returns true if animation is disabled\n */\nexport function isAnimationDisabled>(\n animation: Animation | AnimationRoot | undefined,\n): boolean {\n // Check top-level disabled values\n if (animation === false || animation === \"disabled\") {\n return true;\n }\n\n // Check state property in config objects\n if (typeof animation === \"object\" && animation !== null && \"state\" in animation) {\n const state = animation.state;\n return state === false || state === \"disabled\";\n }\n\n return false;\n}\n\n/**\n * Check if root animation should cascade disable to all children\n * @param animation - Root animation configuration\n * @returns true if all animations should be disabled (including children)\n */\nexport function shouldDisableAll>(\n animation: AnimationRoot | undefined,\n): boolean {\n // Check top-level disable-all value\n if (animation === \"disable-all\") {\n return true;\n }\n\n // Check state property in config objects\n if (typeof animation === \"object\" && animation !== null && \"state\" in animation) {\n const state = animation.state;\n return state === \"disable-all\";\n }\n\n return false;\n}\n\n/**\n * Get animation state including config and disabled status\n * @param animation - Animation configuration\n * @returns Object with animationConfig and isAnimationDisabled\n */\nexport function getAnimationState>(\n animation: Animation | undefined,\n): {\n animationConfig: TConfig | undefined;\n isAnimationDisabled: boolean;\n} {\n const isDisabled = isAnimationDisabled(animation);\n // Always extract config when it's an object, regardless of disabled state\n // This allows users to customize colors/properties even when animations are disabled\n const config =\n typeof animation === \"object\" && animation !== null ? (animation as TConfig) : undefined;\n\n return {\n animationConfig: config,\n isAnimationDisabled: isDisabled,\n };\n}\n\n/**\n * Get root animation state including config, disabled status, and cascade flag\n * @param animation - Root animation configuration\n * @returns Object with animationConfig, isAnimationDisabled, and isAllAnimationsDisabled\n */\nexport function getRootAnimationState>(\n animation: AnimationRoot | undefined,\n): {\n animationConfig: TConfig | undefined;\n isAnimationDisabled: boolean;\n isAllAnimationsDisabled: boolean;\n} {\n const shouldCascade = shouldDisableAll(animation);\n const isDisabled = isAnimationDisabled(animation) || shouldCascade;\n // Always extract config when it's an object, regardless of disabled state\n // This allows users to customize colors/properties even when animations are disabled\n const config =\n typeof animation === \"object\" && animation !== null ? (animation as TConfig) : undefined;\n\n return {\n animationConfig: config,\n isAnimationDisabled: isDisabled,\n isAllAnimationsDisabled: shouldCascade,\n };\n}\n\n/**\n * Get animation value property or return default\n * Extracts a property from the animation value config object\n *\n * @param options - Object containing animationValue, property, and defaultValue\n * @param options.animationValue - The animation value configuration\n * @param options.property - Property name to extract\n * @param options.defaultValue - Default value if property is not found\n * @returns The property value or default (never undefined)\n *\n * @example\n * const scaleValue = getAnimationValueProperty({\n * animationValue: animation?.scale,\n * property: 'value',\n * defaultValue: 0.95\n * });\n */\nexport function getAnimationValueProperty<\n TConfig extends Record,\n K extends keyof TConfig,\n D extends NonNullable,\n>(options: {\n animationValue: AnimationValue | undefined;\n property: K;\n defaultValue: D;\n}): NonNullable {\n // If animation value is undefined, return default\n if (options.animationValue === undefined) {\n return options.defaultValue;\n }\n\n // Return the property value if it exists, otherwise return default\n return (options.animationValue[options.property] ?? options.defaultValue) as NonNullable<\n TConfig[K]\n >;\n}\n\n/**\n * Get animation value merged config or return default\n * Merges the animation value config with defaults, useful when you need multiple properties\n *\n * @param options - Object containing animationValue, property, and defaultValue\n * @param options.animationValue - The animation value configuration\n * @param options.property - Property name to extract from the config\n * @param options.defaultValue - Default configuration object\n * @returns The merged config object or default\n *\n * @example\n * const scaleConfig = getAnimationValueMergedConfig({\n * animationValue: animation?.scale,\n * property: 'timingConfig',\n * defaultValue: { duration: 150 }\n * });\n */\nexport function getAnimationValueMergedConfig<\n TConfig extends Record,\n K extends keyof TConfig,\n>(options: {\n animationValue: AnimationValue | undefined;\n property: K;\n defaultValue: TConfig[K];\n}): TConfig[K] {\n // If animation value is undefined, return default\n if (options.animationValue === undefined) {\n return options.defaultValue;\n }\n\n const value = options.animationValue[options.property];\n\n // If the specific property value is undefined or not an object, return default\n if (value === undefined || typeof value !== \"object\") {\n return options.defaultValue;\n }\n\n // Merge with defaults to ensure all properties exist\n return { ...options.defaultValue, ...value };\n}\n\n/**\n * Determine if animations should be disabled based on disabled flags\n * Priority: isAllAnimationsDisabled > isAnimationDisabled\n *\n * @param options - Object containing isAnimationDisabled and isAllAnimationsDisabled\n * @param options.isAnimationDisabled - Whether animation is explicitly disabled\n * @param options.isAllAnimationsDisabled - Whether all animations should be disabled (cascading from root/global)\n * @returns true if animations should be disabled, false otherwise\n *\n * @example\n * const isDisabled = getIsAnimationDisabledValue({\n * isAnimationDisabled: false,\n * isAllAnimationsDisabled: true\n * });\n * // Returns: true (all animations disabled takes priority)\n */\nexport function getIsAnimationDisabledValue(options: {\n isAnimationDisabled: boolean;\n isAllAnimationsDisabled: boolean | undefined;\n}): boolean {\n const { isAnimationDisabled: isDisabled, isAllAnimationsDisabled } = options;\n\n // First priority: if all animations are disabled, return true\n if (isAllAnimationsDisabled === true) {\n return true;\n }\n\n // Second priority: if this animation is disabled, return true\n if (isDisabled) {\n return true;\n }\n\n // Default: animations are enabled\n return false;\n}\n\n/**\n * Combine global, parent, and own animation disabled states\n * Priority: Global > Parent > Own (global wins if enabled)\n *\n * @param options - Object containing globalIsAllAnimationsDisabled, parentIsAllAnimationsDisabled, and ownIsAllAnimationsDisabled\n * @param options.globalIsAllAnimationsDisabled - Whether global provider has disable-all (from GlobalAnimationSettingsProvider)\n * @param options.parentIsAllAnimationsDisabled - Whether parent context has disable-all (from AnimationSettingsContext)\n * @param options.ownIsAllAnimationsDisabled - Whether own animation prop has disable-all\n * @returns Combined isAllAnimationsDisabled value (global || parent || own)\n *\n * @example\n * const combined = getCombinedAnimationDisabledState({\n * globalIsAllAnimationsDisabled: true,\n * parentIsAllAnimationsDisabled: false,\n * ownIsAllAnimationsDisabled: false\n * });\n * // Returns: true (global wins)\n */\nexport function getCombinedAnimationDisabledState(options: {\n globalIsAllAnimationsDisabled?: boolean;\n parentIsAllAnimationsDisabled: boolean | undefined;\n ownIsAllAnimationsDisabled: boolean;\n}): boolean {\n const {\n globalIsAllAnimationsDisabled,\n parentIsAllAnimationsDisabled,\n ownIsAllAnimationsDisabled,\n } = options;\n\n // Global always wins if it has disable-all\n if (globalIsAllAnimationsDisabled === true) {\n return true;\n }\n\n // Parent wins if it has disable-all\n if (parentIsAllAnimationsDisabled === true) {\n return true;\n }\n\n // Otherwise use own value\n return ownIsAllAnimationsDisabled;\n}\n", "type": "registry:lib", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/utils/animation.ts" }, { "path": "registry/native-ui/src/helpers/internal/utils/children-to-string.ts", "content": "import { Children, isValidElement, type ReactNode } from \"react\";\nimport type { SharedValue } from \"react-native-reanimated\";\n\n/**\n * Recursively checks if children contain any React elements.\n * Used to determine if children can be stringified.\n *\n * @param children - React children to check\n * @returns True if children contain React elements, false otherwise\n */\nfunction hasReactElements(children: ReactNode): boolean {\n if (children == null || typeof children === \"boolean\") {\n return false;\n }\n\n if (isValidElement(children)) {\n return true;\n }\n\n if (Array.isArray(children)) {\n return children.some((child) => hasReactElements(child));\n }\n\n return false;\n}\n\n/**\n * Converts React children to a string representation.\n * Handles cases where children might be an array of mixed types (strings, numbers, variables).\n *\n * @param children - React children that might be string, number, array, or React elements\n * @returns A string representation of the children or null if not convertible\n */\nexport function childrenToString(children: ReactNode | SharedValue): string | null {\n // Handle null/undefined\n if (children == null) {\n return null;\n }\n\n // Handle string directly\n if (typeof children === \"string\") {\n return children;\n }\n\n // Handle number\n if (typeof children === \"number\") {\n return String(children);\n }\n\n // Handle boolean (usually we don't want to render true/false as text)\n if (typeof children === \"boolean\") {\n return null;\n }\n\n // Check if children is a React element - if so, cannot be stringified\n if (isValidElement(children)) {\n return null;\n }\n\n // Handle array of children (e.g., {someVar} text)\n if (Array.isArray(children)) {\n // Check if array contains any React elements - if so, cannot be stringified\n // This handles cases where conditional children create arrays with React elements\n if (hasReactElements(children)) {\n return null;\n }\n\n const stringified = children\n .map((child) => {\n // Recursively handle each child\n if (typeof child === \"string\" || typeof child === \"number\") {\n return String(child);\n }\n // Skip booleans, null, undefined\n if (child == null || typeof child === \"boolean\") {\n return \"\";\n }\n // Recursively process nested arrays (only if they don't contain React elements)\n if (Array.isArray(child)) {\n const nested = childrenToString(child);\n return nested ?? \"\";\n }\n return String(child);\n })\n .join(\"\");\n\n return stringified || null;\n }\n\n // Handle React fragments and other iterable children\n try {\n const childArray = Children.toArray(children as ReactNode);\n if (childArray.length > 0) {\n // Check if any children are React elements\n if (hasReactElements(childArray)) {\n return null;\n }\n return childrenToString(childArray);\n }\n } catch {\n // Not iterable or other error, return null\n }\n\n return null;\n}\n\n/**\n * Checks if React children can be converted to a string.\n *\n * @param children - React children to check\n * @returns True if children can be converted to string, false otherwise\n */\nexport function isStringifiableChildren(children: ReactNode): boolean {\n return childrenToString(children) !== null;\n}\n", "type": "registry:lib", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/utils/children-to-string.ts" }, { "path": "registry/native-ui/src/helpers/internal/utils/combine-styles.ts", "content": "import type { CombinedStyles } from \"../types\";\n\n/**\n * Helper function to combine style objects with proper type inference\n * This preserves the exact types of each style object, including VariantProps\n * @example\n * const styles = combineStyles({\n * root,\n * item,\n * content\n * });\n */\nexport function combineStyles>(styles: T): CombinedStyles {\n return styles as CombinedStyles;\n}\n", "type": "registry:lib", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/utils/combine-styles.ts" }, { "path": "registry/native-ui/src/helpers/internal/utils/create-context.ts", "content": "import * as React from \"react\";\n\nexport interface CreateContextOptions {\n /**\n * If `true`, React will throw if context is `null` or `undefined`\n * In some cases, you might want to support nested context, so you can set it to `false`\n */\n strict?: boolean;\n /**\n * Error message to throw if the context is `undefined`\n */\n errorMessage?: string;\n /**\n * The display name of the context\n */\n name?: string;\n}\n\nexport type CreateContextReturn = [React.Provider, () => T, React.Context];\n\n/**\n * Creates a named context, provider, and hook.\n *\n * @param options create context options\n */\nexport function createContext(options: CreateContextOptions = {}) {\n const {\n strict = true,\n errorMessage = \"useContext: `context` is undefined. Seems you forgot to wrap component within the Provider\",\n name,\n } = options;\n\n const Context = React.createContext(undefined);\n\n Context.displayName = name;\n\n function useContext() {\n const context = React.useContext(Context);\n\n if (!context && strict) {\n const error = new Error(errorMessage);\n\n error.name = \"ContextError\";\n (\n Error as ErrorConstructor & {\n captureStackTrace?: (\n targetObject: object,\n constructorOpt?: (...args: unknown[]) => unknown,\n ) => void;\n }\n ).captureStackTrace?.(error, useContext);\n throw error;\n }\n\n return context;\n }\n\n return [Context.Provider, useContext, Context] as CreateContextReturn;\n}\n", "type": "registry:lib", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/utils/create-context.ts" }, { "path": "registry/native-ui/src/helpers/internal/utils/ease-gradient/create-interpolation.ts", "content": "/**\n * Easing gradient utilities for React Native\n *\n * Original source: https://github.com/phamfoo/react-native-easing-gradient\n * Author: @phamfoo\n * License: MIT\n *\n * This code has been adapted for use in PitsiUI Native with modifications\n * for TypeScript compatibility and integration with the animation system.\n */\n\nimport { Animated } from \"react-native\";\n\n// @ts-expect-error\nconst AnimatedInterpolation = Animated.Interpolation;\n\ntype ColorInterpolateFunction = (input: number) => string;\n\nfunction createInterpolation(config: Animated.InterpolationConfigType): ColorInterpolateFunction {\n if (AnimatedInterpolation.__createInterpolation) {\n return AnimatedInterpolation.__createInterpolation(config);\n }\n\n return (input) => {\n const interpolation = new AnimatedInterpolation({ __getValue: () => input }, config);\n\n return interpolation.__getValue();\n };\n}\n\nexport { createInterpolation };\n", "type": "registry:lib", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/utils/ease-gradient/create-interpolation.ts" }, { "path": "registry/native-ui/src/helpers/internal/utils/ease-gradient/index.ts", "content": "/**\n * Easing gradient utilities for React Native\n *\n * Original source: https://github.com/phamfoo/react-native-easing-gradient\n * Author: @phamfoo\n * License: MIT\n *\n * This code has been adapted for use in PitsiUI Native with modifications\n * for TypeScript compatibility and integration with the animation system.\n */\n\nimport { Easing, type EasingFunction } from \"react-native\";\nimport { createInterpolation } from \"./create-interpolation\";\n\ninterface ColorStops {\n [location: number]: {\n color: string;\n easing?: EasingFunction;\n };\n}\n\ninterface GradientParams {\n colorStops: ColorStops;\n extraColorStopsPerTransition?: number;\n easing?: EasingFunction;\n}\n\nconst easeInOut = Easing.bezier(0.42, 0, 0.58, 1);\n\nfunction easeGradient({\n colorStops,\n easing = easeInOut,\n extraColorStopsPerTransition = 12,\n}: GradientParams): {\n colors: [string, string, ...string[]];\n locations: [number, number, ...number[]];\n} {\n const colors: string[] = [];\n const locations: number[] = [];\n\n const initialLocations = Object.keys(colorStops)\n .map((key) => Number(key))\n .sort();\n\n const totalColorStops = initialLocations.length;\n\n for (let currentStopIndex = 0; currentStopIndex < totalColorStops - 1; currentStopIndex++) {\n const startLocation = initialLocations[currentStopIndex];\n const endLocation = initialLocations[currentStopIndex + 1];\n\n if (startLocation === undefined || endLocation === undefined) {\n continue;\n }\n\n const startStop = colorStops[startLocation];\n const endStop = colorStops[endLocation];\n\n if (!startStop || !endStop) {\n continue;\n }\n\n const startColor = startStop.color;\n const endColor = endStop.color;\n const currentEasing = startStop.easing ?? easing;\n\n const colorScale = createInterpolation({\n inputRange: [0, 1],\n outputRange: [startColor, endColor],\n easing: currentEasing,\n });\n\n const currentTransitionLength = endLocation - startLocation;\n const stepSize = 1 / (extraColorStopsPerTransition + 1);\n\n for (let stepIndex = 0; stepIndex <= extraColorStopsPerTransition + 1; stepIndex++) {\n const progress = stepIndex * stepSize;\n const color = colorScale(progress);\n colors.push(color);\n locations.push(startLocation + currentTransitionLength * progress);\n }\n }\n\n return {\n colors: colors as [string, string, ...string[]],\n locations: locations as [number, number, ...number[]],\n };\n}\n\nexport { easeGradient };\n", "type": "registry:lib", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/utils/ease-gradient/index.ts" }, { "path": "registry/native-ui/src/helpers/internal/utils/get-element-by-display-name.ts", "content": "import React from \"react\";\nimport type { ReactChild } from \"../types\";\n\nexport const getElementByDisplayName = (\n children: React.ReactNode,\n displayName: string,\n): ReactChild | undefined => {\n const element = React.Children.toArray(children).find(\n (child) => React.isValidElement(child) && (child.type as any)?.displayName === displayName,\n );\n\n return element;\n};\n", "type": "registry:lib", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/utils/get-element-by-display-name.ts" }, { "path": "registry/native-ui/src/helpers/internal/utils/get-element-with-default.ts", "content": "import type React from \"react\";\nimport type { ReactChild } from \"../types\";\nimport { getElementByDisplayName } from \"./get-element-by-display-name\";\n\nexport const getElementWithDefault = (\n children: React.ReactNode,\n displayName: string,\n defaultElement: React.ReactElement,\n): ReactChild => {\n const element = getElementByDisplayName(children, displayName);\n\n if (!element) {\n return defaultElement;\n }\n\n return element;\n};\n", "type": "registry:lib", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/utils/get-element-with-default.ts" }, { "path": "registry/native-ui/src/helpers/internal/utils/has-prop.ts", "content": "import type { ReactElement } from \"react\";\n\n/**\n * Checks if a React element has a specific prop defined\n * @param element - The React element to check\n * @param propName - The name of the prop to check for\n * @returns true if the element has the prop, false otherwise\n */\nexport function hasProp(element: ReactElement | null | undefined, propName: string): boolean {\n if (!element?.props || typeof element.props !== \"object\") {\n return false;\n }\n\n return propName in element.props;\n}\n", "type": "registry:lib", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/utils/has-prop.ts" }, { "path": "registry/native-ui/src/helpers/internal/utils/index.ts", "content": "export * from \".//animation\";\nexport * from \"./children-to-string\";\nexport * from \"./combine-styles\";\nexport * from \"./create-context\";\nexport * from \"./ease-gradient\";\nexport * from \"./get-element-by-display-name\";\nexport * from \"./get-element-with-default\";\nexport * from \"./has-prop\";\n", "type": "registry:lib", "target": "@components/pitsi-ui/native-ui/src/helpers/internal/utils/index.ts" }, { "path": "registry/native-ui/src/optional/gorhom-bottom-sheet.ts", "content": "let GorhomBottomSheetPackage: any;\n\ntry {\n GorhomBottomSheetPackage = require(\"@gorhom/bottom-sheet\");\n} catch (_error) {\n /* @gorhom/bottom-sheet is an optional peer dependency */\n}\n\nexport default GorhomBottomSheetPackage;\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/optional/gorhom-bottom-sheet.ts" }, { "path": "registry/native-ui/src/optional/react-native-screens.ts", "content": "let ReactNativeScreensPackage: any;\n\ntry {\n ReactNativeScreensPackage = require(\"react-native-screens\");\n} catch (_error) {\n /* react-native-screens is an optional peer dependency */\n}\n\nexport default ReactNativeScreensPackage;\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/optional/react-native-screens.ts" }, { "path": "registry/native-ui/src/primitives/avatar/avatar.tsx", "content": "import * as React from \"react\";\nimport { createContext, forwardRef, useEffect, useMemo, useState } from \"react\";\nimport {\n type ImageErrorEventData,\n type ImageLoadEvent,\n type NativeSyntheticEvent,\n Image as RNImage,\n View,\n} from \"react-native\";\nimport * as Slot from \"../slot\";\nimport type {\n AvatarStatus,\n FallbackProps,\n FallbackRef,\n ImageProps,\n ImageRef,\n RootProps,\n RootRef,\n} from \"./avatar.types\";\nimport { isSameSource, isValidSource } from \"./avatar.utils\";\n\ninterface IRootContext extends RootProps {\n status: AvatarStatus;\n setStatus: (status: AvatarStatus) => void;\n}\n\nconst RootContext = createContext(null);\n\nexport function useRootContext() {\n const context = React.useContext(RootContext);\n if (!context) {\n throw new Error(\"Avatar compound components cannot be rendered outside the Avatar component\");\n }\n return context;\n}\n\n// --------------------------------------------------\n\nconst Root = forwardRef(({ asChild, alt, ...viewProps }, ref) => {\n const [status, setStatus] = useState(\"error\");\n\n const Component = asChild ? Slot.View : View;\n\n const value = useMemo(\n () => ({\n alt,\n status,\n setStatus,\n }),\n [alt, status],\n );\n\n return (\n \n \n \n );\n});\n\nRoot.displayName = \"PitsiUINative.Primitive.Avatar.Root\";\n\n// --------------------------------------------------\n\nconst Image = forwardRef(\n (\n { asChild, onLoad: onLoadProps, onError: onErrorProps, onLoadingStatusChange, ...props },\n ref,\n ) => {\n const { alt, setStatus, status } = useRootContext();\n\n // Use ref to track the previous source value for comparison\n const previousSourceRef = React.useRef(undefined);\n\n useEffect(() => {\n const currentSource = props?.source;\n const previousSource = previousSourceRef.current;\n\n // Only reset status if the source actually changed (not just reference)\n const sourceChanged = !isSameSource(currentSource, previousSource);\n\n if (sourceChanged) {\n // Update the ref to track the new source\n previousSourceRef.current = currentSource;\n\n if (isValidSource(currentSource)) {\n setStatus(\"loading\");\n } else {\n setStatus(\"error\");\n }\n }\n\n // Cleanup: only reset to error if component unmounts or source becomes invalid\n return () => {\n // Only reset if source is no longer valid or component is unmounting\n if (!isValidSource(currentSource)) {\n setStatus(\"error\");\n }\n };\n }, [props?.source, setStatus]);\n\n const onLoad = React.useCallback(\n (e: ImageLoadEvent) => {\n setStatus(\"loaded\");\n onLoadingStatusChange?.(\"loaded\");\n onLoadProps?.(e);\n },\n [onLoadProps, setStatus, onLoadingStatusChange],\n );\n\n const onError = React.useCallback(\n (e: NativeSyntheticEvent) => {\n setStatus(\"error\");\n onLoadingStatusChange?.(\"error\");\n onErrorProps?.(e);\n },\n [onErrorProps, setStatus, onLoadingStatusChange],\n );\n\n const Component = asChild ? Slot.Image : RNImage;\n\n if (status === \"error\") {\n return null;\n }\n\n return ;\n },\n);\n\nImage.displayName = \"PitsiUINative.Primitive.Avatar.Image\";\n\n// --------------------------------------------------\n\nconst Fallback = forwardRef(({ asChild, ...props }, ref) => {\n const { alt, status } = useRootContext();\n\n if (status !== \"error\") {\n return null;\n }\n\n const Component = asChild ? Slot.View : View;\n\n return ;\n});\n\nFallback.displayName = \"PitsiUINative.Primitive.Avatar.Fallback\";\n\n// --------------------------------------------------\n\nexport { Fallback, Image, Root };\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/primitives/avatar/avatar.tsx" }, { "path": "registry/native-ui/src/primitives/avatar/index.ts", "content": "export * from \"./avatar\";\nexport * from \"./avatar.types\";\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/primitives/avatar/index.ts" }, { "path": "registry/native-ui/src/primitives/slot/index.ts", "content": "export * from \"./slot\";\nexport type * from \"./slot.types\";\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/primitives/slot/index.ts" }, { "path": "registry/native-ui/src/primitives/slot/slot.tsx", "content": "import { type ComponentRef, cloneElement, forwardRef, isValidElement } from \"react\";\nimport type {\n Image as RNImage,\n Pressable as RNPressable,\n PressableProps as RNPressableProps,\n Text as RNText,\n TextProps as RNTextProps,\n View as RNView,\n ViewProps as RNViewProps,\n} from \"react-native\";\nimport type { AnyProps, ImageSlotProps } from \"./slot.types\";\nimport { composeRefs, mergeProps } from \"./utils\";\n\n// --------------------------------------------------\n\nconst Pressable = forwardRef, RNPressableProps>(\n (props, forwardedRef) => {\n const { children, ...pressableSlotProps } = props;\n\n if (!isValidElement(children)) {\n console.log(\"Slot.Pressable - Invalid asChild element\", children);\n return null;\n }\n\n const child = children as React.ReactElement;\n\n return cloneElement(child, {\n ...mergeProps(pressableSlotProps, children.props as AnyProps),\n ref: forwardedRef ? composeRefs(forwardedRef, (children as any).ref) : (children as any).ref,\n });\n },\n);\n\nPressable.displayName = \"PitsiUINative.Primitive.Slot.Pressable\";\n\n// --------------------------------------------------\n\nconst View = forwardRef, RNViewProps>((props, forwardedRef) => {\n const { children, ...viewSlotProps } = props;\n\n if (!isValidElement(children)) {\n console.log(\"Slot.View - Invalid asChild element\", children);\n return null;\n }\n\n const child = children as React.ReactElement;\n\n return cloneElement(child, {\n ...mergeProps(viewSlotProps, children.props as AnyProps),\n ref: forwardedRef ? composeRefs(forwardedRef, (children as any).ref) : (children as any).ref,\n });\n});\n\nView.displayName = \"PitsiUINative.Primitive.Slot.View\";\n\n// --------------------------------------------------\n\nconst Text = forwardRef, RNTextProps>((props, forwardedRef) => {\n const { children, ...textSlotProps } = props;\n\n if (!isValidElement(children)) {\n console.log(\"Slot.Text - Invalid asChild element\", children);\n return null;\n }\n\n const child = children as React.ReactElement;\n\n return cloneElement(child, {\n ...mergeProps(textSlotProps, children.props as AnyProps),\n ref: forwardedRef ? composeRefs(forwardedRef, (children as any).ref) : (children as any).ref,\n });\n});\n\nText.displayName = \"PitsiUINative.Primitive.Slot.Text\";\n\n// --------------------------------------------------\n\nconst Image = forwardRef, ImageSlotProps>((props, forwardedRef) => {\n const { children, ...imageSlotProps } = props;\n\n if (!isValidElement(children)) {\n console.log(\"Slot.Image - Invalid asChild element\", children);\n return null;\n }\n\n const child = children as React.ReactElement;\n\n return cloneElement(child, {\n ...mergeProps(imageSlotProps, children.props as AnyProps),\n ref: forwardedRef ? composeRefs(forwardedRef, (children as any).ref) : (children as any).ref,\n });\n});\n\nImage.displayName = \"PitsiUINative.Primitive.Slot.Image\";\n\nexport { Image, Pressable, Text, View };\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/primitives/slot/slot.tsx" }, { "path": "registry/native-ui/src/primitives/slot/utils.ts", "content": "import type { PressableStateCallbackType } from \"react-native\";\nimport type { AnyProps, Style } from \"./slot.types\";\n\n// --------------------------------------------------\n\nexport function isTextChildren(\n children: React.ReactNode | ((state: PressableStateCallbackType) => React.ReactNode),\n) {\n return Array.isArray(children)\n ? children.every((child) => typeof child === \"string\")\n : typeof children === \"string\";\n}\n\n// --------------------------------------------------\n\nexport function composeRefs(...refs: (React.Ref | undefined)[]) {\n return (node: T) =>\n refs.forEach((ref) => {\n if (typeof ref === \"function\") {\n ref(node);\n } else if (ref != null) {\n (ref as React.MutableRefObject).current = node;\n }\n });\n}\n\n// --------------------------------------------------\n\nexport function mergeProps(slotProps: AnyProps, childProps: AnyProps) {\n // all child props should override\n const overrideProps = { ...childProps };\n\n for (const propName in childProps) {\n const slotPropValue = slotProps[propName];\n const childPropValue = childProps[propName];\n\n const isHandler = /^on[A-Z]/.test(propName);\n if (isHandler) {\n // if the handler exists on both, we compose them\n if (slotPropValue && childPropValue) {\n overrideProps[propName] = (...args: unknown[]) => {\n childPropValue(...args);\n slotPropValue(...args);\n };\n }\n // but if it exists only on the slot, we use only this one\n else if (slotPropValue) {\n overrideProps[propName] = slotPropValue;\n }\n }\n // if it's `style`, we merge them\n else if (propName === \"style\") {\n overrideProps[propName] = combineStyles(slotPropValue, childPropValue);\n } else if (propName === \"className\") {\n overrideProps[propName] = [slotPropValue, childPropValue].filter(Boolean).join(\" \");\n }\n }\n\n return { ...slotProps, ...overrideProps };\n}\n\n// --------------------------------------------------\n\n/**\n * Combines slot and child styles into a style array.\n *\n * Returns a style array instead of flattening via StyleSheet.flatten,\n * because flatten deep-copies style objects into plain objects, which\n * destroys Reanimated animated style bindings (useAnimatedStyle / SharedValues).\n * React Native natively handles nested style arrays, so an array is safe here.\n */\nfunction combineStyles(slotStyle?: Style, childValue?: Style) {\n if (typeof slotStyle === \"function\" && typeof childValue === \"function\") {\n return (state: PressableStateCallbackType) => {\n return [slotStyle(state), childValue(state)];\n };\n }\n if (typeof slotStyle === \"function\") {\n return (state: PressableStateCallbackType) => {\n return childValue ? [slotStyle(state), childValue] : slotStyle(state);\n };\n }\n if (typeof childValue === \"function\") {\n return (state: PressableStateCallbackType) => {\n return slotStyle ? [slotStyle, childValue(state)] : childValue(state);\n };\n }\n\n if (!slotStyle) return childValue;\n if (!childValue) return slotStyle;\n return [slotStyle, childValue];\n}\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/primitives/slot/utils.ts" }, { "path": "registry/native-ui/src/primitives/toast/index.ts", "content": "export * from \"./toast\";\nexport * from \"./toast.types\";\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/primitives/toast/index.ts" }, { "path": "registry/native-ui/src/primitives/toast/toast.tsx", "content": "import { createContext, forwardRef, useContext, useId } from \"react\";\nimport { type GestureResponderEvent, Pressable, Text, View } from \"react-native\";\nimport * as Slot from \"../slot\";\nimport type {\n ActionProps,\n ActionRef,\n CloseProps,\n CloseRef,\n DescriptionProps,\n DescriptionRef,\n RootContext,\n RootProps,\n RootRef,\n TitleProps,\n TitleRef,\n} from \"./toast.types\";\n\nconst ToastContext = createContext(null);\n\nconst Root = forwardRef(({ asChild, id, ...viewProps }, ref) => {\n const generatedId = useId();\n const nativeID = id || generatedId;\n\n const Component = asChild ? Slot.View : View;\n return (\n \n \n \n );\n});\n\nfunction useRootContext() {\n const context = useContext(ToastContext);\n if (!context) {\n throw new Error(\"Toast compound components cannot be rendered outside the Toast component\");\n }\n return context;\n}\n\nRoot.displayName = \"PitsiUINative.Primitive.Toast.Root\";\n\n// --------------------------------------------------\n\nconst Title = forwardRef((props, ref) => {\n const { nativeID } = useRootContext();\n return ;\n});\n\nTitle.displayName = \"PitsiUINative.Primitive.Toast.Title\";\n\n// --------------------------------------------------\n\nconst Description = forwardRef((props, ref) => {\n const { nativeID } = useRootContext();\n return ;\n});\n\nDescription.displayName = \"PitsiUINative.Primitive.Toast.Description\";\n\n// --------------------------------------------------\n\nconst Action = forwardRef(\n ({ asChild, altText, disabled = false, ...props }, ref) => {\n const Component = asChild ? Slot.Pressable : Pressable;\n return (\n \n );\n },\n);\n\nAction.displayName = \"PitsiUINative.Primitive.Toast.Action\";\n\n// --------------------------------------------------\n\nconst Close = forwardRef(\n ({ asChild, disabled = false, onPress: onPressProp, ...props }, ref) => {\n function onPress(ev: GestureResponderEvent) {\n if (disabled) return;\n onPressProp?.(ev);\n }\n\n const Component = asChild ? Slot.Pressable : Pressable;\n return (\n \n );\n },\n);\n\nClose.displayName = \"PitsiUINative.Primitive.Toast.Close\";\n\n// --------------------------------------------------\n\nexport { Action, Close, Description, Root, Title, useRootContext };\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/primitives/toast/toast.tsx" }, { "path": "registry/native-ui/src/providers/animation-settings/index.ts", "content": "export {\n default as GlobalAnimationSettingsProvider,\n useGlobalAnimationSettings,\n} from \"./provider\";\nexport type {\n GlobalAnimationSettingsContextValue,\n GlobalAnimationSettingsProviderProps,\n} from \"./types\";\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/providers/animation-settings/index.ts" }, { "path": "registry/native-ui/src/providers/animation-settings/provider.tsx", "content": "import type React from \"react\";\nimport { useReducedMotion } from \"react-native-reanimated\";\nimport { createContext } from \"../../helpers/internal/utils\";\nimport type {\n GlobalAnimationSettingsContextValue,\n GlobalAnimationSettingsProviderProps,\n} from \"./types\";\n\nconst [GlobalAnimationSettingsProvider, useGlobalAnimationSettings] =\n createContext({\n name: \"GlobalAnimationSettingsContext\",\n strict: false,\n });\n\nexport { useGlobalAnimationSettings };\n\n/**\n * GlobalAnimationSettingsProvider Component\n *\n * @description\n * Provider component that controls global animation settings across the application.\n * When animation is set to 'disable-all', all animations will be disabled globally.\n * Additionally, if the user has enabled reduce motion in accessibility settings,\n * all animations will be disabled automatically.\n *\n * This provider wraps AnimationSettingsProvider to cascade the global setting\n * down through the component tree.\n *\n * @param {GlobalAnimationSettingsProviderProps} props - Provider props\n * @param {AnimationRootDisableAll} [props.animation] - Global animation setting\n * @param {ReactNode} props.children - Child components to wrap\n */\nexport const GlobalAnimationSettingsProviderComponent: React.FC<\n GlobalAnimationSettingsProviderProps\n> = ({ animation, children }) => {\n const reducedMotion = useReducedMotion();\n const globalIsAllAnimationsDisabled = animation === \"disable-all\" || reducedMotion;\n\n return (\n \n {children}\n \n );\n};\n\nexport default GlobalAnimationSettingsProviderComponent;\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/providers/animation-settings/provider.tsx" }, { "path": "registry/native-ui/src/providers/animation-settings/types.ts", "content": "import type { ReactNode } from \"react\";\nimport type { AnimationRootDisableAll } from \"../../helpers/internal/types/animation\";\n\n/**\n * Props for GlobalAnimationSettingsProvider component\n */\nexport interface GlobalAnimationSettingsProviderProps {\n /**\n * Global animation setting\n * When set to 'disable-all', all animations across the app will be disabled\n */\n animation?: AnimationRootDisableAll;\n /**\n * Child components to render within the provider\n */\n children: ReactNode;\n}\n\n/**\n * Context value for global animation settings\n */\nexport interface GlobalAnimationSettingsContextValue {\n /**\n * Whether all animations should be disabled globally\n */\n globalIsAllAnimationsDisabled: boolean;\n}\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/providers/animation-settings/types.ts" }, { "path": "registry/native-ui/src/providers/hero-ui-native/types.ts", "content": "import type { ReactNode } from \"react\";\nimport type { AnimationRootDisableAll } from \"../../helpers/internal/types\";\nimport type { TextComponentContextValue } from \"../text-component/types\";\nimport type { ToastProviderProps } from \"../toast/types\";\n\n/**\n * Developer information messages configuration\n *\n * @interface DevInfoConfig\n *\n * @description\n * Controls developer-facing informational messages displayed in the console.\n * These messages provide important guidance and best practices.\n */\nexport interface DevInfoConfig {\n /**\n * Show styling principles information message\n *\n * @description\n * When set to `false`, disables the styling principles information message\n * that appears in the console during development.\n *\n * @default true\n */\n stylingPrinciples?: boolean;\n}\n\n/**\n * Configuration object for PitsiUINativeProvider\n *\n * @interface PitsiUINativeConfig\n * @extends TextComponentContextValue\n *\n * @description\n * Contains configuration options for the PitsiUI Native provider.\n * Additional configuration options can be added in future versions.\n */\nexport interface PitsiUINativeConfig extends TextComponentContextValue {\n /**\n * Global animation configuration\n *\n * @description\n * When set to 'disable-all', all animations across the application will be disabled.\n */\n animation?: AnimationRootDisableAll;\n /**\n * Toast configuration\n *\n * @description\n * Configure the global toast system including insets and wrapper components.\n * Set to `false` or `'disabled'` to disable the toast provider entirely.\n * Provide a `ToastProviderProps` object for custom configuration.\n */\n toast?: boolean | \"disabled\" | ToastProviderProps;\n /**\n * Developer information messages configuration\n *\n * @description\n * Controls developer-facing informational messages displayed in the console.\n * Use this to disable specific informational messages during development.\n */\n devInfo?: DevInfoConfig;\n}\n\n/**\n * Props for PitsiUINativeProvider component\n *\n * @interface PitsiUINativeProviderProps\n *\n * @description\n * Main provider component props that wraps the entire application\n * or a section of it to provide PitsiUI Native functionality.\n *\n * @example\n * ```tsx\n * \n * \n * \n * ```\n */\nexport interface PitsiUINativeProviderProps {\n /**\n * Child components to render within the provider\n *\n * @description\n * All children will have access to PitsiUI Native theme\n * and configuration through the provider.\n */\n children: ReactNode;\n\n /**\n * Configuration object for the provider\n *\n * @description\n * Contains all configuration options including global text component configuration.\n *\n * @example\n * ```tsx\n * const config: PitsiUINativeConfig = {\n * textProps: {\n * allowFontScaling: false,\n * adjustsFontSizeToFit: false,\n * maxFontSizeMultiplier: 1.5\n * }\n * };\n * ```\n */\n config?: PitsiUINativeConfig;\n}\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/providers/hero-ui-native/types.ts" }, { "path": "registry/native-ui/src/providers/pitsi-ui-native/types.ts", "content": "export * from \"../hero-ui-native/types\";\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/providers/pitsi-ui-native/types.ts" }, { "path": "registry/native-ui/src/providers/text-component/index.ts", "content": "export * from \"./provider\";\nexport * from \"./types\";\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/providers/text-component/index.ts" }, { "path": "registry/native-ui/src/providers/text-component/provider.tsx", "content": "import { createContext } from \"../../helpers/internal/utils\";\nimport type { TextComponentContextValue } from \"./types\";\n\nconst [TextComponentProvider, useTextComponent] = createContext({\n name: \"TextComponentContext\",\n});\n\nexport { TextComponentProvider, useTextComponent };\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/providers/text-component/provider.tsx" }, { "path": "registry/native-ui/src/providers/text-component/types.ts", "content": "import type { TextProps } from \"react-native\";\n\n/**\n * Global text component configuration props.\n * These props are carefully selected to include only those that make sense\n * to configure globally across all Text components in the application.\n *\n * @description\n * Includes accessibility and font scaling settings that typically\n * should be consistent throughout the app for better UX.\n */\nexport type TextComponentProps = {\n /**\n * Specifies whether fonts should be scaled down automatically to fit given style constraints.\n *\n * @default false\n */\n adjustsFontSizeToFit?: TextProps[\"adjustsFontSizeToFit\"];\n /**\n * Specifies whether fonts should scale to respect Text Size accessibility settings.\n *\n * @default true\n */\n allowFontScaling?: TextProps[\"allowFontScaling\"];\n /**\n * Specifies the largest possible scale a font can reach when `allowFontScaling` is enabled.\n *\n * Possible values:\n *\n * - `null` or `undefined`: inherit from the parent node or the global default (0)\n * - `0`: no max, ignore parent/global default\n * - `>= 1`: sets the `maxFontSizeMultiplier` of this node to this value\n *\n * @default `undefined`\n */\n maxFontSizeMultiplier?: TextProps[\"maxFontSizeMultiplier\"];\n /**\n * Specifies the smallest possible scale a font can reach when adjustsFontSizeToFit is enabled. (values 0.01-1.0).\n *\n * iOS only\n *\n * @default `undefined`\n */\n minimumFontScale?: TextProps[\"minimumFontScale\"];\n};\n\n/**\n * Context value for text component configuration\n */\nexport interface TextComponentContextValue {\n textProps?: TextComponentProps;\n}\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/providers/text-component/types.ts" }, { "path": "registry/native-ui/src/providers/toast/index.ts", "content": "export { ToastProvider, useToast } from \"./provider\";\nexport { useToastConfig } from \"./toast-config.context\";\nexport type * from \"./types\";\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/providers/toast/index.ts" }, { "path": "registry/native-ui/src/providers/toast/insets-container.tsx", "content": "import type { ReactNode } from \"react\";\nimport { useMemo } from \"react\";\nimport { Platform, View } from \"react-native\";\nimport { useSafeAreaInsets } from \"react-native-safe-area-context\";\nimport { FullWindowOverlay } from \"../../helpers/internal/components\";\nimport type { ToastInsets } from \"./types\";\n\ninterface InsetsContainerProps {\n /**\n * When true, uses a regular View instead of FullWindowOverlay on iOS.\n * Enables element inspector but toasts won't appear above native modals.\n * @default false\n */\n disableFullWindowOverlay: boolean;\n /**\n * Controls whether VoiceOver treats the overlay window as a modal container.\n * When `false`, VoiceOver can still access elements behind the overlay.\n * When `true`, VoiceOver is restricted to elements inside the overlay.\n * @default false\n * @platform ios\n * @unstable This prop maps directly to the native `accessibilityViewIsModal`\n * on the container view and may change in a future react-native-screens release.\n */\n unstable_accessibilityContainerViewIsModal?: boolean;\n /**\n * Optional inset values for all edges\n * If not provided, defaults to platform-specific values:\n * - iOS: safe area insets + 0px (top), + 6px (bottom), + 12px (left/right)\n * - Android: safe area insets + 12px (all edges)\n */\n insets?: ToastInsets;\n /**\n * Custom wrapper function to wrap the toast content\n * Receives children and should return a component that wraps them\n * The wrapper should apply flex: 1 (via className or style) to ensure proper layout\n * Can be any component wrapper - KeyboardAvoidingView, View, or any custom component\n */\n contentWrapper?: (children: ReactNode) => React.ReactElement;\n /**\n * Children to render inside the container\n */\n children: ReactNode;\n}\n\n/**\n * Container component that applies inset padding to position toasts\n * away from screen edges and safe areas\n *\n * Combines custom insets with safe area insets:\n * - If custom inset is provided, it overrides safe area + default padding\n * - If not provided, uses platform-specific defaults:\n * - iOS: safe area inset + 0px for top, + 6px for bottom, + 12px for left/right\n * - Android: safe area inset + 12px for all edges\n */\nexport function InsetsContainer({\n insets,\n contentWrapper,\n children,\n disableFullWindowOverlay,\n unstable_accessibilityContainerViewIsModal,\n}: InsetsContainerProps) {\n const safeAreaInsets = useSafeAreaInsets();\n\n const finalInsets = useMemo(() => {\n return {\n top: insets?.top ?? safeAreaInsets.top + (Platform.OS === \"ios\" ? 0 : 12),\n bottom: insets?.bottom ?? safeAreaInsets.bottom + (Platform.OS === \"ios\" ? 6 : 12),\n left: insets?.left ?? safeAreaInsets.left + 12,\n right: insets?.right ?? safeAreaInsets.right + 12,\n };\n }, [safeAreaInsets, insets]);\n\n const content = (\n \n {contentWrapper ? contentWrapper(children) : children}\n \n );\n\n if (Platform.OS !== \"ios\") {\n return content;\n }\n\n return (\n \n {content}\n \n );\n}\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/providers/toast/insets-container.tsx" }, { "path": "registry/native-ui/src/providers/toast/provider.tsx", "content": "import {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useReducer,\n useRef,\n} from \"react\";\nimport { View } from \"react-native\";\nimport { useSharedValue } from \"react-native-reanimated\";\nimport { DefaultToast } from \"../../components/toast/toast\";\nimport { InsetsContainer } from \"./insets-container\";\nimport { toastReducer } from \"./reducer\";\nimport { ToastConfigContext } from \"./toast-config.context\";\nimport { ToastItemRenderer } from \"./toast-item-renderer\";\nimport type {\n ToastComponentProps,\n ToasterContextValue,\n ToastGlobalConfig,\n ToastProviderProps,\n ToastShowConfig,\n ToastShowOptions,\n ToastShowOptionsWithComponent,\n} from \"./types\";\n\nconst DEFAULT_DURATION = 4000;\n\n/**\n * Context for toast manager\n */\nconst ToasterContext = createContext(null);\n\n/**\n * Merges global config with local config, ensuring local config takes precedence\n * Only includes defined values from localConfig to avoid overriding global config with undefined\n */\nfunction mergeToastConfig(\n globalConfig: ToastGlobalConfig | undefined,\n localConfig: Partial,\n): Partial {\n const result: Partial = { ...globalConfig };\n\n // Only override with defined values from localConfig\n if (localConfig.variant !== undefined) {\n result.variant = localConfig.variant;\n }\n if (localConfig.placement !== undefined) {\n result.placement = localConfig.placement;\n }\n if (localConfig.isSwipeable !== undefined) {\n result.isSwipeable = localConfig.isSwipeable;\n }\n if (localConfig.animation !== undefined) {\n result.animation = localConfig.animation;\n }\n\n return result;\n}\n\n/**\n * Creates a component function for simple string toast\n */\nfunction createStringToastComponent(\n label: string,\n globalConfig: ToastGlobalConfig | undefined,\n): (props: ToastComponentProps) => React.ReactElement {\n return (props: ToastComponentProps) => {\n const mergedConfig = mergeToastConfig(globalConfig, {\n variant: \"default\",\n });\n return (\n \n );\n };\n}\n\n/**\n * Creates a component function for config-based toast\n */\nfunction createConfigToastComponent(\n config: ToastShowConfig,\n globalConfig: ToastGlobalConfig | undefined,\n): (props: ToastComponentProps) => React.ReactElement {\n return (props: ToastComponentProps) => {\n const mergedConfig = mergeToastConfig(globalConfig, {\n variant: config.variant,\n placement: config.placement,\n isSwipeable: config.isSwipeable,\n animation: config.animation,\n });\n return (\n \n );\n };\n}\n\n/**\n * Toast provider component\n * Wraps your app to enable toast functionality\n */\nexport function ToastProvider({\n defaultProps,\n insets,\n maxVisibleToasts = 3,\n contentWrapper,\n children,\n disableFullWindowOverlay = false,\n unstable_accessibilityContainerViewIsModal,\n}: ToastProviderProps) {\n const [toasts, dispatch] = useReducer(toastReducer, []);\n\n /**\n * Memoize global config to prevent unnecessary re-renders\n */\n const globalConfig = useMemo(() => defaultProps, [defaultProps]);\n\n const isToastVisible = toasts.length > 0;\n\n const heights = useSharedValue>({});\n\n const total = useSharedValue(0);\n\n /**\n * Derive total from toasts.length so the animated opacity/scale/translateY\n * interpolations always use the real count. Manual increment/decrement\n * was prone to drift when hide + show ran in the same tick or when\n * auto-dismiss raced with a manual hide (stale-closure mismatch).\n */\n useEffect(() => {\n total.set(toasts.length);\n }, [toasts.length, total]);\n\n const idCounter = useRef(0);\n const timeoutRefs = useRef>>(new Map());\n const hideRef = useRef<((ids?: string | string[] | \"all\") => void) | null>(null);\n\n /**\n * Hide one or more toasts\n * - No argument: hides the last toast in the array\n * - \"all\": hides all toasts\n * - Single ID: hides that toast\n * - Array of IDs: hides those toasts\n */\n const hide = useCallback(\n (ids?: string | string[] | \"all\") => {\n if (ids === undefined) {\n // Hide the last toast in the array\n if (toasts.length > 0) {\n const lastToast = toasts[toasts.length - 1];\n if (!lastToast) return;\n\n // Clear timeout if exists\n const timeout = timeoutRefs.current.get(lastToast.id);\n if (timeout) {\n clearTimeout(timeout);\n timeoutRefs.current.delete(lastToast.id);\n }\n\n if (lastToast.onHide) {\n lastToast.onHide();\n }\n\n dispatch({\n type: \"HIDE\",\n payload: { ids: [lastToast.id] },\n });\n\n heights.modify(>(value: T): T => {\n \"worklet\";\n const result = { ...value };\n delete result[lastToast.id];\n return result;\n });\n }\n } else if (ids === \"all\") {\n // Clear all timeouts\n timeoutRefs.current.forEach((timeout) => {\n clearTimeout(timeout);\n });\n timeoutRefs.current.clear();\n\n // Hide all toasts - call onHide for each toast before hiding\n toasts.forEach((toast) => {\n if (toast.onHide) {\n toast.onHide();\n }\n });\n dispatch({ type: \"HIDE_ALL\" });\n heights.set({});\n } else {\n // Hide specific toast(s) - call onHide for each toast before hiding\n const idsArray = Array.isArray(ids) ? ids : [ids];\n const idsToRemove = idsArray;\n let removedCount = 0;\n\n // Find and call onHide callbacks before removing, and clear timeouts\n idsToRemove.forEach((id) => {\n // Clear timeout if exists\n const timeout = timeoutRefs.current.get(id);\n if (timeout) {\n clearTimeout(timeout);\n timeoutRefs.current.delete(id);\n }\n\n const toast = toasts.find((t) => String(t.id) === String(id));\n if (toast) {\n removedCount++;\n if (toast.onHide) {\n toast.onHide();\n }\n }\n });\n\n if (removedCount > 0) {\n dispatch({\n type: \"HIDE\",\n payload: { ids: idsArray },\n });\n\n heights.modify(>(value: T): T => {\n \"worklet\";\n const result = { ...value };\n for (const id of idsToRemove) {\n delete result[id];\n }\n return result;\n });\n }\n }\n },\n [heights, toasts],\n );\n\n // Keep hide ref up to date\n hideRef.current = hide;\n\n /**\n * Show a toast\n * Supports three usage patterns:\n * 1. Simple string: toast.show('This is toast')\n * 2. Config object: toast.show({ label, variant, ... })\n * 3. Custom component: toast.show({ component: (props) => ... })\n */\n const show = useCallback(\n (options: string | ToastShowOptions): string => {\n let normalizedOptions: ToastShowOptionsWithComponent;\n let duration: number | \"persistent\" | undefined = DEFAULT_DURATION; // Default duration\n let explicitId: string | undefined;\n\n // Case 1: Simple string\n if (typeof options === \"string\") {\n normalizedOptions = {\n id: undefined,\n component: createStringToastComponent(options, globalConfig),\n duration: DEFAULT_DURATION,\n };\n duration = DEFAULT_DURATION;\n explicitId = undefined;\n }\n // Case 2: Config object without component\n else if (!(\"component\" in options) || options.component === undefined) {\n const config = options as ToastShowConfig;\n duration = config.duration ?? DEFAULT_DURATION;\n explicitId = config.id;\n normalizedOptions = {\n id: config.id,\n component: createConfigToastComponent(config, globalConfig),\n duration,\n onShow: config.onShow,\n onHide: config.onHide,\n };\n }\n // Case 3: Config object with component (existing behavior)\n else {\n normalizedOptions = options as ToastShowOptionsWithComponent;\n duration = normalizedOptions.duration ?? DEFAULT_DURATION;\n explicitId = normalizedOptions.id;\n }\n\n const id = normalizedOptions.id ?? `toast-${Date.now()}-${idCounter.current++}`;\n\n // If an explicit ID was provided, check if a toast with that ID already exists\n // If it exists, skip adding a new toast and return the existing ID\n if (explicitId !== undefined) {\n const existingToast = toasts.find((toast) => String(toast.id) === String(explicitId));\n if (existingToast) {\n return existingToast.id;\n }\n }\n\n dispatch({\n type: \"SHOW\",\n payload: {\n id,\n component: normalizedOptions.component,\n duration,\n onShow: normalizedOptions.onShow,\n onHide: normalizedOptions.onHide,\n },\n });\n\n if (normalizedOptions.onShow) {\n normalizedOptions.onShow();\n }\n\n // Set up auto-dismiss timeout synchronously\n if (\n duration !== \"persistent\" &&\n typeof duration === \"number\" &&\n !Number.isNaN(duration) &&\n duration > 0 &&\n duration !== Infinity\n ) {\n // Handle immediate dismissal\n if (duration === 0) {\n if (hideRef.current) {\n hideRef.current(id);\n }\n } else {\n const timeout = setTimeout(() => {\n if (hideRef.current) {\n hideRef.current(id);\n }\n timeoutRefs.current.delete(id);\n }, duration);\n timeoutRefs.current.set(id, timeout);\n }\n }\n\n return id;\n },\n [toasts, globalConfig],\n );\n\n const contextValue = useMemo(\n () => ({\n toast: {\n show,\n hide,\n },\n isToastVisible,\n }),\n [show, hide, isToastVisible],\n );\n\n return (\n \n \n {children}\n {toasts.length > 0 && (\n \n \n {toasts.map((toastItem, index) => (\n \n ))}\n \n \n )}\n \n \n );\n}\n\n/**\n * Hook to access toast functionality\n *\n * @returns Object containing toast manager and visibility state\n *\n * @example\n * ```tsx\n * const { toast, isToastVisible } = useToast();\n *\n * // Show a toast\n * toast.show({ component: Hello });\n *\n * // Hide a toast\n * toast.hide('my-toast');\n *\n * // Check if any toast is visible\n * if (isToastVisible) {\n * console.log('A toast is currently displayed');\n * }\n * ```\n */\nexport function useToast() {\n const context = useContext(ToasterContext);\n\n if (!context) {\n throw new Error(\"useToast must be used within a ToastProvider provider\");\n }\n\n return {\n toast: context.toast,\n isToastVisible: context.isToastVisible,\n };\n}\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/providers/toast/provider.tsx" }, { "path": "registry/native-ui/src/providers/toast/reducer.ts", "content": "import type { ToastAction, ToastItem } from \"./types\";\n\n/**\n * Reducer for managing toast state\n */\nexport function toastReducer(state: ToastItem[], action: ToastAction): ToastItem[] {\n switch (action.type) {\n case \"SHOW\": {\n // Remove existing toast with same ID if it exists\n const filtered = state.filter((toast) => toast.id !== action.payload.id);\n // Add new toast\n return [...filtered, action.payload];\n }\n\n case \"HIDE\": {\n // Hide specific toasts by ID\n // Use loose equality to handle string/number ID comparisons\n return state.filter(\n (toast) => !action.payload.ids.some((id) => String(id) === String(toast.id)),\n );\n }\n\n case \"HIDE_ALL\":\n // Hide all toasts\n return [];\n\n default:\n return state;\n }\n}\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/providers/toast/reducer.ts" }, { "path": "registry/native-ui/src/providers/toast/toast-item-renderer.tsx", "content": "import { memo } from \"react\";\nimport type { ToastItemRendererProps } from \"./types\";\n\n/**\n * Memoized toast item component to prevent unnecessary re-renders\n * Only re-renders when the toast item itself changes\n */\nexport const ToastItemRenderer = memo(\n ({ toastItem, show, hide, index, total, heights, maxVisibleToasts }: ToastItemRendererProps) => {\n if (typeof toastItem.component !== \"function\") {\n throw new Error(\"Toast component must be a function that receives ToastComponentProps\");\n }\n\n const content = toastItem.component({\n id: toastItem.id,\n index,\n total,\n heights,\n maxVisibleToasts,\n show,\n hide,\n });\n\n return content;\n },\n (prevProps, nextProps) => {\n // Only re-render if the toast ID, component reference, or index changed\n // show, hide, total, and heights are stable references, so we don't need to compare them\n return (\n prevProps.toastItem.id === nextProps.toastItem.id &&\n prevProps.toastItem.component === nextProps.toastItem.component &&\n prevProps.index === nextProps.index\n );\n },\n);\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/providers/toast/toast-item-renderer.tsx" }, { "path": "registry/native-ui/src/providers/toast/types.ts", "content": "import type { SharedValue } from \"react-native-reanimated\";\nimport type { ToastRootProps } from \"../../components/toast\";\n\n/**\n * Global toast configuration\n * These values are used as defaults for all toasts unless overridden locally\n */\nexport interface ToastGlobalConfig\n extends Pick {}\n\n/**\n * Insets for spacing from screen edges\n */\nexport interface ToastInsets {\n /**\n * Inset from the top edge in pixels (added to safe area inset)\n * @default Platform-specific: iOS = 0, Android = 12\n */\n top?: number;\n /**\n * Inset from the bottom edge in pixels (added to safe area inset)\n * @default Platform-specific: iOS = 6, Android = 12\n */\n bottom?: number;\n /**\n * Inset from the left edge in pixels (added to safe area inset)\n * @default 12\n */\n left?: number;\n /**\n * Inset from the right edge in pixels (added to safe area inset)\n * @default 12\n */\n right?: number;\n}\n\n/**\n * Props for the ToastProvider component\n */\nexport interface ToastProviderProps {\n /**\n * When true, uses a regular View instead of FullWindowOverlay on iOS for toasts.\n * Enables React Native element inspector but toasts won't appear above native modals.\n * @default false\n */\n disableFullWindowOverlay?: boolean;\n /**\n * Controls whether VoiceOver treats the toast overlay window as a modal container.\n * When `false`, VoiceOver can still access elements behind the overlay.\n * When `true`, VoiceOver is restricted to elements inside the overlay.\n * @default false\n * @platform ios\n * @unstable This prop maps directly to the native `accessibilityViewIsModal`\n * on the container view and may change in a future react-native-screens release.\n */\n unstable_accessibilityContainerViewIsModal?: boolean;\n /**\n * Global toast configuration\n * These values are used as defaults for all toasts unless overridden locally\n * Local configs have precedence over global config\n */\n defaultProps?: ToastGlobalConfig;\n /**\n * Insets for spacing from screen edges (added to safe area insets)\n * @default Platform-specific:\n * - iOS: { top: 0, bottom: 6, left: 12, right: 12 }\n * - Android: { top: 12, bottom: 12, left: 12, right: 12 }\n */\n insets?: ToastInsets;\n /**\n * Maximum number of visible toasts before opacity starts fading\n * Controls when toast items begin to fade out as they move beyond the visible stack\n * @default 3\n */\n maxVisibleToasts?: number;\n /**\n * Custom wrapper function to wrap the toast content\n * Receives children and should return a component that wraps them\n * The wrapper should apply flex: 1 (via className or style) to ensure proper layout\n * Can be any component wrapper - KeyboardAvoidingView, View, or any custom component\n *\n * @example\n * ```tsx\n * (\n * \n * {children}\n * \n * )}\n * >\n * ```\n */\n contentWrapper?: (children: React.ReactNode) => React.ReactElement;\n /**\n * Children to render\n */\n children?: React.ReactNode;\n}\n\n/**\n * Props passed to the toast component function\n */\nexport interface ToastComponentProps {\n /**\n * The unique ID of the toast\n */\n id: string;\n /**\n * The index of the toast in the array (0-based)\n */\n index: number;\n /**\n * The total number of toasts currently displayed\n */\n total: SharedValue;\n /**\n * Heights of all toast items, keyed by toast ID\n */\n heights: SharedValue>;\n /**\n * Maximum number of visible toasts before opacity starts fading\n * Controls when toast items begin to fade out as they move beyond the visible stack\n * @default 3\n */\n maxVisibleToasts?: number;\n /**\n * Show a new toast\n */\n show: (options: string | ToastShowOptions) => string;\n /**\n * Hide one or more toasts\n * - No argument: hides the last toast in the array\n * - \"all\": hides all toasts\n * - Single ID: hides that toast\n * - Array of IDs: hides those toasts\n */\n hide: (ids?: string | string[] | \"all\") => void;\n}\n\n/**\n * Configuration for showing a default styled toast (usage pattern 2)\n * Used when component is not provided\n */\nexport interface ToastShowConfig\n extends Pick {\n /**\n * Duration in milliseconds before the toast automatically disappears\n * Set to `'persistent'` to prevent auto-hide (toast will remain until manually dismissed)\n * @default 4000\n */\n duration?: number | \"persistent\";\n /**\n * Optional ID for the toast\n * If not provided, one will be generated automatically\n */\n id?: string;\n /**\n * Label text for the toast\n */\n label?: string;\n /**\n * Description text for the toast\n */\n description?: string;\n /**\n * Action button label text\n */\n actionLabel?: string;\n /**\n * Callback function called when the action button is pressed\n * Receives show and hide functions for programmatic toast control\n */\n onActionPress?: (helpers: {\n show: (options: string | ToastShowOptions) => string;\n hide: (ids?: string | string[] | \"all\") => void;\n }) => void;\n /**\n * Icon element to display in the toast\n */\n icon?: React.ReactNode;\n /**\n * Callback function called when the toast is shown\n */\n onShow?: () => void;\n /**\n * Callback function called when the toast is hidden\n */\n onHide?: () => void;\n}\n\n/**\n * Options for showing a toast with custom component (usage pattern 3)\n * Used when component is provided\n */\nexport interface ToastShowOptionsWithComponent {\n /**\n * Optional ID for the toast\n * If not provided, one will be generated automatically\n */\n id?: string;\n /**\n * A function that receives toast props and returns a React element\n */\n component: (props: ToastComponentProps) => React.ReactElement;\n /**\n * Duration in milliseconds before the toast automatically disappears\n * Set to `'persistent'` to prevent auto-hide (toast will remain until manually dismissed)\n * @default 4000\n */\n duration?: number | \"persistent\";\n /**\n * Callback function called when the toast is shown\n */\n onShow?: () => void;\n /**\n * Callback function called when the toast is hidden\n */\n onHide?: () => void;\n}\n\n/**\n * Conditional type for toast show options\n * - If component is provided: only id, component, onShow, onHide are allowed\n * - If component is NOT provided: all config props are available\n */\nexport type ToastShowOptions =\n | ToastShowOptionsWithComponent\n | (ToastShowConfig & { component?: never });\n\n/**\n * Represents a single toast item in the state\n */\nexport interface ToastItem {\n /**\n * Unique identifier for the toast\n */\n id: string;\n /**\n * A function that receives toast props and returns a React element\n */\n component: (props: ToastComponentProps) => React.ReactElement;\n /**\n * Duration in milliseconds before the toast automatically disappears\n * Set to `'persistent'` to prevent auto-hide (toast will remain until manually dismissed)\n * @default 4000\n */\n duration?: number | \"persistent\";\n /**\n * Callback function called when the toast is shown\n */\n onShow?: () => void;\n /**\n * Callback function called when the toast is hidden\n */\n onHide?: () => void;\n}\n\n/**\n * Actions for the toast reducer\n */\nexport type ToastAction =\n | { type: \"SHOW\"; payload: ToastItem }\n | { type: \"HIDE\"; payload: { ids: string[] } }\n | { type: \"HIDE_ALL\" };\n\n/**\n * Toast manager API\n */\nexport interface ToastManager {\n /**\n * Show a toast\n * @param options - Toast configuration options or simple string\n * @returns The ID of the shown toast\n *\n * @example\n * ```tsx\n * const toast = useToast();\n *\n * // Simple string (usage pattern 1)\n * toast.show('This is toast');\n *\n * // Config object with default styling (usage pattern 2)\n * toast.show({\n * label: 'Success!',\n * description: 'Your action was completed',\n * variant: 'success',\n * actionLabel: 'Undo',\n * onActionPress: ({ show, hide }) => hide(),\n * });\n *\n * // Custom component (usage pattern 3)\n * toast.show({\n * component: (props) => Hello,\n * });\n *\n * // With custom ID\n * toast.show({ id: 'my-toast', component: (props) => Hello });\n * ```\n */\n show: (options: string | ToastShowOptions) => string;\n\n /**\n * Hide one or more toasts\n *\n * @param ids - Optional ID(s) of toast(s) to hide\n * - No argument: hides the last toast in the array\n * - \"all\": hides all toasts\n * - Single ID: hides that toast\n * - Array of IDs: hides those toasts\n *\n * @example\n * ```tsx\n * const toast = useToast();\n *\n * toast.hide(); // Hide the last toast\n * toast.hide('all'); // Hide all toasts\n * toast.hide('my-toast'); // Hide specific toast\n * toast.hide(['toast-1', 'toast-2']); // Hide multiple toasts\n * ```\n */\n hide: (ids?: string | string[] | \"all\") => void;\n}\n\n/**\n * Props for the ToastItemRenderer component\n */\nexport interface ToastItemRendererProps {\n /**\n * The toast item to render\n */\n toastItem: ToastItem;\n /**\n * The index of the toast in the array (0-based)\n */\n index: number;\n /**\n * The total number of toasts currently displayed\n */\n total: SharedValue;\n /**\n * Heights of all toast items, keyed by toast ID\n */\n heights: SharedValue>;\n /**\n * Maximum number of visible toasts before opacity starts fading\n * Controls when toast items begin to fade out as they move beyond the visible stack\n * @default 3\n */\n maxVisibleToasts?: number;\n /**\n * Show a new toast\n */\n show: (options: string | ToastShowOptions) => string;\n /**\n * Hide one or more toasts\n * - No argument: hides the last toast in the array\n * - \"all\": hides all toasts\n * - Single ID: hides that toast\n * - Array of IDs: hides those toasts\n */\n hide: (ids?: string | string[] | \"all\") => void;\n}\n\n/**\n * Context value for the toast provider\n */\nexport interface ToasterContextValue {\n toast: ToastManager;\n /**\n * Whether any toast is currently visible\n */\n isToastVisible: boolean;\n}\n", "type": "registry:ui", "target": "@components/pitsi-ui/native-ui/src/providers/toast/types.ts" } ], "categories": [ "native", "react-native", "ui" ], "meta": { "package": "@pitsi-ui/native", "packageSlug": "native-ui", "platform": "native" } }