promise-portal 1.2.1 → 2.0.1

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/README.md CHANGED
@@ -5,14 +5,7 @@ use component as a promisd-like function
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- // pnpm
9
8
  pnpm add promise-portal
10
-
11
- // npm
12
- npm install promise-portal
13
-
14
- // yarn
15
- yarn add promise-portal
16
9
  ```
17
10
 
18
11
  ## Online Demo
@@ -38,6 +31,7 @@ use as a component, with ref value to control visibility and life-cycles
38
31
  ```vue
39
32
  <script setup lang="ts">
40
33
  import Comp from './components/name.vue'
34
+
41
35
  const show = ref(false)
42
36
  const onClick = () => {
43
37
  show.value = true
@@ -46,9 +40,14 @@ const onClosed = () => {
46
40
  show.value = false
47
41
  }
48
42
  </script>
43
+
49
44
  <template>
50
- <el-button @click="onClick"> click to open the Dialog </el-button>
51
- <Comp v-model="show" @closed="onClosed"> a dialog content </Comp>
45
+ <el-button @click="onClick">
46
+ click to open the Dialog
47
+ </el-button>
48
+ <Comp v-model="show" @closed="onClosed">
49
+ a dialog content
50
+ </Comp>
52
51
  </template>
53
52
  ```
54
53
 
@@ -59,14 +58,18 @@ use as a normal promise-style function, so happy to develop
59
58
  ```vue
60
59
  <script setup lang="ts">
61
60
  import Comp from './components/name.vue'
61
+
62
62
  const func = definePortal(Comp)
63
63
  const onClick = async () => {
64
64
  const data = await func()
65
65
  console.log(data)
66
66
  }
67
67
  </script>
68
+
68
69
  <template>
69
- <el-button @click="onClick"> open the Dialog </el-button>
70
+ <el-button @click="onClick">
71
+ open the Dialog
72
+ </el-button>
70
73
  </template>
71
74
  ```
72
75
 
@@ -76,15 +79,11 @@ const onClick = async () => {
76
79
 
77
80
  ```ts
78
81
  // ./main.ts
79
- import { createApp } from 'vue'
80
82
  import { createPromisePortal } from 'promise-portal'
83
+ import { createApp } from 'vue'
81
84
 
82
85
  const app = createApp(App)
83
- app.use(
84
- createPromisePortal({
85
- unmountDelay: 200,
86
- })
87
- )
86
+ app.use(createPromisePortal())
88
87
  ```
89
88
 
90
89
  ### use `ContextProvider` to set context globally
@@ -95,10 +94,11 @@ app.use(
95
94
  import locale from 'ant-design-vue/es/locale/zh_CN'
96
95
  import { ContextProvider } from 'promise-portal'
97
96
  </script>
97
+
98
98
  <template>
99
99
  <a-config-provider :locale="locale">
100
100
  <ContextProvider>
101
- <router-view></router-view>
101
+ <router-view />
102
102
  </ContextProvider>
103
103
  </a-config-provider>
104
104
  </template>
@@ -110,6 +110,7 @@ import { ContextProvider } from 'promise-portal'
110
110
  <!-- ./components/comp.vue -->
111
111
  <script setup lang="ts">
112
112
  import { usePortalContext } from 'promise-portal'
113
+
113
114
  export interface Output {
114
115
  confirm: boolean
115
116
  }
@@ -122,8 +123,11 @@ const onCancel = () => {
122
123
  resolve({ confirm: false })
123
124
  }
124
125
  </script>
126
+
125
127
  <template>
126
- <a-modal v-model:open="show" @cancel="resolve">{{ props.input }}</a-modal>
128
+ <a-modal v-model:open="show" @cancel="resolve">
129
+ {{ props.input }}
130
+ </a-modal>
127
131
  </template>
128
132
  ```
129
133
 
@@ -133,6 +137,7 @@ const onCancel = () => {
133
137
  // ./App.vue
134
138
  import { definePortal } from 'promise-portal'
135
139
  import Comp, { Input, Output } from './components/comp.vue'
140
+
136
141
  const [func] = definePortal<Output, Input>(Comp)
137
142
  const onClick = async () => {
138
143
  const result = await func({
@@ -153,35 +158,6 @@ const instance = createPromisePortal()
153
158
  app.use(instance)
154
159
  ```
155
160
 
156
- you can set default options to instance
157
-
158
- ```ts
159
- const instance = createPromisePortal({
160
- // set a time gap before portal unmount,
161
- // in general, it is to wait for animation effect
162
- unmountDelay: 200,
163
-
164
- // initial value to property show, default value is true
165
- initialShowValue: true,
166
- })
167
- ```
168
-
169
- ### getActiveInstance
170
-
171
- get active promise-portal instance
172
-
173
- ```ts
174
- const instance = getActiveInstance()
175
- ```
176
-
177
- ### setActiveInstance
178
-
179
- set active promise-portal instance
180
-
181
- ```ts
182
- setActiveInstance(instance)
183
- ```
184
-
185
161
  ### ContextProvider
186
162
 
187
163
  a component to set context globally
@@ -191,10 +167,11 @@ a component to set context globally
191
167
  import locale from 'ant-design-vue/es/locale/zh_CN'
192
168
  import { ContextProvider } from 'promise-portal'
193
169
  </script>
170
+
194
171
  <template>
195
172
  <a-config-provider :locale="locale">
196
173
  <ContextProvider>
197
- <router-view></router-view>
174
+ <router-view />
198
175
  </ContextProvider>
199
176
  </a-config-provider>
200
177
  </template>
@@ -211,16 +188,15 @@ const { resolve } = usePortalContext()
211
188
  const {
212
189
  resolve, // promise resolve handler
213
190
  reject, // promise reject handler
214
- el, // portal base element, injecting to body element
191
+ el, // portal base element, injected into 'appendTo' element
215
192
  vnode, // portal base vue vnode
216
- setUnmountDelay, // set delay to unmount
217
- show, // a ref value to use in modal component
193
+ unmountDelay, // Ref for portal unmount delay (ms)
194
+ show, // Ref for modal display state (controlled by portal)
218
195
  } = usePortalContext({
219
- // set a time gap before portal unmount,
220
- // in general, it is to wait for animation effect
196
+ // Unmount delay (ms) for portal, usually for animation effects
221
197
  unmountDelay: 200,
222
198
 
223
- // initial value to property show above, default value is true
199
+ // Initial value for the show ref (defaults to true)
224
200
  initialShowValue: true,
225
201
  })
226
202
  ```
@@ -247,8 +223,9 @@ use `initialShowValue` to set inital value, default inital value is `true`
247
223
  <script setup lang="ts">
248
224
  const { resolve, show } = usePortalContext<Output>({ initialShowValue: true })
249
225
  </script>
226
+
250
227
  <template>
251
- <a-modal v-model:open="show" @cancel="resolve"></a-modal>
228
+ <a-modal v-model:open="show" @cancel="resolve" />
252
229
  </template>
253
230
  ```
254
231
 
@@ -258,6 +235,7 @@ define a portal, return a portal function
258
235
 
259
236
  ```ts
260
237
  import Comp from './component.vue'
238
+
261
239
  const portalFunc = definePortal(Comp)
262
240
  portalFunc()
263
241
  ```
@@ -266,6 +244,9 @@ you can define generic types to check input object and output object
266
244
 
267
245
  ```ts
268
246
  // component.vue
247
+ // App.vue
248
+ import Comp, { Input, Output } from './component.vue'
249
+
269
250
  export interface Input {
270
251
  firstName: string
271
252
  lastName: string
@@ -278,9 +259,6 @@ export interface Output {
278
259
 
279
260
  const props = defineProps<Input>()
280
261
  const { resolve } = usePortalContext<Output>()
281
-
282
- // App.vue
283
- import Comp, { Input, Output } from './component.vue'
284
262
  const portal = definePortal<Output, Input>(Comp)
285
263
  const output = await portal({
286
264
  firstName: 'joe',
@@ -292,15 +270,15 @@ define a portal with empty parameter
292
270
 
293
271
  ```ts
294
272
  // component.vue
273
+ // App.vue
274
+ import Comp, { Output } from './component.vue'
275
+
295
276
  export interface Output {
296
277
  fullName: string
297
278
  confirm: boolean
298
279
  }
299
280
 
300
281
  const { resolve } = usePortalContext<Output>()
301
-
302
- // App.vue
303
- import Comp, { Output } from './component.vue'
304
282
  const portal = definePortal<Output, void>(Comp)
305
283
  const output = await portal() // only allow empty parameter
306
284
  ```
@@ -309,14 +287,15 @@ you can set a options to definePortal
309
287
 
310
288
  ```ts
311
289
  definePortal(Comp, {
312
- // set a time gap before portal unmount,
290
+ // Unmount delay (ms) for portal, usually for animation effects
313
291
  unmountDelay: 200,
314
292
 
315
- // initial value to property show
293
+ // Initial value for the show ref (defaults to true)
316
294
  initialShowValue: true,
317
295
 
318
- // set promise-portal instance explicitly to render this portal
319
- instance: promisePortalInstance,
296
+ // a dom element or CSS selector or Ref value or a function returing a dom element,
297
+ // append the portal element to (defaults to document.body)
298
+ appendTo: document.body,
320
299
  })
321
300
  ```
322
301
 
@@ -347,9 +326,9 @@ detectPromisePortalInstance({
347
326
  })
348
327
  ```
349
328
 
350
- # Relative Resourece
329
+ # Acknowledgements
351
330
 
352
- - [react protal](https://reactjs.org/docs/portals.html)
331
+ - [react portal](https://reactjs.org/docs/portals.html)
353
332
  - [vue teleport](https://vuejs.org/guide/built-ins/teleport.html)
354
333
  - [@filez/portal](https://github.com/lenovo-filez/portal)
355
334
  - [promise-modal](https://github.com/liruifengv/promise-modal)
package/dist/index.cjs CHANGED
@@ -1,165 +1,117 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
1
+ let vue = require("vue");
19
2
 
20
- // src/index.ts
21
- var src_exports = {};
22
- __export(src_exports, {
23
- ContextProvider: () => ContextProvider,
24
- createPromisePortal: () => createPromisePortal,
25
- definePortal: () => definePortal,
26
- detectPromisePortalInstance: () => detectPromisePortalInstance,
27
- getActiveInstance: () => getActiveInstance,
28
- setActiveInstance: () => setActiveInstance,
29
- usePortalContext: () => usePortalContext,
30
- version: () => version
31
- });
32
- module.exports = __toCommonJS(src_exports);
3
+ //#region src/detector.ts
4
+ function detect(options) {
5
+ const { style = "position:fixed;top:0;right:0;text-align:right;line-height:1.3;color:red;z-index:9999;", text = `Detected that the promise-portal instance has not been properly destroyed<br>Please make sure to call resolve/reject to release the instance correctly.` } = options;
6
+ const containers = document.querySelectorAll("[data-promise-portal-container]");
7
+ const detector = document.querySelector("[data-promise-portal-detector]");
8
+ if (containers.length === 0) {
9
+ detector?.remove();
10
+ return;
11
+ }
12
+ if (!detector) {
13
+ const el = document.createElement("div");
14
+ el.setAttribute("data-promise-portal-detector", "");
15
+ el.setAttribute("style", style);
16
+ el.innerHTML = text;
17
+ document.body.appendChild(el);
18
+ }
19
+ }
20
+ const detectPromisePortalInstance = (options = {}) => {
21
+ const timer = setInterval(() => detect(options), 200);
22
+ return () => clearInterval(timer);
23
+ };
33
24
 
34
- // src/portal.ts
35
- var import_vue = require("vue");
36
- var promisePortalSymbol = process.env.NODE_ENV !== "production" ? Symbol("promise-portal") : Symbol();
37
- var version = `1.2.1`;
38
- var activeInstance;
39
- var getActiveInstance = () => activeInstance;
40
- var setActiveInstance = (instance) => activeInstance = instance;
41
- var createPromisePortal = (defaultOptions = {}) => {
42
- const instance = {
43
- defaultOptions,
44
- app: void 0,
45
- map: /* @__PURE__ */ new WeakMap(),
46
- provides: void 0,
47
- install(app) {
48
- instance.app = app;
49
- setActiveInstance(instance);
50
- app.provide(promisePortalSymbol, instance);
51
- }
52
- };
53
- return instance;
25
+ //#endregion
26
+ //#region src/portal.ts
27
+ const promisePortalSymbol = Symbol("promise-portal");
28
+ let activeInstance;
29
+ const createPromisePortal = () => {
30
+ const instance = {
31
+ app: void 0,
32
+ map: /* @__PURE__ */ new WeakMap(),
33
+ provides: void 0,
34
+ install(app) {
35
+ instance.app = app;
36
+ activeInstance = instance;
37
+ app.provide(promisePortalSymbol, instance);
38
+ }
39
+ };
40
+ return instance;
54
41
  };
55
- var usePortalContext = (options = {}) => {
56
- const instance = (0, import_vue.inject)(promisePortalSymbol);
57
- if (!instance) {
58
- throw new Error("[promise-portal]: no instance found.");
59
- }
60
- const vnode = (0, import_vue.getCurrentInstance)()?.vnode;
61
- if (!vnode) {
62
- throw new Error("[promise-portal]: no vnode found.");
63
- }
64
- const data = instance.map.get(vnode);
65
- if (!data) {
66
- throw new Error("[promise-portal]: no inject data found.");
67
- }
68
- if (options.unmountDelay != void 0) {
69
- data.setUnmountDelay(options.unmountDelay);
70
- }
71
- if (options.initialShowValue != void 0) {
72
- data.show.value = options.initialShowValue;
73
- }
74
- return data;
42
+ const usePortalContext = (options = {}) => {
43
+ const instance = (0, vue.inject)(promisePortalSymbol);
44
+ if (!instance) throw new Error("[promise-portal]: no instance found. Did you forget to install promise-portal?");
45
+ const vnode = (0, vue.getCurrentInstance)()?.vnode;
46
+ if (!vnode) throw new Error("[promise-portal]: no vnode found in current component instance.");
47
+ const scope = instance.map.get(vnode);
48
+ if (!scope) throw new Error("[promise-portal]: no inject scope found.");
49
+ if (options.unmountDelay !== void 0) scope.unmountDelay.value = options.unmountDelay;
50
+ if (options.initialShowValue !== void 0) scope.show.value = options.initialShowValue;
51
+ return scope;
75
52
  };
76
- var definePortal = (component, options = {}) => {
77
- const instance = options.instance ?? ((0, import_vue.getCurrentInstance)() && (0, import_vue.inject)(promisePortalSymbol)) ?? getActiveInstance();
78
- if (!instance) {
79
- throw new Error("[promise-portal]: no instance found. Do you forget install promise-portal?");
80
- }
81
- let appContext = (0, import_vue.getCurrentInstance)()?.appContext;
82
- let contextHolderProvides = null;
83
- const ContextHolder = (0, import_vue.defineComponent)(() => () => {
84
- appContext = (0, import_vue.getCurrentInstance)()?.appContext;
85
- contextHolderProvides = (0, import_vue.getCurrentInstance)()?.provides;
86
- });
87
- const portal = (props, children) => {
88
- const el = document.createElement("div");
89
- el.setAttribute("data-promise-portal-container", "");
90
- document.body.appendChild(el);
91
- let unmountDelay = options.unmountDelay ?? instance.defaultOptions.unmountDelay;
92
- const setUnmountDelay = (delay) => {
93
- unmountDelay = delay;
94
- };
95
- const show = (0, import_vue.ref)(options.initialShowValue ?? instance.defaultOptions.initialShowValue ?? true);
96
- let vnode;
97
- const p = new Promise((resolve, reject) => {
98
- vnode = (0, import_vue.createVNode)(component, props, children);
99
- instance.map.set(vnode, { resolve, reject, el, vnode, setUnmountDelay, show });
100
- const ac = appContext ?? instance.app._context;
101
- vnode.appContext = Object.create(ac, {
102
- provides: {
103
- value: contextHolderProvides ?? instance.provides ?? ac.provides
104
- }
105
- });
106
- (0, import_vue.render)(vnode, el);
107
- });
108
- p.finally(() => {
109
- show.value = false;
110
- setTimeout(() => {
111
- (0, import_vue.render)(null, el);
112
- document.body.removeChild(el);
113
- }, unmountDelay);
114
- });
115
- return p;
116
- };
117
- return [portal, ContextHolder];
53
+ const resolveTarget = (to) => {
54
+ if (typeof to === "string") return document.querySelector(to);
55
+ else if ((0, vue.isRef)(to)) return (0, vue.unref)(to);
56
+ else if (typeof to === "function") return to();
57
+ return to;
118
58
  };
119
- var ContextProvider = (0, import_vue.defineComponent)({
120
- name: "PromisePortalContextProvider",
121
- setup(_props, { slots }) {
122
- const p = (0, import_vue.getCurrentInstance)()?.provides;
123
- const instance = getActiveInstance();
124
- if (p && instance) {
125
- instance.provides = p;
126
- }
127
- return () => slots.default?.();
128
- }
129
- });
130
-
131
- // src/detector.ts
132
- function detect(options) {
133
- const {
134
- style = "position:fixed;top:0;right:0;text-align:right;line-height:1.3;color:red;z-index:9999;",
135
- text = `Detected that the promise-portal instance has not been properly destroyed<br>Please make sure to call resolve/reject to release the instance correctly.`
136
- } = options;
137
- const containers = document.querySelectorAll("[data-promise-portal-container]");
138
- const detector = document.querySelector("[data-promise-portal-detector]");
139
- if (containers.length === 0) {
140
- detector?.remove();
141
- return;
142
- }
143
- if (!detector) {
144
- const el = document.createElement("div");
145
- el.setAttribute("data-promise-portal-detector", "");
146
- el.setAttribute("style", style);
147
- el.innerHTML = text;
148
- document.body.appendChild(el);
149
- }
150
- }
151
- var detectPromisePortalInstance = (options = {}) => {
152
- const timer = setInterval(() => detect(options), 200);
153
- return () => clearInterval(timer);
59
+ const definePortal = (component, options = {}) => {
60
+ const instance = ((0, vue.getCurrentInstance)() && (0, vue.inject)(promisePortalSymbol)) ?? activeInstance;
61
+ if (!instance) throw new Error("[promise-portal]: no instance found. Did you forget to install promise-portal?");
62
+ let appContext = (0, vue.getCurrentInstance)()?.appContext ?? instance.app._context;
63
+ let provides = instance.provides ?? appContext.provides;
64
+ const ContextHolder = (0, vue.defineComponent)(() => () => {
65
+ const ci = (0, vue.getCurrentInstance)();
66
+ if (ci) {
67
+ appContext = ci.appContext;
68
+ provides = ci.provides;
69
+ }
70
+ });
71
+ const portal = (props, children) => {
72
+ const el = document.createElement("div");
73
+ el.setAttribute("data-promise-portal-container", "");
74
+ const target = resolveTarget(options.appendTo ?? document.body);
75
+ if (!target) throw new Error("[promise-portal]: invalid appendTo target - no element found or provided.");
76
+ target.appendChild(el);
77
+ const unmountDelay = (0, vue.ref)(options.unmountDelay ?? 0);
78
+ const show = (0, vue.ref)(options.initialShowValue ?? true);
79
+ const promise = new Promise((resolve, reject) => {
80
+ const vnode = (0, vue.createVNode)(component, props, children);
81
+ vnode.appContext = Object.create(appContext, { provides: { value: provides } });
82
+ instance.map.set(vnode, {
83
+ resolve,
84
+ reject,
85
+ el,
86
+ vnode,
87
+ unmountDelay,
88
+ show
89
+ });
90
+ (0, vue.render)(vnode, el);
91
+ });
92
+ promise.finally(() => {
93
+ show.value = false;
94
+ setTimeout(() => {
95
+ (0, vue.render)(null, el);
96
+ el?.remove();
97
+ }, unmountDelay.value);
98
+ });
99
+ return promise;
100
+ };
101
+ return [portal, ContextHolder];
154
102
  };
155
- // Annotate the CommonJS export names for ESM import in node:
156
- 0 && (module.exports = {
157
- ContextProvider,
158
- createPromisePortal,
159
- definePortal,
160
- detectPromisePortalInstance,
161
- getActiveInstance,
162
- setActiveInstance,
163
- usePortalContext,
164
- version
103
+ const ContextProvider = (0, vue.defineComponent)({
104
+ name: "PromisePortalContextProvider",
105
+ setup(_props, { slots }) {
106
+ const provides = (0, vue.getCurrentInstance)()?.provides;
107
+ if (provides && activeInstance) activeInstance.provides = provides;
108
+ return () => slots.default?.();
109
+ }
165
110
  });
111
+
112
+ //#endregion
113
+ exports.ContextProvider = ContextProvider;
114
+ exports.createPromisePortal = createPromisePortal;
115
+ exports.definePortal = definePortal;
116
+ exports.detectPromisePortalInstance = detectPromisePortalInstance;
117
+ exports.usePortalContext = usePortalContext;
@@ -0,0 +1,41 @@
1
+ import * as vue0 from "vue";
2
+ import { App, Component, Ref, VNode } from "vue";
3
+
4
+ //#region src/detector.d.ts
5
+ interface DetectorOptions {
6
+ style?: string;
7
+ text?: string;
8
+ }
9
+ declare const detectPromisePortalInstance: (options?: DetectorOptions) => () => void;
10
+ //#endregion
11
+ //#region src/portal.d.ts
12
+ interface UsePortalContextOptions {
13
+ unmountDelay?: number;
14
+ initialShowValue?: boolean;
15
+ }
16
+ interface DefinePortalOptions extends UsePortalContextOptions {
17
+ appendTo?: string | HTMLElement | Ref<HTMLElement> | (() => HTMLElement);
18
+ }
19
+ type DefinePortalResult<Output, Input> = [(props?: Input, children?: unknown) => Promise<Output>, Component];
20
+ interface Scope<R> {
21
+ resolve: (value: R | PromiseLike<R>) => void;
22
+ reject: (reason?: any) => void;
23
+ el: HTMLDivElement;
24
+ vnode: VNode;
25
+ unmountDelay: Ref<number>;
26
+ show: Ref<boolean>;
27
+ }
28
+ interface Instance<R = any> {
29
+ app: App;
30
+ map: WeakMap<VNode, Scope<R>>;
31
+ provides: any;
32
+ install: (app: App) => void;
33
+ }
34
+ declare const createPromisePortal: () => Instance<any>;
35
+ declare const usePortalContext: <Output = any>(options?: UsePortalContextOptions) => Scope<Output>;
36
+ declare const definePortal: <Output = any, Input = any>(component: Component, options?: DefinePortalOptions) => DefinePortalResult<Output, Input>;
37
+ declare const ContextProvider: vue0.DefineComponent<{}, () => VNode<vue0.RendererNode, vue0.RendererElement, {
38
+ [key: string]: any;
39
+ }>[] | undefined, {}, {}, {}, vue0.ComponentOptionsMixin, vue0.ComponentOptionsMixin, {}, string, vue0.PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, vue0.ComponentProvideOptions, true, {}, any>;
40
+ //#endregion
41
+ export { ContextProvider, DefinePortalOptions, DefinePortalResult, DetectorOptions, Instance, Scope, UsePortalContextOptions, createPromisePortal, definePortal, detectPromisePortalInstance, usePortalContext };
@@ -0,0 +1,41 @@
1
+ import * as vue0 from "vue";
2
+ import { App, Component, Ref, VNode } from "vue";
3
+
4
+ //#region src/detector.d.ts
5
+ interface DetectorOptions {
6
+ style?: string;
7
+ text?: string;
8
+ }
9
+ declare const detectPromisePortalInstance: (options?: DetectorOptions) => () => void;
10
+ //#endregion
11
+ //#region src/portal.d.ts
12
+ interface UsePortalContextOptions {
13
+ unmountDelay?: number;
14
+ initialShowValue?: boolean;
15
+ }
16
+ interface DefinePortalOptions extends UsePortalContextOptions {
17
+ appendTo?: string | HTMLElement | Ref<HTMLElement> | (() => HTMLElement);
18
+ }
19
+ type DefinePortalResult<Output, Input> = [(props?: Input, children?: unknown) => Promise<Output>, Component];
20
+ interface Scope<R> {
21
+ resolve: (value: R | PromiseLike<R>) => void;
22
+ reject: (reason?: any) => void;
23
+ el: HTMLDivElement;
24
+ vnode: VNode;
25
+ unmountDelay: Ref<number>;
26
+ show: Ref<boolean>;
27
+ }
28
+ interface Instance<R = any> {
29
+ app: App;
30
+ map: WeakMap<VNode, Scope<R>>;
31
+ provides: any;
32
+ install: (app: App) => void;
33
+ }
34
+ declare const createPromisePortal: () => Instance<any>;
35
+ declare const usePortalContext: <Output = any>(options?: UsePortalContextOptions) => Scope<Output>;
36
+ declare const definePortal: <Output = any, Input = any>(component: Component, options?: DefinePortalOptions) => DefinePortalResult<Output, Input>;
37
+ declare const ContextProvider: vue0.DefineComponent<{}, () => VNode<vue0.RendererNode, vue0.RendererElement, {
38
+ [key: string]: any;
39
+ }>[] | undefined, {}, {}, {}, vue0.ComponentOptionsMixin, vue0.ComponentOptionsMixin, {}, string, vue0.PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, vue0.ComponentProvideOptions, true, {}, any>;
40
+ //#endregion
41
+ export { ContextProvider, DefinePortalOptions, DefinePortalResult, DetectorOptions, Instance, Scope, UsePortalContextOptions, createPromisePortal, definePortal, detectPromisePortalInstance, usePortalContext };
package/dist/index.mjs ADDED
@@ -0,0 +1,113 @@
1
+ import { createVNode, defineComponent, getCurrentInstance, inject, isRef, ref, render, unref } from "vue";
2
+
3
+ //#region src/detector.ts
4
+ function detect(options) {
5
+ const { style = "position:fixed;top:0;right:0;text-align:right;line-height:1.3;color:red;z-index:9999;", text = `Detected that the promise-portal instance has not been properly destroyed<br>Please make sure to call resolve/reject to release the instance correctly.` } = options;
6
+ const containers = document.querySelectorAll("[data-promise-portal-container]");
7
+ const detector = document.querySelector("[data-promise-portal-detector]");
8
+ if (containers.length === 0) {
9
+ detector?.remove();
10
+ return;
11
+ }
12
+ if (!detector) {
13
+ const el = document.createElement("div");
14
+ el.setAttribute("data-promise-portal-detector", "");
15
+ el.setAttribute("style", style);
16
+ el.innerHTML = text;
17
+ document.body.appendChild(el);
18
+ }
19
+ }
20
+ const detectPromisePortalInstance = (options = {}) => {
21
+ const timer = setInterval(() => detect(options), 200);
22
+ return () => clearInterval(timer);
23
+ };
24
+
25
+ //#endregion
26
+ //#region src/portal.ts
27
+ const promisePortalSymbol = Symbol("promise-portal");
28
+ let activeInstance;
29
+ const createPromisePortal = () => {
30
+ const instance = {
31
+ app: void 0,
32
+ map: /* @__PURE__ */ new WeakMap(),
33
+ provides: void 0,
34
+ install(app) {
35
+ instance.app = app;
36
+ activeInstance = instance;
37
+ app.provide(promisePortalSymbol, instance);
38
+ }
39
+ };
40
+ return instance;
41
+ };
42
+ const usePortalContext = (options = {}) => {
43
+ const instance = inject(promisePortalSymbol);
44
+ if (!instance) throw new Error("[promise-portal]: no instance found. Did you forget to install promise-portal?");
45
+ const vnode = getCurrentInstance()?.vnode;
46
+ if (!vnode) throw new Error("[promise-portal]: no vnode found in current component instance.");
47
+ const scope = instance.map.get(vnode);
48
+ if (!scope) throw new Error("[promise-portal]: no inject scope found.");
49
+ if (options.unmountDelay !== void 0) scope.unmountDelay.value = options.unmountDelay;
50
+ if (options.initialShowValue !== void 0) scope.show.value = options.initialShowValue;
51
+ return scope;
52
+ };
53
+ const resolveTarget = (to) => {
54
+ if (typeof to === "string") return document.querySelector(to);
55
+ else if (isRef(to)) return unref(to);
56
+ else if (typeof to === "function") return to();
57
+ return to;
58
+ };
59
+ const definePortal = (component, options = {}) => {
60
+ const instance = (getCurrentInstance() && inject(promisePortalSymbol)) ?? activeInstance;
61
+ if (!instance) throw new Error("[promise-portal]: no instance found. Did you forget to install promise-portal?");
62
+ let appContext = getCurrentInstance()?.appContext ?? instance.app._context;
63
+ let provides = instance.provides ?? appContext.provides;
64
+ const ContextHolder = defineComponent(() => () => {
65
+ const ci = getCurrentInstance();
66
+ if (ci) {
67
+ appContext = ci.appContext;
68
+ provides = ci.provides;
69
+ }
70
+ });
71
+ const portal = (props, children) => {
72
+ const el = document.createElement("div");
73
+ el.setAttribute("data-promise-portal-container", "");
74
+ const target = resolveTarget(options.appendTo ?? document.body);
75
+ if (!target) throw new Error("[promise-portal]: invalid appendTo target - no element found or provided.");
76
+ target.appendChild(el);
77
+ const unmountDelay = ref(options.unmountDelay ?? 0);
78
+ const show = ref(options.initialShowValue ?? true);
79
+ const promise = new Promise((resolve, reject) => {
80
+ const vnode = createVNode(component, props, children);
81
+ vnode.appContext = Object.create(appContext, { provides: { value: provides } });
82
+ instance.map.set(vnode, {
83
+ resolve,
84
+ reject,
85
+ el,
86
+ vnode,
87
+ unmountDelay,
88
+ show
89
+ });
90
+ render(vnode, el);
91
+ });
92
+ promise.finally(() => {
93
+ show.value = false;
94
+ setTimeout(() => {
95
+ render(null, el);
96
+ el?.remove();
97
+ }, unmountDelay.value);
98
+ });
99
+ return promise;
100
+ };
101
+ return [portal, ContextHolder];
102
+ };
103
+ const ContextProvider = defineComponent({
104
+ name: "PromisePortalContextProvider",
105
+ setup(_props, { slots }) {
106
+ const provides = getCurrentInstance()?.provides;
107
+ if (provides && activeInstance) activeInstance.provides = provides;
108
+ return () => slots.default?.();
109
+ }
110
+ });
111
+
112
+ //#endregion
113
+ export { ContextProvider, createPromisePortal, definePortal, detectPromisePortalInstance, usePortalContext };
package/package.json CHANGED
@@ -1,33 +1,13 @@
1
1
  {
2
2
  "name": "promise-portal",
3
- "version": "1.2.1",
4
- "description": "use component as a promisd-like function",
5
3
  "type": "module",
6
- "main": "dist/index.cjs",
7
- "module": "dist/index.js",
8
- "types": "dist/index.d.ts",
9
- "exports": {
10
- ".": {
11
- "require": "./dist/index.cjs",
12
- "import": "./dist/index.js",
13
- "types": "./dist/index.d.ts"
14
- }
15
- },
16
- "files": [
17
- "dist"
18
- ],
19
- "peerDependencies": {
20
- "vue": "^3.2.13"
21
- },
22
- "devDependencies": {
23
- "tsup": "^6.2.3",
24
- "typescript": "^4.8.2",
25
- "vue": "^3.3.8"
26
- },
4
+ "version": "2.0.1",
5
+ "description": "use component as a promisd-like function",
27
6
  "author": {
28
7
  "name": "tjyuanpeng",
29
8
  "email": "tjyuanpeng@gmail.com"
30
9
  },
10
+ "license": "MIT",
31
11
  "homepage": "https://github.com/tjyuanpeng/promise-portal",
32
12
  "repository": {
33
13
  "type": "git",
@@ -36,14 +16,36 @@
36
16
  "bugs": {
37
17
  "url": "https://github.com/tjyuanpeng/promise-portal/issues"
38
18
  },
39
- "license": "MIT",
40
19
  "keywords": [
41
20
  "vue",
42
21
  "promise",
43
22
  "portal"
44
23
  ],
24
+ "sideEffects": false,
25
+ "exports": {
26
+ ".": {
27
+ "require": "./dist/index.cjs",
28
+ "import": "./dist/index.mjs"
29
+ },
30
+ "./package.json": "./package.json"
31
+ },
32
+ "main": "./dist/index.cjs",
33
+ "module": "./dist/index.mjs",
34
+ "types": "./dist/index.d.cts",
35
+ "files": [
36
+ "dist"
37
+ ],
38
+ "peerDependencies": {
39
+ "vue": "3"
40
+ },
41
+ "devDependencies": {
42
+ "tsdown": "0.20.1",
43
+ "typescript": "~5.9.3"
44
+ },
45
45
  "scripts": {
46
- "build": "tsup-node src/index.ts --dts --format esm,cjs --no-splitting --clean",
47
- "dev": "npm run build -- --watch"
46
+ "dev": "tsdown --watch",
47
+ "build": "tsdown",
48
+ "typecheck": "tsc --noEmit",
49
+ "lint": "eslint ."
48
50
  }
49
51
  }
package/dist/index.d.ts DELETED
@@ -1,41 +0,0 @@
1
- import * as vue from 'vue';
2
- import { VNode, Ref, App, Component } from 'vue';
3
-
4
- declare const version = "1.2.1";
5
- interface Options {
6
- unmountDelay?: number;
7
- initialShowValue?: boolean;
8
- }
9
- interface Context<R> {
10
- resolve: (value: R | PromiseLike<R>) => void;
11
- reject: (reason?: any) => void;
12
- el: HTMLDivElement;
13
- vnode: VNode;
14
- setUnmountDelay: (unmountDelay: number) => void;
15
- show: Ref<boolean>;
16
- }
17
- interface Instance<R = any> {
18
- defaultOptions: Options;
19
- app: App;
20
- map: WeakMap<VNode, Context<R>>;
21
- provides: any;
22
- install: (app: App) => void;
23
- }
24
- declare const getActiveInstance: () => Instance<any>;
25
- declare const setActiveInstance: (instance: Instance) => Instance<any>;
26
- declare const createPromisePortal: (defaultOptions?: Options) => Instance<any>;
27
- declare const usePortalContext: <TOutput = any>(options?: Options) => Context<TOutput>;
28
- declare const definePortal: <TOutput = any, TProps = any>(component: Component, options?: Options & {
29
- instance?: Instance<TOutput> | undefined;
30
- }) => [(props?: TProps | undefined, children?: unknown) => Promise<TOutput>, Component<any, any, any, vue.ComputedOptions, vue.MethodOptions>];
31
- declare const ContextProvider: vue.DefineComponent<{}, () => VNode<vue.RendererNode, vue.RendererElement, {
32
- [key: string]: any;
33
- }>[] | undefined, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.VNodeProps & vue.AllowedComponentProps & vue.ComponentCustomProps, Readonly<vue.ExtractPropTypes<{}>>, {}, {}>;
34
-
35
- interface DetectorOptions {
36
- style?: string;
37
- text?: string;
38
- }
39
- declare const detectPromisePortalInstance: (options?: DetectorOptions) => () => void;
40
-
41
- export { Context, ContextProvider, DetectorOptions, Instance, Options, createPromisePortal, definePortal, detectPromisePortalInstance, getActiveInstance, setActiveInstance, usePortalContext, version };
package/dist/index.js DELETED
@@ -1,131 +0,0 @@
1
- // src/portal.ts
2
- import { createVNode, render, inject, getCurrentInstance, defineComponent, ref } from "vue";
3
- var promisePortalSymbol = process.env.NODE_ENV !== "production" ? Symbol("promise-portal") : Symbol();
4
- var version = `1.2.1`;
5
- var activeInstance;
6
- var getActiveInstance = () => activeInstance;
7
- var setActiveInstance = (instance) => activeInstance = instance;
8
- var createPromisePortal = (defaultOptions = {}) => {
9
- const instance = {
10
- defaultOptions,
11
- app: void 0,
12
- map: /* @__PURE__ */ new WeakMap(),
13
- provides: void 0,
14
- install(app) {
15
- instance.app = app;
16
- setActiveInstance(instance);
17
- app.provide(promisePortalSymbol, instance);
18
- }
19
- };
20
- return instance;
21
- };
22
- var usePortalContext = (options = {}) => {
23
- const instance = inject(promisePortalSymbol);
24
- if (!instance) {
25
- throw new Error("[promise-portal]: no instance found.");
26
- }
27
- const vnode = getCurrentInstance()?.vnode;
28
- if (!vnode) {
29
- throw new Error("[promise-portal]: no vnode found.");
30
- }
31
- const data = instance.map.get(vnode);
32
- if (!data) {
33
- throw new Error("[promise-portal]: no inject data found.");
34
- }
35
- if (options.unmountDelay != void 0) {
36
- data.setUnmountDelay(options.unmountDelay);
37
- }
38
- if (options.initialShowValue != void 0) {
39
- data.show.value = options.initialShowValue;
40
- }
41
- return data;
42
- };
43
- var definePortal = (component, options = {}) => {
44
- const instance = options.instance ?? (getCurrentInstance() && inject(promisePortalSymbol)) ?? getActiveInstance();
45
- if (!instance) {
46
- throw new Error("[promise-portal]: no instance found. Do you forget install promise-portal?");
47
- }
48
- let appContext = getCurrentInstance()?.appContext;
49
- let contextHolderProvides = null;
50
- const ContextHolder = defineComponent(() => () => {
51
- appContext = getCurrentInstance()?.appContext;
52
- contextHolderProvides = getCurrentInstance()?.provides;
53
- });
54
- const portal = (props, children) => {
55
- const el = document.createElement("div");
56
- el.setAttribute("data-promise-portal-container", "");
57
- document.body.appendChild(el);
58
- let unmountDelay = options.unmountDelay ?? instance.defaultOptions.unmountDelay;
59
- const setUnmountDelay = (delay) => {
60
- unmountDelay = delay;
61
- };
62
- const show = ref(options.initialShowValue ?? instance.defaultOptions.initialShowValue ?? true);
63
- let vnode;
64
- const p = new Promise((resolve, reject) => {
65
- vnode = createVNode(component, props, children);
66
- instance.map.set(vnode, { resolve, reject, el, vnode, setUnmountDelay, show });
67
- const ac = appContext ?? instance.app._context;
68
- vnode.appContext = Object.create(ac, {
69
- provides: {
70
- value: contextHolderProvides ?? instance.provides ?? ac.provides
71
- }
72
- });
73
- render(vnode, el);
74
- });
75
- p.finally(() => {
76
- show.value = false;
77
- setTimeout(() => {
78
- render(null, el);
79
- document.body.removeChild(el);
80
- }, unmountDelay);
81
- });
82
- return p;
83
- };
84
- return [portal, ContextHolder];
85
- };
86
- var ContextProvider = defineComponent({
87
- name: "PromisePortalContextProvider",
88
- setup(_props, { slots }) {
89
- const p = getCurrentInstance()?.provides;
90
- const instance = getActiveInstance();
91
- if (p && instance) {
92
- instance.provides = p;
93
- }
94
- return () => slots.default?.();
95
- }
96
- });
97
-
98
- // src/detector.ts
99
- function detect(options) {
100
- const {
101
- style = "position:fixed;top:0;right:0;text-align:right;line-height:1.3;color:red;z-index:9999;",
102
- text = `Detected that the promise-portal instance has not been properly destroyed<br>Please make sure to call resolve/reject to release the instance correctly.`
103
- } = options;
104
- const containers = document.querySelectorAll("[data-promise-portal-container]");
105
- const detector = document.querySelector("[data-promise-portal-detector]");
106
- if (containers.length === 0) {
107
- detector?.remove();
108
- return;
109
- }
110
- if (!detector) {
111
- const el = document.createElement("div");
112
- el.setAttribute("data-promise-portal-detector", "");
113
- el.setAttribute("style", style);
114
- el.innerHTML = text;
115
- document.body.appendChild(el);
116
- }
117
- }
118
- var detectPromisePortalInstance = (options = {}) => {
119
- const timer = setInterval(() => detect(options), 200);
120
- return () => clearInterval(timer);
121
- };
122
- export {
123
- ContextProvider,
124
- createPromisePortal,
125
- definePortal,
126
- detectPromisePortalInstance,
127
- getActiveInstance,
128
- setActiveInstance,
129
- usePortalContext,
130
- version
131
- };