promise-portal 1.0.6 → 1.2.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
@@ -1,30 +1,25 @@
1
1
  # promise-portal
2
2
 
3
- let you use react portal in vue, and with promise
3
+ use component as a promisd-like function
4
4
 
5
- ## Install
5
+ ## Installation
6
6
 
7
7
  ```bash
8
8
  // pnpm
9
- pnpm add promise-portal -D
9
+ pnpm add promise-portal
10
10
 
11
11
  // npm
12
- npm install promise-portal -D
12
+ npm install promise-portal
13
13
 
14
14
  // yarn
15
- yarn add promise-portal --D
15
+ yarn add promise-portal
16
16
  ```
17
17
 
18
18
  ## Online Demo
19
19
 
20
- [Demo on codesandbox](https://codesandbox.io/p/github/tjyuanpeng/promise-portal)
20
+ [https://codesandbox.io/p/github/tjyuanpeng/promise-portal](https://codesandbox.io/p/github/tjyuanpeng/promise-portal)
21
21
 
22
- ## Relative Resourece
23
-
24
- - [react protal](https://reactjs.org/docs/portals.html)
25
- - [vue teleport](https://vuejs.org/guide/built-ins/teleport.html)
26
-
27
- ## Why
22
+ ## Motivation
28
23
 
29
24
  like element-plus, the modal is a vue component
30
25
 
@@ -34,11 +29,13 @@ no `show` property to control show/hide, gettting result is more explicit
34
29
 
35
30
  easier to control workflow, and easier to handle life-cycles
36
31
 
37
- ### Before
32
+ so you can use Promise-Portal to save your life-time
38
33
 
39
- use as acomponent, with ref value to control visibility and life-cycles
34
+ ### before
40
35
 
41
- ```ts
36
+ use as a component, with ref value to control visibility and life-cycles
37
+
38
+ ```vue
42
39
  <script setup lang="ts">
43
40
  import Comp from './components/name.vue'
44
41
  const show = ref(false)
@@ -55,27 +52,27 @@ const onClosed = () => {
55
52
  </template>
56
53
  ```
57
54
 
58
- ### After
55
+ ### after
59
56
 
60
57
  use as a normal promise-style function, so happy to develop
61
58
 
62
- ```ts
59
+ ```vue
63
60
  <script setup lang="ts">
64
- import Comp, { Input, Output } from './components/name.vue'
65
- const func = definePortal<Output, Input>(Comp)
61
+ import Comp from './components/name.vue'
62
+ const func = definePortal(Comp)
66
63
  const onClick = async () => {
67
64
  const data = await func()
68
65
  console.log(data)
69
66
  }
70
67
  </script>
71
68
  <template>
72
- <el-button @click="onClick"> click to open the Dialog </el-button>
69
+ <el-button @click="onClick"> open the Dialog </el-button>
73
70
  </template>
74
71
  ```
75
72
 
76
- ## Use
73
+ ## Use Case
77
74
 
78
- ### install in the entry file
75
+ ### create promise-portal instance in the entry file
79
76
 
80
77
  ```ts
81
78
  // ./main.ts
@@ -83,183 +80,276 @@ import { createApp } from 'vue'
83
80
  import { createPromisePortal } from 'promise-portal'
84
81
 
85
82
  const app = createApp(App)
86
- app.use(createPromisePortal())
83
+ app.use(
84
+ createPromisePortal({
85
+ unmountDelay: 200,
86
+ })
87
+ )
87
88
  ```
88
89
 
89
- ### in component, use `usePortalContext` to get portal context
90
+ ### use `ContextProvider` to set context globally
90
91
 
91
- ```ts
92
- // ./components/name.vue
93
- import { usePortalContext } from 'promise-portal'
92
+ ```vue
93
+ <!-- ./App.vue -->
94
+ <script setup lang="ts">
95
+ import locale from 'ant-design-vue/es/locale/zh_CN'
96
+ import { ContextProvider } from 'promise-portal'
97
+ </script>
98
+ <template>
99
+ <a-config-provider :locale="locale">
100
+ <ContextProvider>
101
+ <router-view></router-view>
102
+ </ContextProvider>
103
+ </a-config-provider>
104
+ </template>
105
+ ```
94
106
 
95
- const { resolve } = usePortalContext<Output>()
96
- const onClose = () => {
97
- resolve({ confirm: false, fullName: '' })
107
+ ### in component, use `usePortalContext` to use portal context
108
+
109
+ ```vue
110
+ <!-- ./components/comp.vue -->
111
+ <script setup lang="ts">
112
+ import { usePortalContext } from 'promise-portal'
113
+ export interface Output {
114
+ confirm: boolean
98
115
  }
116
+ export interface Output {
117
+ input: string
118
+ }
119
+ const props = defineProps<Input>()
120
+ const { resolve, show } = usePortalContext<Output>()
121
+ const onCancel = () => {
122
+ resolve({ confirm: false })
123
+ }
124
+ </script>
125
+ <template>
126
+ <a-modal v-model:open="show" @cancel="resolve">{{ props.input }}</a-modal>
127
+ </template>
99
128
  ```
100
129
 
101
- ### define portal, use it like a promise-style function
130
+ ### define portal in anywhere, then use it like a promise-style function
102
131
 
103
132
  ```ts
104
133
  // ./App.vue
105
134
  import { definePortal } from 'promise-portal'
106
- import Comp, { Input, Output } from './components/name.vue'
107
-
108
- const func = definePortal<Output, Input>(Comp)
135
+ import Comp, { Input, Output } from './components/comp.vue'
136
+ const [func] = definePortal<Output, Input>(Comp)
109
137
  const onClick = async () => {
110
- const data = await func({ firstName: 'joe', lastName: 'watson' })
111
- if (!data.confirm) {
112
- return
113
- }
114
- console.log(data)
138
+ const result = await func({
139
+ input: 'foo',
140
+ })
141
+ console.log(result)
115
142
  }
116
143
  ```
117
144
 
118
145
  ## API Reference
119
146
 
120
- - createPromisePortal
147
+ ### createPromisePortal
148
+
149
+ create promise-portal instance, set to vue instance
121
150
 
122
- create promise-portal instance, set to vue instance
151
+ ```ts
152
+ const instance = createPromisePortal()
153
+ app.use(instance)
154
+ ```
155
+
156
+ you can set default options to instance
123
157
 
124
- ```ts
125
- const instance = createPromisePortal()
126
- app.use(instance)
127
- ```
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
+ ```
128
168
 
129
- you can set default config to instance
169
+ ### getActiveInstance
130
170
 
131
- ```ts
132
- const instance = createPromisePortal({
133
- unmountDelay: 100,
134
- })
135
- ```
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
+ ### ContextProvider
186
+
187
+ a component to set context globally
188
+
189
+ ```vue
190
+ <script setup lang="ts">
191
+ import locale from 'ant-design-vue/es/locale/zh_CN'
192
+ import { ContextProvider } from 'promise-portal'
193
+ </script>
194
+ <template>
195
+ <a-config-provider :locale="locale">
196
+ <ContextProvider>
197
+ <router-view></router-view>
198
+ </ContextProvider>
199
+ </a-config-provider>
200
+ </template>
201
+ ```
136
202
 
137
- - getActiveInstance
203
+ ### usePortalContext
138
204
 
139
- get active promise-portal instance
205
+ a vue composition api, use in portal component to get context of portal
140
206
 
141
- ```ts
142
- const instance = getActiveInstance()
143
- ```
207
+ ```ts
208
+ const { resolve } = usePortalContext()
209
+
210
+ // detail
211
+ const {
212
+ resolve, // promise resolve handler
213
+ reject, // promise reject handler
214
+ el, // portal base element, injecting to body element
215
+ vnode, // portal base vue vnode
216
+ setUnmountDelay, // set delay to unmount
217
+ show, // a ref value to use in modal component
218
+ } = usePortalContext({
219
+ // set a time gap before portal unmount,
220
+ // in general, it is to wait for animation effect
221
+ unmountDelay: 200,
222
+
223
+ // initial value to property show above, default value is true
224
+ initialShowValue: true,
225
+ })
226
+ ```
144
227
 
145
- - setActiveInstance
228
+ you can use typescript generic types to promise fulfilled result
146
229
 
147
- set promise-portal instance to be active
230
+ ```ts
231
+ export interface Output {
232
+ confirm: boolean
233
+ }
234
+ const { resolve } = usePortalContext<Output>()
235
+ resolve({
236
+ confirm: true,
237
+ })
238
+ ```
148
239
 
149
- ```ts
150
- setActiveInstance(instance)
151
- ```
240
+ you can use `show` to control modal component
152
241
 
153
- - usePortalContext
242
+ before `unmount`, `show.value = false` will be setted
154
243
 
155
- a vue composition api, use in portal component to get context of portal
244
+ use `initialShowValue` to set inital value, default inital value is `true`
156
245
 
157
- ```ts
158
- const { resolve, reject, el, vNode, setUnmountDelay } = usePortalContext()
159
- // resolve: promise resolve handler
160
- // reject: promise reject handler
161
- // el: portal base element, injecting to body element
162
- // vNode: portal base vue vnode
163
- // setUnmountDelay: set unmount delay to this portal
164
- ```
246
+ ```vue
247
+ <script setup lang="ts">
248
+ const { resolve, show } = usePortalContext<Output>({ initialShowValue: true })
249
+ </script>
250
+ <template>
251
+ <a-modal v-model:open="show" @cancel="resolve"></a-modal>
252
+ </template>
253
+ ```
165
254
 
166
- you can use typescript generic types
255
+ ### definePortal
167
256
 
168
- ```ts
169
- const { resolve } = usePortalContext<Output>()
170
- resolve({ ... }) // an object of type Output
171
- ```
257
+ define a portal, return a portal function
172
258
 
173
- - definePortal
259
+ ```ts
260
+ import Comp from './component.vue'
261
+ const portalFunc = definePortal(Comp)
262
+ portalFunc()
263
+ ```
174
264
 
175
- define a portal, return a portal function
265
+ you can define generic types to check input object and output object
176
266
 
177
- ```ts
178
- import Comp from './component.vue'
179
- const portal = definePortal(Comp)
180
- portal() // return a promise
181
- ```
267
+ ```ts
268
+ // component.vue
269
+ export interface Input {
270
+ firstName: string
271
+ lastName: string
272
+ }
182
273
 
183
- you can define generic types to check input object and output object
274
+ export interface Output {
275
+ fullName: string
276
+ confirm: boolean
277
+ }
184
278
 
185
- ```ts
186
- // component.vue
187
- export interface Input {
188
- firstName: string
189
- lastName: string
190
- }
279
+ const props = defineProps<Input>()
280
+ const { resolve } = usePortalContext<Output>()
191
281
 
192
- export interface Output {
193
- fullName: string
194
- confirm: boolean
195
- }
282
+ // App.vue
283
+ import Comp, { Input, Output } from './component.vue'
284
+ const portal = definePortal<Output, Input>(Comp)
285
+ const output = await portal({
286
+ firstName: 'joe',
287
+ lastName: 'watson',
288
+ })
289
+ ```
196
290
 
197
- const props = defineProps<Input>()
198
- const { resolve } = usePortalContext<Output>()
291
+ define a portal with empty parameter
199
292
 
200
- // App.vue
201
- import Comp, { Input, Output } from './component.vue'
202
- const portal = definePortal<Output, Input>(Comp)
203
- const output = await portal({
204
- firstName: 'joe',
205
- lastName: 'watson',
206
- })
207
- ```
208
-
209
- how to define a portal with empty parameter
210
-
211
- ```ts
212
- // component.vue
213
- export interface Output {
214
- fullName: string
215
- confirm: boolean
216
- }
217
-
218
- const { resolve } = usePortalContext<Output>()
219
-
220
- // App.vue
221
- import Comp, { Output } from './component.vue'
222
- const portal = definePortal<Output, void>(Comp)
223
- const output = await portal() // only allow empty parameter
224
- ```
225
-
226
- you can set a config to definePortal
227
-
228
- ```ts
229
- definePortal(Comp, {
230
- // set a time gap before portal unmount,
231
- // in general, it to wait for animation effect
232
- unmountDelay: 1000,
233
- // set promise-portal instance explicitly to render this portal
234
- // not use the active instance internally
235
- // of course, you can use `setActiveInstance` to set active instance
236
- instance: promisePortalInstance,
237
- })
238
- ```
293
+ ```ts
294
+ // component.vue
295
+ export interface Output {
296
+ fullName: string
297
+ confirm: boolean
298
+ }
239
299
 
240
- - detectPromisePortalInstance
300
+ const { resolve } = usePortalContext<Output>()
241
301
 
242
- Check if the instance has been properly destroyed
302
+ // App.vue
303
+ import Comp, { Output } from './component.vue'
304
+ const portal = definePortal<Output, void>(Comp)
305
+ const output = await portal() // only allow empty parameter
306
+ ```
243
307
 
244
- ```ts
245
- // main.ts
246
- if (import.meta.env.DEV) {
247
- detectPromisePortalInstance()
248
- }
249
- ```
308
+ you can set a options to definePortal
250
309
 
251
- You can pass in other values to customize it.
310
+ ```ts
311
+ definePortal(Comp, {
312
+ // set a time gap before portal unmount,
313
+ unmountDelay: 200,
252
314
 
253
- ```ts
254
- // default value
255
- detectPromisePortalInstance({
256
- style = 'position:fixed;top:0;right:0;text-align:right;line-height:1.3;color:red;z-index:9999;',
257
- text = `Detected that the promise-portal instance has not been properly destroyed<br>
258
- Please make sure to call resolve/reject to release the instance correctly.`,
259
- })
260
- ```
315
+ // initial value to property show
316
+ initialShowValue: true,
317
+
318
+ // set promise-portal instance explicitly to render this portal
319
+ instance: promisePortalInstance,
320
+ })
321
+ ```
322
+
323
+ ### detectPromisePortalInstance
324
+
325
+ detect whether the instance has been properly destroyed
326
+
327
+ ```ts
328
+ // main.ts
329
+ if (import.meta.env.DEV) {
330
+ detectPromisePortalInstance()
331
+ }
332
+ ```
333
+
334
+ the return value is a function to stop detecting
335
+
336
+ ```ts
337
+ const stopHandler = detectPromisePortalInstance()
338
+ stopHandler() // stop detecting
339
+ ```
261
340
 
262
- ## Link
341
+ You can pass in other values to customize it.
263
342
 
343
+ ```ts
344
+ detectPromisePortalInstance({
345
+ text: 'Detected unreleased promise-portal instance',
346
+ style: ' /styles you like/ ',
347
+ })
348
+ ```
349
+
350
+ # Relative Resourece
351
+
352
+ - [react protal](https://reactjs.org/docs/portals.html)
353
+ - [vue teleport](https://vuejs.org/guide/built-ins/teleport.html)
264
354
  - [@filez/portal](https://github.com/lenovo-filez/portal)
265
355
  - [promise-modal](https://github.com/liruifengv/promise-modal)
package/dist/index.cjs CHANGED
@@ -20,16 +20,21 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var src_exports = {};
22
22
  __export(src_exports, {
23
+ ContextProvider: () => ContextProvider,
23
24
  createPromisePortal: () => createPromisePortal,
24
25
  definePortal: () => definePortal,
25
26
  detectPromisePortalInstance: () => detectPromisePortalInstance,
26
27
  getActiveInstance: () => getActiveInstance,
27
28
  setActiveInstance: () => setActiveInstance,
28
- usePortalContext: () => usePortalContext
29
+ usePortalContext: () => usePortalContext,
30
+ version: () => version
29
31
  });
30
32
  module.exports = __toCommonJS(src_exports);
33
+
34
+ // src/portal.ts
31
35
  var import_vue = require("vue");
32
36
  var promisePortalSymbol = process.env.NODE_ENV !== "production" ? Symbol("promise-portal") : Symbol();
37
+ var version = `1.2.1`;
33
38
  var activeInstance;
34
39
  var getActiveInstance = () => activeInstance;
35
40
  var setActiveInstance = (instance) => activeInstance = instance;
@@ -38,6 +43,7 @@ var createPromisePortal = (defaultOptions = {}) => {
38
43
  defaultOptions,
39
44
  app: void 0,
40
45
  map: /* @__PURE__ */ new WeakMap(),
46
+ provides: void 0,
41
47
  install(app) {
42
48
  instance.app = app;
43
49
  setActiveInstance(instance);
@@ -46,80 +52,114 @@ var createPromisePortal = (defaultOptions = {}) => {
46
52
  };
47
53
  return instance;
48
54
  };
49
- var usePortalContext = () => {
55
+ var usePortalContext = (options = {}) => {
50
56
  const instance = (0, import_vue.inject)(promisePortalSymbol);
51
57
  if (!instance) {
52
58
  throw new Error("[promise-portal]: no instance found.");
53
59
  }
54
- const ins = (0, import_vue.getCurrentInstance)();
55
- if (!ins?.vnode) {
60
+ const vnode = (0, import_vue.getCurrentInstance)()?.vnode;
61
+ if (!vnode) {
56
62
  throw new Error("[promise-portal]: no vnode found.");
57
63
  }
58
- const data = instance.map.get(ins.vnode);
64
+ const data = instance.map.get(vnode);
59
65
  if (!data) {
60
66
  throw new Error("[promise-portal]: no inject data found.");
61
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
+ }
62
74
  return data;
63
75
  };
64
- var definePortal = (component, { instance, unmountDelay } = {}) => {
65
- const _instance = instance || (0, import_vue.getCurrentInstance)() && (0, import_vue.inject)(promisePortalSymbol) || activeInstance;
66
- if (!_instance) {
76
+ var definePortal = (component, options = {}) => {
77
+ const instance = options.instance ?? ((0, import_vue.getCurrentInstance)() && (0, import_vue.inject)(promisePortalSymbol)) ?? getActiveInstance();
78
+ if (!instance) {
67
79
  throw new Error("[promise-portal]: no instance found. Do you forget install promise-portal?");
68
80
  }
69
- return (props, children) => {
70
- let el = document.createElement("div");
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");
71
89
  el.setAttribute("data-promise-portal-container", "");
72
90
  document.body.appendChild(el);
73
- let _delay = unmountDelay || _instance.defaultOptions?.unmountDelay;
91
+ let unmountDelay = options.unmountDelay ?? instance.defaultOptions.unmountDelay;
74
92
  const setUnmountDelay = (delay) => {
75
- _delay = delay;
93
+ unmountDelay = delay;
76
94
  };
77
- let vNode;
95
+ const show = (0, import_vue.ref)(options.initialShowValue ?? instance.defaultOptions.initialShowValue ?? true);
96
+ let vnode;
78
97
  const p = new Promise((resolve, reject) => {
79
- vNode = (0, import_vue.createVNode)(component, props, children);
80
- _instance.map.set(vNode, { resolve, reject, el, vNode, setUnmountDelay });
81
- vNode.appContext = _instance.app._context;
82
- (0, import_vue.render)(vNode, el);
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);
83
107
  });
84
108
  p.finally(() => {
109
+ show.value = false;
85
110
  setTimeout(() => {
86
- if (el) {
87
- (0, import_vue.render)(null, el);
88
- document.body.removeChild(el);
89
- }
90
- el = null;
91
- vNode = null;
92
- }, _delay);
111
+ (0, import_vue.render)(null, el);
112
+ document.body.removeChild(el);
113
+ }, unmountDelay);
93
114
  });
94
115
  return p;
95
116
  };
117
+ return [portal, ContextHolder];
96
118
  };
97
- var detectPromisePortalInstance = (config = {}) => {
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) {
98
133
  const {
99
134
  style = "position:fixed;top:0;right:0;text-align:right;line-height:1.3;color:red;z-index:9999;",
100
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.`
101
- } = config;
102
- const nodes = document.querySelectorAll("[data-promise-portal-container]");
103
- if (nodes.length > 0) {
104
- let el = document.querySelector("[data-promise-portal-detector]");
105
- if (!el) {
106
- el = document.createElement("div");
107
- el.setAttribute("data-promise-portal-detector", "");
108
- el.setAttribute("style", style);
109
- el.innerHTML = text;
110
- document.body.appendChild(el);
111
- }
112
- } else {
113
- document.querySelector("[data-promise-portal-detector]")?.remove();
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);
114
149
  }
115
- setTimeout(() => detectPromisePortalInstance(config), 200);
150
+ }
151
+ var detectPromisePortalInstance = (options = {}) => {
152
+ const timer = setInterval(() => detect(options), 200);
153
+ return () => clearInterval(timer);
116
154
  };
117
155
  // Annotate the CommonJS export names for ESM import in node:
118
156
  0 && (module.exports = {
157
+ ContextProvider,
119
158
  createPromisePortal,
120
159
  definePortal,
121
160
  detectPromisePortalInstance,
122
161
  getActiveInstance,
123
162
  setActiveInstance,
124
- usePortalContext
163
+ usePortalContext,
164
+ version
125
165
  });
package/dist/index.d.ts CHANGED
@@ -1,33 +1,41 @@
1
- import { VNode, App, Component } from 'vue';
1
+ import * as vue from 'vue';
2
+ import { VNode, Ref, App, Component } from 'vue';
2
3
 
3
- interface PortalContext<R> {
4
+ declare const version = "1.2.1";
5
+ interface Options {
6
+ unmountDelay?: number;
7
+ initialShowValue?: boolean;
8
+ }
9
+ interface Context<R> {
4
10
  resolve: (value: R | PromiseLike<R>) => void;
5
11
  reject: (reason?: any) => void;
6
12
  el: HTMLDivElement;
7
- vNode: VNode;
13
+ vnode: VNode;
8
14
  setUnmountDelay: (unmountDelay: number) => void;
15
+ show: Ref<boolean>;
9
16
  }
10
- interface PromisePortal<R = any> {
11
- defaultOptions: {
12
- unmountDelay?: number;
13
- };
17
+ interface Instance<R = any> {
18
+ defaultOptions: Options;
14
19
  app: App;
15
- map: WeakMap<VNode, PortalContext<R>>;
20
+ map: WeakMap<VNode, Context<R>>;
21
+ provides: any;
16
22
  install: (app: App) => void;
17
23
  }
18
- interface PortalOptions<R> {
19
- instance?: PromisePortal<R>;
20
- unmountDelay?: number;
21
- }
22
- declare const getActiveInstance: () => PromisePortal<any>;
23
- declare const setActiveInstance: (instance: PromisePortal) => PromisePortal<any>;
24
- declare const createPromisePortal: (defaultOptions?: {}) => PromisePortal<any>;
25
- declare const usePortalContext: <TOutput = any>() => PortalContext<TOutput>;
26
- declare const definePortal: <TOutput = any, TProps = any>(component: Component, { instance, unmountDelay }?: PortalOptions<TOutput>) => (props?: TProps | undefined, children?: unknown) => Promise<TOutput>;
27
- interface DetectPromisePortalInstanceConfig {
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 {
28
36
  style?: string;
29
37
  text?: string;
30
38
  }
31
- declare const detectPromisePortalInstance: (config?: DetectPromisePortalInstanceConfig) => void;
39
+ declare const detectPromisePortalInstance: (options?: DetectorOptions) => () => void;
32
40
 
33
- export { DetectPromisePortalInstanceConfig, PortalContext, PortalOptions, PromisePortal, createPromisePortal, definePortal, detectPromisePortalInstance, getActiveInstance, setActiveInstance, usePortalContext };
41
+ export { Context, ContextProvider, DetectorOptions, Instance, Options, createPromisePortal, definePortal, detectPromisePortalInstance, getActiveInstance, setActiveInstance, usePortalContext, version };
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
- // src/index.ts
2
- import { createVNode, render, inject, getCurrentInstance } from "vue";
1
+ // src/portal.ts
2
+ import { createVNode, render, inject, getCurrentInstance, defineComponent, ref } from "vue";
3
3
  var promisePortalSymbol = process.env.NODE_ENV !== "production" ? Symbol("promise-portal") : Symbol();
4
+ var version = `1.2.1`;
4
5
  var activeInstance;
5
6
  var getActiveInstance = () => activeInstance;
6
7
  var setActiveInstance = (instance) => activeInstance = instance;
@@ -9,6 +10,7 @@ var createPromisePortal = (defaultOptions = {}) => {
9
10
  defaultOptions,
10
11
  app: void 0,
11
12
  map: /* @__PURE__ */ new WeakMap(),
13
+ provides: void 0,
12
14
  install(app) {
13
15
  instance.app = app;
14
16
  setActiveInstance(instance);
@@ -17,79 +19,113 @@ var createPromisePortal = (defaultOptions = {}) => {
17
19
  };
18
20
  return instance;
19
21
  };
20
- var usePortalContext = () => {
22
+ var usePortalContext = (options = {}) => {
21
23
  const instance = inject(promisePortalSymbol);
22
24
  if (!instance) {
23
25
  throw new Error("[promise-portal]: no instance found.");
24
26
  }
25
- const ins = getCurrentInstance();
26
- if (!ins?.vnode) {
27
+ const vnode = getCurrentInstance()?.vnode;
28
+ if (!vnode) {
27
29
  throw new Error("[promise-portal]: no vnode found.");
28
30
  }
29
- const data = instance.map.get(ins.vnode);
31
+ const data = instance.map.get(vnode);
30
32
  if (!data) {
31
33
  throw new Error("[promise-portal]: no inject data found.");
32
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
+ }
33
41
  return data;
34
42
  };
35
- var definePortal = (component, { instance, unmountDelay } = {}) => {
36
- const _instance = instance || getCurrentInstance() && inject(promisePortalSymbol) || activeInstance;
37
- if (!_instance) {
43
+ var definePortal = (component, options = {}) => {
44
+ const instance = options.instance ?? (getCurrentInstance() && inject(promisePortalSymbol)) ?? getActiveInstance();
45
+ if (!instance) {
38
46
  throw new Error("[promise-portal]: no instance found. Do you forget install promise-portal?");
39
47
  }
40
- return (props, children) => {
41
- let el = document.createElement("div");
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");
42
56
  el.setAttribute("data-promise-portal-container", "");
43
57
  document.body.appendChild(el);
44
- let _delay = unmountDelay || _instance.defaultOptions?.unmountDelay;
58
+ let unmountDelay = options.unmountDelay ?? instance.defaultOptions.unmountDelay;
45
59
  const setUnmountDelay = (delay) => {
46
- _delay = delay;
60
+ unmountDelay = delay;
47
61
  };
48
- let vNode;
62
+ const show = ref(options.initialShowValue ?? instance.defaultOptions.initialShowValue ?? true);
63
+ let vnode;
49
64
  const p = new Promise((resolve, reject) => {
50
- vNode = createVNode(component, props, children);
51
- _instance.map.set(vNode, { resolve, reject, el, vNode, setUnmountDelay });
52
- vNode.appContext = _instance.app._context;
53
- render(vNode, el);
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);
54
74
  });
55
75
  p.finally(() => {
76
+ show.value = false;
56
77
  setTimeout(() => {
57
- if (el) {
58
- render(null, el);
59
- document.body.removeChild(el);
60
- }
61
- el = null;
62
- vNode = null;
63
- }, _delay);
78
+ render(null, el);
79
+ document.body.removeChild(el);
80
+ }, unmountDelay);
64
81
  });
65
82
  return p;
66
83
  };
84
+ return [portal, ContextHolder];
67
85
  };
68
- var detectPromisePortalInstance = (config = {}) => {
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) {
69
100
  const {
70
101
  style = "position:fixed;top:0;right:0;text-align:right;line-height:1.3;color:red;z-index:9999;",
71
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.`
72
- } = config;
73
- const nodes = document.querySelectorAll("[data-promise-portal-container]");
74
- if (nodes.length > 0) {
75
- let el = document.querySelector("[data-promise-portal-detector]");
76
- if (!el) {
77
- el = document.createElement("div");
78
- el.setAttribute("data-promise-portal-detector", "");
79
- el.setAttribute("style", style);
80
- el.innerHTML = text;
81
- document.body.appendChild(el);
82
- }
83
- } else {
84
- document.querySelector("[data-promise-portal-detector]")?.remove();
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);
85
116
  }
86
- setTimeout(() => detectPromisePortalInstance(config), 200);
117
+ }
118
+ var detectPromisePortalInstance = (options = {}) => {
119
+ const timer = setInterval(() => detect(options), 200);
120
+ return () => clearInterval(timer);
87
121
  };
88
122
  export {
123
+ ContextProvider,
89
124
  createPromisePortal,
90
125
  definePortal,
91
126
  detectPromisePortalInstance,
92
127
  getActiveInstance,
93
128
  setActiveInstance,
94
- usePortalContext
129
+ usePortalContext,
130
+ version
95
131
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "promise-portal",
3
- "version": "1.0.6",
4
- "description": "let you use react portal in vue, and with promise",
3
+ "version": "1.2.1",
4
+ "description": "use component as a promisd-like function",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
7
7
  "module": "dist/index.js",
@@ -22,7 +22,7 @@
22
22
  "devDependencies": {
23
23
  "tsup": "^6.2.3",
24
24
  "typescript": "^4.8.2",
25
- "vue": "^3.2.37"
25
+ "vue": "^3.3.8"
26
26
  },
27
27
  "author": {
28
28
  "name": "tjyuanpeng",