@signal24/vue-foundation 4.3.6 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@signal24/vue-foundation",
3
3
  "type": "module",
4
- "version": "4.3.6",
4
+ "version": "4.4.0",
5
5
  "description": "Common components, directives, and helpers for Vue 3 apps",
6
6
  "module": "./dist/vue-foundation.es.js",
7
7
  "bin": {
@@ -1,5 +1,5 @@
1
1
  import AlertModal from './alert-modal.vue';
2
- import { createModalInjection, presentModal, removeModalInjection } from './modal-container';
2
+ import { createOverlayInjection, presentOverlay, removeOverlayInjection } from './overlay-container';
3
3
 
4
4
  function resolveAlertParams(titleOrMessage: string | Error, message?: string | Error) {
5
5
  const title = message ? (titleOrMessage as string) : undefined;
@@ -10,13 +10,13 @@ function resolveAlertParams(titleOrMessage: string | Error, message?: string | E
10
10
  export async function showAlert(title: string, message: string | Error): Promise<void>;
11
11
  export async function showAlert(message: string | Error): Promise<void>;
12
12
  export async function showAlert(titleOrMessage: string | Error, message?: string | Error): Promise<void> {
13
- await presentModal(AlertModal, resolveAlertParams(titleOrMessage, message));
13
+ await presentOverlay(AlertModal, resolveAlertParams(titleOrMessage, message));
14
14
  }
15
15
 
16
16
  export async function showConfirm(title: string, message: string): Promise<boolean>;
17
17
  export async function showConfirm(message: string): Promise<boolean>;
18
18
  export async function showConfirm(titleOrMessage: string, message?: string): Promise<boolean> {
19
- const result = await presentModal(AlertModal, {
19
+ const result = await presentOverlay(AlertModal, {
20
20
  ...resolveAlertParams(titleOrMessage, message),
21
21
  shouldConfirm: true
22
22
  });
@@ -26,7 +26,7 @@ export async function showConfirm(titleOrMessage: string, message?: string): Pro
26
26
  export async function showConfirmDestroy(title: string, message: string): Promise<boolean>;
27
27
  export async function showConfirmDestroy(message: string): Promise<boolean>;
28
28
  export async function showConfirmDestroy(titleOrMessage: string, message?: string): Promise<boolean> {
29
- const result = await presentModal(AlertModal, {
29
+ const result = await presentOverlay(AlertModal, {
30
30
  ...resolveAlertParams(titleOrMessage, message),
31
31
  shouldConfirm: true,
32
32
  classes: ['destructive']
@@ -37,11 +37,11 @@ export async function showConfirmDestroy(titleOrMessage: string, message?: strin
37
37
  export function showWait(title: string, message: string): () => void;
38
38
  export function showWait(message: string): () => void;
39
39
  export function showWait(titleOrMessage: string, message?: string): () => void {
40
- const injection = createModalInjection(AlertModal, {
40
+ const injection = createOverlayInjection(AlertModal, {
41
41
  ...resolveAlertParams(titleOrMessage, message),
42
42
  isBare: true,
43
43
  classes: ['wait'],
44
44
  callback: () => {}
45
45
  });
46
- return () => removeModalInjection(injection);
46
+ return () => removeOverlayInjection(injection);
47
47
  }
@@ -5,6 +5,6 @@ import VfModal from './modal.vue';
5
5
  import VfSmartSelect from './smart-select.vue';
6
6
 
7
7
  export * from './alert-helpers';
8
- export * from './modal-container';
8
+ export * from './overlay-container';
9
9
 
10
10
  export { VfAjaxSelect, VfAlertModal, VfEzSmartSelect, VfModal, VfSmartSelect };
@@ -1,27 +1,25 @@
1
1
  <template>
2
- <Teleport to="#vf-modal-target">
3
- <div :id="id" class="vf-overlay vf-modal-wrap" :class="props.class" ref="overlay">
4
- <form action="." class="vf-modal" :class="{ scrolls }" @submit.prevent="$emit('formSubmit')" ref="form">
5
- <div v-if="$slots.header" class="vf-modal-header">
6
- <slot name="header" />
7
- <i v-if="props.closeX" class="close" @click="closeParent"></i>
8
- </div>
9
- <div class="vf-modal-content">
10
- <slot />
11
- </div>
12
- <div v-if="$slots.footer" class="vf-modal-footer">
13
- <slot name="footer" />
14
- </div>
15
- </form>
16
- </div>
17
- </Teleport>
2
+ <div :id="id" class="vf-overlay vf-modal-wrap" :class="props.class" ref="overlay">
3
+ <form action="." class="vf-modal" :class="{ scrolls }" @submit.prevent="$emit('formSubmit')" ref="form">
4
+ <div v-if="$slots.header" class="vf-modal-header">
5
+ <slot name="header" />
6
+ <i v-if="props.closeX" class="close" @click="closeParent"></i>
7
+ </div>
8
+ <div class="vf-modal-content">
9
+ <slot />
10
+ </div>
11
+ <div v-if="$slots.footer" class="vf-modal-footer">
12
+ <slot name="footer" />
13
+ </div>
14
+ </form>
15
+ </div>
18
16
  </template>
19
17
 
20
18
  <script lang="ts" setup>
21
19
  import { getCurrentInstance, onBeforeUnmount, onMounted, ref } from 'vue';
22
20
 
23
21
  import { maskForm, unmaskForm } from '../helpers/mask';
24
- import { removeModalInjectionByInternalInstance } from './modal-container';
22
+ import { dismissOverlayInjectionByInternalInstance } from './overlay-container';
25
23
 
26
24
  const instance = getCurrentInstance();
27
25
 
@@ -72,7 +70,7 @@ function handleEscapeKey(e: KeyboardEvent) {
72
70
  }
73
71
 
74
72
  function closeParent() {
75
- removeModalInjectionByInternalInstance(instance!);
73
+ dismissOverlayInjectionByInternalInstance(instance!);
76
74
  }
77
75
 
78
76
  function mask() {
@@ -0,0 +1,103 @@
1
+ <template>
2
+ <div class="vf-overlay-anchor" :class="anchorClasses" :style="anchorStyles" @click.stop="removeOverlay">
3
+ <slot></slot>
4
+ </div>
5
+ </template>
6
+
7
+ <script lang="ts" setup>
8
+ import type { CSSProperties } from 'vue';
9
+ import { getCurrentInstance, onMounted, ref } from 'vue';
10
+
11
+ import { dismissOverlayInjectionById } from '.';
12
+ import type { OverlayAnchorOptions, OverlayAnchorOptionsObject } from './overlay-types';
13
+
14
+ const props = defineProps<{
15
+ overlayId: string;
16
+ anchor: OverlayAnchorOptions;
17
+ }>();
18
+
19
+ const anchorEl = props.anchor instanceof HTMLElement ? props.anchor : props.anchor.el;
20
+
21
+ const anchorStyles = ref<CSSProperties>({ visibility: 'hidden', top: '0', left: '0' });
22
+ const anchorClasses = ref<string[]>([]);
23
+ const instance = getCurrentInstance();
24
+
25
+ onMounted(updateAttributes);
26
+
27
+ function updateAttributes() {
28
+ const overlayEl = instance!.vnode.el as HTMLElement;
29
+ const { styles, classes } = computeAnchoredStyle(overlayEl, anchorEl);
30
+ anchorStyles.value = styles;
31
+ anchorClasses.value = classes;
32
+ }
33
+
34
+ function computeAnchoredStyle(
35
+ overlayEl: HTMLElement,
36
+ anchorEl: HTMLElement
37
+ ): {
38
+ styles: CSSProperties;
39
+ classes: string[];
40
+ } {
41
+ const anchorOpts: Omit<OverlayAnchorOptionsObject, 'el'> = props.anchor instanceof HTMLElement ? {} : props.anchor;
42
+
43
+ const anchorRect = anchorEl.getBoundingClientRect();
44
+ const overlayRect = overlayEl.getBoundingClientRect();
45
+
46
+ if (anchorOpts.matchWidth) {
47
+ overlayRect.width = anchorRect.width;
48
+ }
49
+ if (anchorOpts.matchHeight) {
50
+ overlayRect.height = anchorRect.height;
51
+ }
52
+
53
+ const classes: string[] = anchorOpts.class ? (Array.isArray(anchorOpts.class) ? anchorOpts.class : [anchorOpts.class]) : [];
54
+ let top: number, left: number;
55
+
56
+ if (anchorOpts.y === 'center') {
57
+ top = anchorRect.top + anchorRect.height / 2 - overlayRect.height / 2;
58
+ classes.push('anchored-center-y');
59
+ } else {
60
+ const canDisplayBelow = anchorRect.bottom + overlayRect.height < window.innerHeight || anchorOpts.y === 'below';
61
+ const canDisplayBelowResolved = canDisplayBelow && anchorOpts.y !== 'above';
62
+ top = canDisplayBelowResolved ? anchorRect.bottom : anchorRect.top - overlayRect.height;
63
+ classes.push(canDisplayBelowResolved ? 'anchored-top' : 'anchored-bottom');
64
+ }
65
+
66
+ if (anchorOpts.x === 'center') {
67
+ left = anchorRect.left + anchorRect.width / 2 - overlayRect.width / 2;
68
+ classes.push('anchored-center-x');
69
+ } else {
70
+ const canAlignLefts = anchorRect.left + overlayRect.width < window.innerWidth || anchorOpts.x === 'left';
71
+ const canAlignLeftsResolved = canAlignLefts && anchorOpts.x !== 'right';
72
+ left = canAlignLeftsResolved ? anchorRect.left : anchorRect.right - overlayRect.width;
73
+ classes.push(canAlignLeftsResolved ? 'anchored-left' : 'anchored-right');
74
+ }
75
+
76
+ return {
77
+ styles: {
78
+ top: `${top}px`,
79
+ left: `${left}px`,
80
+ ...(anchorOpts.matchWidth ? { width: `${overlayRect.width}px` } : {}),
81
+ ...(anchorOpts.matchHeight ? { height: `${overlayRect.height}px` } : {})
82
+ },
83
+ classes
84
+ };
85
+ }
86
+
87
+ function removeOverlay() {
88
+ window.removeEventListener('click', removeOverlay);
89
+ dismissOverlayInjectionById(props.overlayId);
90
+ }
91
+
92
+ onMounted(() => {
93
+ setTimeout(() => {
94
+ window.addEventListener('click', removeOverlay);
95
+ }, 10);
96
+ });
97
+ </script>
98
+
99
+ <style lang="scss">
100
+ .vf-overlay-anchor {
101
+ position: absolute;
102
+ }
103
+ </style>
@@ -0,0 +1,176 @@
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
+ Teleport,
15
+ type VNode,
16
+ type VNodeProps
17
+ } from 'vue';
18
+
19
+ import OverlayAnchor from './overlay-anchor.vue';
20
+ import type { OverlayAnchorOptions } from './overlay-types';
21
+
22
+ interface OverlayOptions {
23
+ anchor?: OverlayAnchorOptions;
24
+ }
25
+
26
+ interface OverlayInjection<C extends OverlayComponent> {
27
+ id: string;
28
+ component: OverlayComponentUnwrapped<C>;
29
+ props: OverlayComponentProps<C>;
30
+ options: OverlayOptions;
31
+ vnode: VNode;
32
+ wrapperVnode?: VNode;
33
+ }
34
+
35
+ let overlayCount = 0;
36
+ const OverlayInjections: OverlayInjection<any>[] = reactive([]);
37
+
38
+ export const OverlayContainer = defineComponent({
39
+ setup() {
40
+ return () =>
41
+ h('div', [
42
+ renderList(OverlayInjections, injection => {
43
+ return h(Teleport, { key: injection.id, to: '#vf-overlay-target' }, [injection.wrapperVnode ?? injection.vnode]);
44
+ })
45
+ ]);
46
+ }
47
+ });
48
+
49
+ // copied in from Vue since it's not exported
50
+ // tood: it may be a lot easier than this. see the docs for props passed to "h"
51
+ export type Vue__ComponentPublicInstanceConstructor<
52
+ T extends ComponentPublicInstance<Props, RawBindings, D, C, M> = ComponentPublicInstance<any>,
53
+ Props = any,
54
+ RawBindings = any,
55
+ D = any,
56
+ C extends ComputedOptions = ComputedOptions,
57
+ M extends MethodOptions = MethodOptions
58
+ > = {
59
+ __isFragment?: never;
60
+ __isTeleport?: never;
61
+ __isSuspense?: never;
62
+ new (...args: any[]): T;
63
+ };
64
+
65
+ export type ObjectComponentConfig<T extends Vue__ComponentPublicInstanceConstructor> = T extends Vue__ComponentPublicInstanceConstructor<infer P>
66
+ ? P
67
+ : never;
68
+ export type ObjectComponentProps<T extends Vue__ComponentPublicInstanceConstructor> = Writable<
69
+ Omit<ObjectComponentConfig<T>['$props'], keyof VNodeProps | keyof AllowedComponentProps>
70
+ >;
71
+
72
+ type ObjectOrDefault<T> = T extends object ? T : PropsWithCallback<{}>;
73
+ export type OverlayComponent = Vue__ComponentPublicInstanceConstructor | ((props: any) => any);
74
+ export type OverlayComponentConfig<T> = T extends Vue__ComponentPublicInstanceConstructor
75
+ ? {
76
+ props: ObjectComponentProps<T>;
77
+ component: Raw<T>;
78
+ }
79
+ : T extends (props: infer P) => any
80
+ ? {
81
+ props: Omit<ObjectOrDefault<P>, keyof VNodeProps | keyof AllowedComponentProps>;
82
+ component: T;
83
+ }
84
+ : never;
85
+ export type OverlayComponentUnwrapped<T extends OverlayComponent> = OverlayComponentConfig<T>['component'];
86
+ export type OverlayComponentProps<T extends OverlayComponent> = OverlayComponentConfig<T>['props'];
87
+
88
+ interface PropsWithCallback<T> {
89
+ callback?: (result: T) => void;
90
+ }
91
+ type ComponentReturn<M extends OverlayComponent> = OverlayComponentProps<M> extends PropsWithCallback<infer R> ? R : never;
92
+
93
+ export type AnyComponentPublicInstance = { $: ComponentInternalInstance };
94
+
95
+ export function createOverlayInjection<C extends OverlayComponent>(
96
+ component: C,
97
+ props: OverlayComponentProps<C>,
98
+ options?: OverlayOptions
99
+ ): OverlayInjection<C> {
100
+ // create or reconfigure the existing overlay target
101
+ // re-injecting every time keeps the overlay container at the very end of the DOM
102
+ const targetEl = document.getElementById('vf-overlay-target') ?? document.createElement('div');
103
+ targetEl.id = 'vf-overlay-target';
104
+ targetEl.removeAttribute('inert');
105
+ document.body.appendChild(targetEl);
106
+
107
+ const overlayId = String(++overlayCount);
108
+ const rawComponent = markRaw(component);
109
+ const vnode = h(rawComponent, props);
110
+ const wrapperVnode = options?.anchor ? h(OverlayAnchor, { overlayId, anchor: options.anchor }, () => [vnode]) : undefined;
111
+
112
+ // todo: dunno what's going on with types here
113
+ const injection: OverlayInjection<C> = {
114
+ id: overlayId,
115
+ component: rawComponent as any,
116
+ props,
117
+ options: options ?? {},
118
+ vnode,
119
+ wrapperVnode
120
+ };
121
+ OverlayInjections.push(injection);
122
+
123
+ return injection;
124
+ }
125
+
126
+ export function dismissOverlayInjectionByInstance(instance: AnyComponentPublicInstance) {
127
+ dismissOverlayInjectionByInternalInstance(instance.$);
128
+ }
129
+
130
+ export function dismissOverlayInjectionByInternalInstance(instance: ComponentInternalInstance) {
131
+ let targetInstance: ComponentInternalInstance | null = instance;
132
+ while (targetInstance && !dismissOverlayInjectionByVnode(targetInstance.vnode)) {
133
+ targetInstance = targetInstance.parent;
134
+ }
135
+ }
136
+
137
+ export function dismissOverlayInjectionByVnode(vnode: VNode) {
138
+ const injectionIdx = OverlayInjections.findIndex(i => i.vnode.component === vnode.component);
139
+ if (injectionIdx >= 0) {
140
+ OverlayInjections[injectionIdx].props.callback();
141
+ return true;
142
+ }
143
+ return false;
144
+ }
145
+
146
+ export function dismissOverlayInjectionById(id: string) {
147
+ const injectionIdx = OverlayInjections.findIndex(i => i.id === id);
148
+ if (injectionIdx >= 0) {
149
+ OverlayInjections[injectionIdx].props.callback();
150
+ return true;
151
+ }
152
+ return false;
153
+ }
154
+
155
+ export function removeOverlayInjection(injection: OverlayInjection<any>) {
156
+ const index = OverlayInjections.indexOf(injection);
157
+ if (index >= 0) {
158
+ OverlayInjections.splice(index, 1);
159
+ }
160
+ }
161
+
162
+ export async function presentOverlay<C extends OverlayComponent, R extends ComponentReturn<C>>(
163
+ component: C,
164
+ props: Omit<OverlayComponentProps<C>, 'callback'>,
165
+ options?: OverlayOptions
166
+ ): Promise<R | undefined> {
167
+ return new Promise<R>(resolve => {
168
+ let overlayInjection: OverlayInjection<C> | null = null;
169
+ const callback = (result: R) => {
170
+ removeOverlayInjection(overlayInjection!);
171
+ resolve(result);
172
+ };
173
+ const resolvedProps = { ...props, callback } as OverlayComponentProps<C>;
174
+ overlayInjection = createOverlayInjection(component, resolvedProps, options);
175
+ });
176
+ }
@@ -0,0 +1,10 @@
1
+ export interface OverlayAnchorOptionsObject {
2
+ el: HTMLElement;
3
+ class?: string | string[];
4
+ x?: 'auto' | 'left' | 'center' | 'right';
5
+ y?: 'auto' | 'above' | 'center' | 'below';
6
+ matchWidth?: boolean;
7
+ matchHeight?: boolean;
8
+ }
9
+
10
+ export type OverlayAnchorOptions = HTMLElement | OverlayAnchorOptionsObject;
@@ -1,4 +1,4 @@
1
- import type { AnyComponentPublicInstance } from '../components/modal-container';
1
+ import type { AnyComponentPublicInstance } from '../components/overlay-container';
2
2
 
3
3
  /*///////////////////////////////////////////////
4
4
  Component Overlay Masking
@@ -1,33 +0,0 @@
1
- import type { Writable } from 'type-fest';
2
- import { type AllowedComponentProps, type ComponentInternalInstance, type ComponentPublicInstance, type ComputedOptions, type MethodOptions, type Raw, type VNode, type VNodeProps } from 'vue';
3
- interface ModalInjection<C extends Vue__ComponentPublicInstanceConstructor> {
4
- id: string;
5
- component: Raw<C>;
6
- props: ComponentProps<C>;
7
- vnode: VNode;
8
- }
9
- export declare const ModalContainer: import("vue").DefineComponent<{}, () => VNode<import("vue").RendererNode, import("vue").RendererElement, {
10
- [key: string]: any;
11
- }>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, VNodeProps & AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{}>>, {}, {}>;
12
- export type Vue__ComponentPublicInstanceConstructor<T extends ComponentPublicInstance<Props, RawBindings, D, C, M> = ComponentPublicInstance<any>, Props = any, RawBindings = any, D = any, C extends ComputedOptions = ComputedOptions, M extends MethodOptions = MethodOptions> = {
13
- __isFragment?: never;
14
- __isTeleport?: never;
15
- __isSuspense?: never;
16
- new (...args: any[]): T;
17
- };
18
- export type ComponentConfig<T extends Vue__ComponentPublicInstanceConstructor> = T extends Vue__ComponentPublicInstanceConstructor<infer P> ? P : never;
19
- export type ComponentProps<T extends Vue__ComponentPublicInstanceConstructor> = Writable<Omit<ComponentConfig<T>['$props'], keyof VNodeProps | keyof AllowedComponentProps>>;
20
- export type AnyComponentPublicInstance = {
21
- $: ComponentInternalInstance;
22
- };
23
- interface PropsWithCallback<T> {
24
- callback?: (result: T) => void;
25
- }
26
- type ComponentReturn<C extends Vue__ComponentPublicInstanceConstructor> = ComponentProps<C> extends PropsWithCallback<infer R> ? R : never;
27
- export declare function createModalInjection<C extends Vue__ComponentPublicInstanceConstructor>(component: C, props: ComponentProps<C>): ModalInjection<C>;
28
- export declare function removeModalInjection(injection: ModalInjection<any>): void;
29
- export declare function removeModalInjectionByInstance(instance: AnyComponentPublicInstance): void;
30
- export declare function removeModalInjectionByInternalInstance(instance: ComponentInternalInstance): void;
31
- export declare function removeModalInjectionByVnode(vnode: VNode): boolean;
32
- export declare function presentModal<C extends Vue__ComponentPublicInstanceConstructor, R extends ComponentReturn<C>>(component: C, props: Omit<ComponentProps<C>, 'callback'>): Promise<R | undefined>;
33
- export {};
@@ -1,131 +0,0 @@
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
- }