{
  "generatedFrom": "option-row.contract.json",
  "component": "Option row",
  "slug": "option-row",
  "group": "Settings",
  "tier": "leaf-lit",
  "summary": "The repeatable option/answer row — one bordered row with an inside drag handle, a borderless counted textarea, an optional correct toggle (circle or square) and per-option image, and a floating hover delete; marking correct paints the whole card success-green (SETTINGS-20/34/23/39).",
  "install": {
    "package": "@ahaslides-product/design",
    "registry": "https://npm.pkg.github.com",
    "scope": "@ahaslides-product",
    "auth": "Published to GitHub Packages — needs a GitHub token with read:packages. Configure the @ahaslides-product scope in .npmrc before installing.",
    "npmrc": "@ahaslides-product:registry=https://npm.pkg.github.com\n//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}   # a GitHub token with read:packages",
    "command": "npm i @ahaslides-product/design",
    "tokenLayer": "import '@ahaslides-product/design/tokens.css';",
    "import": "import '@ahaslides-product/design/aha-option-row';",
    "element": "aha-option-row",
    "registers": "aha-option-row"
  },
  "feeds": {
    "doc": "https://ahaslides-product.github.io/ahaslides-design/option-row/index.html",
    "md": "https://ahaslides-product.github.io/ahaslides-design/option-row/option-row.md",
    "agentJson": "https://ahaslides-product.github.io/ahaslides-design/option-row.agent.json",
    "llms": "https://ahaslides-product.github.io/ahaslides-design/option-row.llms.txt",
    "index": "https://ahaslides-product.github.io/ahaslides-design/llms.txt"
  },
  "recommendedSnippet": "html",
  "vibeCode": "Emit the HTML snippet by default — <aha-option-row> is a standard custom element that renders on open — React/Vue are thin adapters over the same element. Paste into an .html file and it renders with no build step.",
  "frameworks": {
    "react": {
      "via": "web-component",
      "ref": "aha-option-row"
    },
    "vue": {
      "via": "web-component",
      "ref": "aha-option-row"
    },
    "html": {
      "via": "web-component",
      "ref": "aha-option-row",
      "runnable": "paste-and-run — no build step, renders on open"
    }
  },
  "props": [
    {
      "name": "value",
      "type": "string",
      "default": "\"\"",
      "desc": "The option text"
    },
    {
      "name": "placeholder",
      "type": "string",
      "default": "Option text",
      "desc": "Empty-field hint"
    },
    {
      "name": "maxlength",
      "type": "number",
      "default": "—",
      "desc": "Character cap — turns the focus-only counter on"
    },
    {
      "name": "orderable",
      "type": "boolean",
      "default": "false",
      "desc": "Show the drag handle (order carries meaning) — omit for an unordered set (SETTINGS-39)"
    },
    {
      "name": "correctable",
      "type": "boolean",
      "default": "false",
      "desc": "Show the leading correct-answer checkbox (scored types)"
    },
    {
      "name": "correct",
      "type": "boolean",
      "default": "false",
      "desc": "Whether this option is marked correct — paints the whole card success-green + fills the toggle"
    },
    {
      "name": "checkboxShape",
      "type": "circle | square (attr checkboxshape)",
      "default": "circle",
      "desc": "The correct-answer toggle shape — a 20px outline circle, or a radius-6 square; both fill success-green with a white tick when correct"
    },
    {
      "name": "singleLine",
      "type": "boolean (attr singleline)",
      "default": "false",
      "desc": "Single-line answer — blocks Enter (no newline) and trims the value on blur"
    },
    {
      "name": "image",
      "type": "boolean",
      "default": "false",
      "desc": "Show the per-option image control"
    },
    {
      "name": "imagestate",
      "type": "empty | loading | filled",
      "default": "empty",
      "desc": "The image control's state"
    },
    {
      "name": "imagesrc",
      "type": "string",
      "default": "—",
      "desc": "The image thumbnail URL when filled"
    },
    {
      "name": "candelete",
      "type": "boolean",
      "default": "true",
      "desc": "false disables the delete at the minimum item count"
    },
    {
      "name": "input / correct-change / delete",
      "type": "CustomEvent",
      "default": "—",
      "desc": "Composed events; image-add/change/edit/delete are forwarded from the image control"
    }
  ],
  "tokens": [
    "border",
    "border-hover",
    "color-primary",
    "color-success",
    "border-success",
    "color-error",
    "bg-container",
    "bg-positive",
    "bg-elevated",
    "text-disabled",
    "text-inverse",
    "icon-muted",
    "checkbox-border",
    "radius-default",
    "radius-sm",
    "radius-pill",
    "focus-ring-soft",
    "btn-focus-ring-success",
    "ink-a10"
  ],
  "spec": [
    {
      "label": "Row",
      "value": "the ONE border — 1px #E3E3E3, radius 8; field inside is borderless (no double border)"
    },
    {
      "label": "Drag handle",
      "value": "system-drag, FIRST child inside the border; shown only when order matters (orderable)"
    },
    {
      "label": "Text",
      "value": "borderless counted textarea — focus-only counter, wraps, scrolls beyond ~4 lines (SETTINGS-21)"
    },
    {
      "label": "Correct toggle",
      "value": "optional (correctable), scored types only — 20px circle (default) or radius-6 square; fills --aha-color-success with a white tick when correct"
    },
    {
      "label": "Correct card",
      "value": "when correct the WHOLE row paints success-green: --aha-color-success border over a faint --aha-bg-positive tint fill"
    },
    {
      "label": "Single line",
      "value": "singleLine → Enter never inserts a newline; value is trimmed on blur (single-line answers)"
    },
    {
      "label": "Image",
      "value": "optional per-option ImageActionButton (image) — the full state set (SETTINGS-22)"
    },
    {
      "label": "Delete",
      "value": "floats -14px OUTSIDE the top-right, hover-only, disabled (not hidden) at min (candelete=false)"
    },
    {
      "label": "Focus",
      "value": "focus-within → #6A1EBB row border + ring (success-green ring when correct), on the one persistent border"
    }
  ],
  "opinion": {
    "whenToUse": [
      {
        "what": "Option row",
        "when": "a slide type's option / answer list — the \"Items\" list (SETTINGS-20)"
      },
      {
        "what": "Numbered item",
        "when": "a repeatable COMPOSITE item with its own body of fields (a Question)"
      }
    ],
    "note": "The row is the one border; the field inside is borderless — never a box-in-a-box (SETTINGS-34). The drag handle lives INSIDE the border and only when order matters (SETTINGS-39). The delete is hover-only and disables (not hides) at the minimum count (SETTINGS-23). No \"X of Y\" counter — the +Add disables at max and delete at min (SETTINGS-36).",
    "dsv3": "COVERED — the settings-lab OptionRow control mapped into the DS. It composes the DS borderless field, checkbox and image control into the reference option-row anatomy (SETTINGS-20/34/23/39)."
  },
  "surfaces": [
    "editor",
    "settings"
  ],
  "snippets": {
    "html": "<!-- Paste-and-run: save as .html and open in a browser. No build step.\n     <aha-option-row> is the SAME shared custom element React and Vue consume — here in its native form.\n     It COMPOSES DS leaves (borderless CountedTextarea, ImageActionButton) + a success-green correct\n     toggle (circle | square). Theming comes only from the --aha-* tokens in tokens.css. Hover a row to\n     reveal its delete (top-right). -->\n<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/tokens.css\">\n<script type=\"module\">\n  import 'https://cdn.jsdelivr.net/gh/ahaslides-product/ahaslides-design@master/lib/aha-option-row.js';   // registers <aha-option-row>\n</script>\n\n<div style=\"display:flex; flex-direction:column; gap:8px; max-width:420px\">\n  <!-- quiz answer: orderable + correct toggle (circle, default) + image; correct paints the card green.\n       delete disabled at the minimum of 2. Use checkboxshape=\"square\" for the square toggle,\n       and singleline for a single-line answer (Enter blocked, trims on blur). -->\n  <aha-option-row id=\"a\" orderable correctable image correct value=\"Paris\" maxlength=\"80\"></aha-option-row>\n  <aha-option-row orderable correctable image value=\"London\" maxlength=\"80\"></aha-option-row>\n  <aha-option-row orderable correctable checkboxshape=\"square\" singleline value=\"Rome\" maxlength=\"80\"></aha-option-row>\n</div>\n\n<script>\n  document.getElementById('a').addEventListener('input', (e) => console.log('text:', e.detail.value));\n  document.getElementById('a').addEventListener('correct-change', (e) => console.log('correct:', e.detail.correct));\n</script>\n",
    "react": "import '@ahaslides-product/design/aha-option-row'; // registers <aha-option-row>\nimport { useRef, useEffect } from 'react';\n\n// React 18 needs a thin wrapper; React 19 can use <aha-option-row> directly.\n// Composes DS leaves; forwards input / correct-change / delete (+ the image control's intents).\nfunction AhaOptionRow({ value, maxLength, orderable, correctable, correct, checkboxShape, singleLine, image, canDelete, onInput, onCorrect, onDelete }) {\n  const ref = useRef();\n  useEffect(() => {\n    const el = ref.current;\n    el.value = value ?? '';\n    if (correct) el.setAttribute('correct', ''); else el.removeAttribute('correct');\n    const hi = (e) => onInput?.(e.detail.value);\n    const hc = (e) => onCorrect?.(e.detail.correct);\n    const hd = () => onDelete?.();\n    el.addEventListener('input', hi); el.addEventListener('correct-change', hc); el.addEventListener('delete', hd);\n    return () => { el.removeEventListener('input', hi); el.removeEventListener('correct-change', hc); el.removeEventListener('delete', hd); };\n  });\n  return (\n    <aha-option-row\n      ref={ref}\n      {...(maxLength ? { maxlength: String(maxLength) } : {})}\n      {...(orderable ? { orderable: '' } : {})}\n      {...(correctable ? { correctable: '' } : {})}\n      {...(checkboxShape ? { checkboxshape: checkboxShape } : {})}\n      {...(singleLine ? { singleline: '' } : {})}\n      {...(image ? { image: '' } : {})}\n      {...(canDelete === false ? { candelete: 'false' } : {})}\n    />\n  );\n}\n\n// usage — one per option; disable delete at the minimum count\n{options.map((o, i) => (\n  <AhaOptionRow key={o.id} value={o.text} maxLength={80} orderable correctable image\n    checkboxShape=\"circle\" correct={o.correct} canDelete={options.length > 2}\n    onInput={(v) => update(i, v)} onCorrect={(c) => setCorrect(i, c)} onDelete={() => remove(i)} />\n))}\n",
    "vue": "// main.ts — register the element + mark aha-* as custom elements\nimport '@ahaslides-product/design/aha-option-row';   // registers <aha-option-row>\napp.config.compilerOptions.isCustomElement = (tag) => tag.startsWith('aha-');\n\n// Component.vue\n<script setup>\nimport { ref } from 'vue';\nconst options = ref([{ id: 1, text: 'Paris', correct: true }, { id: 2, text: 'London', correct: false }]);\nfunction remove(i) { options.value.splice(i, 1); }\n</script>\n\n<template>\n  <!-- one row per option; delete disables (not hides) at the minimum count -->\n  <aha-option-row\n    v-for=\"(o, i) in options\"\n    :key=\"o.id\"\n    orderable\n    correctable\n    image\n    checkboxshape=\"circle\"\n    :value.prop=\"o.text\"\n    maxlength=\"80\"\n    :correct=\"o.correct || undefined\"\n    :candelete=\"options.length > 2 ? 'true' : 'false'\"\n    @input=\"o.text = $event.detail.value\"\n    @correct-change=\"o.correct = $event.detail.correct\"\n    @delete=\"remove(i)\"\n  />\n</template>\n"
  }
}
