@xleddyl/443-components-vue 0.3.0 → 0.5.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 (109) hide show
  1. package/README.md +52 -25
  2. package/dist/components/core/FieldBoundary.vue +14 -0
  3. package/dist/components/core/FieldBoundary.vue.d.ts +15 -0
  4. package/dist/components/core/Form.vue +91 -12
  5. package/dist/components/core/Form.vue.d.ts +27 -2
  6. package/dist/components/core/FormAlerts.vue +30 -0
  7. package/dist/{internal/forms → components/core}/FormAlerts.vue.d.ts +4 -4
  8. package/dist/components/core/FormField.vue +18 -3
  9. package/dist/components/core/index.d.ts +2 -0
  10. package/dist/components/core/index.mjs +2 -0
  11. package/dist/components/data/FilterPills.vue +82 -37
  12. package/dist/components/data/FilterPills.vue.d.ts +46 -427
  13. package/dist/components/data/SelectionBar.vue +7 -4
  14. package/dist/components/data/Tree.vue +9 -4
  15. package/dist/components/data/Tree.vue.d.ts +4 -1
  16. package/dist/components/data/types.d.ts +7 -2
  17. package/dist/components/forms/CheckboxGroup.vue +2 -1
  18. package/dist/components/forms/FileInput.vue +98 -26
  19. package/dist/components/forms/FileInput.vue.d.ts +28 -10
  20. package/dist/components/forms/RadioGroup.vue +2 -1
  21. package/dist/components/forms/SegmentedControl.vue +2 -1
  22. package/dist/components/forms/types.d.ts +3 -0
  23. package/dist/components/layout/Accordion.vue.d.ts +4 -4
  24. package/dist/components/layout/AppShell.vue +1 -1
  25. package/dist/components/layout/AppShell.vue.d.ts +2 -2
  26. package/dist/components/layout/Tabs.vue +10 -1
  27. package/dist/components/layout/Tabs.vue.d.ts +4 -0
  28. package/dist/components/layout/types.d.ts +1 -0
  29. package/dist/components/overlays/CommandPalette.vue +10 -4
  30. package/dist/components/overlays/CommandPalette.vue.d.ts +273 -511
  31. package/dist/components/overlays/HoverCard.vue +1 -1
  32. package/dist/components/overlays/HoverCard.vue.d.ts +2 -2
  33. package/dist/components/overlays/Popover.vue +1 -1
  34. package/dist/components/overlays/Popover.vue.d.ts +2 -2
  35. package/dist/components/overlays/Toaster.vue +35 -4
  36. package/dist/components/overlays/Toaster.vue.d.ts +3 -0
  37. package/dist/components/overlays/types.d.ts +8 -4
  38. package/dist/components/templates/DashboardLayout.vue +21 -18
  39. package/dist/components/templates/DashboardLayout.vue.d.ts +332 -565
  40. package/dist/components/templates/ListPage.vue +1 -1
  41. package/dist/components/templates/ListPage.vue.d.ts +3 -3
  42. package/dist/components/templates/types.d.ts +4 -4
  43. package/dist/composables/useField.d.ts +6 -1
  44. package/dist/composables/useField.mjs +40 -12
  45. package/dist/composables/useFieldBoundary.d.ts +1 -0
  46. package/dist/composables/useFieldBoundary.mjs +5 -1
  47. package/dist/composables/useFileDrop.d.ts +14 -1
  48. package/dist/composables/useFileDrop.mjs +21 -20
  49. package/dist/composables/useForm.d.ts +4 -0
  50. package/dist/composables/useForm.mjs +62 -22
  51. package/dist/editor/RichTextEditor.vue +2 -1
  52. package/dist/internal/core/ButtonElement.d.ts +2 -0
  53. package/dist/internal/core/ButtonElement.mjs +8 -0
  54. package/dist/internal/forms/files.d.ts +7 -0
  55. package/dist/internal/forms/files.mjs +24 -0
  56. package/dist/internal/keys.d.ts +2 -1
  57. package/dist/internal/keys.mjs +1 -0
  58. package/dist/internal/layout/route.mjs +1 -1
  59. package/dist/locale/en.mjs +4 -1
  60. package/dist/locale/it.mjs +4 -1
  61. package/dist/locale/types.d.ts +3 -0
  62. package/dist/nuxt/module.mjs +1 -1
  63. package/dist/styles/core/button.css +1 -13
  64. package/dist/styles/core/form-field.css +2 -2
  65. package/dist/styles/data/avatar.css +1 -1
  66. package/dist/styles/data/description-list.css +5 -3
  67. package/dist/styles/data/filter-pills.css +0 -1
  68. package/dist/styles/data/pagination.css +0 -1
  69. package/dist/styles/data/selection-bar.css +4 -0
  70. package/dist/styles/data/stat.css +1 -6
  71. package/dist/styles/data/table.css +1 -2
  72. package/dist/styles/data/tag.css +1 -4
  73. package/dist/styles/data/timeline.css +1 -0
  74. package/dist/styles/data/tree.css +8 -2
  75. package/dist/styles/feedback/alert.css +1 -1
  76. package/dist/styles/feedback/empty-state.css +6 -5
  77. package/dist/styles/forms/color-input.css +1 -1
  78. package/dist/styles/forms/combobox.css +0 -1
  79. package/dist/styles/forms/date-picker.css +1 -1
  80. package/dist/styles/forms/file-input.css +9 -1
  81. package/dist/styles/forms/number-input.css +1 -1
  82. package/dist/styles/forms/password-input.css +1 -5
  83. package/dist/styles/forms/switch.css +0 -1
  84. package/dist/styles/layout/accordion.css +7 -2
  85. package/dist/styles/layout/card.css +13 -7
  86. package/dist/styles/layout/copy-button.css +2 -2
  87. package/dist/styles/layout/nav-link.css +1 -2
  88. package/dist/styles/layout/page-header.css +2 -1
  89. package/dist/styles/layout/sidebar-nav.css +1 -1
  90. package/dist/styles/layout/sidebar.css +1 -1
  91. package/dist/styles/layout/tabs.css +22 -2
  92. package/dist/styles/layout/theme-switch.css +1 -3
  93. package/dist/styles/overlays/command.css +1 -1
  94. package/dist/styles/overlays/confirm.css +0 -1
  95. package/dist/styles/overlays/drawer.css +23 -2
  96. package/dist/styles/overlays/menu.css +2 -4
  97. package/dist/styles/overlays/modal.css +4 -3
  98. package/dist/styles/overlays/toast.css +0 -1
  99. package/dist/styles/templates/auth-layout.css +4 -1
  100. package/dist/styles/templates/empty-page.css +7 -3
  101. package/dist/styles/templates/error-page.css +4 -0
  102. package/dist/styles/templates/form-page.css +1 -1
  103. package/dist/styles/templates/page.css +1 -1
  104. package/dist/styles/templates/settings-modal.css +4 -2
  105. package/dist/styles/tokens.css +1 -1
  106. package/package.json +1 -1
  107. package/dist/internal/forms/FormAlerts.vue +0 -25
  108. package/dist/internal/overlays/FieldBoundary.d.ts +0 -4
  109. package/dist/internal/overlays/FieldBoundary.mjs +0 -9
@@ -37,7 +37,7 @@
37
37
  </template>
38
38
 
39
39
  <script setup>
40
- import { computed, ref, watch } from "vue";
40
+ import { computed, onMounted, ref, watch } from "vue";
41
41
  import { useLocale } from "../../composables/useLocale";
42
42
  import Button from "../core/Button.vue";
43
43
  const props = defineProps({
@@ -67,12 +67,15 @@ const countText = computed(() => {
67
67
  return t("common.selected", { count: props.count });
68
68
  });
69
69
  const announcement = ref("");
70
+ const mounted = ref(false);
70
71
  watch(
71
- () => [props.count, countText.value],
72
- ([count], previous) => {
73
- const before = previous?.[0] ?? 0;
72
+ () => [props.count, countText.value, mounted.value],
73
+ ([count, , isMounted], previous) => {
74
+ if (!isMounted) return;
75
+ const before = previous?.[2] ? previous[0] : 0;
74
76
  if (count > 0) announcement.value = countText.value;
75
77
  else if (before > 0) announcement.value = props.clearedText ?? t("selectionBar.cleared");
76
78
  }
77
79
  );
80
+ onMounted(() => mounted.value = true);
78
81
  </script>
@@ -79,9 +79,12 @@
79
79
  <span class="k-tree-text">
80
80
  <slot name="label" :node="entry.node" :level="entry.level">
81
81
  <span class="k-tree-label">{{ entry.node.label }}</span>
82
- <span v-if="entry.node.description" class="k-tree-description">{{
83
- entry.node.description
84
- }}</span>
82
+ <span
83
+ v-if="entry.node.description"
84
+ class="k-tree-description"
85
+ :class="`k-tree-description-${entry.node.descriptionVariant ?? descriptionVariant}`"
86
+ >{{ entry.node.description }}</span
87
+ >
85
88
  </slot>
86
89
  </span>
87
90
  <span v-if="entry.node.meta || $slots.meta" class="k-tree-meta">
@@ -117,6 +120,7 @@ const props = defineProps({
117
120
  size: { type: String, required: false, default: "md" },
118
121
  checkmark: { type: Boolean, required: false },
119
122
  emptyText: { type: String, required: false },
123
+ descriptionVariant: { type: String, required: false, default: "mono" },
120
124
  reorderable: { type: Boolean, required: false },
121
125
  nestable: { type: Boolean, required: false },
122
126
  canDrop: { type: Function, required: false },
@@ -132,7 +136,8 @@ const selected = defineModel("selected", { type: [String, Number, null], ...{ de
132
136
  const control = useFieldControl({
133
137
  invalid: () => props.invalid,
134
138
  disabled: () => props.disabled,
135
- required: () => props.required
139
+ required: () => props.required,
140
+ labelable: false
136
141
  });
137
142
  const baseId = useId();
138
143
  const hintId = `${baseId}-hint`;
@@ -1,4 +1,4 @@
1
- import type { TreeMove, TreeNode, TreeSize } from './types';
1
+ import type { TreeDescriptionVariant, TreeMove, TreeNode, TreeSize } from './types';
2
2
  type NodeId = TreeNode['id'];
3
3
  declare var __VLS_12: {
4
4
  node: TreeNode;
@@ -21,6 +21,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
21
21
  size?: TreeSize;
22
22
  checkmark?: boolean;
23
23
  emptyText?: string;
24
+ descriptionVariant?: TreeDescriptionVariant;
24
25
  reorderable?: boolean;
25
26
  nestable?: boolean;
26
27
  canDrop?: (move: TreeMove) => boolean;
@@ -234,6 +235,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
234
235
  size?: TreeSize;
235
236
  checkmark?: boolean;
236
237
  emptyText?: string;
238
+ descriptionVariant?: TreeDescriptionVariant;
237
239
  reorderable?: boolean;
238
240
  nestable?: boolean;
239
241
  canDrop?: (move: TreeMove) => boolean;
@@ -444,6 +446,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
444
446
  size: TreeSize;
445
447
  selectable: boolean;
446
448
  bordered: boolean;
449
+ descriptionVariant: TreeDescriptionVariant;
447
450
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
448
451
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
449
452
  declare const _default: typeof __VLS_export;
@@ -57,11 +57,13 @@ export interface TreeNode {
57
57
  id: string | number;
58
58
  label: string;
59
59
  description?: string;
60
+ descriptionVariant?: TreeDescriptionVariant;
60
61
  meta?: string;
61
62
  icon?: string;
62
63
  disabled?: boolean;
63
64
  children?: TreeNode[];
64
65
  }
66
+ export type TreeDescriptionVariant = 'text' | 'mono';
65
67
  export type TreeSize = 'sm' | 'md';
66
68
  export type TreeDropPosition = 'before' | 'after' | 'inside';
67
69
  export interface TreeLocation {
@@ -75,8 +77,9 @@ export interface TreeMove {
75
77
  from: TreeLocation;
76
78
  to: TreeLocation;
77
79
  }
78
- export interface FilterPillOption {
79
- value: string | number;
80
+ export type FilterPillValue = string | number;
81
+ export interface FilterPillOption<T extends FilterPillValue = FilterPillValue> {
82
+ value: T;
80
83
  label: string;
81
84
  valueLabel?: string;
82
85
  count?: number;
@@ -84,6 +87,8 @@ export interface FilterPillOption {
84
87
  disabled?: boolean;
85
88
  menu?: MenuItems;
86
89
  menuLabel?: string;
90
+ panel?: boolean;
91
+ panelLabel?: string;
87
92
  active?: boolean;
88
93
  }
89
94
  export type SelectionBarPlacement = 'inline' | 'sticky' | 'floating';
@@ -52,7 +52,8 @@ const model = defineModel({ type: Array, ...{ default: () => [] } });
52
52
  const control = useFieldControl({
53
53
  invalid: () => props.invalid,
54
54
  disabled: () => props.disabled,
55
- required: () => props.required
55
+ required: () => props.required,
56
+ labelable: false
56
57
  });
57
58
  provide(CHECKBOX_GROUP_KEY, {
58
59
  name: computed(() => props.name ?? control.field?.name.value),
@@ -29,7 +29,7 @@
29
29
  joinIds(
30
30
  $attrs['aria-describedby'],
31
31
  control.describedBy.value,
32
- hint ? hintId : void 0
32
+ hasHint ? hintId : void 0
33
33
  )
34
34
  "
35
35
  type="button"
@@ -44,7 +44,9 @@
44
44
  <span class="k-file-input-dropzone-title">
45
45
  <slot name="title" :over="over" :loading="loading">{{ currentTitle }}</slot>
46
46
  </span>
47
- <span v-if="hint" :id="hintId" class="k-file-input-dropzone-hint">{{ hint }}</span>
47
+ <span v-if="hasHint" :id="hintId" class="k-file-input-dropzone-hint">
48
+ <slot name="hint">{{ hint }}</slot>
49
+ </span>
48
50
  </span>
49
51
  </button>
50
52
  <input
@@ -60,6 +62,34 @@
60
62
  :disabled="control.disabled.value || void 0"
61
63
  @change="onNativeChange"
62
64
  />
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>
63
93
  <ul v-if="list && files.length" role="list" class="k-file-input-list" :aria-label="listText">
64
94
  <li v-for="file in files" :key="fileKey(file)" class="k-file-input-item">
65
95
  <span class="k-file-input-thumb" aria-hidden="true">
@@ -68,7 +98,7 @@
68
98
  </span>
69
99
  <span class="k-file-input-meta">
70
100
  <span class="k-file-input-name" :title="filePath(file)">{{
71
- directory ? filePath(file) : file.name
101
+ walksFolders ? filePath(file) : file.name
72
102
  }}</span>
73
103
  <span class="k-file-input-size">{{ formatBytes(file.size, resolvedLocale) }}</span>
74
104
  </span>
@@ -78,7 +108,7 @@
78
108
  variant="ghost"
79
109
  tone="neutral"
80
110
  icon="x-mark"
81
- :aria-label="`${removeText} ${directory ? filePath(file) : file.name}`"
111
+ :aria-label="`${removeText} ${walksFolders ? filePath(file) : file.name}`"
82
112
  :title="removeText"
83
113
  @click="removeFile(file)"
84
114
  />
@@ -106,6 +136,7 @@ import { readDroppedFiles, useFileDrop } from "../../composables/useFileDrop";
106
136
  import {
107
137
  fileKey,
108
138
  filePath,
139
+ filesTree,
109
140
  formatBytes,
110
141
  isImageFile,
111
142
  matchesAccept,
@@ -119,10 +150,14 @@ defineOptions({ inheritAttrs: false });
119
150
  const props = defineProps({
120
151
  name: { type: String, required: false },
121
152
  accept: { type: [String, Array], required: false },
153
+ acceptMode: { type: String, required: false, default: "filter" },
122
154
  maxSize: { type: Number, required: false },
123
155
  maxFiles: { type: Number, required: false },
124
156
  multiple: { type: Boolean, required: false },
125
- directory: { 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 },
126
161
  list: { type: Boolean, required: false, default: true },
127
162
  loading: { type: Boolean, required: false },
128
163
  loadingText: { type: String, required: false },
@@ -144,10 +179,16 @@ const props = defineProps({
144
179
  class: { type: null, required: false },
145
180
  style: { type: null, required: false }
146
181
  });
182
+ const slots = defineSlots();
147
183
  const { locale: appLocale, t } = useLocale();
148
184
  const resolvedLocale = computed(() => props.locale ?? appLocale.value.code);
149
- const titleLabel = computed(
150
- () => props.title ?? t(props.directory ? "fileInput.directoryTitle" : "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")
151
192
  );
152
193
  const loadingLabel = computed(() => props.loadingText ?? t("fileInput.loading"));
153
194
  const dropLabel = computed(() => props.dropText ?? t("fileInput.drop"));
@@ -160,10 +201,11 @@ function announceAdded(count) {
160
201
  return props.announce ? props.announce(count) : t("fileInput.added", { count });
161
202
  }
162
203
  const model = defineModel({ type: null });
163
- const emit = defineEmits(["invalid", "add", "remove"]);
204
+ const emit = defineEmits(["invalid", "reject", "add", "remove"]);
164
205
  const hintId = `${useId()}-hint`;
165
206
  const rootRef = useTemplateRef("rootRef");
166
207
  const inputRef = useTemplateRef("inputRef");
208
+ const directoryInputRef = useTemplateRef("directoryInputRef");
167
209
  const buttonRef = useTemplateRef("buttonRef");
168
210
  const previews = reactive({});
169
211
  const announcement = ref("");
@@ -182,7 +224,9 @@ const acceptList = computed(() => parseAccept(props.accept));
182
224
  const acceptAttr = computed(() => acceptList.value.join(",") || void 0);
183
225
  const formName = computed(() => props.name ?? control.field?.name.value);
184
226
  const interactive = computed(() => !control.disabled.value && !props.readonly && !props.loading);
185
- const isMultiple = computed(() => props.multiple || props.directory);
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));
186
230
  const currentTitle = computed(() => {
187
231
  if (props.loading) return loadingLabel.value;
188
232
  return over.value ? dropLabel.value : titleLabel.value;
@@ -194,12 +238,14 @@ const { over } = useFileDrop(rootRef, {
194
238
  disabled: () => !interactive.value,
195
239
  onDrop: (dropped, event) => {
196
240
  const transfer = event.dataTransfer;
197
- if (!props.directory) {
198
- addEntries(toEntries(dropped), { source: "drop", event, dataTransfer: transfer });
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: [] });
199
245
  return;
200
246
  }
201
- void readDroppedFiles(transfer).then(
202
- (entries) => addEntries(entries, { source: "drop", event, dataTransfer: transfer })
247
+ void readDroppedFiles(transfer, { directories: true, skipHidden: props.skipHidden }).then(
248
+ (tree) => addEntries(tree.files, { ...origin, directories: tree.directories })
203
249
  );
204
250
  }
205
251
  });
@@ -207,22 +253,32 @@ function browse() {
207
253
  if (!interactive.value) return;
208
254
  inputRef.value?.click();
209
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
+ }
210
263
  function validate(file) {
211
- if (!matchesAccept(file, acceptList.value))
264
+ if (props.acceptMode === "filter" && !matchesAccept(file, acceptList.value))
212
265
  return { reason: "type", file, limit: acceptList.value.join(", ") };
213
266
  if (props.maxSize !== void 0 && file.size > props.maxSize)
214
267
  return { reason: "size", file, limit: props.maxSize };
215
268
  return void 0;
216
269
  }
217
270
  function addFiles(incoming) {
218
- addEntries(toEntries(incoming), {
271
+ const tree = filesTree(incoming, { skipHidden: props.skipHidden });
272
+ addEntries(tree.files, {
219
273
  source: "browse",
220
274
  event: new Event("add"),
221
- dataTransfer: null
275
+ dataTransfer: null,
276
+ directories: walksFolders.value ? tree.directories : []
222
277
  });
223
278
  }
224
279
  function addEntries(incomingEntries, origin) {
225
- if (!interactive.value || !incomingEntries.length) return;
280
+ if (!interactive.value) return;
281
+ if (!incomingEntries.length && !origin.directories.length) return;
226
282
  for (const entry of incomingEntries) setFilePath(entry.file, entry.path);
227
283
  const incoming = incomingEntries.map((entry) => entry.file);
228
284
  const multiple = isMultiple.value;
@@ -231,37 +287,53 @@ function addEntries(incomingEntries, origin) {
231
287
  const existing = new Set(kept.map(fileKey));
232
288
  const limit = multiple ? props.maxFiles ?? Infinity : 1;
233
289
  const room = multiple ? limit - kept.length : 1;
290
+ const rejected = [];
291
+ const reject = (problem) => {
292
+ rejected.push(problem);
293
+ emit("invalid", problem);
294
+ };
234
295
  for (const file of multiple ? incoming : incoming.slice(0, 1)) {
235
296
  const problem = validate(file);
236
297
  if (problem) {
237
- emit("invalid", problem);
298
+ reject(problem);
238
299
  continue;
239
300
  }
240
301
  if (existing.has(fileKey(file))) continue;
241
302
  if (accepted.length >= room) {
242
- emit("invalid", { reason: "count", file, limit });
303
+ reject({ reason: "count", file, limit });
243
304
  continue;
244
305
  }
245
306
  accepted.push(file);
246
307
  existing.add(fileKey(file));
247
308
  }
248
309
  if (!multiple && incoming.length > 1)
249
- for (const file of incoming.slice(1)) emit("invalid", { reason: "count", file, limit: 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);
250
317
  if (!accepted.length) return;
251
- if (props.list) model.value = multiple ? [...files.value, ...accepted] : accepted[0];
252
- emit("add", accepted, { ...origin, entries: toEntries(accepted) });
253
318
  announcement.value = props.list ? announceCount(multiple ? files.value.length : 1) : announceAdded(accepted.length);
254
319
  }
255
320
  function onNativeChange(event) {
256
321
  const target = event.target;
257
- const picked = Array.from(target.files ?? []);
258
- addEntries(toEntries(picked), { source: "browse", event, dataTransfer: null });
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
+ });
259
330
  target.value = "";
331
+ if (target === inputRef.value) syncDirectory();
260
332
  }
261
333
  function syncDirectory() {
262
334
  const input = inputRef.value;
263
335
  if (!input) return;
264
- input.webkitdirectory = props.directory;
336
+ input.webkitdirectory = props.directory === true;
265
337
  }
266
338
  function removeFile(file) {
267
339
  const key = fileKey(file);
@@ -308,5 +380,5 @@ onMounted(() => {
308
380
  onBeforeUnmount(() => {
309
381
  for (const url of Object.values(previews)) URL.revokeObjectURL(url);
310
382
  });
311
- defineExpose({ focus: () => buttonRef.value?.focus(), browse, addFiles });
383
+ defineExpose({ focus: () => buttonRef.value?.focus(), browse, browseDirectory, addFiles });
312
384
  </script>
@@ -1,22 +1,32 @@
1
1
  import type { IconName } from '../../composables/useIcons';
2
2
  import type { FieldSize } from '../core/types';
3
- import type { FileAddDetail, 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_11: {
7
- over: boolean;
8
- loading: boolean;
9
- };
10
- type __VLS_Slots = {} & {
11
- title?: (props: typeof __VLS_11) => any;
12
- };
13
19
  declare const __VLS_base: import("vue").DefineComponent<{
14
20
  name?: string | undefined;
15
21
  accept?: string | string[];
22
+ acceptMode?: FileInputAcceptMode;
16
23
  maxSize?: number;
17
24
  maxFiles?: number;
18
25
  multiple?: boolean;
19
- directory?: boolean;
26
+ directory?: FileInputDirectory;
27
+ directoryAction?: boolean;
28
+ directoryActionLabel?: string;
29
+ skipHidden?: boolean;
20
30
  list?: boolean;
21
31
  loading?: boolean;
22
32
  loadingText?: string;
@@ -240,19 +250,25 @@ declare const __VLS_base: import("vue").DefineComponent<{
240
250
  }, {
241
251
  focus: () => void | undefined;
242
252
  browse: typeof browse;
253
+ browseDirectory: typeof browseDirectory;
243
254
  addFiles: typeof addFiles;
244
255
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
245
256
  remove: (file: File) => any;
246
257
  invalid: (event: FileInvalidEvent) => any;
247
258
  add: (files: File[], detail: FileAddDetail) => any;
248
259
  "update:modelValue": (value: File | File[] | null | undefined) => any;
260
+ reject: (events: FileInvalidEvent[], detail: FileAddDetail) => any;
249
261
  }, string, import("vue").PublicProps, Readonly<{
250
262
  name?: string | undefined;
251
263
  accept?: string | string[];
264
+ acceptMode?: FileInputAcceptMode;
252
265
  maxSize?: number;
253
266
  maxFiles?: number;
254
267
  multiple?: boolean;
255
- directory?: boolean;
268
+ directory?: FileInputDirectory;
269
+ directoryAction?: boolean;
270
+ directoryActionLabel?: string;
271
+ skipHidden?: boolean;
256
272
  list?: boolean;
257
273
  loading?: boolean;
258
274
  loadingText?: string;
@@ -478,11 +494,13 @@ declare const __VLS_base: import("vue").DefineComponent<{
478
494
  onInvalid?: ((event: FileInvalidEvent) => any) | undefined;
479
495
  onAdd?: ((files: File[], detail: FileAddDetail) => any) | undefined;
480
496
  "onUpdate:modelValue"?: ((value: File | File[] | null | undefined) => any) | undefined;
497
+ onReject?: ((events: FileInvalidEvent[], detail: FileAddDetail) => any) | undefined;
481
498
  }>, {
482
499
  icon: IconName;
483
500
  invalid: boolean;
484
501
  list: boolean;
485
502
  size: FieldSize;
503
+ acceptMode: FileInputAcceptMode;
486
504
  previews: boolean;
487
505
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
488
506
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
@@ -60,7 +60,8 @@ const fallbackName = useId();
60
60
  const control = useFieldControl({
61
61
  invalid: () => props.invalid,
62
62
  disabled: () => props.disabled,
63
- required: () => props.required
63
+ required: () => props.required,
64
+ labelable: false
64
65
  });
65
66
  provide(RADIO_GROUP_KEY, {
66
67
  name: computed(() => props.name ?? control.field?.name.value ?? fallbackName),
@@ -92,7 +92,8 @@ let observer;
92
92
  const control = useFieldControl({
93
93
  invalid: () => props.invalid,
94
94
  disabled: () => props.disabled,
95
- required: () => props.required
95
+ required: () => props.required,
96
+ labelable: false
96
97
  });
97
98
  function titleId(index) {
98
99
  return `${baseId}-title-${index}`;
@@ -49,10 +49,13 @@ export interface FileInputEntry {
49
49
  }
50
50
  export interface FileAddDetail {
51
51
  entries: FileInputEntry[];
52
+ directories: string[];
52
53
  source: 'browse' | 'drop';
53
54
  event: Event;
54
55
  dataTransfer: DataTransfer | null;
55
56
  }
57
+ export type FileInputDirectory = boolean | 'drop';
58
+ export type FileInputAcceptMode = 'filter' | 'hint';
56
59
  export type PinInputMode = 'numeric' | 'alphanumeric' | 'alphabetic';
57
60
  export type RadioGroupVariant = 'default' | 'card';
58
61
  export type SliderBubble = boolean | 'always';
@@ -1,4 +1,4 @@
1
- import type { AccordionContentVariant, AccordionItem, HeadingLevel } from './types';
1
+ import type { AccordionContentVariant, AccordionItem, AccordionVariant, HeadingLevel } from './types';
2
2
  type Value = string | number;
3
3
  declare var __VLS_13: {
4
4
  item: AccordionItem;
@@ -18,7 +18,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
18
18
  multiple?: boolean;
19
19
  collapsible?: boolean;
20
20
  level?: HeadingLevel;
21
- variant?: "separated" | "plain" | "card";
21
+ variant?: AccordionVariant;
22
22
  contentVariant?: AccordionContentVariant;
23
23
  modelValue?: Value | Value[] | null;
24
24
  key?: PropertyKey | undefined;
@@ -220,7 +220,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
220
220
  multiple?: boolean;
221
221
  collapsible?: boolean;
222
222
  level?: HeadingLevel;
223
- variant?: "separated" | "plain" | "card";
223
+ variant?: AccordionVariant;
224
224
  contentVariant?: AccordionContentVariant;
225
225
  modelValue?: Value | Value[] | null;
226
226
  key?: PropertyKey | undefined;
@@ -418,7 +418,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
418
418
  }> & Readonly<{
419
419
  "onUpdate:modelValue"?: ((value: Value | Value[] | null) => any) | undefined;
420
420
  }>, {
421
- variant: "plain" | "card" | "separated";
421
+ variant: AccordionVariant;
422
422
  level: HeadingLevel;
423
423
  collapsible: boolean;
424
424
  contentVariant: AccordionContentVariant;
@@ -74,7 +74,7 @@ const props = defineProps({
74
74
  skipLabel: { type: String, required: false },
75
75
  fluid: { type: Boolean, required: false },
76
76
  topbar: { type: Boolean, required: false, default: void 0 },
77
- storageKey: { type: String, required: false },
77
+ storageKey: { type: [String, Boolean], required: false },
78
78
  shortcut: { type: [String, Boolean], required: false, default: false },
79
79
  viewTransition: { type: Boolean, required: false }
80
80
  });
@@ -32,7 +32,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
32
32
  skipLabel?: string;
33
33
  fluid?: boolean;
34
34
  topbar?: boolean;
35
- storageKey?: string;
35
+ storageKey?: string | false;
36
36
  shortcut?: string | false;
37
37
  viewTransition?: boolean;
38
38
  collapsed?: boolean;
@@ -244,7 +244,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
244
244
  skipLabel?: string;
245
245
  fluid?: boolean;
246
246
  topbar?: boolean;
247
- storageKey?: string;
247
+ storageKey?: string | false;
248
248
  shortcut?: string | false;
249
249
  viewTransition?: boolean;
250
250
  collapsed?: boolean;
@@ -1,5 +1,12 @@
1
1
  <template>
2
- <div class="k-tabs" :class="[`k-tabs-${variant}`, `k-tabs-${size}`, { 'is-fill': fill }]">
2
+ <div
3
+ class="k-tabs"
4
+ :class="[
5
+ `k-tabs-${variant}`,
6
+ `k-tabs-${size}`,
7
+ { 'is-fill': fill, 'is-stretch': stretch, 'is-inset': inset }
8
+ ]"
9
+ >
3
10
  <div
4
11
  ref="list"
5
12
  class="k-tabs-list"
@@ -88,6 +95,8 @@ const props = defineProps({
88
95
  activation: { type: String, required: false, default: "automatic" },
89
96
  lazy: { type: Boolean, required: false },
90
97
  fill: { type: Boolean, required: false },
98
+ stretch: { type: Boolean, required: false },
99
+ inset: { type: Boolean, required: false },
91
100
  panelFocusable: { type: Boolean, required: false, default: true }
92
101
  });
93
102
  const slots = defineSlots();
@@ -19,6 +19,8 @@ declare const __VLS_base: import("vue").DefineComponent<{
19
19
  activation?: TabsActivation;
20
20
  lazy?: boolean;
21
21
  fill?: boolean;
22
+ stretch?: boolean;
23
+ inset?: boolean;
22
24
  panelFocusable?: boolean;
23
25
  modelValue?: TabItem["value"];
24
26
  key?: PropertyKey | undefined;
@@ -223,6 +225,8 @@ declare const __VLS_base: import("vue").DefineComponent<{
223
225
  activation?: TabsActivation;
224
226
  lazy?: boolean;
225
227
  fill?: boolean;
228
+ stretch?: boolean;
229
+ inset?: boolean;
226
230
  panelFocusable?: boolean;
227
231
  modelValue?: TabItem["value"];
228
232
  key?: PropertyKey | undefined;
@@ -39,6 +39,7 @@ export interface AccordionItem {
39
39
  icon?: IconName;
40
40
  disabled?: boolean;
41
41
  }
42
+ export type AccordionVariant = 'separated' | 'plain' | 'card' | 'outline';
42
43
  export type AccordionContentVariant = 'text' | 'neutral';
43
44
  export type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
44
45
  export interface AppShellContext {