{"version":3,"file":"copy-command.cjs","names":[],"sources":["../src/content/copy-command/copy-command.ts"],"sourcesContent":["import { define, html, onCleanup, prop, useEmit, useSlots } from '@vielzeug/ore';\nimport { computed, signal } from '@vielzeug/ripple';\nimport { srOnlyMixin } from '../../styles';\nimport type { ComponentSize, RoundedSize } from '../../types';\nimport '../icon/icon';\nimport componentStyles from './copy-command.css?inline';\n\n/** Copy command component properties */\nexport type OreCopyCommandProps = {\n  /** Border radius size */\n  rounded?: RoundedSize;\n  /** Component size */\n  size?: ComponentSize;\n  /** The command string to display and copy to clipboard */\n  value?: string;\n  /** Visual style variant */\n  variant?: 'flat' | 'bordered' | 'ghost';\n};\n\n/** Copy command component events */\nexport type OreCopyCommandEvents = {\n  /** Emitted when the command is successfully copied to the clipboard */\n  copy: { value: string };\n};\n\n/**\n * A styled, copy-to-clipboard command display component.\n * Shows a command string in a monospace code block with a copy button.\n * Clicking the component copies the value to the clipboard, shows a transient\n * check-mark confirmation, and emits a `copy` event.\n *\n * @element ore-copy-command\n *\n * @attr {string} value - The command string to display and copy\n * @attr {string} size - Component size: 'sm' | 'md' | 'lg'\n * @attr {string} variant - Visual style: 'flat' | 'bordered' | 'ghost'\n * @attr {string} rounded - Border radius: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | 'full'\n *\n * @fires copy - Emitted on successful clipboard write. detail: { value: string }\n *\n * @slot suffix - Optional controls appended after the copy button (e.g. a cycle button)\n *\n * @cssprop --copy-command-bg - Background color of the command block\n * @cssprop --copy-command-color - Text color of the command string\n * @cssprop --copy-command-border-color - Border color\n * @cssprop --copy-command-radius - Border radius (overrides the `rounded` attribute)\n * @cssprop --copy-command-padding - Inner padding of the command button\n * @cssprop --copy-command-font-size - Font size of the command string\n * @cssprop --copy-command-icon-color - Color of the copy/check icon\n * @cssprop --copy-command-hover-bg - Background on hover\n *\n * @part wrapper - Outer flex container\n * @part command - The copy button element\n * @part command-text - The `<code>` element displaying the command string\n * @part copy-icon - The icon wrapper span\n * @part suffix - Suffix slot container\n *\n * @example\n * ```html\n * <ore-copy-command value=\"npm install @vielzeug/ripple\"></ore-copy-command>\n *\n * <ore-copy-command value=\"npx -y @vielzeug/codex\" size=\"sm\" variant=\"bordered\"></ore-copy-command>\n *\n * <!-- With a suffix slot (e.g. a cycle button) -->\n * <ore-copy-command value=\"npm install @vielzeug/ripple\">\n *   <button slot=\"suffix\" type=\"button\" aria-label=\"Next package\">›</button>\n * </ore-copy-command>\n * ```\n */\nexport const COPY_COMMAND_TAG = 'ore-copy-command' as const;\n\ndefine<OreCopyCommandProps>(COPY_COMMAND_TAG, {\n  props: {\n    rounded: prop.string<RoundedSize>('md'),\n    size: prop.string<ComponentSize>('md'),\n    value: prop.string(''),\n    variant: prop.string<'flat' | 'bordered' | 'ghost'>('flat'),\n  },\n\n  setup(props) {\n    const emit = useEmit<OreCopyCommandEvents>();\n    const slots = useSlots();\n\n    const copied = signal(false);\n    const copyFailed = signal(false);\n    let resetTimer: ReturnType<typeof setTimeout> | null = null;\n\n    onCleanup(() => {\n      if (resetTimer !== null) clearTimeout(resetTimer);\n    });\n\n    const handleCopy = async () => {\n      const text = props.value.value ?? '';\n\n      if (!text) return;\n\n      if (resetTimer !== null) clearTimeout(resetTimer);\n\n      try {\n        await navigator.clipboard.writeText(text);\n        copied.value = true;\n        copyFailed.value = false;\n        emit('copy', { value: text });\n      } catch {\n        // Clipboard access denied or unavailable (e.g. insecure context) — surface it instead of failing silently\n        copied.value = false;\n        copyFailed.value = true;\n      }\n\n      resetTimer = setTimeout(() => {\n        copied.value = false;\n        copyFailed.value = false;\n        resetTimer = null;\n      }, 2000);\n    };\n\n    const hasSuffix = computed(() => slots.has('suffix').value);\n    const btnLabel = computed(() => {\n      if (copied.value) return 'Copied!';\n\n      if (copyFailed.value) return 'Copy failed — press to try again';\n\n      return `Copy: ${props.value.value ?? ''}`;\n    });\n\n    return html`\n      <div class=\"wrapper\" part=\"wrapper\">\n        <button class=\"command\" part=\"command\" type=\"button\" aria-label=\"${btnLabel}\" @click=${handleCopy}>\n          <code class=\"command-text\" part=\"command-text\">${props.value}</code>\n          <span class=\"copy-icon\" part=\"copy-icon\" aria-hidden=\"true\">\n            ${() => {\n              if (copied.value)\n                return html`\n                  <ore-icon name=\"check\" size=\"14\" class=\"icon-copied\" aria-hidden=\"true\"></ore-icon>\n                `;\n\n              if (copyFailed.value)\n                return html`\n                  <ore-icon name=\"circle-alert\" size=\"14\" class=\"icon-failed\" aria-hidden=\"true\"></ore-icon>\n                `;\n\n              return html`\n                <ore-icon name=\"copy\" size=\"14\" aria-hidden=\"true\"></ore-icon>\n              `;\n            }}\n          </span>\n        </button>\n        <div class=\"suffix\" part=\"suffix\" ?hidden=\"${() => !hasSuffix.value}\">\n          <slot name=\"suffix\"></slot>\n        </div>\n      </div>\n      <div role=\"status\" class=\"sr-only\">\n        ${() => {\n          if (copied.value) return 'Copied to clipboard.';\n\n          if (copyFailed.value) return 'Copy failed. Select the command text and copy it manually.';\n\n          return '';\n        }}\n      </div>\n    `;\n  },\n\n  styles: [srOnlyMixin, componentStyles],\n});\n"],"mappings":"kPAqEA,IAAa,EAAmB,oBAEhC,EAAA,EAAA,OAAA,CAA4B,EAAkB,CAC5C,MAAO,CACL,QAAS,EAAA,KAAK,OAAoB,IAAI,EACtC,KAAM,EAAA,KAAK,OAAsB,IAAI,EACrC,MAAO,EAAA,KAAK,OAAO,EAAE,EACrB,QAAS,EAAA,KAAK,OAAsC,MAAM,CAC5D,EAEA,MAAM,EAAO,CACX,IAAM,GAAA,EAAO,EAAA,QAAA,CAA8B,EACrC,GAAA,EAAQ,EAAA,SAAA,CAAS,EAEjB,GAAA,EAAS,EAAA,OAAA,CAAO,EAAK,EACrB,GAAA,EAAa,EAAA,OAAA,CAAO,EAAK,EAC3B,EAAmD,MAEvD,EAAA,EAAA,UAAA,KAAgB,CACV,IAAe,MAAM,aAAa,CAAU,CAClD,CAAC,EAED,IAAM,EAAa,SAAY,CAC7B,IAAM,EAAO,EAAM,MAAM,OAAS,GAE7B,KAEL,CAAI,IAAe,MAAM,aAAa,CAAU,EAEhD,GAAI,CACF,MAAM,UAAU,UAAU,UAAU,CAAI,EACxC,EAAO,MAAQ,GACf,EAAW,MAAQ,GACnB,EAAK,OAAQ,CAAE,MAAO,CAAK,CAAC,CAC9B,MAAQ,CAEN,EAAO,MAAQ,GACf,EAAW,MAAQ,EACrB,CAEA,EAAa,eAAiB,CAC5B,EAAO,MAAQ,GACf,EAAW,MAAQ,GACnB,EAAa,IACf,EAAG,GAAI,CAjByC,CAkBlD,EAEM,GAAA,EAAY,EAAA,SAAA,KAAe,EAAM,IAAI,QAAQ,CAAC,CAAC,KAAK,EACpD,GAAA,EAAW,EAAA,SAAA,KACX,EAAO,MAAc,UAErB,EAAW,MAAc,mCAEtB,SAAS,EAAM,MAAM,OAAS,IACtC,EAED,MAAO,GAAA,IAAI;;2EAE4D,EAAS,WAAW,EAAW;2DAC/C,EAAM,MAAM;;kBAGrD,EAAO,MACF,EAAA,IAAI;;kBAIT,EAAW,MACN,EAAA,IAAI;;kBAIN,EAAA,IAAI;;gBAGX;;;yDAG6C,CAAC,EAAU,MAAM;;;;;cAM9D,EAAO,MAAc,uBAErB,EAAW,MAAc,6DAEtB,GACP;;KAGR,EAEA,OAAQ,CAAC,EAAA,YAAa,EAAA,OAAe,CACvC,CAAC"}