@xleddyl/443-components-vue 0.2.2 → 0.3.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 (205) hide show
  1. package/README.md +79 -19
  2. package/dist/chart/BarChart.vue.d.ts +2 -2
  3. package/dist/chart/ChartLegend.vue.d.ts +4 -4
  4. package/dist/chart/ChartPanel.vue.d.ts +2 -2
  5. package/dist/chart/ChartSummary.vue.d.ts +2 -2
  6. package/dist/chart/DoughnutChart.vue.d.ts +2 -2
  7. package/dist/chart/LineChart.vue.d.ts +2 -2
  8. package/dist/components/core/Button.vue.d.ts +2 -2
  9. package/dist/components/core/FieldMessage.vue +28 -0
  10. package/dist/components/core/FieldMessage.vue.d.ts +409 -0
  11. package/dist/components/core/Form.vue +22 -3
  12. package/dist/components/core/Form.vue.d.ts +18 -4
  13. package/dist/components/core/FormField.vue +37 -15
  14. package/dist/components/core/FormField.vue.d.ts +5 -4
  15. package/dist/components/core/Icon.vue.d.ts +2 -2
  16. package/dist/components/core/Input.vue.d.ts +4 -4
  17. package/dist/components/core/Kbd.vue.d.ts +2 -2
  18. package/dist/components/core/Root.vue.d.ts +6 -6
  19. package/dist/components/core/Spinner.vue +13 -1
  20. package/dist/components/core/Spinner.vue.d.ts +4 -2
  21. package/dist/components/core/Textarea.vue.d.ts +4 -4
  22. package/dist/components/core/VisuallyHidden.vue.d.ts +6 -6
  23. package/dist/components/core/index.d.ts +1 -0
  24. package/dist/components/core/index.mjs +1 -0
  25. package/dist/components/data/Avatar.vue.d.ts +2 -2
  26. package/dist/components/data/AvatarGroup.vue.d.ts +2 -2
  27. package/dist/components/data/Badge.vue.d.ts +2 -2
  28. package/dist/components/data/DescriptionList.vue.d.ts +2 -2
  29. package/dist/components/data/FilterPill.vue +35 -0
  30. package/dist/components/data/FilterPill.vue.d.ts +445 -0
  31. package/dist/components/data/FilterPills.vue +49 -21
  32. package/dist/components/data/FilterPills.vue.d.ts +22 -5
  33. package/dist/components/data/Pagination.vue.d.ts +4 -4
  34. package/dist/components/data/SelectionBar.vue +78 -0
  35. package/dist/components/data/SelectionBar.vue.d.ts +427 -0
  36. package/dist/components/data/Stat.vue.d.ts +8 -8
  37. package/dist/components/data/Tag.vue.d.ts +4 -4
  38. package/dist/components/data/Timeline.vue.d.ts +2 -2
  39. package/dist/components/data/Tree.vue +237 -18
  40. package/dist/components/data/Tree.vue.d.ts +37 -10
  41. package/dist/components/data/index.d.ts +3 -0
  42. package/dist/components/data/index.mjs +3 -0
  43. package/dist/components/data/types.d.ts +19 -0
  44. package/dist/components/feedback/Alert.vue.d.ts +18 -18
  45. package/dist/components/feedback/EmptyState.vue.d.ts +2 -2
  46. package/dist/components/feedback/LoadingOverlay.vue.d.ts +2 -2
  47. package/dist/components/feedback/Meter.vue.d.ts +2 -2
  48. package/dist/components/feedback/Progress.vue.d.ts +2 -2
  49. package/dist/components/feedback/ProgressRing.vue.d.ts +2 -2
  50. package/dist/components/feedback/Skeleton.vue.d.ts +3 -3
  51. package/dist/components/forms/Calendar.vue.d.ts +4 -4
  52. package/dist/components/forms/ColorInput.vue.d.ts +6 -6
  53. package/dist/components/forms/DateInput.vue.d.ts +14 -14
  54. package/dist/components/forms/DatePicker.vue.d.ts +6 -6
  55. package/dist/components/forms/FileInput.vue +80 -26
  56. package/dist/components/forms/FileInput.vue.d.ts +21 -11
  57. package/dist/components/forms/InputAddon.vue.d.ts +2 -2
  58. package/dist/components/forms/InputGroup.vue.d.ts +2 -2
  59. package/dist/components/forms/NumberInput.vue.d.ts +6 -6
  60. package/dist/components/forms/PasswordInput.vue.d.ts +16 -16
  61. package/dist/components/forms/PinInput.vue.d.ts +6 -6
  62. package/dist/components/forms/Radio.vue.d.ts +2 -2
  63. package/dist/components/forms/SegmentedControl.vue +32 -8
  64. package/dist/components/forms/SegmentedControl.vue.d.ts +15 -7
  65. package/dist/components/forms/Slider.vue.d.ts +6 -6
  66. package/dist/components/forms/Switch.vue.d.ts +6 -6
  67. package/dist/components/forms/TimeInput.vue.d.ts +16 -16
  68. package/dist/components/forms/types.d.ts +14 -0
  69. package/dist/components/layout/Accordion.vue +25 -5
  70. package/dist/components/layout/Accordion.vue.d.ts +8 -5
  71. package/dist/components/layout/AppShell.vue.d.ts +5 -5
  72. package/dist/components/layout/Breadcrumbs.vue +28 -20
  73. package/dist/components/layout/Breadcrumbs.vue.d.ts +18 -3
  74. package/dist/components/layout/Card.vue +22 -3
  75. package/dist/components/layout/Card.vue.d.ts +10 -4
  76. package/dist/components/layout/CopyButton.vue.d.ts +4 -4
  77. package/dist/components/layout/Grid.vue.d.ts +2 -2
  78. package/dist/components/layout/NavLink.vue.d.ts +8 -8
  79. package/dist/components/layout/PageHeader.vue +10 -1
  80. package/dist/components/layout/PageHeader.vue.d.ts +17 -9
  81. package/dist/components/layout/Separator.vue.d.ts +2 -2
  82. package/dist/components/layout/Sidebar.vue.d.ts +2 -2
  83. package/dist/components/layout/SidebarSection.vue.d.ts +2 -2
  84. package/dist/components/layout/Stack.vue.d.ts +6 -6
  85. package/dist/components/layout/Tabs.vue +27 -6
  86. package/dist/components/layout/Tabs.vue.d.ts +7 -4
  87. package/dist/components/layout/ThemeSwitch.vue.d.ts +2 -2
  88. package/dist/components/layout/Toolbar.vue.d.ts +2 -2
  89. package/dist/components/layout/types.d.ts +7 -1
  90. package/dist/components/overlays/CommandPalette.vue +25 -4
  91. package/dist/components/overlays/CommandPalette.vue.d.ts +23 -9
  92. package/dist/components/overlays/ConfirmDialog.vue.d.ts +8 -6
  93. package/dist/components/overlays/ConfirmHost.vue +5 -1
  94. package/dist/components/overlays/ConfirmHost.vue.d.ts +10 -8
  95. package/dist/components/overlays/ContextMenu.vue.d.ts +4 -4
  96. package/dist/components/overlays/Drawer.vue +4 -2
  97. package/dist/components/overlays/Drawer.vue.d.ts +8 -6
  98. package/dist/components/overlays/DropdownMenu.vue.d.ts +2 -2
  99. package/dist/components/overlays/HoverCard.vue +2 -1
  100. package/dist/components/overlays/HoverCard.vue.d.ts +6 -6
  101. package/dist/components/overlays/Modal.vue +5 -3
  102. package/dist/components/overlays/Modal.vue.d.ts +8 -6
  103. package/dist/components/overlays/Popover.vue +2 -1
  104. package/dist/components/overlays/Popover.vue.d.ts +6 -6
  105. package/dist/components/overlays/Toaster.vue.d.ts +4 -4
  106. package/dist/components/overlays/Tooltip.vue.d.ts +2 -2
  107. package/dist/components/overlays/types.d.ts +4 -1
  108. package/dist/components/templates/AccountMenu.vue.d.ts +6 -6
  109. package/dist/components/templates/AppearanceSettings.vue.d.ts +2 -2
  110. package/dist/components/templates/AuthLayout.vue.d.ts +2 -2
  111. package/dist/components/templates/Brand.vue +34 -5
  112. package/dist/components/templates/Brand.vue.d.ts +20 -15
  113. package/dist/components/templates/DashboardLayout.vue +40 -4
  114. package/dist/components/templates/DashboardLayout.vue.d.ts +34 -22
  115. package/dist/components/templates/DashboardPage.vue.d.ts +2 -2
  116. package/dist/components/templates/DetailPage.vue.d.ts +4 -4
  117. package/dist/components/templates/EmptyPage.vue.d.ts +2 -2
  118. package/dist/components/templates/ErrorPage.vue.d.ts +2 -2
  119. package/dist/components/templates/ListPage.vue +16 -10
  120. package/dist/components/templates/NotFoundPage.vue.d.ts +1 -1
  121. package/dist/components/templates/SaveBar.vue.d.ts +4 -4
  122. package/dist/components/templates/SettingsGroup.vue.d.ts +2 -2
  123. package/dist/components/templates/SettingsModal.vue +5 -2
  124. package/dist/components/templates/SettingsModal.vue.d.ts +13 -8
  125. package/dist/components/templates/SettingsPage.vue.d.ts +4 -4
  126. package/dist/components/templates/SettingsSection.vue.d.ts +12 -8
  127. package/dist/components/templates/types.d.ts +24 -1
  128. package/dist/composables/core.d.ts +1 -0
  129. package/dist/composables/core.mjs +1 -0
  130. package/dist/composables/useField.d.ts +2 -0
  131. package/dist/composables/useField.mjs +5 -2
  132. package/dist/composables/useFieldBoundary.d.ts +1 -0
  133. package/dist/composables/useFieldBoundary.mjs +5 -0
  134. package/dist/composables/useFileDrop.d.ts +7 -0
  135. package/dist/composables/useFileDrop.mjs +114 -7
  136. package/dist/composables/useForm.d.ts +32 -19
  137. package/dist/composables/useForm.mjs +212 -45
  138. package/dist/editor/RichTextEditor.vue +30 -3
  139. package/dist/editor/RichTextEditor.vue.d.ts +11 -6
  140. package/dist/editor/rich-text-editor.css +1 -1
  141. package/dist/editor/types.d.ts +3 -0
  142. package/dist/grid/GridToolbar.vue.d.ts +4 -4
  143. package/dist/grid/cells/BadgeCell.vue.d.ts +2 -2
  144. package/dist/grid/cells/BooleanCell.vue.d.ts +2 -2
  145. package/dist/grid/cells/DateCell.vue.d.ts +2 -2
  146. package/dist/grid/cells/ImageCell.vue.d.ts +2 -2
  147. package/dist/grid/cells/LinkCell.vue.d.ts +2 -2
  148. package/dist/internal/data/tree.d.ts +8 -0
  149. package/dist/internal/data/tree.mjs +41 -0
  150. package/dist/internal/forms/FormAlerts.vue +25 -0
  151. package/dist/internal/forms/FormAlerts.vue.d.ts +393 -0
  152. package/dist/internal/forms/files.d.ts +2 -0
  153. package/dist/internal/forms/files.mjs +8 -1
  154. package/dist/internal/forms/standard-schema.d.ts +17 -0
  155. package/dist/internal/forms/standard-schema.mjs +40 -0
  156. package/dist/internal/overlays/DialogFrame.vue +17 -7
  157. package/dist/internal/overlays/DialogFrame.vue.d.ts +9 -5
  158. package/dist/internal/overlays/FieldBoundary.d.ts +4 -0
  159. package/dist/internal/overlays/FieldBoundary.mjs +9 -0
  160. package/dist/internal/overlays/MenuList.vue +2 -0
  161. package/dist/internal/overlays/MenuList.vue.d.ts +5 -5
  162. package/dist/internal/overlays/OverlayView.vue +2 -0
  163. package/dist/internal/overlays/OverlayView.vue.d.ts +14 -12
  164. package/dist/internal/overlays/dialog.d.ts +4 -1
  165. package/dist/internal/overlays/dialog.mjs +39 -5
  166. package/dist/internal/overlays/stack.d.ts +2 -1
  167. package/dist/locale/en.mjs +17 -2
  168. package/dist/locale/it.mjs +17 -2
  169. package/dist/locale/types.d.ts +15 -0
  170. package/dist/nuxt/module.d.ts +5 -4
  171. package/dist/nuxt/module.mjs +9 -6
  172. package/dist/nuxt/runtime/layouts/auth.vue.d.ts +8 -8
  173. package/dist/nuxt/runtime/layouts/dashboard.vue.d.ts +8 -8
  174. package/dist/styles/base.css +7 -0
  175. package/dist/styles/core/field-message.css +34 -0
  176. package/dist/styles/core/form-field.css +0 -23
  177. package/dist/styles/core/form.css +11 -0
  178. package/dist/styles/core/kbd.css +2 -2
  179. package/dist/styles/core/spinner.css +19 -0
  180. package/dist/styles/core.css +1 -0
  181. package/dist/styles/data/badge.css +1 -1
  182. package/dist/styles/data/filter-pills.css +40 -3
  183. package/dist/styles/data/selection-bar.css +85 -0
  184. package/dist/styles/data/tree.css +156 -1
  185. package/dist/styles/data.css +1 -0
  186. package/dist/styles/forms/calendar.css +1 -1
  187. package/dist/styles/forms/file-input.css +8 -0
  188. package/dist/styles/forms/segmented.css +8 -0
  189. package/dist/styles/forms/select.css +1 -1
  190. package/dist/styles/forms/slider.css +2 -2
  191. package/dist/styles/layout/accordion.css +16 -0
  192. package/dist/styles/layout/app-shell.css +4 -0
  193. package/dist/styles/layout/breadcrumbs.css +16 -1
  194. package/dist/styles/layout/card.css +19 -1
  195. package/dist/styles/layout/nav-link.css +1 -1
  196. package/dist/styles/layout/sidebar.css +1 -1
  197. package/dist/styles/layout/tabs.css +43 -1
  198. package/dist/styles/overlays/command.css +20 -2
  199. package/dist/styles/overlays/menu.css +1 -1
  200. package/dist/styles/overlays/modal.css +38 -0
  201. package/dist/styles/templates/brand.css +15 -0
  202. package/dist/styles/templates/empty-page.css +1 -1
  203. package/dist/styles/templates/list-page.css +2 -6
  204. package/dist/styles/templates/settings-modal.css +2 -2
  205. package/package.json +1 -1
@@ -1,9 +1,9 @@
1
- import type { ComputedRef, Ref, UnwrapRef } from 'vue';
2
- import type { StandardSchemaV1 } from '../internal/forms/standard-schema';
3
- export type { StandardSchemaIssue, StandardSchemaResult, StandardSchemaV1, } from '../internal/forms/standard-schema';
1
+ import type { ComputedRef, MaybeRefOrGetter, Ref, UnwrapRef } from 'vue';
2
+ import type { IssueKeyResolver, StandardSchemaIssue, StandardSchemaV1 } from '../internal/forms/standard-schema';
3
+ export type { IssuePath as FormIssuePath, StandardSchemaIssue, StandardSchemaResult, StandardSchemaV1, } from '../internal/forms/standard-schema';
4
4
  export type FieldValidationResult = string | undefined | null | boolean;
5
5
  export interface FieldDefinition<T = unknown> {
6
- initial: T | (() => T);
6
+ initial?: T | (() => T);
7
7
  label?: string;
8
8
  required?: boolean;
9
9
  min?: number;
@@ -37,17 +37,27 @@ export interface FormMessages {
37
37
  invalid: (context: FormMessageContext) => string;
38
38
  }
39
39
  export type FormValidationTrigger = 'submit' | 'blur' | 'input';
40
- export interface UseFormOptions<S extends FormSchema> {
41
- schema: S;
42
- validator?: StandardSchemaV1;
40
+ export type FormIssueKey = IssueKeyResolver;
41
+ export type FormMessageMap = Record<string, string | undefined>;
42
+ export type FormWarningsResult = Record<string, string | undefined> | ReadonlyArray<StandardSchemaIssue> | null | undefined;
43
+ export type FormWarningsFunction<V> = (values: V) => FormWarningsResult | Promise<FormWarningsResult>;
44
+ export type FormWarnings<V> = StandardSchemaV1 | Readonly<Ref<StandardSchemaV1 | undefined>> | FormWarningsFunction<V>;
45
+ export type FormPath<V> = (keyof V & string) | (string & {});
46
+ export interface UseFormOptions<S extends FormSchema = FormSchema, V extends object = FormValues<S>> {
47
+ schema?: S;
48
+ values?: MaybeRefOrGetter<V>;
49
+ validator?: MaybeRefOrGetter<StandardSchemaV1 | undefined>;
50
+ warnings?: FormWarnings<V>;
51
+ issueKey?: FormIssueKey;
43
52
  messages?: Partial<FormMessages>;
44
53
  validateOn?: FormValidationTrigger;
45
- onSubmit?: (values: FormValues<S>) => unknown;
54
+ onSubmit?: (values: V) => unknown;
46
55
  onInvalid?: (errors: FormErrors<S>) => unknown;
47
56
  }
48
- export type FormErrors<S extends FormSchema> = Partial<Record<keyof S & string, string>>;
57
+ export type FormErrors<S extends FormSchema = FormSchema> = Partial<Record<keyof S & string, string>> & FormMessageMap;
49
58
  export interface FormContext {
50
- errors: Readonly<Record<string, string | undefined>>;
59
+ errors: Readonly<FormMessageMap>;
60
+ warnings?: Readonly<FormMessageMap>;
51
61
  submitCount: Readonly<Ref<number>>;
52
62
  submitting: Readonly<Ref<boolean>>;
53
63
  touch: (name: string) => void;
@@ -55,26 +65,29 @@ export interface FormContext {
55
65
  submit: () => Promise<boolean>;
56
66
  reset: () => void;
57
67
  }
58
- export interface UseFormReturn<S extends FormSchema> extends FormContext {
68
+ export interface UseFormReturn<S extends FormSchema = FormSchema, V extends object = FormValues<S>> extends FormContext {
59
69
  schema: S;
60
- values: UnwrapRef<FormValues<S>>;
70
+ values: UnwrapRef<V>;
61
71
  errors: FormErrors<S>;
62
- touched: Partial<Record<keyof S & string, boolean>>;
72
+ warnings: Readonly<FormMessageMap>;
73
+ rootErrors: Readonly<Ref<readonly string[]>>;
74
+ rootWarnings: Readonly<Ref<readonly string[]>>;
75
+ touched: Partial<Record<keyof V & string, boolean>> & Record<string, boolean | undefined>;
63
76
  dirty: ComputedRef<boolean>;
64
- dirtyFields: ComputedRef<(keyof S & string)[]>;
77
+ dirtyFields: ComputedRef<(keyof V & string)[]>;
65
78
  valid: ComputedRef<boolean>;
66
79
  submitCount: Ref<number>;
67
80
  submitting: Ref<boolean>;
68
- isDirty: (name: keyof S & string) => boolean;
81
+ isDirty: (name: keyof V & string) => boolean;
69
82
  validate: () => Promise<boolean>;
70
- validateField: (name: keyof S & string) => Promise<boolean>;
71
- setError: (name: keyof S & string, message: string | undefined) => void;
83
+ validateField: (name: keyof V & string) => Promise<boolean>;
84
+ setError: (name: FormPath<V>, message: string | undefined) => void;
72
85
  setErrors: (errors: FormErrors<S>) => void;
73
86
  clearErrors: () => void;
74
87
  touch: (name: string) => void;
75
- reset: (values?: Partial<FormValues<S>>) => void;
88
+ reset: (values?: Partial<V>) => void;
76
89
  submit: () => Promise<boolean>;
77
90
  }
78
91
  export declare function isEmptyValue(value: unknown): boolean;
79
- export declare function useForm<S extends FormSchema>(options: UseFormOptions<S>): UseFormReturn<S>;
92
+ export declare function useForm<S extends FormSchema = FormSchema, V extends object = FormValues<S>>(options: UseFormOptions<S, V>): UseFormReturn<S, V>;
80
93
  export declare function useFormContext(): FormContext | null;
@@ -1,5 +1,23 @@
1
- import { computed, inject, reactive, ref, watch } from "vue";
2
- import { issuesByField, runStandardSchema } from "../internal/forms/standard-schema.mjs";
1
+ import {
2
+ computed,
3
+ inject,
4
+ isRef,
5
+ reactive,
6
+ readonly,
7
+ ref,
8
+ shallowRef,
9
+ toValue,
10
+ watch,
11
+ watchEffect
12
+ } from "vue";
13
+ import {
14
+ defaultIssueKey,
15
+ groupIssues,
16
+ isStandardSchema,
17
+ keyPath,
18
+ runStandardSchema,
19
+ schemaIssues
20
+ } from "../internal/forms/standard-schema.mjs";
3
21
  import { FORM_KEY } from "../internal/keys.mjs";
4
22
  import { useLocale } from "./useLocale.mjs";
5
23
  function localeFormMessages(t) {
@@ -54,35 +72,88 @@ function measure(value) {
54
72
  if (typeof value === "string" || Array.isArray(value)) return value.length;
55
73
  return void 0;
56
74
  }
75
+ function isThenable(value) {
76
+ return typeof value?.then === "function";
77
+ }
78
+ function settle(value, apply) {
79
+ if (isThenable(value)) void value.then(apply);
80
+ else apply(value);
81
+ }
82
+ function isWithin(key, name) {
83
+ return key === name || key.startsWith(`${name}.`);
84
+ }
85
+ function uniqueMessages(...lists) {
86
+ return [...new Set(lists.flat().filter(Boolean))];
87
+ }
88
+ function replaceEntries(target, next, inScope = () => true) {
89
+ for (const key of Object.keys(target)) {
90
+ if (inScope(key) && !(key in next)) delete target[key];
91
+ }
92
+ for (const [key, message] of Object.entries(next)) {
93
+ if (inScope(key) && target[key] !== message) target[key] = message;
94
+ }
95
+ }
57
96
  export function useForm(options) {
58
- const { schema } = options;
59
- const names = Object.keys(schema);
97
+ const schema = options.schema ?? {};
98
+ const schemaNames = Object.keys(schema);
99
+ const external = options.values !== void 0;
60
100
  const { t } = useLocale();
61
101
  const messages = computed(() => ({ ...localeFormMessages(t), ...options.messages }));
62
102
  const validateOn = options.validateOn ?? "submit";
63
- const initialValues = () => Object.fromEntries(names.map((name) => [name, resolveInitial(schema[name])]));
64
- let baseline = initialValues();
65
- const values = reactive(cloneValue(baseline));
103
+ const issueKey = options.issueKey ?? defaultIssueKey;
104
+ const initialValues = () => Object.fromEntries(schemaNames.map((name) => [name, resolveInitial(schema[name])]));
105
+ const internalValues = external ? void 0 : reactive(initialValues());
106
+ const target = () => external ? toValue(options.values) : internalValues;
107
+ let baseline = cloneValue(target());
108
+ const baselineVersion = ref(0);
109
+ const names = computed(() => {
110
+ if (!external) return schemaNames;
111
+ return [.../* @__PURE__ */ new Set([...schemaNames, ...Object.keys(target())])];
112
+ });
66
113
  const errors = reactive({});
114
+ const warnings = reactive({});
115
+ const rootErrors = shallowRef([]);
116
+ const rootWarnings = shallowRef([]);
67
117
  const touched = reactive({});
68
118
  const submitCount = ref(0);
69
119
  const submitting = ref(false);
70
- const baselineVersion = ref(0);
71
- const readValue = (name) => values[name];
120
+ const ruleErrors = {};
121
+ const manualErrors = {};
122
+ let issueErrors = {};
123
+ let issueRoot = [];
124
+ let manualRoot = [];
125
+ const readValue = (name) => target()[name];
72
126
  const dirtyFields = computed(() => {
73
127
  void baselineVersion.value;
74
- return names.filter(
75
- (name) => !isEqualValue(readValue(name), baseline[name])
76
- );
128
+ const keys = external ? [.../* @__PURE__ */ new Set([...names.value, ...Object.keys(baseline)])] : names.value;
129
+ return keys.filter((name) => !isEqualValue(readValue(name), baseline[name]));
77
130
  });
78
131
  const dirty = computed(() => dirtyFields.value.length > 0);
79
- const valid = computed(() => names.every((name) => !errors[name]));
132
+ const valid = computed(
133
+ () => !Object.values(errors).some(Boolean) && rootErrors.value.length === 0
134
+ );
135
+ function composeErrors() {
136
+ const next = {};
137
+ for (const source of [manualErrors, ruleErrors, issueErrors]) {
138
+ for (const [key, message] of Object.entries(source)) {
139
+ if (message && !(key in next)) next[key] = message;
140
+ }
141
+ }
142
+ return next;
143
+ }
144
+ function syncErrors(inScope) {
145
+ replaceEntries(errors, composeErrors(), inScope);
146
+ if (!inScope) rootErrors.value = uniqueMessages(manualRoot, issueRoot);
147
+ }
80
148
  async function runValidator() {
81
- if (!options.validator) return {};
82
- return issuesByField(await runStandardSchema(options.validator, cloneValue(values)));
149
+ const validator = toValue(options.validator);
150
+ if (!validator) return groupIssues([]);
151
+ const issues = await runStandardSchema(validator, cloneValue(target()));
152
+ return groupIssues(schemaIssues(issues), issueKey);
83
153
  }
84
- async function runRules(name, formIssues) {
154
+ async function runRules(name) {
85
155
  const definition = schema[name];
156
+ if (!definition) return void 0;
86
157
  const value = readValue(name);
87
158
  const fieldMessages = { ...messages.value, ...definition.messages };
88
159
  const context = {
@@ -115,7 +186,7 @@ export function useForm(options) {
115
186
  }
116
187
  }
117
188
  if (definition.validate) {
118
- const result = await definition.validate(value, values);
189
+ const result = await definition.validate(value, target());
119
190
  if (typeof result === "string" && result) return result;
120
191
  if (result === false) return fieldMessages.invalid(context);
121
192
  }
@@ -123,44 +194,108 @@ export function useForm(options) {
123
194
  const issues = await runStandardSchema(definition.schema, value);
124
195
  if (issues.length) return issues[0].message || fieldMessages.invalid(context);
125
196
  }
126
- return formIssues[name];
197
+ return void 0;
127
198
  }
128
- async function applyField(name, formIssues) {
129
- const message = await runRules(name, formIssues);
130
- errors[name] = message;
131
- return !message;
199
+ function hasErrorWithin(name) {
200
+ return Object.entries(errors).some(([key, message]) => message && isWithin(key, name));
132
201
  }
202
+ let validationRun = 0;
203
+ const fieldRuns = /* @__PURE__ */ new Map();
133
204
  async function validateField(name) {
134
- return applyField(name, await runValidator());
205
+ const run = (fieldRuns.get(name) ?? 0) + 1;
206
+ fieldRuns.set(name, run);
207
+ const [issues, message] = await Promise.all([runValidator(), runRules(name)]);
208
+ if (fieldRuns.get(name) !== run) return !message && !(name in issues.fields);
209
+ issueErrors = issues.fields;
210
+ issueRoot = issues.root;
211
+ ruleErrors[name] = message;
212
+ for (const key of Object.keys(manualErrors)) {
213
+ if (isWithin(key, name)) delete manualErrors[key];
214
+ }
215
+ syncErrors(submitCount.value > 0 ? void 0 : (key) => isWithin(key, name));
216
+ return !hasErrorWithin(name);
135
217
  }
136
218
  async function validate() {
137
- const formIssues = await runValidator();
138
- const results = await Promise.all(names.map((name) => applyField(name, formIssues)));
139
- return results.every(Boolean);
219
+ const run = ++validationRun;
220
+ const fields = names.value;
221
+ const [issues, results] = await Promise.all([
222
+ runValidator(),
223
+ Promise.all(fields.map((name) => runRules(name)))
224
+ ]);
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();
233
+ }
234
+ return results.every((message) => !message) && Object.keys(issues.fields).length === 0 && issues.root.length === 0;
140
235
  }
141
236
  function setError(name, message) {
142
- errors[name] = message;
237
+ if (name === "") {
238
+ manualRoot = message ? [message] : [];
239
+ rootErrors.value = uniqueMessages(manualRoot, issueRoot);
240
+ return;
241
+ }
242
+ manualErrors[name] = message;
243
+ if (!message) {
244
+ delete ruleErrors[name];
245
+ delete issueErrors[name];
246
+ }
247
+ syncErrors((key) => key === name);
143
248
  }
144
249
  function setErrors(next) {
145
- for (const name of names) errors[name] = next[name];
250
+ for (const key of Object.keys(manualErrors)) delete manualErrors[key];
251
+ for (const key of Object.keys(ruleErrors)) delete ruleErrors[key];
252
+ issueErrors = {};
253
+ for (const [key, message] of Object.entries(next)) {
254
+ if (key === "") manualRoot = message ? [message] : [];
255
+ else manualErrors[key] = message;
256
+ }
257
+ syncErrors();
146
258
  }
147
259
  function clearErrors() {
148
- for (const name of names) errors[name] = void 0;
260
+ for (const key of Object.keys(manualErrors)) delete manualErrors[key];
261
+ for (const key of Object.keys(ruleErrors)) delete ruleErrors[key];
262
+ issueErrors = {};
263
+ issueRoot = [];
264
+ manualRoot = [];
265
+ syncErrors();
149
266
  }
150
267
  function touch(name) {
151
- if (!(name in schema)) return;
268
+ const field = name.split(".")[0];
269
+ if (!names.value.includes(field)) return;
152
270
  touched[name] = true;
153
- if (validateOn === "blur" || submitCount.value > 0) void validateField(name);
271
+ touched[field] = true;
272
+ if (validateOn === "blur" || submitCount.value > 0) void validateField(field);
154
273
  }
274
+ const snapshots = /* @__PURE__ */ new Map();
275
+ const takeSnapshots = () => {
276
+ snapshots.clear();
277
+ for (const name of names.value) snapshots.set(name, cloneValue(readValue(name)));
278
+ };
279
+ takeSnapshots();
155
280
  function reset(next) {
156
- const fresh = initialValues();
157
- if (next) Object.assign(fresh, cloneValue(next));
158
- baseline = fresh;
281
+ const values = target();
282
+ if (external) {
283
+ const fresh = cloneValue(next ?? baseline);
284
+ for (const key of Object.keys(values)) {
285
+ if (!(key in fresh)) delete values[key];
286
+ }
287
+ Object.assign(values, fresh);
288
+ baseline = cloneValue(fresh);
289
+ } else {
290
+ const fresh = initialValues();
291
+ if (next) Object.assign(fresh, cloneValue(next));
292
+ baseline = fresh;
293
+ Object.assign(values, cloneValue(fresh));
294
+ }
159
295
  baselineVersion.value++;
160
- Object.assign(values, cloneValue(fresh));
161
- for (const name of names) snapshots.set(name, cloneValue(readValue(name)));
296
+ takeSnapshots();
162
297
  clearErrors();
163
- for (const name of names) touched[name] = false;
298
+ for (const key of Object.keys(touched)) touched[key] = false;
164
299
  submitCount.value = 0;
165
300
  }
166
301
  async function submit() {
@@ -173,21 +308,18 @@ export function useForm(options) {
173
308
  }
174
309
  submitting.value = true;
175
310
  try {
176
- await options.onSubmit?.(cloneValue(values));
311
+ await options.onSubmit?.(cloneValue(target()));
177
312
  } finally {
178
313
  submitting.value = false;
179
314
  }
180
315
  return true;
181
316
  }
182
- const snapshots = new Map(
183
- names.map((name) => [name, cloneValue(readValue(name))])
184
- );
185
317
  watch(
186
- () => names.map((name) => readValue(name)),
318
+ () => names.value.map((name) => readValue(name)),
187
319
  () => {
188
- for (const name of names) {
320
+ for (const name of names.value) {
189
321
  const current = readValue(name);
190
- if (isEqualValue(current, snapshots.get(name))) continue;
322
+ if (snapshots.has(name) && isEqualValue(current, snapshots.get(name))) continue;
191
323
  snapshots.set(name, cloneValue(current));
192
324
  if (validateOn === "input" || submitCount.value > 0 || validateOn === "blur" && touched[name])
193
325
  void validateField(name);
@@ -195,10 +327,45 @@ export function useForm(options) {
195
327
  },
196
328
  { deep: true }
197
329
  );
330
+ watch(
331
+ () => toValue(options.validator),
332
+ () => {
333
+ if (submitCount.value > 0) void validate();
334
+ }
335
+ );
336
+ const warningSource = options.warnings;
337
+ if (warningSource) {
338
+ let warningRun = 0;
339
+ const applyWarnings = (result) => {
340
+ const list = Array.isArray(result) ? schemaIssues(result) : Object.entries(result ?? {}).filter((entry) => Boolean(entry[1])).map(([key, message]) => ({ message, path: keyPath(key) }));
341
+ const grouped = groupIssues(list, issueKey);
342
+ replaceEntries(warnings, grouped.fields);
343
+ rootWarnings.value = grouped.root;
344
+ };
345
+ const evaluate = () => {
346
+ if (typeof warningSource === "function") return warningSource(target());
347
+ const schemaSource = isRef(warningSource) ? warningSource.value : warningSource;
348
+ if (!schemaSource || !isStandardSchema(schemaSource)) return void 0;
349
+ const result = schemaSource["~standard"].validate(cloneValue(target()));
350
+ const issuesOf = (outcome) => outcome.issues ?? [];
351
+ return isThenable(result) ? result.then(issuesOf) : issuesOf(result);
352
+ };
353
+ watchEffect(() => {
354
+ const run = ++warningRun;
355
+ settle(evaluate(), (result) => {
356
+ if (run === warningRun) applyWarnings(result);
357
+ });
358
+ });
359
+ }
198
360
  return {
199
361
  schema,
200
- values,
362
+ get values() {
363
+ return target();
364
+ },
201
365
  errors,
366
+ warnings: readonly(warnings),
367
+ rootErrors,
368
+ rootWarnings,
202
369
  touched,
203
370
  dirty,
204
371
  dirtyFields,
@@ -117,6 +117,8 @@ const props = defineProps({
117
117
  maxHeight: { type: String, required: false },
118
118
  label: { type: String, required: false },
119
119
  headings: { type: Array, required: false, default: () => [2, 3, 4] },
120
+ headingLabel: { type: String, required: false, default: "level" },
121
+ toolbar: { type: Array, required: false },
120
122
  extensions: { type: Array, required: false, default: () => [] },
121
123
  messages: { type: Object, required: false }
122
124
  });
@@ -207,7 +209,8 @@ watch(
207
209
  () => editor.value?.setOptions({ editorProps: { attributes: contentAttributes() } })
208
210
  );
209
211
  onBeforeUnmount(() => editor.value?.destroy());
210
- function heading(level, rank) {
212
+ function heading(level, index) {
213
+ const rank = props.headingLabel === "relative" ? index + 1 : level;
211
214
  return {
212
215
  id: `h${level}`,
213
216
  label: formatLocaleMessage(locale.value.code, text.value.heading, { level: rank }),
@@ -227,7 +230,7 @@ function mark(id, label, glyph, glyphClass, name, toggle) {
227
230
  run: toggle
228
231
  };
229
232
  }
230
- const groups = computed(() => [
233
+ const defaultToolbar = computed(() => [
231
234
  [
232
235
  {
233
236
  id: "undo",
@@ -246,7 +249,7 @@ const groups = computed(() => [
246
249
  run: () => editor.value?.chain().focus().redo().run()
247
250
  }
248
251
  ],
249
- props.headings.map((level, index) => heading(level, index + 1)),
252
+ props.headings.map((level, index) => heading(level, index)),
250
253
  [
251
254
  mark(
252
255
  "bold",
@@ -314,6 +317,30 @@ const groups = computed(() => [
314
317
  }
315
318
  ]
316
319
  ]);
320
+ function isToolbarGroups(toolbar) {
321
+ return toolbar.some((entry) => Array.isArray(entry));
322
+ }
323
+ function splitToolbar(toolbar) {
324
+ if (isToolbarGroups(toolbar)) return toolbar;
325
+ const result = [[]];
326
+ for (const entry of toolbar) {
327
+ if (entry === "|") result.push([]);
328
+ else result[result.length - 1].push(entry);
329
+ }
330
+ return result;
331
+ }
332
+ const groups = computed(() => {
333
+ if (!props.toolbar) return defaultToolbar.value.filter((group) => group.length);
334
+ const byId = new Map(defaultToolbar.value.flat().map((item) => [item.id, item]));
335
+ const headingItems = defaultToolbar.value[1] ?? [];
336
+ return splitToolbar(props.toolbar).map(
337
+ (group) => group.flatMap((entry) => {
338
+ if (entry === "headings") return headingItems;
339
+ const item = byId.get(entry);
340
+ return item ? [item] : [];
341
+ })
342
+ ).filter((group) => group.length);
343
+ });
317
344
  function itemAttributes(item) {
318
345
  const disabled = item.enabled ? !item.enabled() : false;
319
346
  return {
@@ -1,6 +1,6 @@
1
1
  import type { Editor } from '@tiptap/vue-3';
2
2
  import type { Extensions } from '@tiptap/vue-3';
3
- import type { EditorFormat, EditorHeadingLevel, EditorMessages, EditorValue } from './types';
3
+ import type { EditorFormat, EditorHeadingLabel, EditorHeadingLevel, EditorMessages, EditorToolbar, EditorValue } from './types';
4
4
  declare const __VLS_export: import("vue").DefineComponent<{
5
5
  format?: EditorFormat;
6
6
  placeholder?: string | undefined;
@@ -14,6 +14,8 @@ declare const __VLS_export: import("vue").DefineComponent<{
14
14
  maxHeight?: string;
15
15
  label?: string;
16
16
  headings?: EditorHeadingLevel[];
17
+ headingLabel?: EditorHeadingLabel;
18
+ toolbar?: EditorToolbar;
17
19
  extensions?: Extensions;
18
20
  messages?: Partial<EditorMessages>;
19
21
  modelValue?: EditorValue;
@@ -22,7 +24,6 @@ declare const __VLS_export: import("vue").DefineComponent<{
22
24
  title?: string | undefined | undefined;
23
25
  style?: import("vue").StyleValue;
24
26
  results?: (string | number) | undefined;
25
- ref?: import("vue").VNodeRef | undefined;
26
27
  class?: import("vue").ClassValue;
27
28
  role?: string | undefined | undefined;
28
29
  'aria-label'?: string | undefined | undefined;
@@ -135,7 +136,7 @@ declare const __VLS_export: import("vue").DefineComponent<{
135
136
  'aria-colindex'?: (string | number) | undefined;
136
137
  'aria-colspan'?: (string | number) | undefined;
137
138
  'aria-controls'?: string | undefined | undefined;
138
- 'aria-current'?: "page" | "location" | "time" | (boolean | "true" | "false") | "step" | "date" | undefined;
139
+ 'aria-current'?: "page" | "time" | (boolean | "true" | "false") | "step" | "location" | "date" | undefined;
139
140
  'aria-describedby'?: string | undefined | undefined;
140
141
  'aria-details'?: string | undefined | undefined;
141
142
  'aria-disabled'?: (boolean | "true" | "false") | undefined;
@@ -172,6 +173,7 @@ declare const __VLS_export: import("vue").DefineComponent<{
172
173
  'aria-valuemin'?: (string | number) | undefined;
173
174
  'aria-valuenow'?: (string | number) | undefined;
174
175
  'aria-valuetext'?: string | undefined | undefined;
176
+ ref?: import("vue").VNodeRef | undefined;
175
177
  ref_for?: boolean | undefined | undefined;
176
178
  ref_key?: string | undefined | undefined;
177
179
  accesskey?: string | undefined | undefined;
@@ -226,6 +228,8 @@ declare const __VLS_export: import("vue").DefineComponent<{
226
228
  maxHeight?: string;
227
229
  label?: string;
228
230
  headings?: EditorHeadingLevel[];
231
+ headingLabel?: EditorHeadingLabel;
232
+ toolbar?: EditorToolbar;
229
233
  extensions?: Extensions;
230
234
  messages?: Partial<EditorMessages>;
231
235
  modelValue?: EditorValue;
@@ -234,7 +238,6 @@ declare const __VLS_export: import("vue").DefineComponent<{
234
238
  title?: string | undefined | undefined;
235
239
  style?: import("vue").StyleValue;
236
240
  results?: (string | number) | undefined;
237
- ref?: import("vue").VNodeRef | undefined;
238
241
  class?: import("vue").ClassValue;
239
242
  role?: string | undefined | undefined;
240
243
  'aria-label'?: string | undefined | undefined;
@@ -347,7 +350,7 @@ declare const __VLS_export: import("vue").DefineComponent<{
347
350
  'aria-colindex'?: (string | number) | undefined;
348
351
  'aria-colspan'?: (string | number) | undefined;
349
352
  'aria-controls'?: string | undefined | undefined;
350
- 'aria-current'?: "page" | "location" | "time" | (boolean | "true" | "false") | "step" | "date" | undefined;
353
+ 'aria-current'?: "page" | "time" | (boolean | "true" | "false") | "step" | "location" | "date" | undefined;
351
354
  'aria-describedby'?: string | undefined | undefined;
352
355
  'aria-details'?: string | undefined | undefined;
353
356
  'aria-disabled'?: (boolean | "true" | "false") | undefined;
@@ -384,6 +387,7 @@ declare const __VLS_export: import("vue").DefineComponent<{
384
387
  'aria-valuemin'?: (string | number) | undefined;
385
388
  'aria-valuenow'?: (string | number) | undefined;
386
389
  'aria-valuetext'?: string | undefined | undefined;
390
+ ref?: import("vue").VNodeRef | undefined;
387
391
  ref_for?: boolean | undefined | undefined;
388
392
  ref_key?: string | undefined | undefined;
389
393
  accesskey?: string | undefined | undefined;
@@ -425,8 +429,9 @@ declare const __VLS_export: import("vue").DefineComponent<{
425
429
  }>, {
426
430
  invalid: boolean;
427
431
  format: EditorFormat;
428
- minHeight: string;
429
432
  headings: EditorHeadingLevel[];
433
+ minHeight: string;
434
+ headingLabel: EditorHeadingLabel;
430
435
  extensions: Extensions;
431
436
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
432
437
  declare const _default: typeof __VLS_export;
@@ -95,7 +95,7 @@
95
95
 
96
96
  .k-editor-glyph-text.is-mono {
97
97
  font-family: var(--k-font-mono);
98
- font-size: 0.6875rem;
98
+ font-size: 0.75rem;
99
99
  }
100
100
 
101
101
  .k-editor-glyph-text.is-tabular {
@@ -4,3 +4,6 @@ export type EditorFormat = 'html' | 'json';
4
4
  export type EditorValue = string | JSONContent | null;
5
5
  export type EditorMessages = KMessages['editor'];
6
6
  export type EditorHeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
7
+ export type EditorHeadingLabel = 'level' | 'relative';
8
+ export type EditorToolbarItem = 'undo' | 'redo' | 'headings' | `h${EditorHeadingLevel}` | 'bold' | 'italic' | 'strike' | 'code' | 'bulletList' | 'orderedList' | 'blockquote' | 'link' | '|';
9
+ export type EditorToolbar = EditorToolbarItem[] | EditorToolbarItem[][];
@@ -18,7 +18,6 @@ declare const __VLS_base: import("vue").DefineComponent<{
18
18
  style?: import("vue").StyleValue;
19
19
  results?: (string | number) | undefined;
20
20
  placeholder?: string | undefined | undefined;
21
- ref?: import("vue").VNodeRef | undefined;
22
21
  class?: import("vue").ClassValue;
23
22
  role?: string | undefined | undefined;
24
23
  'aria-label'?: string | undefined | undefined;
@@ -131,7 +130,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
131
130
  'aria-colindex'?: (string | number) | undefined;
132
131
  'aria-colspan'?: (string | number) | undefined;
133
132
  'aria-controls'?: string | undefined | undefined;
134
- 'aria-current'?: "page" | "location" | "time" | (boolean | "true" | "false") | "step" | "date" | undefined;
133
+ 'aria-current'?: "page" | "time" | (boolean | "true" | "false") | "step" | "location" | "date" | undefined;
135
134
  'aria-describedby'?: string | undefined | undefined;
136
135
  'aria-details'?: string | undefined | undefined;
137
136
  'aria-disabled'?: (boolean | "true" | "false") | undefined;
@@ -168,6 +167,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
168
167
  'aria-valuemin'?: (string | number) | undefined;
169
168
  'aria-valuenow'?: (string | number) | undefined;
170
169
  'aria-valuetext'?: string | undefined | undefined;
170
+ ref?: import("vue").VNodeRef | undefined;
171
171
  ref_for?: boolean | undefined | undefined;
172
172
  ref_key?: string | undefined | undefined;
173
173
  accesskey?: string | undefined | undefined;
@@ -222,7 +222,6 @@ declare const __VLS_base: import("vue").DefineComponent<{
222
222
  style?: import("vue").StyleValue;
223
223
  results?: (string | number) | undefined;
224
224
  placeholder?: string | undefined | undefined;
225
- ref?: import("vue").VNodeRef | undefined;
226
225
  class?: import("vue").ClassValue;
227
226
  role?: string | undefined | undefined;
228
227
  'aria-label'?: string | undefined | undefined;
@@ -335,7 +334,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
335
334
  'aria-colindex'?: (string | number) | undefined;
336
335
  'aria-colspan'?: (string | number) | undefined;
337
336
  'aria-controls'?: string | undefined | undefined;
338
- 'aria-current'?: "page" | "location" | "time" | (boolean | "true" | "false") | "step" | "date" | undefined;
337
+ 'aria-current'?: "page" | "time" | (boolean | "true" | "false") | "step" | "location" | "date" | undefined;
339
338
  'aria-describedby'?: string | undefined | undefined;
340
339
  'aria-details'?: string | undefined | undefined;
341
340
  'aria-disabled'?: (boolean | "true" | "false") | undefined;
@@ -372,6 +371,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
372
371
  'aria-valuemin'?: (string | number) | undefined;
373
372
  'aria-valuenow'?: (string | number) | undefined;
374
373
  'aria-valuetext'?: string | undefined | undefined;
374
+ ref?: import("vue").VNodeRef | undefined;
375
375
  ref_for?: boolean | undefined | undefined;
376
376
  ref_key?: string | undefined | undefined;
377
377
  accesskey?: string | undefined | undefined;
@@ -56,7 +56,7 @@ declare const __VLS_export: import("vue").DefineComponent<{
56
56
  'aria-colindex'?: (string | number) | undefined;
57
57
  'aria-colspan'?: (string | number) | undefined;
58
58
  'aria-controls'?: string | undefined | undefined;
59
- 'aria-current'?: "page" | "location" | "time" | (boolean | "true" | "false") | "step" | "date" | undefined;
59
+ 'aria-current'?: "page" | "time" | (boolean | "true" | "false") | "step" | "location" | "date" | undefined;
60
60
  'aria-describedby'?: string | undefined | undefined;
61
61
  'aria-details'?: string | undefined | undefined;
62
62
  'aria-disabled'?: (boolean | "true" | "false") | undefined;
@@ -250,7 +250,7 @@ declare const __VLS_export: import("vue").DefineComponent<{
250
250
  'aria-colindex'?: (string | number) | undefined;
251
251
  'aria-colspan'?: (string | number) | undefined;
252
252
  'aria-controls'?: string | undefined | undefined;
253
- 'aria-current'?: "page" | "location" | "time" | (boolean | "true" | "false") | "step" | "date" | undefined;
253
+ 'aria-current'?: "page" | "time" | (boolean | "true" | "false") | "step" | "location" | "date" | undefined;
254
254
  'aria-describedby'?: string | undefined | undefined;
255
255
  'aria-details'?: string | undefined | undefined;
256
256
  'aria-disabled'?: (boolean | "true" | "false") | undefined;