@xleddyl/443-components-vue 0.3.0 → 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.
- package/README.md +52 -25
- package/dist/components/core/FieldBoundary.vue +14 -0
- package/dist/components/core/FieldBoundary.vue.d.ts +15 -0
- package/dist/components/core/Form.vue +91 -12
- package/dist/components/core/Form.vue.d.ts +27 -2
- package/dist/components/core/FormAlerts.vue +30 -0
- package/dist/{internal/forms → components/core}/FormAlerts.vue.d.ts +4 -4
- package/dist/components/core/FormField.vue +18 -3
- package/dist/components/core/index.d.ts +2 -0
- package/dist/components/core/index.mjs +2 -0
- package/dist/components/data/FilterPills.vue +82 -37
- package/dist/components/data/FilterPills.vue.d.ts +46 -427
- package/dist/components/data/SelectionBar.vue +7 -4
- package/dist/components/data/Tree.vue +9 -4
- package/dist/components/data/Tree.vue.d.ts +4 -1
- package/dist/components/data/types.d.ts +7 -2
- package/dist/components/forms/CheckboxGroup.vue +2 -1
- package/dist/components/forms/FileInput.vue +98 -26
- package/dist/components/forms/FileInput.vue.d.ts +28 -10
- package/dist/components/forms/RadioGroup.vue +2 -1
- package/dist/components/forms/SegmentedControl.vue +2 -1
- package/dist/components/forms/types.d.ts +3 -0
- package/dist/components/layout/Accordion.vue.d.ts +4 -4
- package/dist/components/layout/AppShell.vue +1 -1
- package/dist/components/layout/AppShell.vue.d.ts +2 -2
- package/dist/components/layout/Tabs.vue +10 -1
- package/dist/components/layout/Tabs.vue.d.ts +4 -0
- package/dist/components/layout/types.d.ts +1 -0
- package/dist/components/overlays/CommandPalette.vue +10 -4
- package/dist/components/overlays/CommandPalette.vue.d.ts +273 -511
- package/dist/components/overlays/HoverCard.vue +1 -1
- package/dist/components/overlays/HoverCard.vue.d.ts +2 -2
- package/dist/components/overlays/Popover.vue +1 -1
- package/dist/components/overlays/Popover.vue.d.ts +2 -2
- package/dist/components/overlays/types.d.ts +7 -4
- package/dist/components/templates/DashboardLayout.vue +21 -18
- package/dist/components/templates/DashboardLayout.vue.d.ts +332 -565
- package/dist/components/templates/ListPage.vue +1 -1
- package/dist/components/templates/ListPage.vue.d.ts +3 -3
- package/dist/components/templates/types.d.ts +4 -4
- package/dist/composables/useField.d.ts +6 -1
- package/dist/composables/useField.mjs +40 -12
- package/dist/composables/useFieldBoundary.d.ts +1 -0
- package/dist/composables/useFieldBoundary.mjs +5 -1
- package/dist/composables/useFileDrop.d.ts +14 -1
- package/dist/composables/useFileDrop.mjs +21 -20
- package/dist/composables/useForm.d.ts +4 -0
- package/dist/composables/useForm.mjs +62 -22
- package/dist/editor/RichTextEditor.vue +2 -1
- package/dist/internal/core/ButtonElement.d.ts +2 -0
- package/dist/internal/core/ButtonElement.mjs +8 -0
- package/dist/internal/forms/files.d.ts +7 -0
- package/dist/internal/forms/files.mjs +24 -0
- package/dist/internal/keys.d.ts +2 -1
- package/dist/internal/keys.mjs +1 -0
- package/dist/locale/en.mjs +2 -0
- package/dist/locale/it.mjs +2 -0
- package/dist/locale/types.d.ts +2 -0
- package/dist/styles/core/button.css +1 -13
- package/dist/styles/data/avatar.css +1 -1
- package/dist/styles/data/filter-pills.css +0 -1
- package/dist/styles/data/pagination.css +0 -1
- package/dist/styles/data/selection-bar.css +4 -0
- package/dist/styles/data/stat.css +1 -6
- package/dist/styles/data/table.css +1 -3
- package/dist/styles/data/tag.css +1 -4
- package/dist/styles/data/tree.css +8 -2
- package/dist/styles/feedback/alert.css +0 -1
- package/dist/styles/feedback/empty-state.css +0 -1
- package/dist/styles/forms/color-input.css +1 -1
- package/dist/styles/forms/combobox.css +0 -1
- package/dist/styles/forms/date-picker.css +1 -1
- package/dist/styles/forms/file-input.css +9 -1
- package/dist/styles/forms/number-input.css +1 -1
- package/dist/styles/forms/password-input.css +1 -5
- package/dist/styles/forms/switch.css +0 -1
- package/dist/styles/layout/accordion.css +6 -1
- package/dist/styles/layout/card.css +1 -5
- package/dist/styles/layout/copy-button.css +2 -2
- package/dist/styles/layout/nav-link.css +1 -2
- package/dist/styles/layout/sidebar-nav.css +1 -1
- package/dist/styles/layout/sidebar.css +1 -1
- package/dist/styles/layout/tabs.css +22 -2
- package/dist/styles/layout/theme-switch.css +1 -3
- package/dist/styles/overlays/command.css +1 -1
- package/dist/styles/overlays/confirm.css +0 -1
- package/dist/styles/overlays/menu.css +2 -4
- package/dist/styles/overlays/toast.css +0 -1
- package/dist/styles/templates/auth-layout.css +0 -1
- package/dist/styles/templates/empty-page.css +3 -3
- package/dist/styles/templates/form-page.css +1 -1
- package/dist/styles/templates/page.css +1 -1
- package/dist/styles/templates/settings-modal.css +0 -1
- package/dist/styles/tokens.css +1 -1
- package/package.json +1 -1
- package/dist/internal/forms/FormAlerts.vue +0 -25
- package/dist/internal/overlays/FieldBoundary.d.ts +0 -4
- 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
|
-
|
|
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
|
|
83
|
-
entry.node.description
|
|
84
|
-
|
|
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
|
|
79
|
-
|
|
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
|
-
|
|
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="
|
|
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
|
-
|
|
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} ${
|
|
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
|
-
(
|
|
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
|
-
|
|
198
|
-
|
|
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
|
-
(
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
298
|
+
reject(problem);
|
|
238
299
|
continue;
|
|
239
300
|
}
|
|
240
301
|
if (existing.has(fileKey(file))) continue;
|
|
241
302
|
if (accepted.length >= room) {
|
|
242
|
-
|
|
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))
|
|
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
|
|
258
|
-
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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:
|
|
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
|
|
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 {
|