{"version":3,"file":"radio.cjs","names":[],"sources":["../src/inputs/radio/radio.ts"],"sourcesContent":["import { bind, define, getHost, html, inject, onCleanup, prop, useField } from '@vielzeug/ore';\nimport { computed } from '@vielzeug/ripple';\nimport { createCheckable, createListControl, elementDirection, lifecycleSignal } from '../../core';\nimport { CONTROL_SIZE_PRESET, disablableBundle, sizableBundle, themableBundle } from '../../shared';\nimport {\n  coarsePointerMixin,\n  colorThemeMixin,\n  disabledStateMixin,\n  forcedColorsFormControlMixin,\n  sizeVariantMixin,\n} from '../../styles';\nimport type { CheckableProps, ComponentSize, ThemeColor } from '../../types';\nimport { RADIO_GROUP_CTX } from '../radio-group/radio-group';\nimport { defineFieldChecked, dispatchNativeFieldEvent, setFieldChecked } from '../shared/native-field-event';\nimport { renderHelperRegion } from '../shared/templates';\nimport componentStyles from './radio.css?inline';\n\n/** Radio component properties */\n\nexport type OreRadioEvents = {\n  change: Event;\n  input: Event;\n};\n\nexport type OreRadioProps = CheckableProps & {\n  /** Theme color */\n  color?: ThemeColor;\n  /** Disable interaction */\n  disabled?: boolean;\n  /** Error message (marks field as invalid) */\n  error?: string;\n  /** Helper text displayed below the radio */\n  helper?: string;\n  /** Component size */\n  size?: ComponentSize;\n};\n\n/**\n * A customizable radio button component for mutually exclusive selections.\n *\n * @element ore-radio\n *\n * @attr {boolean} checked - Checked state\n * @attr {boolean} disabled - Disable radio interaction\n * @attr {string} value - Field value (required for radio groups)\n * @attr {string} name - Form field name (required for radio groups)\n * @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'\n * @attr {string} size - Radio size: 'sm' | 'md' | 'lg'\n * @attr {string} error - Error message (marks field as invalid)\n * @attr {string} helper - Helper text displayed below the radio\n *\n * @fires input - Emitted when radio is selected.\n * @fires change - Emitted when radio is selected.\n *\n * @slot - Radio button label text\n *\n * @cssprop --radio-size - Control size (width and height)\n * @cssprop --radio-bg - Unchecked background color\n * @cssprop --radio-border-color - Unchecked border color\n * @cssprop --radio-checked-bg - Selected indicator background color\n * @cssprop --radio-color - Selected indicator dot color\n * @cssprop --radio-font-size - Label font size\n * @part radio - The radio wrapper element\n * @part circle - The visual radio circle\n * @part label - The label element\n * @part helper-text - The helper/error text element\n *\n * @example\n * ```html\n * <ore-radio name=\"plan\" value=\"free\">Free</ore-radio>\n * <ore-radio name=\"plan\" value=\"pro\" checked color=\"primary\">Pro</ore-radio>\n * <ore-radio name=\"plan\" value=\"enterprise\" disabled>Enterprise</ore-radio>\n * ```\n */\nexport const RADIO_TAG = 'ore-radio' as const;\ndefine<OreRadioProps>(RADIO_TAG, {\n  formAssociated: true,\n  props: {\n    ...themableBundle,\n    ...sizableBundle,\n    ...disablableBundle,\n    checked: prop.bool(false),\n    error: prop.string(),\n    helper: prop.string(),\n    name: prop.string(),\n    value: prop.string(),\n  },\n  setup(props) {\n    const el = getHost();\n\n    const groupCtx = inject(RADIO_GROUP_CTX);\n    const effectiveName = computed(() => groupCtx?.name.value || props.name.value || '');\n    const effectiveSize = computed(() => groupCtx?.size.value ?? props.size.value);\n    const effectiveColor = computed(() => groupCtx?.color.value ?? props.color.value);\n    const checkedFromState = computed(() => {\n      if (groupCtx) return groupCtx.value.value === props.value.value;\n\n      return Boolean(props.checked.value);\n    });\n\n    const getRadioGroup = (): HTMLElement[] => {\n      const radioName = effectiveName.value;\n\n      if (!radioName) return [];\n\n      return Array.from(document.querySelectorAll<HTMLElement>(`ore-radio[name=\"${radioName}\"]`)).filter(\n        (r) => !r.hasAttribute('disabled'),\n      );\n    };\n\n    const selectRadio = (radio: HTMLElement, originalEvent?: Event): void => {\n      if (groupCtx) {\n        groupCtx.select(radio.getAttribute('value') ?? '', originalEvent);\n\n        return;\n      }\n\n      radio.click();\n    };\n\n    const listControl = createListControl({\n      direction: () => elementDirection(el),\n      getItems: () => getRadioGroup(),\n      // 'both' accepts every arrow key regardless of the group's visual layout (radio groups\n      // may render horizontally or vertically via CSS) — RTL mirroring of Left/Right still\n      // applies via `direction` since nav.ts's default keymap covers 'both' too.\n      loop: true,\n      onNavigate: ({ event, item }) => {\n        item.focus();\n        selectRadio(item, event);\n      },\n      orientation: 'both',\n      signal: lifecycleSignal(onCleanup),\n    });\n\n    const activateSelf = (originalEvent?: Event): void => {\n      if (checkable.checked.value) return;\n\n      if (groupCtx) {\n        groupCtx.select(props.value.value ?? '', originalEvent);\n\n        return;\n      }\n\n      checkable.toggle(originalEvent ?? new Event('change'));\n    };\n\n    const checkable = createCheckable({\n      checked: checkedFromState,\n      disabled: computed(() => props.disabled.value || Boolean(groupCtx?.disabled.value)),\n      error: props.error,\n      helper: props.helper,\n      onToggle: (payload) => {\n        setFieldChecked(el, payload.checked);\n        dispatchNativeFieldEvent(el, 'input');\n        dispatchNativeFieldEvent(el, 'change');\n      },\n      prefix: 'radio',\n      signal: lifecycleSignal(onCleanup),\n      value: props.value,\n    });\n\n    checkable.attachFormField(\n      useField<string | null>({\n        disabled: checkable.disabled,\n        toFormValue: (v) => v,\n        value: checkable.checkableFormValue,\n      }),\n    );\n\n    const { assistiveId, checked, disabled, errorText, helperText, labelId, toggle } = checkable;\n\n    defineFieldChecked(\n      el,\n      () => checked.value,\n      (value) => {\n        checked.value = value;\n      },\n    );\n\n    bind({\n      attr: {\n        ariaChecked: () => (checked.value ? 'true' : 'false'),\n        ariaDescribedby: () => (errorText.value || helperText.value ? assistiveId : null),\n        ariaDisabled: () => (disabled.value ? 'true' : null),\n        ariaInvalid: () => (errorText.value ? 'true' : null),\n        ariaLabelledby: labelId,\n        checked,\n        color: effectiveColor,\n        disabled: () => (disabled.value ? true : undefined),\n        name: () => effectiveName.value || undefined,\n        role: 'radio',\n        size: effectiveSize,\n        tabindex: () => {\n          if (disabled.value) return undefined;\n\n          return checked.value ? 0 : -1;\n        },\n      },\n      class: () => ({\n        'is-checked': checked.value,\n        'is-disabled': disabled.value,\n      }),\n      on: {\n        click: (e: MouseEvent) => {\n          if (disabled.value) return;\n\n          if (groupCtx) {\n            groupCtx.select(props.value.value ?? '', e);\n          } else {\n            if (!effectiveName.value) return;\n\n            if (!checked.value) {\n              const radioName = props.name.value;\n              const allRadios = document.querySelectorAll<HTMLElement>(`ore-radio[name=\"${radioName}\"]`);\n\n              allRadios.forEach((radio) => {\n                if (radio !== el) radio.removeAttribute('checked');\n              });\n\n              toggle(e);\n            }\n          }\n        },\n        keydown: (e: KeyboardEvent) => {\n          if (e.key === ' ' || e.key === 'Enter') {\n            e.preventDefault();\n            activateSelf(e);\n\n            return;\n          }\n\n          const radios = getRadioGroup();\n\n          if (radios.length === 0) return;\n\n          const selfIndex = radios.indexOf(el);\n\n          if (selfIndex === -1) return;\n\n          listControl.set(selfIndex);\n          listControl.handleKeydown(e);\n        },\n      },\n    });\n\n    return html`\n      <div class=\"radio-wrapper\" part=\"radio\">\n        <div class=\"circle\" part=\"circle\">\n          <div class=\"dot\" part=\"dot\"></div>\n        </div>\n      </div>\n      <span class=\"label\" part=\"label\" id=\"${labelId}\"><slot></slot></span>\n      ${renderHelperRegion(assistiveId, errorText, helperText)}\n    `;\n  },\n  styles: [\n    colorThemeMixin,\n    forcedColorsFormControlMixin,\n    disabledStateMixin,\n    coarsePointerMixin,\n    sizeVariantMixin(CONTROL_SIZE_PRESET),\n    componentStyles,\n  ],\n});\n"],"mappings":"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"}