@xleddyl/443-components-vue 0.2.3 → 0.4.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 (236) hide show
  1. package/README.md +106 -19
  2. package/dist/chart/BarChart.vue.d.ts +2 -2
  3. package/dist/chart/ChartLegend.vue.d.ts +4 -4
  4. package/dist/chart/ChartPanel.vue.d.ts +2 -2
  5. package/dist/chart/ChartSummary.vue.d.ts +2 -2
  6. package/dist/chart/DoughnutChart.vue.d.ts +2 -2
  7. package/dist/chart/LineChart.vue.d.ts +2 -2
  8. package/dist/components/core/Button.vue.d.ts +2 -2
  9. package/dist/components/core/FieldBoundary.vue +14 -0
  10. package/dist/components/core/FieldBoundary.vue.d.ts +15 -0
  11. package/dist/components/core/FieldMessage.vue +28 -0
  12. package/dist/components/core/FieldMessage.vue.d.ts +409 -0
  13. package/dist/components/core/Form.vue +109 -11
  14. package/dist/components/core/Form.vue.d.ts +45 -6
  15. package/dist/components/core/FormAlerts.vue +30 -0
  16. package/dist/components/core/FormAlerts.vue.d.ts +393 -0
  17. package/dist/components/core/FormField.vue +54 -17
  18. package/dist/components/core/FormField.vue.d.ts +5 -4
  19. package/dist/components/core/Icon.vue.d.ts +2 -2
  20. package/dist/components/core/Input.vue.d.ts +4 -4
  21. package/dist/components/core/Kbd.vue.d.ts +2 -2
  22. package/dist/components/core/Root.vue.d.ts +6 -6
  23. package/dist/components/core/Spinner.vue +13 -1
  24. package/dist/components/core/Spinner.vue.d.ts +4 -2
  25. package/dist/components/core/Textarea.vue.d.ts +4 -4
  26. package/dist/components/core/VisuallyHidden.vue.d.ts +6 -6
  27. package/dist/components/core/index.d.ts +3 -0
  28. package/dist/components/core/index.mjs +3 -0
  29. package/dist/components/data/Avatar.vue.d.ts +2 -2
  30. package/dist/components/data/AvatarGroup.vue.d.ts +2 -2
  31. package/dist/components/data/Badge.vue.d.ts +2 -2
  32. package/dist/components/data/DescriptionList.vue.d.ts +2 -2
  33. package/dist/components/data/FilterPill.vue +35 -0
  34. package/dist/components/data/FilterPill.vue.d.ts +445 -0
  35. package/dist/components/data/FilterPills.vue +100 -27
  36. package/dist/components/data/FilterPills.vue.d.ts +47 -411
  37. package/dist/components/data/Pagination.vue.d.ts +4 -4
  38. package/dist/components/data/SelectionBar.vue +81 -0
  39. package/dist/components/data/SelectionBar.vue.d.ts +427 -0
  40. package/dist/components/data/Stat.vue.d.ts +8 -8
  41. package/dist/components/data/Tag.vue.d.ts +4 -4
  42. package/dist/components/data/Timeline.vue.d.ts +2 -2
  43. package/dist/components/data/Tree.vue +245 -21
  44. package/dist/components/data/Tree.vue.d.ts +40 -10
  45. package/dist/components/data/index.d.ts +3 -0
  46. package/dist/components/data/index.mjs +3 -0
  47. package/dist/components/data/types.d.ts +26 -2
  48. package/dist/components/feedback/Alert.vue.d.ts +18 -18
  49. package/dist/components/feedback/EmptyState.vue.d.ts +2 -2
  50. package/dist/components/feedback/LoadingOverlay.vue.d.ts +2 -2
  51. package/dist/components/feedback/Meter.vue.d.ts +2 -2
  52. package/dist/components/feedback/Progress.vue.d.ts +2 -2
  53. package/dist/components/feedback/ProgressRing.vue.d.ts +2 -2
  54. package/dist/components/feedback/Skeleton.vue.d.ts +3 -3
  55. package/dist/components/forms/Calendar.vue.d.ts +4 -4
  56. package/dist/components/forms/CheckboxGroup.vue +2 -1
  57. package/dist/components/forms/ColorInput.vue.d.ts +6 -6
  58. package/dist/components/forms/DateInput.vue.d.ts +14 -14
  59. package/dist/components/forms/DatePicker.vue.d.ts +6 -6
  60. package/dist/components/forms/FileInput.vue +160 -34
  61. package/dist/components/forms/FileInput.vue.d.ts +43 -15
  62. package/dist/components/forms/InputAddon.vue.d.ts +2 -2
  63. package/dist/components/forms/InputGroup.vue.d.ts +2 -2
  64. package/dist/components/forms/NumberInput.vue.d.ts +6 -6
  65. package/dist/components/forms/PasswordInput.vue.d.ts +16 -16
  66. package/dist/components/forms/PinInput.vue.d.ts +6 -6
  67. package/dist/components/forms/Radio.vue.d.ts +2 -2
  68. package/dist/components/forms/RadioGroup.vue +2 -1
  69. package/dist/components/forms/SegmentedControl.vue +33 -8
  70. package/dist/components/forms/SegmentedControl.vue.d.ts +15 -7
  71. package/dist/components/forms/Slider.vue.d.ts +6 -6
  72. package/dist/components/forms/Switch.vue.d.ts +6 -6
  73. package/dist/components/forms/TimeInput.vue.d.ts +16 -16
  74. package/dist/components/forms/types.d.ts +17 -0
  75. package/dist/components/layout/Accordion.vue +25 -5
  76. package/dist/components/layout/Accordion.vue.d.ts +11 -8
  77. package/dist/components/layout/AppShell.vue +1 -1
  78. package/dist/components/layout/AppShell.vue.d.ts +7 -7
  79. package/dist/components/layout/Breadcrumbs.vue +28 -20
  80. package/dist/components/layout/Breadcrumbs.vue.d.ts +18 -3
  81. package/dist/components/layout/Card.vue +22 -3
  82. package/dist/components/layout/Card.vue.d.ts +10 -4
  83. package/dist/components/layout/CopyButton.vue.d.ts +4 -4
  84. package/dist/components/layout/Grid.vue.d.ts +2 -2
  85. package/dist/components/layout/NavLink.vue.d.ts +8 -8
  86. package/dist/components/layout/PageHeader.vue +10 -1
  87. package/dist/components/layout/PageHeader.vue.d.ts +17 -9
  88. package/dist/components/layout/Separator.vue.d.ts +2 -2
  89. package/dist/components/layout/Sidebar.vue.d.ts +2 -2
  90. package/dist/components/layout/SidebarSection.vue.d.ts +2 -2
  91. package/dist/components/layout/Stack.vue.d.ts +6 -6
  92. package/dist/components/layout/Tabs.vue +37 -7
  93. package/dist/components/layout/Tabs.vue.d.ts +11 -4
  94. package/dist/components/layout/ThemeSwitch.vue.d.ts +2 -2
  95. package/dist/components/layout/Toolbar.vue.d.ts +2 -2
  96. package/dist/components/layout/types.d.ts +8 -1
  97. package/dist/components/overlays/CommandPalette.vue +35 -8
  98. package/dist/components/overlays/CommandPalette.vue.d.ts +273 -497
  99. package/dist/components/overlays/ConfirmDialog.vue.d.ts +8 -6
  100. package/dist/components/overlays/ConfirmHost.vue +5 -1
  101. package/dist/components/overlays/ConfirmHost.vue.d.ts +10 -8
  102. package/dist/components/overlays/ContextMenu.vue.d.ts +4 -4
  103. package/dist/components/overlays/Drawer.vue +4 -2
  104. package/dist/components/overlays/Drawer.vue.d.ts +8 -6
  105. package/dist/components/overlays/DropdownMenu.vue.d.ts +2 -2
  106. package/dist/components/overlays/HoverCard.vue +2 -1
  107. package/dist/components/overlays/HoverCard.vue.d.ts +8 -8
  108. package/dist/components/overlays/Modal.vue +5 -3
  109. package/dist/components/overlays/Modal.vue.d.ts +8 -6
  110. package/dist/components/overlays/Popover.vue +2 -1
  111. package/dist/components/overlays/Popover.vue.d.ts +8 -8
  112. package/dist/components/overlays/Toaster.vue.d.ts +4 -4
  113. package/dist/components/overlays/Tooltip.vue.d.ts +2 -2
  114. package/dist/components/overlays/types.d.ts +11 -5
  115. package/dist/components/templates/AccountMenu.vue.d.ts +6 -6
  116. package/dist/components/templates/AppearanceSettings.vue.d.ts +2 -2
  117. package/dist/components/templates/AuthLayout.vue.d.ts +2 -2
  118. package/dist/components/templates/Brand.vue +34 -5
  119. package/dist/components/templates/Brand.vue.d.ts +20 -15
  120. package/dist/components/templates/DashboardLayout.vue +49 -10
  121. package/dist/components/templates/DashboardLayout.vue.d.ts +332 -553
  122. package/dist/components/templates/DashboardPage.vue.d.ts +2 -2
  123. package/dist/components/templates/DetailPage.vue.d.ts +4 -4
  124. package/dist/components/templates/EmptyPage.vue.d.ts +2 -2
  125. package/dist/components/templates/ErrorPage.vue.d.ts +2 -2
  126. package/dist/components/templates/ListPage.vue +17 -11
  127. package/dist/components/templates/ListPage.vue.d.ts +3 -3
  128. package/dist/components/templates/NotFoundPage.vue.d.ts +1 -1
  129. package/dist/components/templates/SaveBar.vue.d.ts +4 -4
  130. package/dist/components/templates/SettingsGroup.vue.d.ts +2 -2
  131. package/dist/components/templates/SettingsModal.vue +5 -2
  132. package/dist/components/templates/SettingsModal.vue.d.ts +13 -8
  133. package/dist/components/templates/SettingsPage.vue.d.ts +4 -4
  134. package/dist/components/templates/SettingsSection.vue.d.ts +12 -8
  135. package/dist/components/templates/types.d.ts +25 -2
  136. package/dist/composables/core.d.ts +1 -0
  137. package/dist/composables/core.mjs +1 -0
  138. package/dist/composables/useField.d.ts +8 -1
  139. package/dist/composables/useField.mjs +41 -10
  140. package/dist/composables/useFieldBoundary.d.ts +2 -0
  141. package/dist/composables/useFieldBoundary.mjs +9 -0
  142. package/dist/composables/useFileDrop.d.ts +20 -0
  143. package/dist/composables/useFileDrop.mjs +115 -7
  144. package/dist/composables/useForm.d.ts +36 -19
  145. package/dist/composables/useForm.mjs +258 -51
  146. package/dist/editor/RichTextEditor.vue +32 -4
  147. package/dist/editor/RichTextEditor.vue.d.ts +11 -6
  148. package/dist/editor/rich-text-editor.css +1 -1
  149. package/dist/editor/types.d.ts +3 -0
  150. package/dist/grid/GridToolbar.vue.d.ts +4 -4
  151. package/dist/grid/cells/BadgeCell.vue.d.ts +2 -2
  152. package/dist/grid/cells/BooleanCell.vue.d.ts +2 -2
  153. package/dist/grid/cells/DateCell.vue.d.ts +2 -2
  154. package/dist/grid/cells/ImageCell.vue.d.ts +2 -2
  155. package/dist/grid/cells/LinkCell.vue.d.ts +2 -2
  156. package/dist/internal/core/ButtonElement.d.ts +2 -0
  157. package/dist/internal/core/ButtonElement.mjs +8 -0
  158. package/dist/internal/data/tree.d.ts +8 -0
  159. package/dist/internal/data/tree.mjs +41 -0
  160. package/dist/internal/forms/files.d.ts +9 -0
  161. package/dist/internal/forms/files.mjs +32 -1
  162. package/dist/internal/forms/standard-schema.d.ts +17 -0
  163. package/dist/internal/forms/standard-schema.mjs +40 -0
  164. package/dist/internal/keys.d.ts +2 -1
  165. package/dist/internal/keys.mjs +1 -0
  166. package/dist/internal/overlays/DialogFrame.vue +17 -7
  167. package/dist/internal/overlays/DialogFrame.vue.d.ts +9 -5
  168. package/dist/internal/overlays/MenuList.vue +2 -0
  169. package/dist/internal/overlays/MenuList.vue.d.ts +5 -5
  170. package/dist/internal/overlays/OverlayView.vue +2 -0
  171. package/dist/internal/overlays/OverlayView.vue.d.ts +14 -12
  172. package/dist/internal/overlays/dialog.d.ts +4 -1
  173. package/dist/internal/overlays/dialog.mjs +39 -5
  174. package/dist/internal/overlays/stack.d.ts +2 -1
  175. package/dist/locale/en.mjs +19 -2
  176. package/dist/locale/it.mjs +19 -2
  177. package/dist/locale/types.d.ts +17 -0
  178. package/dist/nuxt/module.d.ts +1 -0
  179. package/dist/nuxt/module.mjs +6 -3
  180. package/dist/nuxt/runtime/layouts/auth.vue.d.ts +8 -8
  181. package/dist/nuxt/runtime/layouts/dashboard.vue.d.ts +8 -8
  182. package/dist/styles/base.css +2 -0
  183. package/dist/styles/core/button.css +1 -13
  184. package/dist/styles/core/field-message.css +34 -0
  185. package/dist/styles/core/form-field.css +0 -23
  186. package/dist/styles/core/form.css +11 -0
  187. package/dist/styles/core/kbd.css +2 -2
  188. package/dist/styles/core/spinner.css +19 -0
  189. package/dist/styles/core.css +1 -0
  190. package/dist/styles/data/avatar.css +1 -1
  191. package/dist/styles/data/badge.css +1 -1
  192. package/dist/styles/data/filter-pills.css +40 -4
  193. package/dist/styles/data/pagination.css +0 -1
  194. package/dist/styles/data/selection-bar.css +89 -0
  195. package/dist/styles/data/stat.css +1 -6
  196. package/dist/styles/data/table.css +1 -3
  197. package/dist/styles/data/tag.css +1 -4
  198. package/dist/styles/data/tree.css +163 -2
  199. package/dist/styles/data.css +1 -0
  200. package/dist/styles/feedback/alert.css +0 -1
  201. package/dist/styles/feedback/empty-state.css +0 -1
  202. package/dist/styles/forms/calendar.css +1 -1
  203. package/dist/styles/forms/color-input.css +1 -1
  204. package/dist/styles/forms/combobox.css +0 -1
  205. package/dist/styles/forms/date-picker.css +1 -1
  206. package/dist/styles/forms/file-input.css +17 -1
  207. package/dist/styles/forms/number-input.css +1 -1
  208. package/dist/styles/forms/password-input.css +1 -5
  209. package/dist/styles/forms/segmented.css +8 -0
  210. package/dist/styles/forms/select.css +1 -1
  211. package/dist/styles/forms/slider.css +2 -2
  212. package/dist/styles/forms/switch.css +0 -1
  213. package/dist/styles/layout/accordion.css +22 -1
  214. package/dist/styles/layout/app-shell.css +4 -0
  215. package/dist/styles/layout/breadcrumbs.css +16 -1
  216. package/dist/styles/layout/card.css +20 -6
  217. package/dist/styles/layout/copy-button.css +2 -2
  218. package/dist/styles/layout/nav-link.css +2 -3
  219. package/dist/styles/layout/sidebar-nav.css +1 -1
  220. package/dist/styles/layout/sidebar.css +2 -2
  221. package/dist/styles/layout/tabs.css +65 -3
  222. package/dist/styles/layout/theme-switch.css +1 -3
  223. package/dist/styles/overlays/command.css +21 -3
  224. package/dist/styles/overlays/confirm.css +0 -1
  225. package/dist/styles/overlays/menu.css +3 -5
  226. package/dist/styles/overlays/modal.css +38 -0
  227. package/dist/styles/overlays/toast.css +0 -1
  228. package/dist/styles/templates/auth-layout.css +0 -1
  229. package/dist/styles/templates/brand.css +15 -0
  230. package/dist/styles/templates/empty-page.css +4 -4
  231. package/dist/styles/templates/form-page.css +1 -1
  232. package/dist/styles/templates/list-page.css +2 -6
  233. package/dist/styles/templates/page.css +1 -1
  234. package/dist/styles/templates/settings-modal.css +2 -3
  235. package/dist/styles/tokens.css +1 -1
  236. package/package.json +1 -1
@@ -8,7 +8,8 @@
8
8
  'is-over': over,
9
9
  'is-disabled': control.disabled.value,
10
10
  'is-invalid': control.invalid.value,
11
- 'has-files': files.length > 0
11
+ 'is-loading': loading,
12
+ 'has-files': list && files.length > 0
12
13
  },
13
14
  props.class
14
15
  ]"
@@ -22,12 +23,13 @@
22
23
  :disabled="control.disabled.value || void 0"
23
24
  :aria-required="control.required.value ? 'true' : void 0"
24
25
  :aria-invalid="control.invalid.value ? 'true' : void 0"
25
- :aria-disabled="readonly ? 'true' : void 0"
26
+ :aria-disabled="readonly || loading ? 'true' : void 0"
27
+ :aria-busy="loading ? 'true' : void 0"
26
28
  :aria-describedby="
27
29
  joinIds(
28
30
  $attrs['aria-describedby'],
29
31
  control.describedBy.value,
30
- hint ? hintId : void 0
32
+ hasHint ? hintId : void 0
31
33
  )
32
34
  "
33
35
  type="button"
@@ -35,13 +37,16 @@
35
37
  @click="browse"
36
38
  >
37
39
  <span class="k-file-input-dropzone-icon" aria-hidden="true">
38
- <Icon :name="over ? 'arrow-up-tray' : icon" />
40
+ <Spinner v-if="loading" size="sm" decorative />
41
+ <Icon v-else :name="over ? 'arrow-up-tray' : icon" />
39
42
  </span>
40
43
  <span class="k-file-input-dropzone-text">
41
44
  <span class="k-file-input-dropzone-title">
42
- <slot name="title" :over="over">{{ over ? dropLabel : titleLabel }}</slot>
45
+ <slot name="title" :over="over" :loading="loading">{{ currentTitle }}</slot>
46
+ </span>
47
+ <span v-if="hasHint" :id="hintId" class="k-file-input-dropzone-hint">
48
+ <slot name="hint">{{ hint }}</slot>
43
49
  </span>
44
- <span v-if="hint" :id="hintId" class="k-file-input-dropzone-hint">{{ hint }}</span>
45
50
  </span>
46
51
  </button>
47
52
  <input
@@ -52,19 +57,49 @@
52
57
  aria-hidden="true"
53
58
  :name="formName"
54
59
  :accept="acceptAttr"
55
- :multiple="multiple || void 0"
60
+ :multiple="isMultiple || void 0"
56
61
  :capture="capture"
57
62
  :disabled="control.disabled.value || void 0"
58
63
  @change="onNativeChange"
59
64
  />
60
- <ul v-if="files.length" role="list" class="k-file-input-list" :aria-label="listText">
65
+ <input
66
+ ref="directoryInputRef"
67
+ type="file"
68
+ class="k-file-input-native"
69
+ tabindex="-1"
70
+ aria-hidden="true"
71
+ multiple
72
+ :disabled="control.disabled.value || void 0"
73
+ @change="onNativeChange"
74
+ />
75
+ <div v-if="directoryAction || $slots.actions" class="k-file-input-actions">
76
+ <slot
77
+ name="actions"
78
+ :browse="browse"
79
+ :browse-directory="browseDirectory"
80
+ :disabled="!interactive"
81
+ >
82
+ <Button
83
+ variant="ghost"
84
+ tone="neutral"
85
+ size="sm"
86
+ icon="folder"
87
+ :label="directoryActionText"
88
+ :disabled="!interactive"
89
+ @click="browseDirectory"
90
+ />
91
+ </slot>
92
+ </div>
93
+ <ul v-if="list && files.length" role="list" class="k-file-input-list" :aria-label="listText">
61
94
  <li v-for="file in files" :key="fileKey(file)" class="k-file-input-item">
62
95
  <span class="k-file-input-thumb" aria-hidden="true">
63
96
  <img v-if="previews[fileKey(file)]" :src="previews[fileKey(file)]" alt="" />
64
97
  <Icon v-else name="document" />
65
98
  </span>
66
99
  <span class="k-file-input-meta">
67
- <span class="k-file-input-name" :title="file.name">{{ file.name }}</span>
100
+ <span class="k-file-input-name" :title="filePath(file)">{{
101
+ walksFolders ? filePath(file) : file.name
102
+ }}</span>
68
103
  <span class="k-file-input-size">{{ formatBytes(file.size, resolvedLocale) }}</span>
69
104
  </span>
70
105
  <Button
@@ -73,7 +108,7 @@
73
108
  variant="ghost"
74
109
  tone="neutral"
75
110
  icon="x-mark"
76
- :aria-label="`${removeText} ${file.name}`"
111
+ :aria-label="`${removeText} ${walksFolders ? filePath(file) : file.name}`"
77
112
  :title="removeText"
78
113
  @click="removeFile(file)"
79
114
  />
@@ -96,24 +131,36 @@ import {
96
131
  } from "vue";
97
132
  import { useFieldControl } from "../../composables/useField";
98
133
  import { useLocale } from "../../composables/useLocale";
99
- import { useFileDrop } from "../../composables/useFileDrop";
100
134
  import { joinIds } from "../../internal/forms/control";
135
+ import { readDroppedFiles, useFileDrop } from "../../composables/useFileDrop";
101
136
  import {
102
137
  fileKey,
138
+ filePath,
139
+ filesTree,
103
140
  formatBytes,
104
141
  isImageFile,
105
142
  matchesAccept,
106
- parseAccept
143
+ parseAccept,
144
+ setFilePath
107
145
  } from "../../internal/forms/files";
108
146
  import Button from "../core/Button.vue";
109
147
  import Icon from "../core/Icon.vue";
148
+ import Spinner from "../core/Spinner.vue";
110
149
  defineOptions({ inheritAttrs: false });
111
150
  const props = defineProps({
112
151
  name: { type: String, required: false },
113
152
  accept: { type: [String, Array], required: false },
153
+ acceptMode: { type: String, required: false, default: "filter" },
114
154
  maxSize: { type: Number, required: false },
115
155
  maxFiles: { type: Number, required: false },
116
156
  multiple: { type: Boolean, required: false },
157
+ directory: { type: [Boolean, String], required: false },
158
+ directoryAction: { type: Boolean, required: false },
159
+ directoryActionLabel: { type: String, required: false },
160
+ skipHidden: { type: Boolean, required: false },
161
+ list: { type: Boolean, required: false, default: true },
162
+ loading: { type: Boolean, required: false },
163
+ loadingText: { type: String, required: false },
117
164
  capture: { type: String, required: false },
118
165
  previews: { type: Boolean, required: false, default: true },
119
166
  size: { type: String, required: false, default: "md" },
@@ -132,20 +179,33 @@ const props = defineProps({
132
179
  class: { type: null, required: false },
133
180
  style: { type: null, required: false }
134
181
  });
182
+ const slots = defineSlots();
135
183
  const { locale: appLocale, t } = useLocale();
136
184
  const resolvedLocale = computed(() => props.locale ?? appLocale.value.code);
137
- const titleLabel = computed(() => props.title ?? t("fileInput.title"));
185
+ const titleLabel = computed(() => {
186
+ if (props.title) return props.title;
187
+ if (props.directory === "drop") return t("fileInput.mixedTitle");
188
+ return t(props.directory ? "fileInput.directoryTitle" : "fileInput.title");
189
+ });
190
+ const directoryActionText = computed(
191
+ () => props.directoryActionLabel ?? t("fileInput.browseDirectory")
192
+ );
193
+ const loadingLabel = computed(() => props.loadingText ?? t("fileInput.loading"));
138
194
  const dropLabel = computed(() => props.dropText ?? t("fileInput.drop"));
139
195
  const removeText = computed(() => props.removeLabel ?? t("common.remove"));
140
196
  const listText = computed(() => props.listLabel ?? t("fileInput.list"));
141
197
  function announceCount(count) {
142
198
  return props.announce ? props.announce(count) : t("fileInput.selected", { count });
143
199
  }
200
+ function announceAdded(count) {
201
+ return props.announce ? props.announce(count) : t("fileInput.added", { count });
202
+ }
144
203
  const model = defineModel({ type: null });
145
- const emit = defineEmits(["invalid", "add", "remove"]);
204
+ const emit = defineEmits(["invalid", "reject", "add", "remove"]);
146
205
  const hintId = `${useId()}-hint`;
147
206
  const rootRef = useTemplateRef("rootRef");
148
207
  const inputRef = useTemplateRef("inputRef");
208
+ const directoryInputRef = useTemplateRef("directoryInputRef");
149
209
  const buttonRef = useTemplateRef("buttonRef");
150
210
  const previews = reactive({});
151
211
  const announcement = ref("");
@@ -163,59 +223,123 @@ const files = computed(() => {
163
223
  const acceptList = computed(() => parseAccept(props.accept));
164
224
  const acceptAttr = computed(() => acceptList.value.join(",") || void 0);
165
225
  const formName = computed(() => props.name ?? control.field?.name.value);
166
- const interactive = computed(() => !control.disabled.value && !props.readonly);
226
+ const interactive = computed(() => !control.disabled.value && !props.readonly && !props.loading);
227
+ const isMultiple = computed(() => props.multiple || props.directory === true);
228
+ const walksFolders = computed(() => Boolean(props.directory));
229
+ const hasHint = computed(() => Boolean(props.hint || slots.hint));
230
+ const currentTitle = computed(() => {
231
+ if (props.loading) return loadingLabel.value;
232
+ return over.value ? dropLabel.value : titleLabel.value;
233
+ });
234
+ function toEntries(list) {
235
+ return list.map((file) => ({ file, path: filePath(file) }));
236
+ }
167
237
  const { over } = useFileDrop(rootRef, {
168
238
  disabled: () => !interactive.value,
169
- onDrop: (dropped) => addFiles(dropped)
239
+ onDrop: (dropped, event) => {
240
+ const transfer = event.dataTransfer;
241
+ const origin = { source: "drop", event, dataTransfer: transfer };
242
+ if (!walksFolders.value) {
243
+ const tree = filesTree(dropped, { skipHidden: props.skipHidden });
244
+ addEntries(tree.files, { ...origin, directories: [] });
245
+ return;
246
+ }
247
+ void readDroppedFiles(transfer, { directories: true, skipHidden: props.skipHidden }).then(
248
+ (tree) => addEntries(tree.files, { ...origin, directories: tree.directories })
249
+ );
250
+ }
170
251
  });
171
252
  function browse() {
172
253
  if (!interactive.value) return;
173
254
  inputRef.value?.click();
174
255
  }
256
+ function browseDirectory() {
257
+ if (!interactive.value) return;
258
+ const input = directoryInputRef.value;
259
+ if (!input) return;
260
+ input.webkitdirectory = true;
261
+ input.click();
262
+ }
175
263
  function validate(file) {
176
- if (!matchesAccept(file, acceptList.value))
264
+ if (props.acceptMode === "filter" && !matchesAccept(file, acceptList.value))
177
265
  return { reason: "type", file, limit: acceptList.value.join(", ") };
178
266
  if (props.maxSize !== void 0 && file.size > props.maxSize)
179
267
  return { reason: "size", file, limit: props.maxSize };
180
268
  return void 0;
181
269
  }
182
270
  function addFiles(incoming) {
183
- if (!interactive.value || !incoming.length) return;
271
+ const tree = filesTree(incoming, { skipHidden: props.skipHidden });
272
+ addEntries(tree.files, {
273
+ source: "browse",
274
+ event: new Event("add"),
275
+ dataTransfer: null,
276
+ directories: walksFolders.value ? tree.directories : []
277
+ });
278
+ }
279
+ function addEntries(incomingEntries, origin) {
280
+ if (!interactive.value) return;
281
+ if (!incomingEntries.length && !origin.directories.length) return;
282
+ for (const entry of incomingEntries) setFilePath(entry.file, entry.path);
283
+ const incoming = incomingEntries.map((entry) => entry.file);
284
+ const multiple = isMultiple.value;
184
285
  const accepted = [];
185
- const existing = new Set(files.value.map(fileKey));
186
- const limit = props.multiple ? props.maxFiles ?? Infinity : 1;
187
- const room = props.multiple ? limit - files.value.length : 1;
188
- for (const file of props.multiple ? incoming : incoming.slice(0, 1)) {
286
+ const kept = props.list ? files.value : [];
287
+ const existing = new Set(kept.map(fileKey));
288
+ const limit = multiple ? props.maxFiles ?? Infinity : 1;
289
+ const room = multiple ? limit - kept.length : 1;
290
+ const rejected = [];
291
+ const reject = (problem) => {
292
+ rejected.push(problem);
293
+ emit("invalid", problem);
294
+ };
295
+ for (const file of multiple ? incoming : incoming.slice(0, 1)) {
189
296
  const problem = validate(file);
190
297
  if (problem) {
191
- emit("invalid", problem);
298
+ reject(problem);
192
299
  continue;
193
300
  }
194
301
  if (existing.has(fileKey(file))) continue;
195
302
  if (accepted.length >= room) {
196
- emit("invalid", { reason: "count", file, limit });
303
+ reject({ reason: "count", file, limit });
197
304
  continue;
198
305
  }
199
306
  accepted.push(file);
200
307
  existing.add(fileKey(file));
201
308
  }
202
- if (!props.multiple && incoming.length > 1)
203
- for (const file of incoming.slice(1)) emit("invalid", { reason: "count", file, limit: 1 });
309
+ if (!multiple && incoming.length > 1)
310
+ for (const file of incoming.slice(1)) reject({ reason: "count", file, limit: 1 });
311
+ const detail = { ...origin, entries: toEntries(accepted) };
312
+ if (rejected.length) emit("reject", rejected, detail);
313
+ if (!accepted.length && !origin.directories.length) return;
314
+ if (props.list && accepted.length)
315
+ model.value = multiple ? [...files.value, ...accepted] : accepted[0];
316
+ emit("add", accepted, detail);
204
317
  if (!accepted.length) return;
205
- model.value = props.multiple ? [...files.value, ...accepted] : accepted[0];
206
- emit("add", accepted);
207
- announcement.value = announceCount(props.multiple ? files.value.length : 1);
318
+ announcement.value = props.list ? announceCount(multiple ? files.value.length : 1) : announceAdded(accepted.length);
208
319
  }
209
320
  function onNativeChange(event) {
210
321
  const target = event.target;
211
- addFiles(Array.from(target.files ?? []));
322
+ const tree = filesTree(Array.from(target.files ?? []), { skipHidden: props.skipHidden });
323
+ const folders = target.webkitdirectory;
324
+ addEntries(tree.files, {
325
+ source: "browse",
326
+ event,
327
+ dataTransfer: null,
328
+ directories: folders ? tree.directories : []
329
+ });
212
330
  target.value = "";
331
+ if (target === inputRef.value) syncDirectory();
332
+ }
333
+ function syncDirectory() {
334
+ const input = inputRef.value;
335
+ if (!input) return;
336
+ input.webkitdirectory = props.directory === true;
213
337
  }
214
338
  function removeFile(file) {
215
339
  const key = fileKey(file);
216
- model.value = props.multiple ? files.value.filter((entry) => fileKey(entry) !== key) : null;
340
+ model.value = isMultiple.value ? files.value.filter((entry) => fileKey(entry) !== key) : null;
217
341
  emit("remove", file);
218
- announcement.value = announceCount(props.multiple ? files.value.length : 0);
342
+ announcement.value = announceCount(isMultiple.value ? files.value.length : 0);
219
343
  buttonRef.value?.focus();
220
344
  }
221
345
  function syncPreviews(current) {
@@ -234,7 +358,7 @@ function syncPreviews(current) {
234
358
  }
235
359
  function syncNativeInput(current) {
236
360
  const input = inputRef.value;
237
- if (!input || typeof DataTransfer === "undefined") return;
361
+ if (!input || !props.list || typeof DataTransfer === "undefined") return;
238
362
  try {
239
363
  const transfer = new DataTransfer();
240
364
  for (const file of current) transfer.items.add(file);
@@ -247,12 +371,14 @@ watch(files, (current) => {
247
371
  syncPreviews(current);
248
372
  syncNativeInput(current);
249
373
  });
374
+ watch(() => props.directory, syncDirectory);
250
375
  onMounted(() => {
251
376
  mounted.value = true;
377
+ syncDirectory();
252
378
  syncPreviews(files.value);
253
379
  });
254
380
  onBeforeUnmount(() => {
255
381
  for (const url of Object.values(previews)) URL.revokeObjectURL(url);
256
382
  });
257
- defineExpose({ focus: () => buttonRef.value?.focus(), browse, addFiles });
383
+ defineExpose({ focus: () => buttonRef.value?.focus(), browse, browseDirectory, addFiles });
258
384
  </script>
@@ -1,20 +1,35 @@
1
1
  import type { IconName } from '../../composables/useIcons';
2
2
  import type { FieldSize } from '../core/types';
3
- import type { FileInvalidEvent } from './types';
3
+ import type { FileAddDetail, FileInputAcceptMode, FileInputDirectory, FileInvalidEvent } from './types';
4
+ type __VLS_Slots = {
5
+ title?: (props: {
6
+ over: boolean;
7
+ loading: boolean;
8
+ }) => unknown;
9
+ hint?: () => unknown;
10
+ actions?: (props: {
11
+ browse: () => void;
12
+ browseDirectory: () => void;
13
+ disabled: boolean;
14
+ }) => unknown;
15
+ };
4
16
  declare function browse(): void;
17
+ declare function browseDirectory(): void;
5
18
  declare function addFiles(incoming: File[]): void;
6
- declare var __VLS_6: {
7
- over: boolean;
8
- };
9
- type __VLS_Slots = {} & {
10
- title?: (props: typeof __VLS_6) => any;
11
- };
12
19
  declare const __VLS_base: import("vue").DefineComponent<{
13
20
  name?: string | undefined;
14
21
  accept?: string | string[];
22
+ acceptMode?: FileInputAcceptMode;
15
23
  maxSize?: number;
16
24
  maxFiles?: number;
17
25
  multiple?: boolean;
26
+ directory?: FileInputDirectory;
27
+ directoryAction?: boolean;
28
+ directoryActionLabel?: string;
29
+ skipHidden?: boolean;
30
+ list?: boolean;
31
+ loading?: boolean;
32
+ loadingText?: string;
18
33
  capture?: "user" | "environment";
19
34
  previews?: boolean;
20
35
  size?: FieldSize;
@@ -39,8 +54,6 @@ declare const __VLS_base: import("vue").DefineComponent<{
39
54
  form?: string | undefined | undefined;
40
55
  results?: (string | number) | undefined;
41
56
  placeholder?: string | undefined | undefined;
42
- type?: "submit" | "reset" | "button" | undefined | undefined;
43
- ref?: import("vue").VNodeRef | undefined;
44
57
  role?: string | undefined | undefined;
45
58
  'aria-label'?: string | undefined | undefined;
46
59
  'aria-hidden'?: (boolean | "true" | "false") | undefined;
@@ -141,6 +154,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
141
154
  color?: string | undefined | undefined;
142
155
  id?: string | undefined | undefined;
143
156
  lang?: string | undefined | undefined;
157
+ type?: "submit" | "reset" | "button" | undefined | undefined;
144
158
  tabindex?: (string | number) | undefined;
145
159
  'aria-activedescendant'?: string | undefined | undefined;
146
160
  'aria-atomic'?: (boolean | "true" | "false") | undefined;
@@ -151,7 +165,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
151
165
  'aria-colindex'?: (string | number) | undefined;
152
166
  'aria-colspan'?: (string | number) | undefined;
153
167
  'aria-controls'?: string | undefined | undefined;
154
- 'aria-current'?: "page" | "location" | "time" | (boolean | "true" | "false") | "step" | "date" | undefined;
168
+ 'aria-current'?: "page" | "time" | (boolean | "true" | "false") | "step" | "location" | "date" | undefined;
155
169
  'aria-describedby'?: string | undefined | undefined;
156
170
  'aria-details'?: string | undefined | undefined;
157
171
  'aria-disabled'?: (boolean | "true" | "false") | undefined;
@@ -188,6 +202,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
188
202
  'aria-valuemin'?: (string | number) | undefined;
189
203
  'aria-valuenow'?: (string | number) | undefined;
190
204
  'aria-valuetext'?: string | undefined | undefined;
205
+ ref?: import("vue").VNodeRef | undefined;
191
206
  ref_for?: boolean | undefined | undefined;
192
207
  ref_key?: string | undefined | undefined;
193
208
  accesskey?: string | undefined | undefined;
@@ -235,18 +250,28 @@ declare const __VLS_base: import("vue").DefineComponent<{
235
250
  }, {
236
251
  focus: () => void | undefined;
237
252
  browse: typeof browse;
253
+ browseDirectory: typeof browseDirectory;
238
254
  addFiles: typeof addFiles;
239
255
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
240
256
  remove: (file: File) => any;
241
257
  invalid: (event: FileInvalidEvent) => any;
242
- add: (files: File[]) => any;
258
+ add: (files: File[], detail: FileAddDetail) => any;
243
259
  "update:modelValue": (value: File | File[] | null | undefined) => any;
260
+ reject: (events: FileInvalidEvent[], detail: FileAddDetail) => any;
244
261
  }, string, import("vue").PublicProps, Readonly<{
245
262
  name?: string | undefined;
246
263
  accept?: string | string[];
264
+ acceptMode?: FileInputAcceptMode;
247
265
  maxSize?: number;
248
266
  maxFiles?: number;
249
267
  multiple?: boolean;
268
+ directory?: FileInputDirectory;
269
+ directoryAction?: boolean;
270
+ directoryActionLabel?: string;
271
+ skipHidden?: boolean;
272
+ list?: boolean;
273
+ loading?: boolean;
274
+ loadingText?: string;
250
275
  capture?: "user" | "environment";
251
276
  previews?: boolean;
252
277
  size?: FieldSize;
@@ -271,8 +296,6 @@ declare const __VLS_base: import("vue").DefineComponent<{
271
296
  form?: string | undefined | undefined;
272
297
  results?: (string | number) | undefined;
273
298
  placeholder?: string | undefined | undefined;
274
- type?: "submit" | "reset" | "button" | undefined | undefined;
275
- ref?: import("vue").VNodeRef | undefined;
276
299
  role?: string | undefined | undefined;
277
300
  'aria-label'?: string | undefined | undefined;
278
301
  'aria-hidden'?: (boolean | "true" | "false") | undefined;
@@ -373,6 +396,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
373
396
  color?: string | undefined | undefined;
374
397
  id?: string | undefined | undefined;
375
398
  lang?: string | undefined | undefined;
399
+ type?: "submit" | "reset" | "button" | undefined | undefined;
376
400
  tabindex?: (string | number) | undefined;
377
401
  'aria-activedescendant'?: string | undefined | undefined;
378
402
  'aria-atomic'?: (boolean | "true" | "false") | undefined;
@@ -383,7 +407,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
383
407
  'aria-colindex'?: (string | number) | undefined;
384
408
  'aria-colspan'?: (string | number) | undefined;
385
409
  'aria-controls'?: string | undefined | undefined;
386
- 'aria-current'?: "page" | "location" | "time" | (boolean | "true" | "false") | "step" | "date" | undefined;
410
+ 'aria-current'?: "page" | "time" | (boolean | "true" | "false") | "step" | "location" | "date" | undefined;
387
411
  'aria-describedby'?: string | undefined | undefined;
388
412
  'aria-details'?: string | undefined | undefined;
389
413
  'aria-disabled'?: (boolean | "true" | "false") | undefined;
@@ -420,6 +444,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
420
444
  'aria-valuemin'?: (string | number) | undefined;
421
445
  'aria-valuenow'?: (string | number) | undefined;
422
446
  'aria-valuetext'?: string | undefined | undefined;
447
+ ref?: import("vue").VNodeRef | undefined;
423
448
  ref_for?: boolean | undefined | undefined;
424
449
  ref_key?: string | undefined | undefined;
425
450
  accesskey?: string | undefined | undefined;
@@ -467,12 +492,15 @@ declare const __VLS_base: import("vue").DefineComponent<{
467
492
  }> & Readonly<{
468
493
  onRemove?: ((file: File) => any) | undefined;
469
494
  onInvalid?: ((event: FileInvalidEvent) => any) | undefined;
470
- onAdd?: ((files: File[]) => any) | undefined;
495
+ onAdd?: ((files: File[], detail: FileAddDetail) => any) | undefined;
471
496
  "onUpdate:modelValue"?: ((value: File | File[] | null | undefined) => any) | undefined;
497
+ onReject?: ((events: FileInvalidEvent[], detail: FileAddDetail) => any) | undefined;
472
498
  }>, {
473
499
  icon: IconName;
474
500
  invalid: boolean;
501
+ list: boolean;
475
502
  size: FieldSize;
503
+ acceptMode: FileInputAcceptMode;
476
504
  previews: boolean;
477
505
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
478
506
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
@@ -63,7 +63,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
63
63
  'aria-colindex'?: (string | number) | undefined;
64
64
  'aria-colspan'?: (string | number) | undefined;
65
65
  'aria-controls'?: string | undefined | undefined;
66
- 'aria-current'?: "page" | "location" | "time" | (boolean | "true" | "false") | "step" | "date" | undefined;
66
+ 'aria-current'?: "page" | "time" | (boolean | "true" | "false") | "step" | "location" | "date" | undefined;
67
67
  'aria-describedby'?: string | undefined | undefined;
68
68
  'aria-details'?: string | undefined | undefined;
69
69
  'aria-disabled'?: (boolean | "true" | "false") | undefined;
@@ -259,7 +259,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
259
259
  'aria-colindex'?: (string | number) | undefined;
260
260
  'aria-colspan'?: (string | number) | undefined;
261
261
  'aria-controls'?: string | undefined | undefined;
262
- 'aria-current'?: "page" | "location" | "time" | (boolean | "true" | "false") | "step" | "date" | undefined;
262
+ 'aria-current'?: "page" | "time" | (boolean | "true" | "false") | "step" | "location" | "date" | undefined;
263
263
  'aria-describedby'?: string | undefined | undefined;
264
264
  'aria-details'?: string | undefined | undefined;
265
265
  'aria-disabled'?: (boolean | "true" | "false") | undefined;
@@ -61,7 +61,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
61
61
  'aria-colindex'?: (string | number) | undefined;
62
62
  'aria-colspan'?: (string | number) | undefined;
63
63
  'aria-controls'?: string | undefined | undefined;
64
- 'aria-current'?: "page" | "location" | "time" | (boolean | "true" | "false") | "step" | "date" | undefined;
64
+ 'aria-current'?: "page" | "time" | (boolean | "true" | "false") | "step" | "location" | "date" | undefined;
65
65
  'aria-describedby'?: string | undefined | undefined;
66
66
  'aria-details'?: string | undefined | undefined;
67
67
  'aria-disabled'?: (boolean | "true" | "false") | undefined;
@@ -256,7 +256,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
256
256
  'aria-colindex'?: (string | number) | undefined;
257
257
  'aria-colspan'?: (string | number) | undefined;
258
258
  'aria-controls'?: string | undefined | undefined;
259
- 'aria-current'?: "page" | "location" | "time" | (boolean | "true" | "false") | "step" | "date" | undefined;
259
+ 'aria-current'?: "page" | "time" | (boolean | "true" | "false") | "step" | "location" | "date" | undefined;
260
260
  'aria-describedby'?: string | undefined | undefined;
261
261
  'aria-details'?: string | undefined | undefined;
262
262
  'aria-disabled'?: (boolean | "true" | "false") | undefined;
@@ -31,8 +31,6 @@ declare const __VLS_export: import("vue").DefineComponent<{
31
31
  list?: string | undefined | undefined;
32
32
  results?: (string | number) | undefined;
33
33
  name?: string | undefined | undefined;
34
- type?: import("vue").InputTypeHTMLAttribute | undefined;
35
- ref?: import("vue").VNodeRef | undefined;
36
34
  role?: string | undefined | undefined;
37
35
  'aria-label'?: string | undefined | undefined;
38
36
  'aria-hidden'?: (boolean | "true" | "false") | undefined;
@@ -135,6 +133,7 @@ declare const __VLS_export: import("vue").DefineComponent<{
135
133
  height?: (string | number) | undefined;
136
134
  id?: string | undefined | undefined;
137
135
  lang?: string | undefined | undefined;
136
+ type?: import("vue").InputTypeHTMLAttribute | undefined;
138
137
  width?: (string | number) | undefined;
139
138
  tabindex?: (string | number) | undefined;
140
139
  'aria-activedescendant'?: string | undefined | undefined;
@@ -146,7 +145,7 @@ declare const __VLS_export: import("vue").DefineComponent<{
146
145
  'aria-colindex'?: (string | number) | undefined;
147
146
  'aria-colspan'?: (string | number) | undefined;
148
147
  'aria-controls'?: string | undefined | undefined;
149
- 'aria-current'?: "page" | "location" | "time" | (boolean | "true" | "false") | "step" | "date" | undefined;
148
+ 'aria-current'?: "page" | "time" | (boolean | "true" | "false") | "step" | "location" | "date" | undefined;
150
149
  'aria-describedby'?: string | undefined | undefined;
151
150
  'aria-details'?: string | undefined | undefined;
152
151
  'aria-disabled'?: (boolean | "true" | "false") | undefined;
@@ -183,6 +182,7 @@ declare const __VLS_export: import("vue").DefineComponent<{
183
182
  'aria-valuemin'?: (string | number) | undefined;
184
183
  'aria-valuenow'?: (string | number) | undefined;
185
184
  'aria-valuetext'?: string | undefined | undefined;
185
+ ref?: import("vue").VNodeRef | undefined;
186
186
  ref_for?: boolean | undefined | undefined;
187
187
  ref_key?: string | undefined | undefined;
188
188
  accesskey?: string | undefined | undefined;
@@ -275,8 +275,6 @@ declare const __VLS_export: import("vue").DefineComponent<{
275
275
  list?: string | undefined | undefined;
276
276
  results?: (string | number) | undefined;
277
277
  name?: string | undefined | undefined;
278
- type?: import("vue").InputTypeHTMLAttribute | undefined;
279
- ref?: import("vue").VNodeRef | undefined;
280
278
  role?: string | undefined | undefined;
281
279
  'aria-label'?: string | undefined | undefined;
282
280
  'aria-hidden'?: (boolean | "true" | "false") | undefined;
@@ -379,6 +377,7 @@ declare const __VLS_export: import("vue").DefineComponent<{
379
377
  height?: (string | number) | undefined;
380
378
  id?: string | undefined | undefined;
381
379
  lang?: string | undefined | undefined;
380
+ type?: import("vue").InputTypeHTMLAttribute | undefined;
382
381
  width?: (string | number) | undefined;
383
382
  tabindex?: (string | number) | undefined;
384
383
  'aria-activedescendant'?: string | undefined | undefined;
@@ -390,7 +389,7 @@ declare const __VLS_export: import("vue").DefineComponent<{
390
389
  'aria-colindex'?: (string | number) | undefined;
391
390
  'aria-colspan'?: (string | number) | undefined;
392
391
  'aria-controls'?: string | undefined | undefined;
393
- 'aria-current'?: "page" | "location" | "time" | (boolean | "true" | "false") | "step" | "date" | undefined;
392
+ 'aria-current'?: "page" | "time" | (boolean | "true" | "false") | "step" | "location" | "date" | undefined;
394
393
  'aria-describedby'?: string | undefined | undefined;
395
394
  'aria-details'?: string | undefined | undefined;
396
395
  'aria-disabled'?: (boolean | "true" | "false") | undefined;
@@ -427,6 +426,7 @@ declare const __VLS_export: import("vue").DefineComponent<{
427
426
  'aria-valuemin'?: (string | number) | undefined;
428
427
  'aria-valuenow'?: (string | number) | undefined;
429
428
  'aria-valuetext'?: string | undefined | undefined;
429
+ ref?: import("vue").VNodeRef | undefined;
430
430
  ref_for?: boolean | undefined | undefined;
431
431
  ref_key?: string | undefined | undefined;
432
432
  accesskey?: string | undefined | undefined;