File Upload

PRO

파일 선택과 끌어 놓기, 파일 목록 및 업로드 상태를 구성합니다.

Live preview
컴포넌트를 조작하면 여기에 결과가 표시됩니다.
코드 보기
HTML
<sy-file-upload
  label="첨부 파일"
  accept=".pdf,.png,.txt"
  max-size="5242880"
  max-files="3"
  multiple
  button-label="파일 선택"
  drag-label="파일을 끌어 놓으세요"
  id="sample"
></sy-file-upload>
<div class="demo-actions"
  style="display:flex;flex-wrap:wrap;gap:8px;margin-top:16px"
>
  <sy-button id="action-0" size="small">목록 확인</sy-button>
  <sy-button id="action-1" size="small">모두 지우기</sy-button>
</div>
<output id="demo-result" aria-live="polite"></output>

<script type="module">
  const describeResult = value => JSON.stringify(value, (_key, item) => {
    if (item instanceof File) return {
      name: item.name,
      size: item.size,
      type: item.type
    };
    if (item instanceof Element) return `<${item.localName}>`;
    return item;
  }) ?? '완료';
  const actionTarget0 = document.querySelector("#sample");
  document.querySelector('#action-0').addEventListener('click', async () => {
    const result = await actionTarget0.getFiles();
    document.querySelector('#demo-result').textContent = describeResult(result);
  });
  const actionTarget1 = document.querySelector("#sample");
  document.querySelector('#action-1').addEventListener('click', async () => {
    const result = await actionTarget1.clear();
    document.querySelector('#demo-result').textContent = describeResult(result);
  });
</script>

사용 가이드

#

파일 선택과 업로드 진행 상태를 보여줍니다. 실제 서버 업로드는 앱에서 연결해야 합니다.

  1. 오류 문구는 error 슬롯에 작성합니다.

    컴포넌트 안에 <span slot="error">오류 메시지</span>을 넣으세요. 글자뿐 아니라 링크 등 필요한 안내도 넣을 수 있습니다. noNativeValidity를 지원하는 입력은 이를 켜면 브라우저 팝업 대신 이 안내를 표시합니다. Switch는 항상 컴포넌트 아래에 오류를 표시합니다.

  2. 업무 규칙의 실패는 setCustomError()로 알립니다.

    예를 들어 서버가 중복된 값을 거절하면 error 슬롯의 문구를 바꾸고 setCustomError()를 호출하세요. 이 메서드에는 메시지 인자를 전달하지 않습니다. 사용자 지정 오류가 있으면 폼 제출도 차단됩니다.

  3. 해결 후 해제하고 제출 가능 여부를 확인합니다.

    clearCustomError()로 업무 규칙 오류를 해제하세요. required나 입력 형식 검사까지 해제되지는 않습니다. await checkValidity()는 true/false를, reportValidity()는 검사 결과와 오류 표시를 함께 제공합니다. name을 지정한 유효한 값은 제출 시 FormData로 읽습니다.

  1. 선택 조건 정하기

    accept는 파일 형식, maxSize는 바이트 단위 크기, maxFiles는 최대 개수입니다.

  2. 업로드 연결

    selected 또는 changed에서 선택 파일을 받아 서버로 전송하세요.

  3. 진행 상태 반영

    setProgress()로 진행률을 갱신하고, removeFile() 또는 clear()로 목록을 정리합니다.

예제

#

형식·크기·개수 제한

PNG 또는 PDF 파일을 최대 2개, 파일당 5MB까지 선택할 수 있습니다. 파일은 서버로 전송하지 않습니다.

코드 보기
HTML
<sy-file-upload
  accept=".png,.pdf"
  max-size="5242880"
  max-files="2"
  multiple
  button-label="파일 선택"
  helper-text="PNG, PDF · 파일당 최대 5MB"
></sy-file-upload>

드롭 영역 바꾸기

기본 슬롯에 안내 내용을 넣어도 클릭·끌어 놓기 동작은 유지됩니다.

코드 보기
HTML
<sy-file-upload multiple>
  <div style="padding:20px;text-align:center"><strong>프로젝트 파일 추가</strong>
    <p>이곳을 누르거나 파일을 끌어 놓으세요.</p>
  </div>
</sy-file-upload>

선택 목록과 초기화

파일을 추가한 뒤 getFiles로 목록을 확인하고 clear로 모두 제거합니다.

코드 보기
HTML
<sy-file-upload
  multiple
  button-label="파일 선택"
  id="sample"
></sy-file-upload>
<div class="demo-actions"
  style="display:flex;flex-wrap:wrap;gap:8px;margin-top:16px"
>
  <sy-button id="action-0" size="small">목록 읽기</sy-button>
  <sy-button id="action-1" size="small">모두 지우기</sy-button>
</div>
<output id="demo-result" aria-live="polite"></output>

<script type="module">
  const describeResult = value => JSON.stringify(value, (_key, item) => {
    if (item instanceof File) return {
      name: item.name,
      size: item.size,
      type: item.type
    };
    if (item instanceof Element) return `<${item.localName}>`;
    return item;
  }) ?? '완료';
  const actionTarget0 = document.querySelector("#sample");
  document.querySelector('#action-0').addEventListener('click', async () => {
    const result = await actionTarget0.getFiles();
    document.querySelector('#demo-result').textContent = describeResult(result);
  });
  const actionTarget1 = document.querySelector("#sample");
  document.querySelector('#action-1').addEventListener('click', async () => {
    const result = await actionTarget1.clear();
    document.querySelector('#demo-result').textContent = describeResult(result);
  });
</script>

이벤트와 전달 값

컴포넌트를 조작하고 최근 이벤트의 이름과 detail 값을 확인하세요. 코드에는 이 컴포넌트와 하위 구성 요소가 보내는 이벤트의 구독 방법이 모두 포함되어 있습니다.

코드 보기
HTML
<sy-file-upload
  label="첨부 파일"
  accept=".pdf,.png,.txt"
  max-size="5242880"
  max-files="3"
  multiple
  button-label="파일 선택"
  drag-label="파일을 끌어 놓으세요"
  id="sample"
></sy-file-upload>
<div class="demo-actions"
  style="display:flex;flex-wrap:wrap;gap:8px;margin-top:16px"
>
  <sy-button id="action-0" size="small">목록 확인</sy-button>
  <sy-button id="action-1" size="small">모두 지우기</sy-button>
</div>
<output id="demo-result" aria-live="polite"></output>
<output id="demo-events" aria-live="polite">컴포넌트를 조작하면 최근 이벤트와 전달 값이 표시됩니다.
</output>

<script type="module">
  const describeResult = value => JSON.stringify(value, (_key, item) => {
    if (item instanceof File) return {
      name: item.name,
      size: item.size,
      type: item.type
    };
    if (item instanceof Element) return `<${item.localName}>`;
    return item;
  }) ?? '완료';
  const actionTarget0 = document.querySelector("#sample");
  document.querySelector('#action-0').addEventListener('click', async () => {
    const result = await actionTarget0.getFiles();
    document.querySelector('#demo-result').textContent = describeResult(result);
  });
  const actionTarget1 = document.querySelector("#sample");
  document.querySelector('#action-1').addEventListener('click', async () => {
    const result = await actionTarget1.clear();
    document.querySelector('#demo-result').textContent = describeResult(result);
  });
  for (const name of ["selected", "rejected", "changed", "removed", "focused", "blured"]) {
    document.addEventListener(name, event => {
      document.querySelector('#demo-events').textContent =
        `${event.target.localName} · ${name}: ${describeResult(event.detail)}`;
    });
  }
</script>

파일 선택 상태와 키보드 포커스

파일을 추가한 뒤 첫 파일 선택을 누르면 해당 행을 강조합니다. 선택 해제는 파일을 삭제하지 않습니다. 파일은 이 예제에서 서버로 전송되지 않습니다.

코드 보기
HTML
<sy-file-upload
  label="첨부 파일"
  accept=".pdf,.png,.txt"
  max-size="5242880"
  max-files="3"
  multiple
  button-label="파일 선택"
  drag-label="파일을 끌어 놓으세요"
  id="sample"
></sy-file-upload>
<sy-button id="select-first">첫 파일 선택</sy-button>
<div class="demo-actions"
  style="display:flex;flex-wrap:wrap;gap:8px;margin-top:16px"
>
  <sy-button id="action-0" size="small">선택 해제</sy-button>
  <sy-button id="action-1" size="small">파일 조회</sy-button>
  <sy-button id="action-2" size="small">포커스 이동</sy-button>
  <sy-button id="action-3" size="small">포커스 해제</sy-button>
</div>
<output id="demo-result" aria-live="polite"></output>

<script type="module">
  const describeResult = value => JSON.stringify(value, (_key, item) => {
    if (item instanceof File) return {
      name: item.name,
      size: item.size,
      type: item.type
    };
    if (item instanceof Element) return `<${item.localName}>`;
    return item;
  }) ?? '완료';
  const component1 = document.querySelector("#sample");
  Object.assign(component1, {
    "multiple": true
  });
  const actionTarget0 = document.querySelector("#sample");
  document.querySelector('#action-0').addEventListener('click', async () => {
    const result = await actionTarget0.setSelectedFile("");
    document.querySelector('#demo-result').textContent = describeResult(result);
  });
  const actionTarget1 = document.querySelector("#sample");
  document.querySelector('#action-1').addEventListener('click', async () => {
    const result = await actionTarget1.getFiles();
    document.querySelector('#demo-result').textContent = describeResult(result);
  });
  const actionTarget2 = document.querySelector("#sample");
  document.querySelector('#action-2').addEventListener('click', async () => {
    const result = await actionTarget2.setFocus();
    document.querySelector('#demo-result').textContent = describeResult(result);
  });
  const actionTarget3 = document.querySelector("#sample");
  document.querySelector('#action-3').addEventListener('click', async () => {
    const result = await actionTarget3.setBlur();
    document.querySelector('#demo-result').textContent = describeResult(result);
  });
  await (root => {
    const upload = root.querySelector('sy-file-upload');
    root.querySelector('#select-first').addEventListener('click', async () => {
      const [file] = await upload.getFiles();
      if (file) await upload.setSelectedFile(file.id);
    });
  })(document);
</script>

폼과 유효성 검사

값을 비운 채 제출하면 필수 입력 검사가 동작합니다. 값을 입력한 뒤 제출해 폼 데이터를 확인하고, 사용자 지정 오류도 켜고 꺼보세요.

코드 보기
HTML
<form style="display:grid;gap:16px">
  <sy-file-upload
    name="field"
    required
    button-label="파일 선택"
    id="sample"
  ><span slot="error">파일을 선택하세요.</span></sy-file-upload>
  <div style="display:flex;flex-wrap:wrap;gap:8px">
    <sy-button type="submit" variant="primary">제출</sy-button>
    <sy-button type="reset">초기화</sy-button>
  </div>
</form>
<div class="demo-actions"
  style="display:flex;flex-wrap:wrap;gap:8px;margin-top:16px"
>
  <sy-button id="action-0" size="small">유효성 확인</sy-button>
  <sy-button id="action-1" size="small">오류 표시</sy-button>
  <sy-button id="action-2" size="small">오류 해제</sy-button>
</div>
<output id="demo-result" aria-live="polite"></output>

<script type="module">
  const describeResult = value => JSON.stringify(value, (_key, item) => {
    if (item instanceof File) return {
      name: item.name,
      size: item.size,
      type: item.type
    };
    if (item instanceof Element) return `<${item.localName}>`;
    return item;
  }) ?? '완료';
  const actionTarget0 = document.querySelector("#sample");
  document.querySelector('#action-0').addEventListener('click', async () => {
    const result = await actionTarget0.checkValidity();
    document.querySelector('#demo-result').textContent = describeResult(result);
  });
  const actionTarget1 = document.querySelector("#sample");
  document.querySelector('#action-1').addEventListener('click', async () => {
    const result = await actionTarget1.setCustomError();
    document.querySelector('#demo-result').textContent = describeResult(result);
  });
  const actionTarget2 = document.querySelector("#sample");
  document.querySelector('#action-2').addEventListener('click', async () => {
    const result = await actionTarget2.clearCustomError();
    document.querySelector('#demo-result').textContent = describeResult(result);
  });
  document.querySelector('form').addEventListener('submit', event => {
    event.preventDefault();
    document.querySelector('#demo-result').textContent = describeResult([...new FormData(event
      .currentTarget)]);
  });
</script>

검사 결과와 오류 상태 읽기

제출 전에 검사하거나 오류를 화면에 표시할 수 있습니다. 필수 값을 비워 두고 검사한 다음, 값을 채워 반환 값이 어떻게 달라지는지 확인하세요. 상태 조회는 사용자 지정 오류와 필수 입력 오류를 구분하는 데 사용합니다.

코드 보기
HTML
<form style="display:grid;gap:16px">
  <sy-file-upload
    name="field"
    required
    button-label="파일 선택"
    id="sample"
  ><span slot="error">파일을 선택하세요.</span></sy-file-upload>
  <div style="display:flex;flex-wrap:wrap;gap:8px">
    <sy-button type="submit" variant="primary">제출</sy-button>
    <sy-button type="reset">초기화</sy-button>
  </div>
</form>
<div class="demo-actions"
  style="display:flex;flex-wrap:wrap;gap:8px;margin-top:16px"
>
  <sy-button id="action-0" size="small">유효성 확인</sy-button>
  <sy-button id="action-1" size="small">오류 표시</sy-button>
  <sy-button id="action-2" size="small">오류 해제</sy-button>
  <sy-button id="action-3" size="small">오류와 함께 검사</sy-button>
  <sy-button id="action-4" size="small">검증 상태 조회</sy-button>
</div>
<output id="demo-result" aria-live="polite"></output>

<script type="module">
  const describeResult = value => JSON.stringify(value, (_key, item) => {
    if (item instanceof File) return {
      name: item.name,
      size: item.size,
      type: item.type
    };
    if (item instanceof Element) return `<${item.localName}>`;
    return item;
  }) ?? '완료';
  const actionTarget0 = document.querySelector("#sample");
  document.querySelector('#action-0').addEventListener('click', async () => {
    const result = await actionTarget0.checkValidity();
    document.querySelector('#demo-result').textContent = describeResult(result);
  });
  const actionTarget1 = document.querySelector("#sample");
  document.querySelector('#action-1').addEventListener('click', async () => {
    const result = await actionTarget1.setCustomError();
    document.querySelector('#demo-result').textContent = describeResult(result);
  });
  const actionTarget2 = document.querySelector("#sample");
  document.querySelector('#action-2').addEventListener('click', async () => {
    const result = await actionTarget2.clearCustomError();
    document.querySelector('#demo-result').textContent = describeResult(result);
  });
  const actionTarget3 = document.querySelector("#sample");
  document.querySelector('#action-3').addEventListener('click', async () => {
    const result = await actionTarget3.reportValidity();
    document.querySelector('#demo-result').textContent = describeResult(result);
  });
  const actionTarget4 = document.querySelector("#sample");
  document.querySelector('#action-4').addEventListener('click', async () => {
    const result = await actionTarget4.getStatus();
    document.querySelector('#demo-result').textContent = describeResult(result);
  });
  document.querySelector('form').addEventListener('submit', event => {
    event.preventDefault();
    document.querySelector('#demo-result').textContent = describeResult([...new FormData(event
      .currentTarget)]);
  });
</script>

사용자 지정 오류 메시지

서버 중복 검사나 업무 규칙에 맞지 않을 때 오류 문구를 직접 표시합니다. 아래 문구를 바꿔 보고 제출을 눌러 차단되는지 확인하세요. 오류 해제 후에는 필수 입력 조건까지 만족해야 제출됩니다.

코드 보기
HTML
<label for="error-message">표시할 오류 메시지</label><input
  id="error-message"
  value="서버에서 이 값을 승인하지 않았습니다. 내용을 확인해 주세요."
  type="text"
>
<form style="display:grid;gap:16px">
  <sy-file-upload
    no-native-validity
    name="field"
    required
    button-label="파일 선택"
    id="sample"
  ><span slot="error">파일을 선택하세요.</span></sy-file-upload>
  <div style="display:flex;flex-wrap:wrap;gap:8px">
    <sy-button type="submit" variant="primary">제출</sy-button>
    <sy-button type="reset">초기화</sy-button>
  </div>
</form>
<div class="demo-actions">
  <sy-button id="show-custom-error" variant="primary">오류 표시</sy-button>
  <sy-button id="clear-custom-error">오류 해제</sy-button>
</div>
<output id="custom-error-result" aria-live="polite"></output>
<output id="demo-result" aria-live="polite"></output>

<script type="module">
  const describeResult = value => JSON.stringify(value, (_key, item) => {
    if (item instanceof File) return {
      name: item.name,
      size: item.size,
      type: item.type
    };
    if (item instanceof Element) return `<${item.localName}>`;
    return item;
  }) ?? '완료';
  await (async function customErrorSetup(root) {
    const field = root.querySelector('#sample');
    const message = root.querySelector('[slot="error"]');
    const input = root.querySelector('#error-message');
    const output = root.querySelector('#custom-error-result');
    const update = async () => {
      message.textContent = input.value.trim() || _msg("m_4eea8cef81c6");
      await field.setCustomError();
      output.textContent = _msg("m_e00bd46236d3", [message.textContent]);
    };
    input.addEventListener('input', update);
    root.querySelector('#show-custom-error').addEventListener('click', update);
    root.querySelector('#clear-custom-error').addEventListener('click', async () => {
      await field.clearCustomError();
      output.textContent = await field.checkValidity() ? _msg("m_c454251167b2") : _msg(
        "m_5b8cb48627e5");
    });
    root.querySelector('form').addEventListener('invalid', () => {
      output.textContent = _msg("m_c702c7778166");
    }, true);
    root.querySelector('form').addEventListener('reset', () => {
      output.textContent = _msg("m_b90c2b4dc30f");
    });
    await update();
  })(document);
  document.querySelector('form').addEventListener('submit', event => {
    event.preventDefault();
    document.querySelector('#demo-result').textContent = describeResult([...new FormData(event
      .currentTarget)]);
  });
</script>

API reference

#

이름에 마우스를 올리거나 키보드로 선택하면 적용 코드를 확인할 수 있습니다. 터치 화면에서는 이름을 누르세요.

<sy-file-upload>

Properties

Attribute / PropertyTypeDefaultDescription
booleanfalse사용자 조작과 키보드 포커스를 차단합니다.
booleanfalse값이 없으면 폼 검증을 실패시킵니다.
string''선택 가능한 확장자 또는 MIME 형식입니다.
booleanfalse여러 파일을 선택할 수 있게 합니다.
number0파일 하나의 최대 크기(bytes)입니다. 0이면 제한하지 않습니다.
number0선택 가능한 파일 개수입니다. 0이면 제한하지 않습니다.
string'files'FormData에 사용되는 필드 이름입니다.
string'Browse files'파일 선택 버튼 문구입니다.
string'Drop files here or browse'끌어 놓기 영역의 안내 문구입니다.
string''파일 제한 등의 보조 설명입니다.
booleanfalse선택된 파일 목록을 숨깁니다.
string''항목을 설명하는 레이블입니다.
string''입력 상태에 대한 안내 문구입니다.
booleanfalse브라우저의 기본 오류 표시 대신 앱의 오류 UI를 사용할 때 지정합니다. checkValidity 검사 자체는 유지됩니다.
'' | 'default' | 'error' | 'warning' | 'success'''파일 업로드 영역의 상태 스타일입니다.

Events

EventDetailDescription
CustomEvent<{ files: SyUploadFileItem[] }>Fires after the user adds files (drag-drop or browse). Carries the accepted file items.
CustomEvent<{ file: File; reason: string }>Fires for each file rejected by format, size or count limits.
CustomEvent<{ files: SyUploadFileItem[]; added: SyUploadFileItem[]; removed: SyUploadFileItem[]; isValid: boolean; }>Fires whenever the internal file list changes (selection, removal, status update). `added` / `removed` carry the delta since the previous emit so the consumer can reconcile its own state without diffing the full list.
CustomEvent<{ file: SyUploadFileItem; files: SyUploadFileItem[] }>Fires when a file is removed from the list.
CustomEvent<void>포커스를 받으면 발생합니다.
CustomEvent<void>포커스를 잃으면 발생합니다.

Methods

MethodReturnsDescription
Promise<void>파일 선택 창을 엽니다. 사용자 클릭 동작 안에서 호출하세요.
Promise<void>선택한 파일 목록을 비웁니다.
Promise<void>fileId에 해당하는 파일을 목록에서 제거합니다.
Promise<void>파일의 업로드 진행률과 상태를 갱신합니다.
Promise<SyUploadFileItem[]>선택 파일 목록을 반환합니다.
Promise<SyUploadFileItem | null>지정한 인덱스의 파일을 반환합니다.
Promise<void>지정한 fileId를 활성 파일로 표시합니다.
Promise<boolean>현재 제약 조건에 맞으면 true, 아니면 false를 반환합니다.
Promise<boolean>현재 값을 검사하고 오류를 사용자에게 표시합니다.
Promise<void>사용자 지정 오류 상태를 설정합니다.
Promise<void>사용자 지정 오류 상태를 해제합니다.
Promise<string>현재 유효성 검사 상태를 반환합니다.
Promise<void>입력 또는 동작 요소에 키보드 포커스를 보냅니다.
Promise<void>입력 요소에서 포커스를 해제합니다.

Slots

SlotDescription
컴포넌트의 본문 또는 자식 항목입니다.
유효성 검사 오류 메시지입니다.
입력 안내 메시지입니다.