{"version":3,"file":"radio-group.cjs","names":[],"sources":["../src/inputs/radio-group/radio-group.ts"],"sourcesContent":["import {\n  bind,\n  createContext,\n  createStableId,\n  define,\n  getHost,\n  html,\n  onCleanup,\n  prop,\n  provide,\n  useEmit,\n  useField,\n  useSlots,\n  watchEffect,\n  when,\n} from '@vielzeug/ore';\nimport type { Readable } from '@vielzeug/ripple';\nimport {\n  type ChoiceChangeDetail,\n  createChoiceField,\n  createListControl,\n  getChoiceLabel,\n  getLightChildrenByTag,\n  lifecycleSignal,\n} from '../../core';\nimport { disablableBundle, sizableBundle, themableBundle } from '../../shared';\nimport { disabledStateMixin } from '../../styles';\nimport type { ComponentSize, ThemeColor } from '../../types';\nimport componentStyles from './radio-group.css?inline';\n\n/** Radio group component properties */\nexport type OreRadioGroupProps = {\n  /** Theme color tint */\n  color?: ThemeColor;\n  /** Disabled state */\n  disabled?: boolean;\n  /** Error message text */\n  error?: string;\n  /** Helper text displayed below the items */\n  helper?: string;\n  /** Group label text */\n  label?: string;\n  /** Form field name */\n  name?: string;\n  /** Layout orientation */\n  orientation?: 'horizontal' | 'vertical';\n  /** Required field */\n  required?: boolean;\n  /** Items size preset */\n  size?: ComponentSize;\n  /** Initial selected value */\n  value?: string;\n};\n\nexport type RadioGroupContext = {\n  color: Readable<ThemeColor | undefined>;\n  disabled: Readable<boolean>;\n  name: Readable<string | undefined>;\n  select: (value: string, originalEvent?: Event) => void;\n  size: Readable<ComponentSize | undefined>;\n  value: Readable<string | undefined>;\n};\n\nexport const RADIO_GROUP_CTX = createContext<RadioGroupContext | undefined>('OreRadioGroup');\n\n/** Events emitted by the radio-group component */\nexport type OreRadioGroupEvents = {\n  /** Emitted when the selection changes */\n  change: ChoiceChangeDetail;\n};\n\n/**\n * A group of radio buttons that allows users to select a single option from a set.\n * Supports keyboard navigation (arrows) and automatic value management.\n *\n * @element ore-radio-group\n *\n * @attr {string} value - Selected value\n * @attr {string} name - Form field name\n * @attr {string} label - Group label\n * @attr {string} orientation - Layout: 'vertical' | 'horizontal'\n * @attr {boolean} required - Required field\n *\n * @fires change - Emitted when a radio is selected. detail: { values: string[], labels: string[], originalEvent?: Event }\n *\n * @slot - Place `ore-radio` elements here\n *\n * @cssprop --radio-group-direction - Flex direction of the items list ('row' | 'column')\n * @cssprop --radio-group-gap - Gap between radio items\n * @part items - Items container.\n * @example\n * ```html\n * <ore-radio-group name=\"plan\" label=\"Choose a plan\" value=\"free\" required>\n *   <ore-radio value=\"free\">Free</ore-radio>\n *   <ore-radio value=\"pro\">Pro</ore-radio>\n *   <ore-radio value=\"enterprise\">Enterprise</ore-radio>\n * </ore-radio-group>\n * <ore-radio-group name=\"direction\" orientation=\"horizontal\" color=\"primary\">\n *   <ore-radio value=\"left\">Left</ore-radio>\n *   <ore-radio value=\"right\">Right</ore-radio>\n * </ore-radio-group>\n * ```\n */\nexport const RADIO_GROUP_TAG = 'ore-radio-group' as const;\ndefine<OreRadioGroupProps>(RADIO_GROUP_TAG, {\n  formAssociated: true,\n  props: {\n    ...themableBundle,\n    ...sizableBundle,\n    ...disablableBundle,\n    error: prop.string(),\n    helper: prop.string(),\n    label: prop.string(),\n    name: prop.string(),\n    orientation: prop.oneOf(['horizontal', 'vertical'] as const, 'vertical'),\n    required: prop.bool(false),\n    // Not auto-reflected (`reflect: false`) — the derived, interaction-updated selection\n    // (`selectedValue`) is the single writer for this attribute, via `bind()` below; letting\n    // `prop.string()`'s own default reflection also write the raw incoming value would leave\n    // two effects racing to set the same attribute from different sources.\n    value: { ...prop.string(), reflect: false },\n  },\n  setup(props) {\n    const el = getHost();\n    const emit = useEmit<OreRadioGroupEvents>();\n    const slots = useSlots();\n    const watch = watchEffect;\n\n    const choice = createChoiceField({\n      disabled: props.disabled,\n      error: props.error,\n      helper: props.helper,\n      label: props.label,\n      prefix: 'radio-group',\n      required: props.required,\n      signal: lifecycleSignal(onCleanup),\n      value: props.value,\n    });\n\n    choice.attachFormField(\n      useField<string>({\n        disabled: choice.disabled,\n        onReset: choice.reset,\n        toFormValue: (v) => v,\n        validationMessage: choice.validationMessage,\n        validity: choice.validity,\n        value: choice.formValue,\n      }),\n    );\n\n    const selectedValue = choice.selectedValue;\n    const isDisabled = props.disabled;\n\n    bind({\n      attr: {\n        size: props.size,\n        value: () => selectedValue.value || null,\n      },\n    });\n\n    const getSlottedRadios = (): HTMLElement[] => getLightChildrenByTag(el, 'ore-radio');\n\n    const getEnabledRadios = (): HTMLElement[] =>\n      isDisabled.value ? [] : getSlottedRadios().filter((radio) => !radio.hasAttribute('disabled'));\n\n    const getLabelForValue = (value: string): string => getChoiceLabel(getSlottedRadios(), value);\n\n    const selectRadio = (val: string, originalEvent?: Event): void => {\n      choice.setValues(val ? [val] : []);\n\n      const labels = val ? [getLabelForValue(val)] : [];\n      const values = val ? [val] : [];\n\n      emit('change', { labels, originalEvent, values });\n      choice.triggerValidation('blur');\n    };\n\n    provide(RADIO_GROUP_CTX, {\n      color: props.color as Readable<ThemeColor | undefined>,\n      disabled: isDisabled,\n      name: props.name,\n      select: selectRadio,\n      size: props.size as Readable<ComponentSize | undefined>,\n      value: selectedValue,\n    });\n\n    // Sync name/color/size/disabled/checked onto slotted ore-radio children.\n    watch(() => {\n      void slots.elements().value;\n      void selectedValue.value;\n\n      const radios = getSlottedRadios();\n\n      for (const radio of radios) {\n        const val = radio.getAttribute('value') ?? '';\n\n        radio.toggleAttribute('checked', val === selectedValue.value);\n\n        if (props.name.value) radio.setAttribute('name', props.name.value);\n        else radio.removeAttribute('name');\n\n        if (props.color.value) radio.setAttribute('color', props.color.value);\n        else radio.removeAttribute('color');\n\n        if (props.size.value) radio.setAttribute('size', props.size.value);\n        else radio.removeAttribute('size');\n\n        radio.toggleAttribute('disabled', isDisabled.value);\n      }\n    });\n\n    // Roving tabindex: only the selected (or first) radio is tabbable.\n    watch(() => {\n      void slots.elements().value;\n\n      const radios = getSlottedRadios();\n      let hasFocusable = false;\n\n      for (const radio of radios) {\n        const isSelected = radio.getAttribute('value') === selectedValue.value;\n\n        radio.setAttribute('tabindex', isSelected && !isDisabled.value ? '0' : '-1');\n\n        if (isSelected && !isDisabled.value) hasFocusable = true;\n      }\n\n      if (!hasFocusable && radios.length > 0) {\n        const first = radios.find((r) => !r.hasAttribute('disabled'));\n\n        if (first) first.setAttribute('tabindex', '0');\n      }\n    });\n\n    const listControl = createListControl<HTMLElement>({\n      getItems: getEnabledRadios,\n      keys: { next: ['ArrowDown', 'ArrowRight'], prev: ['ArrowUp', 'ArrowLeft'] },\n      loop: true,\n      onNavigate: ({ event, item }) => {\n        item.focus();\n\n        if (item.tagName === 'ORE-RADIO') {\n          selectRadio(item.getAttribute('value') ?? '', event);\n        }\n      },\n      signal: lifecycleSignal(onCleanup),\n    });\n\n    bind({\n      on: {\n        change: (e: Event) => {\n          if (e.target === el) return;\n\n          e.stopPropagation();\n          selectRadio((e.target as HTMLElement).getAttribute('value') ?? '', e);\n        },\n        keydown: (e: KeyboardEvent) => {\n          const radios = getEnabledRadios();\n\n          if (!radios.length) return;\n\n          const focused = radios.indexOf(document.activeElement as HTMLElement);\n\n          if (focused === -1) return;\n\n          listControl.set(focused);\n          listControl.handleKeydown(e);\n        },\n      },\n    });\n\n    const legendId = createStableId('radio-group-legend');\n\n    return html`\n      <fieldset\n        role=\"radiogroup\"\n        aria-required=\"${() => String(Boolean(props.required.value))}\"\n        aria-invalid=\"${choice.ariaInvalid}\"\n        aria-errormessage=\"${choice.ariaErrorMessage}\"\n        aria-describedby=\"${choice.ariaDescribedBy}\">\n        <legend id=\"${legendId}\" ?hidden=${() => !props.label.value}>\n          ${props.label}${when(\n            () => Boolean(props.required.value),\n            () => html`\n              <span aria-hidden=\"true\">*</span>\n            `,\n          )}\n        </legend>\n        <div class=\"radio-group-items\" part=\"items\">\n          <slot></slot>\n        </div>\n        <div\n          class=\"helper-text\"\n          part=\"helper\"\n          id=\"${choice.assistiveId}\"\n          role=\"${() => (choice.errorText.value ? 'alert' : null)}\"\n          aria-live=\"polite\"\n          ?hidden=\"${() => !choice.errorText.value && !choice.helperText.value}\">\n          ${() => choice.errorText.value || choice.helperText.value}\n        </div>\n      </fieldset>\n    `;\n  },\n  styles: [disabledStateMixin, componentStyles],\n});\n"],"mappings":"gXA+DA,IAAa,GAAA,EAAkB,EAAA,cAAA,CAA6C,eAAe,EAwC9E,EAAkB,mBAC/B,EAAA,EAAA,OAAA,CAA2B,EAAiB,CAC1C,eAAgB,GAChB,MAAO,CACL,GAAG,EAAA,eACH,GAAG,EAAA,cACH,GAAG,EAAA,iBACH,MAAO,EAAA,KAAK,OAAO,EACnB,OAAQ,EAAA,KAAK,OAAO,EACpB,MAAO,EAAA,KAAK,OAAO,EACnB,KAAM,EAAA,KAAK,OAAO,EAClB,YAAa,EAAA,KAAK,MAAM,CAAC,aAAc,UAAU,EAAY,UAAU,EACvE,SAAU,EAAA,KAAK,KAAK,EAAK,EAKzB,MAAO,CAAE,GAAG,EAAA,KAAK,OAAO,EAAG,QAAS,EAAM,CAC5C,EACA,MAAM,EAAO,CACX,IAAM,GAAA,EAAK,EAAA,QAAA,CAAQ,EACb,GAAA,EAAO,EAAA,QAAA,CAA6B,EACpC,GAAA,EAAQ,EAAA,SAAA,CAAS,EACjB,EAAQ,EAAA,YAER,EAAS,EAAA,kBAAkB,CAC/B,SAAU,EAAM,SAChB,MAAO,EAAM,MACb,OAAQ,EAAM,OACd,MAAO,EAAM,MACb,OAAQ,cACR,SAAU,EAAM,SAChB,OAAQ,EAAA,gBAAgB,EAAA,SAAS,EACjC,MAAO,EAAM,KACf,CAAC,EAED,EAAO,iBAAA,EACL,EAAA,SAAA,CAAiB,CACf,SAAU,EAAO,SACjB,QAAS,EAAO,MAChB,YAAc,GAAM,EACpB,kBAAmB,EAAO,kBAC1B,SAAU,EAAO,SACjB,MAAO,EAAO,SAChB,CAAC,CACH,EAEA,IAAM,EAAgB,EAAO,cACvB,EAAa,EAAM,UAEzB,EAAA,EAAA,KAAA,CAAK,CACH,KAAM,CACJ,KAAM,EAAM,KACZ,UAAa,EAAc,OAAS,IACtC,CACF,CAAC,EAED,IAAM,MAAwC,EAAA,sBAAsB,EAAI,WAAW,EAE7E,MACJ,EAAW,MAAQ,CAAC,EAAI,EAAiB,CAAC,CAAC,OAAQ,GAAU,CAAC,EAAM,aAAa,UAAU,CAAC,EAExF,EAAoB,GAA0B,EAAA,eAAe,EAAiB,EAAG,CAAK,EAEtF,GAAe,EAAa,IAAgC,CAChE,EAAO,UAAU,EAAM,CAAC,CAAG,EAAI,CAAC,CAAC,EAEjC,IAAM,EAAS,EAAM,CAAC,EAAiB,CAAG,CAAC,EAAI,CAAC,EAGhD,EAAK,SAAU,CAAE,SAAQ,gBAAe,OAFzB,EAAM,CAAC,CAAG,EAAI,CAAC,CAEiB,CAAC,EAChD,EAAO,kBAAkB,MAAM,CACjC,GAEA,EAAA,EAAA,QAAA,CAAQ,EAAiB,CACvB,MAAO,EAAM,MACb,SAAU,EACV,KAAM,EAAM,KACZ,OAAQ,EACR,KAAM,EAAM,KACZ,MAAO,CACT,CAAC,EAGD,MAAY,CACV,EAAW,SAAS,CAAC,CAAC,MACtB,EAAmB,MAEnB,IAAM,EAAS,EAAiB,EAEhC,IAAK,IAAM,KAAS,EAAQ,CAC1B,IAAM,EAAM,EAAM,aAAa,OAAO,GAAK,GAE3C,EAAM,gBAAgB,UAAW,IAAQ,EAAc,KAAK,EAExD,EAAM,KAAK,MAAO,EAAM,aAAa,OAAQ,EAAM,KAAK,KAAK,EAC5D,EAAM,gBAAgB,MAAM,EAE7B,EAAM,MAAM,MAAO,EAAM,aAAa,QAAS,EAAM,MAAM,KAAK,EAC/D,EAAM,gBAAgB,OAAO,EAE9B,EAAM,KAAK,MAAO,EAAM,aAAa,OAAQ,EAAM,KAAK,KAAK,EAC5D,EAAM,gBAAgB,MAAM,EAEjC,EAAM,gBAAgB,WAAY,EAAW,KAAK,CACpD,CACF,CAAC,EAGD,MAAY,CACV,EAAW,SAAS,CAAC,CAAC,MAEtB,IAAM,EAAS,EAAiB,EAC5B,EAAe,GAEnB,IAAK,IAAM,KAAS,EAAQ,CAC1B,IAAM,EAAa,EAAM,aAAa,OAAO,IAAM,EAAc,MAEjE,EAAM,aAAa,WAAY,GAAc,CAAC,EAAW,MAAQ,IAAM,IAAI,EAEvE,GAAc,CAAC,EAAW,QAAO,EAAe,GACtD,CAEA,GAAI,CAAC,GAAgB,EAAO,OAAS,EAAG,CACtC,IAAM,EAAQ,EAAO,KAAM,GAAM,CAAC,EAAE,aAAa,UAAU,CAAC,EAExD,GAAO,EAAM,aAAa,WAAY,GAAG,CAC/C,CACF,CAAC,EAED,IAAM,EAAc,EAAA,kBAA+B,CACjD,SAAU,EACV,KAAM,CAAE,KAAM,CAAC,YAAa,YAAY,EAAG,KAAM,CAAC,UAAW,WAAW,CAAE,EAC1E,KAAM,GACN,YAAa,CAAE,QAAO,UAAW,CAC/B,EAAK,MAAM,EAEP,EAAK,UAAY,aACnB,EAAY,EAAK,aAAa,OAAO,GAAK,GAAI,CAAK,CAEvD,EACA,OAAQ,EAAA,gBAAgB,EAAA,SAAS,CACnC,CAAC,GAED,EAAA,EAAA,KAAA,CAAK,CACH,GAAI,CACF,OAAS,GAAa,CAChB,EAAE,SAAW,IAEjB,EAAE,gBAAgB,EAClB,EAAa,EAAE,OAAuB,aAAa,OAAO,GAAK,GAAI,CAAC,EACtE,EACA,QAAU,GAAqB,CAC7B,IAAM,EAAS,EAAiB,EAEhC,GAAI,CAAC,EAAO,OAAQ,OAEpB,IAAM,EAAU,EAAO,QAAQ,SAAS,aAA4B,EAEhE,IAAY,KAEhB,EAAY,IAAI,CAAO,EACvB,EAAY,cAAc,CAAC,EAC7B,CACF,CACF,CAAC,EAED,IAAM,GAAA,EAAW,EAAA,eAAA,CAAe,oBAAoB,EAEpD,MAAO,GAAA,IAAI;;;6BAGgB,OAAO,EAAQ,EAAM,SAAS,KAAM,EAAE;wBAC7C,EAAO,YAAY;6BACd,EAAO,iBAAiB;4BACzB,EAAO,gBAAgB;sBAC7B,EAAS,gBAAkB,CAAC,EAAM,MAAM,MAAM;YACxD,EAAM,SAAA,EAAQ,EAAA,KAAA,KACR,EAAQ,EAAM,SAAS,UACvB,EAAA,IAAI;;aAGZ,EAAE;;;;;;;;gBAQI,EAAO,YAAY;sBACV,EAAO,UAAU,MAAQ,QAAU,KAAM;;yBAEvC,CAAC,EAAO,UAAU,OAAS,CAAC,EAAO,WAAW,MAAM;gBAC7D,EAAO,UAAU,OAAS,EAAO,WAAW,MAAM;;;KAIlE,EACA,OAAQ,CAAC,EAAA,mBAAoB,EAAA,OAAe,CAC9C,CAAC"}