@quazardous/qdadm 1.21.0 → 2.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/README.md +1 -1
- package/package.json +1 -1
- package/src/components/editors/VanillaJsonEditor.vue +42 -1
- package/src/components/editors/jsonValidator.ts +44 -0
- package/src/components/index.ts +0 -3
- package/src/components/layout/AppLayout.vue +2 -2
- package/src/components/layout/BaseLayout.vue +1 -1
- package/src/components/layout/PageHeader.vue +1 -1
- package/src/components/show/ShowDisplay.vue +6 -10
- package/src/composables/index.ts +0 -7
- package/src/composables/useListPage.ts +3 -8
- package/src/composables/useNavContext.ts +1 -1
- package/src/composables/usePageTitle.ts +2 -2
- package/src/entity/EntityManager.crud.ts +6 -1
- package/src/entity/storage/ApiStorage.ts +0 -5
- package/src/entity/storage/LocalStorage.ts +0 -5
- package/src/entity/storage/MemoryStorage.ts +0 -5
- package/src/entity/storage/MockApiStorage.ts +0 -5
- package/src/entity/storage/SdkStorage.ts +0 -5
- package/src/index.ts +0 -7
- package/src/utils/formatters.ts +38 -7
- package/src/utils/index.ts +1 -0
- package/src/components/edit/FormTab.vue +0 -217
- package/src/components/edit/FormTabs.vue +0 -120
- package/src/components/lists/ActionColumn.vue +0 -40
- package/src/composables/useCurrentEntity.ts +0 -60
- package/src/composables/useForm.ts +0 -544
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ import { SecurityChecker, PermissionMatcher, PermissionRegistry } from '@quazard
|
|
|
31
31
|
import { PersistableRoleProvider, createLocalStorageRolesProvider } from '@quazardous/qdadm/security'
|
|
32
32
|
|
|
33
33
|
// Composables
|
|
34
|
-
import {
|
|
34
|
+
import { useBareForm, useListPage } from '@quazardous/qdadm/composables'
|
|
35
35
|
|
|
36
36
|
// Components
|
|
37
37
|
import { ListPage, PageLayout, AppLayout, FormField, FormActions } from '@quazardous/qdadm/components'
|
package/package.json
CHANGED
|
@@ -6,10 +6,17 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Usage:
|
|
8
8
|
* <VanillaJsonEditor v-model="jsonData" mode="tree" />
|
|
9
|
+
*
|
|
10
|
+
* Inline JSON Schema validation (errors highlighted live in the editor):
|
|
11
|
+
* <VanillaJsonEditor v-model="jsonData" :schema="mySchema" />
|
|
12
|
+
*
|
|
13
|
+
* Or a raw validator for advanced cases (takes precedence over `schema`):
|
|
14
|
+
* <VanillaJsonEditor v-model="jsonData" :validator="myValidator" />
|
|
9
15
|
*/
|
|
10
16
|
|
|
11
17
|
import { ref, watch, onMounted, onUnmounted, nextTick } from 'vue'
|
|
12
18
|
import { JSONEditor, type Content, type ContentErrors, type OnChangeStatus, type Mode } from 'vanilla-jsoneditor'
|
|
19
|
+
import { buildJsonValidator, type Validator, type JSONSchema, type JsonValidatorOptions } from './jsonValidator'
|
|
13
20
|
|
|
14
21
|
type JsonValue = Record<string, unknown> | unknown[] | string | null
|
|
15
22
|
|
|
@@ -21,6 +28,14 @@ interface Props {
|
|
|
21
28
|
mainMenuBar?: boolean
|
|
22
29
|
navigationBar?: boolean
|
|
23
30
|
statusBar?: boolean
|
|
31
|
+
/** JSON Schema for inline Ajv validation (errors highlighted in the editor). */
|
|
32
|
+
schema?: JSONSchema
|
|
33
|
+
/** Raw validator function — takes precedence over `schema`. */
|
|
34
|
+
validator?: Validator
|
|
35
|
+
/** Optional `$ref` definitions passed through to the Ajv validator. */
|
|
36
|
+
schemaDefinitions?: JsonValidatorOptions['schemaDefinitions']
|
|
37
|
+
/** Optional Ajv options passed through to the Ajv validator. */
|
|
38
|
+
ajvOptions?: JsonValidatorOptions['ajvOptions']
|
|
24
39
|
}
|
|
25
40
|
|
|
26
41
|
const props = withDefaults(defineProps<Props>(), {
|
|
@@ -30,9 +45,23 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
30
45
|
readOnly: false,
|
|
31
46
|
mainMenuBar: true,
|
|
32
47
|
navigationBar: true,
|
|
33
|
-
statusBar: true
|
|
48
|
+
statusBar: true,
|
|
49
|
+
schema: undefined,
|
|
50
|
+
validator: undefined,
|
|
51
|
+
schemaDefinitions: undefined,
|
|
52
|
+
ajvOptions: undefined
|
|
34
53
|
})
|
|
35
54
|
|
|
55
|
+
// Resolve the effective validator from props (raw validator > schema > none).
|
|
56
|
+
function resolveValidator(): Validator | undefined {
|
|
57
|
+
return buildJsonValidator({
|
|
58
|
+
validator: props.validator,
|
|
59
|
+
schema: props.schema,
|
|
60
|
+
schemaDefinitions: props.schemaDefinitions,
|
|
61
|
+
ajvOptions: props.ajvOptions
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
36
65
|
const emit = defineEmits<{
|
|
37
66
|
'update:modelValue': [value: JsonValue]
|
|
38
67
|
'change': [value: JsonValue]
|
|
@@ -76,6 +105,7 @@ onMounted(() => {
|
|
|
76
105
|
mainMenuBar: props.mainMenuBar,
|
|
77
106
|
navigationBar: props.navigationBar,
|
|
78
107
|
statusBar: props.statusBar,
|
|
108
|
+
validator: resolveValidator(),
|
|
79
109
|
onChange: (updatedContent: Content, _previousContent: Content, { contentErrors }: OnChangeStatus) => {
|
|
80
110
|
// Skip if this change came from a programmatic prop update
|
|
81
111
|
if (updatingFromProp.value) return
|
|
@@ -144,6 +174,17 @@ watch(() => props.readOnly, (newReadOnly: boolean) => {
|
|
|
144
174
|
}
|
|
145
175
|
})
|
|
146
176
|
|
|
177
|
+
// Watch validator/schema changes — rebuild and re-apply so edits re-validate live
|
|
178
|
+
watch(
|
|
179
|
+
() => [props.validator, props.schema, props.schemaDefinitions, props.ajvOptions],
|
|
180
|
+
() => {
|
|
181
|
+
if (editor) {
|
|
182
|
+
editor.updateProps({ validator: resolveValidator() })
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
{ deep: true }
|
|
186
|
+
)
|
|
187
|
+
|
|
147
188
|
// Cleanup
|
|
148
189
|
onUnmounted(() => {
|
|
149
190
|
if (editor) {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* jsonValidator - build a vanilla-jsoneditor `Validator` from wrapper props.
|
|
3
|
+
*
|
|
4
|
+
* Pure helper extracted from VanillaJsonEditor so the resolution logic
|
|
5
|
+
* (raw validator vs JSON Schema vs none) is unit-testable without mounting the
|
|
6
|
+
* Svelte editor. See qdadm #1050.
|
|
7
|
+
*/
|
|
8
|
+
import {
|
|
9
|
+
createAjvValidator,
|
|
10
|
+
type Validator,
|
|
11
|
+
type JSONSchema,
|
|
12
|
+
type AjvValidatorOptions,
|
|
13
|
+
} from 'vanilla-jsoneditor'
|
|
14
|
+
|
|
15
|
+
export type { Validator, JSONSchema }
|
|
16
|
+
|
|
17
|
+
export interface JsonValidatorOptions {
|
|
18
|
+
/** Raw validator function — takes precedence over `schema` when both are set. */
|
|
19
|
+
validator?: Validator
|
|
20
|
+
/** JSON Schema; compiled to a validator via Ajv (bundled in vanilla-jsoneditor). */
|
|
21
|
+
schema?: JSONSchema
|
|
22
|
+
/** Optional `$ref` definitions passed through to `createAjvValidator`. */
|
|
23
|
+
schemaDefinitions?: AjvValidatorOptions['schemaDefinitions']
|
|
24
|
+
/** Optional Ajv options passed through to `createAjvValidator`. */
|
|
25
|
+
ajvOptions?: AjvValidatorOptions['ajvOptions']
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Resolve the effective validator:
|
|
30
|
+
* - an explicit `validator` wins;
|
|
31
|
+
* - otherwise a `schema` is compiled with `createAjvValidator`;
|
|
32
|
+
* - otherwise `undefined` (no validation — the wrapper's default behavior).
|
|
33
|
+
*/
|
|
34
|
+
export function buildJsonValidator(options: JsonValidatorOptions): Validator | undefined {
|
|
35
|
+
if (options.validator) return options.validator
|
|
36
|
+
if (options.schema) {
|
|
37
|
+
return createAjvValidator({
|
|
38
|
+
schema: options.schema,
|
|
39
|
+
schemaDefinitions: options.schemaDefinitions,
|
|
40
|
+
ajvOptions: options.ajvOptions,
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
return undefined
|
|
44
|
+
}
|
package/src/components/index.ts
CHANGED
|
@@ -27,8 +27,6 @@ export { default as FormPage } from './edit/FormPage.vue'
|
|
|
27
27
|
export { default as FormField } from './edit/FormField.vue'
|
|
28
28
|
export { default as FormInput } from './edit/FormInput.vue'
|
|
29
29
|
export { default as FormActions } from './edit/FormActions.vue'
|
|
30
|
-
export { default as FormTabs } from './edit/FormTabs.vue' // Deprecated: use FieldGroups with layout="tabs"
|
|
31
|
-
export { default as FormTab } from './edit/FormTab.vue' // Deprecated: use FieldGroups with layout="tabs"
|
|
32
30
|
export { default as LookupField } from './edit/LookupField.vue'
|
|
33
31
|
export { default as LookupPickerDialog } from './edit/LookupPickerDialog.vue'
|
|
34
32
|
|
|
@@ -41,7 +39,6 @@ export { default as ParentCard } from './show/ParentCard.vue'
|
|
|
41
39
|
// Lists
|
|
42
40
|
export { default as ListPage } from './lists/ListPage.vue'
|
|
43
41
|
export { default as ActionButtons } from './lists/ActionButtons.vue'
|
|
44
|
-
export { default as ActionColumn } from './lists/ActionColumn.vue'
|
|
45
42
|
export { default as FilterBar } from './lists/FilterBar.vue'
|
|
46
43
|
|
|
47
44
|
// Editors (vanilla-jsoneditor free)
|
|
@@ -50,7 +50,7 @@ interface UserData {
|
|
|
50
50
|
const features = inject<FeaturesConfig>('qdadmFeatures', { poweredBy: true, breadcrumb: true })
|
|
51
51
|
const homeRoute = inject<string>('qdadmHomeRoute', 'home')
|
|
52
52
|
|
|
53
|
-
// Guard dialog from shared store (registered by useBareForm/
|
|
53
|
+
// Guard dialog from shared store (registered by useBareForm/useEntityItemFormPage when a form is active)
|
|
54
54
|
const guardDialog = useGuardDialog()
|
|
55
55
|
|
|
56
56
|
const router = useRouter()
|
|
@@ -238,7 +238,7 @@ watch(() => route.fullPath, () => {
|
|
|
238
238
|
})
|
|
239
239
|
|
|
240
240
|
// Navigation context (breadcrumb + navlinks from route config)
|
|
241
|
-
// Entity data comes from activeStack (populated by useEntityItemPage/
|
|
241
|
+
// Entity data comes from activeStack (populated by useEntityItemPage/useEntityItemFormPage)
|
|
242
242
|
const { breadcrumb: defaultBreadcrumb, navlinks: defaultNavlinks } = useNavContext()
|
|
243
243
|
|
|
244
244
|
// Allow child pages to override breadcrumb/navlinks via provide/inject
|
|
@@ -50,7 +50,7 @@ import DefaultBreadcrumb from './defaults/DefaultBreadcrumb.vue'
|
|
|
50
50
|
const slots = useSlots()
|
|
51
51
|
const hasMainSlot: boolean = !!slots.main
|
|
52
52
|
|
|
53
|
-
// Guard dialog from shared store (registered by useBareForm/
|
|
53
|
+
// Guard dialog from shared store (registered by useBareForm/useEntityItemFormPage when a form is active)
|
|
54
54
|
const guardDialog = useGuardDialog()
|
|
55
55
|
|
|
56
56
|
// Provide breadcrumb/navlinks override mechanism for child pages
|
|
@@ -47,7 +47,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
47
47
|
badges: () => []
|
|
48
48
|
})
|
|
49
49
|
|
|
50
|
-
// Auto-injected title from
|
|
50
|
+
// Auto-injected title from the form composables (if available)
|
|
51
51
|
const injectedTitleParts = inject<Ref<TitleParts | null> | null>('qdadmPageTitleParts', null)
|
|
52
52
|
|
|
53
53
|
// Use props first, then injected, then fallback
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
import { computed, type PropType } from 'vue'
|
|
29
29
|
import { RouterLink, type RouteLocationRaw } from 'vue-router'
|
|
30
30
|
import Tag from 'primevue/tag'
|
|
31
|
+
import { formatDate, formatNumber, formatCurrency } from '../../utils/formatters'
|
|
31
32
|
|
|
32
33
|
type DisplayType =
|
|
33
34
|
| 'text'
|
|
@@ -101,13 +102,12 @@ const textValue = computed(() => {
|
|
|
101
102
|
return String(props.value)
|
|
102
103
|
})
|
|
103
104
|
|
|
104
|
-
// Format number value
|
|
105
|
+
// Format number value (browser locale by default — utils/formatters policy)
|
|
105
106
|
const numberValue = computed(() => {
|
|
106
107
|
if (isEmpty.value) return '-'
|
|
107
108
|
const num = Number(props.value)
|
|
108
109
|
if (isNaN(num)) return String(props.value)
|
|
109
|
-
|
|
110
|
-
return new Intl.NumberFormat(locale).format(num)
|
|
110
|
+
return formatNumber(num, {}, props.field.locale)
|
|
111
111
|
})
|
|
112
112
|
|
|
113
113
|
// Format currency value
|
|
@@ -115,9 +115,7 @@ const currencyValue = computed(() => {
|
|
|
115
115
|
if (isEmpty.value) return '-'
|
|
116
116
|
const num = Number(props.value)
|
|
117
117
|
if (isNaN(num)) return String(props.value)
|
|
118
|
-
|
|
119
|
-
const currency = props.field.currencyCode || 'USD'
|
|
120
|
-
return new Intl.NumberFormat(locale, { style: 'currency', currency }).format(num)
|
|
118
|
+
return formatCurrency(num, props.field.currencyCode || 'USD', props.field.locale)
|
|
121
119
|
})
|
|
122
120
|
|
|
123
121
|
// Format boolean value
|
|
@@ -143,9 +141,8 @@ const dateValue = computed(() => {
|
|
|
143
141
|
const v = props.value as unknown
|
|
144
142
|
const date = (typeof v === 'object' && v instanceof Date) ? v : new Date(String(v))
|
|
145
143
|
if (isNaN(date.getTime())) return String(props.value)
|
|
146
|
-
const locale = props.field.locale || 'en-US'
|
|
147
144
|
const format = props.field.dateFormat || 'short'
|
|
148
|
-
return
|
|
145
|
+
return formatDate(date, { dateStyle: format as 'short' | 'medium' | 'long' | 'full' }, props.field.locale)
|
|
149
146
|
})
|
|
150
147
|
|
|
151
148
|
// Format datetime value
|
|
@@ -154,8 +151,7 @@ const datetimeValue = computed(() => {
|
|
|
154
151
|
const v = props.value as unknown
|
|
155
152
|
const date = (typeof v === 'object' && v instanceof Date) ? v : new Date(String(v))
|
|
156
153
|
if (isNaN(date.getTime())) return String(props.value)
|
|
157
|
-
|
|
158
|
-
return new Intl.DateTimeFormat(locale, { dateStyle: 'short', timeStyle: 'short' }).format(date)
|
|
154
|
+
return formatDate(date, { dateStyle: 'short', timeStyle: 'short' }, props.field.locale)
|
|
159
155
|
})
|
|
160
156
|
|
|
161
157
|
// Format select value (lookup label from options)
|
package/src/composables/index.ts
CHANGED
|
@@ -15,12 +15,6 @@ export {
|
|
|
15
15
|
type SemanticBreadcrumbItem,
|
|
16
16
|
} from './useSemanticBreadcrumb'
|
|
17
17
|
export { useDirtyState, type UseDirtyStateReturn } from './useDirtyState'
|
|
18
|
-
export {
|
|
19
|
-
useForm,
|
|
20
|
-
type UseFormOptions,
|
|
21
|
-
type UseFormReturn,
|
|
22
|
-
type FormAlterConfig,
|
|
23
|
-
} from './useForm'
|
|
24
18
|
export {
|
|
25
19
|
useEntityItemFormPage,
|
|
26
20
|
useEntityItemFormPage as useFormPageBuilder,
|
|
@@ -132,7 +126,6 @@ export {
|
|
|
132
126
|
type UseUserImpersonatorReturn,
|
|
133
127
|
type UserRecord,
|
|
134
128
|
} from './useUserImpersonator'
|
|
135
|
-
export { useCurrentEntity, type UseCurrentEntityReturn } from './useCurrentEntity'
|
|
136
129
|
export {
|
|
137
130
|
useOptionsLookup,
|
|
138
131
|
type UseOptionsLookupConfig,
|
|
@@ -35,6 +35,7 @@ import { useEntityItemPage, type ParentConfig, type UseEntityItemPageReturn } fr
|
|
|
35
35
|
import { useActiveStack } from '../chain/useActiveStack.js'
|
|
36
36
|
import { FilterQuery } from '../query/FilterQuery'
|
|
37
37
|
import { useI18n } from '../i18n/useI18n'
|
|
38
|
+
import { formatDateOnly } from '../utils/formatters'
|
|
38
39
|
|
|
39
40
|
// Import types from dedicated types file
|
|
40
41
|
import type {
|
|
@@ -1307,18 +1308,12 @@ export function useListPage<T = unknown>(config: UseListPageOptions<T>): UseList
|
|
|
1307
1308
|
})
|
|
1308
1309
|
|
|
1309
1310
|
// ============ UTILITIES ============
|
|
1311
|
+
// Browser-locale by default (unified policy — see utils/formatters).
|
|
1310
1312
|
function formatDate(
|
|
1311
1313
|
dateStr: string | null | undefined,
|
|
1312
1314
|
options: Intl.DateTimeFormatOptions = {}
|
|
1313
1315
|
): string {
|
|
1314
|
-
|
|
1315
|
-
const defaultOptions: Intl.DateTimeFormatOptions = {
|
|
1316
|
-
day: '2-digit',
|
|
1317
|
-
month: '2-digit',
|
|
1318
|
-
year: 'numeric',
|
|
1319
|
-
...options,
|
|
1320
|
-
}
|
|
1321
|
-
return new Date(dateStr).toLocaleDateString('fr-FR', defaultOptions)
|
|
1316
|
+
return formatDateOnly(dateStr, options)
|
|
1322
1317
|
}
|
|
1323
1318
|
|
|
1324
1319
|
// ============ STANDARD ACTIONS ============
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* useNavContext - Route-aware navigation context for breadcrumb and navlinks
|
|
3
3
|
*
|
|
4
4
|
* Uses semantic breadcrumb as the source of truth for navigation structure.
|
|
5
|
-
* Entity data comes from activeStack (set by useEntityItemPage/
|
|
5
|
+
* Entity data comes from activeStack (set by useEntityItemPage/useEntityItemFormPage).
|
|
6
6
|
*
|
|
7
7
|
* Semantic breadcrumb kinds:
|
|
8
8
|
* - entity-list: Entity collection (e.g., /books)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* usePageTitle - Provide custom page title for PageHeader
|
|
3
3
|
*
|
|
4
4
|
* Use this composable in custom pages to set the title displayed in PageHeader.
|
|
5
|
-
* For standard CRUD pages,
|
|
5
|
+
* For standard CRUD pages, useEntityItemFormPage handles this automatically.
|
|
6
6
|
*
|
|
7
7
|
* Usage:
|
|
8
8
|
* ```ts
|
|
@@ -77,7 +77,7 @@ export function usePageTitle(
|
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
// Provide to PageHeader via same key as
|
|
80
|
+
// Provide to PageHeader via same key as the form composables
|
|
81
81
|
provide('qdadmPageTitleParts', titleParts)
|
|
82
82
|
|
|
83
83
|
return { titleParts, setTitle }
|
|
@@ -137,7 +137,7 @@ export function applyCrudMethods(EntityManagerClass: { prototype: any }): void {
|
|
|
137
137
|
const apiResponse = (await storage.request('GET', endpoint, {
|
|
138
138
|
params: requestParams,
|
|
139
139
|
context,
|
|
140
|
-
})) as { data?: unknown }
|
|
140
|
+
})) as { data?: unknown; total?: number; pagination?: { total?: number } }
|
|
141
141
|
// Normalize response
|
|
142
142
|
const data = apiResponse.data ?? apiResponse
|
|
143
143
|
const dataObj = data as {
|
|
@@ -150,9 +150,14 @@ export function applyCrudMethods(EntityManagerClass: { prototype: any }): void {
|
|
|
150
150
|
items: Array.isArray(data)
|
|
151
151
|
? (data as any[])
|
|
152
152
|
: dataObj.items || dataObj.data || [],
|
|
153
|
+
// Wrapper shapes like { data: [...], pagination: { total } } or
|
|
154
|
+
// { data: [...], total } carry the total on apiResponse, not on the
|
|
155
|
+
// unwrapped array — read both levels before falling back to page length.
|
|
153
156
|
total:
|
|
154
157
|
dataObj.total ??
|
|
155
158
|
dataObj.pagination?.total ??
|
|
159
|
+
apiResponse.total ??
|
|
160
|
+
apiResponse.pagination?.total ??
|
|
156
161
|
(Array.isArray(data) ? (data as any[]).length : 0),
|
|
157
162
|
}
|
|
158
163
|
} else {
|
|
@@ -57,11 +57,6 @@ export class ApiStorage<T extends EntityRecord = EntityRecord> extends IStorage<
|
|
|
57
57
|
supportsCaching: true,
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
/** @deprecated Use static ApiStorage.capabilities.supportsCaching instead */
|
|
61
|
-
get supportsCaching(): boolean {
|
|
62
|
-
return ApiStorage.capabilities.supportsCaching
|
|
63
|
-
}
|
|
64
|
-
|
|
65
60
|
readonly endpoint: string
|
|
66
61
|
readonly responseItemsKey: string
|
|
67
62
|
readonly responseTotalKey: string
|
|
@@ -27,11 +27,6 @@ export class LocalStorage<T extends EntityRecord = EntityRecord> extends IStorag
|
|
|
27
27
|
supportsCaching: false,
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
/** @deprecated Use static LocalStorage.capabilities.supportsCaching instead */
|
|
31
|
-
get supportsCaching(): boolean {
|
|
32
|
-
return LocalStorage.capabilities.supportsCaching
|
|
33
|
-
}
|
|
34
|
-
|
|
35
30
|
readonly key: string
|
|
36
31
|
readonly idField: string
|
|
37
32
|
readonly generateId: () => string
|
|
@@ -38,11 +38,6 @@ export class MemoryStorage<T extends EntityRecord = EntityRecord> extends IStora
|
|
|
38
38
|
supportsCaching: false,
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
/** @deprecated Use static MemoryStorage.capabilities.supportsCaching instead */
|
|
42
|
-
get supportsCaching(): boolean {
|
|
43
|
-
return MemoryStorage.capabilities.supportsCaching
|
|
44
|
-
}
|
|
45
|
-
|
|
46
41
|
readonly idField: string
|
|
47
42
|
readonly generateId: () => string
|
|
48
43
|
protected _data: Map<string, T>
|
|
@@ -35,11 +35,6 @@ export class MockApiStorage<T extends EntityRecord = EntityRecord> extends IStor
|
|
|
35
35
|
supportsCaching: false,
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
/** @deprecated Use static MockApiStorage.capabilities.supportsCaching instead */
|
|
39
|
-
get supportsCaching(): boolean {
|
|
40
|
-
return MockApiStorage.capabilities.supportsCaching
|
|
41
|
-
}
|
|
42
|
-
|
|
43
38
|
/**
|
|
44
39
|
* Instance capabilities getter.
|
|
45
40
|
* Merges static capabilities with instance-specific ones like requiresAuth.
|
|
@@ -91,11 +91,6 @@ export class SdkStorage<T extends EntityRecord = EntityRecord> extends IStorage<
|
|
|
91
91
|
supportsCaching: true,
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
/** @deprecated Use SdkStorage.capabilities.supportsCaching instead */
|
|
95
|
-
get supportsCaching(): boolean {
|
|
96
|
-
return SdkStorage.capabilities.supportsCaching
|
|
97
|
-
}
|
|
98
|
-
|
|
99
94
|
readonly clientSidePagination: boolean
|
|
100
95
|
|
|
101
96
|
protected _sdk: SdkInstance | null
|
package/src/index.ts
CHANGED
|
@@ -166,12 +166,6 @@ export {
|
|
|
166
166
|
type SemanticBreadcrumbItem,
|
|
167
167
|
} from './composables/useSemanticBreadcrumb'
|
|
168
168
|
export { useDirtyState, type UseDirtyStateReturn } from './composables/useDirtyState'
|
|
169
|
-
export {
|
|
170
|
-
useForm,
|
|
171
|
-
type UseFormOptions,
|
|
172
|
-
type UseFormReturn,
|
|
173
|
-
type FormAlterConfig,
|
|
174
|
-
} from './composables/useForm'
|
|
175
169
|
export {
|
|
176
170
|
useEntityItemFormPage,
|
|
177
171
|
useEntityItemFormPage as useFormPageBuilder,
|
|
@@ -292,7 +286,6 @@ export {
|
|
|
292
286
|
type UseUserImpersonatorReturn,
|
|
293
287
|
type UserRecord,
|
|
294
288
|
} from './composables/useUserImpersonator'
|
|
295
|
-
export { useCurrentEntity, type UseCurrentEntityReturn } from './composables/useCurrentEntity'
|
|
296
289
|
export {
|
|
297
290
|
useOptionsLookup,
|
|
298
291
|
type UseOptionsLookupConfig,
|
package/src/utils/formatters.ts
CHANGED
|
@@ -16,22 +16,27 @@
|
|
|
16
16
|
* Format a date string to localized date+time
|
|
17
17
|
* Default format: toLocaleString() (e.g., "20/12/2025, 14:30:00")
|
|
18
18
|
*
|
|
19
|
+
* The locale defaults to the browser locale everywhere in qdadm — pass
|
|
20
|
+
* `locale` only for an explicit per-field override.
|
|
21
|
+
*
|
|
19
22
|
* @param dateStr - ISO date string or Date object
|
|
20
23
|
* @param options - Intl.DateTimeFormat options
|
|
24
|
+
* @param locale - Explicit locale override (default: browser locale)
|
|
21
25
|
* @returns Formatted date string or '-' if empty
|
|
22
26
|
*/
|
|
23
27
|
export function formatDate(
|
|
24
28
|
dateStr: string | Date | null | undefined,
|
|
25
|
-
options: Intl.DateTimeFormatOptions = {}
|
|
29
|
+
options: Intl.DateTimeFormatOptions = {},
|
|
30
|
+
locale?: string
|
|
26
31
|
): string {
|
|
27
32
|
if (!dateStr) return '-'
|
|
28
33
|
const date = new Date(dateStr)
|
|
29
34
|
if (isNaN(date.getTime())) return '-'
|
|
30
35
|
|
|
31
36
|
if (Object.keys(options).length === 0) {
|
|
32
|
-
return date.toLocaleString()
|
|
37
|
+
return date.toLocaleString(locale)
|
|
33
38
|
}
|
|
34
|
-
return date.toLocaleString(
|
|
39
|
+
return date.toLocaleString(locale, options)
|
|
35
40
|
}
|
|
36
41
|
|
|
37
42
|
/**
|
|
@@ -61,15 +66,23 @@ export function formatDateTimeShort(dateStr: string | Date | null | undefined):
|
|
|
61
66
|
|
|
62
67
|
/**
|
|
63
68
|
* Format to date only (20/12/2025)
|
|
69
|
+
*
|
|
70
|
+
* @param options - Intl.DateTimeFormat overrides merged over the defaults
|
|
71
|
+
* @param locale - Explicit locale override (default: browser locale)
|
|
64
72
|
*/
|
|
65
|
-
export function formatDateOnly(
|
|
73
|
+
export function formatDateOnly(
|
|
74
|
+
dateStr: string | Date | null | undefined,
|
|
75
|
+
options: Intl.DateTimeFormatOptions = {},
|
|
76
|
+
locale?: string
|
|
77
|
+
): string {
|
|
66
78
|
if (!dateStr) return '-'
|
|
67
79
|
const date = new Date(dateStr)
|
|
68
80
|
if (isNaN(date.getTime())) return '-'
|
|
69
|
-
return date.toLocaleDateString(
|
|
81
|
+
return date.toLocaleDateString(locale, {
|
|
70
82
|
year: 'numeric',
|
|
71
83
|
month: '2-digit',
|
|
72
84
|
day: '2-digit',
|
|
85
|
+
...options,
|
|
73
86
|
})
|
|
74
87
|
}
|
|
75
88
|
|
|
@@ -142,13 +155,31 @@ export function formatDurationBetween(
|
|
|
142
155
|
|
|
143
156
|
/**
|
|
144
157
|
* Format a number with locale-specific separators
|
|
158
|
+
*
|
|
159
|
+
* @param locale - Explicit locale override (default: browser locale)
|
|
145
160
|
*/
|
|
146
161
|
export function formatNumber(
|
|
147
162
|
value: number | null | undefined,
|
|
148
|
-
options: Intl.NumberFormatOptions = {}
|
|
163
|
+
options: Intl.NumberFormatOptions = {},
|
|
164
|
+
locale?: string
|
|
165
|
+
): string {
|
|
166
|
+
if (value === null || value === undefined) return '-'
|
|
167
|
+
return value.toLocaleString(locale, options)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Format a currency amount (e.g., "$1,234.56")
|
|
172
|
+
*
|
|
173
|
+
* @param currencyCode - ISO 4217 code (default 'USD')
|
|
174
|
+
* @param locale - Explicit locale override (default: browser locale)
|
|
175
|
+
*/
|
|
176
|
+
export function formatCurrency(
|
|
177
|
+
value: number | null | undefined,
|
|
178
|
+
currencyCode = 'USD',
|
|
179
|
+
locale?: string
|
|
149
180
|
): string {
|
|
150
181
|
if (value === null || value === undefined) return '-'
|
|
151
|
-
return
|
|
182
|
+
return new Intl.NumberFormat(locale, { style: 'currency', currency: currencyCode }).format(value)
|
|
152
183
|
}
|
|
153
184
|
|
|
154
185
|
/**
|