@signal24/vue-foundation 3.8.1 → 4.1.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 (127) hide show
  1. package/.eslintrc.cjs +35 -0
  2. package/.prettierrc.json +4 -2
  3. package/dist/src/components/ajax-select.vue.d.ts +21 -0
  4. package/dist/src/components/alert-helpers.d.ts +8 -0
  5. package/dist/src/components/alert-modal.vue.d.ts +27 -0
  6. package/dist/src/components/ez-smart-select.vue.d.ts +27 -0
  7. package/dist/src/components/index.d.ts +8 -0
  8. package/dist/src/components/modal-container.d.ts +33 -0
  9. package/dist/src/components/modal.vue.d.ts +34 -0
  10. package/dist/src/components/smart-select.vue.d.ts +121 -0
  11. package/dist/src/config.d.ts +8 -0
  12. package/dist/src/directives/autofocus.d.ts +2 -0
  13. package/dist/src/directives/confirm-button.d.ts +2 -0
  14. package/dist/src/directives/date-input.d.ts +2 -0
  15. package/dist/src/directives/datetime.d.ts +2 -0
  16. package/dist/src/directives/disabled.d.ts +2 -0
  17. package/dist/src/directives/duration.d.ts +2 -0
  18. package/dist/src/directives/index.d.ts +24 -0
  19. package/dist/src/directives/infinite-scroll.d.ts +3 -0
  20. package/dist/src/directives/readonly.d.ts +2 -0
  21. package/dist/src/directives/tooltip.d.ts +41 -0
  22. package/dist/src/filters/index.d.ts +39 -0
  23. package/dist/src/helpers/array.d.ts +3 -0
  24. package/dist/src/helpers/context-menu.d.ts +13 -0
  25. package/dist/src/helpers/delay.d.ts +2 -0
  26. package/dist/src/helpers/error.d.ts +7 -0
  27. package/dist/src/helpers/index.d.ts +9 -0
  28. package/dist/src/helpers/mask.d.ts +15 -0
  29. package/dist/src/helpers/number.d.ts +1 -0
  30. package/dist/src/helpers/object.d.ts +2 -0
  31. package/dist/src/helpers/openapi.d.ts +34 -0
  32. package/dist/src/helpers/string.d.ts +5 -0
  33. package/dist/src/hooks/index.d.ts +2 -0
  34. package/dist/src/hooks/infinite-scroll.d.ts +30 -0
  35. package/dist/src/hooks/resize-watcher.d.ts +1 -0
  36. package/dist/src/index.d.ts +8 -0
  37. package/dist/src/types.d.ts +14 -0
  38. package/dist/src/vite-plugins/index.d.ts +1 -0
  39. package/dist/src/vite-plugins/index.js +2 -0
  40. package/dist/src/vite-plugins/vite-openapi-plugin.cli.d.ts +2 -0
  41. package/dist/src/vite-plugins/vite-openapi-plugin.cli.js +10 -0
  42. package/dist/src/vite-plugins/vite-openapi-plugin.d.ts +5 -0
  43. package/dist/src/vite-plugins/vite-openapi-plugin.js +58 -0
  44. package/dist/vue-foundation.css +1 -1
  45. package/dist/vue-foundation.es.js +880 -1880
  46. package/package.json +47 -16
  47. package/src/components/ajax-select.vue +44 -23
  48. package/src/components/alert-helpers.ts +45 -0
  49. package/src/components/alert-modal.vue +68 -0
  50. package/src/components/ez-smart-select.vue +51 -0
  51. package/src/components/index.ts +10 -0
  52. package/src/components/modal-container.ts +131 -0
  53. package/src/components/modal.vue +44 -129
  54. package/src/components/smart-select.vue +196 -243
  55. package/src/config.ts +15 -0
  56. package/src/directives/autofocus.ts +20 -0
  57. package/src/directives/confirm-button.ts +50 -0
  58. package/src/directives/date-input.ts +19 -0
  59. package/src/directives/datetime.ts +48 -0
  60. package/src/directives/disabled.ts +30 -0
  61. package/src/directives/duration.ts +79 -0
  62. package/src/directives/index.ts +37 -0
  63. package/src/directives/infinite-scroll.ts +9 -0
  64. package/src/directives/readonly.ts +15 -0
  65. package/src/directives/tooltip.ts +190 -0
  66. package/src/filters/index.ts +79 -0
  67. package/src/helpers/array.ts +7 -0
  68. package/src/helpers/context-menu.ts +108 -0
  69. package/src/helpers/delay.ts +2 -0
  70. package/src/helpers/error.ts +41 -0
  71. package/src/helpers/index.ts +9 -0
  72. package/src/helpers/mask.ts +105 -0
  73. package/src/helpers/number.ts +3 -0
  74. package/src/helpers/object.ts +19 -0
  75. package/src/helpers/openapi.ts +82 -0
  76. package/src/helpers/string.ts +27 -0
  77. package/src/hooks/index.ts +2 -0
  78. package/src/hooks/infinite-scroll.ts +107 -0
  79. package/src/hooks/resize-watcher.ts +8 -0
  80. package/src/index.ts +14 -0
  81. package/src/types.ts +14 -0
  82. package/src/vite-plugins/index.ts +2 -0
  83. package/src/vite-plugins/vite-openapi-plugin.cli.ts +15 -0
  84. package/src/vite-plugins/vite-openapi-plugin.ts +71 -0
  85. package/tsconfig.app.json +22 -0
  86. package/tsconfig.json +14 -0
  87. package/tsconfig.node.json +9 -0
  88. package/tsconfig.vite-plugins.json +10 -0
  89. package/tsconfig.vitest.json +9 -0
  90. package/vite.config.js +37 -35
  91. package/vitest.config.js +17 -0
  92. package/.eslintrc.js +0 -16
  93. package/CHANGES.md +0 -2
  94. package/dist/vue-foundation.cjs.js +0 -5
  95. package/dist/vue-foundation.umd.js +0 -6
  96. package/postcss.config.cjs +0 -5
  97. package/src/app.js +0 -25
  98. package/src/components/alert.vue +0 -130
  99. package/src/components/index.js +0 -12
  100. package/src/config.js +0 -11
  101. package/src/directives/autofocus.js +0 -17
  102. package/src/directives/confirm-button.js +0 -40
  103. package/src/directives/date-input.js +0 -18
  104. package/src/directives/datetime.js +0 -46
  105. package/src/directives/disabled.js +0 -28
  106. package/src/directives/duration.js +0 -72
  107. package/src/directives/index.js +0 -10
  108. package/src/directives/infinite-scroll.js +0 -17
  109. package/src/directives/readonly.js +0 -17
  110. package/src/directives/tooltip.js +0 -178
  111. package/src/directives/user-text.js +0 -11
  112. package/src/filters/index.js +0 -82
  113. package/src/helpers/array.js +0 -99
  114. package/src/helpers/context-menu.js +0 -66
  115. package/src/helpers/delay.js +0 -3
  116. package/src/helpers/error.js +0 -36
  117. package/src/helpers/http.js +0 -44
  118. package/src/helpers/index.js +0 -9
  119. package/src/helpers/mask.js +0 -90
  120. package/src/helpers/number.js +0 -6
  121. package/src/helpers/string.js +0 -36
  122. package/src/helpers/vue.js +0 -5
  123. package/src/index.js +0 -33
  124. package/src/plugins/index.js +0 -10
  125. package/src/plugins/infinite-scroll/hook.js +0 -30
  126. package/src/plugins/infinite-scroll.js +0 -100
  127. package/src/plugins/resize-watcher.js +0 -28
package/package.json CHANGED
@@ -1,39 +1,70 @@
1
1
  {
2
2
  "name": "@signal24/vue-foundation",
3
3
  "type": "module",
4
- "version": "3.8.1",
4
+ "version": "4.1.0",
5
5
  "description": "Common components, directives, and helpers for Vue 3 apps",
6
- "main": "dist/vue-foundation.umd.js",
7
6
  "module": "./dist/vue-foundation.es.js",
7
+ "bin": {
8
+ "vf-generate-openapi-client": "./dist/src/vite-plugins/vite-openapi-plugin.cli.js"
9
+ },
8
10
  "exports": {
9
11
  ".": {
10
- "import": "./dist/vue-foundation.es.js",
11
- "require": "./dist/vue-foundation.umd.js"
12
+ "import": "./dist/vue-foundation.es.js"
12
13
  },
13
14
  "./dist/vue-foundation.css": {
14
- "import": "./dist/vue-foundation.css",
15
- "require": "./dist/vue-foundation.css"
15
+ "import": "./dist/vue-foundation.css",
16
+ "require": "./dist/vue-foundation.css"
17
+ },
18
+ "./vite-plugins": {
19
+ "import": "./dist/src/vite-plugins/index.js",
20
+ "types": "./dist/src/vite-plugins/index.d.ts"
16
21
  }
17
22
  },
23
+ "typings": "./dist/src/index.d.ts",
18
24
  "scripts": {
19
25
  "dev": "vite",
20
- "build": "vite build",
21
- "preview": "vite preview"
26
+ "build": "rm -rf dist && vite build && vue-tsc --declaration --emitDeclarationOnly -p tsconfig.app.json && tsc -p tsconfig.vite-plugins.json && find dist -name '*.tsbuildinfo' -delete && chmod +x dist/src/vite-plugins/vite-openapi-plugin.cli.js",
27
+ "preview": "vite preview",
28
+ "test:types": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false",
29
+ "test:unit": "vitest",
30
+ "test:e2e": "start-server-and-test preview http://localhost:4173 'cypress run --e2e'",
31
+ "test:e2e:dev": "start-server-and-test 'vite dev --port 4173' http://localhost:4173 'cypress open --e2e'",
32
+ "test:e2e:dev:remote": "DISPLAY=:0 yarn test:e2e:dev",
33
+ "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts --fix --ignore-path .gitignore",
34
+ "format": "prettier --write ."
22
35
  },
23
36
  "license": "MIT",
24
37
  "dependencies": {
25
- "axios": "^0.27.2",
26
- "jquery": "^3.6.4",
38
+ "date-fns": "^2.29.3",
27
39
  "lodash": "^4.17.21",
28
- "moment": "^2.29.4",
29
- "vue-stash-nested": "fergusean/vue-stash-nested#vue-foundation-3",
40
+ "type-fest": "^3.8.0",
30
41
  "vue": "^3.2.47"
31
42
  },
32
43
  "devDependencies": {
44
+ "@nabla/vite-plugin-eslint": "^1.5.0",
45
+ "@rushstack/eslint-patch": "^1.2.0",
46
+ "@types/jsdom": "^21.1.1",
47
+ "@types/lodash": "^4.14.192",
48
+ "@types/node": "^18.15.11",
33
49
  "@vitejs/plugin-vue": "^4.1.0",
34
- "autoprefixer": "^10.4.14",
35
- "sass": "^1.59.3",
36
- "vite": "^4.2.0",
37
- "vue": "^3.2.47"
50
+ "@vue/eslint-config-prettier": "^7.1.0",
51
+ "@vue/eslint-config-typescript": "^11.0.2",
52
+ "@vue/test-utils": "^2.3.2",
53
+ "@vue/tsconfig": "^0.1.3",
54
+ "cypress": "^12.9.0",
55
+ "eslint": "^8.37.0",
56
+ "eslint-plugin-cypress": "^2.13.2",
57
+ "eslint-plugin-simple-import-sort": "^10.0.0",
58
+ "eslint-plugin-unused-imports": "^2.0.0",
59
+ "eslint-plugin-vue": "^9.10.0",
60
+ "jsdom": "^21.1.1",
61
+ "openapi-typescript-codegen": "^0.23.0",
62
+ "prettier": "^2.8.7",
63
+ "sass": "^1.60.0",
64
+ "start-server-and-test": "^2.0.0",
65
+ "typescript": "~4.9.5",
66
+ "vite": "^4.2.1",
67
+ "vitest": "^0.29.8",
68
+ "vue-tsc": "^1.3.12"
38
69
  }
39
70
  }
@@ -1,28 +1,55 @@
1
1
  <template>
2
- <select v-if="!options" disabled>
3
- <option>{{ loadingText || 'Loading...' }}</option>
2
+ <select v-if="!renderOptions" disabled>
3
+ <option>{{ props.loadingText || 'Loading...' }}</option>
4
4
  </select>
5
5
  <select v-else v-model="selectedItem">
6
- <option v-if="nullText" :value="null">{{ nullText }}</option>
7
- <option v-for="option in options" :key="option.id" :value="option">
8
- {{ textKey ? option[textKey] : option }}
6
+ <option v-if="props.nullText" :value="null">{{ props.nullText }}</option>
7
+ <option v-for="(renderOption, index) in renderOptions" :key="index" :value="options?.[index]">
8
+ {{ renderOption }}
9
9
  </option>
10
10
  </select>
11
11
  </template>
12
12
 
13
- <script>
14
- export default {
15
- props: ['modelValue', 'url', 'params', 'itemsKey', 'preprocessor', 'textKey', 'nullText', 'loadingText'],
13
+ <script lang="ts">
14
+ import { computed, defineComponent, defineEmits, defineProps, ref } from 'vue';
16
15
 
17
- data() {
18
- return {
19
- options: null,
20
- selectedItem: null
21
- };
16
+ // todo: make type safe when Vue alpha is released
17
+
18
+ type GenericObject = { [key: string]: any };
19
+
20
+ export default defineComponent({
21
+ setup() {
22
+ const props = defineProps<{
23
+ modelValue: any;
24
+ loadFn: () => Promise<GenericObject[]>;
25
+ nullText?: string;
26
+ loadingText?: string;
27
+ displayKey?: string;
28
+ preprocesor?: (option: GenericObject) => string;
29
+ }>();
30
+
31
+ defineEmits(['update:modelValue']);
32
+
33
+ const options = ref<GenericObject[] | null>(null);
34
+ const renderOptions = computed(() => {
35
+ if (!options.value) {
36
+ return null;
37
+ }
38
+
39
+ const result = options.value.map(option => {
40
+ return props.preprocesor ? props.preprocesor(option) : option[props.displayKey ?? ''];
41
+ });
42
+
43
+ return result;
44
+ });
45
+
46
+ const selectedItem = ref<GenericObject | null>(props.modelValue ?? null);
47
+
48
+ return { props, options, renderOptions, selectedItem };
22
49
  },
23
50
 
24
51
  watch: {
25
- params() {
52
+ loadFn() {
26
53
  this.load();
27
54
  },
28
55
 
@@ -31,7 +58,7 @@ export default {
31
58
  },
32
59
 
33
60
  modelValue() {
34
- this.selectedItem = this.modelValue;
61
+ this.selectedItem = this.props.modelValue;
35
62
  }
36
63
  },
37
64
 
@@ -41,14 +68,8 @@ export default {
41
68
 
42
69
  methods: {
43
70
  async load() {
44
- this.options = null;
45
- let params = this.params ? { params: this.params } : undefined;
46
- let result = await this.$http.get(this.url, params);
47
- let options = this.itemsKey ? result.data[this.itemsKey] : result.data;
48
- this.preprocessor && this.preprocessor(options);
49
- this.options = options;
50
- this.selectedItem = this.modelValue;
71
+ this.options = await this.props.loadFn();
51
72
  }
52
73
  }
53
- };
74
+ });
54
75
  </script>
@@ -0,0 +1,45 @@
1
+ import AlertModal from './alert-modal.vue';
2
+ import { createModalInjection, presentModal, removeModalInjection } from './modal-container';
3
+
4
+ function resolveAlertParams(titleOrMessage: string | Error, message?: string | Error) {
5
+ const title = message ? (titleOrMessage as string) : undefined;
6
+ const resolvedMessage = message ?? titleOrMessage;
7
+ return { title, message: resolvedMessage };
8
+ }
9
+
10
+ export async function showAlert(title: string, message: string | Error): Promise<void>;
11
+ export async function showAlert(message: string | Error): Promise<void>;
12
+ export async function showAlert(titleOrMessage: string | Error, message?: string | Error): Promise<void> {
13
+ await presentModal(AlertModal, resolveAlertParams(titleOrMessage, message));
14
+ }
15
+
16
+ export async function showConfirm(title: string, message: string): Promise<boolean>;
17
+ export async function showConfirm(message: string): Promise<boolean>;
18
+ export async function showConfirm(titleOrMessage: string, message?: string): Promise<boolean> {
19
+ const result = await presentModal(AlertModal, {
20
+ ...resolveAlertParams(titleOrMessage, message),
21
+ shouldConfirm: true
22
+ });
23
+ return result === true;
24
+ }
25
+
26
+ export async function showConfirmDestroy(title: string, message: string): Promise<boolean>;
27
+ export async function showConfirmDestroy(message: string): Promise<boolean>;
28
+ export async function showConfirmDestroy(titleOrMessage: string, message?: string): Promise<boolean> {
29
+ const result = await presentModal(AlertModal, {
30
+ ...resolveAlertParams(titleOrMessage, message),
31
+ shouldConfirm: true,
32
+ classes: ['destructive']
33
+ });
34
+ return result === true;
35
+ }
36
+
37
+ export function showWait(title: string, message: string): () => void;
38
+ export function showWait(message: string): () => void;
39
+ export function showWait(titleOrMessage: string, message?: string): () => void {
40
+ const injection = createModalInjection(AlertModal, {
41
+ ...resolveAlertParams(titleOrMessage, message),
42
+ callback: () => {}
43
+ });
44
+ return () => removeModalInjection(injection);
45
+ }
@@ -0,0 +1,68 @@
1
+ <template>
2
+ <Modal class="vf-alert" :class="classes">
3
+ <template v-if="!isBare" v-slot:header>
4
+ <h1>{{ title }}</h1>
5
+ </template>
6
+
7
+ <div v-if="isHtml" :innerHtml="message" class="user-message"></div>
8
+ <div v-else :innerText="textMessage"></div>
9
+
10
+ <template v-if="!isBare" v-slot:footer>
11
+ <template v-if="shouldConfirm">
12
+ <button class="primary" @click="() => callback(true)" v-autofocus>Confirm</button>
13
+ <button class="default" @click="() => callback(false)">Cancel</button>
14
+ </template>
15
+ <button v-else class="default" @click="() => callback(true)" v-autofocus>OK</button>
16
+ </template>
17
+ </Modal>
18
+ </template>
19
+
20
+ <script lang="ts" setup>
21
+ import { computed, defineProps } from 'vue';
22
+
23
+ import { formatError } from '@/helpers/error';
24
+
25
+ import Modal from './modal.vue';
26
+
27
+ const props = defineProps<{
28
+ isBare?: boolean;
29
+ isHtml?: boolean;
30
+ classes?: string[];
31
+ title?: string;
32
+ message: string | Error;
33
+ shouldConfirm?: boolean;
34
+ callback: (ok: boolean) => void;
35
+ }>();
36
+
37
+ const textMessage = computed(() => {
38
+ if (props.message instanceof Error) {
39
+ return formatError(props.message);
40
+ }
41
+
42
+ return props.message;
43
+ });
44
+ </script>
45
+
46
+ <style lang="scss">
47
+ .vf-modal-wrap.vf-alert {
48
+ .vf-modal {
49
+ max-width: 800px;
50
+
51
+ > .vf-modal-content {
52
+ padding: 12px;
53
+ }
54
+ }
55
+
56
+ &.wait {
57
+ .vf-modal-content {
58
+ text-align: center;
59
+ }
60
+ }
61
+
62
+ &.destructive {
63
+ button.primary {
64
+ color: red;
65
+ }
66
+ }
67
+ }
68
+ </style>
@@ -0,0 +1,51 @@
1
+ <template>
2
+ <VfSmartSelect v-model="selectedItem" :options="options" :formatter="ezFormatter" :null-title="nullTitle" />
3
+ </template>
4
+
5
+ <script lang="ts" setup>
6
+ import { isEqual } from 'lodash';
7
+ import { computed, defineEmits, defineProps, ref, watch } from 'vue';
8
+
9
+ import VfSmartSelect from './smart-select.vue';
10
+
11
+ type GenericObject = { [key: string]: any };
12
+
13
+ const props = defineProps<{
14
+ modelValue: string | null | undefined;
15
+ nullTitle?: string;
16
+ placeholder?: string;
17
+ options: Record<string, string> | string[];
18
+ formatter?: (value: any) => string;
19
+ }>();
20
+
21
+ const options = computed(() => {
22
+ return Array.isArray(props.options)
23
+ ? props.options.map(o => ({ value: o, label: o }))
24
+ : Object.entries(props.options).map(([value, label]) => ({
25
+ value,
26
+ label
27
+ }));
28
+ });
29
+
30
+ const ezFormatter = computed(() => {
31
+ if (props.formatter) {
32
+ return (o: GenericObject) => props.formatter?.(o.label);
33
+ }
34
+ return (o: GenericObject) => o.label;
35
+ });
36
+
37
+ const emit = defineEmits<{
38
+ (e: 'update:modelValue', value: string | null): void;
39
+ }>();
40
+
41
+ const selectedItem = ref<GenericObject | null>(options.value.find(o => o.value === props.modelValue) ?? null);
42
+ watch(
43
+ () => props.modelValue,
44
+ value => {
45
+ selectedItem.value = options.value.find(o => o.value === value) ?? null;
46
+ }
47
+ );
48
+ watch(selectedItem, value => {
49
+ emit('update:modelValue', value ? options.value.find(o => isEqual(o, value))?.value ?? null : null);
50
+ });
51
+ </script>
@@ -0,0 +1,10 @@
1
+ import VfAjaxSelect from './ajax-select.vue';
2
+ import VfAlertModal from './alert-modal.vue';
3
+ import VfEzSmartSelect from './ez-smart-select.vue';
4
+ import VfModal from './modal.vue';
5
+ import VfSmartSelect from './smart-select.vue';
6
+
7
+ export * from './alert-helpers';
8
+ export * from './modal-container';
9
+
10
+ export { VfAjaxSelect, VfAlertModal, VfEzSmartSelect, VfModal, VfSmartSelect };
@@ -0,0 +1,131 @@
1
+ import type { Writable } from 'type-fest';
2
+ import {
3
+ type AllowedComponentProps,
4
+ type ComponentInternalInstance,
5
+ type ComponentPublicInstance,
6
+ type ComputedOptions,
7
+ defineComponent,
8
+ h,
9
+ markRaw,
10
+ type MethodOptions,
11
+ type Raw,
12
+ reactive,
13
+ renderList,
14
+ type VNode,
15
+ type VNodeProps
16
+ } from 'vue';
17
+
18
+ interface ModalInjection<C extends Vue__ComponentPublicInstanceConstructor> {
19
+ id: string;
20
+ component: Raw<C>;
21
+ props: ComponentProps<C>;
22
+ vnode: VNode;
23
+ }
24
+
25
+ let modalCount = 0;
26
+ const ModalInjections: ModalInjection<any>[] = reactive([]);
27
+
28
+ export const ModalContainer = defineComponent({
29
+ setup() {
30
+ return () =>
31
+ h('div', { id: 'modal-container' }, [
32
+ renderList(ModalInjections, injection => {
33
+ return injection.vnode;
34
+ })
35
+ ]);
36
+ }
37
+ });
38
+
39
+ // copied in from Vue since it's not exported
40
+ export type Vue__ComponentPublicInstanceConstructor<
41
+ T extends ComponentPublicInstance<Props, RawBindings, D, C, M> = ComponentPublicInstance<any>,
42
+ Props = any,
43
+ RawBindings = any,
44
+ D = any,
45
+ C extends ComputedOptions = ComputedOptions,
46
+ M extends MethodOptions = MethodOptions
47
+ > = {
48
+ __isFragment?: never;
49
+ __isTeleport?: never;
50
+ __isSuspense?: never;
51
+ new (...args: any[]): T;
52
+ };
53
+
54
+ export type ComponentConfig<T extends Vue__ComponentPublicInstanceConstructor> = T extends Vue__ComponentPublicInstanceConstructor<infer P>
55
+ ? P
56
+ : never;
57
+ export type ComponentProps<T extends Vue__ComponentPublicInstanceConstructor> = Writable<
58
+ Omit<ComponentConfig<T>['$props'], keyof VNodeProps | keyof AllowedComponentProps>
59
+ >;
60
+
61
+ export type AnyComponentPublicInstance = { $: ComponentInternalInstance };
62
+
63
+ interface PropsWithCallback<T> {
64
+ callback?: (result: T) => void;
65
+ }
66
+ type ComponentReturn<C extends Vue__ComponentPublicInstanceConstructor> = ComponentProps<C> extends PropsWithCallback<infer R> ? R : never;
67
+
68
+ export function createModalInjection<C extends Vue__ComponentPublicInstanceConstructor>(component: C, props: ComponentProps<C>): ModalInjection<C> {
69
+ // create or reconfigure the existing modal target
70
+ // re-injecting every time keeps the modal container at the very end of the DOM
71
+ const targetEl = document.getElementById('vf-modal-target') ?? document.createElement('div');
72
+ targetEl.id = 'vf-modal-target';
73
+ targetEl.removeAttribute('inert');
74
+ document.body.appendChild(targetEl);
75
+
76
+ const rawComponent = markRaw(component);
77
+
78
+ // todo: dunno what's going on with types here
79
+ const injection: ModalInjection<C> = {
80
+ id: String(++modalCount),
81
+ component: rawComponent as any,
82
+ props,
83
+ vnode: h(rawComponent, props)
84
+ };
85
+ ModalInjections.push(injection);
86
+
87
+ return injection;
88
+ }
89
+
90
+ export function removeModalInjection(injection: ModalInjection<any>) {
91
+ const index = ModalInjections.indexOf(injection);
92
+ if (index >= 0) {
93
+ ModalInjections.splice(index, 1);
94
+ }
95
+ }
96
+
97
+ export function removeModalInjectionByInstance(instance: AnyComponentPublicInstance) {
98
+ removeModalInjectionByInternalInstance(instance.$);
99
+ }
100
+
101
+ export function removeModalInjectionByInternalInstance(instance: ComponentInternalInstance) {
102
+ let targetInstance: ComponentInternalInstance | null = instance;
103
+ while (targetInstance && !removeModalInjectionByVnode(targetInstance.vnode)) {
104
+ targetInstance = targetInstance.parent;
105
+ }
106
+ }
107
+
108
+ export function removeModalInjectionByVnode(vnode: VNode) {
109
+ const injectionIdx = ModalInjections.findIndex(i => i.vnode.component === vnode.component);
110
+ if (injectionIdx >= 0) {
111
+ ModalInjections[injectionIdx].props.callback?.(undefined);
112
+ ModalInjections.splice(injectionIdx, 1);
113
+ return true;
114
+ }
115
+ return false;
116
+ }
117
+
118
+ export async function presentModal<C extends Vue__ComponentPublicInstanceConstructor, R extends ComponentReturn<C>>(
119
+ component: C,
120
+ props: Omit<ComponentProps<C>, 'callback'>
121
+ ): Promise<R | undefined> {
122
+ return new Promise<R>(resolve => {
123
+ let modalInjection: ModalInjection<C> | null = null;
124
+ const callback = (result: R) => {
125
+ removeModalInjection(modalInjection!);
126
+ resolve(result);
127
+ };
128
+ const resolvedProps = { ...props, callback } as ComponentProps<C>;
129
+ modalInjection = createModalInjection(component, resolvedProps);
130
+ });
131
+ }