@blueprint-ts/core 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.
- package/.editorconfig +508 -0
- package/.eslintrc.cjs +15 -0
- package/.prettierrc.json +8 -0
- package/LICENSE +21 -0
- package/README.md +1 -0
- package/docker-compose.yaml +8 -0
- package/docs/.vitepress/config.ts +68 -0
- package/docs/.vitepress/theme/Layout.vue +14 -0
- package/docs/.vitepress/theme/components/VersionSelector.vue +64 -0
- package/docs/.vitepress/theme/index.js +13 -0
- package/docs/index.md +70 -0
- package/docs/services/laravel/pagination.md +54 -0
- package/docs/services/laravel/requests.md +62 -0
- package/docs/services/requests/index.md +74 -0
- package/docs/vue/forms.md +326 -0
- package/docs/vue/requests/route-model-binding.md +66 -0
- package/docs/vue/state.md +293 -0
- package/env.d.ts +1 -0
- package/eslint.config.js +15 -0
- package/examples/files/7z2404-x64.exe +0 -0
- package/examples/index.html +14 -0
- package/examples/js/app.js +8 -0
- package/examples/js/router.js +22 -0
- package/examples/js/view/App.vue +49 -0
- package/examples/js/view/layout/DemoPage.vue +28 -0
- package/examples/js/view/pagination/Pagination.vue +28 -0
- package/examples/js/view/pagination/components/errorPagination/ErrorPagination.vue +71 -0
- package/examples/js/view/pagination/components/errorPagination/GetProductsRequest.ts +54 -0
- package/examples/js/view/pagination/components/infiniteScrolling/GetProductsRequest.ts +50 -0
- package/examples/js/view/pagination/components/infiniteScrolling/InfiniteScrolling.vue +57 -0
- package/examples/js/view/pagination/components/tablePagination/GetProductsRequest.ts +50 -0
- package/examples/js/view/pagination/components/tablePagination/TablePagination.vue +63 -0
- package/examples/js/view/requests/Requests.vue +34 -0
- package/examples/js/view/requests/components/abortableRequest/AbortableRequest.vue +36 -0
- package/examples/js/view/requests/components/abortableRequest/GetProductsRequest.ts +25 -0
- package/examples/js/view/requests/components/fileDownloadRequest/DownloadFileRequest.ts +15 -0
- package/examples/js/view/requests/components/fileDownloadRequest/FileDownloadRequest.vue +44 -0
- package/examples/js/view/requests/components/getRequestWithDynamicParams/GetProductsRequest.ts +34 -0
- package/examples/js/view/requests/components/getRequestWithDynamicParams/GetRequestWithDynamicParams.vue +59 -0
- package/examples/js/view/requests/components/serverErrorRequest/ServerErrorRequest.ts +21 -0
- package/examples/js/view/requests/components/serverErrorRequest/ServerErrorRequest.vue +53 -0
- package/package.json +81 -0
- package/release-tool.json +7 -0
- package/src/helpers.ts +78 -0
- package/src/service/bulkRequests/BulkRequestEvent.enum.ts +4 -0
- package/src/service/bulkRequests/BulkRequestSender.ts +184 -0
- package/src/service/bulkRequests/BulkRequestWrapper.ts +49 -0
- package/src/service/bulkRequests/index.ts +6 -0
- package/src/service/laravel/pagination/contracts/PaginationParamsContract.ts +4 -0
- package/src/service/laravel/pagination/contracts/PaginationResponseBodyContract.ts +6 -0
- package/src/service/laravel/pagination/dataDrivers/RequestDriver.ts +32 -0
- package/src/service/laravel/pagination/index.ts +7 -0
- package/src/service/laravel/requests/JsonBaseRequest.ts +35 -0
- package/src/service/laravel/requests/PaginationJsonBaseRequest.ts +29 -0
- package/src/service/laravel/requests/index.ts +9 -0
- package/src/service/laravel/requests/responses/JsonResponse.ts +8 -0
- package/src/service/laravel/requests/responses/PaginationResponse.ts +16 -0
- package/src/service/pagination/InfiniteScroller.ts +21 -0
- package/src/service/pagination/Paginator.ts +149 -0
- package/src/service/pagination/contracts/PaginateableRequestContract.ts +13 -0
- package/src/service/pagination/contracts/PaginationDataDriverContract.ts +5 -0
- package/src/service/pagination/contracts/PaginationResponseContract.ts +7 -0
- package/src/service/pagination/contracts/PaginatorLoadDataOptions.ts +4 -0
- package/src/service/pagination/contracts/ViewDriverContract.ts +12 -0
- package/src/service/pagination/contracts/ViewDriverFactoryContract.ts +5 -0
- package/src/service/pagination/dataDrivers/ArrayDriver.ts +28 -0
- package/src/service/pagination/dtos/PaginationDataDto.ts +14 -0
- package/src/service/pagination/factories/VuePaginationDriverFactory.ts +9 -0
- package/src/service/pagination/frontendDrivers/VuePaginationDriver.ts +61 -0
- package/src/service/pagination/index.ts +16 -0
- package/src/service/persistenceDrivers/LocalStorageDriver.ts +22 -0
- package/src/service/persistenceDrivers/NonPersistentDriver.ts +12 -0
- package/src/service/persistenceDrivers/SessionStorageDriver.ts +22 -0
- package/src/service/persistenceDrivers/index.ts +8 -0
- package/src/service/persistenceDrivers/types/PersistenceDriver.ts +5 -0
- package/src/service/requests/BaseRequest.ts +197 -0
- package/src/service/requests/ErrorHandler.ts +64 -0
- package/src/service/requests/RequestEvents.enum.ts +3 -0
- package/src/service/requests/RequestMethod.enum.ts +8 -0
- package/src/service/requests/bodies/FormDataBody.ts +41 -0
- package/src/service/requests/bodies/JsonBody.ts +16 -0
- package/src/service/requests/contracts/AbortableRequestContract.ts +3 -0
- package/src/service/requests/contracts/BaseRequestContract.ts +36 -0
- package/src/service/requests/contracts/BodyContract.ts +7 -0
- package/src/service/requests/contracts/BodyFactoryContract.ts +5 -0
- package/src/service/requests/contracts/DriverConfigContract.ts +7 -0
- package/src/service/requests/contracts/HeadersContract.ts +5 -0
- package/src/service/requests/contracts/RequestDriverContract.ts +15 -0
- package/src/service/requests/contracts/RequestLoaderContract.ts +5 -0
- package/src/service/requests/contracts/RequestLoaderFactoryContract.ts +5 -0
- package/src/service/requests/contracts/ResponseContract.ts +7 -0
- package/src/service/requests/drivers/contracts/ResponseHandlerContract.ts +10 -0
- package/src/service/requests/drivers/fetch/FetchDriver.ts +115 -0
- package/src/service/requests/drivers/fetch/FetchResponse.ts +30 -0
- package/src/service/requests/exceptions/NoResponseReceivedException.ts +3 -0
- package/src/service/requests/exceptions/NotFoundException.ts +3 -0
- package/src/service/requests/exceptions/PageExpiredException.ts +3 -0
- package/src/service/requests/exceptions/ResponseBodyException.ts +15 -0
- package/src/service/requests/exceptions/ResponseException.ts +11 -0
- package/src/service/requests/exceptions/ServerErrorException.ts +3 -0
- package/src/service/requests/exceptions/UnauthorizedException.ts +3 -0
- package/src/service/requests/exceptions/ValidationException.ts +3 -0
- package/src/service/requests/exceptions/index.ts +19 -0
- package/src/service/requests/factories/FormDataFactory.ts +9 -0
- package/src/service/requests/factories/JsonBodyFactory.ts +9 -0
- package/src/service/requests/index.ts +50 -0
- package/src/service/requests/responses/BaseResponse.ts +41 -0
- package/src/service/requests/responses/BlobResponse.ts +19 -0
- package/src/service/requests/responses/JsonResponse.ts +15 -0
- package/src/service/requests/responses/PlainTextResponse.ts +15 -0
- package/src/service/support/DeferredPromise.ts +67 -0
- package/src/service/support/index.ts +3 -0
- package/src/vue/composables/useConfirmDialog.ts +59 -0
- package/src/vue/composables/useGlobalCheckbox.ts +145 -0
- package/src/vue/composables/useIsEmpty.ts +34 -0
- package/src/vue/composables/useIsOpen.ts +37 -0
- package/src/vue/composables/useIsOpenFromVar.ts +61 -0
- package/src/vue/composables/useModelWrapper.ts +24 -0
- package/src/vue/composables/useOnOpen.ts +34 -0
- package/src/vue/contracts/ModelValueOptions.ts +3 -0
- package/src/vue/contracts/ModelValueProps.ts +3 -0
- package/src/vue/forms/BaseForm.ts +1074 -0
- package/src/vue/forms/PropertyAwareArray.ts +78 -0
- package/src/vue/forms/index.ts +11 -0
- package/src/vue/forms/types/PersistedForm.ts +6 -0
- package/src/vue/forms/validation/ValidationMode.enum.ts +14 -0
- package/src/vue/forms/validation/index.ts +12 -0
- package/src/vue/forms/validation/rules/BaseRule.ts +7 -0
- package/src/vue/forms/validation/rules/ConfirmedRule.ts +39 -0
- package/src/vue/forms/validation/rules/MinRule.ts +61 -0
- package/src/vue/forms/validation/rules/RequiredRule.ts +19 -0
- package/src/vue/forms/validation/rules/UrlRule.ts +24 -0
- package/src/vue/forms/validation/types/BidirectionalRule.ts +11 -0
- package/src/vue/index.ts +14 -0
- package/src/vue/requests/factories/VueRequestLoaderFactory.ts +9 -0
- package/src/vue/requests/index.ts +5 -0
- package/src/vue/requests/loaders/VueRequestBatchLoader.ts +30 -0
- package/src/vue/requests/loaders/VueRequestLoader.ts +18 -0
- package/src/vue/router/routeModelBinding/RouteModelRequestResolver.ts +11 -0
- package/src/vue/router/routeModelBinding/defineRoute.ts +31 -0
- package/src/vue/router/routeModelBinding/index.ts +8 -0
- package/src/vue/router/routeModelBinding/installRouteInjection.ts +73 -0
- package/src/vue/router/routeModelBinding/types.ts +46 -0
- package/src/vue/state/State.ts +391 -0
- package/src/vue/state/index.ts +3 -0
- package/tests/service/helpers/mergeDeep.test.ts +53 -0
- package/tests/service/laravel/pagination/dataDrivers/RequestDriver.test.ts +84 -0
- package/tests/service/laravel/requests/JsonBaseRequest.test.ts +43 -0
- package/tests/service/laravel/requests/PaginationJsonBaseRequest.test.ts +58 -0
- package/tests/service/laravel/requests/responses/JsonResponse.test.ts +59 -0
- package/tests/service/laravel/requests/responses/PaginationResponse.test.ts +127 -0
- package/tests/service/pagination/dtos/PaginationDataDto.test.ts +35 -0
- package/tests/service/pagination/factories/VuePaginationDriverFactory.test.ts +32 -0
- package/tests/service/pagination/frontendDrivers/VuePaginationDriver.test.ts +66 -0
- package/tests/service/requests/ErrorHandler.test.ts +141 -0
- package/tsconfig.json +114 -0
- package/vite.config.ts +34 -0
- package/vitest.config.ts +14 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { getCurrentInstance, shallowRef, computed, type Component, type Ref, type ComputedRef, h, render, onUnmounted } from 'vue'
|
|
2
|
+
|
|
3
|
+
interface UseGlobalCheckboxOptions<T> {
|
|
4
|
+
getAll: () => Promise<T[]>
|
|
5
|
+
getPage: () => T[]
|
|
6
|
+
totalCount: () => number
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default function <T>(
|
|
10
|
+
dialog: Component,
|
|
11
|
+
options: UseGlobalCheckboxOptions<T>,
|
|
12
|
+
querySelector: string = 'body'
|
|
13
|
+
): {
|
|
14
|
+
selectedRows: Ref<T[]>
|
|
15
|
+
indeterminate: ComputedRef<boolean>
|
|
16
|
+
checked: ComputedRef<boolean>
|
|
17
|
+
handleGlobalCheckboxChange: (event: Event) => void
|
|
18
|
+
} {
|
|
19
|
+
const self = getCurrentInstance()
|
|
20
|
+
const selectedRows = shallowRef<T[]>([])
|
|
21
|
+
|
|
22
|
+
function mountDialog() {
|
|
23
|
+
if (!self?.appContext) {
|
|
24
|
+
throw new Error('useGlobalCheckbox must be called inside a setup function')
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const vNode = h(dialog, {
|
|
28
|
+
indeterminate: indeterminate.value,
|
|
29
|
+
checked: checked.value
|
|
30
|
+
})
|
|
31
|
+
vNode.key = Symbol()
|
|
32
|
+
vNode.appContext = self.appContext
|
|
33
|
+
render(vNode, document.querySelector(querySelector) as Element)
|
|
34
|
+
return vNode.component
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function unmountConfirmDialog() {
|
|
38
|
+
render(null, document.querySelector(querySelector) as Element)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
onUnmounted(() => {
|
|
42
|
+
unmountConfirmDialog()
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
const indeterminate = computed(() => {
|
|
46
|
+
const totalCount: number = options.totalCount()
|
|
47
|
+
return selectedRows.value.length > 0 && selectedRows.value.length < totalCount
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
const checked = computed({
|
|
51
|
+
get: () => {
|
|
52
|
+
const totalCount: number = options.totalCount()
|
|
53
|
+
|
|
54
|
+
return selectedRows.value.length > 0 && selectedRows.value.length === totalCount
|
|
55
|
+
},
|
|
56
|
+
set: (value: boolean) => {
|
|
57
|
+
if (!value) {
|
|
58
|
+
selectedRows.value = []
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
// Check if all elements are already displayed on the current page
|
|
64
|
+
const isAllElementsOnCurrentPage = (): boolean => {
|
|
65
|
+
return options.getPage().length === options.totalCount()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function handleGlobalCheckboxChange(event: Event) {
|
|
69
|
+
if (!(event.target instanceof HTMLInputElement)) {
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const pageData: T[] = options.getPage()
|
|
74
|
+
|
|
75
|
+
// If all elements are on the current page, we can skip the dialog
|
|
76
|
+
// as both options would result in the same selection
|
|
77
|
+
const allElementsOnPage = isAllElementsOnCurrentPage()
|
|
78
|
+
|
|
79
|
+
// Case 1: Nothing is selected (unchecked state)
|
|
80
|
+
if (!checked.value && !indeterminate.value) {
|
|
81
|
+
if (allElementsOnPage) {
|
|
82
|
+
// All elements are already on the current page, just select them
|
|
83
|
+
selectedRows.value = [...pageData]
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// We need preventDefault() here because we're showing a dialog
|
|
88
|
+
// and don't want the checkbox to be checked until the user makes a choice
|
|
89
|
+
event.preventDefault()
|
|
90
|
+
|
|
91
|
+
const mountedDialog = mountDialog()
|
|
92
|
+
if (!mountedDialog?.exposed) return
|
|
93
|
+
|
|
94
|
+
// When clicking the empty checkbox, ask if user wants current page or all entries
|
|
95
|
+
if (await mountedDialog.exposed['open']) {
|
|
96
|
+
// User clicked "All Elements" button
|
|
97
|
+
selectedRows.value = await options.getAll()
|
|
98
|
+
} else {
|
|
99
|
+
// User clicked "Current Page" button
|
|
100
|
+
selectedRows.value = [...pageData]
|
|
101
|
+
}
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Case 2: Indeterminate state (some items selected, not all)
|
|
106
|
+
if (indeterminate.value) {
|
|
107
|
+
if (allElementsOnPage) {
|
|
108
|
+
// If all elements are on the page, just select them all
|
|
109
|
+
selectedRows.value = [...pageData]
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// We need preventDefault() here because we're showing a dialog
|
|
114
|
+
// and don't want the checkbox to be checked until the user makes a choice
|
|
115
|
+
event.preventDefault()
|
|
116
|
+
|
|
117
|
+
const mountedDialog = mountDialog()
|
|
118
|
+
if (!mountedDialog?.exposed) return
|
|
119
|
+
|
|
120
|
+
// When clicking the indeterminate checkbox, ask if the user wants all entries or unselect all
|
|
121
|
+
if (await mountedDialog.exposed['open']) {
|
|
122
|
+
// User clicked "All Elements" button
|
|
123
|
+
selectedRows.value = await options.getAll()
|
|
124
|
+
} else {
|
|
125
|
+
// User clicked "Discard" button
|
|
126
|
+
selectedRows.value = []
|
|
127
|
+
}
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Case 3: Checked state (all items selected)
|
|
132
|
+
if (checked.value) {
|
|
133
|
+
// When all items are selected, we just clear the selection without a dialog
|
|
134
|
+
selectedRows.value = []
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
selectedRows,
|
|
141
|
+
indeterminate,
|
|
142
|
+
checked,
|
|
143
|
+
handleGlobalCheckboxChange
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { isEmpty as _isEmpty } from 'lodash-es'
|
|
2
|
+
|
|
3
|
+
export default function () {
|
|
4
|
+
function isObject(value: unknown) {
|
|
5
|
+
return typeof value === 'object' && !Array.isArray(value) && value !== null
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function isEmpty(value: unknown) {
|
|
9
|
+
// Check objects with lodash
|
|
10
|
+
if (isObject(value)) {
|
|
11
|
+
return _isEmpty(value)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Check objects against their length property
|
|
15
|
+
if (Array.isArray(value)) {
|
|
16
|
+
return value.length === 0
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (value === '' || value === null) {
|
|
20
|
+
return true
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return value === undefined
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isNotEmpty(value: unknown) {
|
|
27
|
+
return !isEmpty(value)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
isEmpty,
|
|
32
|
+
isNotEmpty
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ref, computed, type Ref, type WritableComputedRef } from 'vue'
|
|
2
|
+
|
|
3
|
+
export default function (
|
|
4
|
+
callback: (value: boolean) => void = () => {},
|
|
5
|
+
delay: number = 500
|
|
6
|
+
): {
|
|
7
|
+
isOpenKey: Ref<number>
|
|
8
|
+
isOpen: WritableComputedRef<boolean>
|
|
9
|
+
} {
|
|
10
|
+
const internalIsOpen = ref<boolean>(false)
|
|
11
|
+
|
|
12
|
+
const isOpenKey = ref<number>(0)
|
|
13
|
+
|
|
14
|
+
const isOpen = computed({
|
|
15
|
+
get(): boolean {
|
|
16
|
+
return internalIsOpen.value
|
|
17
|
+
},
|
|
18
|
+
set(value: boolean): void {
|
|
19
|
+
// False means we close, so we increment the key
|
|
20
|
+
// Add delay to preserve the closing animation.
|
|
21
|
+
setTimeout(() => {
|
|
22
|
+
if (!value) {
|
|
23
|
+
isOpenKey.value++
|
|
24
|
+
}
|
|
25
|
+
}, delay)
|
|
26
|
+
|
|
27
|
+
internalIsOpen.value = value
|
|
28
|
+
|
|
29
|
+
callback(value)
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
isOpenKey,
|
|
35
|
+
isOpen
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { ref, computed, type Ref, type ComputedRef } from 'vue'
|
|
2
|
+
import useIsEmpty from './useIsEmpty'
|
|
3
|
+
|
|
4
|
+
export default function <FromVarType>(
|
|
5
|
+
defaultValue: FromVarType | undefined = undefined,
|
|
6
|
+
delay: number = 500
|
|
7
|
+
): {
|
|
8
|
+
fromVar: ComputedRef<FromVarType | undefined>
|
|
9
|
+
isOpenFromVar: ComputedRef<boolean>
|
|
10
|
+
isOpenFromVarKey: Ref<number>
|
|
11
|
+
} {
|
|
12
|
+
const isOpenFromVarKey = ref<number>(0)
|
|
13
|
+
|
|
14
|
+
const { isNotEmpty } = useIsEmpty()
|
|
15
|
+
|
|
16
|
+
const internalIsOpen = ref<boolean>(false)
|
|
17
|
+
|
|
18
|
+
const internalFromVar = ref<FromVarType | undefined>(defaultValue)
|
|
19
|
+
|
|
20
|
+
const isOpenFromVar = computed({
|
|
21
|
+
get(): boolean {
|
|
22
|
+
return internalIsOpen.value
|
|
23
|
+
},
|
|
24
|
+
set(value): void {
|
|
25
|
+
if (value) {
|
|
26
|
+
internalIsOpen.value = true
|
|
27
|
+
|
|
28
|
+
internalFromVar.value = value
|
|
29
|
+
} else {
|
|
30
|
+
internalIsOpen.value = false
|
|
31
|
+
|
|
32
|
+
setTimeout((): void => {
|
|
33
|
+
internalFromVar.value = defaultValue
|
|
34
|
+
|
|
35
|
+
isOpenFromVarKey.value++
|
|
36
|
+
}, delay)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const fromVar = computed({
|
|
42
|
+
get(): FromVarType | undefined {
|
|
43
|
+
return internalFromVar.value
|
|
44
|
+
},
|
|
45
|
+
set(value): void {
|
|
46
|
+
if (isNotEmpty(value)) {
|
|
47
|
+
isOpenFromVar.value = true
|
|
48
|
+
|
|
49
|
+
internalFromVar.value = value
|
|
50
|
+
} else {
|
|
51
|
+
isOpenFromVar.value = false
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
fromVar,
|
|
58
|
+
isOpenFromVar,
|
|
59
|
+
isOpenFromVarKey
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { computed } from 'vue'
|
|
2
|
+
import { type ModelValueProps } from '../contracts/ModelValueProps'
|
|
3
|
+
import { type ModelValueOptions as ParentModelValueOptions } from '../contracts/ModelValueOptions'
|
|
4
|
+
|
|
5
|
+
export interface ModelValueOptions<T> extends ParentModelValueOptions {
|
|
6
|
+
callback?: (value: T) => void
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default function <T, EmitType>(props: ModelValueProps, emit: EmitType, options: ModelValueOptions<T> = {}) {
|
|
10
|
+
const { name = 'modelValue', callback = () => {} } = options
|
|
11
|
+
|
|
12
|
+
return computed({
|
|
13
|
+
get(): T {
|
|
14
|
+
/* @ts-expect-error Ignore type */
|
|
15
|
+
return props[name]
|
|
16
|
+
},
|
|
17
|
+
set(value: T): void {
|
|
18
|
+
/* @ts-expect-error Ignore expression is not callable */
|
|
19
|
+
emit(`update:${name}`, value)
|
|
20
|
+
|
|
21
|
+
callback(value)
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { nextTick, type Ref, watch } from 'vue'
|
|
2
|
+
|
|
3
|
+
type Callback = () => void
|
|
4
|
+
|
|
5
|
+
export default function (ref: Ref<unknown>) {
|
|
6
|
+
const openCallbacks: Callback[] = []
|
|
7
|
+
const closeCallbacks: Callback[] = []
|
|
8
|
+
|
|
9
|
+
watch(
|
|
10
|
+
() => ref.value,
|
|
11
|
+
(newValue, oldValue) => {
|
|
12
|
+
nextTick(() => {
|
|
13
|
+
if (newValue && !oldValue) {
|
|
14
|
+
openCallbacks.forEach((callback) => callback())
|
|
15
|
+
} else if (!newValue && oldValue) {
|
|
16
|
+
closeCallbacks.forEach((callback) => callback())
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
function onOpen(callback: Callback): void {
|
|
23
|
+
openCallbacks.push(callback)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function onClose(callback: Callback): void {
|
|
27
|
+
closeCallbacks.push(callback)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
onOpen,
|
|
32
|
+
onClose
|
|
33
|
+
}
|
|
34
|
+
}
|