@countermeasure-platform/web-components 1.3.3-dev.33.1 → 1.3.4
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.
- package/README.md +31 -0
- package/dist/component-D5sRm1fq.js +389 -0
- package/dist/component-D5sRm1fq.js.map +1 -0
- package/dist/components/index.js +27 -27
- package/dist/icons/index.d.ts +12 -0
- package/dist/icons/index.d.ts.map +1 -1
- package/dist/icons/index.js +12 -0
- package/dist/icons/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +127 -126
- package/dist/layout/app-shell.d.ts +50 -3
- package/dist/layout/app-shell.d.ts.map +1 -1
- package/dist/layout/app-shell.js +142 -13
- package/dist/layout/app-shell.js.map +1 -1
- package/dist/layout/core-app-chrome.d.ts +81 -0
- package/dist/layout/core-app-chrome.d.ts.map +1 -0
- package/dist/layout/core-app-chrome.js +349 -0
- package/dist/layout/core-app-chrome.js.map +1 -0
- package/dist/layout/index.d.ts +4 -2
- package/dist/layout/index.d.ts.map +1 -1
- package/dist/layout/index.js +88 -87
- package/dist/layout/index.js.map +1 -1
- package/dist/react/primitives/badge.d.ts +1 -1
- package/dist/react/primitives/button.d.ts +2 -2
- package/dist/react/primitives/copy-button.d.ts +1 -1
- package/dist/react/primitives/stat-card.d.ts +1 -1
- package/dist/react/primitives/toggle.d.ts +1 -1
- package/dist/react/sidebar.d.ts +4 -4
- package/dist/react/sidebar.d.ts.map +1 -1
- package/dist/react/sidebar.js +18 -16
- package/dist/react/sidebar.js.map +1 -1
- package/dist/sidebar/component.d.ts +24 -2
- package/dist/sidebar/component.d.ts.map +1 -1
- package/dist/sidebar/enhance.d.ts +8 -0
- package/dist/sidebar/enhance.d.ts.map +1 -1
- package/dist/sidebar/index.d.ts +3 -0
- package/dist/sidebar/index.d.ts.map +1 -1
- package/dist/sidebar/index.js +81 -28
- package/dist/sidebar/index.js.map +1 -1
- package/dist/sidebar/types.d.ts +126 -4
- package/dist/sidebar/types.d.ts.map +1 -1
- package/dist/styles/layout.css +252 -0
- package/dist/styles/sidebar.css +313 -5
- package/package.json +6 -1
- package/src/icons/icons.test.ts +9 -0
- package/src/icons/index.ts +12 -0
- package/src/index.ts +11 -0
- package/src/layout/app-shell.test.ts +204 -0
- package/src/layout/app-shell.ts +362 -3
- package/src/layout/core-app-chrome.test.ts +507 -0
- package/src/layout/core-app-chrome.ts +662 -0
- package/src/layout/index.ts +36 -2
- package/src/react/sidebar.test.tsx +104 -3
- package/src/react/sidebar.tsx +26 -4
- package/src/sidebar/component.test.ts +395 -1
- package/src/sidebar/component.ts +661 -86
- package/src/sidebar/enhance.ts +118 -0
- package/src/sidebar/index.ts +144 -0
- package/src/sidebar/types.ts +143 -4
- package/src/styles/layout.css +252 -0
- package/src/styles/sidebar.css +313 -5
- package/dist/component-Bxhxf21c.js +0 -167
- package/dist/component-Bxhxf21c.js.map +0 -1
|
@@ -0,0 +1,662 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
SidebarBrandConfig,
|
|
3
|
+
SidebarComponentConfig,
|
|
4
|
+
SidebarFooterAction,
|
|
5
|
+
SidebarNavItem,
|
|
6
|
+
SidebarScopeConfig,
|
|
7
|
+
SidebarSection,
|
|
8
|
+
SidebarUser,
|
|
9
|
+
} from '../sidebar/types'
|
|
10
|
+
import { SidebarComponent } from '../sidebar/component'
|
|
11
|
+
import { resolveContainer } from '../primitives/utils'
|
|
12
|
+
import {
|
|
13
|
+
TopMenuBar,
|
|
14
|
+
type TopMenuBarActionInput,
|
|
15
|
+
type TopMenuBarConfig,
|
|
16
|
+
type TopMenuBarUser,
|
|
17
|
+
} from './app-shell'
|
|
18
|
+
|
|
19
|
+
export interface CoreAppRouteLabel {
|
|
20
|
+
pattern: RegExp
|
|
21
|
+
label: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface CoreAppSidebarPresetOptions extends Omit<
|
|
25
|
+
SidebarComponentConfig,
|
|
26
|
+
'container' | 'sections'
|
|
27
|
+
> {
|
|
28
|
+
basePath?: string
|
|
29
|
+
sections?: SidebarSection[]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface CoreAppTopbarPresetOptions extends Omit<
|
|
33
|
+
TopMenuBarConfig,
|
|
34
|
+
'actions' | 'current' | 'product'
|
|
35
|
+
> {
|
|
36
|
+
basePath?: string
|
|
37
|
+
pathname?: string
|
|
38
|
+
product?: string
|
|
39
|
+
current?: string
|
|
40
|
+
actions?: TopMenuBarActionInput[]
|
|
41
|
+
includeSearchAction?: boolean
|
|
42
|
+
includeFeedbackAction?: boolean
|
|
43
|
+
includeHelpAction?: boolean
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface CoreAppChromePreset {
|
|
47
|
+
sidebar: Omit<SidebarComponentConfig, 'container'>
|
|
48
|
+
topbar: TopMenuBarConfig
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface CoreAppChromePresetOptions {
|
|
52
|
+
basePath?: string
|
|
53
|
+
pathname?: string
|
|
54
|
+
scope?: SidebarScopeConfig
|
|
55
|
+
sidebarUser?: SidebarUser
|
|
56
|
+
sidebarFooterActions?: SidebarFooterAction[]
|
|
57
|
+
topbarUser?: TopMenuBarUser
|
|
58
|
+
topbarActions?: TopMenuBarActionInput[]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface CoreAppChromeMountOptions extends CoreAppChromePresetOptions {
|
|
62
|
+
sidebarContainer: HTMLElement | string
|
|
63
|
+
topbarContainer?: HTMLElement | string
|
|
64
|
+
sidebar?: CoreAppSidebarPresetOptions
|
|
65
|
+
topbar?: CoreAppTopbarPresetOptions
|
|
66
|
+
/**
|
|
67
|
+
* Move explicitly marked server-rendered topbar action nodes into the new topbar.
|
|
68
|
+
* Useful for native hooks that are initialized outside the chrome mount.
|
|
69
|
+
* @default true
|
|
70
|
+
*/
|
|
71
|
+
preserveTopbarActions?: boolean
|
|
72
|
+
/**
|
|
73
|
+
* Selector for preserved action nodes inside the topbar container.
|
|
74
|
+
* @default '[data-core-app-topbar-action]'
|
|
75
|
+
*/
|
|
76
|
+
topbarActionSelector?: string
|
|
77
|
+
/**
|
|
78
|
+
* Remove existing placeholder/server-rendered children before mounting.
|
|
79
|
+
* @default true
|
|
80
|
+
*/
|
|
81
|
+
clearContainers?: boolean
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface CoreAppChromeDomMountOptions extends CoreAppChromePresetOptions {
|
|
85
|
+
root?: ParentNode
|
|
86
|
+
sidebarSelector?: string
|
|
87
|
+
topbarSelector?: string
|
|
88
|
+
sidebar?: CoreAppSidebarPresetOptions
|
|
89
|
+
topbar?: CoreAppTopbarPresetOptions
|
|
90
|
+
preserveTopbarActions?: boolean
|
|
91
|
+
topbarActionSelector?: string
|
|
92
|
+
clearContainers?: boolean
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface CoreAppChromeMount {
|
|
96
|
+
sidebar: SidebarComponent
|
|
97
|
+
topbar?: TopMenuBar
|
|
98
|
+
destroy: () => void
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
interface CoreAppNavItemBlueprint {
|
|
102
|
+
id: string
|
|
103
|
+
label: string
|
|
104
|
+
href: string
|
|
105
|
+
icon: string
|
|
106
|
+
end?: boolean
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
interface CoreAppNavSectionBlueprint {
|
|
110
|
+
id: string
|
|
111
|
+
label: string
|
|
112
|
+
items: CoreAppNavItemBlueprint[]
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const DEFAULT_DOM_SIDEBAR_SELECTOR = '[data-core-app-sidebar]'
|
|
116
|
+
const DEFAULT_DOM_TOPBAR_SELECTOR = '[data-core-app-topbar]'
|
|
117
|
+
const DEFAULT_TOPBAR_ACTION_SELECTOR = '[data-core-app-topbar-action]'
|
|
118
|
+
|
|
119
|
+
const CORE_APP_NAV_SECTIONS: CoreAppNavSectionBlueprint[] = [
|
|
120
|
+
{
|
|
121
|
+
id: 'dashboards',
|
|
122
|
+
label: 'Dashboards',
|
|
123
|
+
items: [
|
|
124
|
+
{ id: 'library', label: 'Library', href: '/', icon: 'library', end: true },
|
|
125
|
+
{ id: 'capacity', label: 'Capacity', href: '/capacity', icon: 'capacity' },
|
|
126
|
+
{ id: 'operational-alerts', label: 'Operational Alerts', href: '/alerts', icon: 'alerts' },
|
|
127
|
+
],
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: 'threat-library',
|
|
131
|
+
label: 'Threat Library',
|
|
132
|
+
items: [
|
|
133
|
+
{ id: 'detections', label: 'Detections', href: '/detections', icon: 'detections' },
|
|
134
|
+
{ id: 'actors', label: 'Actors', href: '/actors', icon: 'actors' },
|
|
135
|
+
{ id: 'library-reports', label: 'Library Reports', href: '/intel', icon: 'intel-reports' },
|
|
136
|
+
{ id: 'mitre', label: 'MITRE ATT&CK', href: '/mitre', icon: 'mitre' },
|
|
137
|
+
],
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
id: 'library-operations',
|
|
141
|
+
label: 'Library Operations',
|
|
142
|
+
items: [{ id: 'pipeline', label: 'Pipeline', href: '/pipeline', icon: 'pipeline' }],
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
id: 'integrations',
|
|
146
|
+
label: 'Integrations',
|
|
147
|
+
items: [
|
|
148
|
+
{ id: 'connectors', label: 'Connectors', href: '/connectors', icon: 'connectors' },
|
|
149
|
+
{ id: 'jobs', label: 'Jobs', href: '/jobs', icon: 'jobs' },
|
|
150
|
+
],
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
id: 'platform',
|
|
154
|
+
label: 'Platform',
|
|
155
|
+
items: [
|
|
156
|
+
{ id: 'tenants', label: 'Tenants', href: '/tenants', icon: 'tenants' },
|
|
157
|
+
{ id: 'runners', label: 'Runners', href: '/runners', icon: 'runners' },
|
|
158
|
+
{ id: 'topology', label: 'Topology', href: '/topology', icon: 'topology' },
|
|
159
|
+
{ id: 'api-access', label: 'API Access', href: '/api-access', icon: 'api-access' },
|
|
160
|
+
],
|
|
161
|
+
},
|
|
162
|
+
]
|
|
163
|
+
|
|
164
|
+
export const CORE_APP_ROUTE_LABELS: CoreAppRouteLabel[] = [
|
|
165
|
+
{ pattern: /^\/(?:dashboards\/)?capacity(?:\/|$)/, label: 'Capacity' },
|
|
166
|
+
{ pattern: /^\/(?:dashboards\/)?alerts(?:\/|$)/, label: 'Operational Alerts' },
|
|
167
|
+
{ pattern: /^\/dashboards\/library(?:\/|$)/, label: 'Library Dashboard' },
|
|
168
|
+
{ pattern: /^\/detections(?:\/|$)/, label: 'Detections' },
|
|
169
|
+
{ pattern: /^\/actors(?:\/|$)/, label: 'Actors' },
|
|
170
|
+
{ pattern: /^\/intel(?:\/|$)/, label: 'Library Reports' },
|
|
171
|
+
{ pattern: /^\/mitre(?:\/|$)/, label: 'MITRE ATT&CK' },
|
|
172
|
+
{ pattern: /^\/pipeline(?:\/|$)/, label: 'Pipeline' },
|
|
173
|
+
{ pattern: /^\/connectors(?:\/|$)/, label: 'Connectors' },
|
|
174
|
+
{ pattern: /^\/jobs(?:\/|$)/, label: 'Jobs' },
|
|
175
|
+
{ pattern: /^\/topology(?:\/|$)/, label: 'Topology' },
|
|
176
|
+
{ pattern: /^\/tenants(?:\/|$)/, label: 'Tenants' },
|
|
177
|
+
{ pattern: /^\/runners(?:\/|$)/, label: 'Runners' },
|
|
178
|
+
{ pattern: /^\/api-access(?:\/|$)/, label: 'API Access' },
|
|
179
|
+
{ pattern: /^\/settings(?:\/|$)/, label: 'Settings' },
|
|
180
|
+
]
|
|
181
|
+
|
|
182
|
+
function normalizeBasePath(basePath: string): string {
|
|
183
|
+
const trimmed = basePath.trim()
|
|
184
|
+
if (trimmed === '' || trimmed === '/') {
|
|
185
|
+
return ''
|
|
186
|
+
}
|
|
187
|
+
return `/${trimmed.replace(/^\/+|\/+$/g, '')}`
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function joinBasePath(basePath: string, href: string): string {
|
|
191
|
+
const normalizedBase = normalizeBasePath(basePath)
|
|
192
|
+
if (href === '/' || href === '') {
|
|
193
|
+
return normalizedBase || '/'
|
|
194
|
+
}
|
|
195
|
+
return `${normalizedBase}/${href.replace(/^\/+/, '')}`
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function stripBasePath(pathname: string, basePath: string): string {
|
|
199
|
+
const normalizedBase = normalizeBasePath(basePath)
|
|
200
|
+
if (normalizedBase === '') {
|
|
201
|
+
return pathname || '/'
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (pathname === normalizedBase) {
|
|
205
|
+
return '/'
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (pathname.startsWith(`${normalizedBase}/`)) {
|
|
209
|
+
return pathname.slice(normalizedBase.length) || '/'
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return pathname || '/'
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function cloneSections(sections: SidebarSection[]): SidebarSection[] {
|
|
216
|
+
return sections.map(section => ({
|
|
217
|
+
...section,
|
|
218
|
+
items: section.items.map(item => ({ ...item })),
|
|
219
|
+
}))
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function createCoreAppSidebarSections(basePath = '/app'): SidebarSection[] {
|
|
223
|
+
return CORE_APP_NAV_SECTIONS.map(section => ({
|
|
224
|
+
id: section.id,
|
|
225
|
+
label: section.label,
|
|
226
|
+
items: section.items.map(item => {
|
|
227
|
+
const navItem: SidebarNavItem = {
|
|
228
|
+
id: item.id,
|
|
229
|
+
label: item.label,
|
|
230
|
+
href: joinBasePath(basePath, item.href),
|
|
231
|
+
icon: item.icon,
|
|
232
|
+
tooltip: item.label,
|
|
233
|
+
activeMatch: item.end === true ? 'exact' : 'prefix',
|
|
234
|
+
}
|
|
235
|
+
if (item.end !== undefined) {
|
|
236
|
+
navItem.end = item.end
|
|
237
|
+
}
|
|
238
|
+
return navItem
|
|
239
|
+
}),
|
|
240
|
+
}))
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function getCoreAppRouteLabel(pathname: string, basePath = '/app'): string {
|
|
244
|
+
const relativePathname = stripBasePath(pathname, basePath)
|
|
245
|
+
if (relativePathname === '/' || relativePathname === '') {
|
|
246
|
+
return 'Platform Overview'
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return (
|
|
250
|
+
CORE_APP_ROUTE_LABELS.find(route => route.pattern.test(relativePathname))?.label ??
|
|
251
|
+
'Threat Library'
|
|
252
|
+
)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export function createCoreAppSidebarConfig(
|
|
256
|
+
options: CoreAppSidebarPresetOptions = {}
|
|
257
|
+
): Omit<SidebarComponentConfig, 'container'> {
|
|
258
|
+
const {
|
|
259
|
+
basePath = '/app',
|
|
260
|
+
sections,
|
|
261
|
+
variant = 'cm',
|
|
262
|
+
brand,
|
|
263
|
+
scope,
|
|
264
|
+
beforeNav,
|
|
265
|
+
afterNav,
|
|
266
|
+
collapsible = true,
|
|
267
|
+
collapseButtonVisible = true,
|
|
268
|
+
user,
|
|
269
|
+
footerActions,
|
|
270
|
+
iconSet,
|
|
271
|
+
collapsed,
|
|
272
|
+
activeItemId,
|
|
273
|
+
pathname,
|
|
274
|
+
storageKey = 'cm-sidebar-collapsed',
|
|
275
|
+
persistCollapsed,
|
|
276
|
+
mobileBreakpoint,
|
|
277
|
+
mobileBackdrop = true,
|
|
278
|
+
closeOnNavigate = true,
|
|
279
|
+
open,
|
|
280
|
+
onNavigate,
|
|
281
|
+
onCollapse,
|
|
282
|
+
onOpenChange,
|
|
283
|
+
} = options
|
|
284
|
+
|
|
285
|
+
const resolvedBrand: SidebarBrandConfig = brand ?? {
|
|
286
|
+
label: 'CounterMeasure',
|
|
287
|
+
href: joinBasePath(basePath, '/'),
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const config: Omit<SidebarComponentConfig, 'container'> = {
|
|
291
|
+
variant,
|
|
292
|
+
sections:
|
|
293
|
+
sections === undefined ? createCoreAppSidebarSections(basePath) : cloneSections(sections),
|
|
294
|
+
brand: resolvedBrand,
|
|
295
|
+
collapsible,
|
|
296
|
+
collapseButtonVisible,
|
|
297
|
+
storageKey,
|
|
298
|
+
mobileBackdrop,
|
|
299
|
+
closeOnNavigate,
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (scope !== undefined) config.scope = scope
|
|
303
|
+
if (beforeNav !== undefined) config.beforeNav = beforeNav
|
|
304
|
+
if (afterNav !== undefined) config.afterNav = afterNav
|
|
305
|
+
if (user !== undefined) config.user = user
|
|
306
|
+
if (footerActions !== undefined) config.footerActions = footerActions
|
|
307
|
+
if (iconSet !== undefined) config.iconSet = iconSet
|
|
308
|
+
if (collapsed !== undefined) config.collapsed = collapsed
|
|
309
|
+
if (activeItemId !== undefined) config.activeItemId = activeItemId
|
|
310
|
+
if (pathname !== undefined) config.pathname = pathname
|
|
311
|
+
if (persistCollapsed !== undefined) config.persistCollapsed = persistCollapsed
|
|
312
|
+
if (mobileBreakpoint !== undefined) config.mobileBreakpoint = mobileBreakpoint
|
|
313
|
+
if (open !== undefined) config.open = open
|
|
314
|
+
if (onNavigate !== undefined) config.onNavigate = onNavigate
|
|
315
|
+
if (onCollapse !== undefined) config.onCollapse = onCollapse
|
|
316
|
+
if (onOpenChange !== undefined) config.onOpenChange = onOpenChange
|
|
317
|
+
|
|
318
|
+
return config
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export function createCoreAppTopbarActions(
|
|
322
|
+
options: Pick<
|
|
323
|
+
CoreAppTopbarPresetOptions,
|
|
324
|
+
'includeSearchAction' | 'includeFeedbackAction' | 'includeHelpAction'
|
|
325
|
+
> = {}
|
|
326
|
+
): TopMenuBarActionInput[] {
|
|
327
|
+
const {
|
|
328
|
+
includeSearchAction = true,
|
|
329
|
+
includeFeedbackAction = true,
|
|
330
|
+
includeHelpAction = true,
|
|
331
|
+
} = options
|
|
332
|
+
const actions: TopMenuBarActionInput[] = []
|
|
333
|
+
|
|
334
|
+
if (includeSearchAction) {
|
|
335
|
+
actions.push({
|
|
336
|
+
id: 'search',
|
|
337
|
+
label: 'Find...',
|
|
338
|
+
icon: 'search',
|
|
339
|
+
showLabel: true,
|
|
340
|
+
className: 'cmm-topbar__action--search',
|
|
341
|
+
attributes: { 'data-app-search-trigger': true },
|
|
342
|
+
})
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (includeFeedbackAction) {
|
|
346
|
+
actions.push({
|
|
347
|
+
id: 'feedback',
|
|
348
|
+
label: 'Feedback',
|
|
349
|
+
showLabel: true,
|
|
350
|
+
attributes: { 'data-feedback-toggle': true },
|
|
351
|
+
})
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (includeHelpAction) {
|
|
355
|
+
actions.push({
|
|
356
|
+
id: 'help',
|
|
357
|
+
label: 'Help',
|
|
358
|
+
icon: 'help',
|
|
359
|
+
attributes: { 'data-help-toggle': true },
|
|
360
|
+
})
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return actions
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export function createCoreAppTopbarConfig(
|
|
367
|
+
options: CoreAppTopbarPresetOptions = {}
|
|
368
|
+
): TopMenuBarConfig {
|
|
369
|
+
const {
|
|
370
|
+
basePath = '/app',
|
|
371
|
+
pathname = typeof window === 'undefined' ? '/' : window.location.pathname,
|
|
372
|
+
product = 'CounterMeasure Core',
|
|
373
|
+
current = getCoreAppRouteLabel(pathname, basePath),
|
|
374
|
+
actions,
|
|
375
|
+
beforeActions,
|
|
376
|
+
afterActions,
|
|
377
|
+
user,
|
|
378
|
+
iconSet,
|
|
379
|
+
className,
|
|
380
|
+
breadcrumbs,
|
|
381
|
+
container,
|
|
382
|
+
includeSearchAction,
|
|
383
|
+
includeFeedbackAction,
|
|
384
|
+
includeHelpAction,
|
|
385
|
+
} = options
|
|
386
|
+
|
|
387
|
+
const actionOptions: Pick<
|
|
388
|
+
CoreAppTopbarPresetOptions,
|
|
389
|
+
'includeSearchAction' | 'includeFeedbackAction' | 'includeHelpAction'
|
|
390
|
+
> = {}
|
|
391
|
+
if (includeSearchAction !== undefined) actionOptions.includeSearchAction = includeSearchAction
|
|
392
|
+
if (includeFeedbackAction !== undefined)
|
|
393
|
+
actionOptions.includeFeedbackAction = includeFeedbackAction
|
|
394
|
+
if (includeHelpAction !== undefined) actionOptions.includeHelpAction = includeHelpAction
|
|
395
|
+
|
|
396
|
+
const config: TopMenuBarConfig = {
|
|
397
|
+
product,
|
|
398
|
+
current,
|
|
399
|
+
actions: actions ?? createCoreAppTopbarActions(actionOptions),
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (container !== undefined) config.container = container
|
|
403
|
+
if (breadcrumbs !== undefined) config.breadcrumbs = breadcrumbs
|
|
404
|
+
if (beforeActions !== undefined) config.beforeActions = beforeActions
|
|
405
|
+
if (afterActions !== undefined) config.afterActions = afterActions
|
|
406
|
+
if (user !== undefined) config.user = user
|
|
407
|
+
if (iconSet !== undefined) config.iconSet = iconSet
|
|
408
|
+
if (className !== undefined) config.className = className
|
|
409
|
+
|
|
410
|
+
return config
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export function createCoreAppChromePreset(
|
|
414
|
+
options: CoreAppChromePresetOptions = {}
|
|
415
|
+
): CoreAppChromePreset {
|
|
416
|
+
const {
|
|
417
|
+
basePath = '/app',
|
|
418
|
+
pathname,
|
|
419
|
+
scope,
|
|
420
|
+
sidebarUser,
|
|
421
|
+
sidebarFooterActions,
|
|
422
|
+
topbarUser,
|
|
423
|
+
topbarActions,
|
|
424
|
+
} = options
|
|
425
|
+
|
|
426
|
+
const sidebarOptions: CoreAppSidebarPresetOptions = { basePath }
|
|
427
|
+
if (scope !== undefined) sidebarOptions.scope = scope
|
|
428
|
+
if (sidebarUser !== undefined) sidebarOptions.user = sidebarUser
|
|
429
|
+
if (sidebarFooterActions !== undefined) sidebarOptions.footerActions = sidebarFooterActions
|
|
430
|
+
if (pathname !== undefined) sidebarOptions.pathname = pathname
|
|
431
|
+
|
|
432
|
+
const topbarOptions: CoreAppTopbarPresetOptions = { basePath }
|
|
433
|
+
if (pathname !== undefined) topbarOptions.pathname = pathname
|
|
434
|
+
if (topbarUser !== undefined) topbarOptions.user = topbarUser
|
|
435
|
+
if (topbarActions !== undefined) topbarOptions.actions = topbarActions
|
|
436
|
+
|
|
437
|
+
return {
|
|
438
|
+
sidebar: createCoreAppSidebarConfig(sidebarOptions),
|
|
439
|
+
topbar: createCoreAppTopbarConfig(topbarOptions),
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function clearContainer(container: HTMLElement, clearContainers: boolean | undefined): void {
|
|
444
|
+
if (clearContainers !== false) {
|
|
445
|
+
container.replaceChildren()
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function collectTopbarActionNodes(options: {
|
|
450
|
+
container: HTMLElement
|
|
451
|
+
preserveTopbarActions: boolean | undefined
|
|
452
|
+
selector: string | undefined
|
|
453
|
+
}): HTMLElement[] {
|
|
454
|
+
if (options.preserveTopbarActions === false) {
|
|
455
|
+
return []
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
return Array.from(
|
|
459
|
+
options.container.querySelectorAll<HTMLElement>(
|
|
460
|
+
options.selector ?? DEFAULT_TOPBAR_ACTION_SELECTOR
|
|
461
|
+
)
|
|
462
|
+
)
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function mergeElements(
|
|
466
|
+
existing: HTMLElement | HTMLElement[] | undefined,
|
|
467
|
+
additions: HTMLElement[]
|
|
468
|
+
): HTMLElement | HTMLElement[] | undefined {
|
|
469
|
+
if (additions.length === 0) {
|
|
470
|
+
return existing
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (existing === undefined) {
|
|
474
|
+
return additions
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return Array.isArray(existing) ? [...existing, ...additions] : [existing, ...additions]
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function hasPreservedActionAttribute(actions: HTMLElement[], attribute: string): boolean {
|
|
481
|
+
return actions.some(action => action.hasAttribute(attribute))
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function dedupeDefaultActionsForPreservedNodes(
|
|
485
|
+
options: CoreAppTopbarPresetOptions,
|
|
486
|
+
preservedActions: HTMLElement[]
|
|
487
|
+
): void {
|
|
488
|
+
if (options.actions !== undefined || preservedActions.length === 0) {
|
|
489
|
+
return
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (
|
|
493
|
+
options.includeSearchAction === undefined &&
|
|
494
|
+
hasPreservedActionAttribute(preservedActions, 'data-app-search-trigger')
|
|
495
|
+
) {
|
|
496
|
+
options.includeSearchAction = false
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (
|
|
500
|
+
options.includeFeedbackAction === undefined &&
|
|
501
|
+
hasPreservedActionAttribute(preservedActions, 'data-feedback-toggle')
|
|
502
|
+
) {
|
|
503
|
+
options.includeFeedbackAction = false
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (
|
|
507
|
+
options.includeHelpAction === undefined &&
|
|
508
|
+
hasPreservedActionAttribute(preservedActions, 'data-help-toggle')
|
|
509
|
+
) {
|
|
510
|
+
options.includeHelpAction = false
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function buildSidebarMountOptions(options: CoreAppChromeMountOptions): CoreAppSidebarPresetOptions {
|
|
515
|
+
const basePath = options.sidebar?.basePath ?? options.basePath ?? '/app'
|
|
516
|
+
const sidebarOptions: CoreAppSidebarPresetOptions = {
|
|
517
|
+
...options.sidebar,
|
|
518
|
+
basePath,
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
if (options.scope !== undefined) sidebarOptions.scope = options.scope
|
|
522
|
+
if (options.sidebarUser !== undefined) sidebarOptions.user = options.sidebarUser
|
|
523
|
+
if (options.sidebarFooterActions !== undefined)
|
|
524
|
+
sidebarOptions.footerActions = options.sidebarFooterActions
|
|
525
|
+
if (options.pathname !== undefined && sidebarOptions.pathname === undefined) {
|
|
526
|
+
sidebarOptions.pathname = options.pathname
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return sidebarOptions
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function buildTopbarMountOptions(
|
|
533
|
+
options: CoreAppChromeMountOptions,
|
|
534
|
+
topbarContainer: HTMLElement,
|
|
535
|
+
preservedActions: HTMLElement[]
|
|
536
|
+
): CoreAppTopbarPresetOptions {
|
|
537
|
+
const basePath = options.topbar?.basePath ?? options.basePath ?? '/app'
|
|
538
|
+
const topbarOptions: CoreAppTopbarPresetOptions = {
|
|
539
|
+
...options.topbar,
|
|
540
|
+
basePath,
|
|
541
|
+
container: topbarContainer,
|
|
542
|
+
}
|
|
543
|
+
const afterActions = mergeElements(options.topbar?.afterActions, preservedActions)
|
|
544
|
+
|
|
545
|
+
if (options.pathname !== undefined) topbarOptions.pathname = options.pathname
|
|
546
|
+
if (options.topbarUser !== undefined) topbarOptions.user = options.topbarUser
|
|
547
|
+
if (options.topbarActions !== undefined) topbarOptions.actions = options.topbarActions
|
|
548
|
+
if (afterActions !== undefined) topbarOptions.afterActions = afterActions
|
|
549
|
+
dedupeDefaultActionsForPreservedNodes(topbarOptions, preservedActions)
|
|
550
|
+
|
|
551
|
+
return topbarOptions
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
export function mountCoreAppChrome(options: CoreAppChromeMountOptions): CoreAppChromeMount {
|
|
555
|
+
const sidebarContainer = resolveContainer(options.sidebarContainer)
|
|
556
|
+
clearContainer(sidebarContainer, options.clearContainers)
|
|
557
|
+
|
|
558
|
+
const sidebar = new SidebarComponent({
|
|
559
|
+
container: sidebarContainer,
|
|
560
|
+
...createCoreAppSidebarConfig(buildSidebarMountOptions(options)),
|
|
561
|
+
})
|
|
562
|
+
|
|
563
|
+
const topbarContainerOption = options.topbarContainer ?? options.topbar?.container
|
|
564
|
+
const topbarContainer =
|
|
565
|
+
topbarContainerOption === undefined ? undefined : resolveContainer(topbarContainerOption)
|
|
566
|
+
let topbar: TopMenuBar | undefined
|
|
567
|
+
|
|
568
|
+
if (topbarContainer !== undefined) {
|
|
569
|
+
const preservedActions = collectTopbarActionNodes({
|
|
570
|
+
container: topbarContainer,
|
|
571
|
+
preserveTopbarActions: options.preserveTopbarActions,
|
|
572
|
+
selector: options.topbarActionSelector,
|
|
573
|
+
})
|
|
574
|
+
clearContainer(topbarContainer, options.clearContainers)
|
|
575
|
+
topbar = new TopMenuBar(
|
|
576
|
+
createCoreAppTopbarConfig(buildTopbarMountOptions(options, topbarContainer, preservedActions))
|
|
577
|
+
)
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const mounted: CoreAppChromeMount = {
|
|
581
|
+
sidebar,
|
|
582
|
+
destroy: () => {
|
|
583
|
+
topbar?.destroy()
|
|
584
|
+
sidebar.destroy()
|
|
585
|
+
},
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (topbar !== undefined) {
|
|
589
|
+
mounted.topbar = topbar
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
return mounted
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function readDataValue(element: HTMLElement | undefined, key: string): string | undefined {
|
|
596
|
+
const value = element?.dataset[key]
|
|
597
|
+
return value === undefined || value.length === 0 ? undefined : value
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
function buildDeclarativeTopbarOptions(
|
|
601
|
+
options: CoreAppChromeDomMountOptions,
|
|
602
|
+
sidebarContainer: HTMLElement,
|
|
603
|
+
topbarContainer: HTMLElement | null
|
|
604
|
+
): CoreAppTopbarPresetOptions | undefined {
|
|
605
|
+
const product =
|
|
606
|
+
options.topbar?.product ??
|
|
607
|
+
readDataValue(topbarContainer ?? undefined, 'product') ??
|
|
608
|
+
readDataValue(sidebarContainer, 'product')
|
|
609
|
+
const current =
|
|
610
|
+
options.topbar?.current ??
|
|
611
|
+
readDataValue(topbarContainer ?? undefined, 'current') ??
|
|
612
|
+
readDataValue(sidebarContainer, 'current')
|
|
613
|
+
|
|
614
|
+
if (options.topbar === undefined && product === undefined && current === undefined) {
|
|
615
|
+
return undefined
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
const topbar: CoreAppTopbarPresetOptions = { ...(options.topbar ?? {}) }
|
|
619
|
+
if (product !== undefined) topbar.product = product
|
|
620
|
+
if (current !== undefined) topbar.current = current
|
|
621
|
+
return topbar
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
export function mountCoreAppChromeFromDom(
|
|
625
|
+
options: CoreAppChromeDomMountOptions = {}
|
|
626
|
+
): CoreAppChromeMount | null {
|
|
627
|
+
const root = options.root ?? document
|
|
628
|
+
const sidebarContainer = root.querySelector<HTMLElement>(
|
|
629
|
+
options.sidebarSelector ?? DEFAULT_DOM_SIDEBAR_SELECTOR
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
if (sidebarContainer === null) {
|
|
633
|
+
return null
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const topbarContainer = root.querySelector<HTMLElement>(
|
|
637
|
+
options.topbarSelector ?? DEFAULT_DOM_TOPBAR_SELECTOR
|
|
638
|
+
)
|
|
639
|
+
const basePath =
|
|
640
|
+
options.basePath ??
|
|
641
|
+
options.sidebar?.basePath ??
|
|
642
|
+
options.topbar?.basePath ??
|
|
643
|
+
readDataValue(sidebarContainer, 'basePath') ??
|
|
644
|
+
readDataValue(topbarContainer ?? undefined, 'basePath') ??
|
|
645
|
+
'/app'
|
|
646
|
+
const pathname =
|
|
647
|
+
options.pathname ??
|
|
648
|
+
readDataValue(sidebarContainer, 'pathname') ??
|
|
649
|
+
readDataValue(topbarContainer ?? undefined, 'pathname')
|
|
650
|
+
|
|
651
|
+
const mountOptions: CoreAppChromeMountOptions = {
|
|
652
|
+
...options,
|
|
653
|
+
sidebarContainer,
|
|
654
|
+
basePath,
|
|
655
|
+
}
|
|
656
|
+
const topbar = buildDeclarativeTopbarOptions(options, sidebarContainer, topbarContainer)
|
|
657
|
+
if (topbar !== undefined) mountOptions.topbar = topbar
|
|
658
|
+
if (topbarContainer !== null) mountOptions.topbarContainer = topbarContainer
|
|
659
|
+
if (pathname !== undefined) mountOptions.pathname = pathname
|
|
660
|
+
|
|
661
|
+
return mountCoreAppChrome(mountOptions)
|
|
662
|
+
}
|
package/src/layout/index.ts
CHANGED
|
@@ -7,8 +7,42 @@
|
|
|
7
7
|
|
|
8
8
|
import { createSafeMarkupFragment } from '../utils/sanitize'
|
|
9
9
|
import { navigateDocumentTo } from '../utils/navigation'
|
|
10
|
-
export {
|
|
11
|
-
|
|
10
|
+
export {
|
|
11
|
+
AppShell,
|
|
12
|
+
TopMenuBar,
|
|
13
|
+
createAppShell,
|
|
14
|
+
createProductShell,
|
|
15
|
+
createTopMenuBar,
|
|
16
|
+
} from './app-shell'
|
|
17
|
+
export type {
|
|
18
|
+
AppShellConfig,
|
|
19
|
+
TopMenuBarAction,
|
|
20
|
+
TopMenuBarActionInput,
|
|
21
|
+
TopMenuBarBreadcrumb,
|
|
22
|
+
TopMenuBarConfig,
|
|
23
|
+
TopMenuBarUser,
|
|
24
|
+
} from './app-shell'
|
|
25
|
+
export {
|
|
26
|
+
CORE_APP_ROUTE_LABELS,
|
|
27
|
+
createCoreAppChromePreset,
|
|
28
|
+
createCoreAppSidebarConfig,
|
|
29
|
+
createCoreAppSidebarSections,
|
|
30
|
+
createCoreAppTopbarActions,
|
|
31
|
+
createCoreAppTopbarConfig,
|
|
32
|
+
getCoreAppRouteLabel,
|
|
33
|
+
mountCoreAppChrome,
|
|
34
|
+
mountCoreAppChromeFromDom,
|
|
35
|
+
} from './core-app-chrome'
|
|
36
|
+
export type {
|
|
37
|
+
CoreAppChromePreset,
|
|
38
|
+
CoreAppChromeDomMountOptions,
|
|
39
|
+
CoreAppChromeMount,
|
|
40
|
+
CoreAppChromeMountOptions,
|
|
41
|
+
CoreAppChromePresetOptions,
|
|
42
|
+
CoreAppRouteLabel,
|
|
43
|
+
CoreAppSidebarPresetOptions,
|
|
44
|
+
CoreAppTopbarPresetOptions,
|
|
45
|
+
} from './core-app-chrome'
|
|
12
46
|
export { PageHeader, createPageHeader } from './page-header'
|
|
13
47
|
export type { PageHeaderConfig, PageHeaderMetadata } from './page-header'
|
|
14
48
|
|