@iyulab/components 1.35.7 → 1.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +1 -1
  3. package/dist/assets/locales/ar.json.js +8 -2
  4. package/dist/assets/locales/de.json.js +8 -2
  5. package/dist/assets/locales/en.json.js +8 -2
  6. package/dist/assets/locales/es.json.js +8 -2
  7. package/dist/assets/locales/fr.json.js +8 -2
  8. package/dist/assets/locales/id.json.js +8 -2
  9. package/dist/assets/locales/ja.json.js +8 -2
  10. package/dist/assets/locales/ko.json.js +8 -2
  11. package/dist/assets/locales/pt-BR.json.js +8 -2
  12. package/dist/assets/locales/ru.json.js +8 -2
  13. package/dist/assets/locales/th.json.js +8 -2
  14. package/dist/assets/locales/vi.json.js +8 -2
  15. package/dist/assets/locales/zh-CN.json.js +8 -2
  16. package/dist/assets/locales/zh-TW.json.js +8 -2
  17. package/dist/components/UFloatingElement.d.ts +9 -1
  18. package/dist/components/date-picker/UDatePicker.d.ts +5 -0
  19. package/dist/components/date-picker/UDatePicker.js +1 -0
  20. package/dist/components/file-input/UFileInput.d.ts +57 -0
  21. package/dist/components/file-input/UFileInput.js +140 -0
  22. package/dist/components/file-input/UFileInput.styles.d.ts +1 -0
  23. package/dist/components/file-input/UFileInput.styles.js +80 -0
  24. package/dist/components/select/USelect.d.ts +4 -0
  25. package/dist/components/select/USelect.js +1 -0
  26. package/dist/index.d.ts +1 -0
  27. package/dist/index.js +2 -1
  28. package/dist/react/UFileInput.d.ts +13 -0
  29. package/dist/react/UFileInput.js +12 -0
  30. package/dist/react/index.d.ts +1 -0
  31. package/dist/react/index.js +1 -0
  32. package/dist/utilities/Locale.d.ts +1 -1
  33. package/package.json +1 -1
  34. package/skills/iyulab-components/SKILL.md +2 -0
  35. package/skills/iyulab-components/references/components/file-input.md +81 -0
  36. package/skills/iyulab-components/references/components/popover.md +7 -1
  37. package/skills/iyulab-components/references/components/tooltip.md +7 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,41 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.37.0] - 2026-09-06
4
+
5
+ ### Added
6
+
7
+ - **`u-date-picker` and `u-select` expose a `:state(open)` custom state.**
8
+ Both already tracked their popover's open/closed state privately; it is now
9
+ surfaced through `ElementInternals.states` so consumers can style the trigger
10
+ while the popover is showing — `u-date-picker:state(open)::part(container)`,
11
+ `u-select:state(open)::part(container)`. Deliberately not a reflected
12
+ attribute: the state stays private to the component and exists only as a CSS
13
+ hook, so no new public attribute enters the API surface.
14
+
15
+ ### Documentation
16
+
17
+ - **`UFloatingElement.placement` documents that `flip` is always applied.**
18
+ Whenever `placement` is set, floating-ui's `flip` middleware runs (switching
19
+ to the opposite side when the placed side has no room) — this has always been
20
+ the behavior but was undocumented, and `shift`'s description ("keeps the
21
+ element on screen") over-promised, since `shift` only corrects the cross axis
22
+ *within* a side. Both properties now describe the split, and a regression test
23
+ covers the flip fallback.
24
+
25
+ ## [1.36.0] - 2026-09-04
26
+
27
+ ### Added
28
+
29
+ - **`UFileInput` (`u-file-input`), a form control wrapping a native
30
+ `<input type="file">`.** Trigger button, selected-file(s) status text,
31
+ and a clear button, styled to match the rest of the form control
32
+ family. `value` is `File[] | null`; native `<form>`/`u-form`
33
+ submission works out of the box via `ElementInternals` (single file)
34
+ or a `FormData` with the file repeatedly appended under the same key
35
+ (multiple files, matching native multi-file submission semantics).
36
+ `accept`/`multiple` pass through to the underlying input.
37
+ Drag-and-drop is out of scope for this release.
38
+
3
39
  ## [1.35.7] - 2026-09-03
4
40
 
5
41
  ### Fixed
package/README.md CHANGED
@@ -70,7 +70,7 @@ npx skills add ./node_modules/@iyulab/components
70
70
 
71
71
  **Buttons & Actions** — `u-button`, `u-button-group`, `u-icon-button`, `u-copy-button`, `u-chip`
72
72
 
73
- **Form Controls** — `u-input`, `u-textarea`, `u-select`, `u-date-picker`, `u-checkbox`, `u-radio`, `u-switch`, `u-slider`, `u-rating`, `u-field`, `u-form`, `u-option`
73
+ **Form Controls** — `u-input`, `u-textarea`, `u-select`, `u-date-picker`, `u-file-input`, `u-checkbox`, `u-radio`, `u-switch`, `u-slider`, `u-rating`, `u-field`, `u-form`, `u-option`
74
74
 
75
75
  **Overlay & Floating** — `u-dialog`, `u-drawer`, `u-popover`, `u-tooltip`
76
76
 
@@ -24,6 +24,9 @@ var nextMonth = "الشهر التالي";
24
24
  var close = "إغلاق";
25
25
  var copy = "نسخ";
26
26
  var copied = "تم النسخ";
27
+ var chooseFile = "اختر ملفًا";
28
+ var noFileChosen = "لم يتم اختيار أي ملف";
29
+ var filesSelected = "تم اختيار {count} ملفات";
27
30
  var ar_default = {
28
31
  valueMissing,
29
32
  badInput,
@@ -49,7 +52,10 @@ var ar_default = {
49
52
  nextMonth,
50
53
  close,
51
54
  copy,
52
- copied
55
+ copied,
56
+ chooseFile,
57
+ noFileChosen,
58
+ filesSelected
53
59
  };
54
60
  //#endregion
55
- export { badInput, chooseDate, clear, close, copied, copy, decrement, ar_default as default, goToSlide, hidePassword, increment, nextMonth, nextSlide, patternMismatch, previousMonth, previousSlide, rangeOverflow, rangeUnderflow, showPassword, stepMismatch, time, today, tooLong, tooShort, typeMismatch, valueMissing };
61
+ export { badInput, chooseDate, chooseFile, clear, close, copied, copy, decrement, ar_default as default, filesSelected, goToSlide, hidePassword, increment, nextMonth, nextSlide, noFileChosen, patternMismatch, previousMonth, previousSlide, rangeOverflow, rangeUnderflow, showPassword, stepMismatch, time, today, tooLong, tooShort, typeMismatch, valueMissing };
@@ -24,6 +24,9 @@ var nextMonth = "Nächster Monat";
24
24
  var close = "Schließen";
25
25
  var copy = "Kopieren";
26
26
  var copied = "Kopiert";
27
+ var chooseFile = "Datei auswählen";
28
+ var noFileChosen = "Keine Datei ausgewählt";
29
+ var filesSelected = "{count} Dateien ausgewählt";
27
30
  var de_default = {
28
31
  valueMissing,
29
32
  badInput,
@@ -49,7 +52,10 @@ var de_default = {
49
52
  nextMonth,
50
53
  close,
51
54
  copy,
52
- copied
55
+ copied,
56
+ chooseFile,
57
+ noFileChosen,
58
+ filesSelected
53
59
  };
54
60
  //#endregion
55
- export { badInput, chooseDate, clear, close, copied, copy, decrement, de_default as default, goToSlide, hidePassword, increment, nextMonth, nextSlide, patternMismatch, previousMonth, previousSlide, rangeOverflow, rangeUnderflow, showPassword, stepMismatch, time, today, tooLong, tooShort, typeMismatch, valueMissing };
61
+ export { badInput, chooseDate, chooseFile, clear, close, copied, copy, decrement, de_default as default, filesSelected, goToSlide, hidePassword, increment, nextMonth, nextSlide, noFileChosen, patternMismatch, previousMonth, previousSlide, rangeOverflow, rangeUnderflow, showPassword, stepMismatch, time, today, tooLong, tooShort, typeMismatch, valueMissing };
@@ -24,6 +24,9 @@ var nextMonth = "Next month";
24
24
  var close = "Close";
25
25
  var copy = "Copy";
26
26
  var copied = "Copied";
27
+ var chooseFile = "Choose file";
28
+ var noFileChosen = "No file chosen";
29
+ var filesSelected = "{count} files selected";
27
30
  var en_default = {
28
31
  valueMissing,
29
32
  badInput,
@@ -49,7 +52,10 @@ var en_default = {
49
52
  nextMonth,
50
53
  close,
51
54
  copy,
52
- copied
55
+ copied,
56
+ chooseFile,
57
+ noFileChosen,
58
+ filesSelected
53
59
  };
54
60
  //#endregion
55
- export { badInput, chooseDate, clear, close, copied, copy, decrement, en_default as default, goToSlide, hidePassword, increment, nextMonth, nextSlide, patternMismatch, previousMonth, previousSlide, rangeOverflow, rangeUnderflow, showPassword, stepMismatch, time, today, tooLong, tooShort, typeMismatch, valueMissing };
61
+ export { badInput, chooseDate, chooseFile, clear, close, copied, copy, decrement, en_default as default, filesSelected, goToSlide, hidePassword, increment, nextMonth, nextSlide, noFileChosen, patternMismatch, previousMonth, previousSlide, rangeOverflow, rangeUnderflow, showPassword, stepMismatch, time, today, tooLong, tooShort, typeMismatch, valueMissing };
@@ -23,6 +23,9 @@ var nextMonth = "Mes siguiente";
23
23
  var close = "Cerrar";
24
24
  var copy = "Copiar";
25
25
  var copied = "Copiado";
26
+ var chooseFile = "Elegir archivo";
27
+ var noFileChosen = "Ningún archivo elegido";
28
+ var filesSelected = "{count} archivos seleccionados";
26
29
  var es_default = {
27
30
  valueMissing,
28
31
  badInput,
@@ -48,7 +51,10 @@ var es_default = {
48
51
  nextMonth,
49
52
  close,
50
53
  copy,
51
- copied
54
+ copied,
55
+ chooseFile,
56
+ noFileChosen,
57
+ filesSelected
52
58
  };
53
59
  //#endregion
54
- export { badInput, chooseDate, clear, close, copied, copy, decrement, es_default as default, goToSlide, hidePassword, increment, nextMonth, nextSlide, patternMismatch, previousMonth, previousSlide, rangeOverflow, rangeUnderflow, showPassword, stepMismatch, time, tooLong, tooShort, typeMismatch, valueMissing };
60
+ export { badInput, chooseDate, chooseFile, clear, close, copied, copy, decrement, es_default as default, filesSelected, goToSlide, hidePassword, increment, nextMonth, nextSlide, noFileChosen, patternMismatch, previousMonth, previousSlide, rangeOverflow, rangeUnderflow, showPassword, stepMismatch, time, tooLong, tooShort, typeMismatch, valueMissing };
@@ -24,6 +24,9 @@ var nextMonth = "Mois suivant";
24
24
  var close = "Fermer";
25
25
  var copy = "Copier";
26
26
  var copied = "Copié";
27
+ var chooseFile = "Choisir un fichier";
28
+ var noFileChosen = "Aucun fichier choisi";
29
+ var filesSelected = "{count} fichiers sélectionnés";
27
30
  var fr_default = {
28
31
  valueMissing,
29
32
  badInput,
@@ -49,7 +52,10 @@ var fr_default = {
49
52
  nextMonth,
50
53
  close,
51
54
  copy,
52
- copied
55
+ copied,
56
+ chooseFile,
57
+ noFileChosen,
58
+ filesSelected
53
59
  };
54
60
  //#endregion
55
- export { badInput, chooseDate, clear, close, copied, copy, decrement, fr_default as default, goToSlide, hidePassword, increment, nextMonth, nextSlide, patternMismatch, previousMonth, previousSlide, rangeOverflow, rangeUnderflow, showPassword, stepMismatch, time, today, tooLong, tooShort, typeMismatch, valueMissing };
61
+ export { badInput, chooseDate, chooseFile, clear, close, copied, copy, decrement, fr_default as default, filesSelected, goToSlide, hidePassword, increment, nextMonth, nextSlide, noFileChosen, patternMismatch, previousMonth, previousSlide, rangeOverflow, rangeUnderflow, showPassword, stepMismatch, time, today, tooLong, tooShort, typeMismatch, valueMissing };
@@ -24,6 +24,9 @@ var nextMonth = "Bulan berikutnya";
24
24
  var close = "Tutup";
25
25
  var copy = "Salin";
26
26
  var copied = "Disalin";
27
+ var chooseFile = "Pilih berkas";
28
+ var noFileChosen = "Tidak ada berkas dipilih";
29
+ var filesSelected = "{count} berkas dipilih";
27
30
  var id_default = {
28
31
  valueMissing,
29
32
  badInput,
@@ -49,7 +52,10 @@ var id_default = {
49
52
  nextMonth,
50
53
  close,
51
54
  copy,
52
- copied
55
+ copied,
56
+ chooseFile,
57
+ noFileChosen,
58
+ filesSelected
53
59
  };
54
60
  //#endregion
55
- export { badInput, chooseDate, clear, close, copied, copy, decrement, id_default as default, goToSlide, hidePassword, increment, nextMonth, nextSlide, patternMismatch, previousMonth, previousSlide, rangeOverflow, rangeUnderflow, showPassword, stepMismatch, time, today, tooLong, tooShort, typeMismatch, valueMissing };
61
+ export { badInput, chooseDate, chooseFile, clear, close, copied, copy, decrement, id_default as default, filesSelected, goToSlide, hidePassword, increment, nextMonth, nextSlide, noFileChosen, patternMismatch, previousMonth, previousSlide, rangeOverflow, rangeUnderflow, showPassword, stepMismatch, time, today, tooLong, tooShort, typeMismatch, valueMissing };
@@ -24,6 +24,9 @@ var nextMonth = "翌月";
24
24
  var close = "閉じる";
25
25
  var copy = "コピー";
26
26
  var copied = "コピーしました";
27
+ var chooseFile = "ファイルを選択";
28
+ var noFileChosen = "ファイルが選択されていません";
29
+ var filesSelected = "{count}個のファイルを選択しました";
27
30
  var ja_default = {
28
31
  valueMissing,
29
32
  badInput,
@@ -49,7 +52,10 @@ var ja_default = {
49
52
  nextMonth,
50
53
  close,
51
54
  copy,
52
- copied
55
+ copied,
56
+ chooseFile,
57
+ noFileChosen,
58
+ filesSelected
53
59
  };
54
60
  //#endregion
55
- export { badInput, chooseDate, clear, close, copied, copy, decrement, ja_default as default, goToSlide, hidePassword, increment, nextMonth, nextSlide, patternMismatch, previousMonth, previousSlide, rangeOverflow, rangeUnderflow, showPassword, stepMismatch, time, today, tooLong, tooShort, typeMismatch, valueMissing };
61
+ export { badInput, chooseDate, chooseFile, clear, close, copied, copy, decrement, ja_default as default, filesSelected, goToSlide, hidePassword, increment, nextMonth, nextSlide, noFileChosen, patternMismatch, previousMonth, previousSlide, rangeOverflow, rangeUnderflow, showPassword, stepMismatch, time, today, tooLong, tooShort, typeMismatch, valueMissing };
@@ -24,6 +24,9 @@ var nextMonth = "다음 달";
24
24
  var close = "닫기";
25
25
  var copy = "복사";
26
26
  var copied = "복사됨";
27
+ var chooseFile = "파일 선택";
28
+ var noFileChosen = "선택된 파일 없음";
29
+ var filesSelected = "{count}개 파일 선택됨";
27
30
  var ko_default = {
28
31
  valueMissing,
29
32
  badInput,
@@ -49,7 +52,10 @@ var ko_default = {
49
52
  nextMonth,
50
53
  close,
51
54
  copy,
52
- copied
55
+ copied,
56
+ chooseFile,
57
+ noFileChosen,
58
+ filesSelected
53
59
  };
54
60
  //#endregion
55
- export { badInput, chooseDate, clear, close, copied, copy, decrement, ko_default as default, goToSlide, hidePassword, increment, nextMonth, nextSlide, patternMismatch, previousMonth, previousSlide, rangeOverflow, rangeUnderflow, showPassword, stepMismatch, time, today, tooLong, tooShort, typeMismatch, valueMissing };
61
+ export { badInput, chooseDate, chooseFile, clear, close, copied, copy, decrement, ko_default as default, filesSelected, goToSlide, hidePassword, increment, nextMonth, nextSlide, noFileChosen, patternMismatch, previousMonth, previousSlide, rangeOverflow, rangeUnderflow, showPassword, stepMismatch, time, today, tooLong, tooShort, typeMismatch, valueMissing };
@@ -24,6 +24,9 @@ var nextMonth = "Próximo mês";
24
24
  var close = "Fechar";
25
25
  var copy = "Copiar";
26
26
  var copied = "Copiado";
27
+ var chooseFile = "Escolher arquivo";
28
+ var noFileChosen = "Nenhum arquivo escolhido";
29
+ var filesSelected = "{count} arquivos selecionados";
27
30
  var pt_BR_default = {
28
31
  valueMissing,
29
32
  badInput,
@@ -49,7 +52,10 @@ var pt_BR_default = {
49
52
  nextMonth,
50
53
  close,
51
54
  copy,
52
- copied
55
+ copied,
56
+ chooseFile,
57
+ noFileChosen,
58
+ filesSelected
53
59
  };
54
60
  //#endregion
55
- export { badInput, chooseDate, clear, close, copied, copy, decrement, pt_BR_default as default, goToSlide, hidePassword, increment, nextMonth, nextSlide, patternMismatch, previousMonth, previousSlide, rangeOverflow, rangeUnderflow, showPassword, stepMismatch, time, today, tooLong, tooShort, typeMismatch, valueMissing };
61
+ export { badInput, chooseDate, chooseFile, clear, close, copied, copy, decrement, pt_BR_default as default, filesSelected, goToSlide, hidePassword, increment, nextMonth, nextSlide, noFileChosen, patternMismatch, previousMonth, previousSlide, rangeOverflow, rangeUnderflow, showPassword, stepMismatch, time, today, tooLong, tooShort, typeMismatch, valueMissing };
@@ -24,6 +24,9 @@ var nextMonth = "Следующий месяц";
24
24
  var close = "Закрыть";
25
25
  var copy = "Копировать";
26
26
  var copied = "Скопировано";
27
+ var chooseFile = "Выбрать файл";
28
+ var noFileChosen = "Файл не выбран";
29
+ var filesSelected = "Выбрано файлов: {count}";
27
30
  var ru_default = {
28
31
  valueMissing,
29
32
  badInput,
@@ -49,7 +52,10 @@ var ru_default = {
49
52
  nextMonth,
50
53
  close,
51
54
  copy,
52
- copied
55
+ copied,
56
+ chooseFile,
57
+ noFileChosen,
58
+ filesSelected
53
59
  };
54
60
  //#endregion
55
- export { badInput, chooseDate, clear, close, copied, copy, decrement, ru_default as default, goToSlide, hidePassword, increment, nextMonth, nextSlide, patternMismatch, previousMonth, previousSlide, rangeOverflow, rangeUnderflow, showPassword, stepMismatch, time, today, tooLong, tooShort, typeMismatch, valueMissing };
61
+ export { badInput, chooseDate, chooseFile, clear, close, copied, copy, decrement, ru_default as default, filesSelected, goToSlide, hidePassword, increment, nextMonth, nextSlide, noFileChosen, patternMismatch, previousMonth, previousSlide, rangeOverflow, rangeUnderflow, showPassword, stepMismatch, time, today, tooLong, tooShort, typeMismatch, valueMissing };
@@ -24,6 +24,9 @@ var nextMonth = "เดือนถัดไป";
24
24
  var close = "ปิด";
25
25
  var copy = "คัดลอก";
26
26
  var copied = "คัดลอกแล้ว";
27
+ var chooseFile = "เลือกไฟล์";
28
+ var noFileChosen = "ยังไม่ได้เลือกไฟล์";
29
+ var filesSelected = "เลือกแล้ว {count} ไฟล์";
27
30
  var th_default = {
28
31
  valueMissing,
29
32
  badInput,
@@ -49,7 +52,10 @@ var th_default = {
49
52
  nextMonth,
50
53
  close,
51
54
  copy,
52
- copied
55
+ copied,
56
+ chooseFile,
57
+ noFileChosen,
58
+ filesSelected
53
59
  };
54
60
  //#endregion
55
- export { badInput, chooseDate, clear, close, copied, copy, decrement, th_default as default, goToSlide, hidePassword, increment, nextMonth, nextSlide, patternMismatch, previousMonth, previousSlide, rangeOverflow, rangeUnderflow, showPassword, stepMismatch, time, today, tooLong, tooShort, typeMismatch, valueMissing };
61
+ export { badInput, chooseDate, chooseFile, clear, close, copied, copy, decrement, th_default as default, filesSelected, goToSlide, hidePassword, increment, nextMonth, nextSlide, noFileChosen, patternMismatch, previousMonth, previousSlide, rangeOverflow, rangeUnderflow, showPassword, stepMismatch, time, today, tooLong, tooShort, typeMismatch, valueMissing };
@@ -24,6 +24,9 @@ var nextMonth = "Tháng sau";
24
24
  var close = "Đóng";
25
25
  var copy = "Sao chép";
26
26
  var copied = "Đã sao chép";
27
+ var chooseFile = "Chọn tệp";
28
+ var noFileChosen = "Chưa chọn tệp nào";
29
+ var filesSelected = "Đã chọn {count} tệp";
27
30
  var vi_default = {
28
31
  valueMissing,
29
32
  badInput,
@@ -49,7 +52,10 @@ var vi_default = {
49
52
  nextMonth,
50
53
  close,
51
54
  copy,
52
- copied
55
+ copied,
56
+ chooseFile,
57
+ noFileChosen,
58
+ filesSelected
53
59
  };
54
60
  //#endregion
55
- export { badInput, chooseDate, clear, close, copied, copy, decrement, vi_default as default, goToSlide, hidePassword, increment, nextMonth, nextSlide, patternMismatch, previousMonth, previousSlide, rangeOverflow, rangeUnderflow, showPassword, stepMismatch, time, today, tooLong, tooShort, typeMismatch, valueMissing };
61
+ export { badInput, chooseDate, chooseFile, clear, close, copied, copy, decrement, vi_default as default, filesSelected, goToSlide, hidePassword, increment, nextMonth, nextSlide, noFileChosen, patternMismatch, previousMonth, previousSlide, rangeOverflow, rangeUnderflow, showPassword, stepMismatch, time, today, tooLong, tooShort, typeMismatch, valueMissing };
@@ -24,6 +24,9 @@ var nextMonth = "下个月";
24
24
  var close = "关闭";
25
25
  var copy = "复制";
26
26
  var copied = "已复制";
27
+ var chooseFile = "选择文件";
28
+ var noFileChosen = "未选择文件";
29
+ var filesSelected = "已选择{count}个文件";
27
30
  var zh_CN_default = {
28
31
  valueMissing,
29
32
  badInput,
@@ -49,7 +52,10 @@ var zh_CN_default = {
49
52
  nextMonth,
50
53
  close,
51
54
  copy,
52
- copied
55
+ copied,
56
+ chooseFile,
57
+ noFileChosen,
58
+ filesSelected
53
59
  };
54
60
  //#endregion
55
- export { badInput, chooseDate, clear, close, copied, copy, decrement, zh_CN_default as default, goToSlide, hidePassword, increment, nextMonth, nextSlide, patternMismatch, previousMonth, previousSlide, rangeOverflow, rangeUnderflow, showPassword, stepMismatch, time, today, tooLong, tooShort, typeMismatch, valueMissing };
61
+ export { badInput, chooseDate, chooseFile, clear, close, copied, copy, decrement, zh_CN_default as default, filesSelected, goToSlide, hidePassword, increment, nextMonth, nextSlide, noFileChosen, patternMismatch, previousMonth, previousSlide, rangeOverflow, rangeUnderflow, showPassword, stepMismatch, time, today, tooLong, tooShort, typeMismatch, valueMissing };
@@ -24,6 +24,9 @@ var nextMonth = "下個月";
24
24
  var close = "關閉";
25
25
  var copy = "複製";
26
26
  var copied = "已複製";
27
+ var chooseFile = "選擇檔案";
28
+ var noFileChosen = "未選擇檔案";
29
+ var filesSelected = "已選擇{count}個檔案";
27
30
  var zh_TW_default = {
28
31
  valueMissing,
29
32
  badInput,
@@ -49,7 +52,10 @@ var zh_TW_default = {
49
52
  nextMonth,
50
53
  close,
51
54
  copy,
52
- copied
55
+ copied,
56
+ chooseFile,
57
+ noFileChosen,
58
+ filesSelected
53
59
  };
54
60
  //#endregion
55
- export { badInput, chooseDate, clear, close, copied, copy, decrement, zh_TW_default as default, goToSlide, hidePassword, increment, nextMonth, nextSlide, patternMismatch, previousMonth, previousSlide, rangeOverflow, rangeUnderflow, showPassword, stepMismatch, time, today, tooLong, tooShort, typeMismatch, valueMissing };
61
+ export { badInput, chooseDate, chooseFile, clear, close, copied, copy, decrement, zh_TW_default as default, filesSelected, goToSlide, hidePassword, increment, nextMonth, nextSlide, noFileChosen, patternMismatch, previousMonth, previousSlide, rangeOverflow, rangeUnderflow, showPassword, stepMismatch, time, today, tooLong, tooShort, typeMismatch, valueMissing };
@@ -51,6 +51,12 @@ export declare class UFloatingElement extends UElement {
51
51
  * 대상 엘리먼트로부터의 배치 위치입니다.
52
52
  * 지정하지 않으면 자동으로 가장 적절한 위치가 선택됩니다.
53
53
  *
54
+ * @remarks
55
+ * 지정한 변(side)에 공간이 없으면 반대 변으로 자동 전환됩니다(floating-ui의
56
+ * `flip` 미들웨어가 `placement`를 지정했을 때 항상 함께 적용됩니다 — 별도
57
+ * 옵션으로 끌 수 없습니다). `shift`는 이 전환과 별개로, 전환된(또는 지정된)
58
+ * 변 **안에서** 교차축 위치만 보정합니다.
59
+ *
54
60
  * @default undefined
55
61
  */
56
62
  placement?: Placement;
@@ -62,7 +68,9 @@ export declare class UFloatingElement extends UElement {
62
68
  */
63
69
  offset: OffsetOptions;
64
70
  /**
65
- * 엘리먼트가 화면 안에 머물도록 자동으로 위치를 조정할지 여부입니다.
71
+ * 지정된(또는 `flip`으로 전환된) 배치 **안에서** 교차축 위치를 자동으로
72
+ * 보정할지 여부입니다 — 그 변 자체에 공간이 없어 반대 변으로 넘어가는 것은
73
+ * `placement` 설명의 `flip` 동작이 담당합니다.
66
74
  *
67
75
  * @default false
68
76
  */
@@ -16,6 +16,11 @@ export type DatePickerMode = 'date' | 'datetime';
16
16
  * first day of week from `Intl.Locale(locale).weekInfo?.firstDay`, falling back to Sunday
17
17
  * where unsupported.
18
18
  *
19
+ * Exposes a `:state(open)` custom state (via `ElementInternals.states`) while the calendar
20
+ * popover is showing — style with `u-date-picker:state(open)::part(container)`. Unlike
21
+ * `mode`/`clearable`, this state is never reflected as a public attribute — it exists purely
22
+ * as a CSS hook for consumers who want to react to the open/closed state from outside.
23
+ *
19
24
  * @csspart field - the u-field element
20
25
  * @csspart container - the element wrapping the trigger area
21
26
  * @csspart popover - the popover element showing the calendar
@@ -178,6 +178,7 @@ var UDatePicker = class UDatePicker extends UFormControlElement {
178
178
  updated(changed) {
179
179
  super.updated(changed);
180
180
  if (changed.has("value")) this.internals?.setFormValue(this.value ?? "");
181
+ if (changed.has("open")) this.internals?.states[this.open ? "add" : "delete"]("open");
181
182
  if (changed.has("open") && this.open || changed.has("focusedDate") && this.open && this.grabFocusOnUpdate) {
182
183
  this.grabFocusOnUpdate = false;
183
184
  this.popoverEl?.updateComplete.then(() => this.focusDayButton(this.focusedDate));
@@ -0,0 +1,57 @@
1
+ import { PropertyValues } from 'lit';
2
+ import { UFormControlElement } from '../UFormControlElement.js';
3
+ /**
4
+ * 파일 선택 입력 컴포넌트입니다. 네이티브 `<input type="file">`를 감싸
5
+ * 디자인시스템 톤의 트리거 버튼 + 선택 결과 표시로 렌더링합니다.
6
+ *
7
+ * 값은 항상 `File[] | null`입니다 — `multiple`이 꺼져 있어도 선택된 파일은
8
+ * 배열(0개 또는 1개)로 담깁니다. 폼 제출 시 파일이 1개면 그 `File`을,
9
+ * 여러 개면 같은 `name`으로 반복 append한 `FormData`를 `ElementInternals`에
10
+ * 전달합니다 — 네이티브 `<input type="file" multiple>`과 같은 제출 형태입니다.
11
+ *
12
+ * @csspart field - u-field 요소
13
+ * @csspart container - 트리거·상태 텍스트·지우기 버튼을 감싸는 컨테이너
14
+ * @csspart trigger - 파일 선택 트리거 버튼
15
+ * @csspart status - 선택 상태 텍스트("선택된 파일 없음" · 파일명 · "N개 파일 선택됨")
16
+ * @csspart clear-button - 선택 해제 버튼(u-icon)
17
+ * @csspart input - 숨겨진 네이티브 `<input type="file">`
18
+ *
19
+ * @cssprop --u-file-input-display - 호스트의 display (기본값: inline-block)
20
+ * @cssprop --u-file-input-width - 호스트의 width (기본값: auto)
21
+ *
22
+ * @event change - 선택된 파일이 바뀔 때 발생(선택·지우기 공통)
23
+ */
24
+ export declare class UFileInput extends UFormControlElement<File[] | null> {
25
+ static styles: import('lit').CSSResultGroup[];
26
+ /** 값은 attribute로 표현될 수 없다 — 항상 프로그램적으로만 설정된다. */
27
+ value: File[] | null;
28
+ /** 선택 가능한 파일 형식을 제한한다(네이티브 `accept` 속성 그대로 통과). */
29
+ accept?: string;
30
+ /** 여러 파일을 한 번에 선택할 수 있는지 여부. */
31
+ multiple: boolean;
32
+ triggerEl?: HTMLButtonElement;
33
+ inputEl?: HTMLInputElement;
34
+ /** `value`가 바뀌는 모든 경로(선택·지우기)에서 폼 제출값을 동기화한다 —
35
+ * `UInput`/`UCheckbox`와 같은 경계(`DL-289-1`). 파일은 문자열로 표현할 수
36
+ * 없으므로 1개면 `File`을, 여러 개면 같은 키로 반복 append한 `FormData`를
37
+ * 넘긴다 — 네이티브 `<input type="file" multiple>`의 제출 형태와 동일하다. */
38
+ protected updated(changedProperties: PropertyValues): void;
39
+ private syncFormValue;
40
+ render(): import('lit-html').TemplateResult<1>;
41
+ protected setValidity(): void;
42
+ reset(): void;
43
+ focus(options?: FocusOptions): void;
44
+ blur(): void;
45
+ private handleTriggerClick;
46
+ private handleInputChange;
47
+ private handleClearButtonClick;
48
+ /** `clear-btn`은 순수 표시 요소(`u-icon`)라 네이티브 키보드 활성화가 없다 —
49
+ * `role="button"`+`tabindex="0"`로 포커스 가능하게 한 뒤, Enter/Space를 같은
50
+ * 클릭 핸들러로 릴레이한다(`UInput.handleSuffixIconKeydown`과 같은 패턴). */
51
+ private handleClearButtonKeydown;
52
+ }
53
+ declare global {
54
+ interface HTMLElementTagNameMap {
55
+ 'u-file-input': UFileInput;
56
+ }
57
+ }
@@ -0,0 +1,140 @@
1
+ import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js";
2
+ import __decorate from "../../_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js";
3
+ import { UFormControlElement } from "../UFormControlElement.js";
4
+ import "../icon/UIcon.js";
5
+ import { Locale } from "../../utilities/Locale.js";
6
+ import "../field/UField.js";
7
+ import { styles } from "./UFileInput.styles.js";
8
+ import { html } from "lit";
9
+ import { customElement, property, query } from "lit/decorators.js";
10
+ import { ifDefined } from "lit/directives/if-defined.js";
11
+ //#region src/components/file-input/UFileInput.ts
12
+ var UFileInput = class UFileInput extends UFormControlElement {
13
+ constructor(..._args) {
14
+ super(..._args);
15
+ this.value = null;
16
+ this.multiple = false;
17
+ this.handleTriggerClick = (e) => {
18
+ e.stopImmediatePropagation();
19
+ if (this.disabled || this.readonly) return;
20
+ this.inputEl?.click();
21
+ };
22
+ this.handleInputChange = (e) => {
23
+ const input = e.target;
24
+ this.value = input.files && input.files.length > 0 ? Array.from(input.files) : null;
25
+ if (!this.novalidate) this.validate();
26
+ this.relay(e);
27
+ };
28
+ this.handleClearButtonClick = (e) => {
29
+ e.stopImmediatePropagation();
30
+ this.reset();
31
+ this.dispatchEvent(new InputEvent("input", {
32
+ bubbles: true,
33
+ composed: true
34
+ }));
35
+ this.dispatchEvent(new Event("change", {
36
+ bubbles: true,
37
+ composed: true
38
+ }));
39
+ this.focus();
40
+ };
41
+ this.handleClearButtonKeydown = (e) => {
42
+ if (e.key !== "Enter" && e.key !== " ") return;
43
+ e.preventDefault();
44
+ this.handleClearButtonClick(e);
45
+ };
46
+ }
47
+ static {
48
+ this.styles = [super.styles, styles];
49
+ }
50
+ /** `value`가 바뀌는 모든 경로(선택·지우기)에서 폼 제출값을 동기화한다 —
51
+ * `UInput`/`UCheckbox`와 같은 경계(`DL-289-1`). 파일은 문자열로 표현할 수
52
+ * 없으므로 1개면 `File`을, 여러 개면 같은 키로 반복 append한 `FormData`를
53
+ * 넘긴다 — 네이티브 `<input type="file" multiple>`의 제출 형태와 동일하다. */
54
+ updated(changedProperties) {
55
+ super.updated(changedProperties);
56
+ if (changedProperties.has("value")) this.syncFormValue();
57
+ }
58
+ syncFormValue() {
59
+ const files = this.value ?? [];
60
+ if (files.length === 0) this.internals?.setFormValue(null);
61
+ else if (files.length === 1) this.internals?.setFormValue(files[0]);
62
+ else {
63
+ const fd = new FormData();
64
+ for (const file of files) fd.append(this.name ?? "", file);
65
+ this.internals?.setFormValue(fd);
66
+ }
67
+ }
68
+ render() {
69
+ const files = this.value ?? [];
70
+ const hasFiles = files.length > 0;
71
+ const status = !hasFiles ? Locale.getValue("noFileChosen") : files.length === 1 ? files[0].name : Locale.getValue("filesSelected", { count: files.length });
72
+ return html`
73
+ <u-field part="field"
74
+ ?required=${this.required}
75
+ ?disabled=${this.disabled}
76
+ ?invalid=${this.invalid}
77
+ .label=${this.label}
78
+ .description=${this.description}
79
+ .validationMessage=${this.validationMessage}
80
+ >
81
+ <div class="container" part="container">
82
+ <button class="trigger" part="trigger"
83
+ type="button"
84
+ ?disabled=${this.disabled || this.readonly}
85
+ @click=${this.handleTriggerClick}
86
+ >${Locale.getValue("chooseFile")}</button>
87
+
88
+ <span class="status" part="status" ?data-empty=${!hasFiles}>${status}</span>
89
+
90
+ <u-icon class="clear-btn" part="clear-button"
91
+ ?hidden=${!hasFiles || this.disabled || this.readonly}
92
+ role="button"
93
+ tabindex="0"
94
+ aria-label=${Locale.getValue("clear")}
95
+ lib="internal"
96
+ name="x"
97
+ @click=${this.handleClearButtonClick}
98
+ @keydown=${this.handleClearButtonKeydown}
99
+ ></u-icon>
100
+
101
+ <input class="native-input" part="input"
102
+ type="file"
103
+ hidden
104
+ name=${ifDefined(this.name)}
105
+ accept=${ifDefined(this.accept)}
106
+ ?multiple=${this.multiple}
107
+ ?disabled=${this.disabled || this.readonly}
108
+ @change=${this.handleInputChange}
109
+ />
110
+ </div>
111
+ </u-field>
112
+ `;
113
+ }
114
+ setValidity() {
115
+ const missing = this.required && (!this.value || this.value.length === 0);
116
+ this.commit(missing ? { valueMissing: true } : {}, missing ? Locale.getValue("valueMissing") : "", this.triggerEl ?? void 0);
117
+ }
118
+ reset() {
119
+ this.value = null;
120
+ this.invalid = false;
121
+ if (this.inputEl) this.inputEl.value = "";
122
+ }
123
+ focus(options) {
124
+ this.triggerEl?.focus(options);
125
+ }
126
+ blur() {
127
+ this.triggerEl?.blur();
128
+ }
129
+ };
130
+ __decorate([property({ attribute: false }), __decorateMetadata("design:type", Object)], UFileInput.prototype, "value", void 0);
131
+ __decorate([property({ type: String }), __decorateMetadata("design:type", String)], UFileInput.prototype, "accept", void 0);
132
+ __decorate([property({
133
+ type: Boolean,
134
+ reflect: true
135
+ }), __decorateMetadata("design:type", Boolean)], UFileInput.prototype, "multiple", void 0);
136
+ __decorate([query(".trigger", true), __decorateMetadata("design:type", typeof HTMLButtonElement === "undefined" ? Object : HTMLButtonElement)], UFileInput.prototype, "triggerEl", void 0);
137
+ __decorate([query("input[type=\"file\"]", true), __decorateMetadata("design:type", typeof HTMLInputElement === "undefined" ? Object : HTMLInputElement)], UFileInput.prototype, "inputEl", void 0);
138
+ UFileInput = __decorate([customElement("u-file-input")], UFileInput);
139
+ //#endregion
140
+ export { UFileInput };
@@ -0,0 +1 @@
1
+ export declare const styles: import('lit').CSSResult;
@@ -0,0 +1,80 @@
1
+ import { css } from "lit";
2
+ //#region src/components/file-input/UFileInput.styles.ts
3
+ var styles = css`
4
+ :host {
5
+ display: var(--u-file-input-display, inline-block);
6
+ width: var(--u-file-input-width, auto);
7
+ color: var(--u-txt-color, #212121);
8
+ font-size: inherit;
9
+ font-family: var(--u-font-base);
10
+ }
11
+
12
+ .container {
13
+ display: flex;
14
+ flex-direction: row;
15
+ align-items: center;
16
+ gap: 0.6em;
17
+ }
18
+
19
+ .trigger {
20
+ flex-shrink: 0;
21
+ display: inline-flex;
22
+ align-items: center;
23
+ padding: 0.4em 0.9em;
24
+ border: 1px solid var(--u-input-border-color, #E0E0E0);
25
+ border-radius: 0.25em;
26
+ background-color: var(--u-panel-bg-color, #FFFFFF);
27
+ color: var(--u-primary-color, #1976D2);
28
+ font: inherit;
29
+ font-weight: 500;
30
+ cursor: pointer;
31
+ transition: border-color var(--u-duration-normal, 220ms) var(--u-ease-standard, cubic-bezier(0.2, 0, 0, 1)), background-color var(--u-duration-normal, 220ms) var(--u-ease-standard, cubic-bezier(0.2, 0, 0, 1));
32
+ }
33
+ .trigger:hover:not(:disabled) {
34
+ background-color: var(--u-neutral-100, #F5F5F5);
35
+ border-color: var(--u-input-border-color-hover, #BDBDBD);
36
+ }
37
+ .trigger:focus-visible {
38
+ outline: none;
39
+ box-shadow: 0 0 0 1px var(--u-input-border-color-focus, #1565C0),
40
+ 0 0 0 3px color-mix(in srgb, var(--u-primary-color-strong, #1565C0) 22%, transparent);
41
+ }
42
+ .trigger:disabled {
43
+ color: var(--u-txt-color-weak, #757575);
44
+ background-color: var(--u-bg-color-disabled, #FAFAFA);
45
+ border-color: var(--u-border-color-weak, #EEEEEE);
46
+ cursor: not-allowed;
47
+ }
48
+
49
+ .status {
50
+ flex: 1 1 auto;
51
+ min-width: 0;
52
+ overflow: hidden;
53
+ text-overflow: ellipsis;
54
+ white-space: nowrap;
55
+ font-size: 0.9em;
56
+ }
57
+ .status[data-empty] {
58
+ color: var(--u-txt-color-weak, #757575);
59
+ }
60
+
61
+ .clear-btn {
62
+ flex-shrink: 0;
63
+ color: var(--u-icon-color, #616161);
64
+ font-size: 1em;
65
+ cursor: pointer;
66
+ transition: color var(--u-duration-normal, 220ms) var(--u-ease-standard, cubic-bezier(0.2, 0, 0, 1));
67
+ }
68
+ .clear-btn:hover {
69
+ color: var(--u-icon-color-hover, #1565C0);
70
+ }
71
+ .clear-btn:active {
72
+ color: var(--u-icon-color-active, #1565C0);
73
+ }
74
+
75
+ :host([invalid]) .trigger {
76
+ border-color: var(--u-input-border-color-invalid, #C62828);
77
+ }
78
+ `;
79
+ //#endregion
80
+ export { styles };
@@ -12,6 +12,10 @@ export type SelectVariant = 'outlined' | 'filled' | 'underlined' | 'borderless';
12
12
  * 팝오버 목록에서 사라지기 때문. 리치 콘텐츠가 필요하면 이 slot에 별도 마크업을 할당하고
13
13
  * `change` 이벤트에서 직접 갱신한다.)
14
14
  *
15
+ * 옵션 목록 팝오버가 열려 있는 동안 `:state(open)` 커스텀 상태를 노출한다(via
16
+ * `ElementInternals.states`) — `u-select:state(open)::part(container)`로 스타일링할 수
17
+ * 있다. 공개 속성으로 반사되지 않는다.
18
+ *
15
19
  * @csspart field - u-field 요소
16
20
  * @csspart container - 트리거 영역을 감싸는 요소
17
21
  * @csspart popover - 옵션 목록이 표시되는 팝오버 요소
@@ -153,6 +153,7 @@ var USelect = class USelect extends UFormControlElement {
153
153
  updated(changedProperties) {
154
154
  super.updated(changedProperties);
155
155
  if (["value", "options"].some((k) => changedProperties.has(k))) this.onChangeValue();
156
+ if (changedProperties.has("open")) this.internals?.states[this.open ? "add" : "delete"]("open");
156
157
  }
157
158
  render() {
158
159
  return html`
package/dist/index.d.ts CHANGED
@@ -20,6 +20,7 @@ export * from './components/divider/UDivider.js';
20
20
  export * from './components/drawer/UDrawer.js';
21
21
  export * from './components/expander/UExpander.js';
22
22
  export * from './components/field/UField.js';
23
+ export * from './components/file-input/UFileInput.js';
23
24
  export * from './components/form/UForm.js';
24
25
  export * from './components/icon/UIcon.js';
25
26
  export * from './components/icon-button/UIconButton.js';
package/dist/index.js CHANGED
@@ -32,6 +32,7 @@ import { UDialog } from "./components/dialog/UDialog.js";
32
32
  import { UDivider } from "./components/divider/UDivider.js";
33
33
  import { UDrawer } from "./components/drawer/UDrawer.js";
34
34
  import { UExpander } from "./components/expander/UExpander.js";
35
+ import { UFileInput } from "./components/file-input/UFileInput.js";
35
36
  import { UForm } from "./components/form/UForm.js";
36
37
  import { UOption } from "./components/option/UOption.js";
37
38
  import { UInput } from "./components/input/UInput.js";
@@ -57,4 +58,4 @@ import { BrowserStorage } from "./utilities/BrowserStorage.js";
57
58
  import { Dialog } from "./utilities/Dialog.js";
58
59
  import { Theme } from "./utilities/Theme.js";
59
60
  import { Toast } from "./utilities/Toast.js";
60
- export { BrowserStorage, Dialog, IconCache, IconRegistry, Locale, OverlayManager, Theme, Toast, UAlert, UAvatar, UBadge, UBreadcrumb, UBreadcrumbItem, UButton, UButtonGroup, UCard, UCarousel, UCheckbox, UChip, UCopyButton, UDatePicker, UDialog, UDivider, UDrawer, UElement, UExpander, UField, UFloatingElement, UForm, UFormControlElement, UIcon, UIconButton, UInput, UMenu, UMenuItem, UOption, UOverlayElement, UPanel, UPopover, UProgressBar, UProgressRing, URadio, URating, USelect, USkeleton, USlider, USpinner, USplitPanel, USwitch, UTab, UTabPanel, UTag, UText, UTextarea, UTooltip, UTree, UTreeItem, arrayAttrConverter, booleanAttrConverter, dateAttrConverter, formatCurrency, formatDate, formatNumber, getDefaultBaseUrl, getParentElement, isCoarsePointer, jsonAttrConverter, querySelectorAllWithin, querySelectorWithin, setDefaultBaseUrl, urlAttrConverter };
61
+ export { BrowserStorage, Dialog, IconCache, IconRegistry, Locale, OverlayManager, Theme, Toast, UAlert, UAvatar, UBadge, UBreadcrumb, UBreadcrumbItem, UButton, UButtonGroup, UCard, UCarousel, UCheckbox, UChip, UCopyButton, UDatePicker, UDialog, UDivider, UDrawer, UElement, UExpander, UField, UFileInput, UFloatingElement, UForm, UFormControlElement, UIcon, UIconButton, UInput, UMenu, UMenuItem, UOption, UOverlayElement, UPanel, UPopover, UProgressBar, UProgressRing, URadio, URating, USelect, USkeleton, USlider, USpinner, USplitPanel, USwitch, UTab, UTabPanel, UTag, UText, UTextarea, UTooltip, UTree, UTreeItem, arrayAttrConverter, booleanAttrConverter, dateAttrConverter, formatCurrency, formatDate, formatNumber, getDefaultBaseUrl, getParentElement, isCoarsePointer, jsonAttrConverter, querySelectorAllWithin, querySelectorWithin, setDefaultBaseUrl, urlAttrConverter };
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import { UFileInput as UFileInputElement } from '../components/file-input/UFileInput';
3
+
4
+ export declare const UFileInput: React.ForwardRefExoticComponent<
5
+ Omit<Partial<UFileInputElement>, keyof React.HTMLAttributes<UFileInputElement>>
6
+ & Omit<React.HTMLAttributes<UFileInputElement>, 'onChange'>
7
+ & React.RefAttributes<UFileInputElement>
8
+ & {
9
+ onChange?: (event: CustomEvent) => void;
10
+ }
11
+ >;
12
+
13
+ export type UFileInputProps = React.ComponentProps<typeof UFileInput>;
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ import { createComponent } from '@lit/react';
3
+ import { UFileInput as UFileInputElement } from '../components/file-input/UFileInput.js';
4
+
5
+ export const UFileInput = createComponent({
6
+ react: React,
7
+ tagName: 'u-file-input',
8
+ elementClass: UFileInputElement,
9
+ events: {
10
+ onChange: 'change',
11
+ },
12
+ });
@@ -25,6 +25,7 @@ export { UInput, UInputProps } from './UInput';
25
25
  export { UIconButton, UIconButtonProps } from './UIconButton';
26
26
  export { UIcon, UIconProps } from './UIcon';
27
27
  export { UForm, UFormProps } from './UForm';
28
+ export { UFileInput, UFileInputProps } from './UFileInput';
28
29
  export { UField, UFieldProps } from './UField';
29
30
  export { UExpander, UExpanderProps } from './UExpander';
30
31
  export { UDrawer, UDrawerProps } from './UDrawer';
@@ -25,6 +25,7 @@ export { UInput } from './UInput.js';
25
25
  export { UIconButton } from './UIconButton.js';
26
26
  export { UIcon } from './UIcon.js';
27
27
  export { UForm } from './UForm.js';
28
+ export { UFileInput } from './UFileInput.js';
28
29
  export { UField } from './UField.js';
29
30
  export { UExpander } from './UExpander.js';
30
31
  export { UDrawer } from './UDrawer.js';
@@ -12,7 +12,7 @@ export type SupportedLocale = 'en' | 'ko' | 'ja' | 'zh-CN' | 'zh-TW' | 'es' | 'f
12
12
  /** 내장 로케일은 자동완성되지만, 임의의 BCP47 태그도 그대로 받는다. */
13
13
  export type LocaleTag = SupportedLocale | (string & {});
14
14
  /** 라이브러리가 제공하는 chrome 문자열 키. 지금은 검증 메시지뿐이지만 이후 다른 UI 문구도 추가될 수 있다. */
15
- export type LocaleMessageKey = 'valueMissing' | 'badInput' | 'typeMismatch' | 'patternMismatch' | 'rangeUnderflow' | 'rangeOverflow' | 'stepMismatch' | 'tooShort' | 'tooLong' | 'increment' | 'decrement' | 'clear' | 'today' | 'time' | 'showPassword' | 'hidePassword' | 'previousSlide' | 'nextSlide' | 'goToSlide' | 'chooseDate' | 'previousMonth' | 'nextMonth' | 'close' | 'copy' | 'copied';
15
+ export type LocaleMessageKey = 'valueMissing' | 'badInput' | 'typeMismatch' | 'patternMismatch' | 'rangeUnderflow' | 'rangeOverflow' | 'stepMismatch' | 'tooShort' | 'tooLong' | 'increment' | 'decrement' | 'clear' | 'today' | 'time' | 'showPassword' | 'hidePassword' | 'previousSlide' | 'nextSlide' | 'goToSlide' | 'chooseDate' | 'previousMonth' | 'nextMonth' | 'close' | 'copy' | 'copied' | 'chooseFile' | 'noFileChosen' | 'filesSelected';
16
16
  type LocaleTable = Record<LocaleMessageKey, string>;
17
17
  /**
18
18
  * 한 패키지(또는 앱 영역)의 문자열 묶음.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@iyulab/components",
3
3
  "description": "web-components library based on lit-element made by iyulab",
4
- "version": "1.35.7",
4
+ "version": "1.37.0",
5
5
  "keywords": [
6
6
  "iyulab",
7
7
  "components",
@@ -67,6 +67,8 @@ import { UButton, UInput } from '@iyulab/components/react';
67
67
  - [`u-input`](./references/components/input.md) — Text input; combobox mode when `u-option` children are present
68
68
  - [`u-textarea`](./references/components/textarea.md) — Multi-line text input with auto-resize and character counter
69
69
  - [`u-select`](./references/components/select.md) — Dropdown select; single or multiple, with search
70
+ - [`u-date-picker`](./references/components/date-picker.md) — Single-date(-time) selection with a popover calendar
71
+ - [`u-file-input`](./references/components/file-input.md) — File picker with a design-system trigger button and selected-file(s) display
70
72
  - [`u-checkbox`](./references/components/checkbox.md) — Checkbox with indeterminate state support
71
73
  - [`u-radio`](./references/components/radio.md) — Radio group built from `u-option` children
72
74
  - [`u-switch`](./references/components/switch.md) — Toggle switch
@@ -0,0 +1,81 @@
1
+ # u-file-input
2
+
3
+ ```ts
4
+ import '@iyulab/components/dist/components/file-input/UFileInput.js';
5
+ ```
6
+
7
+ **Tag:** `u-file-input`
8
+
9
+ File picker with a design-system-styled trigger button and a selected-file(s) status display,
10
+ wrapping a hidden native `<input type="file">`. Form-associated (`formAssociated = true`).
11
+
12
+ `value` is always `File[] | null` — even with `multiple` off, a selected file is held as a
13
+ one-element array. On form submission: 0 files → nothing is sent for `name`; 1 file → the `File`
14
+ itself; more than 1 file → a `FormData` with the same `File` repeatedly appended under `name`,
15
+ matching what a native `<input type="file" multiple>` submits.
16
+
17
+ ```html
18
+ <u-file-input name="attachment" required accept="image/*"></u-file-input>
19
+
20
+ <u-file-input name="attachments" multiple accept=".csv,.xlsx"></u-file-input>
21
+ ```
22
+
23
+ ```ts
24
+ const picker = document.querySelector('u-file-input');
25
+ picker.addEventListener('change', () => {
26
+ console.log(picker.value); // File[] | null
27
+ });
28
+ ```
29
+
30
+ No drag-and-drop in this version — it is a minimal wrapper (trigger + status + clear), by design.
31
+
32
+ ---
33
+
34
+ ## Properties
35
+
36
+ | Property | Type | Default | Reflect | Description |
37
+ |----------|------|---------|---------|-------------|
38
+ | `value` | `File[] \| null` | `null` | — | Selected files (always an array, even for single-file selection) |
39
+ | `accept` | `string` | — | — | Passed through to the native `accept` attribute |
40
+ | `multiple` | `boolean` | `false` | ✓ | Allow selecting more than one file |
41
+ | `disabled` | `boolean` | `false` | ✓ | Disable the control |
42
+ | `readonly` | `boolean` | `false` | ✓ | Read-only (trigger and clear button inert) |
43
+ | `required` | `boolean` | `false` | ✓ | Required field |
44
+ | `invalid` | `boolean` | `false` | ✓ | Validation failed state |
45
+ | `name` | `string` | — | — | Form field name |
46
+ | `label` | `string` | — | — | Label text |
47
+ | `description` | `string` | — | — | Helper text below the control |
48
+ | `validationMessage` | `string` | — | — | Custom validation message |
49
+
50
+ ## Methods
51
+
52
+ | Method | Returns | Description |
53
+ |--------|---------|-------------|
54
+ | `validate()` | `boolean` | Run validation; sets `invalid` |
55
+ | `reset()` | `void` | Clear the selection and validation state |
56
+ | `focus(options?)` | `void` | Focus the trigger button |
57
+ | `blur()` | `void` | Blur the trigger button |
58
+
59
+ ## CSS Parts
60
+
61
+ | Part | Description |
62
+ |------|-------------|
63
+ | `field` | Inner `u-field` element (label/description/validation layout) |
64
+ | `container` | Wrapper around the trigger, status text, and clear button |
65
+ | `trigger` | The file-picker trigger button |
66
+ | `status` | Status text — "No file chosen", the filename, or "N files selected" |
67
+ | `clear-button` | Clear-selection button (a `u-icon`) |
68
+ | `input` | The hidden native `<input type="file">` |
69
+
70
+ ## CSS Custom Properties
71
+
72
+ | Property | Description |
73
+ |----------|--------------|
74
+ | `--u-file-input-display` | Host `display` (default: `inline-block`) |
75
+ | `--u-file-input-width` | Host `width` (default: `auto`) |
76
+
77
+ ## Events
78
+
79
+ | Event | Description |
80
+ |-------|-------------|
81
+ | `change` | Fires when the selection changes (picking files or clearing) |
@@ -40,7 +40,7 @@ Inherits all `UFloatingElement` properties (see [floating.md](../extensions/floa
40
40
  | `disabled` | `boolean` | `false` | ✓ | Disable opening |
41
41
  | `placement` | `Placement` | — | — | Preferred placement (`top`, `bottom`, `left`, `right`, `*-start`, `*-end`) |
42
42
  | `offset` | `OffsetOptions` | `0` | — | Distance from anchor |
43
- | `shift` | `boolean` | `false` | — | Auto-shift to stay in viewport |
43
+ | `shift` | `boolean` | `false` | — | Correct the cross-axis position *within* the placed side (see note below) |
44
44
  | `arrow` | `boolean` | `false` | — | Show arrow pointing to anchor |
45
45
  | `showDelay` | `number` | `0` | — | Open delay in ms |
46
46
  | `hideDelay` | `number` | `0` | — | Close delay in ms |
@@ -48,6 +48,12 @@ Inherits all `UFloatingElement` properties (see [floating.md](../extensions/floa
48
48
  | `dismiss` | `string[]` | `['click','escape','scroll','resize']` | ✓ | Close triggers — see note below |
49
49
  | `autofocus` | `boolean` | `false` | ✓ | Focus first focusable element on open |
50
50
 
51
+ > **Placement fallback**: whenever `placement` is set, floating-ui's `flip` middleware is
52
+ > always applied — if the placed side has no room, the element switches to the opposite side.
53
+ > `shift` is a separate, narrower correction: it nudges the element along the cross axis
54
+ > *within* whichever side ended up being used. Neither can be turned off independently.
55
+
56
+
51
57
  ### `dismiss` semantics
52
58
 
53
59
  `click` and `escape` express user intent and always close the popover.
@@ -43,13 +43,19 @@ Inherits all `UFloatingElement` properties (see [floating.md](../extensions/floa
43
43
  | `open` | `boolean` | `false` | ✓ | Visibility state |
44
44
  | `placement` | `Placement` | — | — | Preferred placement |
45
45
  | `offset` | `OffsetOptions` | `0` | — | Gap from target |
46
- | `shift` | `boolean` | `false` | — | Auto-shift to stay in viewport |
46
+ | `shift` | `boolean` | `false` | — | Correct the cross-axis position *within* the placed side (see note below) |
47
47
  | `arrow` | `boolean` | `false` | — | Show pointing arrow |
48
48
  | `showDelay` | `number` | `0` | — | Open delay in ms |
49
49
  | `hideDelay` | `number` | `0` | — | Close delay in ms |
50
50
  | `interactive` | `boolean` | `false` | ✓ | Keep open when mouse is over tooltip |
51
51
  | `tracking` | `boolean` | `false` | ✓ | Follow mouse cursor position |
52
52
 
53
+ > **Placement fallback**: whenever `placement` is set, floating-ui's `flip` middleware is
54
+ > always applied — if the placed side has no room, the element switches to the opposite side.
55
+ > `shift` is a separate, narrower correction: it nudges the element along the cross axis
56
+ > *within* whichever side ended up being used. Neither can be turned off independently.
57
+
58
+
53
59
  ## Events
54
60
 
55
61
  | Event | Cancelable | Description |