@inertia-vuetify/modal 0.0.0-stage → 3.0.0-rc.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/LICENSE +50 -0
- package/README.md +371 -2
- package/dist/CloseButton.vue.d.ts +3 -0
- package/dist/Deferred.vue.d.ts +16 -0
- package/dist/ModalBtn.vue.d.ts +17 -0
- package/dist/ModalContent.vue.d.ts +33 -0
- package/dist/ModalLink.vue.d.ts +201 -0
- package/dist/ModalRenderer.vue.d.ts +6 -0
- package/dist/ModalRoot.vue.d.ts +13 -0
- package/dist/WhenVisible.vue.d.ts +24 -0
- package/dist/app.d.ts +5 -0
- package/dist/cache.d.ts +12 -0
- package/dist/config.d.ts +16 -0
- package/dist/helpers.d.ts +5 -0
- package/dist/index.d.ts +14 -0
- package/dist/inertia-modal-vuetify.js +1140 -0
- package/dist/keys.d.ts +2 -0
- package/dist/modalStack.d.ts +64 -0
- package/dist/transitions.d.ts +10 -0
- package/dist/types.d.ts +109 -0
- package/dist/useModal.d.ts +3 -0
- package/package.json +71 -4
- package/src/CloseButton.vue +13 -0
- package/src/Deferred.vue +21 -0
- package/src/HeadlessModal.vue +174 -0
- package/src/Modal.vue +120 -0
- package/src/ModalBtn.vue +28 -0
- package/src/ModalContent.vue +238 -0
- package/src/ModalLink.vue +189 -0
- package/src/ModalRenderer.vue +21 -0
- package/src/ModalRoot.vue +152 -0
- package/src/WhenVisible.vue +90 -0
- package/src/app.ts +17 -0
- package/src/cache.ts +67 -0
- package/src/config.ts +116 -0
- package/src/helpers.ts +18 -0
- package/src/index.ts +15 -0
- package/src/keys.ts +3 -0
- package/src/modalStack.ts +525 -0
- package/src/transitions.ts +30 -0
- package/src/types.ts +121 -0
- package/src/useModal.ts +7 -0
package/src/app.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { h, type App, type Component, type VNode } from 'vue'
|
|
2
|
+
import ModalRoot from './ModalRoot.vue'
|
|
3
|
+
|
|
4
|
+
/** Renders the Inertia app inside `ModalRoot`, which renders the modal stack. */
|
|
5
|
+
export function renderApp(App: Component, props: Record<string, any>): () => VNode {
|
|
6
|
+
return () => h(ModalRoot, () => h(App, props))
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Wraps an app's existing render function in `ModalRoot`. */
|
|
10
|
+
export function withInertiaModal(app: App): void {
|
|
11
|
+
const component = app._component as { render?: () => VNode }
|
|
12
|
+
const render = component.render
|
|
13
|
+
|
|
14
|
+
if (render) {
|
|
15
|
+
component.render = () => h(ModalRoot, () => render())
|
|
16
|
+
}
|
|
17
|
+
}
|
package/src/cache.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
interface CacheEntry<T> {
|
|
2
|
+
response: T
|
|
3
|
+
expiresAt: number
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export class ResponseCache<T> {
|
|
7
|
+
private cache = new Map<string, CacheEntry<T>>()
|
|
8
|
+
private timers = new Map<string, ReturnType<typeof setTimeout>>()
|
|
9
|
+
private inFlight = new Map<string, Promise<T>>()
|
|
10
|
+
|
|
11
|
+
static key(method: string, url: string, data: unknown): string {
|
|
12
|
+
return `${method}:${url}:${JSON.stringify(data)}`
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
get(key: string): T | null {
|
|
16
|
+
const cached = this.cache.get(key)
|
|
17
|
+
|
|
18
|
+
if (!cached) {
|
|
19
|
+
return null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (Date.now() > cached.expiresAt) {
|
|
23
|
+
this.delete(key)
|
|
24
|
+
return null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return cached.response
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
set(key: string, response: T, cacheFor: number): void {
|
|
31
|
+
this.delete(key)
|
|
32
|
+
|
|
33
|
+
this.cache.set(key, {
|
|
34
|
+
response,
|
|
35
|
+
expiresAt: Date.now() + cacheFor,
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
if (cacheFor > 0) {
|
|
39
|
+
this.timers.set(
|
|
40
|
+
key,
|
|
41
|
+
setTimeout(() => this.delete(key), cacheFor),
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
delete(key: string): void {
|
|
47
|
+
this.cache.delete(key)
|
|
48
|
+
|
|
49
|
+
const timer = this.timers.get(key)
|
|
50
|
+
if (timer) {
|
|
51
|
+
clearTimeout(timer)
|
|
52
|
+
this.timers.delete(key)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
getInFlight(key: string): Promise<T> | undefined {
|
|
57
|
+
return this.inFlight.get(key)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
setInFlight(key: string, promise: Promise<T>): void {
|
|
61
|
+
this.inFlight.set(key, promise)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
deleteInFlight(key: string): void {
|
|
65
|
+
this.inFlight.delete(key)
|
|
66
|
+
}
|
|
67
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { GlobalModalConfig, ModalPosition, ModalTypeConfig } from './types'
|
|
2
|
+
|
|
3
|
+
type Config = Required<Omit<GlobalModalConfig, 'modal' | 'slideover'>> & {
|
|
4
|
+
modal: ModalTypeConfig
|
|
5
|
+
slideover: ModalTypeConfig
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const defaultConfig: Config = {
|
|
9
|
+
type: 'modal',
|
|
10
|
+
navigate: false,
|
|
11
|
+
appElement: '#app',
|
|
12
|
+
modal: {
|
|
13
|
+
closeButton: true,
|
|
14
|
+
closeExplicitly: false,
|
|
15
|
+
closeOnClickOutside: true,
|
|
16
|
+
maxWidth: '2xl',
|
|
17
|
+
paddingClasses: 'pa-4 pa-sm-6',
|
|
18
|
+
panelClasses: '',
|
|
19
|
+
position: 'center',
|
|
20
|
+
fullscreen: 'sm',
|
|
21
|
+
},
|
|
22
|
+
slideover: {
|
|
23
|
+
closeButton: true,
|
|
24
|
+
closeExplicitly: false,
|
|
25
|
+
closeOnClickOutside: true,
|
|
26
|
+
maxWidth: 'md',
|
|
27
|
+
paddingClasses: 'pa-4 pa-sm-6',
|
|
28
|
+
panelClasses: '',
|
|
29
|
+
position: 'right',
|
|
30
|
+
fullscreen: false,
|
|
31
|
+
},
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let config: Config
|
|
35
|
+
|
|
36
|
+
export function resetConfig(): void {
|
|
37
|
+
config = structuredClone(defaultConfig)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Replaces the configuration (omitted keys get their defaults) or sets one dot-notation key. */
|
|
41
|
+
export function putConfig(config: GlobalModalConfig): void
|
|
42
|
+
export function putConfig(key: string, value: unknown): void
|
|
43
|
+
export function putConfig(key: string | GlobalModalConfig, value?: unknown): void {
|
|
44
|
+
if (typeof key === 'object') {
|
|
45
|
+
config = {
|
|
46
|
+
type: key.type ?? defaultConfig.type,
|
|
47
|
+
navigate: key.navigate ?? defaultConfig.navigate,
|
|
48
|
+
appElement: key.appElement !== undefined ? key.appElement : defaultConfig.appElement,
|
|
49
|
+
modal: { ...defaultConfig.modal, ...key.modal },
|
|
50
|
+
slideover: { ...defaultConfig.slideover, ...key.slideover },
|
|
51
|
+
}
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const keys = key.split('.')
|
|
56
|
+
let current = config as Record<string, unknown>
|
|
57
|
+
|
|
58
|
+
for (const segment of keys.slice(0, -1)) {
|
|
59
|
+
current = (current[segment] ??= {}) as Record<string, unknown>
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
current[keys[keys.length - 1]] = value
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Reads the whole configuration or one dot-notation key; missing keys return `null`. */
|
|
66
|
+
export function getConfig(key?: string): unknown {
|
|
67
|
+
if (key === undefined) {
|
|
68
|
+
return config
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let current: unknown = config
|
|
72
|
+
|
|
73
|
+
for (const segment of key.split('.')) {
|
|
74
|
+
if (current === null || typeof current !== 'object') {
|
|
75
|
+
return null
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
current = (current as Record<string, unknown>)[segment]
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return current ?? null
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function getConfigByType<K extends keyof ModalTypeConfig>(slideover: boolean, key: K): ModalTypeConfig[K] {
|
|
85
|
+
return getConfig(`${slideover ? 'slideover' : 'modal'}.${key}`) as ModalTypeConfig[K]
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Max-width tokens name Tailwind's max-w-* scale, like the original package.
|
|
89
|
+
const maxWidths: Record<string, string> = {
|
|
90
|
+
sm: '384px',
|
|
91
|
+
md: '448px',
|
|
92
|
+
lg: '512px',
|
|
93
|
+
xl: '576px',
|
|
94
|
+
'2xl': '672px',
|
|
95
|
+
'3xl': '768px',
|
|
96
|
+
'4xl': '896px',
|
|
97
|
+
'5xl': '1024px',
|
|
98
|
+
'6xl': '1152px',
|
|
99
|
+
'7xl': '1280px',
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Maps a max-width token to a CSS width; unknown tokens fall back to `2xl`.
|
|
104
|
+
*/
|
|
105
|
+
export function getMaxWidth(maxWidth?: string | null): string {
|
|
106
|
+
return maxWidths[maxWidth ?? ''] ?? maxWidths['2xl']
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Maps a modal position to a Vuetify dialog location.
|
|
111
|
+
*/
|
|
112
|
+
export function getDialogLocation(position?: string | null): ModalPosition {
|
|
113
|
+
return position === 'top' || position === 'bottom' ? position : 'center'
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
resetConfig()
|
package/src/helpers.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { generateId as generateVanillaId } from '@inertiaui/vanilla'
|
|
2
|
+
|
|
3
|
+
export { except, kebabCase, only, rejectNullValues, sameUrlPath } from '@inertiaui/vanilla'
|
|
4
|
+
|
|
5
|
+
export function parseResponseData(data: unknown): unknown {
|
|
6
|
+
return typeof data === 'string' ? JSON.parse(data) : data
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let generateIdUsingCallback: (() => string) | null = null
|
|
10
|
+
|
|
11
|
+
/** Overrides id generation, e.g. to make request headers predictable in tests. */
|
|
12
|
+
export function generateIdUsing(callback: (() => string) | null): void {
|
|
13
|
+
generateIdUsingCallback = callback
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function generateId(prefix = 'inertiaui_modal_'): string {
|
|
17
|
+
return generateIdUsingCallback?.() ?? generateVanillaId(prefix)
|
|
18
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export * as dialogUtils from '@inertiaui/vanilla'
|
|
2
|
+
export type { CleanupFunction, EscapeKeyOptions, FocusTrapOptions } from '@inertiaui/vanilla'
|
|
3
|
+
|
|
4
|
+
export { renderApp, withInertiaModal } from './app'
|
|
5
|
+
export { getConfig, getDialogLocation, getMaxWidth, putConfig, resetConfig } from './config'
|
|
6
|
+
export { default as Deferred } from './Deferred.vue'
|
|
7
|
+
export { default as HeadlessModal } from './HeadlessModal.vue'
|
|
8
|
+
export { default as Modal } from './Modal.vue'
|
|
9
|
+
export { default as ModalBtn } from './ModalBtn.vue'
|
|
10
|
+
export { default as ModalLink } from './ModalLink.vue'
|
|
11
|
+
export { default as ModalRoot } from './ModalRoot.vue'
|
|
12
|
+
export { initFromPageProps, ModalInstance, modalPropNames, prefetch, useModalStack, visitModal, type ModalStack } from './modalStack'
|
|
13
|
+
export type * from './types'
|
|
14
|
+
export { default as useModal } from './useModal'
|
|
15
|
+
export { default as WhenVisible } from './WhenVisible.vue'
|
package/src/keys.ts
ADDED