@quazardous/qdadm 1.19.3

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 (291) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +111 -0
  3. package/package.json +101 -0
  4. package/src/assets/logo.svg +6 -0
  5. package/src/auth/SessionAuthAdapter.ts +459 -0
  6. package/src/auth/index.ts +15 -0
  7. package/src/chain/ActiveStack.ts +154 -0
  8. package/src/chain/StackHydrator.ts +292 -0
  9. package/src/chain/index.ts +25 -0
  10. package/src/chain/useActiveStack.ts +120 -0
  11. package/src/chain/useStackHydrator.ts +155 -0
  12. package/src/components/BoolCell.vue +34 -0
  13. package/src/components/InfoBanner.vue +73 -0
  14. package/src/components/QdadmRoot.vue +52 -0
  15. package/src/components/SeverityTag.vue +86 -0
  16. package/src/components/dialogs/BulkStatusDialog.vue +60 -0
  17. package/src/components/dialogs/MultiStepDialog.vue +331 -0
  18. package/src/components/dialogs/SimpleDialog.vue +122 -0
  19. package/src/components/dialogs/UnsavedChangesDialog.vue +80 -0
  20. package/src/components/display/BannerZone.vue +65 -0
  21. package/src/components/display/CardsGrid.vue +72 -0
  22. package/src/components/display/CopyableId.vue +84 -0
  23. package/src/components/display/EmptyState.vue +62 -0
  24. package/src/components/display/IntensityBar.vue +190 -0
  25. package/src/components/display/RichCardsGrid.vue +101 -0
  26. package/src/components/edit/FormActions.vue +78 -0
  27. package/src/components/edit/FormField.vue +82 -0
  28. package/src/components/edit/FormInput.vue +158 -0
  29. package/src/components/edit/FormPage.vue +260 -0
  30. package/src/components/edit/FormTab.vue +217 -0
  31. package/src/components/edit/FormTabs.vue +120 -0
  32. package/src/components/edit/LookupField.vue +406 -0
  33. package/src/components/edit/LookupPickerDialog.vue +230 -0
  34. package/src/components/editors/JsonEditorFoldable.vue +470 -0
  35. package/src/components/editors/JsonStructuredField.vue +264 -0
  36. package/src/components/editors/JsonViewer.vue +51 -0
  37. package/src/components/editors/KeyValueEditor.vue +314 -0
  38. package/src/components/editors/LanguageEditor.vue +259 -0
  39. package/src/components/editors/PermissionEditor.vue +558 -0
  40. package/src/components/editors/ScopeEditor.vue +372 -0
  41. package/src/components/editors/VanillaJsonEditor.vue +183 -0
  42. package/src/components/index.ts +83 -0
  43. package/src/components/item/FieldGroups.vue +392 -0
  44. package/src/components/layout/AppLayout.vue +1032 -0
  45. package/src/components/layout/BaseLayout.vue +190 -0
  46. package/src/components/layout/Breadcrumb.vue +55 -0
  47. package/src/components/layout/DashboardLayout.vue +104 -0
  48. package/src/components/layout/FormLayout.vue +256 -0
  49. package/src/components/layout/ListLayout.vue +278 -0
  50. package/src/components/layout/PageHeader.vue +156 -0
  51. package/src/components/layout/PageLayout.vue +126 -0
  52. package/src/components/layout/PageNav.vue +192 -0
  53. package/src/components/layout/SidebarBox.vue +87 -0
  54. package/src/components/layout/Zone.vue +182 -0
  55. package/src/components/layout/defaults/DefaultBreadcrumb.vue +149 -0
  56. package/src/components/layout/defaults/DefaultFooter.vue +89 -0
  57. package/src/components/layout/defaults/DefaultFormActions.vue +67 -0
  58. package/src/components/layout/defaults/DefaultHeader.vue +78 -0
  59. package/src/components/layout/defaults/DefaultMenu.vue +188 -0
  60. package/src/components/layout/defaults/DefaultPagination.vue +75 -0
  61. package/src/components/layout/defaults/DefaultTable.vue +137 -0
  62. package/src/components/layout/defaults/DefaultUserInfo.vue +111 -0
  63. package/src/components/layout/defaults/index.ts +16 -0
  64. package/src/components/lists/ActionButtons.vue +49 -0
  65. package/src/components/lists/ActionColumn.vue +40 -0
  66. package/src/components/lists/FilterBar.vue +54 -0
  67. package/src/components/lists/ListPage.vue +467 -0
  68. package/src/components/pages/LoginPage.vue +334 -0
  69. package/src/components/pages/NotFoundPage.vue +98 -0
  70. package/src/components/show/ShowDisplay.vue +343 -0
  71. package/src/components/show/ShowField.vue +67 -0
  72. package/src/components/show/ShowPage.vue +431 -0
  73. package/src/composables/index.ts +155 -0
  74. package/src/composables/useApp.ts +67 -0
  75. package/src/composables/useAuth.ts +98 -0
  76. package/src/composables/useBareForm.ts +319 -0
  77. package/src/composables/useBreadcrumb.ts +252 -0
  78. package/src/composables/useChildPage.ts +113 -0
  79. package/src/composables/useCurrentEntity.ts +60 -0
  80. package/src/composables/useDeferred.ts +94 -0
  81. package/src/composables/useDirtyState.ts +138 -0
  82. package/src/composables/useEntityItemFormPage.ts +942 -0
  83. package/src/composables/useEntityItemFormPage.types.ts +451 -0
  84. package/src/composables/useEntityItemPage.ts +438 -0
  85. package/src/composables/useEntityItemShowPage.ts +722 -0
  86. package/src/composables/useFieldManager.ts +692 -0
  87. package/src/composables/useForm.ts +544 -0
  88. package/src/composables/useGuardStore.ts +53 -0
  89. package/src/composables/useHooks.ts +56 -0
  90. package/src/composables/useInfoBanner.ts +217 -0
  91. package/src/composables/useJsonSyntax.ts +122 -0
  92. package/src/composables/useLayoutResolver.ts +329 -0
  93. package/src/composables/useListPage.ts +1535 -0
  94. package/src/composables/useListPage.types.ts +437 -0
  95. package/src/composables/useListPage.utils.ts +101 -0
  96. package/src/composables/useNavContext.ts +439 -0
  97. package/src/composables/useNavigation.ts +216 -0
  98. package/src/composables/useOptionsLookup.ts +323 -0
  99. package/src/composables/usePageTitle.ts +84 -0
  100. package/src/composables/useSSEBridge.ts +142 -0
  101. package/src/composables/useSemanticBreadcrumb.ts +222 -0
  102. package/src/composables/useSidebarState.ts +24 -0
  103. package/src/composables/useSignals.ts +52 -0
  104. package/src/composables/useUnsavedChangesGuard.ts +155 -0
  105. package/src/composables/useUserImpersonator.ts +317 -0
  106. package/src/composables/useZoneRegistry.ts +252 -0
  107. package/src/core/bundles.ts +558 -0
  108. package/src/core/decorator.ts +420 -0
  109. package/src/core/extension.ts +470 -0
  110. package/src/core/index.ts +59 -0
  111. package/src/deferred/DeferredRegistry.ts +353 -0
  112. package/src/deferred/index.ts +15 -0
  113. package/src/editors/index.ts +12 -0
  114. package/src/entity/EntityManager.cache.ts +530 -0
  115. package/src/entity/EntityManager.crud.ts +615 -0
  116. package/src/entity/EntityManager.interface.ts +78 -0
  117. package/src/entity/EntityManager.query.ts +221 -0
  118. package/src/entity/EntityManager.relations.ts +151 -0
  119. package/src/entity/EntityManager.ts +944 -0
  120. package/src/entity/EntityManager.types.ts +288 -0
  121. package/src/entity/auth/CompositeAuthAdapter.ts +194 -0
  122. package/src/entity/auth/EntityAuthAdapter.ts +119 -0
  123. package/src/entity/auth/PermissiveAdapter.ts +43 -0
  124. package/src/entity/auth/RoleHierarchy.ts +128 -0
  125. package/src/entity/auth/SecurityChecker.ts +202 -0
  126. package/src/entity/auth/factory.ts +203 -0
  127. package/src/entity/auth/index.ts +47 -0
  128. package/src/entity/factory.test.js +189 -0
  129. package/src/entity/factory.ts +146 -0
  130. package/src/entity/index.ts +49 -0
  131. package/src/entity/storage/ApiStorage.ts +228 -0
  132. package/src/entity/storage/IStorage.ts +100 -0
  133. package/src/entity/storage/LocalStorage.ts +200 -0
  134. package/src/entity/storage/MemoryStorage.ts +184 -0
  135. package/src/entity/storage/MockApiStorage.ts +318 -0
  136. package/src/entity/storage/SdkStorage.ts +377 -0
  137. package/src/entity/storage/factory.test.js +159 -0
  138. package/src/entity/storage/factory.ts +209 -0
  139. package/src/entity/storage/index.ts +75 -0
  140. package/src/gen/FieldMapper.ts +122 -0
  141. package/src/gen/StorageProfileFactory.ts +93 -0
  142. package/src/gen/connectors/BaseConnector.ts +150 -0
  143. package/src/gen/connectors/ManualConnector.test.js +499 -0
  144. package/src/gen/connectors/ManualConnector.ts +414 -0
  145. package/src/gen/connectors/OpenAPIConnector.test.js +836 -0
  146. package/src/gen/connectors/OpenAPIConnector.ts +693 -0
  147. package/src/gen/connectors/__fixtures__/sample-openapi.json +311 -0
  148. package/src/gen/connectors/index.ts +27 -0
  149. package/src/gen/createManagers.ts +245 -0
  150. package/src/gen/decorators.ts +134 -0
  151. package/src/gen/generateManagers.test.js +540 -0
  152. package/src/gen/generateManagers.ts +398 -0
  153. package/src/gen/index.ts +66 -0
  154. package/src/gen/schema.ts +248 -0
  155. package/src/gen/vite-plugin.ts +114 -0
  156. package/src/generated/managers/testManager.js +45 -0
  157. package/src/generated/managers/testManager.ts +46 -0
  158. package/src/hooks/HookRegistry.ts +16 -0
  159. package/src/hooks/index.ts +15 -0
  160. package/src/i18n/I18n.ts +344 -0
  161. package/src/i18n/IncrementalDomainProvider.ts +153 -0
  162. package/src/i18n/InlineTranslationProvider.ts +4 -0
  163. package/src/i18n/LazyTranslationProvider.ts +102 -0
  164. package/src/i18n/MessagesRegistry.ts +4 -0
  165. package/src/i18n/Resolver.ts +4 -0
  166. package/src/i18n/__tests__/I18n.test.ts +169 -0
  167. package/src/i18n/__tests__/IncrementalDomainProvider.test.ts +146 -0
  168. package/src/i18n/__tests__/LazyTranslationProvider.test.ts +100 -0
  169. package/src/i18n/__tests__/Resolver.test.ts +271 -0
  170. package/src/i18n/defaults/DefaultCoreProvider.ts +28 -0
  171. package/src/i18n/defaults/core.en.yml +55 -0
  172. package/src/i18n/defaults/core.fr.yml +55 -0
  173. package/src/i18n/index.ts +55 -0
  174. package/src/i18n/loaders/raw-modules.d.ts +15 -0
  175. package/src/i18n/loaders/yaml.ts +35 -0
  176. package/src/i18n/strategies.ts +4 -0
  177. package/src/i18n/types.ts +34 -0
  178. package/src/i18n/useI18n.ts +34 -0
  179. package/src/index.ts +453 -0
  180. package/src/kernel/EventRouter.ts +23 -0
  181. package/src/kernel/Kernel.api.ts +79 -0
  182. package/src/kernel/Kernel.auth.ts +120 -0
  183. package/src/kernel/Kernel.i18n.ts +29 -0
  184. package/src/kernel/Kernel.modules.ts +170 -0
  185. package/src/kernel/Kernel.registries.ts +237 -0
  186. package/src/kernel/Kernel.routing.ts +334 -0
  187. package/src/kernel/Kernel.ts +460 -0
  188. package/src/kernel/Kernel.types.ts +263 -0
  189. package/src/kernel/Kernel.vue.ts +304 -0
  190. package/src/kernel/KernelContext.entities.ts +80 -0
  191. package/src/kernel/KernelContext.events.ts +57 -0
  192. package/src/kernel/KernelContext.i18n.ts +37 -0
  193. package/src/kernel/KernelContext.permissions.ts +38 -0
  194. package/src/kernel/KernelContext.routing.ts +280 -0
  195. package/src/kernel/KernelContext.ts +252 -0
  196. package/src/kernel/KernelContext.types.ts +173 -0
  197. package/src/kernel/KernelContext.zones.ts +54 -0
  198. package/src/kernel/Module.ts +169 -0
  199. package/src/kernel/ModuleLoader.ts +645 -0
  200. package/src/kernel/SSEBridge.ts +11 -0
  201. package/src/kernel/SignalBus.ts +48 -0
  202. package/src/kernel/index.ts +56 -0
  203. package/src/module/index.ts +33 -0
  204. package/src/module/moduleRegistry.ts +492 -0
  205. package/src/modules/debug/AuthCollector.ts +501 -0
  206. package/src/modules/debug/Collector.ts +20 -0
  207. package/src/modules/debug/DebugBridge.ts +14 -0
  208. package/src/modules/debug/DebugModule.ts +220 -0
  209. package/src/modules/debug/EntitiesCollector.ts +839 -0
  210. package/src/modules/debug/ErrorCollector.ts +4 -0
  211. package/src/modules/debug/I18nCollector.ts +9 -0
  212. package/src/modules/debug/LocalStorageAdapter.ts +7 -0
  213. package/src/modules/debug/RouterCollector.ts +449 -0
  214. package/src/modules/debug/SignalCollector.ts +4 -0
  215. package/src/modules/debug/ToastCollector.ts +4 -0
  216. package/src/modules/debug/ZonesCollector.ts +447 -0
  217. package/src/modules/debug/components/DebugBar.vue +31 -0
  218. package/src/modules/debug/components/adminPanelsConfig.ts +42 -0
  219. package/src/modules/debug/components/index.ts +9 -0
  220. package/src/modules/debug/components/panels/AuthPanel.vue +267 -0
  221. package/src/modules/debug/components/panels/EntitiesPanel.vue +572 -0
  222. package/src/modules/debug/components/panels/I18nPanel.vue +738 -0
  223. package/src/modules/debug/components/panels/RouterPanel.vue +440 -0
  224. package/src/modules/debug/components/panels/ZonesPanel.vue +133 -0
  225. package/src/modules/debug/components/panels/index.ts +16 -0
  226. package/src/modules/debug/index.ts +58 -0
  227. package/src/modules/debug/styles.scss +2671 -0
  228. package/src/notifications/NotificationBadge.vue +35 -0
  229. package/src/notifications/NotificationListener.vue +71 -0
  230. package/src/notifications/NotificationModule.ts +76 -0
  231. package/src/notifications/NotificationPanel.vue +478 -0
  232. package/src/notifications/NotificationStore.ts +288 -0
  233. package/src/notifications/index.ts +42 -0
  234. package/src/notifications/styles.scss +32 -0
  235. package/src/orchestrator/Orchestrator.ts +427 -0
  236. package/src/orchestrator/index.ts +8 -0
  237. package/src/orchestrator/useOrchestrator.ts +80 -0
  238. package/src/plugin.ts +251 -0
  239. package/src/query/FilterQuery.ts +306 -0
  240. package/src/query/QueryExecutor.ts +341 -0
  241. package/src/query/index.ts +16 -0
  242. package/src/security/EntityRolesProvider.ts +385 -0
  243. package/src/security/PermissionMatcher.ts +152 -0
  244. package/src/security/PermissionRegistry.ts +273 -0
  245. package/src/security/PersistableRolesProvider.ts +584 -0
  246. package/src/security/RolesManager.ts +136 -0
  247. package/src/security/RolesProvider.ts +167 -0
  248. package/src/security/RolesProviderStorage.ts +211 -0
  249. package/src/security/SecurityModule.ts +94 -0
  250. package/src/security/StaticRolesProvider.ts +117 -0
  251. package/src/security/UsersManager.ts +163 -0
  252. package/src/security/index.ts +77 -0
  253. package/src/security/pages/RoleForm.vue +276 -0
  254. package/src/security/pages/RoleList.vue +123 -0
  255. package/src/styles/_alerts.scss +95 -0
  256. package/src/styles/_buttons.scss +391 -0
  257. package/src/styles/_cards.scss +241 -0
  258. package/src/styles/_code.scss +78 -0
  259. package/src/styles/_dialogs.scss +55 -0
  260. package/src/styles/_filter-bar.scss +70 -0
  261. package/src/styles/_forms.scss +434 -0
  262. package/src/styles/_helpers.scss +153 -0
  263. package/src/styles/_lists.scss +225 -0
  264. package/src/styles/_main.scss +356 -0
  265. package/src/styles/_markdown.scss +88 -0
  266. package/src/styles/_responsive.scss +255 -0
  267. package/src/styles/_show-pages.scss +186 -0
  268. package/src/styles/_sidebar.scss +380 -0
  269. package/src/styles/_states.scss +115 -0
  270. package/src/styles/_stats.scss +47 -0
  271. package/src/styles/_variables.scss +224 -0
  272. package/src/styles/index.scss +38 -0
  273. package/src/styles/theme/_components.scss +331 -0
  274. package/src/styles/theme/_index.scss +10 -0
  275. package/src/styles/theme/_tokens.scss +147 -0
  276. package/src/styles/theme/_utilities.scss +175 -0
  277. package/src/toast/ToastBridgeModule.ts +68 -0
  278. package/src/toast/ToastListener.vue +55 -0
  279. package/src/toast/index.ts +26 -0
  280. package/src/toast/useSignalToast.ts +154 -0
  281. package/src/types/index.ts +285 -0
  282. package/src/types/pluralize.d.ts +19 -0
  283. package/src/types/quarkernel.d.ts +50 -0
  284. package/src/utils/formatters.ts +174 -0
  285. package/src/utils/index.ts +35 -0
  286. package/src/utils/transformers.ts +131 -0
  287. package/src/vite/qdadmDebugPlugin.ts +401 -0
  288. package/src/vite-env.d.ts +50 -0
  289. package/src/zones/ZoneRegistry.ts +934 -0
  290. package/src/zones/index.ts +29 -0
  291. package/src/zones/zones.ts +191 -0
@@ -0,0 +1,222 @@
1
+ import { computed, type ComputedRef } from 'vue'
2
+ import { useRoute, useRouter, type RouteRecordNormalized, type RouteLocationNormalized } from 'vue-router'
3
+
4
+ /**
5
+ * Semantic breadcrumb item kinds
6
+ */
7
+ export type SemanticBreadcrumbKind =
8
+ | 'route'
9
+ | 'entity-list'
10
+ | 'entity-show'
11
+ | 'entity-edit'
12
+ | 'entity-create'
13
+ | 'entity-delete'
14
+
15
+ /**
16
+ * Semantic breadcrumb item
17
+ */
18
+ export interface SemanticBreadcrumbItem {
19
+ kind: SemanticBreadcrumbKind
20
+ entity?: string
21
+ id?: string
22
+ route?: string
23
+ label?: string
24
+ }
25
+
26
+ /**
27
+ * Action segments mapping to entity kinds
28
+ */
29
+ const ACTION_MAP: Record<string, SemanticBreadcrumbKind> = {
30
+ edit: 'entity-edit',
31
+ create: 'entity-create',
32
+ new: 'entity-create',
33
+ show: 'entity-show',
34
+ view: 'entity-show',
35
+ delete: 'entity-delete',
36
+ }
37
+
38
+ /**
39
+ * Compute semantic breadcrumb from route path and routes list
40
+ *
41
+ * Pure function that can be used both in Vue composables and outside components.
42
+ *
43
+ * If the matched route has `meta.breadcrumb`, that array is used directly.
44
+ * This allows routes to define a custom breadcrumb structure.
45
+ *
46
+ * @param path - Current route path (e.g., '/books/1/edit')
47
+ * @param routes - List of all registered routes from router.getRoutes()
48
+ * @param currentRoute - Current route object (optional, for meta.breadcrumb support)
49
+ * @returns Semantic breadcrumb items
50
+ */
51
+ export function computeSemanticBreadcrumb(
52
+ path: string,
53
+ routes: RouteRecordNormalized[],
54
+ currentRoute: RouteLocationNormalized | null = null
55
+ ): SemanticBreadcrumbItem[] {
56
+ if (!path) return []
57
+
58
+ // Check if route defines a custom breadcrumb
59
+ if (currentRoute?.meta?.breadcrumb) {
60
+ return currentRoute.meta.breadcrumb as SemanticBreadcrumbItem[]
61
+ }
62
+
63
+ const items: SemanticBreadcrumbItem[] = []
64
+
65
+ // Filter out catch-all and not-found routes
66
+ const validRoutes = routes.filter(
67
+ (r) => r.name !== 'not-found' && !r.path.includes('*') && !r.path.includes(':pathMatch')
68
+ )
69
+
70
+ // Note: Home is NOT included in semantic breadcrumb
71
+ // It's a display concern - add it in useBreadcrumb if needed
72
+
73
+ const segments = path.split('/').filter(Boolean)
74
+ let currentPath = ''
75
+ let lastEntity: string | null = null
76
+ let pendingId: string | null = null // Track unmatched segments that could be IDs
77
+
78
+ for (let i = 0; i < segments.length; i++) {
79
+ const segment = segments[i]
80
+ const nextSegment = segments[i + 1]
81
+ if (!segment) continue
82
+ currentPath += `/${segment}`
83
+
84
+ // Check if this is an action segment
85
+ const actionKind = ACTION_MAP[segment.toLowerCase()]
86
+ if (actionKind && lastEntity) {
87
+ // If we have a pending ID, add a new entity item with action kind
88
+ if (pendingId) {
89
+ items.push({ kind: actionKind, entity: lastEntity, id: pendingId })
90
+ pendingId = null
91
+ } else {
92
+ // No pending ID - update last item's kind (e.g., /books/create)
93
+ const lastItem = items[items.length - 1]
94
+ if (lastItem && lastItem.kind.startsWith('entity-') && !lastItem.id) {
95
+ lastItem.kind = actionKind
96
+ }
97
+ }
98
+ continue
99
+ }
100
+
101
+ // Find matching route
102
+ const matchedRoute = validRoutes.find((r) => {
103
+ if (r.path === currentPath) return true
104
+ const routeSegs = r.path.split('/').filter(Boolean)
105
+ const pathSegs = currentPath.split('/').filter(Boolean)
106
+ if (routeSegs.length !== pathSegs.length) return false
107
+ return routeSegs.every((rs, idx) => rs.startsWith(':') || rs === pathSegs[idx])
108
+ })
109
+
110
+ if (!matchedRoute) {
111
+ // No route found - this segment could be an ID (e.g., /books/1 when route is /books/:id/edit)
112
+ if (lastEntity && segment) {
113
+ pendingId = segment
114
+ }
115
+ continue
116
+ }
117
+
118
+ const entity = matchedRoute.meta?.entity as string | null
119
+ const isParam = matchedRoute.path
120
+ .split('/')
121
+ .filter(Boolean)
122
+ .some((s, idx) => {
123
+ const pathSegs = currentPath.split('/').filter(Boolean)
124
+ return s.startsWith(':') && idx === pathSegs.length - 1
125
+ })
126
+
127
+ if (entity) {
128
+ lastEntity = entity
129
+ pendingId = null // Clear pending ID when we match a route
130
+ if (isParam) {
131
+ // Entity instance - get param value (the ID)
132
+ const paramValue = segment
133
+ // Determine kind based on next segment or route name
134
+ let kind: SemanticBreadcrumbKind = 'entity-show'
135
+ const nextAction = nextSegment ? ACTION_MAP[nextSegment.toLowerCase()] : undefined
136
+ if (nextAction) {
137
+ kind = nextAction
138
+ } else if ((matchedRoute.name as string)?.includes('edit')) {
139
+ kind = 'entity-edit'
140
+ }
141
+ items.push({ kind, entity, id: paramValue, route: matchedRoute.name as string })
142
+ } else {
143
+ // Entity list - include route name for child routes (e.g., 'bot-command' vs 'commands')
144
+ items.push({ kind: 'entity-list', entity, route: matchedRoute.name as string })
145
+ }
146
+ } else {
147
+ // Flush pending ID as entity-show before generic route
148
+ if (pendingId && lastEntity) {
149
+ items.push({ kind: 'entity-show', entity: lastEntity, id: pendingId })
150
+ pendingId = null
151
+ }
152
+ // Generic route
153
+ items.push({ kind: 'route', route: (matchedRoute.name as string) || segment })
154
+ }
155
+ }
156
+
157
+ return items
158
+ }
159
+
160
+ /**
161
+ * Return type for useSemanticBreadcrumb
162
+ */
163
+ export interface UseSemanticBreadcrumbReturn {
164
+ breadcrumb: ComputedRef<SemanticBreadcrumbItem[]>
165
+ isEntityActive: (entity: string) => boolean
166
+ activeEntity: ComputedRef<string | null>
167
+ }
168
+
169
+ /**
170
+ * useSemanticBreadcrumb - Vue composable for semantic breadcrumb
171
+ *
172
+ * Returns semantic objects per level - adapters resolve labels/paths for display.
173
+ *
174
+ * Kinds:
175
+ * - route: Generic route (e.g., home, dashboard)
176
+ * - entity-list: Entity collection (e.g., /books)
177
+ * - entity-show: Entity instance view (e.g., /books/1)
178
+ * - entity-edit: Entity instance edit (e.g., /books/1/edit)
179
+ * - entity-create: Entity creation (e.g., /books/create)
180
+ *
181
+ * @example
182
+ * const { breadcrumb } = useSemanticBreadcrumb()
183
+ * // For /books/1/edit returns:
184
+ * // [
185
+ * // { kind: 'route', route: 'home' },
186
+ * // { kind: 'entity-list', entity: 'books' },
187
+ * // { kind: 'entity-edit', entity: 'books', id: '1' }
188
+ * // ]
189
+ */
190
+ export function useSemanticBreadcrumb(): UseSemanticBreadcrumbReturn {
191
+ const route = useRoute()
192
+ const router = useRouter()
193
+
194
+ const breadcrumb = computed(() => {
195
+ return computeSemanticBreadcrumb(route.path, router.getRoutes(), route)
196
+ })
197
+
198
+ /**
199
+ * Check if an entity is active (present in current breadcrumb)
200
+ * Useful for menu highlighting
201
+ *
202
+ * @param entity - Entity name to check
203
+ * @returns True if entity is in breadcrumb
204
+ */
205
+ function isEntityActive(entity: string): boolean {
206
+ return breadcrumb.value.some((item) => item.kind.startsWith('entity-') && item.entity === entity)
207
+ }
208
+
209
+ /**
210
+ * Get the active entity from breadcrumb (first entity found)
211
+ */
212
+ const activeEntity = computed(() => {
213
+ const entityItem = breadcrumb.value.find((item) => item.kind.startsWith('entity-'))
214
+ return entityItem?.entity || null
215
+ })
216
+
217
+ return {
218
+ breadcrumb,
219
+ isEntityActive,
220
+ activeEntity,
221
+ }
222
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * useSidebarState - Access sidebar collapsed state from any component
3
+ *
4
+ * The sidebar collapsed ref is provided by AppLayout via Vue's provide/inject.
5
+ * This composable gives blocks rendered inside the sidebar (including zone blocks)
6
+ * programmatic access to the collapsed state.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { useSidebarState } from '@quazardous/qdadm'
11
+ *
12
+ * const { collapsed } = useSidebarState()
13
+ * // collapsed.value === true when sidebar is in icon-only mode
14
+ * ```
15
+ */
16
+
17
+ import { inject, ref, type Ref } from 'vue'
18
+
19
+ export const SIDEBAR_COLLAPSED_KEY = Symbol('qdadm-sidebar-collapsed') as symbol & { __type: Ref<boolean> }
20
+
21
+ export function useSidebarState(): { collapsed: Ref<boolean> } {
22
+ const collapsed = inject<Ref<boolean>>(SIDEBAR_COLLAPSED_KEY, ref(false))
23
+ return { collapsed }
24
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * useSignals - Access the signal bus for event-driven communication
3
+ *
4
+ * Provides access to the SignalBus created by Kernel during bootstrap.
5
+ * Components can subscribe to signals without direct imports.
6
+ *
7
+ * @returns The signal bus instance or null if not available
8
+ *
9
+ * @example
10
+ * // Basic usage
11
+ * const signals = useSignals()
12
+ * signals.on('entity:created', (payload) => {
13
+ * console.log('Entity created:', payload.entity, payload.data)
14
+ * })
15
+ *
16
+ * @example
17
+ * // Subscribe to specific entity
18
+ * const signals = useSignals()
19
+ * signals.on('books:updated', ({ data }) => {
20
+ * refreshBookList()
21
+ * })
22
+ *
23
+ * @example
24
+ * // Wildcard subscription
25
+ * const signals = useSignals()
26
+ * signals.on('*:deleted', ({ entity, data }) => {
27
+ * showDeletedNotification(entity)
28
+ * })
29
+ *
30
+ * @example
31
+ * // Auto-cleanup on unmount
32
+ * import { onUnmounted } from 'vue'
33
+ *
34
+ * const signals = useSignals()
35
+ * const unbind = signals.on('entity:created', handler)
36
+ * onUnmounted(() => unbind())
37
+ */
38
+ import { inject } from 'vue'
39
+ import type { SignalBus } from '../kernel/SignalBus'
40
+
41
+ export function useSignals(): SignalBus | null {
42
+ const signals = inject<SignalBus | null>('qdadmSignals', null)
43
+
44
+ if (!signals) {
45
+ console.warn(
46
+ '[qdadm] useSignals: signal bus not available. Ensure Kernel is initialized.'
47
+ )
48
+ return null
49
+ }
50
+
51
+ return signals
52
+ }
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Composable for guarding against unsaved changes when leaving a page
3
+ *
4
+ * Usage:
5
+ * const { dirty, takeSnapshot, checkDirty } = useDirtyState(...)
6
+ * useUnsavedChangesGuard(dirty, {
7
+ * onSave: () => handleSubmit(false),
8
+ * message: 'You have unsaved changes.'
9
+ * })
10
+ */
11
+
12
+ import { onBeforeUnmount, ref, type Ref } from 'vue'
13
+ import { onBeforeRouteLeave, type NavigationGuardNext } from 'vue-router'
14
+
15
+ /**
16
+ * Guard dialog state
17
+ */
18
+ export interface GuardDialogState {
19
+ visible: Ref<boolean>
20
+ saving: Ref<boolean>
21
+ message: string
22
+ hasOnSave: boolean
23
+ onSaveAndLeave: () => Promise<void>
24
+ onLeave: () => void
25
+ onStay: () => void
26
+ }
27
+
28
+ /**
29
+ * Options for useUnsavedChangesGuard
30
+ */
31
+ export interface UseUnsavedChangesGuardOptions {
32
+ /** Async function to save changes */
33
+ onSave?: (() => Promise<void>) | null
34
+ /** Custom message */
35
+ message?: string
36
+ /** Show native browser dialog on tab close (default: false) */
37
+ browserGuard?: boolean
38
+ }
39
+
40
+ /**
41
+ * Return type for useUnsavedChangesGuard
42
+ */
43
+ export interface UseUnsavedChangesGuardReturn {
44
+ dirty: Ref<boolean>
45
+ guardDialog: GuardDialogState
46
+ }
47
+
48
+ /**
49
+ * Guard against leaving page with unsaved changes
50
+ *
51
+ * Shows a dialog with up to 3 options:
52
+ * - "Save & Leave" (if onSave provided) - saves then navigates
53
+ * - "Leave" - navigates without saving (discards changes)
54
+ * - "Stay" - cancels navigation
55
+ *
56
+ * @param dirty - Reactive dirty state
57
+ * @param options - Options
58
+ */
59
+ export function useUnsavedChangesGuard(
60
+ dirty: Ref<boolean>,
61
+ options: UseUnsavedChangesGuardOptions = {}
62
+ ): UseUnsavedChangesGuardReturn {
63
+ const {
64
+ onSave = null,
65
+ message = 'You have unsaved changes that will be lost.',
66
+ browserGuard = false, // Disabled by default - use soft JS modal only
67
+ } = options
68
+
69
+ // State for custom dialog
70
+ const showDialog = ref(false)
71
+ const pendingNext = ref<NavigationGuardNext | null>(null)
72
+ const saving = ref(false)
73
+
74
+ // Browser beforeunload event (for closing tab/window) - optional
75
+ function handleBeforeUnload(e: BeforeUnloadEvent): string | void {
76
+ if (dirty.value) {
77
+ e.preventDefault()
78
+ e.returnValue = message
79
+ return message
80
+ }
81
+ }
82
+
83
+ // Add browser event listener only if browserGuard is enabled
84
+ if (browserGuard && typeof window !== 'undefined') {
85
+ window.addEventListener('beforeunload', handleBeforeUnload)
86
+ }
87
+
88
+ // Clean up on unmount
89
+ onBeforeUnmount(() => {
90
+ if (browserGuard && typeof window !== 'undefined') {
91
+ window.removeEventListener('beforeunload', handleBeforeUnload)
92
+ }
93
+ })
94
+
95
+ // Dialog actions
96
+ async function handleSaveAndLeave(): Promise<void> {
97
+ if (!onSave || !pendingNext.value) return
98
+
99
+ saving.value = true
100
+ try {
101
+ await onSave()
102
+ showDialog.value = false
103
+ pendingNext.value()
104
+ pendingNext.value = null
105
+ } catch (e) {
106
+ console.error('Save failed:', e)
107
+ // Stay on page - don't close dialog, let user retry or choose Leave
108
+ } finally {
109
+ saving.value = false
110
+ }
111
+ }
112
+
113
+ function handleLeave(): void {
114
+ if (!pendingNext.value) return
115
+ showDialog.value = false
116
+ pendingNext.value()
117
+ pendingNext.value = null
118
+ }
119
+
120
+ function handleStay(): void {
121
+ showDialog.value = false
122
+ if (pendingNext.value) {
123
+ pendingNext.value(false)
124
+ pendingNext.value = null
125
+ }
126
+ }
127
+
128
+ // Vue Router navigation guard
129
+ onBeforeRouteLeave((_to, _from, next) => {
130
+ if (!dirty.value) {
131
+ next()
132
+ return
133
+ }
134
+
135
+ // Store the next callback and show dialog
136
+ pendingNext.value = next
137
+ showDialog.value = true
138
+ })
139
+
140
+ return {
141
+ dirty,
142
+ // Dialog state and handlers for custom dialog component
143
+ guardDialog: {
144
+ visible: showDialog,
145
+ saving,
146
+ message,
147
+ hasOnSave: !!onSave,
148
+ onSaveAndLeave: handleSaveAndLeave,
149
+ onLeave: handleLeave,
150
+ onStay: handleStay,
151
+ },
152
+ }
153
+ }
154
+
155
+ export default useUnsavedChangesGuard