@signal24/vue-foundation 3.8.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.cjs +35 -0
- package/.prettierrc.json +4 -2
- package/dist/src/components/ajax-select.vue.d.ts +21 -0
- package/dist/src/components/alert-helpers.d.ts +8 -0
- package/dist/src/components/alert-modal.vue.d.ts +27 -0
- package/dist/src/components/ez-smart-select.vue.d.ts +27 -0
- package/dist/src/components/index.d.ts +8 -0
- package/dist/src/components/modal-container.d.ts +33 -0
- package/dist/src/components/modal.vue.d.ts +34 -0
- package/dist/src/components/smart-select.vue.d.ts +121 -0
- package/dist/src/config.d.ts +8 -0
- package/dist/src/directives/autofocus.d.ts +2 -0
- package/dist/src/directives/confirm-button.d.ts +2 -0
- package/dist/src/directives/date-input.d.ts +2 -0
- package/dist/src/directives/datetime.d.ts +2 -0
- package/dist/src/directives/disabled.d.ts +2 -0
- package/dist/src/directives/duration.d.ts +2 -0
- package/dist/src/directives/index.d.ts +24 -0
- package/dist/src/directives/infinite-scroll.d.ts +3 -0
- package/dist/src/directives/readonly.d.ts +2 -0
- package/dist/src/directives/tooltip.d.ts +41 -0
- package/dist/src/filters/index.d.ts +39 -0
- package/dist/src/helpers/array.d.ts +3 -0
- package/dist/src/helpers/context-menu.d.ts +13 -0
- package/dist/src/helpers/delay.d.ts +2 -0
- package/dist/src/helpers/error.d.ts +7 -0
- package/dist/src/helpers/index.d.ts +9 -0
- package/dist/src/helpers/mask.d.ts +15 -0
- package/dist/src/helpers/number.d.ts +1 -0
- package/dist/src/helpers/object.d.ts +2 -0
- package/dist/src/helpers/openapi.d.ts +34 -0
- package/dist/src/helpers/string.d.ts +5 -0
- package/dist/src/hooks/index.d.ts +2 -0
- package/dist/src/hooks/infinite-scroll.d.ts +30 -0
- package/dist/src/hooks/resize-watcher.d.ts +1 -0
- package/dist/src/index.d.ts +8 -0
- package/dist/src/types.d.ts +14 -0
- package/dist/src/vite-plugins/index.d.ts +1 -0
- package/dist/src/vite-plugins/index.js +2 -0
- package/dist/src/vite-plugins/vite-openapi-plugin.d.ts +4 -0
- package/dist/src/vite-plugins/vite-openapi-plugin.js +58 -0
- package/dist/vue-foundation.css +1 -0
- package/dist/vue-foundation.es.js +1129 -0
- package/package.json +44 -16
- package/src/components/ajax-select.vue +44 -23
- package/src/components/alert-helpers.ts +45 -0
- package/src/components/alert-modal.vue +68 -0
- package/src/components/ez-smart-select.vue +51 -0
- package/src/components/index.ts +10 -0
- package/src/components/modal-container.ts +131 -0
- package/src/components/modal.vue +44 -129
- package/src/components/smart-select.vue +196 -243
- package/src/config.ts +15 -0
- package/src/directives/autofocus.ts +20 -0
- package/src/directives/confirm-button.ts +50 -0
- package/src/directives/date-input.ts +19 -0
- package/src/directives/datetime.ts +48 -0
- package/src/directives/disabled.ts +30 -0
- package/src/directives/duration.ts +79 -0
- package/src/directives/index.ts +37 -0
- package/src/directives/infinite-scroll.ts +9 -0
- package/src/directives/readonly.ts +15 -0
- package/src/directives/tooltip.ts +190 -0
- package/src/filters/index.ts +79 -0
- package/src/helpers/array.ts +7 -0
- package/src/helpers/context-menu.ts +108 -0
- package/src/helpers/delay.ts +2 -0
- package/src/helpers/error.ts +41 -0
- package/src/helpers/index.ts +9 -0
- package/src/helpers/mask.ts +105 -0
- package/src/helpers/number.ts +3 -0
- package/src/helpers/object.ts +19 -0
- package/src/helpers/openapi.ts +82 -0
- package/src/helpers/string.ts +27 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/infinite-scroll.ts +107 -0
- package/src/hooks/resize-watcher.ts +8 -0
- package/src/index.ts +14 -0
- package/src/types.ts +14 -0
- package/src/vite-plugins/index.ts +2 -0
- package/src/vite-plugins/vite-openapi-plugin.ts +71 -0
- package/tsconfig.app.json +22 -0
- package/tsconfig.json +14 -0
- package/tsconfig.node.json +9 -0
- package/tsconfig.vite-plugins.json +10 -0
- package/tsconfig.vitest.json +9 -0
- package/vite.config.js +37 -35
- package/vitest.config.js +17 -0
- package/.eslintrc.js +0 -16
- package/CHANGES.md +0 -13
- package/postcss.config.cjs +0 -5
- package/src/app.js +0 -25
- package/src/components/alert.vue +0 -130
- package/src/components/index.js +0 -12
- package/src/config.js +0 -11
- package/src/directives/autofocus.js +0 -17
- package/src/directives/confirm-button.js +0 -40
- package/src/directives/date-input.js +0 -18
- package/src/directives/datetime.js +0 -46
- package/src/directives/disabled.js +0 -28
- package/src/directives/duration.js +0 -72
- package/src/directives/index.js +0 -10
- package/src/directives/infinite-scroll.js +0 -17
- package/src/directives/readonly.js +0 -17
- package/src/directives/tooltip.js +0 -178
- package/src/directives/user-text.js +0 -11
- package/src/filters/index.js +0 -82
- package/src/helpers/array.js +0 -99
- package/src/helpers/context-menu.js +0 -66
- package/src/helpers/delay.js +0 -3
- package/src/helpers/error.js +0 -36
- package/src/helpers/http.js +0 -44
- package/src/helpers/index.js +0 -9
- package/src/helpers/mask.js +0 -90
- package/src/helpers/number.js +0 -6
- package/src/helpers/string.js +0 -36
- package/src/helpers/vue.js +0 -5
- package/src/index.js +0 -33
- package/src/plugins/index.js +0 -10
- package/src/plugins/infinite-scroll/hook.js +0 -30
- package/src/plugins/infinite-scroll.js +0 -100
- package/src/plugins/resize-watcher.js +0 -28
package/package.json
CHANGED
|
@@ -1,39 +1,67 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@signal24/vue-foundation",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "4.0.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",
|
|
8
7
|
"exports": {
|
|
9
8
|
".": {
|
|
10
|
-
|
|
11
|
-
"require": "./dist/vue-foundation.umd.js"
|
|
9
|
+
"import": "./dist/vue-foundation.es.js"
|
|
12
10
|
},
|
|
13
11
|
"./dist/vue-foundation.css": {
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
"import": "./dist/vue-foundation.css",
|
|
13
|
+
"require": "./dist/vue-foundation.css"
|
|
14
|
+
},
|
|
15
|
+
"./vite-plugins": {
|
|
16
|
+
"import": "./dist/src/vite-plugins/index.js",
|
|
17
|
+
"types": "./dist/src/vite-plugins/index.d.ts"
|
|
16
18
|
}
|
|
17
19
|
},
|
|
20
|
+
"typings": "./dist/src/index.d.ts",
|
|
18
21
|
"scripts": {
|
|
19
22
|
"dev": "vite",
|
|
20
|
-
"build": "vite build",
|
|
21
|
-
"preview": "vite preview"
|
|
23
|
+
"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",
|
|
24
|
+
"preview": "vite preview",
|
|
25
|
+
"test:types": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false",
|
|
26
|
+
"test:unit": "vitest",
|
|
27
|
+
"test:e2e": "start-server-and-test preview http://localhost:4173 'cypress run --e2e'",
|
|
28
|
+
"test:e2e:dev": "start-server-and-test 'vite dev --port 4173' http://localhost:4173 'cypress open --e2e'",
|
|
29
|
+
"test:e2e:dev:remote": "DISPLAY=:0 yarn test:e2e:dev",
|
|
30
|
+
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts --fix --ignore-path .gitignore",
|
|
31
|
+
"format": "prettier --write ."
|
|
22
32
|
},
|
|
23
33
|
"license": "MIT",
|
|
24
34
|
"dependencies": {
|
|
25
|
-
"
|
|
26
|
-
"jquery": "^3.6.4",
|
|
35
|
+
"date-fns": "^2.29.3",
|
|
27
36
|
"lodash": "^4.17.21",
|
|
28
|
-
"
|
|
29
|
-
"vue-stash-nested": "fergusean/vue-stash-nested#vue-foundation-3",
|
|
37
|
+
"type-fest": "^3.8.0",
|
|
30
38
|
"vue": "^3.2.47"
|
|
31
39
|
},
|
|
32
40
|
"devDependencies": {
|
|
41
|
+
"@nabla/vite-plugin-eslint": "^1.5.0",
|
|
42
|
+
"@rushstack/eslint-patch": "^1.2.0",
|
|
43
|
+
"@types/jsdom": "^21.1.1",
|
|
44
|
+
"@types/lodash": "^4.14.192",
|
|
45
|
+
"@types/node": "^18.15.11",
|
|
33
46
|
"@vitejs/plugin-vue": "^4.1.0",
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"vue": "^
|
|
47
|
+
"@vue/eslint-config-prettier": "^7.1.0",
|
|
48
|
+
"@vue/eslint-config-typescript": "^11.0.2",
|
|
49
|
+
"@vue/test-utils": "^2.3.2",
|
|
50
|
+
"@vue/tsconfig": "^0.1.3",
|
|
51
|
+
"cypress": "^12.9.0",
|
|
52
|
+
"eslint": "^8.37.0",
|
|
53
|
+
"eslint-plugin-cypress": "^2.13.2",
|
|
54
|
+
"eslint-plugin-simple-import-sort": "^10.0.0",
|
|
55
|
+
"eslint-plugin-unused-imports": "^2.0.0",
|
|
56
|
+
"eslint-plugin-vue": "^9.10.0",
|
|
57
|
+
"jsdom": "^21.1.1",
|
|
58
|
+
"openapi-typescript-codegen": "^0.23.0",
|
|
59
|
+
"prettier": "^2.8.7",
|
|
60
|
+
"sass": "^1.60.0",
|
|
61
|
+
"start-server-and-test": "^2.0.0",
|
|
62
|
+
"typescript": "~4.9.5",
|
|
63
|
+
"vite": "^4.2.1",
|
|
64
|
+
"vitest": "^0.29.8",
|
|
65
|
+
"vue-tsc": "^1.3.12"
|
|
38
66
|
}
|
|
39
67
|
}
|
|
@@ -1,28 +1,55 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<select v-if="!
|
|
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="
|
|
8
|
-
{{
|
|
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
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
+
}
|