@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,120 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * FormTabs - Normalized tab container for forms
4
+ *
5
+ * @deprecated Use FieldGroups with layout="tabs" instead. FieldGroups now supports
6
+ * icon, badge, count, visible, and disabled options on groups.
7
+ *
8
+ * ```vue
9
+ * <!-- Before (deprecated) -->
10
+ * <FormTabs v-model="activeTab">
11
+ * <template #tabs>
12
+ * <FormTab value="general" label="General" icon="pi-cog" />
13
+ * </template>
14
+ * <template #panels>
15
+ * <TabPanel value="general">...</TabPanel>
16
+ * </template>
17
+ * </FormTabs>
18
+ *
19
+ * <!-- After (recommended) -->
20
+ * form.group('general', ['field1', 'field2'], { label: 'General', icon: 'cog' })
21
+ * <FieldGroups :groups="form.groups.value" layout="tabs">
22
+ * <template #field="{ field }">
23
+ * <FormField :field="field" />
24
+ * </template>
25
+ * </FieldGroups>
26
+ * ```
27
+ */
28
+
29
+ import Tabs from 'primevue/tabs'
30
+ import TabList from 'primevue/tablist'
31
+ import TabPanels from 'primevue/tabpanels'
32
+
33
+ defineProps({
34
+ /**
35
+ * Active tab value (v-model)
36
+ */
37
+ modelValue: {
38
+ type: String,
39
+ required: true
40
+ }
41
+ })
42
+
43
+ const emit = defineEmits<{
44
+ 'update:modelValue': [value: string]
45
+ }>()
46
+
47
+ function onTabChange(value: string | number): void {
48
+ emit('update:modelValue', String(value))
49
+ }
50
+ </script>
51
+
52
+ <template>
53
+ <div class="form-tabs">
54
+ <Tabs :value="modelValue" @update:value="onTabChange">
55
+ <TabList class="form-tabs__list">
56
+ <slot name="tabs" ></slot>
57
+ </TabList>
58
+ <TabPanels class="form-tabs__panels">
59
+ <slot name="panels" ></slot>
60
+ </TabPanels>
61
+ </Tabs>
62
+ </div>
63
+ </template>
64
+
65
+ <style scoped>
66
+ /*
67
+ * Only keep styles here that REQUIRE scoping (:deep, dynamic binding, component-specific overrides).
68
+ * Generic/reusable styles belong in src/styles/ partials (see _forms.scss, _cards.scss, etc.).
69
+ */
70
+ .form-tabs {
71
+ margin-top: 1rem;
72
+ }
73
+
74
+ .form-tabs :deep(.form-tabs__list) {
75
+ border-bottom: 1px solid var(--p-surface-200);
76
+ gap: 0;
77
+ }
78
+
79
+ .form-tabs :deep(.form-tabs__panels) {
80
+ padding: 1.5rem 0;
81
+ }
82
+
83
+ /* Tab styling */
84
+ .form-tabs :deep(.p-tab) {
85
+ padding: 0.75rem 1.25rem;
86
+ border-radius: 0;
87
+ border-bottom: 2px solid transparent;
88
+ margin-bottom: -1px;
89
+ transition: all 0.2s ease;
90
+ }
91
+
92
+ .form-tabs :deep(.p-tab:hover) {
93
+ background: var(--p-surface-50);
94
+ }
95
+
96
+ .form-tabs :deep(.p-tab[data-p-active="true"]) {
97
+ border-bottom-color: var(--p-primary-500);
98
+ color: var(--p-primary-500);
99
+ }
100
+
101
+ /* Tab icon alignment */
102
+ .form-tabs :deep(.p-tab .pi) {
103
+ margin-right: 0.5rem;
104
+ }
105
+
106
+ /* Tab badge/count styling */
107
+ .form-tabs :deep(.tab-badge) {
108
+ margin-left: 0.5rem;
109
+ font-size: 0.7rem;
110
+ padding: 0.15rem 0.4rem;
111
+ min-width: 1.25rem;
112
+ text-align: center;
113
+ }
114
+
115
+ /* Disabled tab */
116
+ .form-tabs :deep(.p-tab[data-p-disabled="true"]) {
117
+ opacity: 0.5;
118
+ cursor: not-allowed;
119
+ }
120
+ </style>
@@ -0,0 +1,406 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * LookupField - Unified lookup input with two UI modes
4
+ *
5
+ * Wraps useOptionsLookup into a ready-to-use form field component.
6
+ *
7
+ * Modes (pickerMode):
8
+ * - **inline** (default): PrimeVue AutoComplete with dropdown button.
9
+ * - **picker**: Readonly input + search button → modal DataTable.
10
+ *
11
+ * Supports both single and multiple selection (multiple prop).
12
+ *
13
+ * Usage:
14
+ * ```vue
15
+ * <!-- Single inline -->
16
+ * <LookupField v-model="form.data.value.bookId" :lookup="bookLookup" />
17
+ *
18
+ * <!-- Single picker -->
19
+ * <LookupField v-model="bookId" :lookup="bookLookup" pickerMode="picker"
20
+ * :pickerColumns="['title', 'author']" pickerTitle="Select a Book" />
21
+ *
22
+ * <!-- Multi inline (chips) -->
23
+ * <LookupField v-model="conditions.tags" :lookup="tagLookup" multiple />
24
+ *
25
+ * <!-- Multi picker (checkboxes) -->
26
+ * <LookupField v-model="conditions.geoCountry" :lookup="geoLookup"
27
+ * multiple pickerMode="picker" :pickerColumns="['code', 'name']" />
28
+ * ```
29
+ */
30
+ import { ref, computed, watch, type PropType } from 'vue'
31
+ import AutoComplete from 'primevue/autocomplete'
32
+ import InputText from 'primevue/inputtext'
33
+ import InputGroup from 'primevue/inputgroup'
34
+ import InputGroupAddon from 'primevue/inputgroupaddon'
35
+ import Chip from 'primevue/chip'
36
+ import LookupPickerDialog from './LookupPickerDialog.vue'
37
+ import type { UseOptionsLookupReturn } from '../../composables/useOptionsLookup'
38
+ import type { LookupColumn } from './LookupPickerDialog.vue'
39
+
40
+ type PickerMode = 'inline' | 'picker'
41
+
42
+ const props = defineProps({
43
+ /** v-model: raw value (single) or array of raw values (multiple) */
44
+ modelValue: { type: [String, Number, Array, null] as PropType<unknown>, default: null },
45
+ /** useOptionsLookup return object */
46
+ lookup: { type: Object as PropType<UseOptionsLookupReturn>, required: true },
47
+ /** UI mode */
48
+ pickerMode: { type: String as PropType<PickerMode>, default: 'inline' },
49
+ /** Allow multiple selection */
50
+ multiple: { type: Boolean, default: false },
51
+ /** Columns shown in picker dialog (picker mode only) */
52
+ pickerColumns: { type: Array as PropType<(string | LookupColumn)[]>, default: () => [] },
53
+ /** Dialog title (picker mode only) */
54
+ pickerTitle: { type: String, default: 'Select item' },
55
+ /** Dialog width (picker mode only) */
56
+ pickerWidth: { type: String, default: '700px' },
57
+ /** Placeholder text */
58
+ placeholder: { type: String, default: '' },
59
+ /** Disabled state */
60
+ disabled: { type: Boolean, default: false },
61
+ /** CSS class for the root element */
62
+ class: { type: String, default: '' },
63
+ })
64
+
65
+ const emit = defineEmits<{
66
+ 'update:modelValue': [value: unknown]
67
+ }>()
68
+
69
+ // ═══════════════════════════════════════════════════════
70
+ // SINGLE MODE
71
+ // ═══════════════════════════════════════════════════════
72
+
73
+ // Display string for single inline autocomplete
74
+ const displayValue = ref('')
75
+
76
+ watch(() => props.modelValue, (val) => {
77
+ if (props.multiple) return
78
+ if (val != null && val !== '') {
79
+ displayValue.value = props.lookup.resolve(val)
80
+ } else {
81
+ displayValue.value = ''
82
+ }
83
+ }, { immediate: true })
84
+
85
+ watch(() => props.lookup.options.value.length, () => {
86
+ if (props.multiple) return
87
+ if (props.modelValue != null && props.modelValue !== '') {
88
+ displayValue.value = props.lookup.resolve(props.modelValue)
89
+ }
90
+ })
91
+
92
+ function onInlineSelect(_event: { value: string }): void {
93
+ const raw = props.lookup.decode(displayValue.value)
94
+ emit('update:modelValue', raw)
95
+ }
96
+
97
+ function onInlineBlur(): void {
98
+ if (displayValue.value) {
99
+ emit('update:modelValue', props.lookup.decode(displayValue.value))
100
+ } else {
101
+ emit('update:modelValue', null)
102
+ }
103
+ }
104
+
105
+ // Single picker display
106
+ const pickerDisplayLabel = computed(() => {
107
+ if (props.multiple) return ''
108
+ if (props.modelValue == null || props.modelValue === '') return ''
109
+ return props.lookup.resolve(props.modelValue)
110
+ })
111
+
112
+ function onPickerSelect(item: Record<string, unknown>): void {
113
+ const vf = guessValueField()
114
+ emit('update:modelValue', item[vf] ?? item.value ?? item.id)
115
+ }
116
+
117
+ // ═══════════════════════════════════════════════════════
118
+ // MULTI MODE
119
+ // ═══════════════════════════════════════════════════════
120
+
121
+ const multiValues = computed(() => {
122
+ if (!props.multiple) return []
123
+ return Array.isArray(props.modelValue) ? props.modelValue : []
124
+ })
125
+
126
+ // Inline multi: chips as encoded strings
127
+ const multiChips = ref<string[]>([])
128
+
129
+ watch(() => props.modelValue, (val) => {
130
+ if (!props.multiple) return
131
+ const arr = Array.isArray(val) ? val : []
132
+ multiChips.value = arr.map((v) => props.lookup.resolve(v))
133
+ }, { immediate: true })
134
+
135
+ watch(() => props.lookup.options.value.length, () => {
136
+ if (!props.multiple) return
137
+ const arr = Array.isArray(props.modelValue) ? props.modelValue : []
138
+ if (arr.length > 0) {
139
+ multiChips.value = arr.map((v) => props.lookup.resolve(v))
140
+ }
141
+ })
142
+
143
+ function onMultiChipsUpdate(chips: string[]): void {
144
+ emit('update:modelValue', chips.map((c) => props.lookup.decode(c)))
145
+ }
146
+
147
+ function onMultiInlineSelect(): void {
148
+ emit('update:modelValue', multiChips.value.map((c) => props.lookup.decode(c)))
149
+ }
150
+
151
+ // Picker multi: chips display
152
+ const multiPickerChips = computed(() =>
153
+ multiValues.value.map((v) => ({ raw: v, label: props.lookup.resolve(v) })),
154
+ )
155
+
156
+ function removeMultiChip(rawValue: unknown): void {
157
+ const updated = multiValues.value.filter((v) => String(v) !== String(rawValue))
158
+ emit('update:modelValue', updated)
159
+ }
160
+
161
+ function onPickerSelectMultiple(items: Record<string, unknown>[]): void {
162
+ const vf = guessValueField()
163
+ emit('update:modelValue', items.map((item) => item[vf] ?? item.value ?? item.id))
164
+ }
165
+
166
+ // ═══════════════════════════════════════════════════════
167
+ // SHARED
168
+ // ═══════════════════════════════════════════════════════
169
+
170
+ const pickerVisible = ref(false)
171
+
172
+ const resolvedPickerColumns = computed(() => {
173
+ if (props.pickerColumns.length > 0) return props.pickerColumns
174
+ const firstRaw = props.lookup.raw.value[0]
175
+ if (firstRaw && typeof firstRaw === 'object') {
176
+ return Object.keys(firstRaw as Record<string, unknown>).slice(0, 4)
177
+ }
178
+ return ['name', 'id']
179
+ })
180
+
181
+ function guessValueField(): string {
182
+ const firstOpt = props.lookup.options.value[0]
183
+ if (!firstOpt) return 'id'
184
+ const firstRaw = props.lookup.raw.value[0] as Record<string, unknown> | undefined
185
+ if (firstRaw) {
186
+ for (const [key, val] of Object.entries(firstRaw)) {
187
+ if (val === firstOpt.value) return key
188
+ }
189
+ }
190
+ return 'id'
191
+ }
192
+
193
+ function openPicker(): void {
194
+ if (!props.disabled) pickerVisible.value = true
195
+ }
196
+
197
+ const hasValue = computed(() => {
198
+ if (props.multiple) return multiValues.value.length > 0
199
+ return props.modelValue != null && props.modelValue !== ''
200
+ })
201
+
202
+ function clearValue(): void {
203
+ emit('update:modelValue', props.multiple ? [] : null)
204
+ displayValue.value = ''
205
+ multiChips.value = []
206
+ }
207
+ </script>
208
+
209
+ <template>
210
+ <!-- ════════════ SINGLE INLINE ════════════ -->
211
+ <span v-if="!multiple && pickerMode === 'inline'" class="lookup-field-inline" :class="props.class || 'w-full'">
212
+ <AutoComplete
213
+ v-model="displayValue"
214
+ :suggestions="lookup.suggestions.value"
215
+ @complete="lookup.search($event.query)"
216
+ @item-select="onInlineSelect"
217
+ @blur="onInlineBlur"
218
+ :loading="lookup.loading.value"
219
+ :disabled="disabled"
220
+ :placeholder="placeholder"
221
+ dropdown
222
+ class="w-full"
223
+ />
224
+ <button
225
+ v-if="hasValue && !disabled"
226
+ type="button"
227
+ class="lookup-clear-btn lookup-clear-btn--inline"
228
+ @click="clearValue"
229
+ tabindex="-1"
230
+ >
231
+ <i class="pi pi-times" />
232
+ </button>
233
+ </span>
234
+
235
+ <!-- ════════════ SINGLE PICKER ════════════ -->
236
+ <span v-else-if="!multiple && pickerMode === 'picker'" :class="props.class || 'w-full'" class="lookup-field-picker">
237
+ <InputGroup>
238
+ <InputText
239
+ :modelValue="pickerDisplayLabel"
240
+ readonly
241
+ :placeholder="placeholder || 'Click to select...'"
242
+ :disabled="disabled"
243
+ @click="openPicker"
244
+ style="cursor: pointer"
245
+ />
246
+ <InputGroupAddon class="lookup-search-addon" @click="openPicker">
247
+ <i v-if="lookup.loading.value" class="pi pi-spinner pi-spin" />
248
+ <i v-else class="pi pi-search" />
249
+ </InputGroupAddon>
250
+ </InputGroup>
251
+ <button
252
+ v-if="hasValue && !disabled"
253
+ type="button"
254
+ class="lookup-clear-btn lookup-clear-btn--picker"
255
+ @click.stop="clearValue"
256
+ tabindex="-1"
257
+ >
258
+ <i class="pi pi-times" />
259
+ </button>
260
+
261
+ <LookupPickerDialog
262
+ v-model:visible="pickerVisible"
263
+ :title="pickerTitle"
264
+ :items="(lookup.raw.value as Record<string, unknown>[])"
265
+ :columns="resolvedPickerColumns"
266
+ :loading="lookup.loading.value"
267
+ :currentValue="modelValue"
268
+ :valueField="guessValueField()"
269
+ :width="pickerWidth"
270
+ @select="onPickerSelect"
271
+ />
272
+ </span>
273
+
274
+ <!-- ════════════ MULTI INLINE (chips autocomplete) ════════════ -->
275
+ <span v-else-if="multiple && pickerMode === 'inline'" class="lookup-field-inline" :class="props.class || 'w-full'">
276
+ <AutoComplete
277
+ v-model="multiChips"
278
+ :suggestions="lookup.suggestions.value"
279
+ @complete="lookup.search($event.query)"
280
+ @item-select="onMultiInlineSelect"
281
+ @update:modelValue="onMultiChipsUpdate"
282
+ :loading="lookup.loading.value"
283
+ :disabled="disabled"
284
+ :placeholder="!hasValue ? placeholder : ''"
285
+ multiple
286
+ class="w-full"
287
+ />
288
+ <button
289
+ v-if="hasValue && !disabled"
290
+ type="button"
291
+ class="lookup-clear-btn lookup-clear-btn--multi-inline"
292
+ @click="clearValue"
293
+ tabindex="-1"
294
+ >
295
+ <i class="pi pi-times" />
296
+ </button>
297
+ </span>
298
+
299
+ <!-- ════════════ MULTI PICKER (chips + search dialog) ════════════ -->
300
+ <span v-else :class="props.class || 'w-full'" class="lookup-field-picker">
301
+ <InputGroup>
302
+ <div class="lookup-multi-chips" @click="openPicker">
303
+ <template v-if="multiPickerChips.length > 0">
304
+ <Chip
305
+ v-for="chip in multiPickerChips"
306
+ :key="String(chip.raw)"
307
+ :label="chip.label"
308
+ removable
309
+ @remove="removeMultiChip(chip.raw)"
310
+ @click.stop
311
+ />
312
+ </template>
313
+ <span v-else class="lookup-multi-placeholder">
314
+ {{ placeholder || 'Click search to select...' }}
315
+ </span>
316
+ </div>
317
+ <InputGroupAddon class="lookup-search-addon" @click="openPicker">
318
+ <i v-if="lookup.loading.value" class="pi pi-spinner pi-spin" />
319
+ <i v-else class="pi pi-search" />
320
+ </InputGroupAddon>
321
+ </InputGroup>
322
+
323
+ <LookupPickerDialog
324
+ v-model:visible="pickerVisible"
325
+ :title="pickerTitle"
326
+ :items="(lookup.raw.value as Record<string, unknown>[])"
327
+ :columns="resolvedPickerColumns"
328
+ :loading="lookup.loading.value"
329
+ :currentValue="modelValue"
330
+ :valueField="guessValueField()"
331
+ :width="pickerWidth"
332
+ multiple
333
+ @select-multiple="onPickerSelectMultiple"
334
+ />
335
+ </span>
336
+ </template>
337
+
338
+ <style scoped>
339
+ .lookup-field-inline,
340
+ .lookup-field-picker {
341
+ position: relative;
342
+ display: inline-flex;
343
+ }
344
+ .lookup-clear-btn {
345
+ position: absolute;
346
+ top: 50%;
347
+ transform: translateY(-50%);
348
+ z-index: 1;
349
+ background: none;
350
+ border: none;
351
+ cursor: pointer;
352
+ color: var(--p-text-muted-color);
353
+ padding: 0.25rem;
354
+ display: flex;
355
+ align-items: center;
356
+ opacity: 0.6;
357
+ transition: opacity 0.15s;
358
+ }
359
+ .lookup-clear-btn:hover {
360
+ opacity: 1;
361
+ color: var(--p-text-color);
362
+ }
363
+ /* Single inline: before the dropdown arrow button */
364
+ .lookup-clear-btn--inline {
365
+ right: 2.5rem;
366
+ }
367
+ /* Single picker: before the search addon */
368
+ .lookup-clear-btn--picker {
369
+ right: 2.75rem;
370
+ }
371
+ /* Multi inline: top-right corner */
372
+ .lookup-clear-btn--multi-inline {
373
+ right: 0.5rem;
374
+ top: 0.25rem;
375
+ transform: none;
376
+ }
377
+ /* Chips container for multi picker */
378
+ .lookup-multi-chips {
379
+ flex: 1 1 auto;
380
+ display: flex;
381
+ flex-wrap: wrap;
382
+ gap: 0.25rem;
383
+ align-items: center;
384
+ padding: 0.375rem 0.5rem;
385
+ min-height: 2.5rem;
386
+ cursor: pointer;
387
+ border: 1px solid var(--p-inputtext-border-color);
388
+ border-right: none;
389
+ border-radius: var(--p-inputtext-border-radius) 0 0 var(--p-inputtext-border-radius);
390
+ background: var(--p-inputtext-background);
391
+ transition: border-color 0.15s;
392
+ }
393
+ .lookup-multi-chips:hover {
394
+ border-color: var(--p-inputtext-hover-border-color);
395
+ }
396
+ .lookup-multi-placeholder {
397
+ color: var(--p-inputtext-placeholder-color);
398
+ font-size: 0.875rem;
399
+ }
400
+ .lookup-search-addon {
401
+ cursor: pointer;
402
+ }
403
+ .lookup-search-addon:hover {
404
+ background-color: var(--p-surface-100);
405
+ }
406
+ </style>