best-dialog 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,223 @@
1
+ import * as vue from 'vue';
2
+ import { PropType, CSSProperties, VNode } from 'vue';
3
+
4
+ type DialogPosition = 'center' | 'top' | 'bottom' | 'left' | 'right' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
5
+ type DialogEffect = 'fade' | 'slide' | 'zoom';
6
+ type DialogContentType = string | object | null | undefined;
7
+ interface DialogAction {
8
+ label?: string;
9
+ onClick?: (close: () => void) => void | Promise<void> | boolean;
10
+ as?: 'button' | 'a';
11
+ href?: string;
12
+ target?: string;
13
+ class?: string;
14
+ primary?: boolean;
15
+ [key: string]: any;
16
+ }
17
+ type DialogActionItem = string | DialogAction;
18
+ interface DialogCloseEvent {
19
+ source: 'button' | 'overlay' | 'close-btn' | 'esc';
20
+ index: number;
21
+ button?: DialogAction;
22
+ }
23
+ interface DialogOptions {
24
+ title?: string | object;
25
+ content?: DialogContentType;
26
+ html?: boolean;
27
+ url?: string;
28
+ actions?: DialogActionItem[];
29
+ position?: DialogPosition;
30
+ effect?: DialogEffect;
31
+ overlay?: boolean;
32
+ overlayClose?: boolean;
33
+ escClose?: boolean;
34
+ closable?: boolean;
35
+ class?: string | string[] | Record<string, boolean>;
36
+ style?: CSSProperties | string;
37
+ width?: string | number;
38
+ fullscreen?: boolean;
39
+ zIndex?: number;
40
+ onOpen?: () => void;
41
+ onClose?: (e: DialogCloseEvent) => void;
42
+ [key: string]: any;
43
+ }
44
+ interface DialogHandle {
45
+ close: () => void;
46
+ onClose: (cb: (e: DialogCloseEvent) => void) => DialogHandle;
47
+ onOk: (cb: (value?: any) => void) => DialogHandle;
48
+ open: (opts?: DialogOptions) => DialogHandle;
49
+ alert: (content: string, title?: string) => DialogHandle;
50
+ confirm: (content: string, title?: string) => DialogHandle;
51
+ prompt: (placeholder?: string, title?: string, defaultValue?: string) => DialogHandle;
52
+ }
53
+ declare function useDialog(defaults?: DialogOptions): DialogHandle;
54
+ declare function showDialog(options: DialogOptions): (e?: DialogCloseEvent) => void;
55
+ declare const BestDialog: vue.DefineComponent<vue.ExtractPropTypes<{
56
+ modelValue: {
57
+ type: BooleanConstructor;
58
+ default: boolean;
59
+ };
60
+ title: {
61
+ type: PropType<string | object>;
62
+ default: undefined;
63
+ };
64
+ content: {
65
+ type: PropType<DialogContentType>;
66
+ default: undefined;
67
+ };
68
+ html: {
69
+ type: BooleanConstructor;
70
+ default: boolean;
71
+ };
72
+ url: {
73
+ type: StringConstructor;
74
+ default: undefined;
75
+ };
76
+ actions: {
77
+ type: PropType<DialogActionItem[]>;
78
+ default: undefined;
79
+ };
80
+ position: {
81
+ type: PropType<DialogPosition>;
82
+ default: string;
83
+ };
84
+ effect: {
85
+ type: PropType<DialogEffect>;
86
+ default: undefined;
87
+ };
88
+ overlay: {
89
+ type: BooleanConstructor;
90
+ default: boolean;
91
+ };
92
+ overlayClose: {
93
+ type: BooleanConstructor;
94
+ default: boolean;
95
+ };
96
+ escClose: {
97
+ type: BooleanConstructor;
98
+ default: boolean;
99
+ };
100
+ closable: {
101
+ type: BooleanConstructor;
102
+ default: boolean;
103
+ };
104
+ width: {
105
+ type: (StringConstructor | NumberConstructor)[];
106
+ default: string;
107
+ };
108
+ fullscreen: {
109
+ type: BooleanConstructor;
110
+ default: boolean;
111
+ };
112
+ zIndex: {
113
+ type: NumberConstructor;
114
+ default: undefined;
115
+ };
116
+ dialogClass: {
117
+ type: PropType<any>;
118
+ default: undefined;
119
+ };
120
+ dialogStyle: {
121
+ type: PropType<CSSProperties | string>;
122
+ default: undefined;
123
+ };
124
+ }>, () => VNode<vue.RendererNode, vue.RendererElement, {
125
+ [key: string]: any;
126
+ }> | null, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, ("update:modelValue" | "open" | "close")[], "update:modelValue" | "open" | "close", vue.PublicProps, Readonly<vue.ExtractPropTypes<{
127
+ modelValue: {
128
+ type: BooleanConstructor;
129
+ default: boolean;
130
+ };
131
+ title: {
132
+ type: PropType<string | object>;
133
+ default: undefined;
134
+ };
135
+ content: {
136
+ type: PropType<DialogContentType>;
137
+ default: undefined;
138
+ };
139
+ html: {
140
+ type: BooleanConstructor;
141
+ default: boolean;
142
+ };
143
+ url: {
144
+ type: StringConstructor;
145
+ default: undefined;
146
+ };
147
+ actions: {
148
+ type: PropType<DialogActionItem[]>;
149
+ default: undefined;
150
+ };
151
+ position: {
152
+ type: PropType<DialogPosition>;
153
+ default: string;
154
+ };
155
+ effect: {
156
+ type: PropType<DialogEffect>;
157
+ default: undefined;
158
+ };
159
+ overlay: {
160
+ type: BooleanConstructor;
161
+ default: boolean;
162
+ };
163
+ overlayClose: {
164
+ type: BooleanConstructor;
165
+ default: boolean;
166
+ };
167
+ escClose: {
168
+ type: BooleanConstructor;
169
+ default: boolean;
170
+ };
171
+ closable: {
172
+ type: BooleanConstructor;
173
+ default: boolean;
174
+ };
175
+ width: {
176
+ type: (StringConstructor | NumberConstructor)[];
177
+ default: string;
178
+ };
179
+ fullscreen: {
180
+ type: BooleanConstructor;
181
+ default: boolean;
182
+ };
183
+ zIndex: {
184
+ type: NumberConstructor;
185
+ default: undefined;
186
+ };
187
+ dialogClass: {
188
+ type: PropType<any>;
189
+ default: undefined;
190
+ };
191
+ dialogStyle: {
192
+ type: PropType<CSSProperties | string>;
193
+ default: undefined;
194
+ };
195
+ }>> & Readonly<{
196
+ "onUpdate:modelValue"?: ((...args: any[]) => any) | undefined;
197
+ onOpen?: ((...args: any[]) => any) | undefined;
198
+ onClose?: ((...args: any[]) => any) | undefined;
199
+ }>, {
200
+ modelValue: boolean;
201
+ title: string | object;
202
+ content: DialogContentType;
203
+ html: boolean;
204
+ url: string;
205
+ actions: DialogActionItem[];
206
+ position: DialogPosition;
207
+ effect: DialogEffect;
208
+ overlay: boolean;
209
+ overlayClose: boolean;
210
+ escClose: boolean;
211
+ closable: boolean;
212
+ width: string | number;
213
+ fullscreen: boolean;
214
+ zIndex: number;
215
+ dialogClass: any;
216
+ dialogStyle: string | CSSProperties;
217
+ }, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
218
+ declare const BestDialogContainer: vue.DefineComponent<{}, () => VNode<vue.RendererNode, vue.RendererElement, {
219
+ [key: string]: any;
220
+ }>, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
221
+
222
+ export { BestDialog, BestDialogContainer, showDialog, useDialog };
223
+ export type { DialogAction, DialogActionItem, DialogCloseEvent, DialogContentType, DialogEffect, DialogHandle, DialogOptions, DialogPosition };
@@ -0,0 +1,380 @@
1
+ import { ref, defineComponent, h, computed, watch, onMounted, onUnmounted, Teleport, Transition, markRaw } from 'vue';
2
+
3
+ const store = ref([]);
4
+ let _uid = 0;
5
+ function uid() {
6
+ return `bd-${++_uid}`;
7
+ }
8
+ function storeAdd(id, options) {
9
+ const existing = store.value.find((d) => d.id === id);
10
+ if (existing) {
11
+ existing.options = options;
12
+ existing.visible = true;
13
+ } else store.value.push({ id, visible: true, options });
14
+ }
15
+ function storeHide(id) {
16
+ const d = store.value.find((d2) => d2.id === id);
17
+ if (d) d.visible = false;
18
+ }
19
+ function storeRemove(id) {
20
+ const i = store.value.findIndex((d) => d.id === id);
21
+ if (i !== -1) store.value.splice(i, 1);
22
+ }
23
+ function storeClose(id, e) {
24
+ const item = store.value.find((d) => d.id === id);
25
+ if (!item) return;
26
+ item.visible = false;
27
+ item.options.onClose?.(e || { source: "overlay", index: -1 });
28
+ setTimeout(() => storeRemove(id), 420);
29
+ }
30
+ function useDialog(defaults = {}) {
31
+ const id = uid();
32
+ const options = ref({ ...defaults });
33
+ let _onClose = null;
34
+ let _onOk = null;
35
+ let _closing = false;
36
+ onUnmounted(() => storeRemove(id));
37
+ function doClose(e) {
38
+ if (_closing) return;
39
+ _closing = true;
40
+ const evt = { source: "overlay", index: -1 };
41
+ _onClose?.(evt);
42
+ storeHide(id);
43
+ setTimeout(() => {
44
+ storeRemove(id);
45
+ _closing = false;
46
+ }, 420);
47
+ }
48
+ function open(opts) {
49
+ if (opts) options.value = { ...options.value, ...opts };
50
+ _onClose = null;
51
+ _onOk = null;
52
+ _closing = false;
53
+ const userOnClose = options.value.onClose;
54
+ options.value.onClose = (e) => {
55
+ userOnClose?.(e);
56
+ if (!_closing) {
57
+ _closing = true;
58
+ _onClose?.(e);
59
+ }
60
+ };
61
+ storeAdd(id, options.value);
62
+ options.value.onOpen?.();
63
+ return handle;
64
+ }
65
+ const handle = {
66
+ close: () => doClose(),
67
+ onClose(cb) {
68
+ _onClose = cb;
69
+ return handle;
70
+ },
71
+ onOk(cb) {
72
+ _onOk = cb;
73
+ return handle;
74
+ },
75
+ open,
76
+ alert(content, title) {
77
+ open({
78
+ title: title || "\u63D0\u793A",
79
+ content,
80
+ actions: [{ label: "\u786E\u5B9A", primary: true }]
81
+ });
82
+ return handle;
83
+ },
84
+ confirm(content, title) {
85
+ open({
86
+ title: title || "\u786E\u8BA4",
87
+ content,
88
+ actions: [
89
+ { label: "\u53D6\u6D88" },
90
+ {
91
+ label: "\u786E\u5B9A",
92
+ primary: true,
93
+ onClick: () => {
94
+ _onOk?.();
95
+ }
96
+ }
97
+ ]
98
+ });
99
+ return handle;
100
+ },
101
+ prompt(placeholder, title, defaultValue) {
102
+ const inputVal = ref(defaultValue ?? "");
103
+ const PromptInput = markRaw(defineComponent({
104
+ setup() {
105
+ return () => h("input", {
106
+ class: "bd-dialog__input",
107
+ type: "text",
108
+ placeholder: placeholder || "\u8BF7\u8F93\u5165...",
109
+ value: inputVal.value,
110
+ onInput: (e) => {
111
+ inputVal.value = e.target.value;
112
+ }
113
+ });
114
+ }
115
+ }));
116
+ open({
117
+ title: title || "\u8F93\u5165",
118
+ content: PromptInput,
119
+ actions: [
120
+ { label: "\u53D6\u6D88" },
121
+ {
122
+ label: "\u786E\u5B9A",
123
+ primary: true,
124
+ onClick: () => {
125
+ _onOk?.(inputVal.value);
126
+ }
127
+ }
128
+ ]
129
+ });
130
+ return handle;
131
+ }
132
+ };
133
+ return handle;
134
+ }
135
+ function showDialog(options) {
136
+ const id = uid();
137
+ storeAdd(id, options);
138
+ options.onOpen?.();
139
+ const close = (e) => storeClose(id, e);
140
+ return close;
141
+ }
142
+ function effectForPos(pos) {
143
+ return pos === "center" ? "zoom" : "slide";
144
+ }
145
+ function normActions(raw) {
146
+ if (!raw?.length) return [];
147
+ return raw.map((a) => typeof a === "string" ? { label: a } : a);
148
+ }
149
+ const XIcon = defineComponent({
150
+ setup() {
151
+ return () => h("svg", {
152
+ viewBox: "0 0 24 24",
153
+ width: 18,
154
+ height: 18,
155
+ fill: "none",
156
+ stroke: "currentColor",
157
+ "stroke-width": 2,
158
+ "stroke-linecap": "round",
159
+ "stroke-linejoin": "round"
160
+ }, [
161
+ h("line", { x1: 18, y1: 6, x2: 6, y2: 18 }),
162
+ h("line", { x1: 6, y1: 6, x2: 18, y2: 18 })
163
+ ]);
164
+ }
165
+ });
166
+ function renderBtn(a, close, isLast, index) {
167
+ const label = a.label || "\u786E\u5B9A";
168
+ const primary = a.primary ?? isLast;
169
+ const cls = ["bd-dialog__btn", { "bd-dialog__btn--primary": primary }, a.class];
170
+ if (a.as === "a") {
171
+ return h("a", {
172
+ class: cls,
173
+ href: a.href || "#",
174
+ target: a.target,
175
+ "data-index": String(index)
176
+ }, label);
177
+ }
178
+ async function click() {
179
+ const evt = { source: "button", index, button: a };
180
+ if (!a.onClick) {
181
+ close(evt);
182
+ return;
183
+ }
184
+ const r = a.onClick(() => close(evt));
185
+ if (r instanceof Promise) {
186
+ if (await r !== false) close(evt);
187
+ } else if (r !== false) close(evt);
188
+ }
189
+ return h("button", {
190
+ class: cls,
191
+ type: "button",
192
+ onClick: click,
193
+ "data-index": String(index)
194
+ }, label);
195
+ }
196
+ function renderDialogBox(o, close, bodySlot, actionsSlot) {
197
+ const pos = o.position || "center";
198
+ const showOverlay = o.overlay !== false;
199
+ const showClose = o.closable !== false;
200
+ const isFS = !!o.fullscreen;
201
+ const w = o.width ?? "440px";
202
+ const wStyle = typeof w === "number" ? `${w}px` : w;
203
+ const dStyle = {
204
+ ...typeof o.style === "string" ? {} : o.style || {},
205
+ ...isFS ? {} : { width: wStyle },
206
+ ...o.zIndex ? { zIndex: o.zIndex } : {}
207
+ };
208
+ const actions = normActions(o.actions);
209
+ let body = null;
210
+ if (bodySlot) {
211
+ body = bodySlot;
212
+ } else if (o.url) {
213
+ body = h("iframe", { src: o.url, class: "bd-dialog__iframe", frameborder: "0", allow: "fullscreen" });
214
+ } else if (o.html && typeof o.content === "string") {
215
+ body = h("div", { class: "bd-dialog__html", innerHTML: o.content });
216
+ } else if (typeof o.content === "string") {
217
+ body = h("p", { class: "bd-dialog__text" }, o.content);
218
+ } else if (o.content) {
219
+ body = [h(o.content)];
220
+ }
221
+ let titleNode = null;
222
+ if (o.title) {
223
+ titleNode = typeof o.title === "string" ? h("span", { class: "bd-dialog__title" }, o.title) : Array.isArray(o.title) ? o.title : h(o.title);
224
+ }
225
+ let footer = null;
226
+ if (actionsSlot) {
227
+ footer = h("div", { class: "bd-dialog__footer" }, actionsSlot);
228
+ } else if (actions.length > 0) {
229
+ footer = h(
230
+ "div",
231
+ { class: "bd-dialog__footer" },
232
+ actions.map((a, i) => renderBtn(a, close, i === actions.length - 1, i))
233
+ );
234
+ }
235
+ return h("div", {
236
+ class: ["bd-overlay", pos, { "bd-fullscreen": isFS }],
237
+ style: showOverlay ? void 0 : { background: "transparent", pointerEvents: "none" },
238
+ onClick: (e) => {
239
+ if (e.target === e.currentTarget && o.overlayClose !== false) {
240
+ close({ source: "overlay", index: -1 });
241
+ }
242
+ }
243
+ }, [
244
+ h(
245
+ Transition,
246
+ {
247
+ name: `bd-${o.effect || effectForPos(pos)}`,
248
+ appear: true
249
+ },
250
+ () => h("div", {
251
+ class: ["bd-dialog", o.class, { "bd-dialog--fullscreen": isFS }],
252
+ style: dStyle,
253
+ role: "dialog",
254
+ "aria-modal": "true",
255
+ "aria-label": typeof o.title === "string" ? o.title : void 0,
256
+ onClick: (e) => e.stopPropagation()
257
+ }, [
258
+ showClose && h("button", {
259
+ class: "bd-dialog__close",
260
+ type: "button",
261
+ "aria-label": "\u5173\u95ED",
262
+ onClick: () => close({ source: "close-btn", index: -1 })
263
+ }, [h(XIcon)]),
264
+ titleNode && h("div", { class: "bd-dialog__header" }, [titleNode]),
265
+ h("div", { class: "bd-dialog__body" }, [body]),
266
+ footer
267
+ ])
268
+ )
269
+ ]);
270
+ }
271
+ const BestDialog = defineComponent({
272
+ name: "BestDialog",
273
+ inheritAttrs: false,
274
+ props: {
275
+ modelValue: { type: Boolean, default: false },
276
+ title: { type: [String, Object], default: void 0 },
277
+ content: { type: [String, Object], default: void 0 },
278
+ html: { type: Boolean, default: false },
279
+ url: { type: String, default: void 0 },
280
+ actions: { type: Array, default: void 0 },
281
+ position: { type: String, default: "center" },
282
+ effect: { type: String, default: void 0 },
283
+ overlay: { type: Boolean, default: true },
284
+ overlayClose: { type: Boolean, default: true },
285
+ escClose: { type: Boolean, default: true },
286
+ closable: { type: Boolean, default: true },
287
+ width: { type: [String, Number], default: "440px" },
288
+ fullscreen: { type: Boolean, default: false },
289
+ zIndex: { type: Number, default: void 0 },
290
+ dialogClass: { type: [String, Array, Object], default: void 0 },
291
+ dialogStyle: { type: [String, Object], default: void 0 }
292
+ },
293
+ emits: ["update:modelValue", "open", "close"],
294
+ setup(props, { emit, slots }) {
295
+ const localVisible = ref(props.modelValue);
296
+ computed(() => props.effect || effectForPos(props.position));
297
+ watch(() => props.modelValue, (v) => {
298
+ localVisible.value = v;
299
+ if (v) emit("open");
300
+ });
301
+ function close(e) {
302
+ localVisible.value = false;
303
+ emit("update:modelValue", false);
304
+ emit("close", e);
305
+ }
306
+ function onKey(e) {
307
+ if (e.key === "Escape" && localVisible.value && props.escClose) {
308
+ close({ source: "esc", index: -1 });
309
+ }
310
+ }
311
+ onMounted(() => document.addEventListener("keydown", onKey));
312
+ onUnmounted(() => document.removeEventListener("keydown", onKey));
313
+ return () => {
314
+ if (!localVisible.value) return null;
315
+ const bodySlot = slots.default ? slots.default() : void 0;
316
+ const actionsSlot = slots.actions ? slots.actions({ close }) : void 0;
317
+ const opts = {
318
+ title: props.title,
319
+ content: props.content,
320
+ html: props.html,
321
+ url: props.url,
322
+ actions: props.actions,
323
+ position: props.position,
324
+ effect: props.effect,
325
+ overlay: props.overlay,
326
+ overlayClose: props.overlayClose,
327
+ closable: props.closable,
328
+ class: props.dialogClass,
329
+ style: props.dialogStyle,
330
+ width: props.width,
331
+ fullscreen: props.fullscreen,
332
+ zIndex: props.zIndex
333
+ };
334
+ if (slots.title) opts.title = slots.title();
335
+ return h(Teleport, { to: "body" }, [
336
+ h(
337
+ Transition,
338
+ { name: "bd-overlay", appear: true },
339
+ () => renderDialogBox(opts, close, bodySlot, actionsSlot)
340
+ )
341
+ ]);
342
+ };
343
+ }
344
+ });
345
+ const BestDialogContainer = defineComponent({
346
+ name: "BestDialogContainer",
347
+ setup() {
348
+ function onKey(e) {
349
+ if (e.key !== "Escape") return;
350
+ for (let i = store.value.length - 1; i >= 0; i--) {
351
+ const d = store.value[i];
352
+ if (d.visible && d.options.escClose !== false) {
353
+ storeClose(d.id, { source: "esc", index: -1 });
354
+ break;
355
+ }
356
+ }
357
+ }
358
+ onMounted(() => document.addEventListener("keydown", onKey));
359
+ onUnmounted(() => document.removeEventListener("keydown", onKey));
360
+ return () => h(
361
+ Teleport,
362
+ { to: "body" },
363
+ store.value.map((item) => {
364
+ const o = item.options;
365
+ const close = (e) => storeClose(item.id, e);
366
+ return h(
367
+ Transition,
368
+ {
369
+ name: "bd-overlay",
370
+ appear: true,
371
+ key: item.id
372
+ },
373
+ () => item.visible ? renderDialogBox(o, close) : null
374
+ );
375
+ })
376
+ );
377
+ }
378
+ });
379
+
380
+ export { BestDialog, BestDialogContainer, showDialog, useDialog };
@@ -0,0 +1,3 @@
1
+ declare const _default: any;
2
+
3
+ export { _default as default };
@@ -0,0 +1,3 @@
1
+ declare const _default: any;
2
+
3
+ export { _default as default };
@@ -0,0 +1,17 @@
1
+ import { defineNuxtPlugin } from '#app';
2
+ import { createApp, h } from 'vue';
3
+ import { BestDialogContainer } from './index.mjs';
4
+
5
+ const plugin = defineNuxtPlugin((_nuxtApp) => {
6
+ if (import.meta.client) {
7
+ const el = document.createElement("div");
8
+ el.id = "__best-dialog";
9
+ document.body.appendChild(el);
10
+ const app = createApp({
11
+ render: () => h(BestDialogContainer)
12
+ });
13
+ app.mount(el);
14
+ }
15
+ });
16
+
17
+ export { plugin as default };