{"version":3,"file":"button.cjs","names":[],"sources":["../src/inputs/button/button.ts"],"sourcesContent":["import { bind, define, getHost, html, inject, prop, useField } from '@vielzeug/ore';\nimport { computed } from '@vielzeug/ripple';\nimport { commonProps } from '../../shared';\nimport {\n  coarsePointerMixin,\n  colorThemeMixin,\n  disabledLoadingMixin,\n  forcedColorsMixin,\n  frostVariantMixin,\n  rainbowEffectMixin,\n  reducedMotionMixin,\n  roundedVariantMixin,\n  shineEffectMixin,\n  sizeVariantMixin,\n} from '../../styles';\nimport type { ButtonType, ComponentSize, LinkTarget, RoundedSize, ThemeColor } from '../../types';\nimport { useLinkProps } from '../../utils';\nimport { BUTTON_GROUP_CTX } from '../button-group/button-group';\nimport { useFormAction } from '../shared';\nimport componentStyles from './button.css?inline';\n\nexport const BUTTON_VARIANTS = ['solid', 'flat', 'bordered', 'outline', 'ghost', 'text', 'frost'] as const;\n\n/** Visual variant for ore-button — derived from BUTTON_VARIANTS for a single source of truth. */\nexport type ButtonVariant = (typeof BUTTON_VARIANTS)[number];\n\n/** Animated border effect for ore-button. */\nexport type ButtonEffect = 'shine' | 'rainbow';\n\n/** Button component properties */\nexport type OreButtonProps = {\n  /** Theme color */\n  color?: ThemeColor;\n  /** Disable interaction */\n  disabled?: boolean;\n  /** Animated border effect: 'shine' (color-aware neon sweep) or 'rainbow' */\n  effect?: ButtonEffect;\n  /**\n   * Full height button (100% of container) — the inner `[part=\"button\"]` fills its host's\n   * height instead of the size preset's fixed height. Useful when the button itself is the\n   * whole tappable surface of a container it doesn't otherwise control the size of (e.g. a\n   * swipe-revealed row action in `ore-list-item`'s `actions-left`/`actions-right` slots).\n   */\n  fullheight?: boolean;\n  /** Full width button (100% of container) */\n  fullwidth?: boolean;\n  /** When set, renders an `<a>` instead of `<button>` */\n  href?: string;\n  /** Icon-only mode (square aspect ratio, no padding) */\n  iconOnly?: boolean;\n  /** Accessible label for the inner button — required for icon-only buttons */\n  label?: string;\n  /** Show loading state with spinner */\n  loading?: boolean;\n  /** Link rel attribute (requires href) */\n  rel?: string;\n  /** Border radius size */\n  rounded?: RoundedSize;\n  /** Component size */\n  size?: ComponentSize;\n  /** Link target (requires href) */\n  target?: LinkTarget;\n  /** HTML button type attribute */\n  type?: ButtonType;\n  /** Visual style variant */\n  variant?: ButtonVariant;\n};\n\n/**\n * A customizable button component with multiple variants, sizes, and states.\n * Supports icons, loading states, and animated border effects.\n *\n * @element ore-button\n *\n * @attr {string} type - HTML button type: 'button' | 'submit' | 'reset'\n * @attr {boolean} disabled - Disable button interaction\n * @attr {boolean} loading - Show loading state with spinner\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {string} variant - Visual variant: 'solid' | 'flat' | 'bordered' | 'outline' | 'ghost' | 'text' | 'frost'\n * @attr {string} size - Button size: 'sm' | 'md' | 'lg'\n * @attr {string} rounded - Border radius: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | 'full'\n * @attr {string} effect - Animated border effect: 'shine' | 'rainbow'\n * @attr {boolean} icon-only - Icon-only mode (square aspect ratio, no padding)\n * @attr {boolean} fullwidth - Full width button (100% of container)\n * @attr {boolean} fullheight - Full height button (100% of container)\n *\n * @fires click - Emitted when button is clicked (unless disabled/loading)\n *\n * @slot - Button content (text, icons, etc.)\n * @slot prefix - Content before the button text (e.g., icons)\n * @slot suffix - Content after the button text (e.g., icons, badges)\n *\n * @part button - The inner button or anchor element\n * @part loader - The loading spinner element\n * @part content - The button content wrapper\n *\n * @cssprop --button-bg - Background color override\n * @cssprop --button-color - Text color override\n * @cssprop --button-border - Border width override\n * @cssprop --button-border-color - Border color override\n * @cssprop --button-radius - Border radius override\n * @cssprop --button-padding - Inner padding override\n * @cssprop --button-gap - Gap between icon and text override\n * @cssprop --button-font-size - Font size override\n * @cssprop --button-frost-active-bg - Background when pressed (frost variant)\n * @cssprop --button-frost-active-border-color - Border color when pressed (frost variant)\n *\n * @example\n * ```html\n * <ore-button variant=\"solid\" color=\"primary\">Click me</ore-button>\n * <ore-button loading color=\"success\">Processing...</ore-button>\n * <ore-button effect=\"shine\" color=\"primary\">Shine</ore-button>\n * <ore-button effect=\"rainbow\" variant=\"frost\">Rainbow</ore-button>\n * ```\n */\nexport const BUTTON_TAG = 'ore-button' as const;\ndefine<OreButtonProps>(BUTTON_TAG, {\n  formAssociated: true,\n  props: {\n    ...commonProps,\n    effect: prop.string<ButtonEffect>(),\n    fullheight: prop.bool(false),\n    fullwidth: prop.bool(false),\n    href: prop.string(),\n    iconOnly: prop.bool(false),\n    label: prop.string(),\n    rel: prop.string(),\n    target: prop.string<LinkTarget>(),\n    type: prop.oneOf(['button', 'submit', 'reset'] as const, 'button'),\n    variant: prop.oneOf(BUTTON_VARIANTS, 'solid'),\n  },\n  setup(props) {\n    const el = getHost();\n\n    // Prefer group context over own props for color/size/variant.\n    const groupCtx = inject(BUTTON_GROUP_CTX);\n    const effectiveColor = computed(() => groupCtx?.color.value ?? props.color.value);\n    const effectiveSize = computed(() => groupCtx?.size.value ?? props.size.value);\n    const effectiveVariant = computed(() => groupCtx?.variant.value ?? props.variant.value);\n\n    const isDisabled = computed(() => !!(props.disabled.value || props.loading.value));\n\n    // isLink and effectiveRel are computed from signals — correct even if href changes at runtime.\n    const { effectiveRel, isLink } = useLinkProps(props.href, props.rel, props.target);\n\n    // Form association: relay submit/reset clicks to the associated form.\n    // The inner <button> always has type=\"button\" so shadow DOM never drives native form actions.\n    // getForm() returns null for link mode at runtime, so no form actions fire.\n    const formField = useField({\n      disabled: isDisabled,\n      toFormValue: () => null,\n      value: computed(() => ''),\n    });\n\n    const handleClick = (e: MouseEvent) => {\n      if (isDisabled.value) {\n        e.preventDefault();\n\n        return;\n      }\n\n      // Link mode renders a real <a href> below — the browser handles left/middle/ctrl-click\n      // navigation, target, and rel natively. No manual window.open/location.href needed.\n      useFormAction(() => (isLink.value ? null : formField.internals.form), props.type, isDisabled, el)(e);\n    };\n\n    const handleKeydown = (e: KeyboardEvent) => {\n      if (e.key === 'Enter' || e.key === ' ') {\n        e.preventDefault();\n        (el.shadowRoot?.querySelector('[part=\"button\"]') as HTMLElement)?.click();\n      }\n    };\n\n    // ARIA attributes live on the host.\n    bind({\n      attr: {\n        'aria-busy': props.loading,\n        'aria-disabled': isDisabled,\n        'aria-label': props.label,\n        color: effectiveColor,\n        effect: props.effect,\n        role: computed(() => el.getAttribute('role') ?? (isLink.value ? 'link' : 'button')),\n        size: effectiveSize,\n        tabindex: computed(() => (isDisabled.value ? '-1' : '0')),\n        variant: effectiveVariant,\n      },\n      on: {\n        keydown: handleKeydown,\n      },\n    });\n\n    const buttonContent = html`\n      <span class=\"loader\" part=\"loader\" aria-label=\"Loading\" ?hidden=${() => !props.loading.value}></span>\n      <slot name=\"prefix\"></slot>\n      <span class=\"content\" part=\"content\"><slot></slot></span>\n      <slot name=\"suffix\"></slot>\n    `;\n\n    // The inner element (<a> or <span>) is deliberately non-focusable/decorative — this\n    // component's real interactive semantics (role, tabindex, aria-label) live on the\n    // custom-element host itself (see the `bind()` block above), since ore-button is\n    // formAssociated and needs the host, not an inner element, to carry ElementInternals.\n    // This differs from ore-navbar-item / ore-sidebar-item, which aren't form-associated\n    // and so render their <a> as the one real focusable/semantic element.\n    return html`\n      ${() =>\n        isLink.value\n          ? html`\n              <a\n                part=\"button\"\n                role=\"presentation\"\n                tabindex=\"-1\"\n                href=\"${props.href}\"\n                rel=\"${effectiveRel}\"\n                target=\"${props.target}\"\n                @click=\"${handleClick}\">\n                ${buttonContent}\n              </a>\n            `\n          : html`\n              <span part=\"button\" role=\"presentation\" @click=\"${handleClick}\">${buttonContent}</span>\n            `}\n    `;\n  },\n  shadow: { delegatesFocus: false },\n  styles: [\n    colorThemeMixin,\n    coarsePointerMixin,\n    reducedMotionMixin,\n    roundedVariantMixin,\n    forcedColorsMixin,\n    sizeVariantMixin({\n      lg: {\n        fontSize: 'var(--text-base)',\n        gap: 'var(--size-2-5)',\n        height: 'var(--size-12)',\n        iconSize: 'var(--size-6)',\n        lineHeight: 'var(--leading-relaxed)',\n        padding: 'var(--size-2-5) var(--size-5)',\n      },\n      sm: {\n        fontSize: 'var(--text-sm)',\n        gap: 'var(--size-1-5)',\n        height: 'var(--size-8)',\n        iconSize: 'var(--size-4)',\n        lineHeight: 'var(--leading-tight)',\n        padding: 'var(--size-1-5) var(--size-3)',\n      },\n    }),\n    frostVariantMixin('[part=\"button\"]'),\n    rainbowEffectMixin('[part=\"button\"]'),\n    shineEffectMixin('[part=\"button\"]'),\n    disabledLoadingMixin,\n    componentStyles,\n  ],\n});\n"],"mappings":"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"}