qdadm 0.16.0 → 0.18.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 (60) hide show
  1. package/README.md +153 -1
  2. package/package.json +15 -2
  3. package/src/components/forms/FormField.vue +64 -6
  4. package/src/components/forms/FormPage.vue +276 -0
  5. package/src/components/index.js +11 -0
  6. package/src/components/layout/BaseLayout.vue +183 -0
  7. package/src/components/layout/DashboardLayout.vue +100 -0
  8. package/src/components/layout/FormLayout.vue +261 -0
  9. package/src/components/layout/ListLayout.vue +334 -0
  10. package/src/components/layout/Zone.vue +165 -0
  11. package/src/components/layout/defaults/DefaultBreadcrumb.vue +140 -0
  12. package/src/components/layout/defaults/DefaultFooter.vue +56 -0
  13. package/src/components/layout/defaults/DefaultFormActions.vue +53 -0
  14. package/src/components/layout/defaults/DefaultHeader.vue +69 -0
  15. package/src/components/layout/defaults/DefaultMenu.vue +197 -0
  16. package/src/components/layout/defaults/DefaultPagination.vue +79 -0
  17. package/src/components/layout/defaults/DefaultTable.vue +130 -0
  18. package/src/components/layout/defaults/DefaultToaster.vue +16 -0
  19. package/src/components/layout/defaults/DefaultUserInfo.vue +96 -0
  20. package/src/components/layout/defaults/index.js +17 -0
  21. package/src/composables/index.js +6 -6
  22. package/src/composables/useForm.js +135 -0
  23. package/src/composables/useFormPageBuilder.js +1154 -0
  24. package/src/composables/useHooks.js +53 -0
  25. package/src/composables/useLayoutResolver.js +260 -0
  26. package/src/composables/useListPageBuilder.js +336 -52
  27. package/src/composables/useNavigation.js +38 -2
  28. package/src/composables/useSignals.js +49 -0
  29. package/src/composables/useZoneRegistry.js +162 -0
  30. package/src/core/bundles.js +406 -0
  31. package/src/core/decorator.js +322 -0
  32. package/src/core/extension.js +386 -0
  33. package/src/core/index.js +28 -0
  34. package/src/entity/EntityManager.js +359 -16
  35. package/src/entity/auth/AuthAdapter.js +184 -0
  36. package/src/entity/auth/PermissiveAdapter.js +64 -0
  37. package/src/entity/auth/RoleHierarchy.js +153 -0
  38. package/src/entity/auth/SecurityChecker.js +167 -0
  39. package/src/entity/auth/index.js +18 -0
  40. package/src/entity/index.js +3 -0
  41. package/src/entity/storage/MockApiStorage.js +349 -0
  42. package/src/entity/storage/SdkStorage.js +478 -0
  43. package/src/entity/storage/index.js +2 -0
  44. package/src/hooks/HookRegistry.js +411 -0
  45. package/src/hooks/index.js +12 -0
  46. package/src/index.js +13 -0
  47. package/src/kernel/Kernel.js +206 -5
  48. package/src/kernel/SignalBus.js +180 -0
  49. package/src/kernel/index.js +7 -0
  50. package/src/module/moduleRegistry.js +155 -28
  51. package/src/orchestrator/Orchestrator.js +73 -1
  52. package/src/zones/ZoneRegistry.js +828 -0
  53. package/src/zones/index.js +16 -0
  54. package/src/zones/zones.js +189 -0
  55. package/src/composables/useEntityTitle.js +0 -121
  56. package/src/composables/useManager.js +0 -20
  57. package/src/composables/usePageBuilder.js +0 -334
  58. package/src/composables/useStatus.js +0 -146
  59. package/src/composables/useSubEditor.js +0 -165
  60. package/src/composables/useTabSync.js +0 -110
@@ -7,6 +7,7 @@
7
7
  * - Unsaved changes guard (via useBareForm)
8
8
  * - Toast notifications
9
9
  * - Navigation helpers
10
+ * - form:alter hook for extensibility
10
11
  *
11
12
  * Usage:
12
13
  * ```js
@@ -25,9 +26,45 @@
25
26
  * entityName: 'User' // Override manager.label
26
27
  * })
27
28
  * ```
29
+ *
30
+ * ## form:alter Hook
31
+ *
32
+ * The composable invokes `form:alter` and `{entity}:form:alter` hooks after
33
+ * form initialization (load for edit, or initial data for create). This allows
34
+ * modules to modify form configuration dynamically.
35
+ *
36
+ * Hook context structure:
37
+ * @typedef {object} FormAlterConfig
38
+ * @property {string} entity - Entity name
39
+ * @property {Array} fields - Field definitions from manager
40
+ * @property {boolean} isEdit - Whether form is in edit mode
41
+ * @property {*} entityId - Entity ID (null for create)
42
+ * @property {object} form - Current form data
43
+ * @property {object} manager - EntityManager reference
44
+ *
45
+ * @example
46
+ * // Register a hook to modify form fields
47
+ * hooks.register('form:alter', (config) => {
48
+ * if (config.entity === 'books') {
49
+ * // Add a computed field
50
+ * config.fields.push({ name: 'fullTitle', computed: true, readonly: true })
51
+ * }
52
+ * return config
53
+ * })
54
+ *
55
+ * @example
56
+ * // Entity-specific hook
57
+ * hooks.register('books:form:alter', (config) => {
58
+ * // Modify field visibility based on edit mode
59
+ * if (!config.isEdit) {
60
+ * config.fields = config.fields.filter(f => f.name !== 'internal_id')
61
+ * }
62
+ * return config
63
+ * })
28
64
  */
29
65
  import { ref, computed, watch, onMounted, inject, provide } from 'vue'
30
66
  import { useBareForm } from './useBareForm'
67
+ import { useHooks } from './useHooks'
31
68
  import { deepClone } from '../utils/transformers'
32
69
 
33
70
  export function useForm(options = {}) {
@@ -53,6 +90,9 @@ export function useForm(options = {}) {
53
90
  }
54
91
  const manager = orchestrator.get(entity)
55
92
 
93
+ // Get HookRegistry for form:alter hook (optional, may not exist in tests)
94
+ const hooks = useHooks()
95
+
56
96
  // Read config from manager with option overrides
57
97
  const routePrefix = options.routePrefix ?? manager.routePrefix
58
98
  const entityName = options.entityName ?? manager.label
@@ -62,6 +102,23 @@ export function useForm(options = {}) {
62
102
  const form = ref(deepClone(initialData))
63
103
  const originalData = ref(null)
64
104
 
105
+ /**
106
+ * Altered fields configuration after form:alter hook processing
107
+ *
108
+ * Contains the field definitions modified by registered hooks.
109
+ * Modules can register form:alter hooks to modify field visibility,
110
+ * add/remove fields, change labels, or modify validation rules.
111
+ *
112
+ * @type {import('vue').Ref<Array<object>>}
113
+ */
114
+ const alteredFields = ref([])
115
+
116
+ /**
117
+ * Whether form:alter hooks have been invoked
118
+ * @type {import('vue').Ref<boolean>}
119
+ */
120
+ const hooksInvoked = ref(false)
121
+
65
122
  // Dirty state getter
66
123
  const dirtyStateGetter = getDirtyState || (() => ({ form: form.value }))
67
124
 
@@ -98,11 +155,81 @@ export function useForm(options = {}) {
98
155
  // Watch for changes
99
156
  watch(form, checkDirty, { deep: true })
100
157
 
158
+ // ============ FORM:ALTER HOOK ============
159
+
160
+ /**
161
+ * Invoke form:alter hooks to allow modules to modify form configuration
162
+ *
163
+ * Builds a config snapshot from current state, passes it through the hook chain,
164
+ * and stores the altered fields configuration.
165
+ *
166
+ * Hook context structure:
167
+ * @typedef {object} FormAlterConfig
168
+ * @property {string} entity - Entity name
169
+ * @property {Array} fields - Field definitions from manager.getFormFields()
170
+ * @property {boolean} isEdit - Whether form is in edit mode
171
+ * @property {*} entityId - Entity ID (null for create)
172
+ * @property {object} form - Current form data
173
+ * @property {object} manager - EntityManager reference
174
+ *
175
+ * @example
176
+ * // Register a hook to add a custom field
177
+ * hooks.register('form:alter', (config) => {
178
+ * if (config.entity === 'books') {
179
+ * config.fields.push({ name: 'custom', type: 'text', label: 'Custom' })
180
+ * }
181
+ * return config
182
+ * })
183
+ *
184
+ * @example
185
+ * // Register entity-specific hook
186
+ * hooks.register('books:form:alter', (config) => {
187
+ * // Hide internal_id in create mode
188
+ * if (!config.isEdit) {
189
+ * config.fields = config.fields.filter(f => f.name !== 'internal_id')
190
+ * }
191
+ * return config
192
+ * })
193
+ */
194
+ async function invokeFormAlterHook() {
195
+ if (!hooks) return
196
+
197
+ // Get fields from manager (if available)
198
+ const managerFields = typeof manager.getFormFields === 'function'
199
+ ? manager.getFormFields()
200
+ : (manager.fields || [])
201
+
202
+ // Build config snapshot
203
+ const configSnapshot = {
204
+ entity,
205
+ fields: deepClone(managerFields),
206
+ isEdit: isEdit.value,
207
+ entityId: entityId.value,
208
+ form: form.value,
209
+ manager,
210
+ }
211
+
212
+ // Invoke generic form:alter hook
213
+ let alteredConfig = await hooks.alter('form:alter', configSnapshot)
214
+
215
+ // Invoke entity-specific hook: {entity}:form:alter
216
+ const entityHookName = `${entity}:form:alter`
217
+ if (hooks.hasHook(entityHookName)) {
218
+ alteredConfig = await hooks.alter(entityHookName, alteredConfig)
219
+ }
220
+
221
+ // Store altered fields for consumption by FormPage
222
+ alteredFields.value = alteredConfig.fields || []
223
+ hooksInvoked.value = true
224
+ }
225
+
101
226
  // Load entity
102
227
  async function load() {
103
228
  if (!isEdit.value) {
104
229
  form.value = deepClone(initialData)
105
230
  takeSnapshot()
231
+ // Invoke form:alter hooks for create mode
232
+ await invokeFormAlterHook()
106
233
  return
107
234
  }
108
235
 
@@ -114,6 +241,9 @@ export function useForm(options = {}) {
114
241
  originalData.value = deepClone(data)
115
242
  takeSnapshot()
116
243
 
244
+ // Invoke form:alter hooks after data is loaded
245
+ await invokeFormAlterHook()
246
+
117
247
  if (onLoadSuccess) {
118
248
  await onLoadSuccess(data)
119
249
  }
@@ -276,6 +406,11 @@ export function useForm(options = {}) {
276
406
  entityId,
277
407
  originalData,
278
408
 
409
+ // form:alter hook results
410
+ alteredFields,
411
+ hooksInvoked,
412
+ invokeFormAlterHook,
413
+
279
414
  // Actions
280
415
  load,
281
416
  submit,