@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.
Files changed (98) 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/types.d.ts +7 -4
  36. package/dist/components/templates/DashboardLayout.vue +21 -18
  37. package/dist/components/templates/DashboardLayout.vue.d.ts +332 -565
  38. package/dist/components/templates/ListPage.vue +1 -1
  39. package/dist/components/templates/ListPage.vue.d.ts +3 -3
  40. package/dist/components/templates/types.d.ts +4 -4
  41. package/dist/composables/useField.d.ts +6 -1
  42. package/dist/composables/useField.mjs +40 -12
  43. package/dist/composables/useFieldBoundary.d.ts +1 -0
  44. package/dist/composables/useFieldBoundary.mjs +5 -1
  45. package/dist/composables/useFileDrop.d.ts +14 -1
  46. package/dist/composables/useFileDrop.mjs +21 -20
  47. package/dist/composables/useForm.d.ts +4 -0
  48. package/dist/composables/useForm.mjs +62 -22
  49. package/dist/editor/RichTextEditor.vue +2 -1
  50. package/dist/internal/core/ButtonElement.d.ts +2 -0
  51. package/dist/internal/core/ButtonElement.mjs +8 -0
  52. package/dist/internal/forms/files.d.ts +7 -0
  53. package/dist/internal/forms/files.mjs +24 -0
  54. package/dist/internal/keys.d.ts +2 -1
  55. package/dist/internal/keys.mjs +1 -0
  56. package/dist/locale/en.mjs +2 -0
  57. package/dist/locale/it.mjs +2 -0
  58. package/dist/locale/types.d.ts +2 -0
  59. package/dist/styles/core/button.css +1 -13
  60. package/dist/styles/data/avatar.css +1 -1
  61. package/dist/styles/data/filter-pills.css +0 -1
  62. package/dist/styles/data/pagination.css +0 -1
  63. package/dist/styles/data/selection-bar.css +4 -0
  64. package/dist/styles/data/stat.css +1 -6
  65. package/dist/styles/data/table.css +1 -3
  66. package/dist/styles/data/tag.css +1 -4
  67. package/dist/styles/data/tree.css +8 -2
  68. package/dist/styles/feedback/alert.css +0 -1
  69. package/dist/styles/feedback/empty-state.css +0 -1
  70. package/dist/styles/forms/color-input.css +1 -1
  71. package/dist/styles/forms/combobox.css +0 -1
  72. package/dist/styles/forms/date-picker.css +1 -1
  73. package/dist/styles/forms/file-input.css +9 -1
  74. package/dist/styles/forms/number-input.css +1 -1
  75. package/dist/styles/forms/password-input.css +1 -5
  76. package/dist/styles/forms/switch.css +0 -1
  77. package/dist/styles/layout/accordion.css +6 -1
  78. package/dist/styles/layout/card.css +1 -5
  79. package/dist/styles/layout/copy-button.css +2 -2
  80. package/dist/styles/layout/nav-link.css +1 -2
  81. package/dist/styles/layout/sidebar-nav.css +1 -1
  82. package/dist/styles/layout/sidebar.css +1 -1
  83. package/dist/styles/layout/tabs.css +22 -2
  84. package/dist/styles/layout/theme-switch.css +1 -3
  85. package/dist/styles/overlays/command.css +1 -1
  86. package/dist/styles/overlays/confirm.css +0 -1
  87. package/dist/styles/overlays/menu.css +2 -4
  88. package/dist/styles/overlays/toast.css +0 -1
  89. package/dist/styles/templates/auth-layout.css +0 -1
  90. package/dist/styles/templates/empty-page.css +3 -3
  91. package/dist/styles/templates/form-page.css +1 -1
  92. package/dist/styles/templates/page.css +1 -1
  93. package/dist/styles/templates/settings-modal.css +0 -1
  94. package/dist/styles/tokens.css +1 -1
  95. package/package.json +1 -1
  96. package/dist/internal/forms/FormAlerts.vue +0 -25
  97. package/dist/internal/overlays/FieldBoundary.d.ts +0 -4
  98. package/dist/internal/overlays/FieldBoundary.mjs +0 -9
@@ -230,7 +230,7 @@ defineSlots();
230
230
  const page = defineModel("page", { type: Number, ...{ default: 1 } });
231
231
  const pageSize = defineModel("pageSize", { type: Number, ...{ default: 20 } });
232
232
  const search = defineModel("search", { type: String, ...{ default: "" } });
233
- const filter = defineModel("filter", { type: [String, Number, Array, null], ...{ default: null } });
233
+ const filter = defineModel("filter", { type: null, ...{ default: null } });
234
234
  const sort = defineModel("sort", { type: [Object, null], ...{ default: null } });
235
235
  const selected = defineModel("selected", { type: Array, ...{ default: () => [] } });
236
236
  const slots = useSlots();
@@ -45,7 +45,7 @@ declare const __VLS_export: <T extends object>(__VLS_props: NonNullable<Awaited<
45
45
  page?: number;
46
46
  pageSize?: number;
47
47
  search?: string;
48
- filter?: (string | number) | (string | number)[] | null;
48
+ filter?: import("../data/types").FilterPillValue | import("../data/types").FilterPillValue[] | null;
49
49
  sort?: TableSort | null;
50
50
  selected?: RowKey[];
51
51
  }) & {
@@ -57,7 +57,7 @@ declare const __VLS_export: <T extends object>(__VLS_props: NonNullable<Awaited<
57
57
  "onUpdate:sort"?: ((value: TableSort | null) => any) | undefined;
58
58
  "onUpdate:selected"?: ((value: RowKey[]) => any) | undefined;
59
59
  "onUpdate:search"?: ((value: string) => any) | undefined;
60
- "onUpdate:filter"?: ((value: string | number | (string | number)[] | null) => any) | undefined;
60
+ "onUpdate:filter"?: ((value: import("../data/types").FilterPillValue | import("../data/types").FilterPillValue[] | null) => any) | undefined;
61
61
  } & Partial<import("vue").HTMLAttributes & import("vue").ReservedProps>> & (typeof globalThis extends {
62
62
  __VLS_PROPS_FALLBACK: infer P;
63
63
  } ? P : {});
@@ -100,7 +100,7 @@ declare const __VLS_export: <T extends object>(__VLS_props: NonNullable<Awaited<
100
100
  retry: () => void;
101
101
  }) => unknown;
102
102
  };
103
- emit: (((evt: "clear") => void) & ((evt: "retry") => void) & ((evt: "rowClick", row: T, index: number, event: MouseEvent) => void)) & (((event: "update:page", value: number) => void) & ((event: "update:pageSize", value: number) => void) & ((event: "update:sort", value: TableSort | null) => void) & ((event: "update:selected", value: RowKey[]) => void) & ((event: "update:search", value: string) => void) & ((event: "update:filter", value: string | number | (string | number)[] | null) => void));
103
+ emit: (((evt: "clear") => void) & ((evt: "retry") => void) & ((evt: "rowClick", row: T, index: number, event: MouseEvent) => void)) & (((event: "update:page", value: number) => void) & ((event: "update:pageSize", value: number) => void) & ((event: "update:sort", value: TableSort | null) => void) & ((event: "update:selected", value: RowKey[]) => void) & ((event: "update:search", value: string) => void) & ((event: "update:filter", value: import("../data/types").FilterPillValue | import("../data/types").FilterPillValue[] | null) => void));
104
104
  }>) => import("vue").VNode & {
105
105
  __ctx?: NonNullable<Awaited<typeof __VLS_setup>>;
106
106
  };
@@ -2,7 +2,7 @@ import type { Component } from 'vue';
2
2
  import type { LinkTarget, Tone } from '../core/types';
3
3
  import type { DescriptionItem, StatTrend } from '../data/types';
4
4
  import type { BreadcrumbItem, NavItem, NavSection } from '../layout/types';
5
- import type { CommandGroup, MenuItems } from '../overlays/types';
5
+ import type { CommandEmptyText, CommandGroup, MenuItems } from '../overlays/types';
6
6
  import type { ThemePreset } from '../../internal/core/preset-data';
7
7
  export type DashboardVariant = 'inset' | 'sidebar' | 'floating';
8
8
  export type ThemeSwitchPlacement = 'topbar' | 'sidebar' | false;
@@ -43,8 +43,8 @@ export interface DashboardBrand {
43
43
  logo?: string;
44
44
  to?: LinkTarget;
45
45
  }
46
- export type DashboardSearchFunction = (query: string, signal: AbortSignal) => CommandGroup[] | Promise<CommandGroup[]>;
47
- export type DashboardSearch = boolean | CommandGroup[] | DashboardSearchFunction;
46
+ export type DashboardSearchFunction<T = unknown> = (query: string, signal: AbortSignal) => CommandGroup<T>[] | Promise<CommandGroup<T>[]>;
47
+ export type DashboardSearch<T = unknown> = boolean | CommandGroup<T>[] | DashboardSearchFunction<T>;
48
48
  export interface DashboardSearchProps {
49
49
  label?: string;
50
50
  placeholder?: string;
@@ -55,7 +55,7 @@ export interface DashboardSearchProps {
55
55
  recentKey?: string;
56
56
  recentLimit?: number;
57
57
  recentLabel?: string;
58
- emptyText?: string;
58
+ emptyText?: CommandEmptyText;
59
59
  minQueryText?: string;
60
60
  errorText?: string;
61
61
  loadingText?: string;
@@ -8,7 +8,9 @@ export interface FieldContext {
8
8
  required: ComputedRef<boolean>;
9
9
  disabled: ComputedRef<boolean>;
10
10
  setControlError: (message: string | undefined, source?: object) => void;
11
+ setLabelable?: (labelable: boolean | undefined, source: object) => void;
11
12
  }
13
+ export type FieldControlRole = 'primary' | 'auxiliary';
12
14
  export declare function useField(): FieldContext | null;
13
15
  export interface FieldControlOptions {
14
16
  id?: MaybeRefOrGetter<string | undefined>;
@@ -16,9 +18,11 @@ export interface FieldControlOptions {
16
18
  required?: MaybeRefOrGetter<boolean | undefined>;
17
19
  disabled?: MaybeRefOrGetter<boolean | undefined>;
18
20
  describedBy?: MaybeRefOrGetter<string | undefined>;
21
+ role?: MaybeRefOrGetter<FieldControlRole | undefined>;
22
+ labelable?: MaybeRefOrGetter<boolean | undefined>;
19
23
  }
20
24
  export declare function useFieldControl(options?: FieldControlOptions): {
21
- field: FieldContext | null;
25
+ readonly field: FieldContext | null;
22
26
  id: ComputedRef<string | undefined>;
23
27
  invalid: ComputedRef<boolean>;
24
28
  required: ComputedRef<boolean>;
@@ -32,5 +36,6 @@ export declare function useFieldControl(options?: FieldControlOptions): {
32
36
  'aria-required': "true" | undefined;
33
37
  disabled: true | undefined;
34
38
  }>;
39
+ auxiliary: ComputedRef<boolean>;
35
40
  setControlError: (message: string | undefined) => void | undefined;
36
41
  };
@@ -1,28 +1,56 @@
1
- import { computed, getCurrentScope, inject, onScopeDispose, toValue } from "vue";
2
- import { FIELD_KEY } from "../internal/keys.mjs";
1
+ import { computed, getCurrentScope, inject, onScopeDispose, toValue, watch } from "vue";
2
+ import { FIELD_KEY, FIELD_ROLE_KEY } from "../internal/keys.mjs";
3
3
  export function useField() {
4
4
  return inject(FIELD_KEY, null);
5
5
  }
6
6
  export function useFieldControl(options = {}) {
7
- const field = useField();
8
- const id = computed(() => toValue(options.id) ?? field?.id);
9
- const invalid = computed(() => Boolean(toValue(options.invalid) ?? field?.invalid.value));
10
- const required = computed(() => Boolean(toValue(options.required) || field?.required.value));
11
- const disabled = computed(() => Boolean(toValue(options.disabled) || field?.disabled.value));
7
+ const parent = useField();
8
+ const inheritedRole = inject(FIELD_ROLE_KEY, "primary");
9
+ const auxiliary = computed(() => (toValue(options.role) ?? inheritedRole) === "auxiliary");
10
+ const field = computed(() => auxiliary.value ? null : parent);
11
+ const id = computed(() => toValue(options.id) ?? field.value?.id);
12
+ const invalid = computed(() => Boolean(toValue(options.invalid) ?? field.value?.invalid.value));
13
+ const required = computed(
14
+ () => Boolean(toValue(options.required) || field.value?.required.value)
15
+ );
16
+ const disabled = computed(() => Boolean(toValue(options.disabled) || parent?.disabled.value));
12
17
  const describedBy = computed(() => {
13
- const ids = [toValue(options.describedBy), field?.describedBy.value].filter(Boolean);
18
+ const ids = [toValue(options.describedBy), field.value?.describedBy.value].filter(Boolean);
14
19
  return ids.length ? ids.join(" ") : void 0;
15
20
  });
16
21
  const attrs = computed(() => ({
17
22
  id: id.value,
18
- name: field?.name.value,
23
+ name: field.value?.name.value,
19
24
  "aria-invalid": invalid.value ? "true" : void 0,
20
25
  "aria-describedby": describedBy.value,
21
26
  "aria-required": required.value ? "true" : void 0,
22
27
  disabled: disabled.value || void 0
23
28
  }));
24
29
  const source = {};
25
- const setControlError = (message) => field?.setControlError(message || void 0, source);
26
- if (field && getCurrentScope()) onScopeDispose(() => field.setControlError(void 0, source));
27
- return { field, id, invalid, required, disabled, describedBy, attrs, setControlError };
30
+ const setControlError = (message) => parent?.setControlError(message || void 0, source);
31
+ if (parent && getCurrentScope()) {
32
+ if (parent.setLabelable && options.labelable !== void 0)
33
+ watch(
34
+ () => auxiliary.value ? void 0 : toValue(options.labelable),
35
+ (labelable) => parent.setLabelable?.(labelable, source),
36
+ { immediate: true }
37
+ );
38
+ onScopeDispose(() => {
39
+ parent.setControlError(void 0, source);
40
+ parent.setLabelable?.(void 0, source);
41
+ });
42
+ }
43
+ return {
44
+ get field() {
45
+ return field.value;
46
+ },
47
+ id,
48
+ invalid,
49
+ required,
50
+ disabled,
51
+ describedBy,
52
+ attrs,
53
+ auxiliary,
54
+ setControlError
55
+ };
28
56
  }
@@ -1 +1,2 @@
1
1
  export declare function provideFieldBoundary(): void;
2
+ export declare function provideAuxiliaryControls(): void;
@@ -1,5 +1,9 @@
1
1
  import { provide } from "vue";
2
- import { FIELD_KEY } from "../internal/keys.mjs";
2
+ import { FIELD_KEY, FIELD_ROLE_KEY } from "../internal/keys.mjs";
3
3
  export function provideFieldBoundary() {
4
4
  provide(FIELD_KEY, null);
5
+ provide(FIELD_ROLE_KEY, "primary");
6
+ }
7
+ export function provideAuxiliaryControls() {
8
+ provide(FIELD_ROLE_KEY, "auxiliary");
5
9
  }
@@ -8,7 +8,20 @@ export interface DroppedFile {
8
8
  file: File;
9
9
  path: string;
10
10
  }
11
- export declare function readDroppedFiles(transfer: DataTransfer | null | undefined): Promise<DroppedFile[]>;
11
+ export interface DroppedTree {
12
+ files: DroppedFile[];
13
+ directories: string[];
14
+ }
15
+ export interface ReadDroppedFilesOptions {
16
+ directories?: boolean;
17
+ skipHidden?: boolean;
18
+ }
19
+ export declare function readDroppedFiles(transfer: DataTransfer | null | undefined, options: ReadDroppedFilesOptions & {
20
+ directories: true;
21
+ }): Promise<DroppedTree>;
22
+ export declare function readDroppedFiles(transfer: DataTransfer | null | undefined, options?: ReadDroppedFilesOptions & {
23
+ directories?: false;
24
+ }): Promise<DroppedFile[]>;
12
25
  export declare function useFileDrop(target: MaybeRefOrGetter<HTMLElement | null | undefined>, options: UseFileDropOptions): {
13
26
  over: import("vue").Ref<boolean, boolean>;
14
27
  reset: () => void;
@@ -1,4 +1,5 @@
1
1
  import { onScopeDispose, ref, toValue, watch } from "vue";
2
+ import { filesTree, isHiddenName } from "../internal/forms/files.mjs";
2
3
  const claimedEvents = /* @__PURE__ */ new WeakSet();
3
4
  function carriesFiles(event) {
4
5
  return Array.from(event.dataTransfer?.types ?? []).includes("Files");
@@ -9,17 +10,20 @@ function readFileEntry(entry) {
9
10
  function readDirectoryBatch(reader) {
10
11
  return new Promise((resolve, reject) => reader.readEntries(resolve, reject));
11
12
  }
12
- async function walkEntry(entry, prefix, result) {
13
+ async function walkEntry(entry, prefix, tree, skipHidden) {
14
+ if (skipHidden && isHiddenName(entry.name)) return;
15
+ const path = `${prefix}${entry.name}`;
13
16
  if (entry.isFile) {
14
17
  try {
15
18
  const file = await readFileEntry(entry);
16
- result.push({ file, path: `${prefix}${entry.name}` });
19
+ tree.files.push({ file, path });
17
20
  } catch {
18
21
  return;
19
22
  }
20
23
  return;
21
24
  }
22
25
  if (!entry.isDirectory) return;
26
+ tree.directories.push(path);
23
27
  const reader = entry.createReader();
24
28
  const children = [];
25
29
  for (; ; ) {
@@ -32,10 +36,10 @@ async function walkEntry(entry, prefix, result) {
32
36
  if (!batch.length) break;
33
37
  children.push(...batch);
34
38
  }
35
- for (const child of children) await walkEntry(child, `${prefix}${entry.name}/`, result);
39
+ for (const child of children) await walkEntry(child, `${path}/`, tree, skipHidden);
36
40
  }
37
- export function readDroppedFiles(transfer) {
38
- if (!transfer) return Promise.resolve([]);
41
+ async function readDroppedTree(transfer, skipHidden) {
42
+ if (!transfer) return { files: [], directories: [] };
39
43
  const pending = [];
40
44
  for (const item of Array.from(transfer.items ?? [])) {
41
45
  if (item.kind !== "file") continue;
@@ -44,21 +48,18 @@ export function readDroppedFiles(transfer) {
44
48
  if (entry) pending.push(entry);
45
49
  else if (fallback) pending.push(fallback);
46
50
  }
47
- if (!pending.length)
48
- return Promise.resolve(
49
- Array.from(transfer.files ?? []).map((file) => ({
50
- file,
51
- path: file.webkitRelativePath || file.name
52
- }))
53
- );
54
- return (async () => {
55
- const result = [];
56
- for (const entry of pending) {
57
- if (entry instanceof File) result.push({ file: entry, path: entry.name });
58
- else await walkEntry(entry, "", result);
59
- }
60
- return result;
61
- })();
51
+ if (!pending.length) return filesTree(Array.from(transfer.files ?? []), { skipHidden });
52
+ const tree = { files: [], directories: [] };
53
+ for (const entry of pending) {
54
+ if (!(entry instanceof File)) await walkEntry(entry, "", tree, skipHidden);
55
+ else if (!skipHidden || !isHiddenName(entry.name))
56
+ tree.files.push({ file: entry, path: entry.name });
57
+ }
58
+ return tree;
59
+ }
60
+ export async function readDroppedFiles(transfer, options = {}) {
61
+ const tree = await readDroppedTree(transfer, Boolean(options.skipHidden));
62
+ return options.directories ? tree : tree.files;
62
63
  }
63
64
  export function useFileDrop(target, options) {
64
65
  const over = ref(false);
@@ -58,6 +58,8 @@ export type FormErrors<S extends FormSchema = FormSchema> = Partial<Record<keyof
58
58
  export interface FormContext {
59
59
  errors: Readonly<FormMessageMap>;
60
60
  warnings?: Readonly<FormMessageMap>;
61
+ rootErrors?: Readonly<Ref<readonly string[]>>;
62
+ rootWarnings?: Readonly<Ref<readonly string[]>>;
61
63
  submitCount: Readonly<Ref<number>>;
62
64
  submitting: Readonly<Ref<boolean>>;
63
65
  touch: (name: string) => void;
@@ -80,6 +82,7 @@ export interface UseFormReturn<S extends FormSchema = FormSchema, V extends obje
80
82
  submitting: Ref<boolean>;
81
83
  isDirty: (name: keyof V & string) => boolean;
82
84
  validate: () => Promise<boolean>;
85
+ validateSync: () => boolean;
83
86
  validateField: (name: keyof V & string) => Promise<boolean>;
84
87
  setError: (name: FormPath<V>, message: string | undefined) => void;
85
88
  setErrors: (errors: FormErrors<S>) => void;
@@ -91,3 +94,4 @@ export interface UseFormReturn<S extends FormSchema = FormSchema, V extends obje
91
94
  export declare function isEmptyValue(value: unknown): boolean;
92
95
  export declare function useForm<S extends FormSchema = FormSchema, V extends object = FormValues<S>>(options: UseFormOptions<S, V>): UseFormReturn<S, V>;
93
96
  export declare function useFormContext(): FormContext | null;
97
+ export declare function provideForm(form: FormContext): FormContext;
@@ -2,6 +2,7 @@ import {
2
2
  computed,
3
3
  inject,
4
4
  isRef,
5
+ provide,
5
6
  reactive,
6
7
  readonly,
7
8
  ref,
@@ -15,7 +16,6 @@ import {
15
16
  groupIssues,
16
17
  isStandardSchema,
17
18
  keyPath,
18
- runStandardSchema,
19
19
  schemaIssues
20
20
  } from "../internal/forms/standard-schema.mjs";
21
21
  import { FORM_KEY } from "../internal/keys.mjs";
@@ -79,6 +79,9 @@ function settle(value, apply) {
79
79
  if (isThenable(value)) void value.then(apply);
80
80
  else apply(value);
81
81
  }
82
+ function chain(value, next) {
83
+ return isThenable(value) ? value.then(next) : next(value);
84
+ }
82
85
  function isWithin(key, name) {
83
86
  return key === name || key.startsWith(`${name}.`);
84
87
  }
@@ -145,13 +148,18 @@ export function useForm(options) {
145
148
  replaceEntries(errors, composeErrors(), inScope);
146
149
  if (!inScope) rootErrors.value = uniqueMessages(manualRoot, issueRoot);
147
150
  }
148
- async function runValidator() {
151
+ function evaluateValidator() {
149
152
  const validator = toValue(options.validator);
150
153
  if (!validator) return groupIssues([]);
151
- const issues = await runStandardSchema(validator, cloneValue(target()));
152
- return groupIssues(schemaIssues(issues), issueKey);
154
+ return chain(
155
+ validator["~standard"].validate(cloneValue(target())),
156
+ (result) => groupIssues(schemaIssues(result.issues ?? []), issueKey)
157
+ );
153
158
  }
154
- async function runRules(name) {
159
+ async function runValidator() {
160
+ return evaluateValidator();
161
+ }
162
+ function evaluateRules(name) {
155
163
  const definition = schema[name];
156
164
  if (!definition) return void 0;
157
165
  const value = readValue(name);
@@ -185,16 +193,22 @@ export function useForm(options) {
185
193
  if (!definition.pattern.test(value)) return fieldMessages.pattern(context);
186
194
  }
187
195
  }
188
- if (definition.validate) {
189
- const result = await definition.validate(value, target());
196
+ const runSchema = () => {
197
+ if (!definition.schema) return void 0;
198
+ return chain(definition.schema["~standard"].validate(value), (result) => {
199
+ const issues = result.issues ?? [];
200
+ return issues.length ? issues[0].message || fieldMessages.invalid(context) : void 0;
201
+ });
202
+ };
203
+ if (!definition.validate) return runSchema();
204
+ return chain(definition.validate(value, target()), (result) => {
190
205
  if (typeof result === "string" && result) return result;
191
206
  if (result === false) return fieldMessages.invalid(context);
192
- }
193
- if (definition.schema) {
194
- const issues = await runStandardSchema(definition.schema, value);
195
- if (issues.length) return issues[0].message || fieldMessages.invalid(context);
196
- }
197
- return void 0;
207
+ return runSchema();
208
+ });
209
+ }
210
+ async function runRules(name) {
211
+ return evaluateRules(name);
198
212
  }
199
213
  function hasErrorWithin(name) {
200
214
  return Object.entries(errors).some(([key, message]) => message && isWithin(key, name));
@@ -215,6 +229,18 @@ export function useForm(options) {
215
229
  syncErrors(submitCount.value > 0 ? void 0 : (key) => isWithin(key, name));
216
230
  return !hasErrorWithin(name);
217
231
  }
232
+ function applyValidation(fields, issues, results) {
233
+ issueErrors = issues.fields;
234
+ issueRoot = issues.root;
235
+ for (const key of Object.keys(ruleErrors)) delete ruleErrors[key];
236
+ fields.forEach((name, index) => ruleErrors[name] = results[index]);
237
+ for (const key of Object.keys(manualErrors)) delete manualErrors[key];
238
+ manualRoot = [];
239
+ syncErrors();
240
+ }
241
+ function isValidResult(issues, results) {
242
+ return results.every((message) => !message) && Object.keys(issues.fields).length === 0 && issues.root.length === 0;
243
+ }
218
244
  async function validate() {
219
245
  const run = ++validationRun;
220
246
  const fields = names.value;
@@ -222,16 +248,25 @@ export function useForm(options) {
222
248
  runValidator(),
223
249
  Promise.all(fields.map((name) => runRules(name)))
224
250
  ]);
225
- if (run === validationRun) {
226
- issueErrors = issues.fields;
227
- issueRoot = issues.root;
228
- for (const key of Object.keys(ruleErrors)) delete ruleErrors[key];
229
- fields.forEach((name, index) => ruleErrors[name] = results[index]);
230
- for (const key of Object.keys(manualErrors)) delete manualErrors[key];
231
- manualRoot = [];
232
- syncErrors();
251
+ if (run === validationRun) applyValidation(fields, issues, results);
252
+ return isValidResult(issues, results);
253
+ }
254
+ function validateSync() {
255
+ const fields = names.value;
256
+ const issues = evaluateValidator();
257
+ const results = fields.map((name) => evaluateRules(name));
258
+ if (isThenable(issues) || results.some((result) => isThenable(result))) {
259
+ for (const pending of [issues, ...results]) {
260
+ if (isThenable(pending)) void pending.catch(() => void 0);
261
+ }
262
+ throw new Error(
263
+ "[k] useForm: validateSync() found an async rule or validator. Use validate() instead."
264
+ );
233
265
  }
234
- return results.every((message) => !message) && Object.keys(issues.fields).length === 0 && issues.root.length === 0;
266
+ const messages2 = results;
267
+ ++validationRun;
268
+ applyValidation(fields, issues, messages2);
269
+ return isValidResult(issues, messages2);
235
270
  }
236
271
  function setError(name, message) {
237
272
  if (name === "") {
@@ -374,6 +409,7 @@ export function useForm(options) {
374
409
  submitting,
375
410
  isDirty: (name) => dirtyFields.value.includes(name),
376
411
  validate,
412
+ validateSync,
377
413
  validateField,
378
414
  setError,
379
415
  setErrors,
@@ -387,3 +423,7 @@ export function useForm(options) {
387
423
  export function useFormContext() {
388
424
  return inject(FORM_KEY, null);
389
425
  }
426
+ export function provideForm(form) {
427
+ provide(FORM_KEY, form);
428
+ return form;
429
+ }
@@ -126,7 +126,8 @@ const model = defineModel({ type: [String, Object, null], ...{ default: null } }
126
126
  const control = useFieldControl({
127
127
  invalid: () => props.invalid,
128
128
  disabled: () => props.disabled,
129
- required: () => props.required
129
+ required: () => props.required,
130
+ labelable: false
130
131
  });
131
132
  const { locale } = useLocale();
132
133
  const text = computed(() => ({ ...locale.value.messages.editor, ...props.messages }));
@@ -0,0 +1,2 @@
1
+ import type { ButtonHTMLAttributes, DefineComponent } from 'vue';
2
+ export declare const ButtonElement: DefineComponent<Omit<ButtonHTMLAttributes, "onSelect">>;
@@ -0,0 +1,8 @@
1
+ import { defineComponent, h } from "vue";
2
+ export const ButtonElement = defineComponent({
3
+ name: "KButtonElement",
4
+ inheritAttrs: false,
5
+ setup(_, { attrs, slots }) {
6
+ return () => h("button", attrs, slots.default?.());
7
+ }
8
+ });
@@ -1,3 +1,4 @@
1
+ import type { DroppedTree } from '../../composables/useFileDrop';
1
2
  export declare function parseAccept(accept: string | string[] | undefined): string[];
2
3
  export declare function matchesAccept(file: File, accept: string[]): boolean;
3
4
  export declare function formatBytes(bytes: number, locale?: string): string;
@@ -5,3 +6,9 @@ export declare function isImageFile(file: File): boolean;
5
6
  export declare function setFilePath(file: File, path: string): void;
6
7
  export declare function filePath(file: File): string;
7
8
  export declare function fileKey(file: File): string;
9
+ export declare function isHiddenName(name: string): boolean;
10
+ export declare function isHiddenPath(path: string): boolean;
11
+ export declare function directoriesOf(paths: Iterable<string>): string[];
12
+ export declare function filesTree(files: Iterable<File>, options?: {
13
+ skipHidden?: boolean;
14
+ }): DroppedTree;
@@ -37,3 +37,27 @@ export function filePath(file) {
37
37
  export function fileKey(file) {
38
38
  return `${filePath(file)}|${file.size}|${file.lastModified}`;
39
39
  }
40
+ export function isHiddenName(name) {
41
+ return name.startsWith(".");
42
+ }
43
+ export function isHiddenPath(path) {
44
+ return path.split("/").some(isHiddenName);
45
+ }
46
+ export function directoriesOf(paths) {
47
+ const directories = /* @__PURE__ */ new Set();
48
+ for (const path of paths) {
49
+ const segments = path.split("/").filter(Boolean);
50
+ for (let depth = 1; depth < segments.length; depth++)
51
+ directories.add(segments.slice(0, depth).join("/"));
52
+ }
53
+ return [...directories];
54
+ }
55
+ export function filesTree(files, options = {}) {
56
+ const entries = [];
57
+ for (const file of files) {
58
+ const path = file.webkitRelativePath || file.name;
59
+ if (options.skipHidden && isHiddenPath(path)) continue;
60
+ entries.push({ file, path });
61
+ }
62
+ return { files: entries, directories: directoriesOf(entries.map((entry) => entry.path)) };
63
+ }
@@ -1,5 +1,5 @@
1
1
  import type { Component, ComputedRef, InjectionKey, Ref } from 'vue';
2
- import type { FieldContext } from '../composables/useField';
2
+ import type { FieldContext, FieldControlRole } from '../composables/useField';
3
3
  import type { FormContext } from '../composables/useForm';
4
4
  import type { IconRegistry } from '../composables/useIcons';
5
5
  import type { ThemePreference } from '../composables/useTheme';
@@ -11,6 +11,7 @@ export declare const THEME_KEY: InjectionKey<Ref<ThemePreference>>;
11
11
  export declare const PRESET_KEY: InjectionKey<Ref<ThemePreset>>;
12
12
  export declare const LINK_KEY: InjectionKey<Component>;
13
13
  export declare const FIELD_KEY: InjectionKey<FieldContext>;
14
+ export declare const FIELD_ROLE_KEY: InjectionKey<FieldControlRole>;
14
15
  export declare const ROOT_KEY: InjectionKey<true>;
15
16
  export declare const FORM_KEY: InjectionKey<FormContext>;
16
17
  export declare const LOCALE_KEY: InjectionKey<ComputedRef<KLocale>>;
@@ -4,6 +4,7 @@ export const THEME_KEY = Symbol("k-theme");
4
4
  export const PRESET_KEY = Symbol("k-preset");
5
5
  export const LINK_KEY = Symbol("k-link");
6
6
  export const FIELD_KEY = Symbol("k-field");
7
+ export const FIELD_ROLE_KEY = Symbol("k-field-role");
7
8
  export const ROOT_KEY = Symbol("k-root");
8
9
  export const FORM_KEY = Symbol("k-form");
9
10
  export const LOCALE_KEY = Symbol("k-locale");
@@ -189,6 +189,8 @@ export const en = {
189
189
  list: "Selected files",
190
190
  selected: "{count} file selected | {count} files selected",
191
191
  directoryTitle: "Drop a folder here or click to browse",
192
+ mixedTitle: "Drop files or folders here or click to browse",
193
+ browseDirectory: "Upload a folder",
192
194
  loading: "Uploading",
193
195
  added: "{count} file added | {count} files added"
194
196
  },
@@ -189,6 +189,8 @@ export const it = {
189
189
  list: "File selezionati",
190
190
  selected: "{count} file selezionato | {count} file selezionati",
191
191
  directoryTitle: "Trascina una cartella qui o fai clic per sfogliare",
192
+ mixedTitle: "Trascina file o cartelle qui o fai clic per sfogliare",
193
+ browseDirectory: "Carica una cartella",
192
194
  loading: "Caricamento in corso",
193
195
  added: "{count} file aggiunto | {count} file aggiunti"
194
196
  },
@@ -187,6 +187,8 @@ export interface KMessages {
187
187
  list: string;
188
188
  selected: string;
189
189
  directoryTitle: string;
190
+ mixedTitle: string;
191
+ browseDirectory: string;
190
192
  loading: string;
191
193
  added: string;
192
194
  };
@@ -64,7 +64,7 @@
64
64
  }
65
65
 
66
66
  .k-btn > .k-icon {
67
- transition: transform var(--k-duration-lg) var(--k-ease-spring);
67
+ transition: transform var(--k-duration-lg) var(--k-ease-out);
68
68
  }
69
69
 
70
70
  .k-btn:hover > .k-icon[data-icon='arrow-right'],
@@ -77,18 +77,6 @@
77
77
  transform: translateX(-2px);
78
78
  }
79
79
 
80
- .k-btn:hover > .k-icon[data-icon='plus'] {
81
- transform: rotate(90deg);
82
- }
83
-
84
- .k-btn:hover > .k-icon[data-icon='arrow-path'] {
85
- transform: rotate(60deg);
86
- }
87
-
88
- .k-btn:hover > .k-icon[data-icon='arrow-up-tray'] {
89
- transform: translateY(-1px);
90
- }
91
-
92
80
  .k-btn-xs {
93
81
  --k-btn-size: var(--k-size-xs);
94
82
  --k-icon-size: 0.875rem;
@@ -118,7 +118,7 @@
118
118
 
119
119
  .k-avatar-group > .k-avatar {
120
120
  box-shadow: 0 0 0 2px var(--k-surface);
121
- transition: translate var(--k-duration-lg) var(--k-ease-spring);
121
+ transition: translate var(--k-duration-lg) var(--k-ease-out);
122
122
  }
123
123
 
124
124
  .k-avatar-group > .k-avatar + .k-avatar {
@@ -71,7 +71,6 @@
71
71
  }
72
72
 
73
73
  .k-filter-pill[aria-pressed='true'] .k-filter-pill-check > .k-icon {
74
- animation: k-icon-pop 300ms var(--k-ease-spring);
75
74
  }
76
75
 
77
76
  .k-filter-pill-count {
@@ -94,7 +94,6 @@
94
94
  border-color: var(--k-field-line);
95
95
  box-shadow: var(--k-shadow-xs);
96
96
  color: var(--k-text);
97
- animation: k-icon-pop 300ms var(--k-ease-spring);
98
97
  }
99
98
 
100
99
  .k-pagination-page:disabled {
@@ -3,6 +3,10 @@
3
3
  min-width: 0;
4
4
  }
5
5
 
6
+ .k-selection-bar:not(.is-visible) {
7
+ display: contents;
8
+ }
9
+
6
10
  .k-selection-bar-sticky {
7
11
  position: sticky;
8
12
  top: 0;