qdadm 0.15.1 → 0.17.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 (66) hide show
  1. package/README.md +153 -1
  2. package/package.json +15 -2
  3. package/src/components/BoolCell.vue +11 -6
  4. package/src/components/forms/FormField.vue +64 -6
  5. package/src/components/forms/FormPage.vue +276 -0
  6. package/src/components/index.js +11 -0
  7. package/src/components/layout/AppLayout.vue +18 -9
  8. package/src/components/layout/BaseLayout.vue +183 -0
  9. package/src/components/layout/DashboardLayout.vue +100 -0
  10. package/src/components/layout/FormLayout.vue +261 -0
  11. package/src/components/layout/ListLayout.vue +334 -0
  12. package/src/components/layout/PageHeader.vue +6 -9
  13. package/src/components/layout/PageNav.vue +15 -0
  14. package/src/components/layout/Zone.vue +165 -0
  15. package/src/components/layout/defaults/DefaultBreadcrumb.vue +140 -0
  16. package/src/components/layout/defaults/DefaultFooter.vue +56 -0
  17. package/src/components/layout/defaults/DefaultFormActions.vue +53 -0
  18. package/src/components/layout/defaults/DefaultHeader.vue +69 -0
  19. package/src/components/layout/defaults/DefaultMenu.vue +197 -0
  20. package/src/components/layout/defaults/DefaultPagination.vue +79 -0
  21. package/src/components/layout/defaults/DefaultTable.vue +130 -0
  22. package/src/components/layout/defaults/DefaultToaster.vue +16 -0
  23. package/src/components/layout/defaults/DefaultUserInfo.vue +96 -0
  24. package/src/components/layout/defaults/index.js +17 -0
  25. package/src/composables/index.js +8 -6
  26. package/src/composables/useBreadcrumb.js +9 -5
  27. package/src/composables/useForm.js +135 -0
  28. package/src/composables/useFormPageBuilder.js +1154 -0
  29. package/src/composables/useHooks.js +53 -0
  30. package/src/composables/useLayoutResolver.js +260 -0
  31. package/src/composables/useListPageBuilder.js +336 -52
  32. package/src/composables/useNavContext.js +372 -0
  33. package/src/composables/useNavigation.js +38 -2
  34. package/src/composables/usePageTitle.js +59 -0
  35. package/src/composables/useSignals.js +49 -0
  36. package/src/composables/useZoneRegistry.js +162 -0
  37. package/src/core/bundles.js +406 -0
  38. package/src/core/decorator.js +322 -0
  39. package/src/core/extension.js +386 -0
  40. package/src/core/index.js +28 -0
  41. package/src/entity/EntityManager.js +314 -16
  42. package/src/entity/auth/AuthAdapter.js +125 -0
  43. package/src/entity/auth/PermissiveAdapter.js +64 -0
  44. package/src/entity/auth/index.js +11 -0
  45. package/src/entity/index.js +3 -0
  46. package/src/entity/storage/MockApiStorage.js +349 -0
  47. package/src/entity/storage/SdkStorage.js +478 -0
  48. package/src/entity/storage/index.js +2 -0
  49. package/src/hooks/HookRegistry.js +411 -0
  50. package/src/hooks/index.js +12 -0
  51. package/src/index.js +12 -0
  52. package/src/kernel/Kernel.js +141 -4
  53. package/src/kernel/SignalBus.js +180 -0
  54. package/src/kernel/index.js +7 -0
  55. package/src/module/moduleRegistry.js +124 -6
  56. package/src/orchestrator/Orchestrator.js +73 -1
  57. package/src/plugin.js +5 -0
  58. package/src/zones/ZoneRegistry.js +821 -0
  59. package/src/zones/index.js +16 -0
  60. package/src/zones/zones.js +189 -0
  61. package/src/composables/useEntityTitle.js +0 -121
  62. package/src/composables/useManager.js +0 -20
  63. package/src/composables/usePageBuilder.js +0 -334
  64. package/src/composables/useStatus.js +0 -146
  65. package/src/composables/useSubEditor.js +0 -165
  66. package/src/composables/useTabSync.js +0 -110
@@ -1,146 +0,0 @@
1
- /**
2
- * useStatus - Generic status composable
3
- *
4
- * Loads and formats status options from any API endpoint.
5
- * Provides caching, label/severity lookup helpers.
6
- *
7
- * Usage:
8
- * const { options, loadOptions, getLabel, getSeverity } = useStatus({
9
- * endpoint: '/reference/queue-statuses',
10
- * labelField: 'label',
11
- * valueField: 'value',
12
- * severityField: 'severity'
13
- * })
14
- */
15
-
16
- import { ref, inject } from 'vue'
17
-
18
- // Global cache for status options (keyed by endpoint)
19
- const statusCache = new Map()
20
-
21
- export function useStatus(config = {}) {
22
- const {
23
- endpoint,
24
- labelField = 'label',
25
- valueField = 'value',
26
- severityField = 'severity',
27
- // Optional: static options instead of API fetch
28
- staticOptions = null,
29
- // Optional: custom severity mapping function
30
- severityMapper = null,
31
- // Optional: default severity if not found
32
- defaultSeverity = 'secondary'
33
- } = config
34
-
35
- const api = inject('apiAdapter')
36
- const options = ref([])
37
- const loading = ref(false)
38
- const error = ref(null)
39
-
40
- /**
41
- * Load options from API or use static options
42
- */
43
- async function loadOptions(force = false) {
44
- // Use static options if provided
45
- if (staticOptions) {
46
- options.value = staticOptions
47
- return options.value
48
- }
49
-
50
- if (!endpoint) {
51
- console.warn('[useStatus] No endpoint provided')
52
- return []
53
- }
54
-
55
- // Check cache first (unless force refresh)
56
- if (!force && statusCache.has(endpoint)) {
57
- options.value = statusCache.get(endpoint)
58
- return options.value
59
- }
60
-
61
- if (!api) {
62
- console.warn('[useStatus] apiAdapter not provided')
63
- return []
64
- }
65
-
66
- loading.value = true
67
- error.value = null
68
-
69
- try {
70
- const response = await api.request('GET', endpoint)
71
- // Handle both array response and { items: [] } response
72
- const items = Array.isArray(response) ? response : (response?.items || response?.data || [])
73
- options.value = items
74
- statusCache.set(endpoint, items)
75
- return items
76
- } catch (e) {
77
- error.value = e.message
78
- console.error(`[useStatus] Failed to load options from ${endpoint}:`, e)
79
- return []
80
- } finally {
81
- loading.value = false
82
- }
83
- }
84
-
85
- /**
86
- * Get label for a status value
87
- */
88
- function getLabel(value) {
89
- if (value == null) return ''
90
- const option = options.value.find(opt =>
91
- (typeof opt === 'string' ? opt : opt[valueField]) === value
92
- )
93
- if (!option) return String(value)
94
- return typeof option === 'string' ? option : option[labelField]
95
- }
96
-
97
- /**
98
- * Get severity for a status value (for Tag/Badge color)
99
- */
100
- function getSeverity(value) {
101
- if (value == null) return defaultSeverity
102
-
103
- // Use custom mapper if provided
104
- if (severityMapper) {
105
- return severityMapper(value) || defaultSeverity
106
- }
107
-
108
- const option = options.value.find(opt =>
109
- (typeof opt === 'string' ? opt : opt[valueField]) === value
110
- )
111
- if (!option || typeof option === 'string') return defaultSeverity
112
- return option[severityField] || defaultSeverity
113
- }
114
-
115
- /**
116
- * Get full option object for a value
117
- */
118
- function getOption(value) {
119
- if (value == null) return null
120
- return options.value.find(opt =>
121
- (typeof opt === 'string' ? opt : opt[valueField]) === value
122
- )
123
- }
124
-
125
- /**
126
- * Clear cache for this endpoint or all endpoints
127
- */
128
- function clearCache(all = false) {
129
- if (all) {
130
- statusCache.clear()
131
- } else if (endpoint) {
132
- statusCache.delete(endpoint)
133
- }
134
- }
135
-
136
- return {
137
- options,
138
- loading,
139
- error,
140
- loadOptions,
141
- getLabel,
142
- getSeverity,
143
- getOption,
144
- clearCache
145
- }
146
- }
@@ -1,165 +0,0 @@
1
- import { computed, inject } from 'vue'
2
-
3
- /**
4
- * Composable for building sub-editor components that edit a portion of parent form data.
5
- *
6
- * Sub-editors receive data via v-model and emit updates, but don't handle persistence.
7
- * The parent form (using useForm/useBareForm) handles loading, saving, and dirty tracking.
8
- *
9
- * Usage in sub-editor:
10
- * const props = defineProps({ modelValue: Object })
11
- * const emit = defineEmits(['update:modelValue'])
12
- *
13
- * const { data, update, field } = useSubEditor(props, emit)
14
- *
15
- * // Access data
16
- * data.value.someField
17
- *
18
- * // Update single field
19
- * update('someField', newValue)
20
- *
21
- * // Computed getter/setter for field
22
- * const myField = field('someField', defaultValue)
23
- * myField.value = 'new value' // auto-emits update
24
- *
25
- * // Nested field access
26
- * const nested = field('config.subsection.value', 0)
27
- *
28
- * Features:
29
- * - Simplified update pattern (no manual spread)
30
- * - Computed fields with get/set for v-model binding
31
- * - Nested path support (dot notation)
32
- * - Default values
33
- * - Optional dirty field indicator integration with parent
34
- *
35
- * @param {Object} props - Component props (must include modelValue)
36
- * @param {Function} emit - Component emit function
37
- * @param {Object} options - Additional options
38
- * @param {*} options.defaultData - Default data structure if modelValue is empty
39
- */
40
- export function useSubEditor(props, emit, options = {}) {
41
- const { defaultData = {} } = options
42
-
43
- // Computed reference to data (with default fallback)
44
- const data = computed(() => props.modelValue ?? defaultData)
45
-
46
- /**
47
- * Get value at nested path
48
- */
49
- function getNestedValue(obj, path) {
50
- if (!path.includes('.')) {
51
- return obj?.[path]
52
- }
53
- return path.split('.').reduce((curr, key) => curr?.[key], obj)
54
- }
55
-
56
- /**
57
- * Set value at nested path, returning new object
58
- */
59
- function setNestedValue(obj, path, value) {
60
- if (!path.includes('.')) {
61
- return { ...obj, [path]: value }
62
- }
63
-
64
- const keys = path.split('.')
65
- const result = { ...obj }
66
- let current = result
67
-
68
- for (let i = 0; i < keys.length - 1; i++) {
69
- const key = keys[i]
70
- current[key] = { ...current[key] }
71
- current = current[key]
72
- }
73
-
74
- current[keys[keys.length - 1]] = value
75
- return result
76
- }
77
-
78
- /**
79
- * Update a field and emit the new value
80
- * @param {string} path - Field path (supports dot notation for nested)
81
- * @param {*} value - New value
82
- */
83
- function update(path, value) {
84
- const newData = setNestedValue(data.value, path, value)
85
- emit('update:modelValue', newData)
86
- }
87
-
88
- /**
89
- * Replace entire data object
90
- * @param {Object} newData - Complete new data object
91
- */
92
- function replace(newData) {
93
- emit('update:modelValue', newData)
94
- }
95
-
96
- /**
97
- * Create a computed ref for a field with get/set
98
- * Useful for v-model binding in template
99
- *
100
- * @param {string} path - Field path (supports dot notation)
101
- * @param {*} defaultValue - Default value if field is undefined
102
- * @returns {ComputedRef} Writable computed ref
103
- */
104
- function field(path, defaultValue = undefined) {
105
- return computed({
106
- get: () => {
107
- const value = getNestedValue(data.value, path)
108
- return value !== undefined ? value : defaultValue
109
- },
110
- set: (value) => update(path, value)
111
- })
112
- }
113
-
114
- /**
115
- * Create multiple field refs at once
116
- * @param {Object} fields - { fieldName: defaultValue, ... }
117
- * @returns {Object} { fieldName: computedRef, ... }
118
- */
119
- function fields(fieldDefs) {
120
- const result = {}
121
- for (const [path, defaultValue] of Object.entries(fieldDefs)) {
122
- result[path] = field(path, defaultValue)
123
- }
124
- return result
125
- }
126
-
127
- /**
128
- * Get value with default fallback (non-reactive, for one-time reads)
129
- */
130
- function get(path, defaultValue = undefined) {
131
- const value = getNestedValue(data.value, path)
132
- return value !== undefined ? value : defaultValue
133
- }
134
-
135
- // Try to inject field-level dirty tracking from parent (if available)
136
- const parentIsFieldDirty = inject('isFieldDirty', null)
137
-
138
- /**
139
- * Check if a field is dirty (if parent provides tracking)
140
- * This allows sub-editors to show field-level dirty indicators
141
- */
142
- function isFieldDirty(path) {
143
- if (!parentIsFieldDirty) return false
144
- // The parent tracks 'form.value.fieldName' or similar
145
- // We need to prefix with the path the parent uses
146
- return parentIsFieldDirty(path)
147
- }
148
-
149
- return {
150
- // Data access
151
- data,
152
- get,
153
-
154
- // Updates
155
- update,
156
- replace,
157
-
158
- // Field helpers
159
- field,
160
- fields,
161
-
162
- // Dirty tracking (from parent)
163
- isFieldDirty
164
- }
165
- }
@@ -1,110 +0,0 @@
1
- /**
2
- * useTabSync - Composable for tab navigation with URL hash sync
3
- *
4
- * Provides:
5
- * - activeTab computed from URL hash
6
- * - onTabChange handler to update URL
7
- * - Support for conditional tabs (edit mode only)
8
- * - Lazy loading trigger on tab activation
9
- *
10
- * Usage:
11
- * const { activeTab, onTabChange } = useTabSync({
12
- * validTabs: ['general', 'style', 'behavior', 'newsrooms'],
13
- * defaultTab: 'general',
14
- * restrictedTabs: { newsrooms: () => isEdit.value },
15
- * onTabActivate: { newsrooms: () => loadNewsroomData() }
16
- * })
17
- */
18
-
19
- import { computed } from 'vue'
20
- import { useRoute, useRouter } from 'vue-router'
21
-
22
- /**
23
- * @param {Object} options
24
- * @param {string[]} options.validTabs - List of valid tab values
25
- * @param {string} [options.defaultTab='general'] - Default tab when hash is invalid
26
- * @param {Object.<string, Function>} [options.restrictedTabs={}] - Tab name -> condition function
27
- * @param {Object.<string, Function>} [options.onTabActivate={}] - Tab name -> callback on first activation
28
- */
29
- export function useTabSync(options = {}) {
30
- const {
31
- validTabs = ['general'],
32
- defaultTab = 'general',
33
- restrictedTabs = {},
34
- onTabActivate = {}
35
- } = options
36
-
37
- const route = useRoute()
38
- const router = useRouter()
39
-
40
- // Track which tabs have been activated (for lazy loading)
41
- const activatedTabs = new Set([defaultTab])
42
-
43
- /**
44
- * Active tab derived from URL hash
45
- */
46
- const activeTab = computed(() => {
47
- const hash = route.hash?.replace('#', '')
48
-
49
- if (hash && validTabs.includes(hash)) {
50
- // Check if tab is restricted
51
- const restriction = restrictedTabs[hash]
52
- if (restriction && !restriction()) {
53
- return defaultTab
54
- }
55
- return hash
56
- }
57
-
58
- return defaultTab
59
- })
60
-
61
- /**
62
- * Handle tab change - update URL hash and trigger callbacks
63
- * @param {string} newTab
64
- */
65
- function onTabChange(newTab) {
66
- // Update URL hash
67
- router.replace({ ...route, hash: `#${newTab}` })
68
-
69
- // Trigger lazy load callback if first activation
70
- if (!activatedTabs.has(newTab)) {
71
- activatedTabs.add(newTab)
72
- const callback = onTabActivate[newTab]
73
- if (callback) {
74
- callback()
75
- }
76
- }
77
- }
78
-
79
- /**
80
- * Check if a tab should be visible
81
- * @param {string} tabName
82
- * @returns {boolean}
83
- */
84
- function isTabVisible(tabName) {
85
- const restriction = restrictedTabs[tabName]
86
- if (restriction) {
87
- return restriction()
88
- }
89
- return true
90
- }
91
-
92
- /**
93
- * Navigate to a specific tab programmatically
94
- * @param {string} tabName
95
- */
96
- function goToTab(tabName) {
97
- if (validTabs.includes(tabName) && isTabVisible(tabName)) {
98
- onTabChange(tabName)
99
- }
100
- }
101
-
102
- return {
103
- activeTab,
104
- onTabChange,
105
- isTabVisible,
106
- goToTab
107
- }
108
- }
109
-
110
- export default useTabSync