@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,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repräsentiert eine reaktive Property mit model.value Zugriff
|
|
3
|
+
*/
|
|
4
|
+
export interface PropertyAwareField<T> {
|
|
5
|
+
model: {
|
|
6
|
+
value: T
|
|
7
|
+
}
|
|
8
|
+
errors: any[]
|
|
9
|
+
suggestions: any[]
|
|
10
|
+
dirty: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Wandelt ein reguläres Interface in ein property-aware Interface um
|
|
15
|
+
* Jedes Feld vom Typ T wird zu einem PropertyAwareField<T>
|
|
16
|
+
*/
|
|
17
|
+
export type PropertyAware<T> = {
|
|
18
|
+
[K in keyof T]: T[K] extends Array<infer U> ? Array<PropertyAware<U>> : PropertyAwareField<T[K]>
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Extends Array with property awareness.
|
|
22
|
+
* When a form field is defined as an instance of PropertyAwareArray,
|
|
23
|
+
* the BaseForm will transform each element into reactive properties with
|
|
24
|
+
* computed getters/setters, error/suggestion tracking, and dirty flags.
|
|
25
|
+
*/
|
|
26
|
+
export class PropertyAwareArray<T = any> extends Array<T> {
|
|
27
|
+
/**
|
|
28
|
+
* Creates a new PropertyAwareArray instance
|
|
29
|
+
*/
|
|
30
|
+
public constructor(items: T[] = []) {
|
|
31
|
+
// Call Array constructor with array length
|
|
32
|
+
super()
|
|
33
|
+
|
|
34
|
+
// Add items to the array
|
|
35
|
+
if (items && items.length) {
|
|
36
|
+
items.forEach((item) => this.push(item))
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Create a PropertyAwareArray from an existing array
|
|
42
|
+
*/
|
|
43
|
+
public static override from<T>(arrayLike: ArrayLike<T>): PropertyAwareArray<T> {
|
|
44
|
+
return new PropertyAwareArray(Array.from(arrayLike))
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Maps each element using the provided function
|
|
49
|
+
* Overridden to ensure the return type is PropertyAwareArray<U> rather than Array<U>
|
|
50
|
+
*/
|
|
51
|
+
public override map<U>(callbackfn: (value: T, index: number, array: T[]) => U): PropertyAwareArray<U> {
|
|
52
|
+
return new PropertyAwareArray(super.map(callbackfn))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Filters elements using the provided function
|
|
57
|
+
* Overridden to ensure the return type is PropertyAwareArray<T> rather than Array<T>
|
|
58
|
+
*/
|
|
59
|
+
public override filter(predicate: (value: T, index: number, array: T[]) => boolean): PropertyAwareArray<T> {
|
|
60
|
+
return new PropertyAwareArray(super.filter(predicate))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Returns a shallow copy of the array
|
|
65
|
+
* Overridden to ensure the return type is PropertyAwareArray<T> rather than Array<T>
|
|
66
|
+
*/
|
|
67
|
+
public override slice(start?: number, end?: number): PropertyAwareArray<T> {
|
|
68
|
+
return new PropertyAwareArray(super.slice(start, end))
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Combines two or more arrays
|
|
73
|
+
* Overridden to ensure the return type is PropertyAwareArray<T> rather than Array<T>
|
|
74
|
+
*/
|
|
75
|
+
public override concat(...items: (T | ConcatArray<T>)[]): PropertyAwareArray<T> {
|
|
76
|
+
return new PropertyAwareArray(super.concat(...items))
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BaseForm, propertyAwareToRaw } from './BaseForm'
|
|
2
|
+
import { type PersistedForm } from './types/PersistedForm'
|
|
3
|
+
import { LocalStorageDriver } from '../../service/persistenceDrivers/LocalStorageDriver'
|
|
4
|
+
import { NonPersistentDriver } from '../../service/persistenceDrivers/NonPersistentDriver'
|
|
5
|
+
import { SessionStorageDriver } from '../../service/persistenceDrivers/SessionStorageDriver'
|
|
6
|
+
import { type PersistenceDriver } from '../../service/persistenceDrivers/types/PersistenceDriver'
|
|
7
|
+
import { PropertyAwareArray, type PropertyAwareField, type PropertyAware } from './PropertyAwareArray'
|
|
8
|
+
|
|
9
|
+
export { BaseForm, propertyAwareToRaw, PropertyAwareArray, NonPersistentDriver, SessionStorageDriver, LocalStorageDriver }
|
|
10
|
+
|
|
11
|
+
export type { PersistedForm, PersistenceDriver, PropertyAwareField, PropertyAware }
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export enum ValidationMode {
|
|
2
|
+
// Individual flags
|
|
3
|
+
NEVER = 0,
|
|
4
|
+
INSTANTLY = 1, // Validate as soon as form is initialized
|
|
5
|
+
ON_TOUCH = 2, // Validate when field is touched (focused/blurred)
|
|
6
|
+
ON_DIRTY = 4, // Validate when field value changes
|
|
7
|
+
ON_SUBMIT = 8, // Validate on form submit
|
|
8
|
+
ON_DEPENDENT_CHANGE = 16, // Validate when a field it depends on changes
|
|
9
|
+
|
|
10
|
+
// Common combinations
|
|
11
|
+
DEFAULT = ON_TOUCH | ON_DIRTY | ON_SUBMIT, // Validate when dirty or on submit
|
|
12
|
+
AGGRESSIVE = INSTANTLY | ON_TOUCH | ON_DIRTY | ON_SUBMIT, // Validate in all situations
|
|
13
|
+
PASSIVE = ON_SUBMIT // Only validate on submit
|
|
14
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseRule } from './rules/BaseRule'
|
|
2
|
+
import { ConfirmedRule } from './rules/ConfirmedRule'
|
|
3
|
+
import { RequiredRule } from './rules/RequiredRule'
|
|
4
|
+
import { UrlRule } from './rules/UrlRule'
|
|
5
|
+
import { MinRule } from './rules/MinRule'
|
|
6
|
+
import { ValidationMode } from './ValidationMode.enum'
|
|
7
|
+
|
|
8
|
+
import { type BidirectionalRule } from './types/BidirectionalRule'
|
|
9
|
+
|
|
10
|
+
export { BaseRule, ConfirmedRule, RequiredRule, UrlRule, MinRule, ValidationMode }
|
|
11
|
+
|
|
12
|
+
export type { BidirectionalRule }
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { BaseRule } from './BaseRule'
|
|
2
|
+
import { BidirectionalRule } from '../types/BidirectionalRule'
|
|
3
|
+
|
|
4
|
+
export class ConfirmedRule<FormBody extends object> extends BaseRule<FormBody> implements BidirectionalRule {
|
|
5
|
+
protected message: string
|
|
6
|
+
|
|
7
|
+
public constructor(
|
|
8
|
+
protected confirmationField: keyof FormBody,
|
|
9
|
+
message?: string
|
|
10
|
+
) {
|
|
11
|
+
super()
|
|
12
|
+
|
|
13
|
+
this.message = message || `Must match ${String(confirmationField)}`
|
|
14
|
+
|
|
15
|
+
// Add the confirmation field as a dependency
|
|
16
|
+
this.dependsOn = [confirmationField]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public validate(value: unknown, formState: FormBody): boolean {
|
|
20
|
+
// Only validate if both fields have values
|
|
21
|
+
const confirmationValue = formState[this.confirmationField]
|
|
22
|
+
|
|
23
|
+
// If either field is empty, don't show an error yet
|
|
24
|
+
if (!value || !confirmationValue) {
|
|
25
|
+
return true
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return value === confirmationValue
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public getMessage(): string {
|
|
32
|
+
return this.message
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Implement BidirectionalRule interface
|
|
36
|
+
public getBidirectionalFields(): string[] {
|
|
37
|
+
return [this.confirmationField as string]
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { BaseRule } from './BaseRule'
|
|
2
|
+
|
|
3
|
+
export class MinRule<FormBody extends object> extends BaseRule<FormBody> {
|
|
4
|
+
/**
|
|
5
|
+
* Creates a new MinRule
|
|
6
|
+
* @param min The minimum value (length for strings, value for numbers, length for arrays)
|
|
7
|
+
* @param message The error message to display if validation fails
|
|
8
|
+
*/
|
|
9
|
+
public constructor(
|
|
10
|
+
protected min: number,
|
|
11
|
+
protected message?: string
|
|
12
|
+
) {
|
|
13
|
+
super()
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Validates that the value meets the minimum requirement
|
|
18
|
+
* - For strings: checks if length is at least the specified minimum
|
|
19
|
+
* - For numbers: checks if value is at least the specified minimum
|
|
20
|
+
* - For arrays: checks if length is at least the specified minimum
|
|
21
|
+
* - For other types: always returns false
|
|
22
|
+
*
|
|
23
|
+
* @param value The value to validate
|
|
24
|
+
* @returns boolean indicating if validation passed
|
|
25
|
+
*/
|
|
26
|
+
public validate(value: unknown): boolean {
|
|
27
|
+
// Null or undefined values should be handled by RequiredRule, not this rule
|
|
28
|
+
if (value === null || value === undefined) {
|
|
29
|
+
return true
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// For strings, check the length
|
|
33
|
+
if (typeof value === 'string') {
|
|
34
|
+
return value.length >= this.min
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// For numbers, check the value
|
|
38
|
+
if (typeof value === 'number') {
|
|
39
|
+
return value >= this.min
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// For arrays, check the length
|
|
43
|
+
if (Array.isArray(value)) {
|
|
44
|
+
return value.length >= this.min
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// For other types, validation fails
|
|
48
|
+
return false
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Returns the error message for the validation
|
|
53
|
+
*/
|
|
54
|
+
public getMessage(): string {
|
|
55
|
+
if (this.message) {
|
|
56
|
+
return this.message
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return `This field must be at least ${this.min}`
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { BaseRule } from './BaseRule'
|
|
2
|
+
|
|
3
|
+
export class RequiredRule<FormBody extends object> extends BaseRule<FormBody> {
|
|
4
|
+
public constructor(protected message: string = 'This field is required') {
|
|
5
|
+
super()
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
public validate(value: keyof FormBody): boolean {
|
|
9
|
+
if (value === null || value === undefined || value === '') {
|
|
10
|
+
return false
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return true
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public getMessage(): string {
|
|
17
|
+
return this.message
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { BaseRule } from './BaseRule'
|
|
2
|
+
|
|
3
|
+
export class UrlRule<FormBody extends object> extends BaseRule<FormBody> {
|
|
4
|
+
public constructor(protected message: string = 'Please enter a valid URL') {
|
|
5
|
+
super()
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
public validate(value: unknown): boolean {
|
|
9
|
+
if (!value || typeof value !== 'string') {
|
|
10
|
+
return false
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
new URL(value)
|
|
15
|
+
return true
|
|
16
|
+
} catch {
|
|
17
|
+
return false
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public getMessage(): string {
|
|
22
|
+
return this.message
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interface for rules that require bidirectional validation
|
|
3
|
+
* Rules implementing this interface will have their dependencies
|
|
4
|
+
* automatically set up bidirectionally
|
|
5
|
+
*/
|
|
6
|
+
export interface BidirectionalRule {
|
|
7
|
+
/**
|
|
8
|
+
* Returns the fields that should have bidirectional validation with the current field
|
|
9
|
+
*/
|
|
10
|
+
getBidirectionalFields(): string[]
|
|
11
|
+
}
|
package/src/vue/index.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import useIsOpen from './composables/useIsOpen'
|
|
2
|
+
import useModelWrapper from './composables/useModelWrapper'
|
|
3
|
+
import useOnOpen from './composables/useOnOpen'
|
|
4
|
+
import useIsOpenFromVar from './composables/useIsOpenFromVar'
|
|
5
|
+
import useGlobalCheckbox from './composables/useGlobalCheckbox'
|
|
6
|
+
import useIsEmpty from './composables/useIsEmpty'
|
|
7
|
+
import useConfirmDialog from './composables/useConfirmDialog'
|
|
8
|
+
|
|
9
|
+
import { type ConfirmDialogSeverity } from './composables/useConfirmDialog'
|
|
10
|
+
import { type ConfirmDialogOptions } from './composables/useConfirmDialog'
|
|
11
|
+
|
|
12
|
+
export { useIsOpen, useModelWrapper, useOnOpen, useIsOpenFromVar, useGlobalCheckbox, useIsEmpty, useConfirmDialog }
|
|
13
|
+
|
|
14
|
+
export type { ConfirmDialogSeverity, ConfirmDialogOptions }
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { VueRequestLoader } from '../loaders/VueRequestLoader'
|
|
2
|
+
import { type RequestLoaderContract } from '../../../service/requests/contracts/RequestLoaderContract'
|
|
3
|
+
import { type Ref } from 'vue'
|
|
4
|
+
|
|
5
|
+
export class VueRequestLoaderFactory {
|
|
6
|
+
public make(): RequestLoaderContract<Ref<boolean>> {
|
|
7
|
+
return new VueRequestLoader()
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { VueRequestLoader } from './loaders/VueRequestLoader'
|
|
2
|
+
import { VueRequestBatchLoader } from './loaders/VueRequestBatchLoader'
|
|
3
|
+
import { VueRequestLoaderFactory } from './factories/VueRequestLoaderFactory'
|
|
4
|
+
|
|
5
|
+
export { VueRequestLoader, VueRequestBatchLoader, VueRequestLoaderFactory }
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type Ref, ref, computed } from 'vue'
|
|
2
|
+
import { type RequestLoaderContract } from '../../../service/requests/contracts/RequestLoaderContract'
|
|
3
|
+
|
|
4
|
+
export class VueRequestBatchLoader implements RequestLoaderContract<Ref<boolean>> {
|
|
5
|
+
private inFlight: Ref<number> = ref(0)
|
|
6
|
+
private completed: Ref<number> = ref(0)
|
|
7
|
+
private expected: number = 0
|
|
8
|
+
|
|
9
|
+
constructor(expectedRequests: number = 0) {
|
|
10
|
+
this.expected = expectedRequests
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
public startBatch(count: number) {
|
|
14
|
+
this.expected = count
|
|
15
|
+
this.completed.value = 0
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public isLoading(): Ref<boolean> {
|
|
19
|
+
return computed(() => this.inFlight.value > 0 || (this.expected > 0 && this.completed.value < this.expected))
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public setLoading(loading: boolean): void {
|
|
23
|
+
if (loading) {
|
|
24
|
+
this.inFlight.value++
|
|
25
|
+
} else {
|
|
26
|
+
this.inFlight.value = Math.max(0, this.inFlight.value - 1)
|
|
27
|
+
this.completed.value++
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type Ref, ref } from 'vue'
|
|
2
|
+
import { type RequestLoaderContract } from '../../../service/requests/contracts/RequestLoaderContract'
|
|
3
|
+
|
|
4
|
+
export class VueRequestLoader implements RequestLoaderContract<Ref<boolean>> {
|
|
5
|
+
protected loading: Ref<boolean>
|
|
6
|
+
|
|
7
|
+
public constructor(initialLoading: boolean = false) {
|
|
8
|
+
this.loading = ref<boolean>(initialLoading)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
isLoading(): Ref<boolean> {
|
|
12
|
+
return this.loading
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
setLoading(value: boolean): void {
|
|
16
|
+
this.loading.value = value
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type DataRequest, type Resolver } from './types'
|
|
2
|
+
|
|
3
|
+
export class RouteModelRequestResolver<T> implements Resolver<T> {
|
|
4
|
+
public constructor(private request: DataRequest<T>) {}
|
|
5
|
+
|
|
6
|
+
public async resolve(): Promise<T> {
|
|
7
|
+
const response = await this.request.send()
|
|
8
|
+
|
|
9
|
+
return response.getData()
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type RouteRecordRaw } from 'vue-router'
|
|
2
|
+
import { type InjectConfig } from './types'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Type-safe route definition helper that ties:
|
|
6
|
+
* - component props type (Props)
|
|
7
|
+
* - inject config types (must resolve Props[K])
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* export default defineRoute<{ product: ProductResource }>()({ ... })
|
|
11
|
+
*/
|
|
12
|
+
export function defineRoute<Props extends Record<string, unknown>>() {
|
|
13
|
+
return function <
|
|
14
|
+
T extends RouteRecordRaw & {
|
|
15
|
+
inject?: InjectConfig<Props>
|
|
16
|
+
}
|
|
17
|
+
>(route: T): RouteRecordRaw {
|
|
18
|
+
const originalProps = route.props
|
|
19
|
+
|
|
20
|
+
route.props = (to) => {
|
|
21
|
+
const baseProps = typeof originalProps === 'function' ? originalProps(to) : originalProps === true ? to.params : (originalProps ?? {})
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
...(baseProps as Record<string, unknown>),
|
|
25
|
+
...((to.meta._injectedProps as Record<string, unknown>) ?? {})
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return route
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { defineRoute } from './defineRoute'
|
|
2
|
+
import { installRouteInjection } from './installRouteInjection'
|
|
3
|
+
import { RouteModelRequestResolver } from './RouteModelRequestResolver'
|
|
4
|
+
import type { DataRequest, InjectConfig, Resolver } from './types'
|
|
5
|
+
|
|
6
|
+
export { defineRoute, installRouteInjection, RouteModelRequestResolver }
|
|
7
|
+
|
|
8
|
+
export type { DataRequest, InjectConfig, Resolver }
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { type Router } from 'vue-router'
|
|
2
|
+
|
|
3
|
+
type InjectRuntimeConfig = {
|
|
4
|
+
from: string
|
|
5
|
+
resolve: (param: string) => { resolve(): Promise<unknown> }
|
|
6
|
+
getter: (payload: unknown) => unknown
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Installs the runtime part:
|
|
11
|
+
* - resolves all route.inject entries before navigation completes
|
|
12
|
+
* - stores results on to.meta._injectedProps
|
|
13
|
+
* - ensures route props include injected results (so components receive them as props)
|
|
14
|
+
*
|
|
15
|
+
* Notes:
|
|
16
|
+
* - This keeps router files clean: only an `inject` block per route.
|
|
17
|
+
* - This is intentionally runtime-generic; type safety happens at route definition time.
|
|
18
|
+
*/
|
|
19
|
+
export function installRouteInjection(router: Router) {
|
|
20
|
+
router.beforeResolve(async (to) => {
|
|
21
|
+
console.log('[Route Injection] Resolving route injections...')
|
|
22
|
+
|
|
23
|
+
const resolvedProps: Record<string, unknown> = {}
|
|
24
|
+
|
|
25
|
+
// Iterate through all matched route records (from parent to child)
|
|
26
|
+
for (const record of to.matched) {
|
|
27
|
+
console.debug(`[Route Injection] Processing route "${record.path}"`)
|
|
28
|
+
|
|
29
|
+
// Access the custom 'inject' property directly from the record
|
|
30
|
+
const inject = record.meta.inject as Record<string, InjectRuntimeConfig> | undefined
|
|
31
|
+
|
|
32
|
+
if (!inject) {
|
|
33
|
+
console.debug(`[Route Injection] No injections found for route "${record.path}"`)
|
|
34
|
+
|
|
35
|
+
continue
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for (const [propName, cfg] of Object.entries(inject)) {
|
|
39
|
+
const rawParam = to.params[cfg.from]
|
|
40
|
+
|
|
41
|
+
if (rawParam === undefined || rawParam === null) {
|
|
42
|
+
console.warn(`[Route Injection] Param "${cfg.from}" not found for prop "${propName}"`)
|
|
43
|
+
|
|
44
|
+
continue
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const paramValue = Array.isArray(rawParam) ? rawParam[0] : String(rawParam)
|
|
48
|
+
|
|
49
|
+
if (paramValue === undefined || paramValue === null) {
|
|
50
|
+
console.warn(`[Route Injection] Param value "${cfg.from}" not found for prop "${propName}"`)
|
|
51
|
+
|
|
52
|
+
continue
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const resolver = cfg.resolve(paramValue)
|
|
56
|
+
|
|
57
|
+
let payload = await resolver.resolve()
|
|
58
|
+
|
|
59
|
+
if (typeof cfg.getter === 'function') {
|
|
60
|
+
payload = cfg.getter(payload)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
resolvedProps[propName] = payload
|
|
64
|
+
|
|
65
|
+
console.debug(
|
|
66
|
+
`[Route Injection] Successfully resolved prop "${propName}" for route "${record.path}": ${JSON.stringify(resolvedProps[propName])}`
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
to.meta._injectedProps = resolvedProps
|
|
72
|
+
})
|
|
73
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type RouteLocationNormalized } from 'vue-router'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generic resolver interface.
|
|
5
|
+
*/
|
|
6
|
+
export interface Resolver<T> {
|
|
7
|
+
resolve(): Promise<T>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A minimal "request" contract that matches your request classes:
|
|
12
|
+
* new ProductShowRequest(id).send() -> { getData(): T }
|
|
13
|
+
*/
|
|
14
|
+
export interface DataRequest<T> {
|
|
15
|
+
send(): Promise<{ getData(): T }>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Inject config that is type-safe against component props.
|
|
20
|
+
*
|
|
21
|
+
* Props[K] is the required resolved type for prop K.
|
|
22
|
+
*/
|
|
23
|
+
export type InjectConfig<Props extends Record<string, unknown>> = {
|
|
24
|
+
[K in keyof Props]?: {
|
|
25
|
+
/**
|
|
26
|
+
* Route param name, e.g. "productId"
|
|
27
|
+
*/
|
|
28
|
+
from: keyof RouteLocationNormalized['params'] | string
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Create a resolver from the param value.
|
|
32
|
+
* Param value is treated as string (most common in routes).
|
|
33
|
+
*/
|
|
34
|
+
resolve: (param: string) => Resolver<Props[K]>
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Extended route meta to store injected props at runtime.
|
|
40
|
+
*/
|
|
41
|
+
declare module 'vue-router' {
|
|
42
|
+
interface RouteMeta {
|
|
43
|
+
_injectedProps?: Record<string, unknown>
|
|
44
|
+
inject?: Record<string, unknown>
|
|
45
|
+
}
|
|
46
|
+
}
|