@ngbplatform/ui 1.3.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 (217) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +44 -0
  3. package/package.json +61 -0
  4. package/public/favicon.svg +6 -0
  5. package/public/silent-check-sso.html +8 -0
  6. package/src/assets/ngb_logo.svg +17 -0
  7. package/src/assets/ngb_logo_color.svg +17 -0
  8. package/src/index.ts +1023 -0
  9. package/src/ngb/README.md +27 -0
  10. package/src/ngb/accounting/NgbAccountingPeriodClosingPage.vue +1157 -0
  11. package/src/ngb/accounting/NgbChartOfAccountEditor.vue +590 -0
  12. package/src/ngb/accounting/NgbChartOfAccountsPage.vue +420 -0
  13. package/src/ngb/accounting/NgbGeneralJournalEntryEditPage.vue +796 -0
  14. package/src/ngb/accounting/NgbGeneralJournalEntryLinesEditor.vue +454 -0
  15. package/src/ngb/accounting/NgbGeneralJournalEntryListPage.vue +186 -0
  16. package/src/ngb/accounting/api.ts +79 -0
  17. package/src/ngb/accounting/generalJournalEntry.ts +127 -0
  18. package/src/ngb/accounting/generalJournalEntryApi.ts +112 -0
  19. package/src/ngb/accounting/generalJournalEntryTypes.ts +155 -0
  20. package/src/ngb/accounting/navigation.ts +83 -0
  21. package/src/ngb/accounting/periodClosing.ts +75 -0
  22. package/src/ngb/accounting/periodClosingApi.ts +60 -0
  23. package/src/ngb/accounting/periodClosingTypes.ts +73 -0
  24. package/src/ngb/accounting/types.ts +57 -0
  25. package/src/ngb/api/audit.ts +23 -0
  26. package/src/ngb/api/catalogs.ts +50 -0
  27. package/src/ngb/api/contracts.ts +285 -0
  28. package/src/ngb/api/documents.ts +170 -0
  29. package/src/ngb/api/http.ts +446 -0
  30. package/src/ngb/api/lookups.ts +14 -0
  31. package/src/ngb/api/types.ts +6 -0
  32. package/src/ngb/auth/index.ts +13 -0
  33. package/src/ngb/auth/keycloak.ts +374 -0
  34. package/src/ngb/auth/router.ts +35 -0
  35. package/src/ngb/auth/types.ts +12 -0
  36. package/src/ngb/auth/useAuthStore.ts +209 -0
  37. package/src/ngb/command-palette/NgbCommandPaletteDialog.vue +342 -0
  38. package/src/ngb/command-palette/NgbCommandPaletteEmptyState.vue +55 -0
  39. package/src/ngb/command-palette/NgbCommandPaletteFooterHints.vue +29 -0
  40. package/src/ngb/command-palette/api.ts +10 -0
  41. package/src/ngb/command-palette/config.ts +16 -0
  42. package/src/ngb/command-palette/presentation.ts +85 -0
  43. package/src/ngb/command-palette/search.ts +130 -0
  44. package/src/ngb/command-palette/staticItems.ts +243 -0
  45. package/src/ngb/command-palette/storage.ts +45 -0
  46. package/src/ngb/command-palette/store.ts +716 -0
  47. package/src/ngb/command-palette/types.ts +120 -0
  48. package/src/ngb/command-palette/useCommandPaletteHotkeys.ts +27 -0
  49. package/src/ngb/command-palette/useCommandPalettePageContext.ts +18 -0
  50. package/src/ngb/components/NgbConfirmDialog.vue +63 -0
  51. package/src/ngb/components/NgbDialog.vue +44 -0
  52. package/src/ngb/components/NgbDrawer.vue +156 -0
  53. package/src/ngb/components/NgbHeaderActionCluster.vue +140 -0
  54. package/src/ngb/components/NgbModalShell.vue +99 -0
  55. package/src/ngb/components/NgbToolbar.vue +14 -0
  56. package/src/ngb/components/forms/NgbFormLayout.vue +13 -0
  57. package/src/ngb/components/forms/NgbFormRow.vue +21 -0
  58. package/src/ngb/components/forms/NgbFormSection.vue +16 -0
  59. package/src/ngb/components/forms/NgbValidationSummary.vue +18 -0
  60. package/src/ngb/components/navigation/NgbNavigationTree.vue +68 -0
  61. package/src/ngb/components/navigation/NgbTreeNode.vue +94 -0
  62. package/src/ngb/components/register/NgbColumnChooser.vue +101 -0
  63. package/src/ngb/components/register/NgbRegisterGrid.vue +473 -0
  64. package/src/ngb/components/register/registerTypes.ts +51 -0
  65. package/src/ngb/components/register/useRegisterColumnResize.ts +99 -0
  66. package/src/ngb/components/register/useRegisterColumnState.ts +220 -0
  67. package/src/ngb/components/register/useRegisterRows.ts +209 -0
  68. package/src/ngb/components/register/useRegisterViewport.ts +56 -0
  69. package/src/ngb/editor/NgbDocumentEffectsPage.vue +512 -0
  70. package/src/ngb/editor/NgbDocumentFlowPage.vue +409 -0
  71. package/src/ngb/editor/NgbDocumentPrintPage.vue +712 -0
  72. package/src/ngb/editor/NgbEditorDiscardDialog.vue +40 -0
  73. package/src/ngb/editor/NgbEntityAuditSidebar.vue +288 -0
  74. package/src/ngb/editor/NgbEntityEditor.vue +250 -0
  75. package/src/ngb/editor/NgbEntityEditorDrawerActions.vue +88 -0
  76. package/src/ngb/editor/NgbEntityEditorHeader.vue +114 -0
  77. package/src/ngb/editor/catalogNavigation.ts +13 -0
  78. package/src/ngb/editor/config.ts +252 -0
  79. package/src/ngb/editor/documentCopyDraft.ts +245 -0
  80. package/src/ngb/editor/documentEffects.ts +43 -0
  81. package/src/ngb/editor/documentEffectsDateFormatting.ts +18 -0
  82. package/src/ngb/editor/documentNavigation.ts +115 -0
  83. package/src/ngb/editor/documentStatus.ts +45 -0
  84. package/src/ngb/editor/entityEditorErrors.ts +162 -0
  85. package/src/ngb/editor/entityEditorPersistence.ts +241 -0
  86. package/src/ngb/editor/extensions.ts +40 -0
  87. package/src/ngb/editor/types.ts +295 -0
  88. package/src/ngb/editor/useConfiguredEntityEditorDocumentActions.ts +183 -0
  89. package/src/ngb/editor/useDocumentEditorDrawerState.ts +152 -0
  90. package/src/ngb/editor/useEditorDrawerState.ts +88 -0
  91. package/src/ngb/editor/useEntityEditorBusinessContext.ts +97 -0
  92. package/src/ngb/editor/useEntityEditorCapabilities.ts +190 -0
  93. package/src/ngb/editor/useEntityEditorCommandPalette.ts +154 -0
  94. package/src/ngb/editor/useEntityEditorCommitHandlers.ts +102 -0
  95. package/src/ngb/editor/useEntityEditorHeaderActions.ts +253 -0
  96. package/src/ngb/editor/useEntityEditorLeaveGuard.ts +66 -0
  97. package/src/ngb/editor/useEntityEditorNavigationActions.ts +235 -0
  98. package/src/ngb/editor/useEntityEditorOutputs.ts +59 -0
  99. package/src/ngb/editor/useEntityEditorPageActions.ts +57 -0
  100. package/src/ngb/editor/useRouteQueryEditorDrawer.ts +173 -0
  101. package/src/ngb/env/runtimeConfig.ts +25 -0
  102. package/src/ngb/lookup/config.ts +42 -0
  103. package/src/ngb/lookup/defaultConfig.ts +136 -0
  104. package/src/ngb/lookup/navigation.ts +76 -0
  105. package/src/ngb/lookup/prefetch.ts +40 -0
  106. package/src/ngb/lookup/store.ts +261 -0
  107. package/src/ngb/metadata/NgbDocumentListFiltersDrawer.vue +78 -0
  108. package/src/ngb/metadata/NgbDocumentPeriodFilter.vue +53 -0
  109. package/src/ngb/metadata/NgbEntityForm.vue +84 -0
  110. package/src/ngb/metadata/NgbEntityFormFieldsBlock.vue +110 -0
  111. package/src/ngb/metadata/NgbEntityListPageHeader.vue +80 -0
  112. package/src/ngb/metadata/NgbFilterFieldControl.vue +104 -0
  113. package/src/ngb/metadata/NgbMetadataCatalogEditPage.vue +42 -0
  114. package/src/ngb/metadata/NgbMetadataCatalogListPage.vue +269 -0
  115. package/src/ngb/metadata/NgbMetadataDocumentEditPage.vue +68 -0
  116. package/src/ngb/metadata/NgbMetadataDocumentListPage.vue +353 -0
  117. package/src/ngb/metadata/NgbMetadataFieldRenderer.vue +124 -0
  118. package/src/ngb/metadata/NgbMetadataLookupControl.vue +92 -0
  119. package/src/ngb/metadata/NgbRecycleBinFilter.vue +53 -0
  120. package/src/ngb/metadata/NgbRegisterPageLayout.vue +143 -0
  121. package/src/ngb/metadata/config.ts +28 -0
  122. package/src/ngb/metadata/dataTypes.ts +27 -0
  123. package/src/ngb/metadata/entityForm.ts +114 -0
  124. package/src/ngb/metadata/entityModel.ts +34 -0
  125. package/src/ngb/metadata/entityValueFormatting.ts +83 -0
  126. package/src/ngb/metadata/fieldRendererState.ts +84 -0
  127. package/src/ngb/metadata/filtering.ts +153 -0
  128. package/src/ngb/metadata/lookup.ts +25 -0
  129. package/src/ngb/metadata/monthPagedListQuery.ts +108 -0
  130. package/src/ngb/metadata/normalization.ts +100 -0
  131. package/src/ngb/metadata/referenceHydration.ts +99 -0
  132. package/src/ngb/metadata/register.ts +135 -0
  133. package/src/ngb/metadata/routePages.ts +86 -0
  134. package/src/ngb/metadata/store.ts +35 -0
  135. package/src/ngb/metadata/types.ts +276 -0
  136. package/src/ngb/metadata/useMetadataListFilters.ts +306 -0
  137. package/src/ngb/metadata/useMetadataRegisterPageData.ts +246 -0
  138. package/src/ngb/metadata/useValidationFocus.ts +49 -0
  139. package/src/ngb/primitives/NgbBadge.vue +31 -0
  140. package/src/ngb/primitives/NgbButton.vue +53 -0
  141. package/src/ngb/primitives/NgbCheckbox.vue +33 -0
  142. package/src/ngb/primitives/NgbDatePicker.vue +141 -0
  143. package/src/ngb/primitives/NgbIcon.vue +545 -0
  144. package/src/ngb/primitives/NgbInput.vue +57 -0
  145. package/src/ngb/primitives/NgbLookup.vue +264 -0
  146. package/src/ngb/primitives/NgbMonthPicker.vue +132 -0
  147. package/src/ngb/primitives/NgbMultiSelect.vue +179 -0
  148. package/src/ngb/primitives/NgbPeriodPicker.vue +125 -0
  149. package/src/ngb/primitives/NgbPickerNavButton.vue +31 -0
  150. package/src/ngb/primitives/NgbPickerPopover.vue +79 -0
  151. package/src/ngb/primitives/NgbRadioGroup.vue +53 -0
  152. package/src/ngb/primitives/NgbSelect.vue +108 -0
  153. package/src/ngb/primitives/NgbStatusIcon.vue +63 -0
  154. package/src/ngb/primitives/NgbSwitch.vue +42 -0
  155. package/src/ngb/primitives/NgbTabs.vue +116 -0
  156. package/src/ngb/primitives/NgbToastHost.vue +49 -0
  157. package/src/ngb/primitives/NgbTooltip.vue +27 -0
  158. package/src/ngb/primitives/iconNames.ts +83 -0
  159. package/src/ngb/primitives/toast.ts +66 -0
  160. package/src/ngb/primitives/useAsyncComboboxQuery.ts +85 -0
  161. package/src/ngb/primitives/useFloatingDropdownPosition.ts +129 -0
  162. package/src/ngb/reporting/NgbReportComposerCollectionSection.vue +106 -0
  163. package/src/ngb/reporting/NgbReportComposerPanel.vue +1187 -0
  164. package/src/ngb/reporting/NgbReportDateRangeFilter.vue +54 -0
  165. package/src/ngb/reporting/NgbReportPage.vue +1124 -0
  166. package/src/ngb/reporting/NgbReportSheet.vue +467 -0
  167. package/src/ngb/reporting/api.ts +35 -0
  168. package/src/ngb/reporting/composer.ts +904 -0
  169. package/src/ngb/reporting/config.ts +120 -0
  170. package/src/ngb/reporting/defaultConfig.ts +15 -0
  171. package/src/ngb/reporting/lookupFilters.ts +43 -0
  172. package/src/ngb/reporting/navigation.ts +181 -0
  173. package/src/ngb/reporting/pageHelpers.ts +73 -0
  174. package/src/ngb/reporting/pageSession.ts +92 -0
  175. package/src/ngb/reporting/paging.ts +66 -0
  176. package/src/ngb/reporting/types.ts +333 -0
  177. package/src/ngb/router/backNavigation.ts +122 -0
  178. package/src/ngb/router/queryParams.ts +129 -0
  179. package/src/ngb/router/queryState.ts +121 -0
  180. package/src/ngb/router/routeAliases.ts +66 -0
  181. package/src/ngb/router/routeParams.ts +15 -0
  182. package/src/ngb/router/shareLink.ts +39 -0
  183. package/src/ngb/security/NgbAccessAuditPanel.vue +32 -0
  184. package/src/ngb/security/NgbAccessDeniedState.vue +27 -0
  185. package/src/ngb/security/NgbEffectiveAccessPanel.vue +60 -0
  186. package/src/ngb/security/NgbPermissionMatrix.vue +160 -0
  187. package/src/ngb/security/NgbRoleEditorPage.vue +319 -0
  188. package/src/ngb/security/NgbRolesPage.vue +125 -0
  189. package/src/ngb/security/NgbUserEditorPage.vue +615 -0
  190. package/src/ngb/security/NgbUsersPage.vue +125 -0
  191. package/src/ngb/security/api.ts +91 -0
  192. package/src/ngb/security/audit.ts +21 -0
  193. package/src/ngb/security/permissions.ts +65 -0
  194. package/src/ngb/security/types.ts +157 -0
  195. package/src/ngb/security/useAccessStore.ts +69 -0
  196. package/src/ngb/site/NgbDashboardAsOfToolbar.vue +100 -0
  197. package/src/ngb/site/NgbDashboardStatusBanner.vue +91 -0
  198. package/src/ngb/site/NgbLogo.vue +44 -0
  199. package/src/ngb/site/NgbPageHeader.vue +49 -0
  200. package/src/ngb/site/NgbSiteShell.vue +286 -0
  201. package/src/ngb/site/NgbSiteSidebar.vue +270 -0
  202. package/src/ngb/site/NgbTopBar.vue +425 -0
  203. package/src/ngb/site/NgbTrendChart.vue +224 -0
  204. package/src/ngb/site/dashboardData.ts +324 -0
  205. package/src/ngb/site/mainMenuStore.ts +49 -0
  206. package/src/ngb/site/types.ts +31 -0
  207. package/src/ngb/site/useDashboardPageState.ts +98 -0
  208. package/src/ngb/site/useTheme.ts +95 -0
  209. package/src/ngb/utils/clone.ts +43 -0
  210. package/src/ngb/utils/dateValues.ts +107 -0
  211. package/src/ngb/utils/errorMessage.ts +14 -0
  212. package/src/ngb/utils/guid.ts +21 -0
  213. package/src/ngb/utils/stableValue.ts +21 -0
  214. package/src/ngb/utils/storage.ts +135 -0
  215. package/src/styles/tailwind.css +165 -0
  216. package/src/vite/publicAssets.ts +104 -0
  217. package/vite-public-assets.js +68 -0
@@ -0,0 +1,270 @@
1
+ <template>
2
+ <aside
3
+ data-testid="site-sidebar"
4
+ class="h-full bg-ngb-card border-r border-ngb-border flex flex-col min-h-0 relative"
5
+ :class="collapsed ? 'w-[72px]' : 'w-full'"
6
+ >
7
+ <!-- Brand bar aligned with topbar height -->
8
+ <div data-testid="site-sidebar-brand" class="h-14 border-b border-ngb-border relative">
9
+ <!-- Collapsed: centered >> button, no logo/title -->
10
+ <div v-if="collapsed" class="h-full w-full flex items-center justify-center">
11
+ <button
12
+ v-if="allowCollapse"
13
+ class="ngb-iconbtn"
14
+ title="Expand sidebar"
15
+ @click="$emit('toggleCollapsed')"
16
+ >
17
+ <NgbIcon name="chevrons-right" :size="18" />
18
+ </button>
19
+ </div>
20
+
21
+ <!-- Expanded: << button top-left, logo+title centered -->
22
+ <div v-else class="h-full w-full">
23
+ <button
24
+ v-if="allowCollapse"
25
+ class="ngb-iconbtn absolute left-3 top-1/2 -translate-y-1/2 z-10"
26
+ title="Collapse sidebar"
27
+ @click="$emit('toggleCollapsed')"
28
+ >
29
+ <NgbIcon name="chevrons-left" :size="18" />
30
+ </button>
31
+
32
+ <div class="h-full w-full flex items-center justify-center mt-1">
33
+ <div class="flex flex-col items-center -translate-y-1">
34
+ <NgbLogo
35
+ title="NGB"
36
+ class="block select-none h-8 w-auto"
37
+ :style="{ color: 'var(--ngb-logo)' }"
38
+ />
39
+ <div v-if="brandSubtitle" class="text-sm leading-none font-semibold text-ngb-text/90">
40
+ {{ brandSubtitle }}
41
+ </div>
42
+ </div>
43
+ </div>
44
+ </div>
45
+ </div>
46
+
47
+ <!-- Nav -->
48
+ <div data-testid="site-sidebar-nav" class="flex-1 min-h-0 overflow-auto py-2" :class="collapsed ? 'px-2' : 'px-3'">
49
+ <!-- Expanded -->
50
+ <div v-if="!collapsed" class="space-y-2">
51
+ <template v-for="n in nodes" :key="n.id">
52
+ <!-- leaf root -->
53
+ <button
54
+ v-if="!(n.children?.length)"
55
+ class="w-full h-9 rounded-[var(--ngb-radius)] px-2 border flex items-center gap-2 text-left"
56
+ :class="leafClass(n)"
57
+ @click="onLeafClick(n)"
58
+ >
59
+ <span class="text-ngb-muted">
60
+ <NgbIcon :name="resolveNodeIcon(n)" :size="18" />
61
+ </span>
62
+ <span class="truncate text-sm">{{ n.label }}</span>
63
+ <span
64
+ v-if="n.badge"
65
+ class="ml-auto text-[11px] px-2 py-0.5 rounded-full border border-ngb-border text-ngb-muted bg-ngb-card"
66
+ >
67
+ {{ n.badge }}
68
+ </span>
69
+ </button>
70
+
71
+ <!-- section -->
72
+ <div v-else>
73
+ <button
74
+ class="w-full h-9 rounded-[var(--ngb-radius)] px-2 flex items-center justify-between border border-transparent hover:border-ngb-border hover:bg-ngb-bg"
75
+ @click="toggle(n.id)"
76
+ >
77
+ <span class="flex items-center gap-2 min-w-0">
78
+ <span class="text-ngb-muted">
79
+ <NgbIcon :name="resolveNodeIcon(n)" :size="18" />
80
+ </span>
81
+ <span class="text-sm font-semibold truncate">{{ n.label }}</span>
82
+ </span>
83
+ <span class="text-ngb-muted text-sm">{{ expanded.has(n.id) ? '▾' : '▸' }}</span>
84
+ </button>
85
+
86
+ <div v-if="expanded.has(n.id)" class="mt-1 pl-8 space-y-1">
87
+ <button
88
+ v-for="c in (n.children ?? [])"
89
+ :key="c.id"
90
+ class="w-full h-9 rounded-[var(--ngb-radius)] px-2 border flex items-center gap-2 text-left"
91
+ :class="leafClass(c)"
92
+ @click="onLeafClick(c)"
93
+ >
94
+ <span class="text-ngb-muted">
95
+ <NgbIcon :name="resolveNodeIcon(c)" :size="16" />
96
+ </span>
97
+ <span class="truncate text-sm">{{ c.label }}</span>
98
+ <span
99
+ v-if="c.badge"
100
+ class="ml-auto text-[11px] px-2 py-0.5 rounded-full border border-ngb-border text-ngb-muted bg-ngb-card"
101
+ >
102
+ {{ c.badge }}
103
+ </span>
104
+ </button>
105
+ </div>
106
+ </div>
107
+ </template>
108
+ </div>
109
+
110
+ <!-- Collapsed -->
111
+ <div v-else class="flex flex-col items-center gap-1">
112
+ <template v-for="n in nodes" :key="n.id">
113
+ <button
114
+ class="ngb-iconbtn"
115
+ :class="isRootActive(n) ? 'bg-ngb-bg/80' : ''"
116
+ :title="n.label"
117
+ @click="onCollapsedRootClick(n)"
118
+ >
119
+ <NgbIcon :name="resolveNodeIcon(n)" :size="18" />
120
+ <span v-if="n.badge" class="absolute -top-0.5 -right-0.5 h-2 w-2 rounded-full bg-red-500" />
121
+ </button>
122
+ </template>
123
+
124
+ <!-- flyout -->
125
+ <div
126
+ v-if="flyoutRoot"
127
+ data-testid="site-sidebar-flyout"
128
+ class="absolute left-full top-14 z-30 ml-2 w-[260px] max-h-[calc(100%-56px-52px)] overflow-auto rounded-[var(--ngb-radius)] border border-ngb-border bg-ngb-card shadow-lg p-2"
129
+ >
130
+ <div class="px-2 py-1 text-xs font-semibold text-ngb-muted">{{ flyoutRoot.label }}</div>
131
+
132
+ <div class="mt-1 space-y-1">
133
+ <button
134
+ v-for="c in (flyoutRoot.children ?? [])"
135
+ :key="c.id"
136
+ class="w-full h-9 rounded-[var(--ngb-radius)] px-2 border flex items-center gap-2 text-left"
137
+ :class="leafClass(c)"
138
+ @click="onLeafClick(c); flyoutRootId = null"
139
+ >
140
+ <span class="text-ngb-muted">
141
+ <NgbIcon :name="resolveNodeIcon(c)" :size="16" />
142
+ </span>
143
+ <span class="truncate text-sm">{{ c.label }}</span>
144
+ <span
145
+ v-if="c.badge"
146
+ class="ml-auto text-[11px] px-2 py-0.5 rounded-full border border-ngb-border text-ngb-muted bg-ngb-card"
147
+ >
148
+ {{ c.badge }}
149
+ </span>
150
+ </button>
151
+ </div>
152
+ </div>
153
+ </div>
154
+ </div>
155
+
156
+ <div class="px-3 py-3 border-t border-ngb-border text-xs text-ngb-muted text-center">
157
+ <template v-if="collapsed">
158
+ <span class="font-semibold text-ngb-text">NGB</span>
159
+ </template>
160
+ <template v-else>
161
+ powered by: <span class="font-semibold text-ngb-text">NGB</span>
162
+ </template>
163
+ </div>
164
+ </aside>
165
+ </template>
166
+
167
+ <script setup lang="ts">
168
+ import { computed, ref, watch } from 'vue';
169
+ import type { SiteNavNode, SiteQuickLink } from './types';
170
+ import NgbIcon from '../primitives/NgbIcon.vue';
171
+ import NgbLogo from './NgbLogo.vue';
172
+ import { coerceNgbIconName, type NgbIconName } from '../primitives/iconNames';
173
+
174
+ const props = withDefaults(defineProps<{
175
+ productTitle: string;
176
+ brandSubtitle?: string;
177
+ pinned: SiteQuickLink[];
178
+ recent: SiteQuickLink[];
179
+ nodes: SiteNavNode[];
180
+ selectedId: string | null;
181
+ collapsed?: boolean;
182
+ themeResolved?: 'light' | 'dark';
183
+ allowCollapse?: boolean;
184
+ }>(), {
185
+ allowCollapse: true,
186
+ });
187
+
188
+ const emit = defineEmits<{
189
+ (e: 'navigate', route: string): void;
190
+ (e: 'select', id: string, route: string): void;
191
+ (e: 'toggleCollapsed'): void;
192
+ }>();
193
+
194
+ const expanded = ref(new Set<string>());
195
+ const seenExpandableRootIds = new Set<string>();
196
+ const flyoutRootId = ref<string | null>(null);
197
+
198
+ const collapsed = computed(() => !!props.collapsed);
199
+ const allowCollapse = computed(() => props.allowCollapse);
200
+
201
+ watch(
202
+ () => props.selectedId,
203
+ () => {
204
+ // when selection changes, close flyout in collapsed mode
205
+ flyoutRootId.value = null;
206
+ },
207
+ );
208
+
209
+ watch(
210
+ () => props.nodes,
211
+ (nodes) => {
212
+ const next = new Set(expanded.value);
213
+
214
+ for (const node of nodes) {
215
+ if (!node.children?.length || seenExpandableRootIds.has(node.id)) continue;
216
+
217
+ seenExpandableRootIds.add(node.id);
218
+ next.add(node.id);
219
+ }
220
+
221
+ expanded.value = next;
222
+ },
223
+ { immediate: true },
224
+ );
225
+
226
+ const flyoutRoot = computed(() => props.nodes.find(x => x.id === flyoutRootId.value) ?? null);
227
+
228
+ function toggle(id: string) {
229
+ const s = new Set(expanded.value);
230
+ if (s.has(id)) s.delete(id);
231
+ else s.add(id);
232
+ expanded.value = s;
233
+ }
234
+
235
+ function isDisabled(n: SiteNavNode) {
236
+ return !!n.disabled || !n.route;
237
+ }
238
+
239
+ function onLeafClick(n: SiteNavNode) {
240
+ if (isDisabled(n) || !n.route) return;
241
+ // leaf click selects and navigates
242
+ emit('select', n.id, n.route);
243
+ emit('navigate', n.route);
244
+ }
245
+
246
+ function leafClass(n: SiteNavNode) {
247
+ if (isDisabled(n)) return 'bg-transparent border-transparent opacity-60 cursor-not-allowed';
248
+ if (props.selectedId === n.id) return 'bg-ngb-bg border-ngb-border';
249
+ return 'bg-transparent border-transparent hover:bg-ngb-bg hover:border-ngb-border';
250
+ }
251
+
252
+ function resolveNodeIcon(n: SiteNavNode): NgbIconName {
253
+ return coerceNgbIconName(n.icon, 'file-text');
254
+ }
255
+
256
+ function isRootActive(root: SiteNavNode) {
257
+ if (props.selectedId === root.id) return true;
258
+ const kids = root.children ?? [];
259
+ return kids.some(x => x.id === props.selectedId);
260
+ }
261
+
262
+ function onCollapsedRootClick(root: SiteNavNode) {
263
+ if (!root.children?.length) {
264
+ onLeafClick(root);
265
+ flyoutRootId.value = null;
266
+ return;
267
+ }
268
+ flyoutRootId.value = flyoutRootId.value === root.id ? null : root.id;
269
+ }
270
+ </script>