@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.
Files changed (158) hide show
  1. package/.editorconfig +508 -0
  2. package/.eslintrc.cjs +15 -0
  3. package/.prettierrc.json +8 -0
  4. package/LICENSE +21 -0
  5. package/README.md +1 -0
  6. package/docker-compose.yaml +8 -0
  7. package/docs/.vitepress/config.ts +68 -0
  8. package/docs/.vitepress/theme/Layout.vue +14 -0
  9. package/docs/.vitepress/theme/components/VersionSelector.vue +64 -0
  10. package/docs/.vitepress/theme/index.js +13 -0
  11. package/docs/index.md +70 -0
  12. package/docs/services/laravel/pagination.md +54 -0
  13. package/docs/services/laravel/requests.md +62 -0
  14. package/docs/services/requests/index.md +74 -0
  15. package/docs/vue/forms.md +326 -0
  16. package/docs/vue/requests/route-model-binding.md +66 -0
  17. package/docs/vue/state.md +293 -0
  18. package/env.d.ts +1 -0
  19. package/eslint.config.js +15 -0
  20. package/examples/files/7z2404-x64.exe +0 -0
  21. package/examples/index.html +14 -0
  22. package/examples/js/app.js +8 -0
  23. package/examples/js/router.js +22 -0
  24. package/examples/js/view/App.vue +49 -0
  25. package/examples/js/view/layout/DemoPage.vue +28 -0
  26. package/examples/js/view/pagination/Pagination.vue +28 -0
  27. package/examples/js/view/pagination/components/errorPagination/ErrorPagination.vue +71 -0
  28. package/examples/js/view/pagination/components/errorPagination/GetProductsRequest.ts +54 -0
  29. package/examples/js/view/pagination/components/infiniteScrolling/GetProductsRequest.ts +50 -0
  30. package/examples/js/view/pagination/components/infiniteScrolling/InfiniteScrolling.vue +57 -0
  31. package/examples/js/view/pagination/components/tablePagination/GetProductsRequest.ts +50 -0
  32. package/examples/js/view/pagination/components/tablePagination/TablePagination.vue +63 -0
  33. package/examples/js/view/requests/Requests.vue +34 -0
  34. package/examples/js/view/requests/components/abortableRequest/AbortableRequest.vue +36 -0
  35. package/examples/js/view/requests/components/abortableRequest/GetProductsRequest.ts +25 -0
  36. package/examples/js/view/requests/components/fileDownloadRequest/DownloadFileRequest.ts +15 -0
  37. package/examples/js/view/requests/components/fileDownloadRequest/FileDownloadRequest.vue +44 -0
  38. package/examples/js/view/requests/components/getRequestWithDynamicParams/GetProductsRequest.ts +34 -0
  39. package/examples/js/view/requests/components/getRequestWithDynamicParams/GetRequestWithDynamicParams.vue +59 -0
  40. package/examples/js/view/requests/components/serverErrorRequest/ServerErrorRequest.ts +21 -0
  41. package/examples/js/view/requests/components/serverErrorRequest/ServerErrorRequest.vue +53 -0
  42. package/package.json +81 -0
  43. package/release-tool.json +7 -0
  44. package/src/helpers.ts +78 -0
  45. package/src/service/bulkRequests/BulkRequestEvent.enum.ts +4 -0
  46. package/src/service/bulkRequests/BulkRequestSender.ts +184 -0
  47. package/src/service/bulkRequests/BulkRequestWrapper.ts +49 -0
  48. package/src/service/bulkRequests/index.ts +6 -0
  49. package/src/service/laravel/pagination/contracts/PaginationParamsContract.ts +4 -0
  50. package/src/service/laravel/pagination/contracts/PaginationResponseBodyContract.ts +6 -0
  51. package/src/service/laravel/pagination/dataDrivers/RequestDriver.ts +32 -0
  52. package/src/service/laravel/pagination/index.ts +7 -0
  53. package/src/service/laravel/requests/JsonBaseRequest.ts +35 -0
  54. package/src/service/laravel/requests/PaginationJsonBaseRequest.ts +29 -0
  55. package/src/service/laravel/requests/index.ts +9 -0
  56. package/src/service/laravel/requests/responses/JsonResponse.ts +8 -0
  57. package/src/service/laravel/requests/responses/PaginationResponse.ts +16 -0
  58. package/src/service/pagination/InfiniteScroller.ts +21 -0
  59. package/src/service/pagination/Paginator.ts +149 -0
  60. package/src/service/pagination/contracts/PaginateableRequestContract.ts +13 -0
  61. package/src/service/pagination/contracts/PaginationDataDriverContract.ts +5 -0
  62. package/src/service/pagination/contracts/PaginationResponseContract.ts +7 -0
  63. package/src/service/pagination/contracts/PaginatorLoadDataOptions.ts +4 -0
  64. package/src/service/pagination/contracts/ViewDriverContract.ts +12 -0
  65. package/src/service/pagination/contracts/ViewDriverFactoryContract.ts +5 -0
  66. package/src/service/pagination/dataDrivers/ArrayDriver.ts +28 -0
  67. package/src/service/pagination/dtos/PaginationDataDto.ts +14 -0
  68. package/src/service/pagination/factories/VuePaginationDriverFactory.ts +9 -0
  69. package/src/service/pagination/frontendDrivers/VuePaginationDriver.ts +61 -0
  70. package/src/service/pagination/index.ts +16 -0
  71. package/src/service/persistenceDrivers/LocalStorageDriver.ts +22 -0
  72. package/src/service/persistenceDrivers/NonPersistentDriver.ts +12 -0
  73. package/src/service/persistenceDrivers/SessionStorageDriver.ts +22 -0
  74. package/src/service/persistenceDrivers/index.ts +8 -0
  75. package/src/service/persistenceDrivers/types/PersistenceDriver.ts +5 -0
  76. package/src/service/requests/BaseRequest.ts +197 -0
  77. package/src/service/requests/ErrorHandler.ts +64 -0
  78. package/src/service/requests/RequestEvents.enum.ts +3 -0
  79. package/src/service/requests/RequestMethod.enum.ts +8 -0
  80. package/src/service/requests/bodies/FormDataBody.ts +41 -0
  81. package/src/service/requests/bodies/JsonBody.ts +16 -0
  82. package/src/service/requests/contracts/AbortableRequestContract.ts +3 -0
  83. package/src/service/requests/contracts/BaseRequestContract.ts +36 -0
  84. package/src/service/requests/contracts/BodyContract.ts +7 -0
  85. package/src/service/requests/contracts/BodyFactoryContract.ts +5 -0
  86. package/src/service/requests/contracts/DriverConfigContract.ts +7 -0
  87. package/src/service/requests/contracts/HeadersContract.ts +5 -0
  88. package/src/service/requests/contracts/RequestDriverContract.ts +15 -0
  89. package/src/service/requests/contracts/RequestLoaderContract.ts +5 -0
  90. package/src/service/requests/contracts/RequestLoaderFactoryContract.ts +5 -0
  91. package/src/service/requests/contracts/ResponseContract.ts +7 -0
  92. package/src/service/requests/drivers/contracts/ResponseHandlerContract.ts +10 -0
  93. package/src/service/requests/drivers/fetch/FetchDriver.ts +115 -0
  94. package/src/service/requests/drivers/fetch/FetchResponse.ts +30 -0
  95. package/src/service/requests/exceptions/NoResponseReceivedException.ts +3 -0
  96. package/src/service/requests/exceptions/NotFoundException.ts +3 -0
  97. package/src/service/requests/exceptions/PageExpiredException.ts +3 -0
  98. package/src/service/requests/exceptions/ResponseBodyException.ts +15 -0
  99. package/src/service/requests/exceptions/ResponseException.ts +11 -0
  100. package/src/service/requests/exceptions/ServerErrorException.ts +3 -0
  101. package/src/service/requests/exceptions/UnauthorizedException.ts +3 -0
  102. package/src/service/requests/exceptions/ValidationException.ts +3 -0
  103. package/src/service/requests/exceptions/index.ts +19 -0
  104. package/src/service/requests/factories/FormDataFactory.ts +9 -0
  105. package/src/service/requests/factories/JsonBodyFactory.ts +9 -0
  106. package/src/service/requests/index.ts +50 -0
  107. package/src/service/requests/responses/BaseResponse.ts +41 -0
  108. package/src/service/requests/responses/BlobResponse.ts +19 -0
  109. package/src/service/requests/responses/JsonResponse.ts +15 -0
  110. package/src/service/requests/responses/PlainTextResponse.ts +15 -0
  111. package/src/service/support/DeferredPromise.ts +67 -0
  112. package/src/service/support/index.ts +3 -0
  113. package/src/vue/composables/useConfirmDialog.ts +59 -0
  114. package/src/vue/composables/useGlobalCheckbox.ts +145 -0
  115. package/src/vue/composables/useIsEmpty.ts +34 -0
  116. package/src/vue/composables/useIsOpen.ts +37 -0
  117. package/src/vue/composables/useIsOpenFromVar.ts +61 -0
  118. package/src/vue/composables/useModelWrapper.ts +24 -0
  119. package/src/vue/composables/useOnOpen.ts +34 -0
  120. package/src/vue/contracts/ModelValueOptions.ts +3 -0
  121. package/src/vue/contracts/ModelValueProps.ts +3 -0
  122. package/src/vue/forms/BaseForm.ts +1074 -0
  123. package/src/vue/forms/PropertyAwareArray.ts +78 -0
  124. package/src/vue/forms/index.ts +11 -0
  125. package/src/vue/forms/types/PersistedForm.ts +6 -0
  126. package/src/vue/forms/validation/ValidationMode.enum.ts +14 -0
  127. package/src/vue/forms/validation/index.ts +12 -0
  128. package/src/vue/forms/validation/rules/BaseRule.ts +7 -0
  129. package/src/vue/forms/validation/rules/ConfirmedRule.ts +39 -0
  130. package/src/vue/forms/validation/rules/MinRule.ts +61 -0
  131. package/src/vue/forms/validation/rules/RequiredRule.ts +19 -0
  132. package/src/vue/forms/validation/rules/UrlRule.ts +24 -0
  133. package/src/vue/forms/validation/types/BidirectionalRule.ts +11 -0
  134. package/src/vue/index.ts +14 -0
  135. package/src/vue/requests/factories/VueRequestLoaderFactory.ts +9 -0
  136. package/src/vue/requests/index.ts +5 -0
  137. package/src/vue/requests/loaders/VueRequestBatchLoader.ts +30 -0
  138. package/src/vue/requests/loaders/VueRequestLoader.ts +18 -0
  139. package/src/vue/router/routeModelBinding/RouteModelRequestResolver.ts +11 -0
  140. package/src/vue/router/routeModelBinding/defineRoute.ts +31 -0
  141. package/src/vue/router/routeModelBinding/index.ts +8 -0
  142. package/src/vue/router/routeModelBinding/installRouteInjection.ts +73 -0
  143. package/src/vue/router/routeModelBinding/types.ts +46 -0
  144. package/src/vue/state/State.ts +391 -0
  145. package/src/vue/state/index.ts +3 -0
  146. package/tests/service/helpers/mergeDeep.test.ts +53 -0
  147. package/tests/service/laravel/pagination/dataDrivers/RequestDriver.test.ts +84 -0
  148. package/tests/service/laravel/requests/JsonBaseRequest.test.ts +43 -0
  149. package/tests/service/laravel/requests/PaginationJsonBaseRequest.test.ts +58 -0
  150. package/tests/service/laravel/requests/responses/JsonResponse.test.ts +59 -0
  151. package/tests/service/laravel/requests/responses/PaginationResponse.test.ts +127 -0
  152. package/tests/service/pagination/dtos/PaginationDataDto.test.ts +35 -0
  153. package/tests/service/pagination/factories/VuePaginationDriverFactory.test.ts +32 -0
  154. package/tests/service/pagination/frontendDrivers/VuePaginationDriver.test.ts +66 -0
  155. package/tests/service/requests/ErrorHandler.test.ts +141 -0
  156. package/tsconfig.json +114 -0
  157. package/vite.config.ts +34 -0
  158. 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
+ }
@@ -0,0 +1,3 @@
1
+ export interface ModelValueOptions {
2
+ name?: string
3
+ }
@@ -0,0 +1,3 @@
1
+ export interface ModelValueProps {
2
+ [key: string]: string | number | Array<unknown> | boolean | object
3
+ }