@poupe/vue
Version:
Vue component library for Poupe UI framework with theme customization and accessibility support
1 lines • 80.2 kB
Source Map (JSON)
{"version":3,"file":"index.mjs","names":["borderVariants","usePoupeIcons","usePoupeIcons","borderVariants","$slots","$props"],"sources":["../src/components/icon.vue","../src/components/icon.vue","../src/components/variants/consts.ts","../src/components/variants/on-slot.ts","../src/components/variants/index.ts","../src/components/surface.vue","../src/components/surface.vue","../src/components/button.vue","../src/components/button.vue","../src/utils/utils.ts","../src/components/card.vue","../src/components/card.vue","../src/composables/use-icons.ts","../src/composables/use-password.ts","../src/components/input/types.ts","../src/components/input/wrapper.vue","../src/components/input/wrapper.vue","../src/components/placeholder.vue","../src/components/placeholder.vue"],"sourcesContent":["<script lang=\"ts\">\nimport {\n type IconifyIconProps,\n} from '@iconify/vue';\n\nexport interface IconProps extends Omit<IconifyIconProps, 'icon'> {\n icon?: IconifyIconProps['icon']\n}\n</script>\n\n<script setup lang=\"ts\">\nimport { Icon as IconifyIcon } from '@iconify/vue';\nimport { computed } from 'vue';\nimport { usePoupeIcons } from '../composables/use-icons';\n\nconst props = defineProps<IconProps>();\n\nconst { toIcon } = usePoupeIcons();\n\nconst icon = computed<IconifyIconProps['icon']>(() => toIcon(props.icon));\n</script>\n\n<template>\n <IconifyIcon\n v-bind=\"props\"\n :icon=\"icon\"\n />\n</template>\n","<script lang=\"ts\">\nimport {\n type IconifyIconProps,\n} from '@iconify/vue';\n\nexport interface IconProps extends Omit<IconifyIconProps, 'icon'> {\n icon?: IconifyIconProps['icon']\n}\n</script>\n\n<script setup lang=\"ts\">\nimport { Icon as IconifyIcon } from '@iconify/vue';\nimport { computed } from 'vue';\nimport { usePoupeIcons } from '../composables/use-icons';\n\nconst props = defineProps<IconProps>();\n\nconst { toIcon } = usePoupeIcons();\n\nconst icon = computed<IconifyIconProps['icon']>(() => toIcon(props.icon));\n</script>\n\n<template>\n <IconifyIcon\n v-bind=\"props\"\n :icon=\"icon\"\n />\n</template>\n","export const pe: Record<number, string> = {\n 0: 'pe-0',\n 1: 'pe-1',\n 2: 'pe-2',\n 3: 'pe-3',\n 4: 'pe-4',\n }, ps: Record<number, string> = {\n 0: 'ps-0',\n 1: 'ps-1',\n 2: 'ps-2',\n 3: 'ps-3',\n 4: 'ps-4',\n 5: 'ps-5',\n 6: 'ps-6',\n }, px: Record<number, string> = {\n 0: 'px-0',\n 1: 'px-1',\n 2: 'px-2',\n 3: 'px-3',\n 4: 'px-4',\n }, py: Record<number, string> = {\n 0: 'py-0',\n 1: 'py-1',\n 2: 'py-2',\n 3: 'py-3',\n 4: 'py-4',\n };\n\n/** Mapping of justify content classes for Tailwind CSS grid and flex layouts */\nexport const justifyClasses: Record<string, string> = {\n start: 'justify-start',\n between: 'justify-between',\n around: 'justify-around',\n evenly: 'justify-evenly',\n};\n\n/** Mapping of grid column classes for Tailwind CSS grid layouts */\nexport const gridColsClasses: Record<number, string> = {\n 1: 'grid-cols-1',\n 2: 'grid-cols-2',\n 3: 'grid-cols-3',\n 4: 'grid-cols-4',\n 5: 'grid-cols-5',\n 6: 'grid-cols-6',\n 7: 'grid-cols-7',\n};\n","import { unsafeKeys } from '@poupe/css';\n\nimport {\n type ClassValue,\n} from 'tailwind-variants';\n\n/** @returns a variant applied to the specified slot */\nexport const onSlot = <K extends string> (slot: string | string[], classes: Record<K, ClassValue>) => {\n const slots = Array.isArray(slot) ? slot : [slot];\n\n type S = typeof slots[number];\n type V = Record<S, ClassValue>;\n type T = Record<K, V>;\n\n const out: Partial<T> = {};\n for (const name of unsafeKeys(classes)) {\n const v: V = {};\n for (const s of slots) {\n v[s] = classes[name];\n }\n out[name] = v;\n }\n\n return out as T;\n};\n","export * from './consts';\nexport * from './on-slot';\n\nexport * from './on-variant';\nexport { twMerge } from 'tailwind-merge';\nexport { tv, type VariantProps } from 'tailwind-variants';\n\nexport const borderVariantsBase = 'border-solid border';\n\nexport const borderVariants = {\n none: 'border-transparent',\n current: 'border-current',\n primary: 'border-primary',\n secondary: 'border-secondary',\n tertiary: 'border-tertiary',\n error: 'border-error',\n outline: 'border-outline',\n};\n\nexport const borderInFocusVariantsBase = 'ring-solid ring-2 ring-transparent';\n\nexport const borderInFocusVariants = {\n none: '',\n current: 'focus-within:ring-current',\n primary: 'focus-within:ring-primary',\n secondary: 'focus-within:ring-secondary',\n tertiary: 'focus-within:ring-tertiary',\n error: 'focus-within:ring-error',\n outline: 'focus-within:ring-outline',\n};\n\nexport const roundedVariants = {\n full: 'rounded-full',\n none: 'rounded-none',\n\n xs: 'rounded-sm',\n sm: 'rounded',\n md: 'rounded-md',\n lg: 'rounded-lg',\n xl: 'rounded-xl',\n ['2xl']: 'rounded-2xl',\n ['3xl']: 'rounded-3xl',\n};\n\nexport const shapeVariants = {\n 'none': 'shape-none',\n // Size-only variant is an alias for the squircle of the same size\n 'xs': 'shape-squircle-extra-small',\n 'sm': 'shape-squircle-small',\n 'md': 'shape-squircle-medium',\n 'lg': 'shape-squircle-large',\n 'xl': 'shape-squircle-extra-large',\n // Rounded means fully rounded (no size customization)\n 'rounded': 'shape-full',\n 'full': 'shape-full', // Alias for rounded\n // Explicit squircle variants (same as size-only)\n 'squircle-xs': 'shape-squircle-extra-small',\n 'squircle-sm': 'shape-squircle-small',\n 'squircle-md': 'shape-squircle-medium',\n 'squircle-lg': 'shape-squircle-large',\n 'squircle-xl': 'shape-squircle-extra-large',\n 'squircle-full': 'shape-squircle-full',\n};\n\nexport const shadowVariants = {\n current: 'shadow-shadow shadow',\n none: 'shadow-none',\n\n z1: 'shadow-shadow shadow-z1',\n z2: 'shadow-shadow shadow-z2',\n z3: 'shadow-shadow shadow-z3',\n z4: 'shadow-shadow shadow-z4',\n z5: 'shadow-shadow shadow-z5',\n inset: 'inset-shadow-shadow inset-shadow',\n};\n\nexport const colorSurfaceVariants = {\n primary: 'surface-primary',\n secondary: 'surface-secondary',\n tertiary: 'surface-tertiary',\n error: 'surface-error',\n\n ['primary-fixed']: 'surface-primary-fixed',\n ['secondary-fixed']: 'surface-secondary-fixed',\n ['tertiary-fixed']: 'surface-tertiary-fixed',\n\n ['primary-container']: 'surface-primary-container',\n ['secondary-container']: 'surface-secondary-container',\n ['tertiary-container']: 'surface-tertiary-container',\n ['error-container']: 'surface-error-container',\n\n inverse: 'surface-inverse',\n ['inverse-primary']: 'surface-inverse-primary',\n};\n\nexport const surfaceVariants = {\n ...colorSurfaceVariants,\n\n dim: 'surface-dim',\n base: 'surface',\n bright: 'surface-bright',\n variant: 'surface-variant',\n};\n\nexport const containerVariants = {\n ...colorSurfaceVariants,\n\n lowest: 'surface-container-lowest',\n low: 'surface-container-low',\n base: 'surface-container',\n high: 'surface-container-high',\n highest: 'surface-container-highest',\n};\n\nexport const colorSurfaceInteractiveVariants = {\n primary: 'interactive-surface-primary',\n secondary: 'interactive-surface-secondary',\n tertiary: 'interactive-surface-tertiary',\n error: 'interactive-surface-error',\n\n ['primary-fixed']: 'interactive-surface-primary-fixed',\n ['secondary-fixed']: 'interactive-surface-secondary-fixed',\n ['tertiary-fixed']: 'interactive-surface-tertiary-fixed',\n\n ['primary-container']: 'interactive-surface-primary-container',\n ['secondary-container']: 'interactive-surface-secondary-container',\n ['tertiary-container']: 'interactive-surface-tertiary-container',\n ['error-container']: 'interactive-surface-error-container',\n\n inverse: 'interactive-surface-inverse',\n ['inverse-primary']: 'interactive-surface-inverse-primary',\n};\n\nexport const surfaceInteractiveVariants = {\n ...colorSurfaceInteractiveVariants,\n\n dim: 'interactive-surface-dim',\n base: 'interactive-surface',\n bright: 'interactive-surface-bright',\n variant: 'interactive-surface-variant',\n};\n\nexport const containerInteractiveVariants = {\n ...colorSurfaceInteractiveVariants,\n\n lowest: 'interactive-surface-container-lowest',\n low: 'interactive-surface-container-low',\n base: 'interactive-surface-container',\n high: 'interactive-surface-container-high',\n highest: 'interactive-surface-container-highest',\n};\n","<script lang=\"ts\">\nimport {\n tv,\n twMerge,\n type VariantProps,\n\n borderVariants,\n borderVariantsBase,\n containerInteractiveVariants,\n onSlot,\n shadowVariants,\n shapeVariants,\n surfaceInteractiveVariants,\n} from './variants';\n\n// Define defaults in one place\nconst surfaceDefaults = {\n tag: 'div',\n class: '',\n role: 'surface',\n tone: 'base',\n level: 'base',\n shape: 'none',\n shadow: 'none',\n border: 'none',\n interactive: false,\n padding: 'none',\n} as const;\n\nexport const surface = tv({\n slots: {\n wrapper: 'relative',\n },\n variants: {\n role: {\n surface: {},\n container: {},\n },\n // Surface tones (for role='surface')\n tone: {\n dim: { wrapper: 'surface-dim' },\n base: { wrapper: 'surface' },\n bright: { wrapper: 'surface-bright' },\n },\n // Container levels (for role='container')\n level: {\n lowest: { wrapper: 'surface-container-lowest' },\n low: { wrapper: 'surface-container-low' },\n base: { wrapper: 'surface-container' },\n high: { wrapper: 'surface-container-high' },\n highest: { wrapper: 'surface-container-highest' },\n },\n // Semantic color variants\n variant: {\n 'primary': { wrapper: 'surface-primary' },\n 'secondary': { wrapper: 'surface-secondary' },\n 'tertiary': { wrapper: 'surface-tertiary' },\n 'error': { wrapper: 'surface-error' },\n 'primary-fixed': { wrapper: 'surface-primary-fixed' },\n 'secondary-fixed': { wrapper: 'surface-secondary-fixed' },\n 'tertiary-fixed': { wrapper: 'surface-tertiary-fixed' },\n 'primary-container': { wrapper: 'surface-primary-container' },\n 'secondary-container': { wrapper: 'surface-secondary-container' },\n 'tertiary-container': { wrapper: 'surface-tertiary-container' },\n 'error-container': { wrapper: 'surface-error-container' },\n 'inverse': { wrapper: 'surface-inverse' },\n 'inverse-primary': { wrapper: 'surface-inverse-primary' },\n },\n shape: onSlot('wrapper', shapeVariants),\n shadow: onSlot('wrapper', shadowVariants),\n border: onSlot('wrapper', borderVariants),\n interactive: {\n true: {\n wrapper: 'cursor-pointer',\n },\n false: {},\n },\n padding: {\n none: {},\n sm: { wrapper: 'p-2' },\n md: { wrapper: 'p-4' },\n lg: { wrapper: 'p-6' },\n xl: { wrapper: 'p-8' },\n },\n },\n defaultVariants: {\n role: surfaceDefaults.role,\n tone: surfaceDefaults.tone,\n level: surfaceDefaults.level,\n shape: surfaceDefaults.shape,\n shadow: surfaceDefaults.shadow,\n border: surfaceDefaults.border,\n interactive: surfaceDefaults.interactive,\n padding: surfaceDefaults.padding,\n },\n compoundVariants: [\n {\n border: ['primary', 'secondary', 'tertiary', 'error', 'outline', 'current'],\n class: {\n wrapper: borderVariantsBase,\n },\n },\n ],\n});\n\nexport type SurfaceVariantProps = VariantProps<typeof surface>;\n\n/** Surface component props */\nexport interface SurfaceProps {\n /** HTML tag to render */\n tag?: string\n\n /** Surface role (surface or container) */\n role?: NonNullable<SurfaceVariantProps['role']>\n\n /** Surface tone (for role='surface': dim, base, bright) */\n tone?: NonNullable<SurfaceVariantProps['tone']>\n\n /** Container level (for role='container': lowest, low, base, high, highest) */\n level?: NonNullable<SurfaceVariantProps['level']>\n\n /** Semantic color variant (primary, secondary, tertiary, error, etc.) */\n variant?: NonNullable<SurfaceVariantProps['variant']>\n\n /** Shape variant */\n shape?: NonNullable<SurfaceVariantProps['shape']>\n\n /** Shadow elevation */\n shadow?: NonNullable<SurfaceVariantProps['shadow']>\n\n /** Border style */\n border?: NonNullable<SurfaceVariantProps['border']>\n\n /** Enable interactive states */\n interactive?: NonNullable<SurfaceVariantProps['interactive']>\n\n /** Padding size */\n padding?: NonNullable<SurfaceVariantProps['padding']>\n\n /** Additional CSS classes */\n class?: string\n}\n\n// Module augmentation for global defaults\ndeclare module '../composables/use-poupe' {\n interface PoupeComponentDefaults {\n surface?: Partial<SurfaceProps>\n }\n}\n</script>\n\n<script setup lang=\"ts\">\n/* global HTMLElement */\nimport { computed, ref } from 'vue';\nimport { usePoupeMergedProps } from '../composables';\n\n// Define props without withDefaults\nconst directProps = defineProps<SurfaceProps>();\n\n// Expose the rendered root DOM element so consumers (e.g. Button)\n// can attach refs/listeners to the real <button>/<div>/... rather\n// than to a layout-invisible wrapper.\nconst rootElement = ref<HTMLElement | undefined>();\ndefineExpose({ rootElement });\n\n// Merge with global defaults and component defaults\n// This provides three-level merging: global defaults < component defaults < props\nconst props = computed(() =>\n usePoupeMergedProps(directProps, 'surface', surfaceDefaults),\n);\n\nconst variants = computed(() => {\n const baseVariants = surface({\n role: props.value.role,\n tone: props.value.tone,\n level: props.value.level,\n variant: props.value.variant,\n shape: props.value.shape,\n shadow: props.value.shadow,\n border: props.value.border,\n interactive: props.value.interactive,\n padding: props.value.padding,\n });\n\n // If interactive, use the interactive surface variant\n if (props.value.interactive) {\n const role = props.value.role as 'container' | 'surface';\n const interactiveVariants = role === 'container' ?\n containerInteractiveVariants :\n surfaceInteractiveVariants;\n\n // Determine the color key based on role, tone/level, and variant\n let colorKey: string | undefined;\n if (props.value.variant) {\n colorKey = props.value.variant;\n } else if (role === 'surface' && props.value.tone) {\n colorKey = props.value.tone;\n } else if (role === 'container' && props.value.level) {\n colorKey = props.value.level;\n }\n\n if (colorKey && colorKey in interactiveVariants) {\n return {\n wrapper: () => twMerge(\n baseVariants.wrapper(),\n interactiveVariants[colorKey as keyof typeof interactiveVariants],\n ),\n };\n }\n }\n\n return baseVariants;\n});\n\nconst classes = computed(() =>\n twMerge(variants.value.wrapper(), props.value.class),\n);\n</script>\n\n<template>\n <component\n :is=\"props.tag\"\n ref=\"rootElement\"\n :class=\"classes\"\n >\n <slot />\n </component>\n</template>\n","<script lang=\"ts\">\nimport {\n tv,\n twMerge,\n type VariantProps,\n\n borderVariants,\n borderVariantsBase,\n containerInteractiveVariants,\n onSlot,\n shadowVariants,\n shapeVariants,\n surfaceInteractiveVariants,\n} from './variants';\n\n// Define defaults in one place\nconst surfaceDefaults = {\n tag: 'div',\n class: '',\n role: 'surface',\n tone: 'base',\n level: 'base',\n shape: 'none',\n shadow: 'none',\n border: 'none',\n interactive: false,\n padding: 'none',\n} as const;\n\nexport const surface = tv({\n slots: {\n wrapper: 'relative',\n },\n variants: {\n role: {\n surface: {},\n container: {},\n },\n // Surface tones (for role='surface')\n tone: {\n dim: { wrapper: 'surface-dim' },\n base: { wrapper: 'surface' },\n bright: { wrapper: 'surface-bright' },\n },\n // Container levels (for role='container')\n level: {\n lowest: { wrapper: 'surface-container-lowest' },\n low: { wrapper: 'surface-container-low' },\n base: { wrapper: 'surface-container' },\n high: { wrapper: 'surface-container-high' },\n highest: { wrapper: 'surface-container-highest' },\n },\n // Semantic color variants\n variant: {\n 'primary': { wrapper: 'surface-primary' },\n 'secondary': { wrapper: 'surface-secondary' },\n 'tertiary': { wrapper: 'surface-tertiary' },\n 'error': { wrapper: 'surface-error' },\n 'primary-fixed': { wrapper: 'surface-primary-fixed' },\n 'secondary-fixed': { wrapper: 'surface-secondary-fixed' },\n 'tertiary-fixed': { wrapper: 'surface-tertiary-fixed' },\n 'primary-container': { wrapper: 'surface-primary-container' },\n 'secondary-container': { wrapper: 'surface-secondary-container' },\n 'tertiary-container': { wrapper: 'surface-tertiary-container' },\n 'error-container': { wrapper: 'surface-error-container' },\n 'inverse': { wrapper: 'surface-inverse' },\n 'inverse-primary': { wrapper: 'surface-inverse-primary' },\n },\n shape: onSlot('wrapper', shapeVariants),\n shadow: onSlot('wrapper', shadowVariants),\n border: onSlot('wrapper', borderVariants),\n interactive: {\n true: {\n wrapper: 'cursor-pointer',\n },\n false: {},\n },\n padding: {\n none: {},\n sm: { wrapper: 'p-2' },\n md: { wrapper: 'p-4' },\n lg: { wrapper: 'p-6' },\n xl: { wrapper: 'p-8' },\n },\n },\n defaultVariants: {\n role: surfaceDefaults.role,\n tone: surfaceDefaults.tone,\n level: surfaceDefaults.level,\n shape: surfaceDefaults.shape,\n shadow: surfaceDefaults.shadow,\n border: surfaceDefaults.border,\n interactive: surfaceDefaults.interactive,\n padding: surfaceDefaults.padding,\n },\n compoundVariants: [\n {\n border: ['primary', 'secondary', 'tertiary', 'error', 'outline', 'current'],\n class: {\n wrapper: borderVariantsBase,\n },\n },\n ],\n});\n\nexport type SurfaceVariantProps = VariantProps<typeof surface>;\n\n/** Surface component props */\nexport interface SurfaceProps {\n /** HTML tag to render */\n tag?: string\n\n /** Surface role (surface or container) */\n role?: NonNullable<SurfaceVariantProps['role']>\n\n /** Surface tone (for role='surface': dim, base, bright) */\n tone?: NonNullable<SurfaceVariantProps['tone']>\n\n /** Container level (for role='container': lowest, low, base, high, highest) */\n level?: NonNullable<SurfaceVariantProps['level']>\n\n /** Semantic color variant (primary, secondary, tertiary, error, etc.) */\n variant?: NonNullable<SurfaceVariantProps['variant']>\n\n /** Shape variant */\n shape?: NonNullable<SurfaceVariantProps['shape']>\n\n /** Shadow elevation */\n shadow?: NonNullable<SurfaceVariantProps['shadow']>\n\n /** Border style */\n border?: NonNullable<SurfaceVariantProps['border']>\n\n /** Enable interactive states */\n interactive?: NonNullable<SurfaceVariantProps['interactive']>\n\n /** Padding size */\n padding?: NonNullable<SurfaceVariantProps['padding']>\n\n /** Additional CSS classes */\n class?: string\n}\n\n// Module augmentation for global defaults\ndeclare module '../composables/use-poupe' {\n interface PoupeComponentDefaults {\n surface?: Partial<SurfaceProps>\n }\n}\n</script>\n\n<script setup lang=\"ts\">\n/* global HTMLElement */\nimport { computed, ref } from 'vue';\nimport { usePoupeMergedProps } from '../composables';\n\n// Define props without withDefaults\nconst directProps = defineProps<SurfaceProps>();\n\n// Expose the rendered root DOM element so consumers (e.g. Button)\n// can attach refs/listeners to the real <button>/<div>/... rather\n// than to a layout-invisible wrapper.\nconst rootElement = ref<HTMLElement | undefined>();\ndefineExpose({ rootElement });\n\n// Merge with global defaults and component defaults\n// This provides three-level merging: global defaults < component defaults < props\nconst props = computed(() =>\n usePoupeMergedProps(directProps, 'surface', surfaceDefaults),\n);\n\nconst variants = computed(() => {\n const baseVariants = surface({\n role: props.value.role,\n tone: props.value.tone,\n level: props.value.level,\n variant: props.value.variant,\n shape: props.value.shape,\n shadow: props.value.shadow,\n border: props.value.border,\n interactive: props.value.interactive,\n padding: props.value.padding,\n });\n\n // If interactive, use the interactive surface variant\n if (props.value.interactive) {\n const role = props.value.role as 'container' | 'surface';\n const interactiveVariants = role === 'container' ?\n containerInteractiveVariants :\n surfaceInteractiveVariants;\n\n // Determine the color key based on role, tone/level, and variant\n let colorKey: string | undefined;\n if (props.value.variant) {\n colorKey = props.value.variant;\n } else if (role === 'surface' && props.value.tone) {\n colorKey = props.value.tone;\n } else if (role === 'container' && props.value.level) {\n colorKey = props.value.level;\n }\n\n if (colorKey && colorKey in interactiveVariants) {\n return {\n wrapper: () => twMerge(\n baseVariants.wrapper(),\n interactiveVariants[colorKey as keyof typeof interactiveVariants],\n ),\n };\n }\n }\n\n return baseVariants;\n});\n\nconst classes = computed(() =>\n twMerge(variants.value.wrapper(), props.value.class),\n);\n</script>\n\n<template>\n <component\n :is=\"props.tag\"\n ref=\"rootElement\"\n :class=\"classes\"\n >\n <slot />\n </component>\n</template>\n","<script lang=\"ts\">\nimport type { SurfaceProps } from './surface.vue';\n\n/** Material Design 3 button types */\nexport type ButtonType = 'elevated' | 'filled' | 'outlined' | 'text' | 'tonal';\n\n/** Button color variants */\nexport type ButtonVariant = 'base' | 'error' | 'primary' | 'secondary' | 'tertiary';\n\n/** Button size scale */\nexport type ButtonSize = 'base' | 'lg' | 'sm' | 'xl' | 'xs';\n\n/** Button component props */\nexport interface ButtonProps extends Omit<SurfaceProps, 'level' | 'padding' | 'role' | 'tone' | 'variant'> {\n /** Button label text */\n label?: string\n\n /** Material Design 3 button type */\n type?: ButtonType\n\n /** Button size */\n size?: ButtonSize\n\n /** Semantic color variant for the button */\n variant?: ButtonVariant\n\n /** Loading state */\n loading?: boolean\n\n /** Disabled state */\n disabled?: boolean\n\n /** Full width button */\n expand?: boolean\n\n /** Add ellipsis to label */\n ellipsis?: boolean\n\n /** Floating Action Button mode */\n fab?: boolean\n\n /** Extended FAB (only with fab=true) */\n extended?: boolean\n\n /** Icon button mode */\n iconButton?: boolean\n\n /** Toggle button mode */\n toggle?: boolean\n\n /** Toggle pressed state */\n pressed?: boolean\n\n /** Leading icon */\n icon?: string\n\n /** Trailing icon */\n trailingIcon?: string\n\n /** Icon start slot content */\n iconStart?: string\n\n /** Icon end slot content */\n iconEnd?: string\n}\n\n/** Button default props */\nexport const buttonDefaults: Partial<ButtonProps> = {\n type: 'filled',\n size: 'base',\n variant: 'primary',\n shape: 'md',\n disabled: false,\n loading: false,\n expand: false,\n ellipsis: false,\n fab: false,\n extended: false,\n iconButton: false,\n toggle: false,\n pressed: false,\n};\n\n// Augment the PoupeComponentDefaults interface\ndeclare module '../composables/use-poupe' {\n interface PoupeComponentDefaults {\n button?: {\n border?: ButtonProps['border']\n disabled?: ButtonProps['disabled']\n ellipsis?: ButtonProps['ellipsis']\n expand?: ButtonProps['expand']\n extended?: ButtonProps['extended']\n fab?: ButtonProps['fab']\n iconButton?: ButtonProps['iconButton']\n interactive?: ButtonProps['interactive']\n loading?: ButtonProps['loading']\n pressed?: ButtonProps['pressed']\n shadow?: ButtonProps['shadow']\n shape?: ButtonProps['shape']\n size?: ButtonProps['size']\n toggle?: ButtonProps['toggle']\n type?: ButtonType\n variant?: ButtonVariant\n }\n }\n}\n</script>\n\n<script setup lang=\"ts\">\n/* global MouseEvent, HTMLButtonElement, HTMLElement */\nimport { computed, onMounted, ref, useTemplateRef } from 'vue';\nimport { usePoupeMergedProps, useRipple } from '../composables';\nimport Icon from './icon.vue';\nimport Surface from './surface.vue';\n\n// Define props without withDefaults\nconst directProps = defineProps<ButtonProps>();\n\nconst emit = defineEmits<{\n click: [event: MouseEvent]\n}>();\n\n// Merge props with global defaults\nconst props = computed(() =>\n usePoupeMergedProps(directProps, 'button', buttonDefaults),\n);\n\n// Bind useRipple to the rendered <button> (forwarded by Surface),\n// not to an inner wrapper — listeners + bounding-rect measurements\n// must match what the user actually clicks.\nconst surfaceReference = useTemplateRef<{ rootElement: HTMLElement | undefined }>('surfaceReference');\nconst buttonElement = ref<HTMLButtonElement | undefined>();\nonMounted(() => {\n buttonElement.value = surfaceReference.value?.rootElement as HTMLButtonElement | undefined;\n});\n\nconst isDisabled = computed(() => props.value.disabled);\nconst { ripples, getRippleStyle } = useRipple(buttonElement, {\n disabled: isDisabled,\n});\n\n// Compute surface props based on button type\nconst surfaceProps = computed<Partial<SurfaceProps>>(() => {\n const { type, variant, shape, shadow, border, interactive } = props.value;\n\n // Base props\n const baseProps: Partial<SurfaceProps> = {\n shape,\n padding: 'none',\n interactive: interactive ?? true,\n };\n\n // FAB-specific defaults\n if (props.value.fab) {\n baseProps.shape = props.value.extended ? 'lg' : 'full';\n baseProps.shadow = shadow ?? 'z3';\n }\n\n // Icon button defaults\n if (props.value.iconButton && !props.value.fab) {\n baseProps.shape = shape ?? 'full';\n }\n\n // Map variant to appropriate border color for outlined type\n const getOutlineBorder = () => {\n if (border) return border;\n switch (variant) {\n case 'primary':\n case 'secondary':\n case 'tertiary':\n case 'error':\n return variant;\n default:\n return 'outline';\n }\n };\n\n // Apply type-specific props\n switch (type) {\n case 'text':\n return {\n ...baseProps,\n role: 'surface',\n tone: 'base',\n shadow: shadow ?? 'none',\n border: border ?? 'none',\n };\n\n case 'outlined':\n return {\n ...baseProps,\n role: 'surface',\n tone: 'base',\n shadow: shadow ?? 'none',\n border: getOutlineBorder(),\n };\n\n case 'filled':\n return {\n ...baseProps,\n variant: variant === 'base' ? undefined : variant,\n role: variant === 'base' ? 'surface' : undefined,\n tone: variant === 'base' ? 'base' : undefined,\n shadow: shadow ?? 'none',\n border: border ?? 'none',\n };\n\n case 'elevated':\n return {\n ...baseProps,\n role: 'surface',\n tone: 'base',\n shadow: shadow ?? 'z1',\n border: border ?? 'none',\n };\n\n case 'tonal': {\n // For tonal buttons, use container variant colors\n const tonalVariant = variant === 'base' ?\n undefined :\n `${variant}-container` as SurfaceProps['variant'];\n return {\n ...baseProps,\n variant: tonalVariant,\n role: variant === 'base' ? 'container' : undefined,\n level: variant === 'base' ? 'base' : undefined,\n shadow: shadow ?? 'none',\n border: border ?? 'none',\n };\n }\n\n default:\n return baseProps;\n }\n});\n\n// Compute button classes\nconst buttonClasses = computed(() => {\n const { size, expand, disabled, loading, fab, iconButton } = props.value;\n const classes: string[] = [\n 'inline-flex items-center justify-center gap-2',\n 'transition-all duration-200',\n 'select-none',\n 'font-medium',\n ];\n\n // Size classes\n const sizeClasses: Record<ButtonSize, string> = {\n xs: 'text-xs h-8 px-2',\n sm: 'text-sm h-9 px-3',\n base: 'text-sm h-10 px-6',\n lg: 'text-base h-12 px-7',\n xl: 'text-lg h-14 px-8',\n };\n\n // FAB size classes\n const fabSizeClasses: Record<ButtonSize, string> = {\n xs: 'h-10 w-10',\n sm: 'h-12 w-12',\n base: 'h-14 w-14',\n lg: 'h-16 w-16',\n xl: 'h-20 w-20',\n };\n\n // Extended FAB size classes\n const extendedFabSizeClasses: Record<ButtonSize, string> = {\n xs: 'h-10 px-3',\n sm: 'h-12 px-4',\n base: 'h-14 px-5',\n lg: 'h-16 px-6',\n xl: 'h-20 px-8',\n };\n\n // Icon button size classes\n const iconButtonSizeClasses: Record<ButtonSize, string> = {\n xs: 'h-8 w-8',\n sm: 'h-9 w-9',\n base: 'h-10 w-10',\n lg: 'h-12 w-12',\n xl: 'h-14 w-14',\n };\n\n // Apply size classes\n if (fab && !props.value.extended) {\n classes.push(fabSizeClasses[size]);\n } else if (fab && props.value.extended) {\n classes.push(extendedFabSizeClasses[size]);\n } else if (iconButton) {\n classes.push(iconButtonSizeClasses[size]);\n } else {\n classes.push(sizeClasses[size]);\n }\n\n // State classes\n if (disabled || loading) {\n classes.push('opacity-38 cursor-not-allowed');\n }\n\n if (expand) {\n classes.push('w-full');\n }\n\n return classes.join(' ');\n});\n\n// Compute label\nconst computedLabel = computed(() => {\n const label = props.value.label || '';\n return props.value.ellipsis ? `${label}…` : label;\n});\n\n// Icon size based on button size\nconst iconSize = computed(() => {\n const sizes: Record<ButtonSize, string> = {\n xs: 'text-base',\n sm: 'text-lg',\n base: 'text-xl',\n lg: 'text-2xl',\n xl: 'text-3xl',\n };\n return sizes[props.value.size];\n});\n</script>\n\n<template>\n <Surface\n ref=\"surfaceReference\"\n v-bind=\"surfaceProps\"\n tag=\"button\"\n :class=\"buttonClasses\"\n :disabled=\"props.disabled || props.loading\"\n @click=\"emit('click', $event)\"\n >\n <!-- Leading icon -->\n <Icon\n v-if=\"props.icon || props.iconStart\"\n :icon=\"props.icon || props.iconStart\"\n :class=\"iconSize\"\n />\n\n <!-- Label -->\n <span\n v-if=\"computedLabel && (!props.iconButton || props.extended)\"\n class=\"leading-none\"\n >\n {{ computedLabel }}\n </span>\n\n <!-- Default slot -->\n <slot v-if=\"!computedLabel && !props.iconButton\" />\n\n <!-- Trailing icon -->\n <Icon\n v-if=\"props.trailingIcon || props.iconEnd\"\n :icon=\"props.trailingIcon || props.iconEnd\"\n :class=\"iconSize\"\n />\n\n <!-- Loading indicator -->\n <Icon\n v-if=\"props.loading\"\n icon=\"svg-spinners:12-dots-scale-rotate\"\n :class=\"iconSize\"\n />\n\n <!-- Ripple particles -->\n <span\n v-for=\"r in ripples\"\n :key=\"r.id\"\n aria-hidden=\"true\"\n :style=\"getRippleStyle(r)\"\n />\n </Surface>\n</template>\n","<script lang=\"ts\">\nimport type { SurfaceProps } from './surface.vue';\n\n/** Material Design 3 button types */\nexport type ButtonType = 'elevated' | 'filled' | 'outlined' | 'text' | 'tonal';\n\n/** Button color variants */\nexport type ButtonVariant = 'base' | 'error' | 'primary' | 'secondary' | 'tertiary';\n\n/** Button size scale */\nexport type ButtonSize = 'base' | 'lg' | 'sm' | 'xl' | 'xs';\n\n/** Button component props */\nexport interface ButtonProps extends Omit<SurfaceProps, 'level' | 'padding' | 'role' | 'tone' | 'variant'> {\n /** Button label text */\n label?: string\n\n /** Material Design 3 button type */\n type?: ButtonType\n\n /** Button size */\n size?: ButtonSize\n\n /** Semantic color variant for the button */\n variant?: ButtonVariant\n\n /** Loading state */\n loading?: boolean\n\n /** Disabled state */\n disabled?: boolean\n\n /** Full width button */\n expand?: boolean\n\n /** Add ellipsis to label */\n ellipsis?: boolean\n\n /** Floating Action Button mode */\n fab?: boolean\n\n /** Extended FAB (only with fab=true) */\n extended?: boolean\n\n /** Icon button mode */\n iconButton?: boolean\n\n /** Toggle button mode */\n toggle?: boolean\n\n /** Toggle pressed state */\n pressed?: boolean\n\n /** Leading icon */\n icon?: string\n\n /** Trailing icon */\n trailingIcon?: string\n\n /** Icon start slot content */\n iconStart?: string\n\n /** Icon end slot content */\n iconEnd?: string\n}\n\n/** Button default props */\nexport const buttonDefaults: Partial<ButtonProps> = {\n type: 'filled',\n size: 'base',\n variant: 'primary',\n shape: 'md',\n disabled: false,\n loading: false,\n expand: false,\n ellipsis: false,\n fab: false,\n extended: false,\n iconButton: false,\n toggle: false,\n pressed: false,\n};\n\n// Augment the PoupeComponentDefaults interface\ndeclare module '../composables/use-poupe' {\n interface PoupeComponentDefaults {\n button?: {\n border?: ButtonProps['border']\n disabled?: ButtonProps['disabled']\n ellipsis?: ButtonProps['ellipsis']\n expand?: ButtonProps['expand']\n extended?: ButtonProps['extended']\n fab?: ButtonProps['fab']\n iconButton?: ButtonProps['iconButton']\n interactive?: ButtonProps['interactive']\n loading?: ButtonProps['loading']\n pressed?: ButtonProps['pressed']\n shadow?: ButtonProps['shadow']\n shape?: ButtonProps['shape']\n size?: ButtonProps['size']\n toggle?: ButtonProps['toggle']\n type?: ButtonType\n variant?: ButtonVariant\n }\n }\n}\n</script>\n\n<script setup lang=\"ts\">\n/* global MouseEvent, HTMLButtonElement, HTMLElement */\nimport { computed, onMounted, ref, useTemplateRef } from 'vue';\nimport { usePoupeMergedProps, useRipple } from '../composables';\nimport Icon from './icon.vue';\nimport Surface from './surface.vue';\n\n// Define props without withDefaults\nconst directProps = defineProps<ButtonProps>();\n\nconst emit = defineEmits<{\n click: [event: MouseEvent]\n}>();\n\n// Merge props with global defaults\nconst props = computed(() =>\n usePoupeMergedProps(directProps, 'button', buttonDefaults),\n);\n\n// Bind useRipple to the rendered <button> (forwarded by Surface),\n// not to an inner wrapper — listeners + bounding-rect measurements\n// must match what the user actually clicks.\nconst surfaceReference = useTemplateRef<{ rootElement: HTMLElement | undefined }>('surfaceReference');\nconst buttonElement = ref<HTMLButtonElement | undefined>();\nonMounted(() => {\n buttonElement.value = surfaceReference.value?.rootElement as HTMLButtonElement | undefined;\n});\n\nconst isDisabled = computed(() => props.value.disabled);\nconst { ripples, getRippleStyle } = useRipple(buttonElement, {\n disabled: isDisabled,\n});\n\n// Compute surface props based on button type\nconst surfaceProps = computed<Partial<SurfaceProps>>(() => {\n const { type, variant, shape, shadow, border, interactive } = props.value;\n\n // Base props\n const baseProps: Partial<SurfaceProps> = {\n shape,\n padding: 'none',\n interactive: interactive ?? true,\n };\n\n // FAB-specific defaults\n if (props.value.fab) {\n baseProps.shape = props.value.extended ? 'lg' : 'full';\n baseProps.shadow = shadow ?? 'z3';\n }\n\n // Icon button defaults\n if (props.value.iconButton && !props.value.fab) {\n baseProps.shape = shape ?? 'full';\n }\n\n // Map variant to appropriate border color for outlined type\n const getOutlineBorder = () => {\n if (border) return border;\n switch (variant) {\n case 'primary':\n case 'secondary':\n case 'tertiary':\n case 'error':\n return variant;\n default:\n return 'outline';\n }\n };\n\n // Apply type-specific props\n switch (type) {\n case 'text':\n return {\n ...baseProps,\n role: 'surface',\n tone: 'base',\n shadow: shadow ?? 'none',\n border: border ?? 'none',\n };\n\n case 'outlined':\n return {\n ...baseProps,\n role: 'surface',\n tone: 'base',\n shadow: shadow ?? 'none',\n border: getOutlineBorder(),\n };\n\n case 'filled':\n return {\n ...baseProps,\n variant: variant === 'base' ? undefined : variant,\n role: variant === 'base' ? 'surface' : undefined,\n tone: variant === 'base' ? 'base' : undefined,\n shadow: shadow ?? 'none',\n border: border ?? 'none',\n };\n\n case 'elevated':\n return {\n ...baseProps,\n role: 'surface',\n tone: 'base',\n shadow: shadow ?? 'z1',\n border: border ?? 'none',\n };\n\n case 'tonal': {\n // For tonal buttons, use container variant colors\n const tonalVariant = variant === 'base' ?\n undefined :\n `${variant}-container` as SurfaceProps['variant'];\n return {\n ...baseProps,\n variant: tonalVariant,\n role: variant === 'base' ? 'container' : undefined,\n level: variant === 'base' ? 'base' : undefined,\n shadow: shadow ?? 'none',\n border: border ?? 'none',\n };\n }\n\n default:\n return baseProps;\n }\n});\n\n// Compute button classes\nconst buttonClasses = computed(() => {\n const { size, expand, disabled, loading, fab, iconButton } = props.value;\n const classes: string[] = [\n 'inline-flex items-center justify-center gap-2',\n 'transition-all duration-200',\n 'select-none',\n 'font-medium',\n ];\n\n // Size classes\n const sizeClasses: Record<ButtonSize, string> = {\n xs: 'text-xs h-8 px-2',\n sm: 'text-sm h-9 px-3',\n base: 'text-sm h-10 px-6',\n lg: 'text-base h-12 px-7',\n xl: 'text-lg h-14 px-8',\n };\n\n // FAB size classes\n const fabSizeClasses: Record<ButtonSize, string> = {\n xs: 'h-10 w-10',\n sm: 'h-12 w-12',\n base: 'h-14 w-14',\n lg: 'h-16 w-16',\n xl: 'h-20 w-20',\n };\n\n // Extended FAB size classes\n const extendedFabSizeClasses: Record<ButtonSize, string> = {\n xs: 'h-10 px-3',\n sm: 'h-12 px-4',\n base: 'h-14 px-5',\n lg: 'h-16 px-6',\n xl: 'h-20 px-8',\n };\n\n // Icon button size classes\n const iconButtonSizeClasses: Record<ButtonSize, string> = {\n xs: 'h-8 w-8',\n sm: 'h-9 w-9',\n base: 'h-10 w-10',\n lg: 'h-12 w-12',\n xl: 'h-14 w-14',\n };\n\n // Apply size classes\n if (fab && !props.value.extended) {\n classes.push(fabSizeClasses[size]);\n } else if (fab && props.value.extended) {\n classes.push(extendedFabSizeClasses[size]);\n } else if (iconButton) {\n classes.push(iconButtonSizeClasses[size]);\n } else {\n classes.push(sizeClasses[size]);\n }\n\n // State classes\n if (disabled || loading) {\n classes.push('opacity-38 cursor-not-allowed');\n }\n\n if (expand) {\n classes.push('w-full');\n }\n\n return classes.join(' ');\n});\n\n// Compute label\nconst computedLabel = computed(() => {\n const label = props.value.label || '';\n return props.value.ellipsis ? `${label}…` : label;\n});\n\n// Icon size based on button size\nconst iconSize = computed(() => {\n const sizes: Record<ButtonSize, string> = {\n xs: 'text-base',\n sm: 'text-lg',\n base: 'text-xl',\n lg: 'text-2xl',\n xl: 'text-3xl',\n };\n return sizes[props.value.size];\n});\n</script>\n\n<template>\n <Surface\n ref=\"surfaceReference\"\n v-bind=\"surfaceProps\"\n tag=\"button\"\n :class=\"buttonClasses\"\n :disabled=\"props.disabled || props.loading\"\n @click=\"emit('click', $event)\"\n >\n <!-- Leading icon -->\n <Icon\n v-if=\"props.icon || props.iconStart\"\n :icon=\"props.icon || props.iconStart\"\n :class=\"iconSize\"\n />\n\n <!-- Label -->\n <span\n v-if=\"computedLabel && (!props.iconButton || props.extended)\"\n class=\"leading-none\"\n >\n {{ computedLabel }}\n </span>\n\n <!-- Default slot -->\n <slot v-if=\"!computedLabel && !props.iconButton\" />\n\n <!-- Trailing icon -->\n <Icon\n v-if=\"props.trailingIcon || props.iconEnd\"\n :icon=\"props.trailingIcon || props.iconEnd\"\n :class=\"iconSize\"\n />\n\n <!-- Loading indicator -->\n <Icon\n v-if=\"props.loading\"\n icon=\"svg-spinners:12-dots-scale-rotate\"\n :class=\"iconSize\"\n />\n\n <!-- Ripple particles -->\n <span\n v-for=\"r in ripples\"\n :key=\"r.id\"\n aria-hidden=\"true\"\n :style=\"getRippleStyle(r)\"\n />\n </Surface>\n</template>\n","/** @returns a typed array of keys of the object */\nexport const unsafeKeys = Object.keys as <T>(object: T) => Array<keyof T>;\n\n/**\n * Shape of `import.meta` we read at runtime. Vite injects\n * `import.meta.env.DEV` at build time; downstream type-check passes that\n * cross into this file (without `vite/client` augmentation) see `env`\n * as `undefined`, which is the safe no-op for `tryWarn`.\n */\ntype ViteImportMeta = {\n env?: {\n DEV?: boolean\n }\n};\n\n/**\n * Safely log a warning message in development mode.\n * @param message - The warning message to log\n */\nexport function tryWarn(message: string): void {\n if (typeof console === 'undefined' || !console.warn) return;\n const { env } = import.meta as ViteImportMeta;\n if (env?.DEV) console.warn(message);\n}\n","<script lang=\"ts\">\nimport type { SurfaceProps } from './surface.vue';\n\n/** Card component props */\nexport interface CardProps extends Omit<SurfaceProps, 'padding'> {\n /** Card title displayed in header */\n title?: string\n\n /** Convenience prop for surface colors */\n surface?: 'base' | 'bright' | 'container' | 'dim' |\n 'high' | 'highest' | 'low' |\n 'lowest'\n\n /** Convenience prop for container colors */\n container?: 'error' | 'primary' | 'secondary' | 'tertiary'\n}\n\n// Define defaults in one place\nconst cardDefaults = {\n title: undefined,\n surface: 'container',\n container: undefined,\n shape: 'md',\n shadow: 'z1',\n} as const;\n\n// Module augmentation for global defaults\ndeclare module '../composables/use-poupe' {\n interface PoupeComponentDefaults {\n card?: Partial<CardProps>\n }\n}\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, useSlots } from 'vue';\nimport { usePoupeMergedProps } from '../composables';\nimport { tryWarn } from '../utils/utils';\nimport Surface from './surface.vue';\n\n// Define props without withDefaults\nconst directProps = defineProps<CardProps>();\n\n// Merge with global defaults and component defaults\nconst props = computed(() =>\n usePoupeMergedProps(directProps, 'card', cardDefaults),\n);\n\n// Helper to map surface convenience prop to role and level/tone\nfunction mapSurfaceToRoleAndLevel(surface: CardProps['surface']) {\n switch (surface) {\n case 'lowest':\n case 'low':\n case 'container':\n case 'high':\n case 'highest':\n return {\n role: 'container' as const,\n level: surface === 'container' ? 'base' as const : surface,\n };\n case 'base':\n case 'dim':\n case 'bright':\n return {\n role: 'surface' as const,\n tone: surface,\n };\n default:\n return undefined;\n }\n}\n\n// Compute variant and color from surface/container props\nconst surfaceProps = computed(() => {\n // Warn if both surface and container are specified\n if (props.value.surface && props.value.container) {\n tryWarn(\n '[PCard] Both \"surface\" and \"container\" props are specified. ' +\n 'The \"container\" prop will take precedence. ' +\n 'Please use only one of these props.',\n );\n }\n\n const result: Partial<SurfaceProps> = {\n shape: props.value.shape,\n shadow: props.value.shadow,\n border: props.value.border,\n interactive: props.value.interactive,\n padding: 'none',\n };\n\n if (props.value.container) {\n result.variant = props.value.container;\n } else if (props.value.surface) {\n const mapped = mapSurfaceToRoleAndLevel(props.value.surface);\n if (mapped) {\n Object.assign(result, mapped);\n }\n } else {\n // Pass through any other Surface props\n if (props.value.role) result.role = props.value.role;\n if (props.value.tone) result.tone = props.value.tone;\n if (props.value.level) result.level = props.value.level;\n if (props.value.variant) result.variant = props.value.variant;\n }\n\n return result;\n});\n\nconst slots = useSlots();\nconst hasHeader = computed(() => props.value.title !== undefined || slots.header !== undefined);\nconst hasFooter = computed(() => slots.footer !== undefined);\n</script>\n\n<template>\n <Surface v-bind=\"surfaceProps\">\n <!-- Header -->\n <div\n v-if=\"hasHeader\"\n class=\"px-4 pt-4\"\n >\n <slot name=\"header\">\n <h3\n v-if=\"props.title\"\n class=\"text-xl font-medium\"\n >\n {{ props.title }}\n </h3>\n </slot>\n </div>\n\n <!-- Content -->\n <div class=\"px-4 py-4\">\n <slot />\n </div>\n\n <!-- Footer -->\n <div\n v-if=\"hasFooter\"\n class=\"px-4 pb-4\"\n >\n <slot name=\"footer\" />\n </div>\n </Surface>\n</template>\n","<script lang=\"ts\">\nimport type { SurfaceProps } from './surface.vue';\n\n/** Card component props */\nexport interface CardProps extends Omit<SurfaceProps, 'padding'> {\n /** Card title displayed in header */\n title?: string\n\n /** Convenience prop for surface colors */\n surface?: 'base' | 'bright' | 'container' | 'dim' |\n 'high' | 'highest' | 'low' |\n 'lowest'\n\n /** Convenience prop for container colors */\n container?: 'error' | 'primary' | 'secondary' | 'tertiary'\n}\n\n// Define defaults in one place\nconst cardDefaults = {\n title: undefined,\n surface: 'container',\n container: undefined,\n shape: 'md',\n shadow: 'z1',\n} as const;\n\n// Module augmentation for global defaults\ndeclare module '../composables/use-poupe' {\n interface PoupeComponentDefaults {\n card?: Partial<CardProps>\n }\n}\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, useSlots } from 'vue';\nimport { usePoupeMergedProps } from '../composables';\nimport { tryWarn } from '../utils/utils';\nimport Surface from './surface.vue';\n\n// Define props without withDefaults\nconst directProps = defineProps<CardProps>();\n\n// Merge with global defaults and component defaults\nconst props = computed(() =>\n usePoupeMergedProps(directProps, 'card', cardDefaults),\n);\n\n// Helper to map surface convenience prop to role and level/tone\nfunction mapSurfaceToRoleAndLevel(surface: CardProps['surface']) {\n switch (surface) {\n case 'lowest':\n case 'low':\n case 'container':\n case 'high':\n case 'highest':\n return {\n role: 'container' as const,\n level: surface === 'container' ? 'base' as const : surface,\n };\n case 'base':\n case 'dim':\n case 'bright':\n return {\n role: 'surface' as const,\n tone: surface,\n };\n default:\n return undefined;\n }\n}\n\n// Compute variant and color from surface/container props\nconst surfaceProps = computed(() => {\n // Warn if both surface and container are specified\n if (props.value.surface && props.value.container) {\n tryWarn(\n '[PCard] Both \"surface\" and \"container\" props are specified. ' +\n 'The \"container\" prop will take precedence. ' +\n 'Please use only one of these props.',\n );\n }\n\n const result: Partial<SurfaceProps> = {\n shape: props.value.shape,\n shadow: props.value.shadow,\n border: props.value.border,\n interactive: props.value.interactive,\n padding: 'none',\n };\n\n if (props.value.container) {\n result.variant = props.value.container;\n } else if (props.value.surface) {\n const mapped = mapSurfaceToRoleAndLevel(props.value.surface);\n if (mapped) {\n Object.assign(result, mapped);\n }\n } else {\n // Pass through any other Surface props\n if (props.value.role) result.role = props.value.role;\n if (props.value.tone) result.tone = props.value.tone;\n if (props.value.level) result.level = props.value.level;\n if (props.value.variant) result.variant = props.value.variant;\n }\n\n return result;\n});\n\nconst slots = useSlots();\nconst hasHeader = computed(() => props.value.title !== undefined || slots.header !== undefined);\nconst hasFooter = computed(() => slots.footer !== undefined);\n</script>\n\n<template>\n <Surface v-bind=\"surfaceProps\">\n <!-- Header -->\n <div\n v-if=\"hasHeader\"\n class=\"px-4 pt-4\"\n >\n <slot name=\"header\">\n <h3\n v-if=\"props.title\"\n class=\"text-xl font-medium\"\n >\n {{ props.title }}\n </h3>\n </slot>\n </div>\n\n <!-- Content -->\n <div class=\"px-4 py-4\">\n <slot />\n </div>\n\n <!-- Footer -->\n <div\n v-if=\"hasFooter\"\n class=\"px-4 pb-4\"\n >\n <slot name=\"footer\" />\n </div>\n </Surface>\n</template>\n","import {\n type DeepReadonly,\n type MaybeRefOrGetter,\n\n reactive,\n readonly,\n toValue,\n} from 'vue';\n\nimport { type IconifyIcon } from '@iconify/vue';\n\ntype IconValue = IconifyIcon | string;\n\nconst blankIcon: IconValue = {\n body: '',\n width: 24,\n height: 24,\n }, unknownIcon: IconValue = {\n body: '<text y=\".9em\" font-size=\"90\" fill=\"currentColor\">�</text>',\n width: 100,\n height: 100,\n };\n\ntype IconNames = 'blankIcon' | 'hidePassword' | 'showPassword' | 'spinner' | 'unknownIcon';\n\nconst defaultIcons: Record<IconNames, IconValue> = {\n blankIcon,\n unknownIcon