@sfxcode/nuxt-ui-formkit 1.2.3 → 1.2.4

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 (32) hide show
  1. package/README.md +1 -0
  2. package/dist/module.json +1 -1
  3. package/dist/runtime/components/FUAutoFormOverlay.d.vue.ts +82 -0
  4. package/dist/runtime/components/FUAutoFormOverlay.vue +106 -0
  5. package/dist/runtime/components/FUAutoFormOverlay.vue.d.ts +82 -0
  6. package/dist/runtime/components/FUDataEdit.vue +2 -1
  7. package/dist/runtime/components/FUDataEditOverlay.d.vue.ts +64 -0
  8. package/dist/runtime/components/FUDataEditOverlay.vue +94 -0
  9. package/dist/runtime/components/FUDataEditOverlay.vue.d.ts +64 -0
  10. package/dist/runtime/components/inputs/FUCalendar.vue +3 -0
  11. package/dist/runtime/components/inputs/FUCheckbox.vue +2 -1
  12. package/dist/runtime/components/inputs/FUCheckboxGroup.vue +3 -0
  13. package/dist/runtime/components/inputs/FUEditor.vue +58 -54
  14. package/dist/runtime/components/inputs/FUFileUpload.vue +2 -1
  15. package/dist/runtime/components/inputs/FUInput.vue +2 -1
  16. package/dist/runtime/components/inputs/FUInputDate.vue +5 -1
  17. package/dist/runtime/components/inputs/FUInputMenu.vue +2 -1
  18. package/dist/runtime/components/inputs/FUInputNumber.vue +2 -1
  19. package/dist/runtime/components/inputs/FUInputTags.vue +2 -1
  20. package/dist/runtime/components/inputs/FUInputTime.vue +5 -1
  21. package/dist/runtime/components/inputs/FUListbox.vue +4 -0
  22. package/dist/runtime/components/inputs/FUPinInput.vue +8 -1
  23. package/dist/runtime/components/inputs/FURadioGroup.vue +3 -0
  24. package/dist/runtime/components/inputs/FUSwitch.vue +2 -1
  25. package/dist/runtime/components/inputs/FUTextarea.vue +2 -1
  26. package/dist/runtime/composables/useFormKitOverlay.d.ts +20 -0
  27. package/dist/runtime/composables/useFormKitOverlay.js +42 -0
  28. package/dist/runtime/utils/useFormKitContainerBlur.d.ts +37 -0
  29. package/dist/runtime/utils/useFormKitContainerBlur.js +9 -0
  30. package/dist/runtime/utils/useOverlayCloseResolution.d.ts +20 -0
  31. package/dist/runtime/utils/useOverlayCloseResolution.js +17 -0
  32. package/package.json +1 -1
package/README.md CHANGED
@@ -75,6 +75,7 @@ FormKit Nuxt UI bridges the gap between [FormKit](https://formkit.com/)'s powerf
75
75
  - `useFormKitMultiStep` - Multi-step tab-item mapping and navigation bridging
76
76
  - `useFormKitForm` - Submit/reset/error-management wrapper for a form's imperative APIs, callable from outside the form
77
77
  - `useFormKitAutoForm` - Schema inference from data value shapes, Valibot, or Zod schemas (`inferFormSchema`/`inferFormSchemaFromValibot`/`inferFormSchemaFromZod`)
78
+ - `useFormKitOverlay` - Promise-based modal/slideover forms: `await overlay.edit({ data, schema, title })` (or `.auto(...)` for a schema-free version), resolving to the saved data or `null` on cancel
78
79
  - `colorConverter` - Color format conversion utilities
79
80
  - `durationConverter` - Duration format conversion utilities
80
81
 
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=4.0.0"
6
6
  },
7
- "version": "1.2.3",
7
+ "version": "1.2.4",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
@@ -0,0 +1,82 @@
1
+ import type { PropType } from 'vue';
2
+ import type { AutoFormOverrides, ValibotLikeSchema, ZodLikeSchema } from '../composables/useFormKitAutoForm.js';
3
+ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
4
+ id: {
5
+ type: StringConstructor;
6
+ required: true;
7
+ };
8
+ data: {
9
+ type: ObjectConstructor;
10
+ default: null;
11
+ };
12
+ valibotSchema: {
13
+ type: PropType<ValibotLikeSchema>;
14
+ default: undefined;
15
+ };
16
+ zodSchema: {
17
+ type: PropType<ZodLikeSchema>;
18
+ default: undefined;
19
+ };
20
+ overrides: {
21
+ type: PropType<AutoFormOverrides>;
22
+ default: undefined;
23
+ };
24
+ title: {
25
+ type: StringConstructor;
26
+ default: undefined;
27
+ };
28
+ as: {
29
+ type: PropType<"modal" | "slideover">;
30
+ default: string;
31
+ };
32
+ open: {
33
+ type: PropType<boolean>;
34
+ };
35
+ }>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
36
+ close: (value: unknown) => any;
37
+ "update:open": (value: boolean) => any;
38
+ }, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
39
+ id: {
40
+ type: StringConstructor;
41
+ required: true;
42
+ };
43
+ data: {
44
+ type: ObjectConstructor;
45
+ default: null;
46
+ };
47
+ valibotSchema: {
48
+ type: PropType<ValibotLikeSchema>;
49
+ default: undefined;
50
+ };
51
+ zodSchema: {
52
+ type: PropType<ZodLikeSchema>;
53
+ default: undefined;
54
+ };
55
+ overrides: {
56
+ type: PropType<AutoFormOverrides>;
57
+ default: undefined;
58
+ };
59
+ title: {
60
+ type: StringConstructor;
61
+ default: undefined;
62
+ };
63
+ as: {
64
+ type: PropType<"modal" | "slideover">;
65
+ default: string;
66
+ };
67
+ open: {
68
+ type: PropType<boolean>;
69
+ };
70
+ }>> & Readonly<{
71
+ onClose?: ((value: unknown) => any) | undefined;
72
+ "onUpdate:open"?: ((value: boolean) => any) | undefined;
73
+ }>, {
74
+ data: Record<string, any>;
75
+ title: string;
76
+ overrides: AutoFormOverrides;
77
+ valibotSchema: ValibotLikeSchema;
78
+ zodSchema: ZodLikeSchema;
79
+ as: "modal" | "slideover";
80
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
81
+ declare const _default: typeof __VLS_export;
82
+ export default _default;
@@ -0,0 +1,106 @@
1
+ <script setup>
2
+ import { useOverlayCloseResolution } from "../utils/useOverlayCloseResolution";
3
+ import FUAutoForm from "./FUAutoForm.vue";
4
+ defineProps({
5
+ id: {
6
+ type: String,
7
+ required: true
8
+ },
9
+ data: {
10
+ type: Object,
11
+ default: null
12
+ },
13
+ valibotSchema: {
14
+ type: Object,
15
+ default: void 0
16
+ },
17
+ zodSchema: {
18
+ type: Object,
19
+ default: void 0
20
+ },
21
+ overrides: {
22
+ type: Object,
23
+ default: void 0
24
+ },
25
+ title: {
26
+ type: String,
27
+ default: void 0
28
+ },
29
+ as: {
30
+ type: String,
31
+ default: "modal"
32
+ }
33
+ });
34
+ const emit = defineEmits(["close"]);
35
+ const open = defineModel("open", { type: Boolean, ...{ default: false } });
36
+ const { resolveOnce } = useOverlayCloseResolution(open, emit);
37
+ function handleSaved(savedData) {
38
+ resolveOnce(savedData);
39
+ }
40
+ function handleCancel() {
41
+ resolveOnce(null);
42
+ }
43
+ </script>
44
+
45
+ <template>
46
+ <UModal
47
+ v-if="as === 'modal'"
48
+ v-model:open="open"
49
+ :title="title"
50
+ >
51
+ <template #body>
52
+ <FUAutoForm
53
+ :id="id"
54
+ :data="data"
55
+ :valibot-schema="valibotSchema"
56
+ :zod-schema="zodSchema"
57
+ :overrides="overrides"
58
+ @data-saved="handleSaved"
59
+ >
60
+ <template #submit>
61
+ <UButton
62
+ type="submit"
63
+ label="Save"
64
+ />
65
+ <UButton
66
+ type="button"
67
+ label="Cancel"
68
+ color="neutral"
69
+ variant="outline"
70
+ @click="handleCancel"
71
+ />
72
+ </template>
73
+ </FUAutoForm>
74
+ </template>
75
+ </UModal>
76
+ <USlideover
77
+ v-else
78
+ v-model:open="open"
79
+ :title="title"
80
+ >
81
+ <template #body>
82
+ <FUAutoForm
83
+ :id="id"
84
+ :data="data"
85
+ :valibot-schema="valibotSchema"
86
+ :zod-schema="zodSchema"
87
+ :overrides="overrides"
88
+ @data-saved="handleSaved"
89
+ >
90
+ <template #submit>
91
+ <UButton
92
+ type="submit"
93
+ label="Save"
94
+ />
95
+ <UButton
96
+ type="button"
97
+ label="Cancel"
98
+ color="neutral"
99
+ variant="outline"
100
+ @click="handleCancel"
101
+ />
102
+ </template>
103
+ </FUAutoForm>
104
+ </template>
105
+ </USlideover>
106
+ </template>
@@ -0,0 +1,82 @@
1
+ import type { PropType } from 'vue';
2
+ import type { AutoFormOverrides, ValibotLikeSchema, ZodLikeSchema } from '../composables/useFormKitAutoForm.js';
3
+ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
4
+ id: {
5
+ type: StringConstructor;
6
+ required: true;
7
+ };
8
+ data: {
9
+ type: ObjectConstructor;
10
+ default: null;
11
+ };
12
+ valibotSchema: {
13
+ type: PropType<ValibotLikeSchema>;
14
+ default: undefined;
15
+ };
16
+ zodSchema: {
17
+ type: PropType<ZodLikeSchema>;
18
+ default: undefined;
19
+ };
20
+ overrides: {
21
+ type: PropType<AutoFormOverrides>;
22
+ default: undefined;
23
+ };
24
+ title: {
25
+ type: StringConstructor;
26
+ default: undefined;
27
+ };
28
+ as: {
29
+ type: PropType<"modal" | "slideover">;
30
+ default: string;
31
+ };
32
+ open: {
33
+ type: PropType<boolean>;
34
+ };
35
+ }>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
36
+ close: (value: unknown) => any;
37
+ "update:open": (value: boolean) => any;
38
+ }, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
39
+ id: {
40
+ type: StringConstructor;
41
+ required: true;
42
+ };
43
+ data: {
44
+ type: ObjectConstructor;
45
+ default: null;
46
+ };
47
+ valibotSchema: {
48
+ type: PropType<ValibotLikeSchema>;
49
+ default: undefined;
50
+ };
51
+ zodSchema: {
52
+ type: PropType<ZodLikeSchema>;
53
+ default: undefined;
54
+ };
55
+ overrides: {
56
+ type: PropType<AutoFormOverrides>;
57
+ default: undefined;
58
+ };
59
+ title: {
60
+ type: StringConstructor;
61
+ default: undefined;
62
+ };
63
+ as: {
64
+ type: PropType<"modal" | "slideover">;
65
+ default: string;
66
+ };
67
+ open: {
68
+ type: PropType<boolean>;
69
+ };
70
+ }>> & Readonly<{
71
+ onClose?: ((value: unknown) => any) | undefined;
72
+ "onUpdate:open"?: ((value: boolean) => any) | undefined;
73
+ }>, {
74
+ data: Record<string, any>;
75
+ title: string;
76
+ overrides: AutoFormOverrides;
77
+ valibotSchema: ValibotLikeSchema;
78
+ zodSchema: ZodLikeSchema;
79
+ as: "modal" | "slideover";
80
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
81
+ declare const _default: typeof __VLS_export;
82
+ export default _default;
@@ -95,7 +95,8 @@ if (props.data) {
95
95
  formData.value = props.data;
96
96
  }
97
97
  function handleSave() {
98
- emit("dataSaved", formData.value);
98
+ const { slots: _slots, ...data } = formData.value ?? {};
99
+ emit("dataSaved", data);
99
100
  }
100
101
  function handleReset() {
101
102
  reset(props.id);
@@ -0,0 +1,64 @@
1
+ import type { FormKitSchemaDefinition } from '@formkit/core';
2
+ import type { PropType } from 'vue';
3
+ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
4
+ id: {
5
+ type: StringConstructor;
6
+ required: true;
7
+ };
8
+ data: {
9
+ type: ObjectConstructor;
10
+ default: null;
11
+ };
12
+ schema: {
13
+ type: PropType<FormKitSchemaDefinition>;
14
+ default: null;
15
+ };
16
+ title: {
17
+ type: StringConstructor;
18
+ default: undefined;
19
+ };
20
+ as: {
21
+ type: PropType<"modal" | "slideover">;
22
+ default: string;
23
+ };
24
+ open: {
25
+ type: PropType<boolean>;
26
+ };
27
+ }>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
28
+ close: (value: unknown) => any;
29
+ "update:open": (value: boolean) => any;
30
+ }, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
31
+ id: {
32
+ type: StringConstructor;
33
+ required: true;
34
+ };
35
+ data: {
36
+ type: ObjectConstructor;
37
+ default: null;
38
+ };
39
+ schema: {
40
+ type: PropType<FormKitSchemaDefinition>;
41
+ default: null;
42
+ };
43
+ title: {
44
+ type: StringConstructor;
45
+ default: undefined;
46
+ };
47
+ as: {
48
+ type: PropType<"modal" | "slideover">;
49
+ default: string;
50
+ };
51
+ open: {
52
+ type: PropType<boolean>;
53
+ };
54
+ }>> & Readonly<{
55
+ onClose?: ((value: unknown) => any) | undefined;
56
+ "onUpdate:open"?: ((value: boolean) => any) | undefined;
57
+ }>, {
58
+ schema: FormKitSchemaDefinition;
59
+ data: Record<string, any>;
60
+ title: string;
61
+ as: "modal" | "slideover";
62
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
63
+ declare const _default: typeof __VLS_export;
64
+ export default _default;
@@ -0,0 +1,94 @@
1
+ <script setup>
2
+ import { useOverlayCloseResolution } from "../utils/useOverlayCloseResolution";
3
+ import FUDataEdit from "./FUDataEdit.vue";
4
+ defineProps({
5
+ id: {
6
+ type: String,
7
+ required: true
8
+ },
9
+ data: {
10
+ type: Object,
11
+ default: null
12
+ },
13
+ schema: {
14
+ type: Object,
15
+ default: null
16
+ },
17
+ title: {
18
+ type: String,
19
+ default: void 0
20
+ },
21
+ as: {
22
+ type: String,
23
+ default: "modal"
24
+ }
25
+ });
26
+ const emit = defineEmits(["close"]);
27
+ const open = defineModel("open", { type: Boolean, ...{ default: false } });
28
+ const { resolveOnce } = useOverlayCloseResolution(open, emit);
29
+ function handleSaved(savedData) {
30
+ resolveOnce(savedData);
31
+ }
32
+ function handleCancel() {
33
+ resolveOnce(null);
34
+ }
35
+ </script>
36
+
37
+ <template>
38
+ <UModal
39
+ v-if="as === 'modal'"
40
+ v-model:open="open"
41
+ :title="title"
42
+ >
43
+ <template #body>
44
+ <FUDataEdit
45
+ :id="id"
46
+ :data="data"
47
+ :schema="schema"
48
+ @data-saved="handleSaved"
49
+ >
50
+ <template #submit>
51
+ <UButton
52
+ type="submit"
53
+ label="Save"
54
+ />
55
+ <UButton
56
+ type="button"
57
+ label="Cancel"
58
+ color="neutral"
59
+ variant="outline"
60
+ @click="handleCancel"
61
+ />
62
+ </template>
63
+ </FUDataEdit>
64
+ </template>
65
+ </UModal>
66
+ <USlideover
67
+ v-else
68
+ v-model:open="open"
69
+ :title="title"
70
+ >
71
+ <template #body>
72
+ <FUDataEdit
73
+ :id="id"
74
+ :data="data"
75
+ :schema="schema"
76
+ @data-saved="handleSaved"
77
+ >
78
+ <template #submit>
79
+ <UButton
80
+ type="submit"
81
+ label="Save"
82
+ />
83
+ <UButton
84
+ type="button"
85
+ label="Cancel"
86
+ color="neutral"
87
+ variant="outline"
88
+ @click="handleCancel"
89
+ />
90
+ </template>
91
+ </FUDataEdit>
92
+ </template>
93
+ </USlideover>
94
+ </template>
@@ -0,0 +1,64 @@
1
+ import type { FormKitSchemaDefinition } from '@formkit/core';
2
+ import type { PropType } from 'vue';
3
+ declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
4
+ id: {
5
+ type: StringConstructor;
6
+ required: true;
7
+ };
8
+ data: {
9
+ type: ObjectConstructor;
10
+ default: null;
11
+ };
12
+ schema: {
13
+ type: PropType<FormKitSchemaDefinition>;
14
+ default: null;
15
+ };
16
+ title: {
17
+ type: StringConstructor;
18
+ default: undefined;
19
+ };
20
+ as: {
21
+ type: PropType<"modal" | "slideover">;
22
+ default: string;
23
+ };
24
+ open: {
25
+ type: PropType<boolean>;
26
+ };
27
+ }>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
28
+ close: (value: unknown) => any;
29
+ "update:open": (value: boolean) => any;
30
+ }, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
31
+ id: {
32
+ type: StringConstructor;
33
+ required: true;
34
+ };
35
+ data: {
36
+ type: ObjectConstructor;
37
+ default: null;
38
+ };
39
+ schema: {
40
+ type: PropType<FormKitSchemaDefinition>;
41
+ default: null;
42
+ };
43
+ title: {
44
+ type: StringConstructor;
45
+ default: undefined;
46
+ };
47
+ as: {
48
+ type: PropType<"modal" | "slideover">;
49
+ default: string;
50
+ };
51
+ open: {
52
+ type: PropType<boolean>;
53
+ };
54
+ }>> & Readonly<{
55
+ onClose?: ((value: unknown) => any) | undefined;
56
+ "onUpdate:open"?: ((value: boolean) => any) | undefined;
57
+ }>, {
58
+ schema: FormKitSchemaDefinition;
59
+ data: Record<string, any>;
60
+ title: string;
61
+ as: "modal" | "slideover";
62
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
63
+ declare const _default: typeof __VLS_export;
64
+ export default _default;
@@ -1,6 +1,7 @@
1
1
  <script setup>
2
2
  import { computed } from "vue";
3
3
  import { useFormKitInput } from "../../utils/useFormKitInput";
4
+ import { createContainerBlurHandler } from "../../utils/useFormKitContainerBlur";
4
5
  import { toDateValue, toDateValueOrRangeOrArray, fromDateValueOrRangeOrArray } from "../../utils/dateValueConversion";
5
6
  const props = defineProps({
6
7
  context: {
@@ -9,6 +10,7 @@ const props = defineProps({
9
10
  }
10
11
  });
11
12
  const { handleInput, styleClass, color, validSlotNames } = useFormKitInput(props.context);
13
+ const handleContainerBlur = createContainerBlurHandler(props.context);
12
14
  const conversionOptions = computed(() => ({ timeZone: props.context.timeZone }));
13
15
  const modelValue = computed({
14
16
  get: () => toDateValueOrRangeOrArray(props.context._value, conversionOptions.value),
@@ -46,6 +48,7 @@ const maxValue = computed(() => toDateValue(props.context.maxValue, conversionOp
46
48
  :is-date-unavailable="context.isDateUnavailable"
47
49
  :ui="context.ui"
48
50
  @update:model-value="handleInput"
51
+ @focusout="handleContainerBlur"
49
52
  >
50
53
  <template
51
54
  v-for="slotName in validSlotNames"
@@ -6,7 +6,7 @@ const props = defineProps({
6
6
  required: true
7
7
  }
8
8
  });
9
- const { handleInput, handleChange, styleClass, modelValue, color, validSlotNames } = useFormKitInput(props.context);
9
+ const { handleInput, handleChange, handleBlur, styleClass, modelValue, color, validSlotNames } = useFormKitInput(props.context);
10
10
  </script>
11
11
 
12
12
  <template>
@@ -31,6 +31,7 @@ const { handleInput, handleChange, styleClass, modelValue, color, validSlotNames
31
31
  :ui="context.ui"
32
32
  @change="handleChange"
33
33
  @update:model-value="handleInput"
34
+ @blur="handleBlur"
34
35
  >
35
36
  <template
36
37
  v-for="slotName in validSlotNames"
@@ -1,5 +1,6 @@
1
1
  <script setup>
2
2
  import { useFormKitInput } from "../../utils/useFormKitInput";
3
+ import { createContainerBlurHandler } from "../../utils/useFormKitContainerBlur";
3
4
  const props = defineProps({
4
5
  context: {
5
6
  type: Object,
@@ -7,6 +8,7 @@ const props = defineProps({
7
8
  }
8
9
  });
9
10
  const { handleInput, handleChange, styleClass, color, modelValue, items, validSlotNames } = useFormKitInput(props.context);
11
+ const handleContainerBlur = createContainerBlurHandler(props.context);
10
12
  </script>
11
13
 
12
14
  <template>
@@ -31,6 +33,7 @@ const { handleInput, handleChange, styleClass, color, modelValue, items, validSl
31
33
  :name="context.name"
32
34
  @change="handleChange"
33
35
  @update:model-value="handleInput"
36
+ @focusout="handleContainerBlur"
34
37
  >
35
38
  <template
36
39
  v-for="slotName in validSlotNames"
@@ -1,5 +1,6 @@
1
1
  <script setup>
2
2
  import { useFormKitInput } from "../../utils/useFormKitInput";
3
+ import { createContainerBlurHandler } from "../../utils/useFormKitContainerBlur";
3
4
  const DEFAULT_EDITOR_TOOLBAR_ITEMS = [
4
5
  [
5
6
  { kind: "undo", icon: "i-lucide-undo", tooltip: { text: "Undo" } },
@@ -61,61 +62,64 @@ const props = defineProps({
61
62
  }
62
63
  });
63
64
  const { handleInput, styleClass, modelValue } = useFormKitInput(props.context);
65
+ const handleContainerBlur = createContainerBlurHandler(props.context);
64
66
  </script>
65
67
 
66
68
  <template>
67
- <UEditor
68
- :id="context.id"
69
- v-model="modelValue"
70
- v-bind="{ ...context?.attrs }"
71
- :class="styleClass"
72
- :editable="!context?.disabled"
73
- :style="context?.attrs.style"
74
- :content-type="context.contentType"
75
- :starter-kit="context.starterKit"
76
- :placeholder="context.placeholder"
77
- :markdown="context.markdown"
78
- :image="context.image"
79
- :mention="context.mention"
80
- :handlers="context.editorHandlers"
81
- :extensions="context.extensions"
82
- :autofocus="context.autofocus"
83
- :ui="context.ui"
84
- @update:model-value="handleInput"
85
- >
86
- <template #default="{ editor, handlers }">
87
- <UEditorToolbar
88
- v-if="context.toolbar !== false"
89
- :editor="editor"
90
- :items="context.toolbarItems ?? DEFAULT_EDITOR_TOOLBAR_ITEMS"
91
- class="border-b border-default px-2 py-1 mb-2 flex-wrap"
92
- />
93
- <UEditorToolbar
94
- v-if="context.bubbleToolbar !== false"
95
- :editor="editor"
96
- :items="context.bubbleToolbarItems ?? DEFAULT_BUBBLE_TOOLBAR_ITEMS"
97
- layout="bubble"
98
- :should-show="bubbleToolbarShouldShow"
99
- />
100
- <UEditorDragHandle
101
- v-if="context.dragHandle"
102
- :editor="editor"
103
- />
104
- <UEditorSuggestionMenu
105
- v-if="context.suggestionMenu"
106
- :editor="editor"
107
- :items="context.suggestionMenuItems ?? DEFAULT_SUGGESTION_MENU_ITEMS"
108
- />
109
- <UEditorMentionMenu
110
- v-if="context.mentionItems?.length"
111
- :editor="editor"
112
- :items="context.mentionItems"
113
- />
114
- <component
115
- :is="context.slots.default"
116
- v-if="context?.slots?.default"
117
- v-bind="{ ...context, editor, handlers }"
118
- />
119
- </template>
120
- </UEditor>
69
+ <div @focusout="handleContainerBlur">
70
+ <UEditor
71
+ :id="context.id"
72
+ v-model="modelValue"
73
+ v-bind="{ ...context?.attrs }"
74
+ :class="styleClass"
75
+ :editable="!context?.disabled"
76
+ :style="context?.attrs.style"
77
+ :content-type="context.contentType"
78
+ :starter-kit="context.starterKit"
79
+ :placeholder="context.placeholder"
80
+ :markdown="context.markdown"
81
+ :image="context.image"
82
+ :mention="context.mention"
83
+ :handlers="context.editorHandlers"
84
+ :extensions="context.extensions"
85
+ :autofocus="context.autofocus"
86
+ :ui="context.ui"
87
+ @update:model-value="handleInput"
88
+ >
89
+ <template #default="{ editor, handlers }">
90
+ <UEditorToolbar
91
+ v-if="context.toolbar !== false"
92
+ :editor="editor"
93
+ :items="context.toolbarItems ?? DEFAULT_EDITOR_TOOLBAR_ITEMS"
94
+ class="border-b border-default px-2 py-1 mb-2 flex-wrap"
95
+ />
96
+ <UEditorToolbar
97
+ v-if="context.bubbleToolbar !== false"
98
+ :editor="editor"
99
+ :items="context.bubbleToolbarItems ?? DEFAULT_BUBBLE_TOOLBAR_ITEMS"
100
+ layout="bubble"
101
+ :should-show="bubbleToolbarShouldShow"
102
+ />
103
+ <UEditorDragHandle
104
+ v-if="context.dragHandle"
105
+ :editor="editor"
106
+ />
107
+ <UEditorSuggestionMenu
108
+ v-if="context.suggestionMenu"
109
+ :editor="editor"
110
+ :items="context.suggestionMenuItems ?? DEFAULT_SUGGESTION_MENU_ITEMS"
111
+ />
112
+ <UEditorMentionMenu
113
+ v-if="context.mentionItems?.length"
114
+ :editor="editor"
115
+ :items="context.mentionItems"
116
+ />
117
+ <component
118
+ :is="context.slots.default"
119
+ v-if="context?.slots?.default"
120
+ v-bind="{ ...context, editor, handlers }"
121
+ />
122
+ </template>
123
+ </UEditor>
124
+ </div>
121
125
  </template>
@@ -6,7 +6,7 @@ const props = defineProps({
6
6
  required: true
7
7
  }
8
8
  });
9
- const { handleInput, handleChange, isInvalid, styleClass, color, modelValue, validSlotNames } = useFormKitInput(props.context);
9
+ const { handleInput, handleChange, handleBlur, isInvalid, styleClass, color, modelValue, validSlotNames } = useFormKitInput(props.context);
10
10
  </script>
11
11
 
12
12
  <template>
@@ -41,6 +41,7 @@ const { handleInput, handleChange, isInvalid, styleClass, color, modelValue, val
41
41
  :ui="context.ui"
42
42
  @change="handleChange"
43
43
  @update:model-value="handleInput"
44
+ @blur="handleBlur"
44
45
  >
45
46
  <template
46
47
  v-for="slotName in validSlotNames"
@@ -6,7 +6,7 @@ const props = defineProps({
6
6
  required: true
7
7
  }
8
8
  });
9
- const { handleInput, handleChange, isInvalid, styleClass, color, modelValue, validSlotNames } = useFormKitInput(props.context);
9
+ const { handleInput, handleChange, handleBlur, isInvalid, styleClass, color, modelValue, validSlotNames } = useFormKitInput(props.context);
10
10
  </script>
11
11
 
12
12
  <template>
@@ -46,6 +46,7 @@ const { handleInput, handleChange, isInvalid, styleClass, color, modelValue, val
46
46
  :ui="context.ui"
47
47
  @change="handleChange"
48
48
  @update:model-value="handleInput"
49
+ @blur="handleBlur"
49
50
  >
50
51
  <template
51
52
  v-for="slotName in validSlotNames"
@@ -1,6 +1,7 @@
1
1
  <script setup>
2
2
  import { computed } from "vue";
3
3
  import { useFormKitInput } from "../../utils/useFormKitInput";
4
+ import { createContainerBlurHandler } from "../../utils/useFormKitContainerBlur";
4
5
  import { fromDateValueOrRange, toDateValue, toDateValueOrRange } from "../../utils/dateValueConversion";
5
6
  const props = defineProps({
6
7
  context: {
@@ -8,7 +9,8 @@ const props = defineProps({
8
9
  required: true
9
10
  }
10
11
  });
11
- const { handleInput, handleChange, isInvalid, styleClass, color, validSlotNames } = useFormKitInput(props.context);
12
+ const { handleInput, handleChange, handleBlur, isInvalid, styleClass, color, validSlotNames } = useFormKitInput(props.context);
13
+ const handleContainerBlur = createContainerBlurHandler(props.context);
12
14
  const conversionOptions = computed(() => ({
13
15
  granularity: props.context.granularity,
14
16
  hideTimeZone: props.context.hideTimeZone,
@@ -66,6 +68,8 @@ const minValue = computed(() => toDateValue(props.context.minValue, conversionOp
66
68
  :ui="context.ui"
67
69
  @change="handleChange"
68
70
  @update:model-value="handleInput"
71
+ @blur="handleBlur"
72
+ @focusout="handleContainerBlur"
69
73
  >
70
74
  <template
71
75
  v-for="slotName in validSlotNames"
@@ -6,7 +6,7 @@ const props = defineProps({
6
6
  required: true
7
7
  }
8
8
  });
9
- const { handleInput, handleChange, isInvalid, styleClass, color, modelValue, items, validSlotNames } = useFormKitInput(props.context);
9
+ const { handleInput, handleChange, handleBlur, isInvalid, styleClass, color, modelValue, items, validSlotNames } = useFormKitInput(props.context);
10
10
  </script>
11
11
 
12
12
  <template>
@@ -65,6 +65,7 @@ const { handleInput, handleChange, isInvalid, styleClass, color, modelValue, ite
65
65
  :ui="context.ui"
66
66
  @change="handleChange"
67
67
  @update:model-value="handleInput"
68
+ @blur="handleBlur"
68
69
  >
69
70
  <template
70
71
  v-for="slotName in validSlotNames"
@@ -6,7 +6,7 @@ const props = defineProps({
6
6
  required: true
7
7
  }
8
8
  });
9
- const { handleInput, handleChange, isInvalid, styleClass, color, modelValue, validSlotNames } = useFormKitInput(props.context);
9
+ const { handleInput, handleChange, handleBlur, isInvalid, styleClass, color, modelValue, validSlotNames } = useFormKitInput(props.context);
10
10
  </script>
11
11
 
12
12
  <template>
@@ -47,6 +47,7 @@ const { handleInput, handleChange, isInvalid, styleClass, color, modelValue, val
47
47
  :ui="context.ui"
48
48
  @change="handleChange"
49
49
  @update:model-value="handleInput"
50
+ @blur="handleBlur"
50
51
  >
51
52
  <template
52
53
  v-for="slotName in validSlotNames"
@@ -6,7 +6,7 @@ const props = defineProps({
6
6
  required: true
7
7
  }
8
8
  });
9
- const { handleInput, handleChange, isInvalid, styleClass, color, modelValue, validSlotNames } = useFormKitInput(props.context);
9
+ const { handleInput, handleChange, handleBlur, isInvalid, styleClass, color, modelValue, validSlotNames } = useFormKitInput(props.context);
10
10
  </script>
11
11
 
12
12
  <template>
@@ -49,6 +49,7 @@ const { handleInput, handleChange, isInvalid, styleClass, color, modelValue, val
49
49
  :ui="context.ui"
50
50
  @change="handleChange"
51
51
  @update:model-value="handleInput"
52
+ @blur="handleBlur"
52
53
  >
53
54
  <template
54
55
  v-for="slotName in validSlotNames"
@@ -1,12 +1,14 @@
1
1
  <script setup>
2
2
  import { useFormKitInput } from "../../utils/useFormKitInput";
3
+ import { createContainerBlurHandler } from "../../utils/useFormKitContainerBlur";
3
4
  const props = defineProps({
4
5
  context: {
5
6
  type: Object,
6
7
  required: true
7
8
  }
8
9
  });
9
- const { handleInput, handleChange, isInvalid, styleClass, color, modelValue, validSlotNames } = useFormKitInput(props.context);
10
+ const { handleInput, handleChange, handleBlur, isInvalid, styleClass, color, modelValue, validSlotNames } = useFormKitInput(props.context);
11
+ const handleContainerBlur = createContainerBlurHandler(props.context);
10
12
  </script>
11
13
 
12
14
  <template>
@@ -46,6 +48,8 @@ const { handleInput, handleChange, isInvalid, styleClass, color, modelValue, val
46
48
  :ui="context.ui"
47
49
  @change="handleChange"
48
50
  @update:model-value="handleInput"
51
+ @blur="handleBlur"
52
+ @focusout="handleContainerBlur"
49
53
  >
50
54
  <template
51
55
  v-for="slotName in validSlotNames"
@@ -1,6 +1,7 @@
1
1
  <script setup>
2
2
  import { computed, nextTick, ref, watch } from "vue";
3
3
  import { useFormKitInput } from "../../utils/useFormKitInput";
4
+ import { createContainerBlurHandler } from "../../utils/useFormKitContainerBlur";
4
5
  const props = defineProps({
5
6
  context: {
6
7
  type: Object,
@@ -8,6 +9,7 @@ const props = defineProps({
8
9
  }
9
10
  });
10
11
  const { handleInput, handleChange, isInvalid, styleClass, color, modelValue, items, validSlotNames } = useFormKitInput(props.context);
12
+ const handleContainerBlur = createContainerBlurHandler(props.context);
11
13
  const targetItems = ref(modelValue.value || []);
12
14
  const sourceSelection = ref([]);
13
15
  const targetSelection = ref([]);
@@ -240,6 +242,7 @@ watch(targetItems, (newVal) => {
240
242
  :ui="context.ui"
241
243
  @change="handleChange"
242
244
  @update:model-value="handleInput"
245
+ @focusout="handleContainerBlur"
243
246
  >
244
247
  <template
245
248
  v-for="slotName in validSlotNames"
@@ -255,6 +258,7 @@ watch(targetItems, (newVal) => {
255
258
  <div
256
259
  v-if="context.displayMode === 'transfer'"
257
260
  class="flex items-stretch gap-4 w-full"
261
+ @focusout="handleContainerBlur"
258
262
  >
259
263
  <div
260
264
  ref="sourceContainerRef"
@@ -1,12 +1,17 @@
1
1
  <script setup>
2
2
  import { useFormKitInput } from "../../utils/useFormKitInput";
3
+ import { createContainerBlurHandler } from "../../utils/useFormKitContainerBlur";
3
4
  const props = defineProps({
4
5
  context: {
5
6
  type: Object,
6
7
  required: true
7
8
  }
8
9
  });
9
- const { handleInput, handleChange, isInvalid, styleClass, color, modelValue } = useFormKitInput(props.context);
10
+ const { handleInput, handleChange, handleBlur, isInvalid, styleClass, color, modelValue } = useFormKitInput(props.context);
11
+ const handleContainerBlur = createContainerBlurHandler(props.context);
12
+ function handlePinBlur(event) {
13
+ handleBlur(event);
14
+ }
10
15
  </script>
11
16
 
12
17
  <template>
@@ -35,5 +40,7 @@ const { handleInput, handleChange, isInvalid, styleClass, color, modelValue } =
35
40
  :ui="context.ui"
36
41
  @change="handleChange"
37
42
  @update:model-value="handleInput"
43
+ @blur="handlePinBlur"
44
+ @focusout="handleContainerBlur"
38
45
  />
39
46
  </template>
@@ -1,5 +1,6 @@
1
1
  <script setup>
2
2
  import { useFormKitInput } from "../../utils/useFormKitInput";
3
+ import { createContainerBlurHandler } from "../../utils/useFormKitContainerBlur";
3
4
  const props = defineProps({
4
5
  context: {
5
6
  type: Object,
@@ -7,6 +8,7 @@ const props = defineProps({
7
8
  }
8
9
  });
9
10
  const { handleInput, handleChange, styleClass, color, modelValue, items, validSlotNames } = useFormKitInput(props.context);
11
+ const handleContainerBlur = createContainerBlurHandler(props.context);
10
12
  </script>
11
13
 
12
14
  <template>
@@ -31,6 +33,7 @@ const { handleInput, handleChange, styleClass, color, modelValue, items, validSl
31
33
  :ui="context.ui"
32
34
  @change="handleChange"
33
35
  @update:model-value="handleInput"
36
+ @focusout="handleContainerBlur"
34
37
  >
35
38
  <template
36
39
  v-for="slotName in validSlotNames"
@@ -6,7 +6,7 @@ const props = defineProps({
6
6
  required: true
7
7
  }
8
8
  });
9
- const { handleInput, handleChange, styleClass, color, modelValue, validSlotNames } = useFormKitInput(props.context);
9
+ const { handleInput, handleChange, handleBlur, styleClass, color, modelValue, validSlotNames } = useFormKitInput(props.context);
10
10
  </script>
11
11
 
12
12
  <template>
@@ -32,6 +32,7 @@ const { handleInput, handleChange, styleClass, color, modelValue, validSlotNames
32
32
  :ui="context.ui"
33
33
  @change="handleChange"
34
34
  @update:model-value="handleInput"
35
+ @blur="handleBlur"
35
36
  >
36
37
  <template
37
38
  v-for="slotName in validSlotNames"
@@ -6,7 +6,7 @@ const props = defineProps({
6
6
  required: true
7
7
  }
8
8
  });
9
- const { handleInput, handleChange, styleClass, color, isInvalid, modelValue, validSlotNames } = useFormKitInput(props.context);
9
+ const { handleInput, handleChange, handleBlur, styleClass, color, isInvalid, modelValue, validSlotNames } = useFormKitInput(props.context);
10
10
  </script>
11
11
 
12
12
  <template>
@@ -46,6 +46,7 @@ const { handleInput, handleChange, styleClass, color, isInvalid, modelValue, val
46
46
  :ui="context.ui"
47
47
  @change="handleChange"
48
48
  @update:model-value="handleInput"
49
+ @blur="handleBlur"
49
50
  >
50
51
  <template
51
52
  v-for="slotName in validSlotNames"
@@ -0,0 +1,20 @@
1
+ import type { FormKitSchemaDefinition } from '@formkit/core';
2
+ import type { AutoFormOverrides, ValibotLikeSchema, ZodLikeSchema } from './useFormKitAutoForm.js';
3
+ export interface FormKitOverlayEditOptions {
4
+ data: unknown;
5
+ schema: FormKitSchemaDefinition;
6
+ title?: string;
7
+ as?: 'modal' | 'slideover';
8
+ }
9
+ export interface FormKitOverlayAutoOptions {
10
+ data?: unknown;
11
+ valibotSchema?: ValibotLikeSchema;
12
+ zodSchema?: ZodLikeSchema;
13
+ overrides?: AutoFormOverrides;
14
+ title?: string;
15
+ as?: 'modal' | 'slideover';
16
+ }
17
+ export declare function useFormKitOverlay(): {
18
+ edit: (options: FormKitOverlayEditOptions) => Promise<unknown>;
19
+ auto: (options: FormKitOverlayAutoOptions) => Promise<unknown>;
20
+ };
@@ -0,0 +1,42 @@
1
+ import { useOverlay } from "@nuxt/ui/composables";
2
+ import FUAutoFormOverlay from "../components/FUAutoFormOverlay.vue";
3
+ import FUDataEditOverlay from "../components/FUDataEditOverlay.vue";
4
+ const registeredOverlays = /* @__PURE__ */ new WeakMap();
5
+ const registeredAutoOverlays = /* @__PURE__ */ new WeakMap();
6
+ let formIdCounter = 0;
7
+ export function useFormKitOverlay() {
8
+ const overlay = useOverlay();
9
+ let registeredOverlay = registeredOverlays.get(overlay);
10
+ if (!registeredOverlay) {
11
+ registeredOverlay = overlay.create(FUDataEditOverlay);
12
+ registeredOverlays.set(overlay, registeredOverlay);
13
+ }
14
+ let registeredAutoOverlay = registeredAutoOverlays.get(overlay);
15
+ if (!registeredAutoOverlay) {
16
+ registeredAutoOverlay = overlay.create(FUAutoFormOverlay);
17
+ registeredAutoOverlays.set(overlay, registeredAutoOverlay);
18
+ }
19
+ function edit(options) {
20
+ const id = `formkit-overlay-${++formIdCounter}`;
21
+ return registeredOverlay.open({
22
+ id,
23
+ data: options.data,
24
+ schema: options.schema,
25
+ title: options.title,
26
+ as: options.as ?? "modal"
27
+ });
28
+ }
29
+ function auto(options) {
30
+ const id = `formkit-autoform-overlay-${++formIdCounter}`;
31
+ return registeredAutoOverlay.open({
32
+ id,
33
+ data: options.data,
34
+ valibotSchema: options.valibotSchema,
35
+ zodSchema: options.zodSchema,
36
+ overrides: options.overrides,
37
+ title: options.title,
38
+ as: options.as ?? "modal"
39
+ });
40
+ }
41
+ return { edit, auto };
42
+ }
@@ -0,0 +1,37 @@
1
+ import type { FormKitFrameworkContext } from '@formkit/core';
2
+ /**
3
+ * For an input whose real focusable sub-elements live inside one container
4
+ * - a Reka UI `DateField`'s day/month/year segments, a checkbox/radio
5
+ * group's individual items, a listbox's options - `context.handlers.blur`
6
+ * must only fire once focus has genuinely left the whole container, not
7
+ * just moved between its own children. Native `blur` doesn't bubble, so a
8
+ * plain `@blur` on the container never sees a child's blur at all; this
9
+ * binds to `focusout` (which does bubble) instead, and uses
10
+ * `relatedTarget` to tell "moved to a sibling inside" apart from "left
11
+ * entirely."
12
+ *
13
+ * Discovered while wiring `FUInputDate`/`FUInputTime`: Nuxt UI's own
14
+ * `InputDate.vue`/`InputTime.vue` bind `@blur` on their internal
15
+ * `DateField.Root`, assuming Reka UI aggregates blur across segments the
16
+ * same way - it doesn't (`DateField.Root` doesn't declare `blur` as its own
17
+ * emit, so that binding is a native fallthrough on a never-focused
18
+ * container div, and silently never fires). This helper is what actually
19
+ * fixes it, bound to `@focusout` on the outer `<UInputDate>`/`<UInputTime>`
20
+ * component instead.
21
+ *
22
+ * Uses `event.currentTarget` (the element the listener is actually attached
23
+ * to) as the container, rather than a template ref to the component's own
24
+ * `$el` - `UInputDate`/`UInputTime` render a multi-root template (a
25
+ * `createReusableTemplate`-style definition block plus the real
26
+ * `DateField.Root`), so `$el` resolves to that definition block's own
27
+ * placeholder node, not the visible container (confirmed empirically).
28
+ * `currentTarget` has no such ambiguity: it's always the exact node the
29
+ * `focusout` listener is bound to.
30
+ *
31
+ * Returns a plain `(event: Event) => void`, not `(event: FocusEvent) =>
32
+ * void` - some Nuxt UI components (e.g. `UPinInput`) type their own `blur`
33
+ * emit as generic `Event`, and a stricter parameter type isn't assignable
34
+ * to a listener bound there. It's genuinely a `FocusEvent` at runtime
35
+ * (`focusout` always is), so it's cast internally.
36
+ */
37
+ export declare function createContainerBlurHandler(context: FormKitFrameworkContext): (event: Event) => void;
@@ -0,0 +1,9 @@
1
+ export function createContainerBlurHandler(context) {
2
+ return (event) => {
3
+ const focusEvent = event;
4
+ const container = focusEvent.currentTarget;
5
+ const related = focusEvent.relatedTarget;
6
+ if (!container || !related || !container.contains(related))
7
+ context.handlers.blur(focusEvent);
8
+ };
9
+ }
@@ -0,0 +1,20 @@
1
+ import type { Ref } from 'vue';
2
+ /**
3
+ * Shared by `FUDataEditOverlay.vue`/`FUAutoFormOverlay.vue`: both need to
4
+ * resolve their `useOverlay` `close` emit exactly once per open cycle, from
5
+ * whichever of several sources fires first - an explicit Save/Cancel button,
6
+ * or `UModal`/`USlideover`'s own Esc/backdrop dismissal, which only ever
7
+ * surfaces as their `open` v-model transitioning to `false` with no
8
+ * dedicated event of its own.
9
+ *
10
+ * `useOverlay` reuses the same wrapper instance across sequential `edit()`/
11
+ * `auto()` calls (its registered `id` is fixed at `.create()` time, not
12
+ * per-`open()` call), so "has this open cycle already resolved" has to reset
13
+ * on every reopen, not just be set once ever - otherwise a *later* call's
14
+ * Esc/backdrop dismissal would see the flag still `true` from a previous
15
+ * cycle's Save/Cancel and silently no-op, leaving that later promise pending
16
+ * forever.
17
+ */
18
+ export declare function useOverlayCloseResolution(open: Ref<boolean>, emit: (event: 'close', value: unknown) => void): {
19
+ resolveOnce: (value: unknown) => void;
20
+ };
@@ -0,0 +1,17 @@
1
+ import { watch } from "vue";
2
+ export function useOverlayCloseResolution(open, emit) {
3
+ let hasResolved = false;
4
+ function resolveOnce(value) {
5
+ if (hasResolved)
6
+ return;
7
+ hasResolved = true;
8
+ emit("close", value);
9
+ }
10
+ watch(open, (isOpen) => {
11
+ if (isOpen)
12
+ hasResolved = false;
13
+ else
14
+ resolveOnce(null);
15
+ });
16
+ return { resolveOnce };
17
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sfxcode/nuxt-ui-formkit",
3
- "version": "1.2.3",
3
+ "version": "1.2.4",
4
4
  "description": "FormKit integration for Nuxt UI - Seamlessly connect FormKit form handling with Nuxt UI components",
5
5
  "author": {
6
6
  "name": "Tom",