{"version":3,"file":"file-input.cjs","names":[],"sources":["../src/inputs/file-input/file-input.ts"],"sourcesContent":["import { createDropZone } from '@vielzeug/dnd';\nimport {\n  bind,\n  createStableId,\n  define,\n  html,\n  onCleanup,\n  onElement,\n  onEvent,\n  onMounted,\n  prop,\n  ref,\n  useEmit,\n  useField,\n  when,\n} from '@vielzeug/ore';\nimport { computed, signal, watch } from '@vielzeug/ripple';\n\nimport '../../content/icon/icon';\nimport '../../feedback/progress/progress';\nimport { bindRefCallback, createInteraction } from '../../core';\nimport { FILE_INPUT_SIZE_PRESET } from '../../shared';\nimport {\n  coarsePointerMixin,\n  colorThemeMixin,\n  disabledLoadingMixin,\n  forcedColorsFocusMixin,\n  reducedMotionMixin,\n  roundedVariantMixin,\n  sizeVariantMixin,\n} from '../../styles';\nimport componentStyles from './file-input.css?inline';\nimport { createFileQueue, type FileUploadFn, formatBytes } from './file-input-upload';\n\nexport type { FileUploadFn, FileUploadState, FileUploadStatus } from './file-input-upload';\n\nconst isImageFile = (file: File): boolean => file.type.startsWith('image/');\n\n/** File input component properties */\nexport type OreFileInputProps = {\n  /** Accepted file types (comma-separated, e.g. '.jpg, .png, image/*') */\n  accept?: string;\n  /** Theme color tint */\n  color?: string;\n  /** Disabled state */\n  disabled?: boolean;\n  /** Error message text */\n  error?: string;\n  /**\n   * Render selected files as a grid of image thumbnails/previews instead of the default\n   * single-column list. Non-image files fall back to a generic file icon in the same grid.\n   */\n  gallery?: boolean;\n  /** Helper text displayed below the input */\n  helper?: string;\n  /** Input label text */\n  label?: string;\n  /** Max number of files allowed (only used if multiple is true) */\n  'max-files'?: number;\n  /** Max size of a single file in bytes */\n  'max-size'?: number;\n  /** Allow multiple files selection */\n  multiple?: boolean;\n  /** Form field name */\n  name?: string;\n  /**\n   * JS-only callback fired with the inner `<input type=\"file\">` element when it\n   * mounts, and with `null` when it unmounts. Intended for composed components\n   * that need imperative access to the raw element.\n   * Set as a JS property: `bitFileInput.ref = (el) => { ... }`.\n   */\n  ref?: ((el: HTMLInputElement | null) => void) | null;\n  /** Required field */\n  required?: boolean;\n  /** Field size preset */\n  size?: string;\n  /**\n   * JS-only upload transport (see `FileUploadFn`). When set, every newly added file starts\n   * uploading immediately and independently — progress/speed/ETA, retry-on-failure, and a\n   * success confirmation card are all driven from here. Omit it to keep the component in its\n   * original picker-only mode (select files, no upload lifecycle).\n   * Set as a JS property: `fileInput.upload = async (file, { onProgress, signal }) => { ... }`.\n   */\n  upload?: FileUploadFn | null;\n};\n\n/** Events emitted by the file-input component */\nexport type OreFileInputEvents = {\n  /** Emitted when files are added or removed */\n  change: { files: File[]; originalEvent?: Event; value: File[] };\n  /** Emitted when a specific file is removed */\n  remove: { file: File; files: File[]; originalEvent?: Event; value: File[] };\n  /** Emitted when `upload` rejects for a file (after a fresh attempt or a retry) */\n  'upload-error': { error: unknown; file: File };\n  /** Emitted whenever `upload`'s `onProgress` reports new bytes for a file */\n  'upload-progress': { file: File; loaded: number; total: number };\n  /** Emitted when `upload` resolves for a file */\n  'upload-success': { file: File };\n};\n\n/**\n * A file upload field with drag-and-drop support, built-in validation messaging, and — once a\n * `upload` transport is wired up — a full per-file upload lifecycle: live progress/speed/ETA,\n * retry-on-failure, a success confirmation card, and fully independent handling of concurrent\n * files (one failing never blocks or cancels the others). Picking files with no `upload` set\n * keeps the original, upload-free selection-only behavior.\n *\n * @element ore-file-input\n *\n * @attr {string} accept - Comma-separated file extensions or MIME types\n * @attr {boolean} multiple - Enable multiple files selection\n * @attr {boolean} gallery - Show selected files as an image thumbnail/preview grid\n * @attr {number} max-files - Max number of files allowed\n * @attr {number} max-size - Max size of each file in bytes\n * @attr {boolean} disabled - Disable interaction\n * @attr {string} error - Show an error state/message\n * @attr {string} helper - Provide helper context below the dropzone\n *\n * @fires change - detail: { files: File[], value: File[] }\n * @fires remove - detail: { file: File, files: File[] }\n * @fires upload-progress - detail: { file: File, loaded: number, total: number }\n * @fires upload-success - detail: { file: File }\n * @fires upload-error - detail: { file: File, error: unknown }\n *\n * @cssprop --file-input-bg - Dropzone background color\n * @cssprop --file-input-border-color - Dropzone border color\n * @cssprop --file-input-font-size - Font size\n * @cssprop --file-input-radius - Dropzone border radius\n * @cssprop --file-input-min-height - Minimum dropzone height\n * @cssprop --file-input-hover-bg - Dropzone background on hover (flat/ghost variants)\n * @cssprop --file-input-hover-border-color - Dropzone border on hover (flat/bordered variants)\n * @cssprop --file-input-focus-bg - Dropzone background when focused/drag-over (flat variant)\n * @cssprop --file-input-focus-border-color - Dropzone border when focused/drag-over (flat variant)\n * @cssprop --file-input-thumb-size - Gallery thumbnail width/height\n * @part wrapper - Root wrapper around the file input field\n * @part label - Visible label rendered above the dropzone\n * @part dropzone - Interactive drag-and-drop target\n * @part input - Native file input element\n * @part gallery - Gallery grid container (rendered instead of `file-list` when `gallery` is set)\n * @part helper - Helper text shown beneath the dropzone\n * @part error - Error message shown beneath the field\n * @example\n * ```html\n * <ore-file-input label=\"Upload files\" accept=\"image/*\" multiple />\n * <ore-file-input label=\"Photos\" accept=\"image/*\" multiple gallery />\n * <ore-file-input label=\"Resume\" accept=\".pdf,.doc,.docx\" max-size=\"5242880\" />\n * <ore-file-input variant=\"bordered\" color=\"primary\" />\n * ```\n * ```ts\n * // Wire up a real upload transport — progress/retry/success are then handled automatically.\n * // `XMLHttpRequest` is used here (not `fetch`) because it's the only browser API that reports\n * // upload progress; swap in whatever transport the app already uses.\n * const input = document.querySelector('ore-file-input');\n * input.upload = (file, { onProgress, signal, resumeFrom }) =>\n *   new Promise((resolve, reject) => {\n *     const xhr = new XMLHttpRequest();\n *\n *     xhr.upload.addEventListener('progress', (e) => onProgress(resumeFrom + e.loaded, file.size));\n *     xhr.addEventListener('load', () => (xhr.status < 400 ? resolve() : reject(new Error(xhr.statusText))));\n *     xhr.addEventListener('error', () => reject(new Error('Network error')));\n *     signal.addEventListener('abort', () => xhr.abort());\n *\n *     xhr.open('PUT', '/uploads');\n *     if (resumeFrom > 0) xhr.setRequestHeader('Content-Range', `bytes ${resumeFrom}-/${file.size}`);\n *     xhr.send(file);\n *   });\n * ```\n */\nexport const FILE_INPUT_TAG = 'ore-file-input' as const;\ndefine<OreFileInputProps>(FILE_INPUT_TAG, {\n  formAssociated: true,\n  props: {\n    accept: prop.string(),\n    color: prop.string(),\n    disabled: prop.bool(false),\n    error: prop.string(),\n    gallery: prop.bool(false),\n    helper: prop.string(),\n    label: prop.string(),\n    'max-files': prop.number(0),\n    'max-size': prop.number(0),\n    multiple: prop.bool(false),\n    name: prop.string(),\n    ref: prop.data<((el: HTMLInputElement | null) => void) | null>(),\n    required: prop.bool(false),\n    size: prop.string(),\n    upload: prop.data<FileUploadFn>(),\n  },\n  setup(props) {\n    const emit = useEmit<OreFileInputEvents>();\n\n    // ============================================\n    // State\n    // ============================================\n\n    const isDragging = signal(false);\n    // Set while a \"Replace\" action is waiting for the (shared) native file input's next\n    // `change` — routes that selection into `queue.replaceFile()` instead of `queue.addFiles()`.\n    const replaceTarget = signal<File | null>(null);\n\n    const isDisabled = computed(() => Boolean(props.disabled.value));\n    const maxFilesLimit = computed(() => props['max-files'].value ?? 0);\n    const maxSizeLimit = computed(() => props['max-size'].value ?? 0);\n\n    // File selection + the opt-in upload lifecycle (progress, retry, replace, per-file\n    // isolation) live in `createFileQueue` — see file-input-upload.ts for why they're one unit.\n    const queue = createFileQueue({\n      accept: props.accept,\n      disabled: isDisabled,\n      maxFiles: maxFilesLimit,\n      maxSize: maxSizeLimit,\n      multiple: computed(() => Boolean(props.multiple.value)),\n      onChange: (changedFiles, originalEvent) =>\n        emit('change', { files: changedFiles, originalEvent, value: changedFiles }),\n      onRemove: (file, remainingFiles, originalEvent) =>\n        emit('remove', { file, files: remainingFiles, originalEvent, value: remainingFiles }),\n      onUploadError: (file, error) => emit('upload-error', { error, file }),\n      onUploadProgress: (file, loaded, total) => emit('upload-progress', { file, loaded, total }),\n      onUploadSuccess: (file) => emit('upload-success', { file }),\n      upload: computed(() => props.upload.value ?? undefined),\n    });\n\n    onCleanup(() => queue.dispose());\n\n    // ============================================\n    // Form Integration\n    // ============================================\n\n    useField({\n      disabled: isDisabled,\n      toFormValue: (fi: File[]) => {\n        if (fi.length === 0) return null;\n\n        const name = props.name.value || 'file';\n        const fd = new FormData();\n\n        for (const file of fi) fd.append(name, file);\n\n        return fd;\n      },\n      value: queue.files,\n    });\n\n    // Sync host attributes for CSS selectors\n    const isInvalid = computed(() => Boolean(props.error.value));\n\n    bind({\n      attr: {\n        'drag-over': () => (isDragging.value ? true : undefined),\n        invalid: () => (isInvalid.value ? true : undefined),\n        size: props.size,\n      },\n    });\n\n    // ============================================\n    // IDs\n    // ============================================\n    const fileInputId = createStableId('file-input');\n    const labelId = `label-${fileInputId}`;\n    const helperId = `helper-${fileInputId}`;\n    const errorId = `error-${fileInputId}`;\n\n    // ============================================\n    // Refs\n    // ============================================\n    const dropzoneRef = ref<HTMLDivElement>();\n    const inputRef = ref<HTMLInputElement>();\n    const hintText = computed(() => {\n      const parts: string[] = [];\n\n      if (props.accept.value) {\n        parts.push(\n          props.accept.value\n            .split(',')\n            .map((s: string) => s.trim())\n            .join(', '),\n        );\n      }\n\n      const maxSize = maxSizeLimit.value;\n\n      if (maxSize > 0) parts.push(`max ${formatBytes(maxSize)}`);\n\n      const maxFiles = maxFilesLimit.value;\n\n      if (maxFiles > 0) parts.push(`up to ${maxFiles} file${maxFiles !== 1 ? 's' : ''}`);\n\n      return parts.join(' · ');\n    });\n\n    // ============================================\n    // Replace (\"swap this one file\" — see file-input-upload.ts's `replaceFile`)\n    // ============================================\n    function beginReplace(file: File): void {\n      replaceTarget.value = file;\n      inputRef.value?.click();\n    }\n\n    // ============================================\n    // Gallery Preview URLs\n    // ============================================\n    // Object URLs are created lazily (only while `gallery` is enabled) and revoked as soon\n    // as their file is no longer selected, plus unconditionally on disconnect — otherwise\n    // each preview leaks its backing blob for the life of the page.\n    const previewUrls = new Map<File, string>();\n\n    function getPreviewUrl(file: File): string {\n      let url = previewUrls.get(file);\n\n      if (!url) {\n        url = URL.createObjectURL(file);\n        previewUrls.set(file, url);\n      }\n\n      return url;\n    }\n\n    function revokeStalePreviewUrls(activeFiles: File[]): void {\n      const keep = new Set(activeFiles);\n\n      for (const [file, url] of previewUrls) {\n        if (!keep.has(file)) {\n          URL.revokeObjectURL(url);\n          previewUrls.delete(file);\n        }\n      }\n    }\n\n    watch(queue.files, revokeStalePreviewUrls);\n\n    onCleanup(() => {\n      for (const url of previewUrls.values()) URL.revokeObjectURL(url);\n\n      previewUrls.clear();\n    });\n\n    // ============================================\n    // Mount\n    // ============================================\n    // ============================================\n    // Template\n    // ============================================\n    onElement(inputRef, (inp) => bindRefCallback(props.ref, inp));\n\n    onMounted(() => {\n      const inp = inputRef.value!;\n      const dz = dropzoneRef.value!;\n      let skipNextClick = false;\n      const pressControl = createInteraction({\n        disabled: isDisabled,\n        onPress: () => {\n          inp.click();\n        },\n      });\n\n      // Native input → add files, or — if a \"Replace\" action opened the picker — swap the one\n      // file it targeted instead of appending a new entry.\n      onEvent(inp, 'change', (e: Event) => {\n        const input = e.target as HTMLInputElement;\n        const target = replaceTarget.value;\n\n        replaceTarget.value = null;\n\n        if (target && input.files?.[0]) queue.replaceFile(target, input.files[0], e);\n        else if (input.files?.length) queue.addFiles(Array.from(input.files), e);\n\n        input.value = ''; // reset so the same file triggers change again\n      });\n      // Click dropzone → open file picker\n      onEvent(dz, 'click', (e: MouseEvent) => {\n        if (e.target === inp) return;\n\n        if (skipNextClick) {\n          skipNextClick = false;\n\n          return;\n        }\n\n        if (!isDisabled.value) inp.click();\n      });\n      // Keyboard: Enter / Space → open picker\n      onEvent(dz, 'keydown', (e: KeyboardEvent) => {\n        skipNextClick = pressControl.handleKeydown(e) && e.key === 'Enter';\n      });\n\n      // `createDropZone` has no way to update `disabled` after creation — recreate the zone\n      // whenever the prop changes instead of capturing a stale snapshot from this one `onMounted`\n      // run. `watch`'s own returned cleanup (not a second `onCleanup`) disposes the current zone\n      // both on the next toggle and on final teardown.\n      watch(\n        isDisabled,\n        (disabled) => {\n          const dropZone = createDropZone({\n            disabled,\n            element: dz,\n            onDrop: (droppedFiles) => queue.addFiles(droppedFiles),\n            onHoverChange: (hovered) => {\n              isDragging.value = hovered;\n            },\n          });\n\n          return () => dropZone.dispose();\n        },\n        { immediate: true },\n      );\n    });\n\n    return html`\n      <div class=\"file-input-wrapper\" part=\"wrapper\">\n        <label class=\"label-outside\" id=\"${labelId}\" part=\"label\" ?hidden=${() => !props.label.value}>\n          ${props.label}\n        </label>\n        <div\n          class=\"dropzone\"\n          part=\"dropzone\"\n          ref=${dropzoneRef}\n          role=\"button\"\n          tabindex=\"${() => (isDisabled.value ? '-1' : '0')}\"\n          aria-disabled=\"${() => String(isDisabled.value)}\"\n          aria-label=\"${() => (!props.label.value ? 'File upload drop zone' : null)}\"\n          aria-labelledby=\"${() => (props.label.value ? labelId : null)}\"\n          aria-describedby=\"${helperId}\">\n          <input\n            type=\"file\"\n            ref=${inputRef}\n            part=\"input\"\n            id=\"${fileInputId}\"\n            accept=\"${props.accept}\"\n            ?multiple=\"${props.multiple}\"\n            ?required=\"${props.required}\"\n            ?disabled=\"${isDisabled}\"\n            name=\"${props.name}\"\n            hidden\n            inert\n            tabindex=\"-1\" />\n          <div class=\"dropzone-content\">\n            <span class=\"dropzone-icon\" aria-hidden=\"true\" data-status=\"${() => (isInvalid.value ? 'error' : null)}\">\n              ${() =>\n                isInvalid.value\n                  ? html`\n                      <ore-icon name=\"alert-circle\" size=\"36\" stroke-width=\"1.5\" aria-hidden=\"true\"></ore-icon>\n                    `\n                  : html`\n                      <ore-icon name=\"upload\" size=\"36\" stroke-width=\"1.5\" aria-hidden=\"true\"></ore-icon>\n                    `}\n            </span>\n            <!-- Signal 01: the copy itself shifts on drag-entry (not just border/glow) — a\n                 static \"Drop files here\" during an active drag reads as if the drop target\n                 hasn't noticed the file yet. -->\n            ${when(\n              () => isDragging.value,\n              () => html`\n                <span class=\"dropzone-title dropzone-title-active\">Release to upload</span>\n              `,\n            )}\n            ${when(\n              () => !isDragging.value,\n              () => html`\n                <span class=\"dropzone-title\">\n                  Drop files here or\n                  <u>click to browse</u>\n                </span>\n              `,\n            )}\n            <span class=\"dropzone-hint\" ?hidden=${() => !hintText.value}>${hintText}</span>\n          </div>\n        </div>\n        <ul\n          class=\"${() => (props.gallery.value ? 'file-grid' : 'file-list')}\"\n          part=\"${() => (props.gallery.value ? 'gallery' : null)}\"\n          role=\"list\"\n          aria-label=\"Selected files\"\n          ?hidden=${() => queue.files.value.length === 0}>\n          ${() =>\n            queue.files.value.map((file: File) =>\n              props.gallery.value\n                ? html`\n                    <li class=\"file-card\" data-status=${() => queue.fileState(file).status}>\n                      <span class=\"file-thumb-frame\">\n                        ${\n                          // Decorative: the file name is already announced via the visible\n                          // `.file-card-name` caption below — repeating it as `alt` text would\n                          // duplicate it (and often trips redundant-alt checks, since real\n                          // filenames commonly contain words like \"photo\" or \"image\").\n                          isImageFile(file)\n                            ? // Object URLs use the `blob:` scheme, which ore's attribute-level\n                              // XSS guard blocks unconditionally on `src` (and other\n                              // URL-accepting attributes) — set it as a DOM property via `ref`\n                              // instead, bypassing that string-based check. Safe here: the value\n                              // comes from `URL.createObjectURL(file)` on a real `File` object we\n                              // control, never from untrusted text.\n                              html`\n                                <img\n                                  class=\"file-thumb\"\n                                  alt=\"\"\n                                  ref=\"${(el: HTMLImageElement | null) => {\n                                    if (el) el.src = getPreviewUrl(file);\n                                  }}\" />\n                              `\n                            : html`\n                                <span class=\"file-thumb file-thumb-generic\" aria-hidden=\"true\">\n                                  <ore-icon name=\"file\" size=\"28\" stroke-width=\"1.5\" aria-hidden=\"true\"></ore-icon>\n                                </span>\n                              `\n                        }\n                        ${when(\n                          () => queue.fileState(file).status === 'success',\n                          () => html`\n                            <span class=\"file-status-badge file-status-badge-success\" aria-hidden=\"true\">\n                              <ore-icon name=\"check\" size=\"11\" stroke-width=\"3\" aria-hidden=\"true\"></ore-icon>\n                            </span>\n                          `,\n                        )}\n                        ${when(\n                          () => queue.fileState(file).status === 'error',\n                          () => html`\n                            <span class=\"file-status-badge file-status-badge-error\" aria-hidden=\"true\">\n                              <ore-icon name=\"alert-circle\" size=\"12\" stroke-width=\"2.5\" aria-hidden=\"true\"></ore-icon>\n                            </span>\n                          `,\n                        )}\n                        <span class=\"file-card-actions\">\n                          ${when(\n                            () => queue.fileState(file).status === 'error',\n                            () => html`\n                              <button\n                                class=\"file-card-action\"\n                                type=\"button\"\n                                aria-label=\"${`Retry uploading ${file.name}`}\"\n                                @click=${() => queue.retryUpload(file)}>\n                                <ore-icon name=\"refresh-cw\" size=\"12\" stroke-width=\"2.5\" aria-hidden=\"true\"></ore-icon>\n                              </button>\n                            `,\n                          )}\n                          ${when(\n                            () =>\n                              queue.fileState(file).status === 'success' &&\n                              Boolean(props.multiple.value) &&\n                              Boolean(props.upload.value),\n                            () => html`\n                              <button\n                                class=\"file-card-action\"\n                                type=\"button\"\n                                aria-label=\"${`Replace ${file.name}`}\"\n                                @click=${() => beginReplace(file)}>\n                                <ore-icon name=\"upload\" size=\"12\" stroke-width=\"2.5\" aria-hidden=\"true\"></ore-icon>\n                              </button>\n                            `,\n                          )}\n                          <button\n                            class=\"file-card-action file-card-remove\"\n                            type=\"button\"\n                            aria-label=\"${`Remove ${file.name}`}\"\n                            @click=${(e: Event) => queue.removeFile(file, e)}>\n                            <ore-icon name=\"x\" size=\"12\" stroke-width=\"2.5\" aria-hidden=\"true\"></ore-icon>\n                          </button>\n                        </span>\n                      </span>\n                      <span class=\"file-card-name\" title=\"${file.name}\">${file.name}</span>\n                      ${when(\n                        () => queue.fileState(file).status === 'uploading',\n                        () => html`\n                          <ore-progress\n                            type=\"linear\"\n                            size=\"sm\"\n                            value=${() => queue.uploadPercent(file)}\n                            label=${() => `${queue.uploadPercent(file)}%`}></ore-progress>\n                          <span class=\"file-progress-meta\">${() => queue.uploadMetaText(file)}</span>\n                        `,\n                      )}\n                      ${when(\n                        () => queue.fileState(file).status === 'error',\n                        () => html`\n                          <span class=\"file-error-text\">${() => queue.fileState(file).error}</span>\n                        `,\n                      )}\n                    </li>\n                  `\n                : html`\n                    <li class=\"file-item\" data-status=${() => queue.fileState(file).status}>\n                      <span class=\"file-icon\" aria-hidden=\"true\">\n                        <ore-icon\n                          name=${() => queue.statusIconName(file)}\n                          size=\"18\"\n                          stroke-width=\"1.75\"\n                          aria-hidden=\"true\"></ore-icon>\n                      </span>\n                      <span class=\"file-meta\">\n                        <span class=\"file-name\" title=\"${file.name}\">${file.name}</span>\n                        ${when(\n                          () => queue.fileState(file).status === 'uploading',\n                          () => html`\n                            <ore-progress\n                              type=\"linear\"\n                              size=\"sm\"\n                              value=${() => queue.uploadPercent(file)}\n                              label=${() => `${queue.uploadPercent(file)}%`}></ore-progress>\n                            <span class=\"file-progress-meta\">${() => queue.uploadMetaText(file)}</span>\n                          `,\n                        )}\n                        ${when(\n                          () => queue.fileState(file).status === 'error',\n                          () => html`\n                            <span class=\"file-error-text\">${() => queue.fileState(file).error}</span>\n                          `,\n                        )}\n                        ${when(\n                          () => queue.fileState(file).status === 'idle' || queue.fileState(file).status === 'success',\n                          () => html`\n                            <span class=\"file-size\">${formatBytes(file.size)}</span>\n                          `,\n                        )}\n                      </span>\n                      <span class=\"file-actions\">\n                        ${when(\n                          () => queue.fileState(file).status === 'error',\n                          () => html`\n                            <button\n                              class=\"file-action\"\n                              type=\"button\"\n                              aria-label=\"${`Retry uploading ${file.name}`}\"\n                              @click=${() => queue.retryUpload(file)}>\n                              <ore-icon name=\"refresh-cw\" size=\"14\" stroke-width=\"2\" aria-hidden=\"true\"></ore-icon>\n                            </button>\n                          `,\n                        )}\n                        ${when(\n                          () =>\n                            queue.fileState(file).status === 'success' &&\n                            Boolean(props.multiple.value) &&\n                            Boolean(props.upload.value),\n                          () => html`\n                            <button\n                              class=\"file-action\"\n                              type=\"button\"\n                              aria-label=\"${`Replace ${file.name}`}\"\n                              @click=${() => beginReplace(file)}>\n                              <ore-icon name=\"upload\" size=\"14\" stroke-width=\"2\" aria-hidden=\"true\"></ore-icon>\n                            </button>\n                          `,\n                        )}\n                        <button\n                          class=\"file-remove\"\n                          type=\"button\"\n                          aria-label=\"${`Remove ${file.name}`}\"\n                          @click=${(e: Event) => queue.removeFile(file, e)}>\n                          <ore-icon name=\"x\" size=\"12\" stroke-width=\"2.5\" aria-hidden=\"true\"></ore-icon>\n                        </button>\n                      </span>\n                    </li>\n                  `,\n            )}\n        </ul>\n        <div class=\"helper-text\" id=\"${helperId}\" part=\"helper\" ?hidden=${() => isInvalid.value || !props.helper.value}>\n          ${props.helper}\n        </div>\n        <div\n          class=\"helper-text helper-text-error\"\n          id=\"${errorId}\"\n          role=\"alert\"\n          part=\"error\"\n          ?hidden=${() => !isInvalid.value}>\n          ${() => props.error.value ?? ''}\n        </div>\n      </div>\n    `;\n  },\n  shadow: { delegatesFocus: true },\n  styles: [\n    colorThemeMixin,\n    coarsePointerMixin,\n    reducedMotionMixin,\n    roundedVariantMixin,\n    disabledLoadingMixin,\n    sizeVariantMixin(FILE_INPUT_SIZE_PRESET),\n    forcedColorsFocusMixin('.dropzone'),\n    componentStyles,\n  ],\n});\n"],"mappings":"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"}