@scion/components 22.2.0 → 22.4.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.
- package/_index.scss +94 -5
- package/common/package.json +4 -0
- package/design/_theming.scss +25 -10
- package/design/components/_scion-component-dark-tokens.scss +153 -0
- package/design/components/_scion-component-light-tokens.scss +153 -0
- package/design/icons/_scion-icon-font.scss +50 -0
- package/design/themes/_scion-dark-theme.scss +2 -7
- package/design/themes/_scion-light-theme.scss +3 -8
- package/fesm2022/scion-components-common.mjs +251 -0
- package/fesm2022/scion-components-common.mjs.map +1 -0
- package/fesm2022/scion-components-dimension.mjs +7 -6
- package/fesm2022/scion-components-dimension.mjs.map +1 -1
- package/fesm2022/scion-components-icon.mjs +327 -0
- package/fesm2022/scion-components-icon.mjs.map +1 -0
- package/fesm2022/scion-components-menu.mjs +3277 -0
- package/fesm2022/scion-components-menu.mjs.map +1 -0
- package/fesm2022/scion-components-sashbox.mjs +21 -21
- package/fesm2022/scion-components-sashbox.mjs.map +1 -1
- package/fesm2022/scion-components-splitter.mjs +16 -13
- package/fesm2022/scion-components-splitter.mjs.map +1 -1
- package/fesm2022/scion-components-table.mjs +2337 -0
- package/fesm2022/scion-components-table.mjs.map +1 -0
- package/fesm2022/scion-components-text.mjs +322 -0
- package/fesm2022/scion-components-text.mjs.map +1 -0
- package/fesm2022/scion-components-throbber.mjs +15 -15
- package/fesm2022/scion-components-throbber.mjs.map +1 -1
- package/fesm2022/scion-components-viewport.mjs +41 -72
- package/fesm2022/scion-components-viewport.mjs.map +1 -1
- package/icon/package.json +4 -0
- package/menu/package.json +4 -0
- package/package.json +23 -3
- package/table/package.json +4 -0
- package/text/package.json +4 -0
- package/types/scion-components-common.d.ts +185 -0
- package/types/scion-components-icon.d.ts +93 -0
- package/types/scion-components-menu.d.ts +1922 -0
- package/types/scion-components-sashbox.d.ts +4 -2
- package/types/scion-components-splitter.d.ts +4 -3
- package/types/scion-components-table.d.ts +595 -0
- package/types/scion-components-text.d.ts +131 -0
- package/types/scion-components-viewport.d.ts +7 -4
- package/design/components/_scion-component-tokens.scss +0 -48
|
@@ -0,0 +1,3277 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { untracked, inject, Injector, InjectionToken, computed, runInInjectionContext, effect, assertNotInReactiveContext, NgZone, DOCUMENT, DestroyRef, isSignal, Injectable, Pipe, input, createComponent, inputBinding, ElementRef, EnvironmentInjector, ApplicationRef, Component, model, viewChild, signal, viewChildren, linkedSignal, ViewContainerRef, afterRenderEffect, Directive } from '@angular/core';
|
|
3
|
+
import * as i1$1 from '@scion/components/common';
|
|
4
|
+
import { coerceSignal, createDestroyableInjector, SciComponentOutletDirective, SciAttributesDirective, assertInInjectionContext } from '@scion/components/common';
|
|
5
|
+
import { Objects, Arrays, prune, Maps } from '@scion/toolkit/util';
|
|
6
|
+
import { text, SciTextPipe } from '@scion/components/text';
|
|
7
|
+
import { noop, merge, fromEvent, animationFrameScheduler, EMPTY } from 'rxjs';
|
|
8
|
+
import { subscribeIn, observeIn } from '@scion/toolkit/operators';
|
|
9
|
+
import { coerceElement } from '@angular/cdk/coercion';
|
|
10
|
+
import { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop';
|
|
11
|
+
import * as i1 from '@angular/forms';
|
|
12
|
+
import { FormsModule } from '@angular/forms';
|
|
13
|
+
import { SciIconComponent } from '@scion/components/icon';
|
|
14
|
+
import { NgTemplateOutlet } from '@angular/common';
|
|
15
|
+
import { SciViewportComponent } from '@scion/components/viewport';
|
|
16
|
+
import { filter, observeOn, raceWith, take, switchMap } from 'rxjs/operators';
|
|
17
|
+
import { dimension } from '@scion/components/dimension';
|
|
18
|
+
import { UUID } from '@scion/toolkit/uuid';
|
|
19
|
+
|
|
20
|
+
/*
|
|
21
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
22
|
+
*
|
|
23
|
+
* This program and the accompanying materials are made
|
|
24
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
25
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
26
|
+
*
|
|
27
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* Translates the given {@link Translatable}.
|
|
31
|
+
*/
|
|
32
|
+
function translate(translatable) {
|
|
33
|
+
return translatable !== undefined ? untracked(() => text(translatable)) : undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/*
|
|
37
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
38
|
+
*
|
|
39
|
+
* This program and the accompanying materials are made
|
|
40
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
41
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
42
|
+
*
|
|
43
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
44
|
+
*/
|
|
45
|
+
/**
|
|
46
|
+
* Injects the context for menu locations and contributions based on the current injection context.
|
|
47
|
+
*
|
|
48
|
+
* Must be called within an injection context, or an explicit {@link Injector} passed.
|
|
49
|
+
*
|
|
50
|
+
* @see provideMenuContextProvider
|
|
51
|
+
* @docs-private Not public API. For internal use only.
|
|
52
|
+
*/
|
|
53
|
+
function injectMenuContext(options) {
|
|
54
|
+
const injector = options?.injector ?? inject(Injector);
|
|
55
|
+
const providers = injector.get(SCI_MENU_CONTEXT_PROVIDERS, [], { optional: true });
|
|
56
|
+
// Get contexts available in the current injection context.
|
|
57
|
+
const contexts = providers.map(providerFn => coerceSignal(providerFn(injector)));
|
|
58
|
+
// Merge contexts.
|
|
59
|
+
return computed(() => new Map(contexts.flatMap(context => [...context?.() ?? new Map()])), { equal: Objects.isEqual });
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Injects dependency injection providers of the given menu context based on the current injection context.
|
|
63
|
+
*
|
|
64
|
+
* Must be called within an injection context, or an explicit {@link Injector} passed.
|
|
65
|
+
*
|
|
66
|
+
* @see provideMenuInjectionContextProvider
|
|
67
|
+
* @docs-private Not public API. Used by frameworks integrating the SCION Menu API. Applications should not use this function.
|
|
68
|
+
*/
|
|
69
|
+
function injectMenuInjectionContextProviders(menuContext, options) {
|
|
70
|
+
const injector = options?.injector ?? inject(Injector);
|
|
71
|
+
const providers = injector.get(SCI_MENU_INJECTION_CONTEXT_PROVIDERS, [], { optional: true });
|
|
72
|
+
return providers.flatMap(providerFn => providerFn(menuContext));
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Injects accelerator targets for menu locations based on the current injection context.
|
|
76
|
+
*
|
|
77
|
+
* Must be called within an injection context, or an explicit {@link Injector} passed.
|
|
78
|
+
*
|
|
79
|
+
* @see provideMenuAcceleratorTargetProvider
|
|
80
|
+
* @docs-private Not public API. For internal use only.
|
|
81
|
+
*/
|
|
82
|
+
function injectMenuAcceleratorTargets(options) {
|
|
83
|
+
const injector = options?.injector ?? inject(Injector);
|
|
84
|
+
const providers = injector.get(SCI_MENU_ACCELERATOR_TARGET_PROVIDERS, [], { optional: true });
|
|
85
|
+
// Get accelerator targets available in the current injection context.
|
|
86
|
+
const acceleratorTargets = providers.map(providerFn => coerceSignal(providerFn(injector)));
|
|
87
|
+
// Concat accelerator targets.
|
|
88
|
+
return computed(() => acceleratorTargets.flatMap(acceleratorTarget => Arrays.coerce(acceleratorTarget?.())).map(coerceElement), { equal: Objects.isEqual });
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Multi-DI token for registering a {@link SciMenuContextProviderFn} in a specific environment.
|
|
92
|
+
*
|
|
93
|
+
* @see provideMenuContextProvider
|
|
94
|
+
*/
|
|
95
|
+
const SCI_MENU_CONTEXT_PROVIDER = new InjectionToken('SCI_MENU_CONTEXT_PROVIDER');
|
|
96
|
+
/**
|
|
97
|
+
* DI token to inject {@link SciMenuContextProviderFn} functions provided in the current and parent injectors.
|
|
98
|
+
*
|
|
99
|
+
* @see injectMenuContext
|
|
100
|
+
*/
|
|
101
|
+
const SCI_MENU_CONTEXT_PROVIDERS = new InjectionToken('SCI_MENU_CONTEXT_PROVIDERS');
|
|
102
|
+
/**
|
|
103
|
+
* Multi-DI token for registering a {@link SciMenuInjectionContextProviderFn} in a specific environment.
|
|
104
|
+
*
|
|
105
|
+
* @see provideMenuInjectionContextProvider
|
|
106
|
+
*/
|
|
107
|
+
const SCI_MENU_INJECTION_CONTEXT_PROVIDER = new InjectionToken('SCI_MENU_INJECTION_CONTEXT_PROVIDER');
|
|
108
|
+
/**
|
|
109
|
+
* DI token to inject {@link SciMenuInjectionContextProviderFn} functions provided in the current and parent injectors.
|
|
110
|
+
*
|
|
111
|
+
* @see injectMenuInjectionContextProviders
|
|
112
|
+
*/
|
|
113
|
+
const SCI_MENU_INJECTION_CONTEXT_PROVIDERS = new InjectionToken('SCI_MENU_INJECTION_CONTEXT_PROVIDERS');
|
|
114
|
+
/**
|
|
115
|
+
* Multi-DI token for registering a {@link SciMenuAcceleratorTargetProviderFn} in a specific environment.
|
|
116
|
+
*
|
|
117
|
+
* @see provideMenuAcceleratorTargetProvider
|
|
118
|
+
*/
|
|
119
|
+
const SCI_MENU_ACCELERATOR_TARGET_PROVIDER = new InjectionToken('SCI_MENU_ACCELERATOR_TARGET_PROVIDER');
|
|
120
|
+
/**
|
|
121
|
+
* DI token to inject {@link SciMenuAcceleratorTargetProviderFn} functions provided in the current and parent injectors.
|
|
122
|
+
*
|
|
123
|
+
*
|
|
124
|
+
* @see injectMenuAcceleratorTargets
|
|
125
|
+
*/
|
|
126
|
+
const SCI_MENU_ACCELERATOR_TARGET_PROVIDERS = new InjectionToken('SCI_MENU_ACCELERATOR_TARGET_PROVIDERS');
|
|
127
|
+
|
|
128
|
+
/*
|
|
129
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
130
|
+
*
|
|
131
|
+
* This program and the accompanying materials are made
|
|
132
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
133
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
134
|
+
*
|
|
135
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
136
|
+
*/
|
|
137
|
+
/**
|
|
138
|
+
* Installs accelerators for menu items contributed to the given location matching the specified context.
|
|
139
|
+
*
|
|
140
|
+
* An accelerator maps a physical key combination (key combined with modifiers such as `Ctrl`, `Shift`, or `Alt`) to an application action,
|
|
141
|
+
* enabling users to trigger an action without using a pointer device.
|
|
142
|
+
*
|
|
143
|
+
* Context and accelerator targets are inherited from the current injector hierarchy, but can be overridden via {@link SciMenuAcceleratorOptions.context} and {@link SciMenuAcceleratorOptions.target} options.
|
|
144
|
+
*
|
|
145
|
+
* This function must be called within an injection context, or an explicit {@link Injector} passed. Destroying the injection context uninstalls the accelerators.
|
|
146
|
+
*
|
|
147
|
+
* @param location - Specifies the menu, toolbar, or menubar for which to install accelerators.
|
|
148
|
+
* @param options - Controls the installation of accelerators.
|
|
149
|
+
* @returns Handle to uninstall the accelerators.
|
|
150
|
+
*
|
|
151
|
+
* @experimental since 22.3.0; API and behavior may change in any version without notice.
|
|
152
|
+
*/
|
|
153
|
+
function installMenuAccelerators(location, options) {
|
|
154
|
+
const injector = createDestroyableInjector({ parent: options?.injector ?? inject(Injector) });
|
|
155
|
+
return runInInjectionContext(injector, () => {
|
|
156
|
+
const environmentContext = injectMenuContext();
|
|
157
|
+
const environmentTargets = injectMenuAcceleratorTargets();
|
|
158
|
+
const context = computed(() => new Map([...environmentContext(), ...options?.context ?? new Map()]), /* @ts-ignore */
|
|
159
|
+
...(ngDevMode ? [{ debugName: "context" }] : /* istanbul ignore next */ []));
|
|
160
|
+
const menuItems = inject(ɵSciMenuService).menuItems(location, context, { metadata: options?.metadata });
|
|
161
|
+
effect(onCleanup => {
|
|
162
|
+
const tracked = {
|
|
163
|
+
menuItems: menuItems(),
|
|
164
|
+
environmentTargets: environmentTargets(),
|
|
165
|
+
};
|
|
166
|
+
untracked(() => {
|
|
167
|
+
const accelerators = ɵinstallMenuAccelerators(tracked.menuItems, {
|
|
168
|
+
targets: options?.target,
|
|
169
|
+
environmentTargets: tracked.environmentTargets,
|
|
170
|
+
injector,
|
|
171
|
+
});
|
|
172
|
+
onCleanup(() => accelerators.dispose());
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
return {
|
|
176
|
+
dispose: () => injector.destroy(),
|
|
177
|
+
};
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Installs accelerators for given menu items, recursively for menu items in submenus and groups.
|
|
182
|
+
*/
|
|
183
|
+
function ɵinstallMenuAccelerators(menuItems, options) {
|
|
184
|
+
assertNotInReactiveContext(installMenuAccelerators, 'Call installMenuAccelerators() in a non-reactive (non-tracking) context, such as within the untracked() function.');
|
|
185
|
+
const menuItemsWithAccelerator = collectMenuItemsWithAccelerator(menuItems);
|
|
186
|
+
if (!menuItemsWithAccelerator.length) {
|
|
187
|
+
return { dispose: noop };
|
|
188
|
+
}
|
|
189
|
+
const injector = createDestroyableInjector({ parent: options?.injector ?? inject(Injector) });
|
|
190
|
+
const zone = injector.get(NgZone);
|
|
191
|
+
const targets = coerceElements(options?.targets) ?? coerceElements(options?.environmentTargets) ?? [injector.get(DOCUMENT)];
|
|
192
|
+
const onSelectFn = options?.onSelect ?? (menuItem => menuItem.onSelect?.());
|
|
193
|
+
merge(...targets.map(target => fromEvent(target, 'keydown')))
|
|
194
|
+
.pipe(subscribeIn(fn => zone.runOutsideAngular(fn)), takeUntilDestroyed(injector.get(DestroyRef)))
|
|
195
|
+
.subscribe(event => {
|
|
196
|
+
// Skip if only pressing a modifier key.
|
|
197
|
+
switch (event.key) {
|
|
198
|
+
case 'Control':
|
|
199
|
+
case 'Shift':
|
|
200
|
+
case 'Alt':
|
|
201
|
+
case 'AltGraph':
|
|
202
|
+
return;
|
|
203
|
+
// UNDOCUMENTED: `event.key` can be `undefined`, for example, when selecting an option from an input element's datalist.
|
|
204
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
205
|
+
case undefined:
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
// Skip if not pressing a modifier key. Accelerators must have a modifier key.
|
|
209
|
+
if (!event.ctrlKey && !event.altKey && !event.shiftKey && !event.metaKey && !allowedSingleKeys.has(event.key.toLowerCase())) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const matchingMenuItems = menuItemsWithAccelerator.filter(menuItem => !menuItem.disabled?.() && matchesAccelerator(event, menuItem.accelerator));
|
|
213
|
+
if (!matchingMenuItems.length) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
// Invoke menu item handler.
|
|
217
|
+
event.preventDefault();
|
|
218
|
+
event.stopPropagation();
|
|
219
|
+
zone.run(() => matchingMenuItems.forEach(menuItem => onSelectFn(menuItem)));
|
|
220
|
+
});
|
|
221
|
+
return {
|
|
222
|
+
dispose: () => injector.destroy(),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Returns given native elements, or `undefined` if empty.
|
|
227
|
+
*/
|
|
228
|
+
function coerceElements(targets) {
|
|
229
|
+
const elements = Arrays.coerce(targets).map(coerceElement);
|
|
230
|
+
return elements.length ? elements : undefined;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Returns a flat list of all menu items that have an accelerator.
|
|
234
|
+
*/
|
|
235
|
+
function collectMenuItemsWithAccelerator(menuItemLikes) {
|
|
236
|
+
return menuItemLikes.reduce((menuItems, menuItemLike) => {
|
|
237
|
+
switch (menuItemLike.type) {
|
|
238
|
+
case 'menu-item': {
|
|
239
|
+
return menuItems
|
|
240
|
+
.concat(menuItemLike.accelerator ? menuItemLike : [])
|
|
241
|
+
.concat(collectMenuItemsWithAccelerator(menuItemLike.actions ?? []))
|
|
242
|
+
.concat(collectMenuItemsWithAccelerator(menuItemLike.menu?.children ?? []));
|
|
243
|
+
}
|
|
244
|
+
case 'group': {
|
|
245
|
+
return menuItems
|
|
246
|
+
.concat(collectMenuItemsWithAccelerator(menuItemLike.children))
|
|
247
|
+
.concat(collectMenuItemsWithAccelerator(menuItemLike.actions ?? []));
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}, new Array());
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Validates the passed menu accelerator to have a modifier, unless a function key, 'Delete', or 'Escape'.
|
|
254
|
+
*/
|
|
255
|
+
function validateMenuAccelerator(accelerator) {
|
|
256
|
+
if (!accelerator) {
|
|
257
|
+
return undefined;
|
|
258
|
+
}
|
|
259
|
+
if (!accelerator.ctrl && !accelerator.alt && !accelerator.shift && !allowedSingleKeys.has(accelerator.key.toLowerCase())) {
|
|
260
|
+
console.warn(`[MenuDefinitionError] Illegal menu accelerator. The key '${accelerator.key}' requires a modifier such as 'Ctrl', 'Shift', or 'Alt'. Only function keys F1-F12 and 'Delete' are allowed without a modifier. Examples: ['ctrl', 's'], ['F5'].`);
|
|
261
|
+
return undefined;
|
|
262
|
+
}
|
|
263
|
+
return accelerator;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Tests if the given keyboard event matches the specified accelerator.
|
|
267
|
+
*/
|
|
268
|
+
function matchesAccelerator(event, accelerator) {
|
|
269
|
+
// Fallback to code if key does not match to support diacritics like umlauts and accents.
|
|
270
|
+
const keyMatches = accelerator.key.toLowerCase() === event.key.toLowerCase() || `Key${accelerator.key.toUpperCase()}` === event.code;
|
|
271
|
+
if (!keyMatches) {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
// Handle Meta key as the Control key as on Apple devices the Command (⌘) key is used instead of the Control (Ctrl) key.
|
|
275
|
+
if ((accelerator.ctrl ?? false) !== (event.ctrlKey || event.metaKey)) {
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
if ((accelerator.shift ?? false) !== event.shiftKey) {
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
if ((accelerator.alt ?? false) !== event.altKey) {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
if (accelerator.location === 'left' && event.location !== 1) {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
if (accelerator.location === 'right' && event.location !== 2) {
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
if (accelerator.location === 'numpad' && event.location !== 3) {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Keys that can be used without a modifier (ctrl, alt, shift).
|
|
297
|
+
*/
|
|
298
|
+
const allowedSingleKeys = new Set([
|
|
299
|
+
...Array.from(Array(20).keys()).map(i => `f${i + 1}`), // F1 - F20
|
|
300
|
+
'delete',
|
|
301
|
+
]);
|
|
302
|
+
|
|
303
|
+
/*
|
|
304
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
305
|
+
*
|
|
306
|
+
* This program and the accompanying materials are made
|
|
307
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
308
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
309
|
+
*
|
|
310
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
311
|
+
*/
|
|
312
|
+
/**
|
|
313
|
+
* Translation note: Texts are translated when added (not lazily when rendered) to simplify filtering.
|
|
314
|
+
*
|
|
315
|
+
* @inheritDoc
|
|
316
|
+
*/
|
|
317
|
+
class ɵSciToolbarFactory {
|
|
318
|
+
menuItems = [];
|
|
319
|
+
/** @inheritDoc */
|
|
320
|
+
addToolbarButton(descriptor) {
|
|
321
|
+
this.menuItems.push(prune({
|
|
322
|
+
type: 'menu-item',
|
|
323
|
+
name: descriptor.name,
|
|
324
|
+
labelText: translate(getLabelIfText$1(descriptor.label)),
|
|
325
|
+
labelComponent: getLabelIfComponent$1(descriptor.label),
|
|
326
|
+
iconLigature: coerceSignal(getIconIfLigature$1(descriptor.icon)),
|
|
327
|
+
iconComponent: getIconIfComponent$1(descriptor.icon),
|
|
328
|
+
checked: coerceSignal(descriptor.checked),
|
|
329
|
+
tooltip: translate(descriptor.tooltip),
|
|
330
|
+
accelerator: validateMenuAccelerator(descriptor.accelerator),
|
|
331
|
+
disabled: coerceSignal(descriptor.disabled),
|
|
332
|
+
visible: coerceSignal(descriptor.visible),
|
|
333
|
+
cssClass: Arrays.coerce(descriptor.cssClass),
|
|
334
|
+
attributes: descriptor.attributes,
|
|
335
|
+
onSelect: async () => await descriptor.onSelect() ?? descriptor.checked === undefined, // Returning `true` will close the popover. Non-checkable items close by default.
|
|
336
|
+
}));
|
|
337
|
+
return this;
|
|
338
|
+
}
|
|
339
|
+
/** @inheritDoc */
|
|
340
|
+
addToolbarSplitButton(descriptor, menuFactoryFn) {
|
|
341
|
+
this.menuItems.push(prune({
|
|
342
|
+
type: 'menu-item',
|
|
343
|
+
name: descriptor.name,
|
|
344
|
+
labelText: translate(getLabelIfText$1(descriptor.label)),
|
|
345
|
+
labelComponent: getLabelIfComponent$1(descriptor.label),
|
|
346
|
+
iconLigature: coerceSignal(getIconIfLigature$1(descriptor.icon)),
|
|
347
|
+
iconComponent: getIconIfComponent$1(descriptor.icon),
|
|
348
|
+
checked: coerceSignal(descriptor.checked),
|
|
349
|
+
tooltip: translate(descriptor.tooltip),
|
|
350
|
+
accelerator: validateMenuAccelerator(descriptor.accelerator),
|
|
351
|
+
disabled: coerceSignal(descriptor.disabled),
|
|
352
|
+
visible: coerceSignal(descriptor.visible),
|
|
353
|
+
cssClass: Arrays.coerce(descriptor.cssClass),
|
|
354
|
+
attributes: descriptor.attributes,
|
|
355
|
+
menu: constructMenu(menuFactoryFn, descriptor.menu),
|
|
356
|
+
onSelect: async () => await descriptor.onSelect() ?? descriptor.checked === undefined, // Returning `true` will close the popover. Non-checkable items close by default.
|
|
357
|
+
}));
|
|
358
|
+
return this;
|
|
359
|
+
}
|
|
360
|
+
/** @inheritDoc */
|
|
361
|
+
addToolbarMenu(descriptor, menuFactoryFn) {
|
|
362
|
+
this.menuItems.push(prune({
|
|
363
|
+
type: 'menu-item',
|
|
364
|
+
name: descriptor.name,
|
|
365
|
+
labelText: translate(getLabelIfText$1(descriptor.label)),
|
|
366
|
+
labelComponent: getLabelIfComponent$1(descriptor.label),
|
|
367
|
+
iconLigature: coerceSignal(getIconIfLigature$1(descriptor.icon)),
|
|
368
|
+
iconComponent: getIconIfComponent$1(descriptor.icon),
|
|
369
|
+
tooltip: translate(descriptor.tooltip),
|
|
370
|
+
disabled: coerceSignal(descriptor.disabled),
|
|
371
|
+
visible: coerceSignal(descriptor.visible),
|
|
372
|
+
visualMenuIndicator: descriptor.visualMenuIndicator,
|
|
373
|
+
cssClass: Arrays.coerce(descriptor.cssClass),
|
|
374
|
+
attributes: descriptor.attributes,
|
|
375
|
+
menu: constructMenu(menuFactoryFn, descriptor.menu),
|
|
376
|
+
}));
|
|
377
|
+
return this;
|
|
378
|
+
}
|
|
379
|
+
/** @inheritDoc */
|
|
380
|
+
addToolbarControl(descriptor) {
|
|
381
|
+
this.menuItems.push(prune({
|
|
382
|
+
type: 'menu-item',
|
|
383
|
+
name: descriptor.name,
|
|
384
|
+
control: prune({
|
|
385
|
+
component: descriptor.component,
|
|
386
|
+
bindings: descriptor.bindings,
|
|
387
|
+
injector: descriptor.injector,
|
|
388
|
+
providers: descriptor.providers,
|
|
389
|
+
}),
|
|
390
|
+
tooltip: translate(descriptor.tooltip),
|
|
391
|
+
visible: coerceSignal(descriptor.visible),
|
|
392
|
+
cssClass: Arrays.coerce(descriptor.cssClass),
|
|
393
|
+
attributes: descriptor.attributes,
|
|
394
|
+
}));
|
|
395
|
+
return this;
|
|
396
|
+
}
|
|
397
|
+
/** @inheritDoc */
|
|
398
|
+
addToolbarSplitControl(descriptor, menuFactoryFn) {
|
|
399
|
+
this.menuItems.push(prune({
|
|
400
|
+
type: 'menu-item',
|
|
401
|
+
name: descriptor.name,
|
|
402
|
+
control: prune({
|
|
403
|
+
component: descriptor.component,
|
|
404
|
+
bindings: descriptor.bindings,
|
|
405
|
+
injector: descriptor.injector,
|
|
406
|
+
providers: descriptor.providers,
|
|
407
|
+
}),
|
|
408
|
+
tooltip: translate(descriptor.tooltip),
|
|
409
|
+
visible: coerceSignal(descriptor.visible),
|
|
410
|
+
cssClass: Arrays.coerce(descriptor.cssClass),
|
|
411
|
+
attributes: descriptor.attributes,
|
|
412
|
+
menu: constructMenu(menuFactoryFn, descriptor.menu),
|
|
413
|
+
}));
|
|
414
|
+
return this;
|
|
415
|
+
}
|
|
416
|
+
addGroup(factoryOrDescriptor, factoryIfDescriptor) {
|
|
417
|
+
const [descriptor, groupFactoryFn] = typeof factoryOrDescriptor === 'function' ? [{}, factoryOrDescriptor] : [factoryOrDescriptor, factoryIfDescriptor];
|
|
418
|
+
// Construct group.
|
|
419
|
+
const groupFactory = new ɵSciToolbarFactory();
|
|
420
|
+
groupFactoryFn?.(groupFactory);
|
|
421
|
+
// Add group.
|
|
422
|
+
this.menuItems.push(prune({
|
|
423
|
+
type: 'group',
|
|
424
|
+
name: descriptor.name,
|
|
425
|
+
disabled: coerceSignal(descriptor.disabled),
|
|
426
|
+
visible: coerceSignal(descriptor.visible),
|
|
427
|
+
children: groupFactory.menuItems,
|
|
428
|
+
cssClass: Arrays.coerce(descriptor.cssClass),
|
|
429
|
+
}));
|
|
430
|
+
return this;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
function getLabelIfText$1(label) {
|
|
434
|
+
if (typeof label === 'string' || isSignal(label)) {
|
|
435
|
+
return label;
|
|
436
|
+
}
|
|
437
|
+
return undefined;
|
|
438
|
+
}
|
|
439
|
+
function getLabelIfComponent$1(label) {
|
|
440
|
+
if (typeof label === 'function' && !isSignal(label)) {
|
|
441
|
+
return { component: label };
|
|
442
|
+
}
|
|
443
|
+
else if (typeof label === 'object') {
|
|
444
|
+
return label;
|
|
445
|
+
}
|
|
446
|
+
return undefined;
|
|
447
|
+
}
|
|
448
|
+
function getIconIfLigature$1(icon) {
|
|
449
|
+
if (typeof icon === 'string' || isSignal(icon)) {
|
|
450
|
+
return icon;
|
|
451
|
+
}
|
|
452
|
+
return undefined;
|
|
453
|
+
}
|
|
454
|
+
function getIconIfComponent$1(icon) {
|
|
455
|
+
if (typeof icon === 'function' && !isSignal(icon)) {
|
|
456
|
+
return { component: icon };
|
|
457
|
+
}
|
|
458
|
+
else if (typeof icon === 'object') {
|
|
459
|
+
return icon;
|
|
460
|
+
}
|
|
461
|
+
return undefined;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/*
|
|
465
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
466
|
+
*
|
|
467
|
+
* This program and the accompanying materials are made
|
|
468
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
469
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
470
|
+
*
|
|
471
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
472
|
+
*/
|
|
473
|
+
/**
|
|
474
|
+
* Translation note: Texts are translated when added (not lazily when rendered) to simplify filtering.
|
|
475
|
+
*/
|
|
476
|
+
class ɵSciMenuFactory {
|
|
477
|
+
menuItems = [];
|
|
478
|
+
/** @inheritDoc */
|
|
479
|
+
addMenuItem(descriptor) {
|
|
480
|
+
// Construct action toolbar.
|
|
481
|
+
const actionsFactory = new ɵSciToolbarFactory();
|
|
482
|
+
descriptor.actions?.(actionsFactory);
|
|
483
|
+
// Add menu item.
|
|
484
|
+
this.menuItems.push(prune({
|
|
485
|
+
type: 'menu-item',
|
|
486
|
+
name: descriptor.name,
|
|
487
|
+
labelText: translate(getLabelIfText(descriptor.label)),
|
|
488
|
+
labelComponent: getLabelIfComponent(descriptor.label),
|
|
489
|
+
iconLigature: coerceSignal(getIconIfLigature(descriptor.icon)),
|
|
490
|
+
iconComponent: getIconIfComponent(descriptor.icon),
|
|
491
|
+
checked: coerceSignal(descriptor.checked),
|
|
492
|
+
active: coerceSignal(descriptor.active),
|
|
493
|
+
tooltip: translate(descriptor.tooltip),
|
|
494
|
+
accelerator: validateMenuAccelerator(descriptor.accelerator),
|
|
495
|
+
disabled: coerceSignal(descriptor.disabled),
|
|
496
|
+
visible: coerceSignal(descriptor.visible),
|
|
497
|
+
actions: actionsFactory.menuItems,
|
|
498
|
+
matchesFilter: descriptor.onFilter,
|
|
499
|
+
cssClass: Arrays.coerce(descriptor.cssClass),
|
|
500
|
+
attributes: descriptor.attributes,
|
|
501
|
+
onSelect: async () => await descriptor.onSelect() ?? descriptor.checked === undefined, // Returning `true` will close the popover. Non-checkable items close by default.
|
|
502
|
+
}));
|
|
503
|
+
if (descriptor.checked && descriptor.icon) {
|
|
504
|
+
throw Error('[MenuDefinitionError] Cannot use `checked` and `icon` together.');
|
|
505
|
+
}
|
|
506
|
+
return this;
|
|
507
|
+
}
|
|
508
|
+
/** @inheritDoc */
|
|
509
|
+
addMenu(descriptor, menuFactoryFn) {
|
|
510
|
+
this.menuItems.push(prune({
|
|
511
|
+
type: 'menu-item',
|
|
512
|
+
name: descriptor.name,
|
|
513
|
+
labelText: translate(getLabelIfText(descriptor.label)),
|
|
514
|
+
labelComponent: getLabelIfComponent(descriptor.label),
|
|
515
|
+
iconLigature: coerceSignal(getIconIfLigature(descriptor.icon)),
|
|
516
|
+
iconComponent: getIconIfComponent(descriptor.icon),
|
|
517
|
+
disabled: coerceSignal(descriptor.disabled),
|
|
518
|
+
visible: coerceSignal(descriptor.visible),
|
|
519
|
+
cssClass: Arrays.coerce(descriptor.cssClass),
|
|
520
|
+
attributes: descriptor.attributes,
|
|
521
|
+
menu: constructMenu(menuFactoryFn, descriptor.menu),
|
|
522
|
+
}));
|
|
523
|
+
return this;
|
|
524
|
+
}
|
|
525
|
+
addGroup(factoryOrDescriptor, factoryIfDescriptor) {
|
|
526
|
+
const [descriptor, groupFactoryFn] = typeof factoryOrDescriptor === 'function' ? [{}, factoryOrDescriptor] : [factoryOrDescriptor, factoryIfDescriptor];
|
|
527
|
+
// Construct group.
|
|
528
|
+
const groupFactory = new ɵSciMenuFactory();
|
|
529
|
+
groupFactoryFn?.(groupFactory);
|
|
530
|
+
// Construct action toolbar.
|
|
531
|
+
const actionsFactory = new ɵSciToolbarFactory();
|
|
532
|
+
descriptor.actions?.(actionsFactory);
|
|
533
|
+
// Add group.
|
|
534
|
+
this.menuItems.push(prune({
|
|
535
|
+
type: 'group',
|
|
536
|
+
name: descriptor.name,
|
|
537
|
+
label: translate(descriptor.label),
|
|
538
|
+
collapsible: coerceCollapsibleGroupConfig(descriptor),
|
|
539
|
+
glyphArea: descriptor.glyphArea,
|
|
540
|
+
disabled: coerceSignal(descriptor.disabled),
|
|
541
|
+
visible: coerceSignal(descriptor.visible),
|
|
542
|
+
actions: actionsFactory.menuItems,
|
|
543
|
+
children: groupFactory.menuItems,
|
|
544
|
+
cssClass: Arrays.coerce(descriptor.cssClass),
|
|
545
|
+
}));
|
|
546
|
+
return this;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
function getLabelIfText(label) {
|
|
550
|
+
if (typeof label === 'string' || isSignal(label)) {
|
|
551
|
+
return label;
|
|
552
|
+
}
|
|
553
|
+
return undefined;
|
|
554
|
+
}
|
|
555
|
+
function getLabelIfComponent(label) {
|
|
556
|
+
if (typeof label === 'function' && !isSignal(label)) {
|
|
557
|
+
return { component: label };
|
|
558
|
+
}
|
|
559
|
+
else if (typeof label === 'object') {
|
|
560
|
+
return label;
|
|
561
|
+
}
|
|
562
|
+
return undefined;
|
|
563
|
+
}
|
|
564
|
+
function getIconIfLigature(icon) {
|
|
565
|
+
if (typeof icon === 'string' || isSignal(icon)) {
|
|
566
|
+
return icon;
|
|
567
|
+
}
|
|
568
|
+
return undefined;
|
|
569
|
+
}
|
|
570
|
+
function getIconIfComponent(icon) {
|
|
571
|
+
if (typeof icon === 'function' && !isSignal(icon)) {
|
|
572
|
+
return { component: icon };
|
|
573
|
+
}
|
|
574
|
+
else if (typeof icon === 'object') {
|
|
575
|
+
return icon;
|
|
576
|
+
}
|
|
577
|
+
return undefined;
|
|
578
|
+
}
|
|
579
|
+
function coerceCollapsibleGroupConfig(groupDescriptor) {
|
|
580
|
+
const collapsible = groupDescriptor.collapsible;
|
|
581
|
+
if (typeof collapsible === 'object') {
|
|
582
|
+
return collapsible;
|
|
583
|
+
}
|
|
584
|
+
return collapsible === true ? { collapsed: false } : undefined;
|
|
585
|
+
}
|
|
586
|
+
function constructMenu(menuFactoryFn, menuDescriptor) {
|
|
587
|
+
const menuFactory = new ɵSciMenuFactory();
|
|
588
|
+
menuFactoryFn?.(menuFactory);
|
|
589
|
+
return prune({
|
|
590
|
+
name: menuDescriptor?.name,
|
|
591
|
+
children: menuFactory.menuItems,
|
|
592
|
+
width: menuDescriptor?.width,
|
|
593
|
+
minWidth: menuDescriptor?.minWidth,
|
|
594
|
+
maxWidth: menuDescriptor?.maxWidth,
|
|
595
|
+
maxHeight: menuDescriptor?.maxHeight,
|
|
596
|
+
filter: coerceFilterConfig(menuDescriptor),
|
|
597
|
+
});
|
|
598
|
+
function coerceFilterConfig(menuDescriptor) {
|
|
599
|
+
const filter = menuDescriptor?.filter;
|
|
600
|
+
if (typeof filter === 'object') {
|
|
601
|
+
return prune({
|
|
602
|
+
placeholder: coerceSignal(filter.placeholder),
|
|
603
|
+
notFoundMessage: coerceSignal(filter.notFoundMessage),
|
|
604
|
+
focus: filter.focus,
|
|
605
|
+
}, { pruneIfEmpty: true });
|
|
606
|
+
}
|
|
607
|
+
return filter === true ? {} : undefined;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/*
|
|
612
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
613
|
+
*
|
|
614
|
+
* This program and the accompanying materials are made
|
|
615
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
616
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
617
|
+
*
|
|
618
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
619
|
+
*/
|
|
620
|
+
/**
|
|
621
|
+
* Sorts passed menu items based on its preferred {@link SciMenuItemLike.position}.
|
|
622
|
+
*
|
|
623
|
+
* This function is not recursive, not sorting menu items in submenus or groups.
|
|
624
|
+
*/
|
|
625
|
+
function sortMenuItems(menuItems) {
|
|
626
|
+
// Note: The standard JavaScript sorting algorithms cannot be used as it requires strict weak element ordering (transitivity), not given for elements positioned relative to each other.
|
|
627
|
+
const sorted = [];
|
|
628
|
+
const queue = new Set(menuItems);
|
|
629
|
+
// Add contributions which do not declare an insertion point or a non-existent insertion point.
|
|
630
|
+
queue.forEach(menuItem => {
|
|
631
|
+
// Add contribution if not declaring an insertion point.
|
|
632
|
+
if (!menuItem.position) {
|
|
633
|
+
sorted.push(menuItem);
|
|
634
|
+
queue.delete(menuItem);
|
|
635
|
+
}
|
|
636
|
+
// Add contribution if declaring a non-existent "before" insertion point.
|
|
637
|
+
else if (menuItem.position.before && !menuItems.some(it => it.name === menuItem.position.before)) {
|
|
638
|
+
sorted.push(menuItem);
|
|
639
|
+
queue.delete(menuItem);
|
|
640
|
+
}
|
|
641
|
+
// Add contribution if declaring a non-existent "after" insertion point.
|
|
642
|
+
else if (menuItem.position.after && !menuItems.some(it => it.name === menuItem.position.after)) {
|
|
643
|
+
sorted.push(menuItem);
|
|
644
|
+
queue.delete(menuItem);
|
|
645
|
+
}
|
|
646
|
+
});
|
|
647
|
+
// Add contributions at the start or end.
|
|
648
|
+
queue.forEach(menuItem => {
|
|
649
|
+
if (menuItem.position?.position === 'start') {
|
|
650
|
+
sorted.unshift(menuItem);
|
|
651
|
+
queue.delete(menuItem);
|
|
652
|
+
}
|
|
653
|
+
else if (menuItem.position?.position === 'end') {
|
|
654
|
+
sorted.push(menuItem);
|
|
655
|
+
queue.delete(menuItem);
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
// Add contributions declaring an insertion point. Multiple iterations may be required.
|
|
659
|
+
while (queue.size) {
|
|
660
|
+
const queueSize = queue.size;
|
|
661
|
+
for (const menuItem of queue) {
|
|
662
|
+
// Add contributions declaring a "before" insertion point.
|
|
663
|
+
if (menuItem.position?.before) {
|
|
664
|
+
const index = sorted.findIndex(candidate => candidate.name === menuItem.position.before);
|
|
665
|
+
if (index !== -1) {
|
|
666
|
+
sorted.splice(index, 0, menuItem);
|
|
667
|
+
queue.delete(menuItem);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
// Add contributions declaring an "after" insertion point.
|
|
671
|
+
else if (menuItem.position?.after) {
|
|
672
|
+
const index = sorted.findIndex(candidate => candidate.name === menuItem.position.after);
|
|
673
|
+
if (index !== -1) {
|
|
674
|
+
sorted.splice(index + 1, 0, menuItem);
|
|
675
|
+
queue.delete(menuItem);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
if (queue.size === queueSize) {
|
|
680
|
+
// TODO [menu] Better message, Algo error
|
|
681
|
+
console.warn('[@scion/components] Menu contributions reference non-existent menu items.', queue);
|
|
682
|
+
sorted.push(...queue);
|
|
683
|
+
queue.clear();
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
return sorted;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/*
|
|
690
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
691
|
+
*
|
|
692
|
+
* This program and the accompanying materials are made
|
|
693
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
694
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
695
|
+
*
|
|
696
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
697
|
+
*/
|
|
698
|
+
/**
|
|
699
|
+
* Provides registration instants for contributions.
|
|
700
|
+
*
|
|
701
|
+
* Two consecutive calls always have different instants, in ascending order.
|
|
702
|
+
*/
|
|
703
|
+
class SciMenuContributionInstantProvider {
|
|
704
|
+
_instant = 0;
|
|
705
|
+
/**
|
|
706
|
+
* Provides the next instant.
|
|
707
|
+
*/
|
|
708
|
+
next() {
|
|
709
|
+
return ++this._instant;
|
|
710
|
+
}
|
|
711
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciMenuContributionInstantProvider, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
712
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciMenuContributionInstantProvider, providedIn: 'root' });
|
|
713
|
+
}
|
|
714
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciMenuContributionInstantProvider, decorators: [{
|
|
715
|
+
type: Injectable,
|
|
716
|
+
args: [{ providedIn: 'root' }]
|
|
717
|
+
}] });
|
|
718
|
+
|
|
719
|
+
/*
|
|
720
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
721
|
+
*
|
|
722
|
+
* This program and the accompanying materials are made
|
|
723
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
724
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
725
|
+
*
|
|
726
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
727
|
+
*/
|
|
728
|
+
/**
|
|
729
|
+
* Formats a keyboard accelerator for display.
|
|
730
|
+
*
|
|
731
|
+
* If `tooltip` is specified, appends the accelerator in parentheses.
|
|
732
|
+
*/
|
|
733
|
+
class SciFormatAcceleratorPipe {
|
|
734
|
+
transform(accelerator, tooltip) {
|
|
735
|
+
if (!accelerator) {
|
|
736
|
+
return tooltip;
|
|
737
|
+
}
|
|
738
|
+
const modifiers = [];
|
|
739
|
+
if (accelerator.ctrl) {
|
|
740
|
+
modifiers.push(ctrlKeySymbol);
|
|
741
|
+
}
|
|
742
|
+
if (accelerator.shift) {
|
|
743
|
+
modifiers.push('Shift');
|
|
744
|
+
}
|
|
745
|
+
if (accelerator.alt) {
|
|
746
|
+
modifiers.push('Alt');
|
|
747
|
+
}
|
|
748
|
+
const key = formatKey(accelerator);
|
|
749
|
+
const formattedAccelerator = `${accelerator.location === 'numpad' ? `NumPad ` : ''}${modifiers.concat(key).join('+')}`;
|
|
750
|
+
return tooltip ? `${tooltip} (${formattedAccelerator})` : formattedAccelerator;
|
|
751
|
+
}
|
|
752
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciFormatAcceleratorPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
753
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.1.7", ngImport: i0, type: SciFormatAcceleratorPipe, isStandalone: true, name: "sciFormatAccelerator" });
|
|
754
|
+
}
|
|
755
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciFormatAcceleratorPipe, decorators: [{
|
|
756
|
+
type: Pipe,
|
|
757
|
+
args: [{ name: 'sciFormatAccelerator' }]
|
|
758
|
+
}] });
|
|
759
|
+
function formatKey(accelerator) {
|
|
760
|
+
switch (accelerator.key.toLowerCase()) {
|
|
761
|
+
case ' ':
|
|
762
|
+
return 'Space';
|
|
763
|
+
case 'arrowdown':
|
|
764
|
+
return 'Down';
|
|
765
|
+
case 'arrowup':
|
|
766
|
+
return 'Up';
|
|
767
|
+
case 'arrowleft':
|
|
768
|
+
return 'Left';
|
|
769
|
+
case 'arrowright':
|
|
770
|
+
return 'Right';
|
|
771
|
+
default:
|
|
772
|
+
return accelerator.key[0].toUpperCase() + accelerator.key.substring(1);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* Platform-specific name for the Control modifier key.
|
|
777
|
+
*
|
|
778
|
+
* Resolves to `⌘` on macOS and `Ctrl` on other operating systems.
|
|
779
|
+
*/
|
|
780
|
+
const ctrlKeySymbol = navigator.platform.startsWith('Mac') ? '⌘' : 'Ctrl';
|
|
781
|
+
|
|
782
|
+
/*
|
|
783
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
784
|
+
*
|
|
785
|
+
* This program and the accompanying materials are made
|
|
786
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
787
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
788
|
+
*
|
|
789
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
790
|
+
*/
|
|
791
|
+
/**
|
|
792
|
+
* Alias for {@link SciMenuComponent} with `sci-menu-group` as tag name.
|
|
793
|
+
*/
|
|
794
|
+
class SciMenuGroupComponent {
|
|
795
|
+
group = input.required(/* @ts-ignore */
|
|
796
|
+
...(ngDevMode ? [{ debugName: "group" }] : /* istanbul ignore next */ []));
|
|
797
|
+
glyphArea = input.required(/* @ts-ignore */
|
|
798
|
+
...(ngDevMode ? [{ debugName: "glyphArea" }] : /* istanbul ignore next */ []));
|
|
799
|
+
disabled = input(/* @ts-ignore */
|
|
800
|
+
...(ngDevMode ? [undefined, { debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
801
|
+
cssClass = input(/* @ts-ignore */
|
|
802
|
+
...(ngDevMode ? [undefined, { debugName: "cssClass" }] : /* istanbul ignore next */ []));
|
|
803
|
+
constructor() {
|
|
804
|
+
const menuComponent = createComponent(SciMenuComponent, {
|
|
805
|
+
elementInjector: inject(Injector),
|
|
806
|
+
environmentInjector: inject(EnvironmentInjector),
|
|
807
|
+
hostElement: inject(ElementRef).nativeElement,
|
|
808
|
+
bindings: [
|
|
809
|
+
inputBinding('menuItems', computed(() => this.group().children)),
|
|
810
|
+
inputBinding('disabled', this.disabled),
|
|
811
|
+
inputBinding('cssClass', this.cssClass),
|
|
812
|
+
inputBinding('glyphArea', this.glyphArea),
|
|
813
|
+
inputBinding('group', computed(() => ({
|
|
814
|
+
label: this.group().label?.(),
|
|
815
|
+
collapsible: !!this.group().collapsible,
|
|
816
|
+
collapsed: this.group().collapsible?.collapsed ?? false,
|
|
817
|
+
hideGlyphArea: this.group().glyphArea === false,
|
|
818
|
+
actions: this.group().actions ?? [],
|
|
819
|
+
}))),
|
|
820
|
+
],
|
|
821
|
+
directives: [
|
|
822
|
+
provideMenuComponentRole('group'),
|
|
823
|
+
],
|
|
824
|
+
});
|
|
825
|
+
// Attach component to Angular component tree for change detection.
|
|
826
|
+
inject(ApplicationRef).attachView(menuComponent.hostView);
|
|
827
|
+
// Destroy component when host is destroyed.
|
|
828
|
+
inject(DestroyRef).onDestroy(() => menuComponent.destroy());
|
|
829
|
+
}
|
|
830
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciMenuGroupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
831
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.7", type: SciMenuGroupComponent, isStandalone: true, selector: "sci-menu-group", inputs: { group: { classPropertyName: "group", publicName: "group", isSignal: true, isRequired: true, transformFunction: null }, glyphArea: { classPropertyName: "glyphArea", publicName: "glyphArea", isSignal: true, isRequired: true, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, cssClass: { classPropertyName: "cssClass", publicName: "cssClass", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: '', isInline: true });
|
|
832
|
+
}
|
|
833
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciMenuGroupComponent, decorators: [{
|
|
834
|
+
type: Component,
|
|
835
|
+
args: [{
|
|
836
|
+
selector: 'sci-menu-group',
|
|
837
|
+
template: '', // Template provided by `SciMenuComponent`.
|
|
838
|
+
}]
|
|
839
|
+
}], ctorParameters: () => [], propDecorators: { group: [{ type: i0.Input, args: [{ isSignal: true, alias: "group", required: true }] }], glyphArea: [{ type: i0.Input, args: [{ isSignal: true, alias: "glyphArea", required: true }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], cssClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "cssClass", required: false }] }] } });
|
|
840
|
+
|
|
841
|
+
/*
|
|
842
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
843
|
+
*
|
|
844
|
+
* This program and the accompanying materials are made
|
|
845
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
846
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
847
|
+
*
|
|
848
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
849
|
+
*/
|
|
850
|
+
class SciMenuFilterComponent {
|
|
851
|
+
placeholder = input.required(/* @ts-ignore */
|
|
852
|
+
...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
853
|
+
text = model('', /* @ts-ignore */
|
|
854
|
+
...(ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []));
|
|
855
|
+
_inputField = viewChild.required('input', /* @ts-ignore */
|
|
856
|
+
...(ngDevMode ? [{ debugName: "_inputField" }] : /* istanbul ignore next */ []));
|
|
857
|
+
focus() {
|
|
858
|
+
this._inputField().nativeElement.focus();
|
|
859
|
+
}
|
|
860
|
+
onClear() {
|
|
861
|
+
this.text.set('');
|
|
862
|
+
this.focus();
|
|
863
|
+
}
|
|
864
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciMenuFilterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
865
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.7", type: SciMenuFilterComponent, isStandalone: true, selector: "sci-menu-filter", inputs: { placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: true, transformFunction: null }, text: { classPropertyName: "text", publicName: "text", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { text: "textChange" }, host: { listeners: { "focus": "focus()" }, properties: { "attr.tabindex": "-1" } }, viewQueries: [{ propertyName: "_inputField", first: true, predicate: ["input"], descendants: true, isSignal: true }], ngImport: i0, template: "<input type=\"text\" autocomplete=\"off\" class=\"e2e-menu-filter-input\" [attr.placeholder]=\"placeholder()\" [(ngModel)]=\"text\" #input>\r\n<button class=\"clear\" (click)=\"onClear()\" [title]=\"('%scion.components.clear.tooltip' | sciText)()\" tabindex=\"-1\">\r\n <sci-icon>scion.clear</sci-icon>\r\n</button>\r\n", styles: [":host{display:flex}:host>input{all:unset;flex:1 1 0;appearance:auto;width:0;min-width:0}:host>input::placeholder{color:var(--sci-color-gray-400)}:host>input:placeholder-shown+button:is(.clear,#sci-reset){visibility:hidden}:host>button:is(.clear,#sci-reset){all:unset;display:inline-grid;place-content:center;place-items:center;padding:.25em;border-radius:var(--sci-button-border-radius);-webkit-user-select:none;user-select:none;overflow:hidden;cursor:var(--sci-button-cursor);--sci-icon-size: var(--sci-button-icon-size)}:host>button:is(.clear,#sci-reset):hover:where(:not(:disabled)){background-color:var(--sci-button-background-color-hover)}:host>button:is(.clear,#sci-reset):active:where(:not(:disabled)){background-color:var(--sci-button-background-color-active)}:host>button:is(.clear,#sci-reset):focus:not(:focus-visible){outline:none}:host>button:is(.clear,#sci-reset):focus-visible{outline:var(--sci-button-outline-width) solid var(--sci-color-accent)}:host>button:is(.clear,#sci-reset):disabled{color:var(--sci-button-text-color-disabled)}:host>button:is(.clear,#sci-reset){padding:1px}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: SciIconComponent, selector: "sci-icon" }, { kind: "pipe", type: SciTextPipe, name: "sciText" }] });
|
|
866
|
+
}
|
|
867
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciMenuFilterComponent, decorators: [{
|
|
868
|
+
type: Component,
|
|
869
|
+
args: [{ selector: 'sci-menu-filter', imports: [
|
|
870
|
+
FormsModule,
|
|
871
|
+
SciTextPipe,
|
|
872
|
+
SciIconComponent,
|
|
873
|
+
], host: {
|
|
874
|
+
'[attr.tabindex]': '-1', // enable delegation of programmatic focus requests
|
|
875
|
+
'(focus)': 'focus()',
|
|
876
|
+
}, template: "<input type=\"text\" autocomplete=\"off\" class=\"e2e-menu-filter-input\" [attr.placeholder]=\"placeholder()\" [(ngModel)]=\"text\" #input>\r\n<button class=\"clear\" (click)=\"onClear()\" [title]=\"('%scion.components.clear.tooltip' | sciText)()\" tabindex=\"-1\">\r\n <sci-icon>scion.clear</sci-icon>\r\n</button>\r\n", styles: [":host{display:flex}:host>input{all:unset;flex:1 1 0;appearance:auto;width:0;min-width:0}:host>input::placeholder{color:var(--sci-color-gray-400)}:host>input:placeholder-shown+button:is(.clear,#sci-reset){visibility:hidden}:host>button:is(.clear,#sci-reset){all:unset;display:inline-grid;place-content:center;place-items:center;padding:.25em;border-radius:var(--sci-button-border-radius);-webkit-user-select:none;user-select:none;overflow:hidden;cursor:var(--sci-button-cursor);--sci-icon-size: var(--sci-button-icon-size)}:host>button:is(.clear,#sci-reset):hover:where(:not(:disabled)){background-color:var(--sci-button-background-color-hover)}:host>button:is(.clear,#sci-reset):active:where(:not(:disabled)){background-color:var(--sci-button-background-color-active)}:host>button:is(.clear,#sci-reset):focus:not(:focus-visible){outline:none}:host>button:is(.clear,#sci-reset):focus-visible{outline:var(--sci-button-outline-width) solid var(--sci-color-accent)}:host>button:is(.clear,#sci-reset):disabled{color:var(--sci-button-text-color-disabled)}:host>button:is(.clear,#sci-reset){padding:1px}\n"] }]
|
|
877
|
+
}], propDecorators: { placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: true }] }], text: [{ type: i0.Input, args: [{ isSignal: true, alias: "text", required: false }] }, { type: i0.Output, args: ["textChange"] }], _inputField: [{ type: i0.ViewChild, args: ['input', { isSignal: true }] }] } });
|
|
878
|
+
|
|
879
|
+
/*
|
|
880
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
881
|
+
*
|
|
882
|
+
* This program and the accompanying materials are made
|
|
883
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
884
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
885
|
+
*
|
|
886
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
887
|
+
*/
|
|
888
|
+
/**
|
|
889
|
+
* Filters menu items based on the passed filter text.
|
|
890
|
+
*
|
|
891
|
+
* Filtering is hierarchical; submenus and groups inherit the filter of their parent menus.
|
|
892
|
+
* A menu item matches only if matching its menu filter and all parent menu filters.
|
|
893
|
+
*
|
|
894
|
+
* Can be injected into {@link SciMenuComponent}.
|
|
895
|
+
*/
|
|
896
|
+
class MenuFilter {
|
|
897
|
+
_parentMenuFilter;
|
|
898
|
+
_filterText = signal(null, /* @ts-ignore */
|
|
899
|
+
...(ngDevMode ? [{ debugName: "_filterText" }] : /* istanbul ignore next */ []));
|
|
900
|
+
/**
|
|
901
|
+
* Indicates whether filtering is active.
|
|
902
|
+
*/
|
|
903
|
+
active = computed(() => {
|
|
904
|
+
return !!this._filterText() || !!this._parentMenuFilter?.active();
|
|
905
|
+
}, /* @ts-ignore */
|
|
906
|
+
...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
|
|
907
|
+
constructor(_parentMenuFilter) {
|
|
908
|
+
this._parentMenuFilter = _parentMenuFilter;
|
|
909
|
+
}
|
|
910
|
+
/**
|
|
911
|
+
* Sets the filter for filtering menu items.
|
|
912
|
+
*/
|
|
913
|
+
setFilterText(filterText) {
|
|
914
|
+
this._filterText.set(filterText || null); // eslint-disable-line @typescript-eslint/prefer-nullish-coalescing
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* Tests whether the given menu item matches the filter.
|
|
918
|
+
*/
|
|
919
|
+
matches(menuItem) {
|
|
920
|
+
return computed(() => {
|
|
921
|
+
const filterText = this._filterText();
|
|
922
|
+
// Test if the menu item matches the filter using its filter function, if any.
|
|
923
|
+
if (filterText && menuItem.matchesFilter && !menuItem.matchesFilter(filterText)) {
|
|
924
|
+
return false;
|
|
925
|
+
}
|
|
926
|
+
// Test if the display text of the menu item matches the filter.
|
|
927
|
+
if (filterText && !menuItem.matchesFilter && menuItem.labelText && !menuItem.labelText().toLowerCase().includes(filterText.toLowerCase())) {
|
|
928
|
+
return false;
|
|
929
|
+
}
|
|
930
|
+
// Test if the menu item matches the parent filter, if any.
|
|
931
|
+
if (this._parentMenuFilter && !this._parentMenuFilter.matches(menuItem)()) {
|
|
932
|
+
return false;
|
|
933
|
+
}
|
|
934
|
+
return true;
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
/*
|
|
940
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
941
|
+
*
|
|
942
|
+
* This program and the accompanying materials are made
|
|
943
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
944
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
945
|
+
*
|
|
946
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
947
|
+
*/
|
|
948
|
+
/**
|
|
949
|
+
* Represents a semantic element for a toolbar control provided as slotted content.
|
|
950
|
+
*/
|
|
951
|
+
class SciToolbarControlComponent {
|
|
952
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciToolbarControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
953
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: SciToolbarControlComponent, isStandalone: true, selector: "sci-toolbar-control", ngImport: i0, template: '<ng-content/>', isInline: true });
|
|
954
|
+
}
|
|
955
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciToolbarControlComponent, decorators: [{
|
|
956
|
+
type: Component,
|
|
957
|
+
args: [{
|
|
958
|
+
selector: 'sci-toolbar-control',
|
|
959
|
+
template: '<ng-content/>',
|
|
960
|
+
}]
|
|
961
|
+
}] });
|
|
962
|
+
|
|
963
|
+
/*
|
|
964
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
965
|
+
*
|
|
966
|
+
* This program and the accompanying materials are made
|
|
967
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
968
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
969
|
+
*
|
|
970
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
971
|
+
*/
|
|
972
|
+
/**
|
|
973
|
+
* Represents a semantic element for a toolbar split button provided as slotted content.
|
|
974
|
+
*/
|
|
975
|
+
class SciToolbarSplitButtonComponent {
|
|
976
|
+
host = inject(ElementRef).nativeElement;
|
|
977
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciToolbarSplitButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
978
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: SciToolbarSplitButtonComponent, isStandalone: true, selector: "sci-toolbar-split-button", ngImport: i0, template: '<ng-content/>', isInline: true });
|
|
979
|
+
}
|
|
980
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciToolbarSplitButtonComponent, decorators: [{
|
|
981
|
+
type: Component,
|
|
982
|
+
args: [{
|
|
983
|
+
selector: 'sci-toolbar-split-button',
|
|
984
|
+
template: '<ng-content/>',
|
|
985
|
+
}]
|
|
986
|
+
}] });
|
|
987
|
+
|
|
988
|
+
/*
|
|
989
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
990
|
+
*
|
|
991
|
+
* This program and the accompanying materials are made
|
|
992
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
993
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
994
|
+
*
|
|
995
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
996
|
+
*/
|
|
997
|
+
/**
|
|
998
|
+
* Represents a semantic element for the icon of a toolbar item provided as slotted content.
|
|
999
|
+
*/
|
|
1000
|
+
class SciToolbarIconComponent {
|
|
1001
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciToolbarIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1002
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: SciToolbarIconComponent, isStandalone: true, selector: "sci-toolbar-icon", ngImport: i0, template: '<ng-content/>', isInline: true });
|
|
1003
|
+
}
|
|
1004
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciToolbarIconComponent, decorators: [{
|
|
1005
|
+
type: Component,
|
|
1006
|
+
args: [{
|
|
1007
|
+
selector: 'sci-toolbar-icon',
|
|
1008
|
+
template: '<ng-content/>',
|
|
1009
|
+
}]
|
|
1010
|
+
}] });
|
|
1011
|
+
|
|
1012
|
+
/*
|
|
1013
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
1014
|
+
*
|
|
1015
|
+
* This program and the accompanying materials are made
|
|
1016
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
1017
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
1018
|
+
*
|
|
1019
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
1020
|
+
*/
|
|
1021
|
+
/**
|
|
1022
|
+
* Represents a semantic element for the label of a toolbar item provided as slotted content.
|
|
1023
|
+
*/
|
|
1024
|
+
class SciToolbarLabelComponent {
|
|
1025
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciToolbarLabelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1026
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: SciToolbarLabelComponent, isStandalone: true, selector: "sci-toolbar-label", ngImport: i0, template: '<ng-content/>', isInline: true });
|
|
1027
|
+
}
|
|
1028
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciToolbarLabelComponent, decorators: [{
|
|
1029
|
+
type: Component,
|
|
1030
|
+
args: [{
|
|
1031
|
+
selector: 'sci-toolbar-label',
|
|
1032
|
+
template: '<ng-content/>',
|
|
1033
|
+
}]
|
|
1034
|
+
}] });
|
|
1035
|
+
|
|
1036
|
+
/*
|
|
1037
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
1038
|
+
*
|
|
1039
|
+
* This program and the accompanying materials are made
|
|
1040
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
1041
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
1042
|
+
*
|
|
1043
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
1044
|
+
*/
|
|
1045
|
+
/**
|
|
1046
|
+
* Provides a reference to the menu's popover element and child popover, if any.
|
|
1047
|
+
*
|
|
1048
|
+
* A child popover can be a submenu or a menu opened from a menu item's action toolbar.
|
|
1049
|
+
*
|
|
1050
|
+
* Can be injected into {@link SciMenuComponent}.
|
|
1051
|
+
*/
|
|
1052
|
+
class PopoverRef {
|
|
1053
|
+
_element = inject(ElementRef).nativeElement;
|
|
1054
|
+
_parentPopover = inject(PopoverRef, { skipSelf: true, optional: true });
|
|
1055
|
+
/**
|
|
1056
|
+
* Reference to the child popover, if any. Can be a submenu or a menu opened from a menu item's action toolbar.
|
|
1057
|
+
*/
|
|
1058
|
+
childPopover = signal(undefined, /* @ts-ignore */
|
|
1059
|
+
...(ngDevMode ? [{ debugName: "childPopover" }] : /* istanbul ignore next */ []));
|
|
1060
|
+
/**
|
|
1061
|
+
* Indicates if this is a submenu popover, i.e., not a top-level menu nor a menu opened from a menu item's action toolbar.
|
|
1062
|
+
*/
|
|
1063
|
+
isSubmenu;
|
|
1064
|
+
constructor(options) {
|
|
1065
|
+
this.isSubmenu = options.isSubmenu;
|
|
1066
|
+
this.registerPopover();
|
|
1067
|
+
this.registerPopoverInParentPopover();
|
|
1068
|
+
}
|
|
1069
|
+
/**
|
|
1070
|
+
* Registers this popover in {@link POPOVER_REFS}.
|
|
1071
|
+
*/
|
|
1072
|
+
registerPopover() {
|
|
1073
|
+
const popovers = inject(POPOVER_REFS);
|
|
1074
|
+
// Register this popover.
|
|
1075
|
+
popovers.update(popovers => new Set(popovers).add(this));
|
|
1076
|
+
// Unregister popover when destroyed.
|
|
1077
|
+
inject(DestroyRef).onDestroy(() => popovers.update(popovers => {
|
|
1078
|
+
const copy = new Set(popovers);
|
|
1079
|
+
copy.delete(this);
|
|
1080
|
+
return copy;
|
|
1081
|
+
}));
|
|
1082
|
+
}
|
|
1083
|
+
/**
|
|
1084
|
+
* Registers this popover as child popover in the parent {@link PopoverRef}, if any.
|
|
1085
|
+
*/
|
|
1086
|
+
registerPopoverInParentPopover() {
|
|
1087
|
+
const parentPopover = this._parentPopover;
|
|
1088
|
+
if (!parentPopover) {
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
parentPopover.childPopover.set(this);
|
|
1092
|
+
// Unregister popover when destroyed.
|
|
1093
|
+
inject(DestroyRef).onDestroy(() => parentPopover.childPopover.update(childPopover => childPopover == this ? undefined : childPopover));
|
|
1094
|
+
}
|
|
1095
|
+
/**
|
|
1096
|
+
* Closes this popover, and parent popovers if specified.
|
|
1097
|
+
*
|
|
1098
|
+
* Defaults to not closing parent popovers.
|
|
1099
|
+
*/
|
|
1100
|
+
close(options) {
|
|
1101
|
+
this._element.hidePopover();
|
|
1102
|
+
if (options?.closeParents) {
|
|
1103
|
+
this._parentPopover?.close(options);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
/**
|
|
1108
|
+
* DI token to inject currently open menu popovers.
|
|
1109
|
+
*/
|
|
1110
|
+
const POPOVER_REFS = new InjectionToken('POPOVER_REFS', {
|
|
1111
|
+
providedIn: 'root',
|
|
1112
|
+
factory: () => signal(new Set()),
|
|
1113
|
+
});
|
|
1114
|
+
|
|
1115
|
+
/*
|
|
1116
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
1117
|
+
*
|
|
1118
|
+
* This program and the accompanying materials are made
|
|
1119
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
1120
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
1121
|
+
*
|
|
1122
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
1123
|
+
*/
|
|
1124
|
+
/**
|
|
1125
|
+
* Represents a visually separated group of related items within a toolbar.
|
|
1126
|
+
*/
|
|
1127
|
+
class SciToolbarGroupComponent {
|
|
1128
|
+
/**
|
|
1129
|
+
* Specifies the toolbar items to display in this group.
|
|
1130
|
+
*/
|
|
1131
|
+
menuItems = input.required(/* @ts-ignore */
|
|
1132
|
+
...(ngDevMode ? [{ debugName: "menuItems" }] : /* istanbul ignore next */ []));
|
|
1133
|
+
/**
|
|
1134
|
+
* Specifies the orientation of the items in this group.
|
|
1135
|
+
*/
|
|
1136
|
+
orientation = input.required(/* @ts-ignore */
|
|
1137
|
+
...(ngDevMode ? [{ debugName: "orientation" }] : /* istanbul ignore next */ []));
|
|
1138
|
+
/**
|
|
1139
|
+
* Controls whether to disable the items in this group.
|
|
1140
|
+
*/
|
|
1141
|
+
disabled = input(/* @ts-ignore */
|
|
1142
|
+
...(ngDevMode ? [undefined, { debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
1143
|
+
/**
|
|
1144
|
+
* Controls where to attach popovers in the DOM.
|
|
1145
|
+
*/
|
|
1146
|
+
popoverViewContainerRef = input.required(/* @ts-ignore */
|
|
1147
|
+
...(ngDevMode ? [{ debugName: "popoverViewContainerRef" }] : /* istanbul ignore next */ []));
|
|
1148
|
+
/**
|
|
1149
|
+
* Indicates whether any menu within this toolbar group is currently open.
|
|
1150
|
+
*/
|
|
1151
|
+
menuOpen = this.computeMenuOpen();
|
|
1152
|
+
_childToolbarGroups = viewChildren(SciToolbarGroupComponent, /* @ts-ignore */
|
|
1153
|
+
...(ngDevMode ? [{ debugName: "_childToolbarGroups" }] : /* istanbul ignore next */ []));
|
|
1154
|
+
/** Reference to the popover. Only set if displayed in a menu (menu item action toolbar). */
|
|
1155
|
+
_popoverRef = inject(PopoverRef, { optional: true });
|
|
1156
|
+
activeMenuItem = linkedSignal({ ...(ngDevMode ? { debugName: "activeMenuItem" } : /* istanbul ignore next */ {}), source: this.menuItems, // Unset when menu items change.
|
|
1157
|
+
computation: () => null });
|
|
1158
|
+
constructor() {
|
|
1159
|
+
this.installMenuOpener();
|
|
1160
|
+
}
|
|
1161
|
+
/**
|
|
1162
|
+
* Method invoked when clicking on a toolbar button.
|
|
1163
|
+
*/
|
|
1164
|
+
async onSelect(menuItem) {
|
|
1165
|
+
if (await menuItem.onSelect()) {
|
|
1166
|
+
this._popoverRef?.close();
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
/**
|
|
1170
|
+
* Method invoked when clicking on a menu button.
|
|
1171
|
+
*/
|
|
1172
|
+
onToolbarMenuButtonClick(menuItem, element) {
|
|
1173
|
+
this.activeMenuItem.update(activeMenuItem => activeMenuItem?.menuItem === menuItem ? null : { menuItem, element });
|
|
1174
|
+
}
|
|
1175
|
+
/**
|
|
1176
|
+
* Method invoked before clicking on a menu button.
|
|
1177
|
+
*
|
|
1178
|
+
* When clicking on the active menu button, mark it as closing to prevent immediate re-opening race condition on subsequent click event.
|
|
1179
|
+
*/
|
|
1180
|
+
onToolbarMenuButtonMouseDown(menuItem) {
|
|
1181
|
+
const activeMenuItem = this.activeMenuItem();
|
|
1182
|
+
if (activeMenuItem?.menuItem === menuItem) {
|
|
1183
|
+
activeMenuItem.closing = true;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
/**
|
|
1187
|
+
* Method invoked when moving the pointer out of a menu button.
|
|
1188
|
+
*
|
|
1189
|
+
* Clean up closing state set by {@link onToolbarMenuButtonMouseDown}, if any.
|
|
1190
|
+
*/
|
|
1191
|
+
onToolbarMenuButtonMouseLeave(menuItem) {
|
|
1192
|
+
const activeMenuItem = this.activeMenuItem();
|
|
1193
|
+
if (activeMenuItem?.menuItem !== menuItem) {
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
if (activeMenuItem.closing === 'prevented') {
|
|
1197
|
+
this.activeMenuItem.set(null); // unset active menu
|
|
1198
|
+
}
|
|
1199
|
+
else if (activeMenuItem.closing) {
|
|
1200
|
+
delete activeMenuItem.closing; // unset closing state
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
/**
|
|
1204
|
+
* Opens a menu based on {@link activeMenuItem}.
|
|
1205
|
+
*/
|
|
1206
|
+
installMenuOpener() {
|
|
1207
|
+
const menuService = inject(ɵSciMenuService);
|
|
1208
|
+
effect(onCleanup => {
|
|
1209
|
+
if (!this.activeMenuItem()) {
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
const { menuItem, element } = this.activeMenuItem();
|
|
1213
|
+
const menu = menuItem.menu;
|
|
1214
|
+
const align = this.orientation() === 'horizontal' ? 'vertical' : 'horizontal';
|
|
1215
|
+
untracked(() => {
|
|
1216
|
+
const menuRef = menuService.open(menu.children, {
|
|
1217
|
+
anchor: element,
|
|
1218
|
+
viewContainerRef: this.popoverViewContainerRef(),
|
|
1219
|
+
filter: menu.filter,
|
|
1220
|
+
width: menu.width,
|
|
1221
|
+
minWidth: menu.minWidth,
|
|
1222
|
+
maxWidth: menu.maxWidth,
|
|
1223
|
+
maxHeight: menu.maxHeight,
|
|
1224
|
+
cssClass: menuItem.cssClass,
|
|
1225
|
+
attributes: menuItem.attributes,
|
|
1226
|
+
offset: {
|
|
1227
|
+
x: align === 'horizontal' ? 1 : 0,
|
|
1228
|
+
y: align === 'vertical' ? 1 : 0,
|
|
1229
|
+
},
|
|
1230
|
+
align,
|
|
1231
|
+
});
|
|
1232
|
+
// Close when opening another menu.
|
|
1233
|
+
onCleanup(() => menuRef.close());
|
|
1234
|
+
// Unset when closing menu.
|
|
1235
|
+
menuRef.onClose(() => {
|
|
1236
|
+
const activeMenuItem = this.activeMenuItem();
|
|
1237
|
+
// Do not unset if another menu was opened in the meantime.
|
|
1238
|
+
if (activeMenuItem?.menuItem !== menuItem) {
|
|
1239
|
+
return;
|
|
1240
|
+
}
|
|
1241
|
+
// Prevent immediate re-opening race condition when toolbar button is clicked to close.
|
|
1242
|
+
if (activeMenuItem.closing) {
|
|
1243
|
+
activeMenuItem.closing = 'prevented';
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
this.activeMenuItem.set(null);
|
|
1247
|
+
});
|
|
1248
|
+
});
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
/**
|
|
1252
|
+
* Computes whether any menu within this toolbar group is currently open.
|
|
1253
|
+
*/
|
|
1254
|
+
computeMenuOpen() {
|
|
1255
|
+
// Do not return `computed` signal to prevent illegal access to required component inputs until Angular has completed the initialization of this component,
|
|
1256
|
+
// happening when reading the signal from the parent component's template, e.g., via reference variable.
|
|
1257
|
+
const menuOpen = signal(false, /* @ts-ignore */
|
|
1258
|
+
...(ngDevMode ? [{ debugName: "menuOpen" }] : /* istanbul ignore next */ []));
|
|
1259
|
+
effect(() => {
|
|
1260
|
+
menuOpen.set(!!this.activeMenuItem() || this._childToolbarGroups().some(childGroup => childGroup.menuOpen()));
|
|
1261
|
+
});
|
|
1262
|
+
return menuOpen;
|
|
1263
|
+
}
|
|
1264
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciToolbarGroupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1265
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: SciToolbarGroupComponent, isStandalone: true, selector: "sci-toolbar-group", inputs: { menuItems: { classPropertyName: "menuItems", publicName: "menuItems", isSignal: true, isRequired: true, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: true, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, popoverViewContainerRef: { classPropertyName: "popoverViewContainerRef", publicName: "popoverViewContainerRef", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "attr.data-orientation": "orientation()" } }, viewQueries: [{ propertyName: "_childToolbarGroups", predicate: SciToolbarGroupComponent, descendants: true, isSignal: true }], ngImport: i0, template: "@for (menuItem of menuItems(); track $index) {\r\n @let disabled = this.disabled() || menuItem.disabled?.() || false;\r\n @switch (menuItem.type) {\r\n <!-- Toolbar Item -->\r\n @case ('menu-item') {\r\n @let isSplitButton = (menuItem.onSelect || menuItem.control) && menuItem.menu?.children?.length;\r\n\r\n @if (isSplitButton) {\r\n <sci-toolbar-split-button [class]=\"menuItem.cssClass\" #split_button_element>\r\n <ng-container *ngTemplateOutlet=\"menu_item_template; context: {anchor: split_button_element.host}\"/>\r\n </sci-toolbar-split-button>\r\n } @else {\r\n <ng-container *ngTemplateOutlet=\"menu_item_template\"/>\r\n }\r\n\r\n <!-- Template for rendering the content of a toolbar item. -->\r\n <ng-template #menu_item_template let-anchor=\"anchor\">\r\n <!-- Render push or toggle button. -->\r\n @if (menuItem.onSelect) {\r\n <button [attr.disabled]=\"disabled || null\"\r\n [attr.title]=\"menuItem.accelerator | sciFormatAccelerator:menuItem.tooltip?.()\"\r\n [class.checked]=\"menuItem.checked?.()\"\r\n [class]=\"menuItem.cssClass\" class=\"menu-item toolbar-button e2e-menu-item\"\r\n [sciAttributes]=\"menuItem.attributes\"\r\n (click)=\"onSelect(menuItem)\">\r\n <!-- Render icon, if any. -->\r\n <ng-container *ngTemplateOutlet=\"icon_template; context: {$implicit: menuItem}\"/>\r\n <!-- Render label, if any. -->\r\n <ng-container *ngTemplateOutlet=\"label_template; context: {$implicit: menuItem}\"/>\r\n </button>\r\n } @else if (menuItem.control) {\r\n <!-- Render custom control. -->\r\n <sci-toolbar-control [attr.title]=\"menuItem.accelerator | sciFormatAccelerator:menuItem.tooltip?.()\"\r\n [class]=\"menuItem.cssClass\" class=\"e2e-menu-item\"\r\n [sciAttributes]=\"menuItem.attributes\">\r\n <ng-container *sciComponentOutlet=\"menuItem.control\"/>\r\n </sci-toolbar-control>\r\n }\r\n\r\n <!-- Render menu button. -->\r\n @if (menuItem.menu?.children?.length) {\r\n @let hasLabel = menuItem.labelText || menuItem.labelComponent;\r\n @let hasIcon = menuItem.iconLigature || menuItem.iconComponent;\r\n @let hasVisualMenuIndicator = menuItem.visualMenuIndicator ?? true;\r\n @let hasIconChevron = hasVisualMenuIndicator && !isSplitButton && hasIcon && !hasLabel;\r\n @let hasChevron = hasVisualMenuIndicator && (isSplitButton || !hasIconChevron);\r\n\r\n <button [attr.disabled]=\"disabled || null\"\r\n [attr.title]=\"menuItem.tooltip?.()\"\r\n [class.menu-open]=\"activeMenuItem()?.menuItem === menuItem\"\r\n [class.menu-indicator]=\"hasIconChevron\"\r\n [class]=\"menuItem.cssClass\" class=\"menu-item e2e-menu-item\"\r\n [sciAttributes]=\"menuItem.attributes\"\r\n (click)=\"onToolbarMenuButtonClick(menuItem, anchor ?? menu_button)\"\r\n (mousedown)=\"onToolbarMenuButtonMouseDown(menuItem)\"\r\n (mouseleave)=\"onToolbarMenuButtonMouseLeave(menuItem)\"\r\n #menu_button>\r\n @if (!isSplitButton) {\r\n <!-- Render icon, if any. -->\r\n <ng-container *ngTemplateOutlet=\"icon_template; context: {$implicit: menuItem}\"/>\r\n <!-- Render label, if any. -->\r\n <ng-container *ngTemplateOutlet=\"label_template; context: {$implicit: menuItem}\"/>\r\n }\r\n <!-- Render chevron to indicate dropdown. -->\r\n @if (hasChevron) {\r\n <sci-icon class=\"chevron\">scion.chevron_down</sci-icon>\r\n }\r\n </button>\r\n }\r\n </ng-template>\r\n }\r\n <!-- Toolbar Group -->\r\n @case ('group') {\r\n <sci-toolbar-group [menuItems]=\"menuItem.children\"\r\n [orientation]=\"orientation()\"\r\n [disabled]=\"disabled\"\r\n [popoverViewContainerRef]=\"popoverViewContainerRef()\"\r\n [class]=\"menuItem.cssClass\"/>\r\n }\r\n }\r\n}\r\n\r\n<!-- Renders the icon of a menu item, if any. -->\r\n<ng-template #icon_template let-menuItem>\r\n @if (menuItem.iconLigature) {\r\n <sci-icon>\r\n {{menuItem.iconLigature()}}\r\n </sci-icon>\r\n } @else if (menuItem.iconComponent) {\r\n <sci-toolbar-icon>\r\n <ng-container *sciComponentOutlet=\"menuItem.iconComponent\"/>\r\n </sci-toolbar-icon>\r\n }\r\n</ng-template>\r\n\r\n<!-- Renders the label of a menu item, if any. -->\r\n<ng-template #label_template let-menuItem>\r\n @if (menuItem.labelText) {\r\n <sci-toolbar-label>\r\n {{menuItem.labelText()}}\r\n </sci-toolbar-label>\r\n } @else if (menuItem.labelComponent) {\r\n <sci-toolbar-label>\r\n <ng-container *sciComponentOutlet=\"menuItem.labelComponent\"/>\r\n </sci-toolbar-label>\r\n }\r\n</ng-template>\r\n", styles: ["@charset \"UTF-8\";:host{--sci-icon-size: var(--sci-toolbar-item-size);--\\275sci-toolbar-item-padding-inline: calc(.5 * var(--sci-toolbar-item-size));--\\275sci-toolbar-item-padding-block: calc(.125 * var(--sci-toolbar-item-size));--\\275sci-toolbar-font-size: calc(.8125 * var(--sci-toolbar-item-size));--\\275sci-toolbar-item-border-size: 1px;font-size:var(--sci-toolbar-font-size, var(--\\275sci-toolbar-font-size));display:flex;gap:2px}:host[name]{min-height:calc(var(--sci-toolbar-item-size) + 2 * var(--\\275sci-toolbar-item-padding-block) + 2 * var(--\\275sci-toolbar-item-border-size));box-sizing:content-box}:host[data-orientation=vertical]{flex-direction:column}:host>sci-toolbar-group:not(:first-child):not(:has(>sci-toolbar-group:first-child)):before{content:\"\";background-color:var(--sci-color-border);margin:2px}:host>sci-toolbar-group:not(:first-child):not(:has(>sci-toolbar-group:first-child))[data-orientation=horizontal]:before{width:1px}:host>sci-toolbar-group:not(:first-child):not(:has(>sci-toolbar-group:first-child))[data-orientation=vertical]:before{height:1px}:host>sci-toolbar-group:not(:last-child):not(:has(+sci-toolbar-group)):after{content:\"\";background-color:var(--sci-color-border);margin:2px}:host>sci-toolbar-group:not(:last-child):not(:has(+sci-toolbar-group))[data-orientation=horizontal]:after{width:1px}:host>sci-toolbar-group:not(:last-child):not(:has(+sci-toolbar-group))[data-orientation=vertical]:after{height:1px}:host>sci-toolbar-split-button{display:flex;--\\275sci-toolbar-split-button: true}:host>sci-toolbar-split-button:has(>button:first-child.checked){--\\275sci-toolbar-split-button-checked: true}:host>sci-toolbar-split-button:has(>button:last-child.menu-open){--sci-toolbar-split-button-menu-open: true}:host>sci-toolbar-split-button:hover{--sci-toolbar-split-button-hover: true}:host>sci-toolbar-split-button:where(:hover,:has(>button:last-child.menu-open),:has(>button:first-child.checked),:has(>sci-toolbar-control:focus-within)):not(:has(>button:first-child:disabled)){--sci-toolbar-split-button-outline-visible: true}:host>sci-toolbar-split-button:focus-within{--sci-toolbar-split-button-focus-within: true}:host>sci-toolbar-split-button>sci-toolbar-control{border:var(--\\275sci-toolbar-item-border-size) solid var(--sci-color-border);border-radius:var(--sci-toolbar-item-border-radius);overflow:hidden}@container style(--sci-toolbar-split-button-outline-visible: true){@container not style(--sci-toolbar-item-border-color-hover: transparent){:host>sci-toolbar-split-button>sci-toolbar-control{border-color:var(--sci-toolbar-item-border-color-hover)}}:host>sci-toolbar-split-button>sci-toolbar-control{border-top-right-radius:0;border-bottom-right-radius:0}}:host button:is(.menu-item,#sci-reset){all:unset;display:inline-flex;gap:.3em;place-content:center;align-items:center;text-wrap:nowrap;padding:var(--\\275sci-toolbar-item-padding-block) var(--\\275sci-toolbar-item-padding-inline);border-radius:var(--sci-toolbar-item-border-radius);border:var(--\\275sci-toolbar-item-border-size) solid transparent;color:var(--sci-toolbar-item-text-color);background-color:var(--sci-toolbar-item-background-color);-webkit-user-select:none;user-select:none;cursor:var(--sci-toolbar-item-cursor)}:host button:is(.menu-item,#sci-reset):not(:has(>sci-toolbar-label)){--\\275sci-toolbar-item-padding-inline: var(--\\275sci-toolbar-item-padding-block)}@container style(--\\275sci-toolbar-split-button: true){:host button:is(.menu-item,#sci-reset):first-child{border-top-right-radius:0;border-bottom-right-radius:0}@container style(--sci-toolbar-split-button-menu-open: true) or (style(--\\275sci-toolbar-split-button-checked: true) and (not style(--sci-toolbar-split-button-hover: true))){:host button:is(.menu-item,#sci-reset):first-child{border-right:0;padding-right:calc(var(--\\275sci-toolbar-item-padding-inline) + 1px)}}:host button:is(.menu-item,#sci-reset):last-child{border-top-left-radius:0;border-bottom-left-radius:0;border-left:0;padding:0 .125em}@container style(--sci-toolbar-split-button-outline-visible: true){:host button:is(.menu-item,#sci-reset){border-color:var(--sci-color-border)}@container not style(--sci-toolbar-item-border-color-hover: transparent){:host button:is(.menu-item,#sci-reset){border-color:var(--sci-toolbar-item-border-color-hover)}}}@container style(--\\275sci-toolbar-split-button-checked: true){:host button:is(.menu-item,#sci-reset){border-color:var(--sci-toolbar-item-border-color-checked)}:host button:is(.menu-item,#sci-reset):not(:disabled){background-color:var(--sci-toolbar-item-background-color-checked)}}:host button:is(.menu-item,#sci-reset):focus-visible{border-radius:var(--sci-toolbar-item-border-radius);position:relative}}:host button:is(.menu-item,#sci-reset)>sci-toolbar-icon{display:inline-grid;height:var(--sci-toolbar-item-size);width:var(--sci-toolbar-item-size);overflow:hidden}:host button:is(.menu-item,#sci-reset):has(>sci-toolbar-label)>sci-icon.chevron{margin-left:calc(var(--\\275sci-toolbar-item-padding-inline) / 2)}:host button:is(.menu-item,#sci-reset)>sci-icon.chevron{--sci-icon-size: calc(.875 * var(--sci-icon-size))}:host button:is(.menu-item,#sci-reset).menu-indicator{position:relative}:host button:is(.menu-item,#sci-reset).menu-indicator:after{content:\"\";position:absolute;box-sizing:border-box;width:var(--sci-toolbar-item-menu-indicator-size);height:var(--sci-toolbar-item-menu-indicator-size);border:var(--sci-toolbar-item-menu-indicator-size) solid transparent;border-right-color:var(--sci-toolbar-item-menu-indicator-color);border-bottom-color:var(--sci-toolbar-item-menu-indicator-color);bottom:calc(var(--sci-corner) / 4);right:calc(var(--sci-corner) / 4)}:host button:is(.menu-item,#sci-reset).menu-indicator:disabled:after{border-right-color:var(--sci-toolbar-item-menu-indicator-color-disabled);border-bottom-color:var(--sci-toolbar-item-menu-indicator-color-disabled)}@container not style(--\\275sci-toolbar-split-button: true){:host button:is(.menu-item,#sci-reset).checked{border-color:var(--sci-toolbar-item-border-color-checked)}:host button:is(.menu-item,#sci-reset).checked:not(:disabled){background-color:var(--sci-toolbar-item-background-color-checked)}@container not style(--sci-toolbar-item-border-color-hover: transparent){:host button:is(.menu-item,#sci-reset):hover{border-color:var(--sci-toolbar-item-border-color-hover)}}}:host button:is(.menu-item,#sci-reset):disabled{color:var(--sci-toolbar-item-text-color-disabled)}:host button:is(.menu-item,#sci-reset):hover:not(:disabled){background-color:var(--sci-toolbar-item-background-color-hover)}:host button:is(.menu-item,#sci-reset):active:not(:disabled){background-color:var(--sci-toolbar-item-background-color-active)}:host button:is(.menu-item,#sci-reset):focus:not(:focus-visible){outline:none}:host button:is(.menu-item,#sci-reset):focus-visible{outline:var(--sci-toolbar-item-outline-width) solid var(--sci-color-accent)}:host button:is(.menu-item,#sci-reset).menu-open{background-color:var(--sci-toolbar-item-background-color-hover)}@container style(--sci-toolbar-split-button-menu-open: true){:host button:is(.menu-item,#sci-reset){background-color:var(--sci-toolbar-item-background-color-hover)}}\n"], dependencies: [{ kind: "component", type: SciToolbarGroupComponent, selector: "sci-toolbar-group", inputs: ["menuItems", "orientation", "disabled", "popoverViewContainerRef"] }, { kind: "directive", type: SciComponentOutletDirective, selector: "ng-template[sciComponentOutlet]", inputs: ["sciComponentOutlet"] }, { kind: "directive", type: SciAttributesDirective, selector: "[sciAttributes]", inputs: ["sciAttributes"] }, { kind: "component", type: SciIconComponent, selector: "sci-icon" }, { kind: "component", type: SciToolbarSplitButtonComponent, selector: "sci-toolbar-split-button" }, { kind: "component", type: SciToolbarControlComponent, selector: "sci-toolbar-control" }, { kind: "component", type: SciToolbarIconComponent, selector: "sci-toolbar-icon" }, { kind: "component", type: SciToolbarLabelComponent, selector: "sci-toolbar-label" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: SciFormatAcceleratorPipe, name: "sciFormatAccelerator" }] });
|
|
1266
|
+
}
|
|
1267
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciToolbarGroupComponent, decorators: [{
|
|
1268
|
+
type: Component,
|
|
1269
|
+
args: [{ selector: 'sci-toolbar-group', imports: [
|
|
1270
|
+
SciComponentOutletDirective,
|
|
1271
|
+
SciAttributesDirective,
|
|
1272
|
+
SciIconComponent,
|
|
1273
|
+
SciFormatAcceleratorPipe,
|
|
1274
|
+
SciToolbarSplitButtonComponent,
|
|
1275
|
+
SciToolbarControlComponent,
|
|
1276
|
+
SciToolbarIconComponent,
|
|
1277
|
+
SciToolbarLabelComponent,
|
|
1278
|
+
NgTemplateOutlet,
|
|
1279
|
+
], host: {
|
|
1280
|
+
'[attr.data-orientation]': 'orientation()',
|
|
1281
|
+
}, template: "@for (menuItem of menuItems(); track $index) {\r\n @let disabled = this.disabled() || menuItem.disabled?.() || false;\r\n @switch (menuItem.type) {\r\n <!-- Toolbar Item -->\r\n @case ('menu-item') {\r\n @let isSplitButton = (menuItem.onSelect || menuItem.control) && menuItem.menu?.children?.length;\r\n\r\n @if (isSplitButton) {\r\n <sci-toolbar-split-button [class]=\"menuItem.cssClass\" #split_button_element>\r\n <ng-container *ngTemplateOutlet=\"menu_item_template; context: {anchor: split_button_element.host}\"/>\r\n </sci-toolbar-split-button>\r\n } @else {\r\n <ng-container *ngTemplateOutlet=\"menu_item_template\"/>\r\n }\r\n\r\n <!-- Template for rendering the content of a toolbar item. -->\r\n <ng-template #menu_item_template let-anchor=\"anchor\">\r\n <!-- Render push or toggle button. -->\r\n @if (menuItem.onSelect) {\r\n <button [attr.disabled]=\"disabled || null\"\r\n [attr.title]=\"menuItem.accelerator | sciFormatAccelerator:menuItem.tooltip?.()\"\r\n [class.checked]=\"menuItem.checked?.()\"\r\n [class]=\"menuItem.cssClass\" class=\"menu-item toolbar-button e2e-menu-item\"\r\n [sciAttributes]=\"menuItem.attributes\"\r\n (click)=\"onSelect(menuItem)\">\r\n <!-- Render icon, if any. -->\r\n <ng-container *ngTemplateOutlet=\"icon_template; context: {$implicit: menuItem}\"/>\r\n <!-- Render label, if any. -->\r\n <ng-container *ngTemplateOutlet=\"label_template; context: {$implicit: menuItem}\"/>\r\n </button>\r\n } @else if (menuItem.control) {\r\n <!-- Render custom control. -->\r\n <sci-toolbar-control [attr.title]=\"menuItem.accelerator | sciFormatAccelerator:menuItem.tooltip?.()\"\r\n [class]=\"menuItem.cssClass\" class=\"e2e-menu-item\"\r\n [sciAttributes]=\"menuItem.attributes\">\r\n <ng-container *sciComponentOutlet=\"menuItem.control\"/>\r\n </sci-toolbar-control>\r\n }\r\n\r\n <!-- Render menu button. -->\r\n @if (menuItem.menu?.children?.length) {\r\n @let hasLabel = menuItem.labelText || menuItem.labelComponent;\r\n @let hasIcon = menuItem.iconLigature || menuItem.iconComponent;\r\n @let hasVisualMenuIndicator = menuItem.visualMenuIndicator ?? true;\r\n @let hasIconChevron = hasVisualMenuIndicator && !isSplitButton && hasIcon && !hasLabel;\r\n @let hasChevron = hasVisualMenuIndicator && (isSplitButton || !hasIconChevron);\r\n\r\n <button [attr.disabled]=\"disabled || null\"\r\n [attr.title]=\"menuItem.tooltip?.()\"\r\n [class.menu-open]=\"activeMenuItem()?.menuItem === menuItem\"\r\n [class.menu-indicator]=\"hasIconChevron\"\r\n [class]=\"menuItem.cssClass\" class=\"menu-item e2e-menu-item\"\r\n [sciAttributes]=\"menuItem.attributes\"\r\n (click)=\"onToolbarMenuButtonClick(menuItem, anchor ?? menu_button)\"\r\n (mousedown)=\"onToolbarMenuButtonMouseDown(menuItem)\"\r\n (mouseleave)=\"onToolbarMenuButtonMouseLeave(menuItem)\"\r\n #menu_button>\r\n @if (!isSplitButton) {\r\n <!-- Render icon, if any. -->\r\n <ng-container *ngTemplateOutlet=\"icon_template; context: {$implicit: menuItem}\"/>\r\n <!-- Render label, if any. -->\r\n <ng-container *ngTemplateOutlet=\"label_template; context: {$implicit: menuItem}\"/>\r\n }\r\n <!-- Render chevron to indicate dropdown. -->\r\n @if (hasChevron) {\r\n <sci-icon class=\"chevron\">scion.chevron_down</sci-icon>\r\n }\r\n </button>\r\n }\r\n </ng-template>\r\n }\r\n <!-- Toolbar Group -->\r\n @case ('group') {\r\n <sci-toolbar-group [menuItems]=\"menuItem.children\"\r\n [orientation]=\"orientation()\"\r\n [disabled]=\"disabled\"\r\n [popoverViewContainerRef]=\"popoverViewContainerRef()\"\r\n [class]=\"menuItem.cssClass\"/>\r\n }\r\n }\r\n}\r\n\r\n<!-- Renders the icon of a menu item, if any. -->\r\n<ng-template #icon_template let-menuItem>\r\n @if (menuItem.iconLigature) {\r\n <sci-icon>\r\n {{menuItem.iconLigature()}}\r\n </sci-icon>\r\n } @else if (menuItem.iconComponent) {\r\n <sci-toolbar-icon>\r\n <ng-container *sciComponentOutlet=\"menuItem.iconComponent\"/>\r\n </sci-toolbar-icon>\r\n }\r\n</ng-template>\r\n\r\n<!-- Renders the label of a menu item, if any. -->\r\n<ng-template #label_template let-menuItem>\r\n @if (menuItem.labelText) {\r\n <sci-toolbar-label>\r\n {{menuItem.labelText()}}\r\n </sci-toolbar-label>\r\n } @else if (menuItem.labelComponent) {\r\n <sci-toolbar-label>\r\n <ng-container *sciComponentOutlet=\"menuItem.labelComponent\"/>\r\n </sci-toolbar-label>\r\n }\r\n</ng-template>\r\n", styles: ["@charset \"UTF-8\";:host{--sci-icon-size: var(--sci-toolbar-item-size);--\\275sci-toolbar-item-padding-inline: calc(.5 * var(--sci-toolbar-item-size));--\\275sci-toolbar-item-padding-block: calc(.125 * var(--sci-toolbar-item-size));--\\275sci-toolbar-font-size: calc(.8125 * var(--sci-toolbar-item-size));--\\275sci-toolbar-item-border-size: 1px;font-size:var(--sci-toolbar-font-size, var(--\\275sci-toolbar-font-size));display:flex;gap:2px}:host[name]{min-height:calc(var(--sci-toolbar-item-size) + 2 * var(--\\275sci-toolbar-item-padding-block) + 2 * var(--\\275sci-toolbar-item-border-size));box-sizing:content-box}:host[data-orientation=vertical]{flex-direction:column}:host>sci-toolbar-group:not(:first-child):not(:has(>sci-toolbar-group:first-child)):before{content:\"\";background-color:var(--sci-color-border);margin:2px}:host>sci-toolbar-group:not(:first-child):not(:has(>sci-toolbar-group:first-child))[data-orientation=horizontal]:before{width:1px}:host>sci-toolbar-group:not(:first-child):not(:has(>sci-toolbar-group:first-child))[data-orientation=vertical]:before{height:1px}:host>sci-toolbar-group:not(:last-child):not(:has(+sci-toolbar-group)):after{content:\"\";background-color:var(--sci-color-border);margin:2px}:host>sci-toolbar-group:not(:last-child):not(:has(+sci-toolbar-group))[data-orientation=horizontal]:after{width:1px}:host>sci-toolbar-group:not(:last-child):not(:has(+sci-toolbar-group))[data-orientation=vertical]:after{height:1px}:host>sci-toolbar-split-button{display:flex;--\\275sci-toolbar-split-button: true}:host>sci-toolbar-split-button:has(>button:first-child.checked){--\\275sci-toolbar-split-button-checked: true}:host>sci-toolbar-split-button:has(>button:last-child.menu-open){--sci-toolbar-split-button-menu-open: true}:host>sci-toolbar-split-button:hover{--sci-toolbar-split-button-hover: true}:host>sci-toolbar-split-button:where(:hover,:has(>button:last-child.menu-open),:has(>button:first-child.checked),:has(>sci-toolbar-control:focus-within)):not(:has(>button:first-child:disabled)){--sci-toolbar-split-button-outline-visible: true}:host>sci-toolbar-split-button:focus-within{--sci-toolbar-split-button-focus-within: true}:host>sci-toolbar-split-button>sci-toolbar-control{border:var(--\\275sci-toolbar-item-border-size) solid var(--sci-color-border);border-radius:var(--sci-toolbar-item-border-radius);overflow:hidden}@container style(--sci-toolbar-split-button-outline-visible: true){@container not style(--sci-toolbar-item-border-color-hover: transparent){:host>sci-toolbar-split-button>sci-toolbar-control{border-color:var(--sci-toolbar-item-border-color-hover)}}:host>sci-toolbar-split-button>sci-toolbar-control{border-top-right-radius:0;border-bottom-right-radius:0}}:host button:is(.menu-item,#sci-reset){all:unset;display:inline-flex;gap:.3em;place-content:center;align-items:center;text-wrap:nowrap;padding:var(--\\275sci-toolbar-item-padding-block) var(--\\275sci-toolbar-item-padding-inline);border-radius:var(--sci-toolbar-item-border-radius);border:var(--\\275sci-toolbar-item-border-size) solid transparent;color:var(--sci-toolbar-item-text-color);background-color:var(--sci-toolbar-item-background-color);-webkit-user-select:none;user-select:none;cursor:var(--sci-toolbar-item-cursor)}:host button:is(.menu-item,#sci-reset):not(:has(>sci-toolbar-label)){--\\275sci-toolbar-item-padding-inline: var(--\\275sci-toolbar-item-padding-block)}@container style(--\\275sci-toolbar-split-button: true){:host button:is(.menu-item,#sci-reset):first-child{border-top-right-radius:0;border-bottom-right-radius:0}@container style(--sci-toolbar-split-button-menu-open: true) or (style(--\\275sci-toolbar-split-button-checked: true) and (not style(--sci-toolbar-split-button-hover: true))){:host button:is(.menu-item,#sci-reset):first-child{border-right:0;padding-right:calc(var(--\\275sci-toolbar-item-padding-inline) + 1px)}}:host button:is(.menu-item,#sci-reset):last-child{border-top-left-radius:0;border-bottom-left-radius:0;border-left:0;padding:0 .125em}@container style(--sci-toolbar-split-button-outline-visible: true){:host button:is(.menu-item,#sci-reset){border-color:var(--sci-color-border)}@container not style(--sci-toolbar-item-border-color-hover: transparent){:host button:is(.menu-item,#sci-reset){border-color:var(--sci-toolbar-item-border-color-hover)}}}@container style(--\\275sci-toolbar-split-button-checked: true){:host button:is(.menu-item,#sci-reset){border-color:var(--sci-toolbar-item-border-color-checked)}:host button:is(.menu-item,#sci-reset):not(:disabled){background-color:var(--sci-toolbar-item-background-color-checked)}}:host button:is(.menu-item,#sci-reset):focus-visible{border-radius:var(--sci-toolbar-item-border-radius);position:relative}}:host button:is(.menu-item,#sci-reset)>sci-toolbar-icon{display:inline-grid;height:var(--sci-toolbar-item-size);width:var(--sci-toolbar-item-size);overflow:hidden}:host button:is(.menu-item,#sci-reset):has(>sci-toolbar-label)>sci-icon.chevron{margin-left:calc(var(--\\275sci-toolbar-item-padding-inline) / 2)}:host button:is(.menu-item,#sci-reset)>sci-icon.chevron{--sci-icon-size: calc(.875 * var(--sci-icon-size))}:host button:is(.menu-item,#sci-reset).menu-indicator{position:relative}:host button:is(.menu-item,#sci-reset).menu-indicator:after{content:\"\";position:absolute;box-sizing:border-box;width:var(--sci-toolbar-item-menu-indicator-size);height:var(--sci-toolbar-item-menu-indicator-size);border:var(--sci-toolbar-item-menu-indicator-size) solid transparent;border-right-color:var(--sci-toolbar-item-menu-indicator-color);border-bottom-color:var(--sci-toolbar-item-menu-indicator-color);bottom:calc(var(--sci-corner) / 4);right:calc(var(--sci-corner) / 4)}:host button:is(.menu-item,#sci-reset).menu-indicator:disabled:after{border-right-color:var(--sci-toolbar-item-menu-indicator-color-disabled);border-bottom-color:var(--sci-toolbar-item-menu-indicator-color-disabled)}@container not style(--\\275sci-toolbar-split-button: true){:host button:is(.menu-item,#sci-reset).checked{border-color:var(--sci-toolbar-item-border-color-checked)}:host button:is(.menu-item,#sci-reset).checked:not(:disabled){background-color:var(--sci-toolbar-item-background-color-checked)}@container not style(--sci-toolbar-item-border-color-hover: transparent){:host button:is(.menu-item,#sci-reset):hover{border-color:var(--sci-toolbar-item-border-color-hover)}}}:host button:is(.menu-item,#sci-reset):disabled{color:var(--sci-toolbar-item-text-color-disabled)}:host button:is(.menu-item,#sci-reset):hover:not(:disabled){background-color:var(--sci-toolbar-item-background-color-hover)}:host button:is(.menu-item,#sci-reset):active:not(:disabled){background-color:var(--sci-toolbar-item-background-color-active)}:host button:is(.menu-item,#sci-reset):focus:not(:focus-visible){outline:none}:host button:is(.menu-item,#sci-reset):focus-visible{outline:var(--sci-toolbar-item-outline-width) solid var(--sci-color-accent)}:host button:is(.menu-item,#sci-reset).menu-open{background-color:var(--sci-toolbar-item-background-color-hover)}@container style(--sci-toolbar-split-button-menu-open: true){:host button:is(.menu-item,#sci-reset){background-color:var(--sci-toolbar-item-background-color-hover)}}\n"] }]
|
|
1282
|
+
}], ctorParameters: () => [], propDecorators: { menuItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "menuItems", required: true }] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: true }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], popoverViewContainerRef: [{ type: i0.Input, args: [{ isSignal: true, alias: "popoverViewContainerRef", required: true }] }], _childToolbarGroups: [{ type: i0.ViewChildren, args: [i0.forwardRef(() => SciToolbarGroupComponent), { isSignal: true }] }] } });
|
|
1283
|
+
|
|
1284
|
+
/*
|
|
1285
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
1286
|
+
*
|
|
1287
|
+
* This program and the accompanying materials are made
|
|
1288
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
1289
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
1290
|
+
*
|
|
1291
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
1292
|
+
*/
|
|
1293
|
+
/**
|
|
1294
|
+
* Represents a semantic element for the label of a menu group provided as slotted content.
|
|
1295
|
+
*/
|
|
1296
|
+
class SciMenuGroupLabelComponent {
|
|
1297
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciMenuGroupLabelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1298
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: SciMenuGroupLabelComponent, isStandalone: true, selector: "sci-menu-group-label", ngImport: i0, template: '<ng-content/>', isInline: true });
|
|
1299
|
+
}
|
|
1300
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciMenuGroupLabelComponent, decorators: [{
|
|
1301
|
+
type: Component,
|
|
1302
|
+
args: [{
|
|
1303
|
+
selector: 'sci-menu-group-label',
|
|
1304
|
+
template: '<ng-content/>',
|
|
1305
|
+
}]
|
|
1306
|
+
}] });
|
|
1307
|
+
|
|
1308
|
+
/*
|
|
1309
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
1310
|
+
*
|
|
1311
|
+
* This program and the accompanying materials are made
|
|
1312
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
1313
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
1314
|
+
*
|
|
1315
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
1316
|
+
*/
|
|
1317
|
+
/**
|
|
1318
|
+
* Represents a semantic element for the label of a menu item provided as slotted content.
|
|
1319
|
+
*/
|
|
1320
|
+
class SciMenuItemLabelComponent {
|
|
1321
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciMenuItemLabelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1322
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: SciMenuItemLabelComponent, isStandalone: true, selector: "sci-menu-item-label", ngImport: i0, template: '<ng-content/>', isInline: true });
|
|
1323
|
+
}
|
|
1324
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciMenuItemLabelComponent, decorators: [{
|
|
1325
|
+
type: Component,
|
|
1326
|
+
args: [{
|
|
1327
|
+
selector: 'sci-menu-item-label',
|
|
1328
|
+
template: '<ng-content/>',
|
|
1329
|
+
}]
|
|
1330
|
+
}] });
|
|
1331
|
+
|
|
1332
|
+
/*
|
|
1333
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
1334
|
+
*
|
|
1335
|
+
* This program and the accompanying materials are made
|
|
1336
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
1337
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
1338
|
+
*
|
|
1339
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
1340
|
+
*/
|
|
1341
|
+
/**
|
|
1342
|
+
* Represents a semantic element for the icon of a menu item provided as slotted content.
|
|
1343
|
+
*/
|
|
1344
|
+
class SciMenuItemIconComponent {
|
|
1345
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciMenuItemIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1346
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: SciMenuItemIconComponent, isStandalone: true, selector: "sci-menu-item-icon", ngImport: i0, template: '<ng-content/>', isInline: true });
|
|
1347
|
+
}
|
|
1348
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciMenuItemIconComponent, decorators: [{
|
|
1349
|
+
type: Component,
|
|
1350
|
+
args: [{
|
|
1351
|
+
selector: 'sci-menu-item-icon',
|
|
1352
|
+
template: '<ng-content/>',
|
|
1353
|
+
}]
|
|
1354
|
+
}] });
|
|
1355
|
+
|
|
1356
|
+
/*
|
|
1357
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
1358
|
+
*
|
|
1359
|
+
* This program and the accompanying materials are made
|
|
1360
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
1361
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
1362
|
+
*
|
|
1363
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
1364
|
+
*/
|
|
1365
|
+
/**
|
|
1366
|
+
* Represents a menu or a group of menu items.
|
|
1367
|
+
*/
|
|
1368
|
+
class SciMenuComponent {
|
|
1369
|
+
/**
|
|
1370
|
+
* Specifies the menu items to display in this menu or group.
|
|
1371
|
+
*/
|
|
1372
|
+
menuItems = input.required(/* @ts-ignore */
|
|
1373
|
+
...(ngDevMode ? [{ debugName: "menuItems" }] : /* istanbul ignore next */ []));
|
|
1374
|
+
/**
|
|
1375
|
+
* Controls whether to disable menu items in this menu or group.
|
|
1376
|
+
*/
|
|
1377
|
+
disabled = input(/* @ts-ignore */
|
|
1378
|
+
...(ngDevMode ? [undefined, { debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
1379
|
+
/**
|
|
1380
|
+
* Enables and configures filtering of this menu.
|
|
1381
|
+
*
|
|
1382
|
+
* Has no effect if a menu group.
|
|
1383
|
+
*/
|
|
1384
|
+
filter = input(/* @ts-ignore */
|
|
1385
|
+
...(ngDevMode ? [undefined, { debugName: "filter" }] : /* istanbul ignore next */ []));
|
|
1386
|
+
/**
|
|
1387
|
+
* Configures features of this group.
|
|
1388
|
+
*
|
|
1389
|
+
* Has no effect if a menu or submenu
|
|
1390
|
+
*/
|
|
1391
|
+
group = input(/* @ts-ignore */
|
|
1392
|
+
...(ngDevMode ? [undefined, { debugName: "group" }] : /* istanbul ignore next */ []));
|
|
1393
|
+
/**
|
|
1394
|
+
* Specifies the preferred menu width.
|
|
1395
|
+
*
|
|
1396
|
+
* Has no effect if a menu group.
|
|
1397
|
+
*/
|
|
1398
|
+
width = input(/* @ts-ignore */
|
|
1399
|
+
...(ngDevMode ? [undefined, { debugName: "width" }] : /* istanbul ignore next */ []));
|
|
1400
|
+
/**
|
|
1401
|
+
* Specifies the preferred minimum menu width.
|
|
1402
|
+
*
|
|
1403
|
+
* Defaults to the `--sci-menu-min-width` or `--sci-menu-submenu-min-width` CSS variables.
|
|
1404
|
+
*
|
|
1405
|
+
* If {@link anchorWidth} is specified (e.g., for toolbar menus), the effective minimum width evaluates to `max(anchorWidth, minWidth)`.
|
|
1406
|
+
*
|
|
1407
|
+
* Has no effect if a menu group.
|
|
1408
|
+
*/
|
|
1409
|
+
minWidth = input(/* @ts-ignore */
|
|
1410
|
+
...(ngDevMode ? [undefined, { debugName: "minWidth" }] : /* istanbul ignore next */ []));
|
|
1411
|
+
/**
|
|
1412
|
+
* Specifies the maximum menu width.
|
|
1413
|
+
*
|
|
1414
|
+
* Has no effect if a menu group.
|
|
1415
|
+
*/
|
|
1416
|
+
maxWidth = input(/* @ts-ignore */
|
|
1417
|
+
...(ngDevMode ? [undefined, { debugName: "maxWidth" }] : /* istanbul ignore next */ []));
|
|
1418
|
+
/**
|
|
1419
|
+
* Specifies the maximum menu height.
|
|
1420
|
+
*
|
|
1421
|
+
* Has no effect if a menu group.
|
|
1422
|
+
*/
|
|
1423
|
+
maxHeight = input(/* @ts-ignore */
|
|
1424
|
+
...(ngDevMode ? [undefined, { debugName: "maxHeight" }] : /* istanbul ignore next */ []));
|
|
1425
|
+
/**
|
|
1426
|
+
* Controls whether to display the glyph area.
|
|
1427
|
+
*
|
|
1428
|
+
* The glyph area is the leftmost column for displaying icons and checkmarks.
|
|
1429
|
+
*/
|
|
1430
|
+
glyphArea = input(/* @ts-ignore */
|
|
1431
|
+
...(ngDevMode ? [undefined, { debugName: "glyphArea" }] : /* istanbul ignore next */ []));
|
|
1432
|
+
/**
|
|
1433
|
+
* Specifies the width of the popover anchor.
|
|
1434
|
+
*
|
|
1435
|
+
* Used to compute the minimum width of the menu.
|
|
1436
|
+
*/
|
|
1437
|
+
anchorWidth = input(undefined, { ...(ngDevMode ? { debugName: "anchorWidth" } : /* istanbul ignore next */ {}), transform: (width) => width ? `${width}px` : undefined });
|
|
1438
|
+
/**
|
|
1439
|
+
* Specifies CSS classes to associate with the menu, submenu, or group.
|
|
1440
|
+
*/
|
|
1441
|
+
cssClass = input(/* @ts-ignore */
|
|
1442
|
+
...(ngDevMode ? [undefined, { debugName: "cssClass" }] : /* istanbul ignore next */ []));
|
|
1443
|
+
_menuService = inject(ɵSciMenuService);
|
|
1444
|
+
_menuFilter = inject(MenuFilter);
|
|
1445
|
+
_popoverRef = inject(PopoverRef);
|
|
1446
|
+
_filterField = viewChild(SciMenuFilterComponent, /* @ts-ignore */
|
|
1447
|
+
...(ngDevMode ? [{ debugName: "_filterField" }] : /* istanbul ignore next */ []));
|
|
1448
|
+
_viewport = viewChild(SciViewportComponent, /* @ts-ignore */
|
|
1449
|
+
...(ngDevMode ? [{ debugName: "_viewport" }] : /* istanbul ignore next */ []));
|
|
1450
|
+
hasGlyphArea = computed(() => this.glyphArea() ?? requiresGlyphArea(this.menuItems()), /* @ts-ignore */
|
|
1451
|
+
...(ngDevMode ? [{ debugName: "hasGlyphArea" }] : /* istanbul ignore next */ []));
|
|
1452
|
+
menuItemsFiltered = computed(() => this.menuItems().filter(menuItem => this.matchesFilter(menuItem)()), /* @ts-ignore */
|
|
1453
|
+
...(ngDevMode ? [{ debugName: "menuItemsFiltered" }] : /* istanbul ignore next */ []));
|
|
1454
|
+
popoverViewContainerRef = viewChild.required('popover_view_container_ref', { ...(ngDevMode ? { debugName: "popoverViewContainerRef" } : /* istanbul ignore next */ {}), read: ViewContainerRef });
|
|
1455
|
+
scrolling = computed(() => this._viewport()?.scrolling(), /* @ts-ignore */
|
|
1456
|
+
...(ngDevMode ? [{ debugName: "scrolling" }] : /* istanbul ignore next */ []));
|
|
1457
|
+
activeSubmenuItem = linkedSignal({ ...(ngDevMode ? { debugName: "activeSubmenuItem" } : /* istanbul ignore next */ {}), source: this.menuItemsFiltered, // Unset when filtered menu items change. Otherwise, popopver may lose anchor and render left-top of the page viewport.
|
|
1458
|
+
computation: () => null,
|
|
1459
|
+
equal: (a, b) => a?.menuItem === b?.menuItem });
|
|
1460
|
+
role = inject(SCI_MENU_COMPONENT_ROLE);
|
|
1461
|
+
isGroupExpanded = this.computeGroupExpanded();
|
|
1462
|
+
popoverHidden = this.computePopoverHidden();
|
|
1463
|
+
menuSize = this.computeMenuSize();
|
|
1464
|
+
constructor() {
|
|
1465
|
+
this.installSubmenuOpener();
|
|
1466
|
+
this.installAccelerators();
|
|
1467
|
+
this.lockInitialMenuWidth();
|
|
1468
|
+
this.closeChildPopoverOnComponentReuse();
|
|
1469
|
+
this.requestInitialFocus();
|
|
1470
|
+
this.focusFilterFieldOnKeyDown();
|
|
1471
|
+
this.stopKeyDownEventPropagation();
|
|
1472
|
+
}
|
|
1473
|
+
/**
|
|
1474
|
+
* Method invoked when clicking on a menu item.
|
|
1475
|
+
*/
|
|
1476
|
+
async onSelect(menuItem) {
|
|
1477
|
+
if (await menuItem.onSelect()) {
|
|
1478
|
+
this._popoverRef.close({ closeParents: true });
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
/**
|
|
1482
|
+
* Method invoked when clicking on the group header of a collapsible group.
|
|
1483
|
+
*/
|
|
1484
|
+
onGroupHeaderButtonClick() {
|
|
1485
|
+
this.isGroupExpanded.update(expanded => !expanded);
|
|
1486
|
+
}
|
|
1487
|
+
/**
|
|
1488
|
+
* Method invoked when moving the pointer over a menu item.
|
|
1489
|
+
*/
|
|
1490
|
+
onMenuItemMouseEnter(menuItem) {
|
|
1491
|
+
const disabled = this.disabled() || menuItem?.disabled?.(); // eslint-disable-line @typescript-eslint/prefer-nullish-coalescing
|
|
1492
|
+
if (disabled) {
|
|
1493
|
+
return;
|
|
1494
|
+
}
|
|
1495
|
+
this.activeSubmenuItem.set(null);
|
|
1496
|
+
// Close submenu of other groups.
|
|
1497
|
+
if (this._popoverRef.childPopover()?.isSubmenu) {
|
|
1498
|
+
this._popoverRef.childPopover().close();
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
/**
|
|
1502
|
+
* Method invoked when moving the pointer over a submenu item.
|
|
1503
|
+
*/
|
|
1504
|
+
onSubmenuItemMouseEnter(menuItem) {
|
|
1505
|
+
this.onSubmenuItemClick(menuItem);
|
|
1506
|
+
}
|
|
1507
|
+
/**
|
|
1508
|
+
* Method invoked when clicking on a submenu item.
|
|
1509
|
+
*/
|
|
1510
|
+
onSubmenuItemClick(menuItem) {
|
|
1511
|
+
const disabled = this.disabled() || menuItem.menuItem.disabled?.(); // eslint-disable-line @typescript-eslint/prefer-nullish-coalescing
|
|
1512
|
+
if (disabled) {
|
|
1513
|
+
return;
|
|
1514
|
+
}
|
|
1515
|
+
this.activeSubmenuItem.set(menuItem);
|
|
1516
|
+
}
|
|
1517
|
+
/**
|
|
1518
|
+
* Method invoked when setting a menu filter.
|
|
1519
|
+
*/
|
|
1520
|
+
onFilterChange(filter) {
|
|
1521
|
+
this._menuFilter.setFilterText(filter);
|
|
1522
|
+
}
|
|
1523
|
+
/**
|
|
1524
|
+
* Opens a submenu based on {@link activeSubmenuItem}.
|
|
1525
|
+
*/
|
|
1526
|
+
installSubmenuOpener() {
|
|
1527
|
+
effect(onCleanup => {
|
|
1528
|
+
if (!this.activeSubmenuItem()) {
|
|
1529
|
+
return;
|
|
1530
|
+
}
|
|
1531
|
+
const { menuItem, element } = this.activeSubmenuItem();
|
|
1532
|
+
const menu = menuItem.menu;
|
|
1533
|
+
untracked(() => {
|
|
1534
|
+
const menuRef = this._menuService.open(menu.children, {
|
|
1535
|
+
anchor: element,
|
|
1536
|
+
viewContainerRef: this.popoverViewContainerRef(),
|
|
1537
|
+
filter: menu.filter,
|
|
1538
|
+
width: menu.width,
|
|
1539
|
+
minWidth: menu.minWidth,
|
|
1540
|
+
maxWidth: menu.maxWidth,
|
|
1541
|
+
maxHeight: menu.maxHeight,
|
|
1542
|
+
cssClass: menuItem.cssClass,
|
|
1543
|
+
attributes: menuItem.attributes,
|
|
1544
|
+
submenu: true,
|
|
1545
|
+
align: 'horizontal',
|
|
1546
|
+
offset: { x: 1 },
|
|
1547
|
+
});
|
|
1548
|
+
// Close when opening another submenu.
|
|
1549
|
+
onCleanup(() => menuRef.close());
|
|
1550
|
+
// Unset when closing submenu.
|
|
1551
|
+
menuRef.onClose(() => {
|
|
1552
|
+
const activeSubmenuItem = this.activeSubmenuItem();
|
|
1553
|
+
// Do not unset if another submenu was opened in the meantime.
|
|
1554
|
+
if (activeSubmenuItem?.menuItem !== menuItem) {
|
|
1555
|
+
return;
|
|
1556
|
+
}
|
|
1557
|
+
this.activeSubmenuItem.set(null);
|
|
1558
|
+
});
|
|
1559
|
+
});
|
|
1560
|
+
});
|
|
1561
|
+
}
|
|
1562
|
+
/**
|
|
1563
|
+
* Installs accelerators of menu items in this menu, recursively for menu items in submenus and groups.
|
|
1564
|
+
*
|
|
1565
|
+
* Has no effect if a submenu or menu group.
|
|
1566
|
+
*/
|
|
1567
|
+
installAccelerators() {
|
|
1568
|
+
if (this.role !== 'menu') {
|
|
1569
|
+
return;
|
|
1570
|
+
}
|
|
1571
|
+
const host = inject(ElementRef).nativeElement;
|
|
1572
|
+
const injector = inject(Injector);
|
|
1573
|
+
effect(onCleanup => {
|
|
1574
|
+
const menuItems = this.menuItems();
|
|
1575
|
+
untracked(() => {
|
|
1576
|
+
const accelerators = ɵinstallMenuAccelerators(menuItems, {
|
|
1577
|
+
targets: host,
|
|
1578
|
+
onSelect: menuItem => void this.onSelect(menuItem),
|
|
1579
|
+
injector,
|
|
1580
|
+
});
|
|
1581
|
+
onCleanup(() => accelerators.dispose());
|
|
1582
|
+
});
|
|
1583
|
+
});
|
|
1584
|
+
}
|
|
1585
|
+
/**
|
|
1586
|
+
* Locks the initial menu width to maintain a stable width when expanding/collapsing groups or hovering menu items with a toolbar.
|
|
1587
|
+
*
|
|
1588
|
+
* Has no effect if a menu group.
|
|
1589
|
+
*/
|
|
1590
|
+
lockInitialMenuWidth() {
|
|
1591
|
+
if (this.role === 'group') {
|
|
1592
|
+
return;
|
|
1593
|
+
}
|
|
1594
|
+
const host = inject(ElementRef).nativeElement;
|
|
1595
|
+
// Wait until menu items are available, maximal until idle; may initially be empty due to asynchronous menu contributions.
|
|
1596
|
+
toObservable(this.menuItems)
|
|
1597
|
+
.pipe(filter(menuItems => !!menuItems.length), observeOn(animationFrameScheduler), // Wait until next render cycle to capture menu width.
|
|
1598
|
+
raceWith(new Promise(resolve => {
|
|
1599
|
+
if (typeof requestIdleCallback === 'function') {
|
|
1600
|
+
requestIdleCallback(resolve, { timeout: 250 });
|
|
1601
|
+
}
|
|
1602
|
+
else {
|
|
1603
|
+
setTimeout(resolve, 250); // Fallback if browser does not support `requestIdleCallback`, e.g., Safari
|
|
1604
|
+
}
|
|
1605
|
+
})), take(1), takeUntilDestroyed())
|
|
1606
|
+
.subscribe(() => {
|
|
1607
|
+
this.menuSize.update(size => ({ ...size, width: `${host.getBoundingClientRect().width}px` }));
|
|
1608
|
+
});
|
|
1609
|
+
}
|
|
1610
|
+
/**
|
|
1611
|
+
* Closes a child popover (submenu or action menu) when this component is reused.
|
|
1612
|
+
*/
|
|
1613
|
+
closeChildPopoverOnComponentReuse() {
|
|
1614
|
+
effect(() => {
|
|
1615
|
+
// Track menu items.
|
|
1616
|
+
this.menuItems();
|
|
1617
|
+
// Close child popover, if any.
|
|
1618
|
+
untracked(() => this._popoverRef.childPopover()?.close());
|
|
1619
|
+
});
|
|
1620
|
+
}
|
|
1621
|
+
/**
|
|
1622
|
+
* Focuses the popover or filter field based on {@link filter.focus} option.
|
|
1623
|
+
*
|
|
1624
|
+
* The popover must receive focus to enable light dismiss (closing via Escape or clicking outside it) when opened from microfrontends.
|
|
1625
|
+
*/
|
|
1626
|
+
requestInitialFocus() {
|
|
1627
|
+
const host = inject(ElementRef).nativeElement;
|
|
1628
|
+
const effectRef = afterRenderEffect(() => {
|
|
1629
|
+
if (this.popoverHidden()) {
|
|
1630
|
+
return; // cannot be focused until visible
|
|
1631
|
+
}
|
|
1632
|
+
untracked(() => {
|
|
1633
|
+
if (this.filter()?.focus) {
|
|
1634
|
+
this._filterField()?.focus();
|
|
1635
|
+
}
|
|
1636
|
+
else if (this.role === 'menu') {
|
|
1637
|
+
host.focus();
|
|
1638
|
+
}
|
|
1639
|
+
effectRef.destroy();
|
|
1640
|
+
});
|
|
1641
|
+
});
|
|
1642
|
+
}
|
|
1643
|
+
/**
|
|
1644
|
+
* Focuses the filter field when the user starts typing.
|
|
1645
|
+
*
|
|
1646
|
+
* Has no effect if filtering is not supported.
|
|
1647
|
+
*/
|
|
1648
|
+
focusFilterFieldOnKeyDown() {
|
|
1649
|
+
const host = inject(ElementRef).nativeElement;
|
|
1650
|
+
const zone = inject(NgZone);
|
|
1651
|
+
toObservable(this._filterField)
|
|
1652
|
+
.pipe(switchMap(filterField => filterField ? fromEvent(host, 'keydown') : EMPTY),
|
|
1653
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
1654
|
+
filter(event => !event.ctrlKey && !event.altKey && !event.metaKey && event.key?.trim().length === 1), // UNDOCUMENTED: `event.key` can be `undefined`, for example, when selecting an option from an input element's datalist.
|
|
1655
|
+
subscribeIn(fn => zone.runOutsideAngular(fn)), observeIn(fn => zone.run(fn)), takeUntilDestroyed())
|
|
1656
|
+
.subscribe(event => {
|
|
1657
|
+
this._filterField().focus();
|
|
1658
|
+
event.stopPropagation(); // prevent parent filter field from receiving focus.
|
|
1659
|
+
});
|
|
1660
|
+
}
|
|
1661
|
+
/**
|
|
1662
|
+
* Stops keydown event propagation to prevent side effects, such as closing an overlay on `Escape` or other keystrokes from triggering.
|
|
1663
|
+
*
|
|
1664
|
+
* Has no effect if a submenu or menu group.
|
|
1665
|
+
*/
|
|
1666
|
+
stopKeyDownEventPropagation() {
|
|
1667
|
+
if (this.role !== 'menu') {
|
|
1668
|
+
return;
|
|
1669
|
+
}
|
|
1670
|
+
const host = inject(ElementRef).nativeElement;
|
|
1671
|
+
const zone = inject(NgZone);
|
|
1672
|
+
fromEvent(host, 'keydown')
|
|
1673
|
+
.pipe(subscribeIn(fn => zone.runOutsideAngular(fn)), takeUntilDestroyed())
|
|
1674
|
+
.subscribe(event => {
|
|
1675
|
+
event.stopPropagation();
|
|
1676
|
+
});
|
|
1677
|
+
}
|
|
1678
|
+
/**
|
|
1679
|
+
* Computes whether the popover is hidden.
|
|
1680
|
+
*
|
|
1681
|
+
* The popover must be hidden until the initial width is computed. Otherwise, dynamic anchor positioning based on `--ɵsci-menu-width` causes flickering.
|
|
1682
|
+
* See `menu-opener.service.ts` for more details.
|
|
1683
|
+
*/
|
|
1684
|
+
computePopoverHidden() {
|
|
1685
|
+
return computed(() => this.role === 'menu' && !this.menuSize().width);
|
|
1686
|
+
}
|
|
1687
|
+
/**
|
|
1688
|
+
* Computes whether menu items are visible.
|
|
1689
|
+
*/
|
|
1690
|
+
computeGroupExpanded() {
|
|
1691
|
+
return linkedSignal(() => {
|
|
1692
|
+
const group = this.group();
|
|
1693
|
+
return !group || !group.collapsible || !group.collapsed || this._menuFilter.active();
|
|
1694
|
+
});
|
|
1695
|
+
}
|
|
1696
|
+
/**
|
|
1697
|
+
* Computes the menu size based on configured size constraints.
|
|
1698
|
+
*/
|
|
1699
|
+
computeMenuSize() {
|
|
1700
|
+
return linkedSignal(() => {
|
|
1701
|
+
if (this.role === 'group') {
|
|
1702
|
+
return {};
|
|
1703
|
+
}
|
|
1704
|
+
// Compute only once; otherwise, the popover would hide since the initial width is only computed initially.
|
|
1705
|
+
return untracked(() => {
|
|
1706
|
+
const defaultMinWidth = this.role === 'submenu' ? 'var(--sci-menu-submenu-min-width)' : 'var(--sci-menu-min-width)';
|
|
1707
|
+
const preferredMinWidth = this.minWidth() ?? defaultMinWidth;
|
|
1708
|
+
return {
|
|
1709
|
+
width: this.width(),
|
|
1710
|
+
minWidth: this.anchorWidth() ? `max(${preferredMinWidth}, ${this.anchorWidth()})` : preferredMinWidth,
|
|
1711
|
+
maxWidth: this.maxWidth() ?? this.width(),
|
|
1712
|
+
maxHeight: this.maxHeight(),
|
|
1713
|
+
};
|
|
1714
|
+
});
|
|
1715
|
+
});
|
|
1716
|
+
}
|
|
1717
|
+
/**
|
|
1718
|
+
* Evaluates whether the given menu item (or any of its child menu items) match the active filter.
|
|
1719
|
+
*/
|
|
1720
|
+
matchesFilter(menuItem) {
|
|
1721
|
+
return computed(() => {
|
|
1722
|
+
if (!this._menuFilter.active()) {
|
|
1723
|
+
return true;
|
|
1724
|
+
}
|
|
1725
|
+
switch (menuItem.type) {
|
|
1726
|
+
case 'menu-item':
|
|
1727
|
+
return menuItem.menu ? menuItem.menu.children.some(menuItem => this.matchesFilter(menuItem)()) : this._menuFilter.matches(menuItem)();
|
|
1728
|
+
case 'group':
|
|
1729
|
+
return menuItem.children.some(menuItem => this.matchesFilter(menuItem)());
|
|
1730
|
+
}
|
|
1731
|
+
});
|
|
1732
|
+
}
|
|
1733
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1734
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: SciMenuComponent, isStandalone: true, selector: "sci-menu", inputs: { menuItems: { classPropertyName: "menuItems", publicName: "menuItems", isSignal: true, isRequired: true, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, filter: { classPropertyName: "filter", publicName: "filter", isSignal: true, isRequired: false, transformFunction: null }, group: { classPropertyName: "group", publicName: "group", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, minWidth: { classPropertyName: "minWidth", publicName: "minWidth", isSignal: true, isRequired: false, transformFunction: null }, maxWidth: { classPropertyName: "maxWidth", publicName: "maxWidth", isSignal: true, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: true, isRequired: false, transformFunction: null }, glyphArea: { classPropertyName: "glyphArea", publicName: "glyphArea", isSignal: true, isRequired: false, transformFunction: null }, anchorWidth: { classPropertyName: "anchorWidth", publicName: "anchorWidth", isSignal: true, isRequired: false, transformFunction: null }, cssClass: { classPropertyName: "cssClass", publicName: "cssClass", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.--\u0275sci-menu-width": "menuSize().width", "style.--\u0275sci-menu-min-width": "menuSize().minWidth", "style.--\u0275sci-menu-max-width": "menuSize().maxWidth", "style.--\u0275sci-menu-max-height": "menuSize().maxHeight", "style.--\u0275sci-menu-scrolling": "scrolling() ? `true` : null", "attr.data-glyph-area": "hasGlyphArea() ? null : `none`", "style.visibility": "popoverHidden() ? `hidden` : null", "attr.tabindex": "role === `menu` ? `-1` : null", "class": "cssClass()" } }, providers: [
|
|
1735
|
+
providePopoverRef(),
|
|
1736
|
+
provideMenuFilter(),
|
|
1737
|
+
], viewQueries: [{ propertyName: "_filterField", first: true, predicate: SciMenuFilterComponent, descendants: true, isSignal: true }, { propertyName: "_viewport", first: true, predicate: SciViewportComponent, descendants: true, isSignal: true }, { propertyName: "popoverViewContainerRef", first: true, predicate: ["popover_view_container_ref"], descendants: true, read: ViewContainerRef, isSignal: true }], hostDirectives: [{ directive: i1$1.SciAttributesDirective, inputs: ["sciAttributes", "attributes"] }], ngImport: i0, template: "<!-- Filter Field -->\r\n@let filter = this.filter();\r\n@if (filter) {\r\n <div class=\"filter e2e-filter\">\r\n <sci-icon>scion.search</sci-icon>\r\n @let message = filter.placeholder?.() ?? '%scion.components.type_to_filter.action';\r\n <sci-menu-filter (textChange)=\"onFilterChange($event)\" [placeholder]=\"(message | sciText)()\"/>\r\n </div>\r\n <hr>\r\n}\r\n\r\n<!-- Group Header -->\r\n@if (group(); as group) {\r\n @if (group.collapsible) {\r\n <button class=\"group-header e2e-group-header\" (click)=\"onGroupHeaderButtonClick()\" (mouseenter)=\"onMenuItemMouseEnter(null)\">\r\n <sci-icon>{{isGroupExpanded() ? 'scion.chevron_down' : 'scion.chevron_right'}}</sci-icon>\r\n\r\n <div class=\"content\">\r\n <ng-container *ngTemplateOutlet=\"group_header_template\"/>\r\n </div>\r\n </button>\r\n } @else if (group.label) {\r\n <header class=\"group-header e2e-group-header\">\r\n <ng-container *ngTemplateOutlet=\"group_header_template\"/>\r\n </header>\r\n }\r\n\r\n <!-- Template for rendering group header. -->\r\n <ng-template #group_header_template>\r\n <sci-menu-group-label>{{group.label}}</sci-menu-group-label>\r\n\r\n @if (group.actions.length && !this.disabled()) {\r\n <sci-toolbar-group [menuItems]=\"group.actions\"\r\n [orientation]=\"'horizontal'\"\r\n [popoverViewContainerRef]=\"popoverViewContainerRef()\"\r\n (click)=\"$event.stopPropagation()\"\r\n class=\"actions e2e-actions\"/>\r\n }\r\n </ng-template>\r\n}\r\n\r\n<!-- Menu Items -->\r\n@if (isGroupExpanded()) {\r\n @if (maxHeight()) {\r\n <sci-viewport>\r\n <ng-container *ngTemplateOutlet=\"menu_items_template\"/>\r\n </sci-viewport>\r\n } @else {\r\n <ng-container *ngTemplateOutlet=\"menu_items_template\"/>\r\n }\r\n\r\n <!-- Renders menu items. -->\r\n <ng-template #menu_items_template>\r\n @for (menuItem of menuItemsFiltered(); track $index) {\r\n @let disabled = this.disabled() || menuItem.disabled?.() || false;\r\n\r\n @switch (menuItem.type) {\r\n @case ('menu-item') {\r\n <!-- Menu Item -->\r\n @if (!menuItem.menu) {\r\n @let hasActionToolbar = !disabled && menuItem.actions?.length;\r\n @let showAcceleratorInTooltip = menuItem.accelerator && hasActionToolbar;\r\n\r\n <button [attr.disabled]=\"disabled || null\"\r\n [attr.title]=\"showAcceleratorInTooltip ? (menuItem.accelerator | sciFormatAccelerator:menuItem.tooltip?.()) : menuItem.tooltip?.()\"\r\n [class.checked]=\"menuItem.checked?.()\"\r\n [class.active]=\"menuItem.active?.()\"\r\n [class]=\"menuItem.cssClass\" class=\"menu-item e2e-menu-item\"\r\n [sciAttributes]=\"menuItem.attributes\"\r\n (mouseenter)=\"onMenuItemMouseEnter(menuItem)\"\r\n (click)=\"onSelect(menuItem)\">\r\n <!-- Render icon if glyph area. -->\r\n @if (hasGlyphArea() && !group()?.hideGlyphArea) {\r\n <ng-container *ngTemplateOutlet=\"icon_template; context: {$implicit: menuItem}\"/>\r\n }\r\n\r\n <!-- Render label, accelerator and action toolbar. -->\r\n <div class=\"content\">\r\n <ng-container *ngTemplateOutlet=\"label_template; context: {$implicit: menuItem}\"/>\r\n\r\n @if (menuItem.accelerator) {\r\n <span class=\"accelerator\">{{menuItem.accelerator | sciFormatAccelerator}}</span>\r\n }\r\n\r\n @if (hasActionToolbar) {\r\n <sci-toolbar-group [menuItems]=\"menuItem.actions!\"\r\n [orientation]=\"'horizontal'\"\r\n [popoverViewContainerRef]=\"popoverViewContainerRef()\"\r\n [class.menu-open]=\"actions_toolbar.menuOpen()\"\r\n class=\"actions e2e-actions\"\r\n (click)=\"$event.stopPropagation()\"\r\n #actions_toolbar/>\r\n }\r\n </div>\r\n </button>\r\n }\r\n\r\n <!-- Submenu Item -->\r\n @if (menuItem.menu) {\r\n <button [attr.disabled]=\"disabled || null\"\r\n [attr.title]=\"menuItem.tooltip?.()\"\r\n [class.open]=\"activeSubmenuItem()?.menuItem === menuItem\"\r\n [class]=\"menuItem.cssClass\" class=\"menu e2e-menu-item\"\r\n [sciAttributes]=\"menuItem.attributes\"\r\n (mouseenter)=\"onSubmenuItemMouseEnter({menuItem, element})\"\r\n (click)=\"onSubmenuItemClick({menuItem, element})\"\r\n #element>\r\n @if (hasGlyphArea() && !group()?.hideGlyphArea) {\r\n <!-- Render icon if glyph area. -->\r\n <ng-container *ngTemplateOutlet=\"icon_template; context: {$implicit: menuItem}\"/>\r\n }\r\n\r\n <!-- Render label. -->\r\n <div class=\"content\">\r\n <ng-container *ngTemplateOutlet=\"label_template; context: {$implicit: menuItem}\"/>\r\n <sci-icon class=\"chevron\">scion.chevron_right</sci-icon>\r\n </div>\r\n </button>\r\n }\r\n }\r\n @case ('group') {\r\n <sci-menu-group [group]=\"menuItem\"\r\n [glyphArea]=\"hasGlyphArea()\"\r\n [disabled]=\"disabled\"\r\n [cssClass]=\"menuItem.cssClass ?? []\"/>\r\n }\r\n }\r\n } @empty {\r\n <!-- TODO [Angular 23] Replace with optional chaining operator once the Angular compiler no longer logs the NG8107 false-positive warning. -->\r\n <!-- @let message = filter?.notFoundMessage?.() ?? '%scion.components.no_items.message'; -->\r\n @let message = (filter && filter.notFoundMessage?.()) ?? '%scion.components.no_items.message';\r\n <div class=\"message no-items e2e-no-items\">{{(message | sciText)()}}</div>\r\n }\r\n </ng-template>\r\n}\r\n\r\n<!-- Location where to insert the menu popover. Must not be a child of the menu item button to prevent hovering the menu item when hovering menu items in the popover. -->\r\n<ng-container #popover_view_container_ref/>\r\n\r\n<!-- Renders the icon of the passed menu item. -->\r\n<ng-template #icon_template let-menuItem>\r\n @if (menuItem.checked !== undefined) {\r\n <sci-icon>{{menuItem.checked() ? 'scion.checkmark' : undefined}}</sci-icon>\r\n } @else if (menuItem.iconLigature) {\r\n <sci-icon>{{menuItem.iconLigature()}}</sci-icon>\r\n } @else if (menuItem.iconComponent) {\r\n <sci-menu-item-icon>\r\n <ng-container *sciComponentOutlet=\"menuItem.iconComponent\"/>\r\n </sci-menu-item-icon>\r\n } @else {\r\n <sci-menu-item-icon/>\r\n }\r\n</ng-template>\r\n\r\n<!-- Renders the label of the passed menu item. -->\r\n<ng-template #label_template let-menuItem>\r\n @if (menuItem.labelText) {\r\n <sci-menu-item-label>\r\n {{menuItem.labelText()}}\r\n </sci-menu-item-label>\r\n } @else if (menuItem.labelComponent) {\r\n <sci-menu-item-label>\r\n <ng-container *sciComponentOutlet=\"menuItem.labelComponent\"/>\r\n </sci-menu-item-label>\r\n }\r\n</ng-template>\r\n", styles: ["@charset \"UTF-8\";:host{--sci-icon-size: var(--sci-menu-item-size);--\\275sci-menu-item-padding-inline: calc(.5 * var(--sci-menu-item-size));--\\275sci-menu-item-padding-block: calc(.225 * var(--sci-menu-item-size));--\\275sci-menu-item-min-height: calc(var(--sci-menu-item-size) + 2px);--\\275sci-menu-item-gap-inline: calc(.75 * var(--sci-menu-item-size));--\\275sci-menu-item-gap-block: calc(.125 * var(--sci-menu-item-size));--\\275sci-menu-padding-inline: .4em;--\\275sci-menu-padding-block: .3em;--\\275sci-menu-glyph-area: true;--\\275sci-menu-action-background-color-hover: color-mix(in srgb, var(--sci-menu-item-background-color-hover) 90%, light-dark(var(--sci-static-color-black), var(--sci-static-color-white)));--\\275sci-menu-action-background-color-active: color-mix(in srgb, var(--sci-menu-item-background-color-active) 90%, light-dark(var(--sci-static-color-black), var(--sci-static-color-white)));--\\275sci-menu-item-chevron-x-offset: calc(var(--sci-menu-item-size) / 4);--\\275sci-menu-font-size: var(--sci-menu-font-size, calc(.8125 * var(--sci-menu-item-size)));--\\275sci-menu-width: max-content;--\\275sci-menu-min-width: initial;--\\275sci-menu-max-width: initial;--\\275sci-menu-max-height: initial;--\\275sci-menu-scrolling: false;display:grid;gap:var(--\\275sci-menu-item-gap-block) var(--\\275sci-menu-item-gap-inline);align-items:center;font-size:var(--\\275sci-menu-font-size);border:1px solid var(--sci-color-border);border-radius:var(--sci-menu-border-radius);background-color:var(--sci-color-background-elevation);padding:var(--\\275sci-menu-padding-block) var(--\\275sci-menu-padding-inline);outline:none;box-sizing:border-box;width:var(--\\275sci-menu-width);min-width:var(--\\275sci-menu-min-width);max-width:var(--\\275sci-menu-max-width);-webkit-user-select:none;user-select:none;grid-template-columns:var(--sci-menu-item-size) minmax(0,1fr)}:host[data-glyph-area=none]{--\\275sci-menu-glyph-area: false;grid-template-columns:minmax(0,1fr)}:host[popover]{position:fixed;margin:unset;inset:unset;height:auto}:host[popover]:not(:popover-open){display:none}:host:not([popover]){grid-column:1/-1;grid-template-columns:subgrid;border:unset;border-radius:unset;color:inherit;padding:unset;background-color:inherit}:host>header.group-header{grid-column:1/-1;display:flex;gap:var(--\\275sci-menu-item-gap-inline);justify-content:space-between;align-items:center;padding:var(--\\275sci-menu-item-padding-block) var(--\\275sci-menu-item-padding-inline);box-sizing:content-box;min-height:var(--\\275sci-menu-item-min-height)}:host>header.group-header>sci-menu-group-label{flex:auto;color:var(--sci-menu-group-header-text-color);font-family:var(--sci-menu-group-header-font-family),sans-serif;font-weight:var(--sci-menu-group-header-font-weight);font-size:var(--sci-menu-group-header-font-size);text-overflow:ellipsis;overflow:hidden;white-space:nowrap}:host>header.group-header>sci-toolbar-group.actions{flex:none;align-self:center}:host>button:is(.group-header,#sci-reset){all:unset;text-wrap:nowrap;padding:var(--\\275sci-menu-item-padding-block) var(--\\275sci-menu-item-padding-inline);border-radius:var(--sci-menu-item-border-radius);-webkit-user-select:none;user-select:none;cursor:var(--sci-menu-item-cursor);color:var(--sci-menu-item-text-color);min-height:var(--\\275sci-menu-item-min-height);box-sizing:content-box}@container not style(--\\275sci-menu-scrolling: true){:host>button:is(.group-header,#sci-reset):hover:where(:not(:disabled)){background-color:var(--sci-menu-item-background-color-hover)}}:host>button:is(.group-header,#sci-reset):active:where(:not(:disabled)):focus{background-color:var(--sci-menu-item-background-color-active)}:host>button:is(.group-header,#sci-reset):focus:not(:focus-visible){outline:none}:host>button:is(.group-header,#sci-reset):focus-visible{outline:var(--sci-menu-item-outline-width) solid var(--sci-color-accent)}:host>button:is(.group-header,#sci-reset):disabled{color:var(--sci-menu-item-text-color-disabled)}:host>button:is(.group-header,#sci-reset){grid-column:1/-1;display:inline-grid;grid-template-columns:subgrid;align-items:center}:host>button:is(.group-header,#sci-reset):hover{--\\275sci-menu-item-hover: true}:host>button:is(.group-header,#sci-reset)>sci-icon{--sci-icon-size: calc(.875 * var(--sci-icon-size))}:host>button:is(.group-header,#sci-reset)>div.content{display:flex;gap:var(--\\275sci-menu-item-gap-inline);justify-content:space-between;align-items:center}:host>button:is(.group-header,#sci-reset)>div.content>sci-menu-group-label{flex:auto;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}:host>button:is(.group-header,#sci-reset)>div.content>sci-toolbar-group.actions{flex:none;align-self:center}@container style(--\\275sci-menu-item-hover: true){:host>button:is(.group-header,#sci-reset)>div.content>sci-toolbar-group.actions{--sci-toolbar-item-background-color-hover: var(--\\275sci-menu-action-background-color-hover);--sci-toolbar-item-background-color-active: var(--\\275sci-menu-action-background-color-active)}}:host:has(>sci-viewport){padding:var(--\\275sci-menu-padding-block) 0}:host:has(>sci-viewport)>header.group-header,:host:has(>sci-viewport)>button.group-header,:host:has(>sci-viewport)>div.filter,:host:has(>sci-viewport)>hr{margin:0 var(--\\275sci-menu-padding-inline)}:host>sci-viewport{grid-column:1/-1;max-height:var(--\\275sci-menu-max-height);box-sizing:border-box}:host>sci-viewport::part(content){grid-column:1/-1;display:grid;gap:var(--\\275sci-menu-item-gap-block) var(--\\275sci-menu-item-gap-inline);grid-template-columns:var(--sci-menu-item-size) minmax(0,1fr);padding:2px var(--\\275sci-menu-padding-inline)}@container style(--\\275sci-menu-glyph-area: false){:host>sci-viewport::part(content){grid-template-columns:minmax(0,1fr)}}:host>div.filter{grid-column:1/-1;display:inline-grid;grid-template-columns:subgrid;align-items:center;padding:var(--\\275sci-menu-item-padding-block) var(--\\275sci-menu-item-padding-inline);border-radius:var(--sci-menu-filter-outline-radius);box-sizing:content-box;min-height:var(--\\275sci-menu-item-min-height)}:host>div.filter:focus-within{outline:var(--sci-menu-filter-outline-width) solid var(--sci-color-accent)}@container style(--\\275sci-menu-glyph-area: false){:host>div.filter{display:flex;gap:var(--\\275sci-menu-item-gap-inline)}:host>div.filter>sci-icon{flex:none}:host>div.filter>sci-menu-filter{flex:auto}}:host>hr{all:unset;grid-column:1/-1;height:1px;background-color:var(--sci-color-border)}:host div.message.no-items{grid-column:1/-1;justify-self:center;color:var(--sci-color-text-subtlest);padding:var(--\\275sci-menu-item-padding-block) var(--\\275sci-menu-item-padding-inline)}:host *:not(hr)+sci-menu-group:not(:has(>sci-menu-group:first-child)):before{content:\"\";grid-column:1/-1;height:1px;background-color:var(--sci-color-border)}:host sci-menu-group:not(:last-child):not(:has(+sci-menu-group,+sci-menu:last-child)):after{content:\"\";grid-column:1/-1;height:1px;background-color:var(--sci-color-border)}:host button:is(.menu-item,.menu,#sci-reset){all:unset;text-wrap:nowrap;padding:var(--\\275sci-menu-item-padding-block) var(--\\275sci-menu-item-padding-inline);border-radius:var(--sci-menu-item-border-radius);-webkit-user-select:none;user-select:none;cursor:var(--sci-menu-item-cursor);color:var(--sci-menu-item-text-color);min-height:var(--\\275sci-menu-item-min-height);box-sizing:content-box}@container not style(--\\275sci-menu-scrolling: true){:host button:is(.menu-item,.menu,#sci-reset):hover:where(:not(:disabled)){background-color:var(--sci-menu-item-background-color-hover)}}:host button:is(.menu-item,.menu,#sci-reset):active:where(:not(:disabled)):focus{background-color:var(--sci-menu-item-background-color-active)}:host button:is(.menu-item,.menu,#sci-reset):focus:not(:focus-visible){outline:none}:host button:is(.menu-item,.menu,#sci-reset):focus-visible{outline:var(--sci-menu-item-outline-width) solid var(--sci-color-accent)}:host button:is(.menu-item,.menu,#sci-reset):disabled{color:var(--sci-menu-item-text-color-disabled)}:host button:is(.menu-item,.menu,#sci-reset){grid-column:1/-1;display:inline-grid;grid-template-columns:subgrid;align-items:center;position:relative}:host button:is(.menu-item,.menu,#sci-reset):hover{--\\275sci-menu-item-hover: true}:host button:is(.menu-item,.menu,#sci-reset).checked{--\\275sci-menu-item-checked: true}:host button:is(.menu-item,.menu,#sci-reset).active{--\\275sci-menu-item-active: true}:host button:is(.menu-item,.menu,#sci-reset):has(sci-toolbar-group.actions.menu-open){--\\275sci-menu-item-actions-menu-open: true}:host button:is(.menu-item,.menu,#sci-reset):not(:disabled):is(:hover,.active,:has(sci-toolbar-group.actions.menu-open)){--\\275sci-action-toolbar-visible: true}:host button:is(.menu-item,.menu,#sci-reset).menu-item.active:before{position:absolute;content:\"\";box-sizing:border-box;height:calc(100% - var(--sci-menu-item-active-indicator-border-radius));width:var(--sci-menu-item-active-indicator-size);left:calc(-1 * var(--\\275sci-menu-padding-inline) + 1px);border-radius:var(--sci-menu-item-active-indicator-border-radius);background-color:var(--sci-menu-item-active-indicator-background-color)}:host button:is(.menu-item,.menu,#sci-reset).menu.open{background-color:var(--sci-menu-item-background-color-hover)}:host button:is(.menu-item,.menu,#sci-reset).menu>div.content>sci-icon.chevron{transform:translate(var(--\\275sci-menu-item-chevron-x-offset));--sci-icon-size: calc(.875 * var(--sci-icon-size))}:host button:is(.menu-item,.menu,#sci-reset).menu-item.checked>sci-icon:first-child{--sci-icon-size: calc(.875 * var(--sci-icon-size))}:host button:is(.menu-item,.menu,#sci-reset)>sci-menu-item-icon{display:inline-grid;place-content:center;height:var(--sci-menu-item-size);width:var(--sci-menu-item-size);overflow:hidden}:host button:is(.menu-item,.menu,#sci-reset)>div.content{display:flex;align-items:center;gap:var(--\\275sci-menu-item-gap-inline)}:host button:is(.menu-item,.menu,#sci-reset)>div.content:first-child{grid-column:1/-1}:host button:is(.menu-item,.menu,#sci-reset)>div.content>sci-menu-item-label{flex:auto;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}:host button:is(.menu-item,.menu,#sci-reset)>div.content>span.accelerator{flex:none;color:var(--sci-menu-item-accelerator-text-color);font-size:.9em}@container style(--\\275sci-action-toolbar-visible: true){:host button:is(.menu-item,.menu,#sci-reset)>div.content>span.accelerator:has(+sci-toolbar-group.actions){display:none}}:host button:is(.menu-item,.menu,#sci-reset)>div.content>sci-toolbar-group.actions{flex:none;align-self:center}@container style(--\\275sci-menu-item-hover: true){:host button:is(.menu-item,.menu,#sci-reset)>div.content>sci-toolbar-group.actions{--sci-toolbar-item-background-color-hover: var(--\\275sci-menu-action-background-color-hover);--sci-toolbar-item-background-color-active: var(--\\275sci-menu-action-background-color-active)}}@container not style(--\\275sci-action-toolbar-visible: true){:host button:is(.menu-item,.menu,#sci-reset)>div.content>sci-toolbar-group.actions{display:none}}@container style(--\\275sci-menu-scrolling: true) and (not style(--\\275sci-menu-item-active: true)) and (not style(--\\275sci-menu-item-actions-menu-open: true)){:host button:is(.menu-item,.menu,#sci-reset)>div.content>sci-toolbar-group.actions{visibility:hidden}}:host sci-toolbar-group.actions{transform:translate(var(--\\275sci-menu-item-chevron-x-offset));--sci-toolbar-item-size: var(--sci-menu-item-size);--\\275sci-toolbar-item-padding-block: 0}\n"], dependencies: [{ kind: "component", type: SciMenuGroupComponent, selector: "sci-menu-group", inputs: ["group", "glyphArea", "disabled", "cssClass"] }, { kind: "component", type: SciMenuFilterComponent, selector: "sci-menu-filter", inputs: ["placeholder", "text"], outputs: ["textChange"] }, { kind: "component", type: SciToolbarGroupComponent, selector: "sci-toolbar-group", inputs: ["menuItems", "orientation", "disabled", "popoverViewContainerRef"] }, { kind: "component", type: SciViewportComponent, selector: "sci-viewport", inputs: ["scrollbarStyle"], outputs: ["scroll"] }, { kind: "directive", type: SciComponentOutletDirective, selector: "ng-template[sciComponentOutlet]", inputs: ["sciComponentOutlet"] }, { kind: "directive", type: SciAttributesDirective, selector: "[sciAttributes]", inputs: ["sciAttributes"] }, { kind: "component", type: SciIconComponent, selector: "sci-icon" }, { kind: "component", type: SciMenuGroupLabelComponent, selector: "sci-menu-group-label" }, { kind: "component", type: SciMenuItemLabelComponent, selector: "sci-menu-item-label" }, { kind: "component", type: SciMenuItemIconComponent, selector: "sci-menu-item-icon" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: SciTextPipe, name: "sciText" }, { kind: "pipe", type: SciFormatAcceleratorPipe, name: "sciFormatAccelerator" }] });
|
|
1738
|
+
}
|
|
1739
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciMenuComponent, decorators: [{
|
|
1740
|
+
type: Component,
|
|
1741
|
+
args: [{ selector: 'sci-menu', imports: [
|
|
1742
|
+
SciMenuGroupComponent,
|
|
1743
|
+
SciMenuFilterComponent,
|
|
1744
|
+
SciToolbarGroupComponent,
|
|
1745
|
+
SciViewportComponent,
|
|
1746
|
+
SciTextPipe,
|
|
1747
|
+
SciComponentOutletDirective,
|
|
1748
|
+
SciAttributesDirective,
|
|
1749
|
+
SciIconComponent,
|
|
1750
|
+
SciMenuGroupLabelComponent,
|
|
1751
|
+
SciMenuItemLabelComponent,
|
|
1752
|
+
SciMenuItemIconComponent,
|
|
1753
|
+
SciFormatAcceleratorPipe,
|
|
1754
|
+
NgTemplateOutlet,
|
|
1755
|
+
], providers: [
|
|
1756
|
+
providePopoverRef(),
|
|
1757
|
+
provideMenuFilter(),
|
|
1758
|
+
], host: {
|
|
1759
|
+
'[style.--ɵsci-menu-width]': 'menuSize().width',
|
|
1760
|
+
'[style.--ɵsci-menu-min-width]': 'menuSize().minWidth',
|
|
1761
|
+
'[style.--ɵsci-menu-max-width]': 'menuSize().maxWidth',
|
|
1762
|
+
'[style.--ɵsci-menu-max-height]': 'menuSize().maxHeight',
|
|
1763
|
+
'[style.--ɵsci-menu-scrolling]': 'scrolling() ? `true` : null',
|
|
1764
|
+
'[attr.data-glyph-area]': 'hasGlyphArea() ? null : `none`',
|
|
1765
|
+
// Hide popover until computed the initial width to prevent flickering caused by dynamic anchor positioning based on `--ɵsci-menu-width`. See `menu-opener.service.ts` for more details.
|
|
1766
|
+
'[style.visibility]': 'popoverHidden() ? `hidden` : null',
|
|
1767
|
+
'[attr.tabindex]': 'role === `menu` ? `-1` : null', // enable programmatic focus of popover
|
|
1768
|
+
'[class]': 'cssClass()',
|
|
1769
|
+
}, hostDirectives: [
|
|
1770
|
+
{ directive: SciAttributesDirective, inputs: ['sciAttributes: attributes'] },
|
|
1771
|
+
], template: "<!-- Filter Field -->\r\n@let filter = this.filter();\r\n@if (filter) {\r\n <div class=\"filter e2e-filter\">\r\n <sci-icon>scion.search</sci-icon>\r\n @let message = filter.placeholder?.() ?? '%scion.components.type_to_filter.action';\r\n <sci-menu-filter (textChange)=\"onFilterChange($event)\" [placeholder]=\"(message | sciText)()\"/>\r\n </div>\r\n <hr>\r\n}\r\n\r\n<!-- Group Header -->\r\n@if (group(); as group) {\r\n @if (group.collapsible) {\r\n <button class=\"group-header e2e-group-header\" (click)=\"onGroupHeaderButtonClick()\" (mouseenter)=\"onMenuItemMouseEnter(null)\">\r\n <sci-icon>{{isGroupExpanded() ? 'scion.chevron_down' : 'scion.chevron_right'}}</sci-icon>\r\n\r\n <div class=\"content\">\r\n <ng-container *ngTemplateOutlet=\"group_header_template\"/>\r\n </div>\r\n </button>\r\n } @else if (group.label) {\r\n <header class=\"group-header e2e-group-header\">\r\n <ng-container *ngTemplateOutlet=\"group_header_template\"/>\r\n </header>\r\n }\r\n\r\n <!-- Template for rendering group header. -->\r\n <ng-template #group_header_template>\r\n <sci-menu-group-label>{{group.label}}</sci-menu-group-label>\r\n\r\n @if (group.actions.length && !this.disabled()) {\r\n <sci-toolbar-group [menuItems]=\"group.actions\"\r\n [orientation]=\"'horizontal'\"\r\n [popoverViewContainerRef]=\"popoverViewContainerRef()\"\r\n (click)=\"$event.stopPropagation()\"\r\n class=\"actions e2e-actions\"/>\r\n }\r\n </ng-template>\r\n}\r\n\r\n<!-- Menu Items -->\r\n@if (isGroupExpanded()) {\r\n @if (maxHeight()) {\r\n <sci-viewport>\r\n <ng-container *ngTemplateOutlet=\"menu_items_template\"/>\r\n </sci-viewport>\r\n } @else {\r\n <ng-container *ngTemplateOutlet=\"menu_items_template\"/>\r\n }\r\n\r\n <!-- Renders menu items. -->\r\n <ng-template #menu_items_template>\r\n @for (menuItem of menuItemsFiltered(); track $index) {\r\n @let disabled = this.disabled() || menuItem.disabled?.() || false;\r\n\r\n @switch (menuItem.type) {\r\n @case ('menu-item') {\r\n <!-- Menu Item -->\r\n @if (!menuItem.menu) {\r\n @let hasActionToolbar = !disabled && menuItem.actions?.length;\r\n @let showAcceleratorInTooltip = menuItem.accelerator && hasActionToolbar;\r\n\r\n <button [attr.disabled]=\"disabled || null\"\r\n [attr.title]=\"showAcceleratorInTooltip ? (menuItem.accelerator | sciFormatAccelerator:menuItem.tooltip?.()) : menuItem.tooltip?.()\"\r\n [class.checked]=\"menuItem.checked?.()\"\r\n [class.active]=\"menuItem.active?.()\"\r\n [class]=\"menuItem.cssClass\" class=\"menu-item e2e-menu-item\"\r\n [sciAttributes]=\"menuItem.attributes\"\r\n (mouseenter)=\"onMenuItemMouseEnter(menuItem)\"\r\n (click)=\"onSelect(menuItem)\">\r\n <!-- Render icon if glyph area. -->\r\n @if (hasGlyphArea() && !group()?.hideGlyphArea) {\r\n <ng-container *ngTemplateOutlet=\"icon_template; context: {$implicit: menuItem}\"/>\r\n }\r\n\r\n <!-- Render label, accelerator and action toolbar. -->\r\n <div class=\"content\">\r\n <ng-container *ngTemplateOutlet=\"label_template; context: {$implicit: menuItem}\"/>\r\n\r\n @if (menuItem.accelerator) {\r\n <span class=\"accelerator\">{{menuItem.accelerator | sciFormatAccelerator}}</span>\r\n }\r\n\r\n @if (hasActionToolbar) {\r\n <sci-toolbar-group [menuItems]=\"menuItem.actions!\"\r\n [orientation]=\"'horizontal'\"\r\n [popoverViewContainerRef]=\"popoverViewContainerRef()\"\r\n [class.menu-open]=\"actions_toolbar.menuOpen()\"\r\n class=\"actions e2e-actions\"\r\n (click)=\"$event.stopPropagation()\"\r\n #actions_toolbar/>\r\n }\r\n </div>\r\n </button>\r\n }\r\n\r\n <!-- Submenu Item -->\r\n @if (menuItem.menu) {\r\n <button [attr.disabled]=\"disabled || null\"\r\n [attr.title]=\"menuItem.tooltip?.()\"\r\n [class.open]=\"activeSubmenuItem()?.menuItem === menuItem\"\r\n [class]=\"menuItem.cssClass\" class=\"menu e2e-menu-item\"\r\n [sciAttributes]=\"menuItem.attributes\"\r\n (mouseenter)=\"onSubmenuItemMouseEnter({menuItem, element})\"\r\n (click)=\"onSubmenuItemClick({menuItem, element})\"\r\n #element>\r\n @if (hasGlyphArea() && !group()?.hideGlyphArea) {\r\n <!-- Render icon if glyph area. -->\r\n <ng-container *ngTemplateOutlet=\"icon_template; context: {$implicit: menuItem}\"/>\r\n }\r\n\r\n <!-- Render label. -->\r\n <div class=\"content\">\r\n <ng-container *ngTemplateOutlet=\"label_template; context: {$implicit: menuItem}\"/>\r\n <sci-icon class=\"chevron\">scion.chevron_right</sci-icon>\r\n </div>\r\n </button>\r\n }\r\n }\r\n @case ('group') {\r\n <sci-menu-group [group]=\"menuItem\"\r\n [glyphArea]=\"hasGlyphArea()\"\r\n [disabled]=\"disabled\"\r\n [cssClass]=\"menuItem.cssClass ?? []\"/>\r\n }\r\n }\r\n } @empty {\r\n <!-- TODO [Angular 23] Replace with optional chaining operator once the Angular compiler no longer logs the NG8107 false-positive warning. -->\r\n <!-- @let message = filter?.notFoundMessage?.() ?? '%scion.components.no_items.message'; -->\r\n @let message = (filter && filter.notFoundMessage?.()) ?? '%scion.components.no_items.message';\r\n <div class=\"message no-items e2e-no-items\">{{(message | sciText)()}}</div>\r\n }\r\n </ng-template>\r\n}\r\n\r\n<!-- Location where to insert the menu popover. Must not be a child of the menu item button to prevent hovering the menu item when hovering menu items in the popover. -->\r\n<ng-container #popover_view_container_ref/>\r\n\r\n<!-- Renders the icon of the passed menu item. -->\r\n<ng-template #icon_template let-menuItem>\r\n @if (menuItem.checked !== undefined) {\r\n <sci-icon>{{menuItem.checked() ? 'scion.checkmark' : undefined}}</sci-icon>\r\n } @else if (menuItem.iconLigature) {\r\n <sci-icon>{{menuItem.iconLigature()}}</sci-icon>\r\n } @else if (menuItem.iconComponent) {\r\n <sci-menu-item-icon>\r\n <ng-container *sciComponentOutlet=\"menuItem.iconComponent\"/>\r\n </sci-menu-item-icon>\r\n } @else {\r\n <sci-menu-item-icon/>\r\n }\r\n</ng-template>\r\n\r\n<!-- Renders the label of the passed menu item. -->\r\n<ng-template #label_template let-menuItem>\r\n @if (menuItem.labelText) {\r\n <sci-menu-item-label>\r\n {{menuItem.labelText()}}\r\n </sci-menu-item-label>\r\n } @else if (menuItem.labelComponent) {\r\n <sci-menu-item-label>\r\n <ng-container *sciComponentOutlet=\"menuItem.labelComponent\"/>\r\n </sci-menu-item-label>\r\n }\r\n</ng-template>\r\n", styles: ["@charset \"UTF-8\";:host{--sci-icon-size: var(--sci-menu-item-size);--\\275sci-menu-item-padding-inline: calc(.5 * var(--sci-menu-item-size));--\\275sci-menu-item-padding-block: calc(.225 * var(--sci-menu-item-size));--\\275sci-menu-item-min-height: calc(var(--sci-menu-item-size) + 2px);--\\275sci-menu-item-gap-inline: calc(.75 * var(--sci-menu-item-size));--\\275sci-menu-item-gap-block: calc(.125 * var(--sci-menu-item-size));--\\275sci-menu-padding-inline: .4em;--\\275sci-menu-padding-block: .3em;--\\275sci-menu-glyph-area: true;--\\275sci-menu-action-background-color-hover: color-mix(in srgb, var(--sci-menu-item-background-color-hover) 90%, light-dark(var(--sci-static-color-black), var(--sci-static-color-white)));--\\275sci-menu-action-background-color-active: color-mix(in srgb, var(--sci-menu-item-background-color-active) 90%, light-dark(var(--sci-static-color-black), var(--sci-static-color-white)));--\\275sci-menu-item-chevron-x-offset: calc(var(--sci-menu-item-size) / 4);--\\275sci-menu-font-size: var(--sci-menu-font-size, calc(.8125 * var(--sci-menu-item-size)));--\\275sci-menu-width: max-content;--\\275sci-menu-min-width: initial;--\\275sci-menu-max-width: initial;--\\275sci-menu-max-height: initial;--\\275sci-menu-scrolling: false;display:grid;gap:var(--\\275sci-menu-item-gap-block) var(--\\275sci-menu-item-gap-inline);align-items:center;font-size:var(--\\275sci-menu-font-size);border:1px solid var(--sci-color-border);border-radius:var(--sci-menu-border-radius);background-color:var(--sci-color-background-elevation);padding:var(--\\275sci-menu-padding-block) var(--\\275sci-menu-padding-inline);outline:none;box-sizing:border-box;width:var(--\\275sci-menu-width);min-width:var(--\\275sci-menu-min-width);max-width:var(--\\275sci-menu-max-width);-webkit-user-select:none;user-select:none;grid-template-columns:var(--sci-menu-item-size) minmax(0,1fr)}:host[data-glyph-area=none]{--\\275sci-menu-glyph-area: false;grid-template-columns:minmax(0,1fr)}:host[popover]{position:fixed;margin:unset;inset:unset;height:auto}:host[popover]:not(:popover-open){display:none}:host:not([popover]){grid-column:1/-1;grid-template-columns:subgrid;border:unset;border-radius:unset;color:inherit;padding:unset;background-color:inherit}:host>header.group-header{grid-column:1/-1;display:flex;gap:var(--\\275sci-menu-item-gap-inline);justify-content:space-between;align-items:center;padding:var(--\\275sci-menu-item-padding-block) var(--\\275sci-menu-item-padding-inline);box-sizing:content-box;min-height:var(--\\275sci-menu-item-min-height)}:host>header.group-header>sci-menu-group-label{flex:auto;color:var(--sci-menu-group-header-text-color);font-family:var(--sci-menu-group-header-font-family),sans-serif;font-weight:var(--sci-menu-group-header-font-weight);font-size:var(--sci-menu-group-header-font-size);text-overflow:ellipsis;overflow:hidden;white-space:nowrap}:host>header.group-header>sci-toolbar-group.actions{flex:none;align-self:center}:host>button:is(.group-header,#sci-reset){all:unset;text-wrap:nowrap;padding:var(--\\275sci-menu-item-padding-block) var(--\\275sci-menu-item-padding-inline);border-radius:var(--sci-menu-item-border-radius);-webkit-user-select:none;user-select:none;cursor:var(--sci-menu-item-cursor);color:var(--sci-menu-item-text-color);min-height:var(--\\275sci-menu-item-min-height);box-sizing:content-box}@container not style(--\\275sci-menu-scrolling: true){:host>button:is(.group-header,#sci-reset):hover:where(:not(:disabled)){background-color:var(--sci-menu-item-background-color-hover)}}:host>button:is(.group-header,#sci-reset):active:where(:not(:disabled)):focus{background-color:var(--sci-menu-item-background-color-active)}:host>button:is(.group-header,#sci-reset):focus:not(:focus-visible){outline:none}:host>button:is(.group-header,#sci-reset):focus-visible{outline:var(--sci-menu-item-outline-width) solid var(--sci-color-accent)}:host>button:is(.group-header,#sci-reset):disabled{color:var(--sci-menu-item-text-color-disabled)}:host>button:is(.group-header,#sci-reset){grid-column:1/-1;display:inline-grid;grid-template-columns:subgrid;align-items:center}:host>button:is(.group-header,#sci-reset):hover{--\\275sci-menu-item-hover: true}:host>button:is(.group-header,#sci-reset)>sci-icon{--sci-icon-size: calc(.875 * var(--sci-icon-size))}:host>button:is(.group-header,#sci-reset)>div.content{display:flex;gap:var(--\\275sci-menu-item-gap-inline);justify-content:space-between;align-items:center}:host>button:is(.group-header,#sci-reset)>div.content>sci-menu-group-label{flex:auto;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}:host>button:is(.group-header,#sci-reset)>div.content>sci-toolbar-group.actions{flex:none;align-self:center}@container style(--\\275sci-menu-item-hover: true){:host>button:is(.group-header,#sci-reset)>div.content>sci-toolbar-group.actions{--sci-toolbar-item-background-color-hover: var(--\\275sci-menu-action-background-color-hover);--sci-toolbar-item-background-color-active: var(--\\275sci-menu-action-background-color-active)}}:host:has(>sci-viewport){padding:var(--\\275sci-menu-padding-block) 0}:host:has(>sci-viewport)>header.group-header,:host:has(>sci-viewport)>button.group-header,:host:has(>sci-viewport)>div.filter,:host:has(>sci-viewport)>hr{margin:0 var(--\\275sci-menu-padding-inline)}:host>sci-viewport{grid-column:1/-1;max-height:var(--\\275sci-menu-max-height);box-sizing:border-box}:host>sci-viewport::part(content){grid-column:1/-1;display:grid;gap:var(--\\275sci-menu-item-gap-block) var(--\\275sci-menu-item-gap-inline);grid-template-columns:var(--sci-menu-item-size) minmax(0,1fr);padding:2px var(--\\275sci-menu-padding-inline)}@container style(--\\275sci-menu-glyph-area: false){:host>sci-viewport::part(content){grid-template-columns:minmax(0,1fr)}}:host>div.filter{grid-column:1/-1;display:inline-grid;grid-template-columns:subgrid;align-items:center;padding:var(--\\275sci-menu-item-padding-block) var(--\\275sci-menu-item-padding-inline);border-radius:var(--sci-menu-filter-outline-radius);box-sizing:content-box;min-height:var(--\\275sci-menu-item-min-height)}:host>div.filter:focus-within{outline:var(--sci-menu-filter-outline-width) solid var(--sci-color-accent)}@container style(--\\275sci-menu-glyph-area: false){:host>div.filter{display:flex;gap:var(--\\275sci-menu-item-gap-inline)}:host>div.filter>sci-icon{flex:none}:host>div.filter>sci-menu-filter{flex:auto}}:host>hr{all:unset;grid-column:1/-1;height:1px;background-color:var(--sci-color-border)}:host div.message.no-items{grid-column:1/-1;justify-self:center;color:var(--sci-color-text-subtlest);padding:var(--\\275sci-menu-item-padding-block) var(--\\275sci-menu-item-padding-inline)}:host *:not(hr)+sci-menu-group:not(:has(>sci-menu-group:first-child)):before{content:\"\";grid-column:1/-1;height:1px;background-color:var(--sci-color-border)}:host sci-menu-group:not(:last-child):not(:has(+sci-menu-group,+sci-menu:last-child)):after{content:\"\";grid-column:1/-1;height:1px;background-color:var(--sci-color-border)}:host button:is(.menu-item,.menu,#sci-reset){all:unset;text-wrap:nowrap;padding:var(--\\275sci-menu-item-padding-block) var(--\\275sci-menu-item-padding-inline);border-radius:var(--sci-menu-item-border-radius);-webkit-user-select:none;user-select:none;cursor:var(--sci-menu-item-cursor);color:var(--sci-menu-item-text-color);min-height:var(--\\275sci-menu-item-min-height);box-sizing:content-box}@container not style(--\\275sci-menu-scrolling: true){:host button:is(.menu-item,.menu,#sci-reset):hover:where(:not(:disabled)){background-color:var(--sci-menu-item-background-color-hover)}}:host button:is(.menu-item,.menu,#sci-reset):active:where(:not(:disabled)):focus{background-color:var(--sci-menu-item-background-color-active)}:host button:is(.menu-item,.menu,#sci-reset):focus:not(:focus-visible){outline:none}:host button:is(.menu-item,.menu,#sci-reset):focus-visible{outline:var(--sci-menu-item-outline-width) solid var(--sci-color-accent)}:host button:is(.menu-item,.menu,#sci-reset):disabled{color:var(--sci-menu-item-text-color-disabled)}:host button:is(.menu-item,.menu,#sci-reset){grid-column:1/-1;display:inline-grid;grid-template-columns:subgrid;align-items:center;position:relative}:host button:is(.menu-item,.menu,#sci-reset):hover{--\\275sci-menu-item-hover: true}:host button:is(.menu-item,.menu,#sci-reset).checked{--\\275sci-menu-item-checked: true}:host button:is(.menu-item,.menu,#sci-reset).active{--\\275sci-menu-item-active: true}:host button:is(.menu-item,.menu,#sci-reset):has(sci-toolbar-group.actions.menu-open){--\\275sci-menu-item-actions-menu-open: true}:host button:is(.menu-item,.menu,#sci-reset):not(:disabled):is(:hover,.active,:has(sci-toolbar-group.actions.menu-open)){--\\275sci-action-toolbar-visible: true}:host button:is(.menu-item,.menu,#sci-reset).menu-item.active:before{position:absolute;content:\"\";box-sizing:border-box;height:calc(100% - var(--sci-menu-item-active-indicator-border-radius));width:var(--sci-menu-item-active-indicator-size);left:calc(-1 * var(--\\275sci-menu-padding-inline) + 1px);border-radius:var(--sci-menu-item-active-indicator-border-radius);background-color:var(--sci-menu-item-active-indicator-background-color)}:host button:is(.menu-item,.menu,#sci-reset).menu.open{background-color:var(--sci-menu-item-background-color-hover)}:host button:is(.menu-item,.menu,#sci-reset).menu>div.content>sci-icon.chevron{transform:translate(var(--\\275sci-menu-item-chevron-x-offset));--sci-icon-size: calc(.875 * var(--sci-icon-size))}:host button:is(.menu-item,.menu,#sci-reset).menu-item.checked>sci-icon:first-child{--sci-icon-size: calc(.875 * var(--sci-icon-size))}:host button:is(.menu-item,.menu,#sci-reset)>sci-menu-item-icon{display:inline-grid;place-content:center;height:var(--sci-menu-item-size);width:var(--sci-menu-item-size);overflow:hidden}:host button:is(.menu-item,.menu,#sci-reset)>div.content{display:flex;align-items:center;gap:var(--\\275sci-menu-item-gap-inline)}:host button:is(.menu-item,.menu,#sci-reset)>div.content:first-child{grid-column:1/-1}:host button:is(.menu-item,.menu,#sci-reset)>div.content>sci-menu-item-label{flex:auto;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}:host button:is(.menu-item,.menu,#sci-reset)>div.content>span.accelerator{flex:none;color:var(--sci-menu-item-accelerator-text-color);font-size:.9em}@container style(--\\275sci-action-toolbar-visible: true){:host button:is(.menu-item,.menu,#sci-reset)>div.content>span.accelerator:has(+sci-toolbar-group.actions){display:none}}:host button:is(.menu-item,.menu,#sci-reset)>div.content>sci-toolbar-group.actions{flex:none;align-self:center}@container style(--\\275sci-menu-item-hover: true){:host button:is(.menu-item,.menu,#sci-reset)>div.content>sci-toolbar-group.actions{--sci-toolbar-item-background-color-hover: var(--\\275sci-menu-action-background-color-hover);--sci-toolbar-item-background-color-active: var(--\\275sci-menu-action-background-color-active)}}@container not style(--\\275sci-action-toolbar-visible: true){:host button:is(.menu-item,.menu,#sci-reset)>div.content>sci-toolbar-group.actions{display:none}}@container style(--\\275sci-menu-scrolling: true) and (not style(--\\275sci-menu-item-active: true)) and (not style(--\\275sci-menu-item-actions-menu-open: true)){:host button:is(.menu-item,.menu,#sci-reset)>div.content>sci-toolbar-group.actions{visibility:hidden}}:host sci-toolbar-group.actions{transform:translate(var(--\\275sci-menu-item-chevron-x-offset));--sci-toolbar-item-size: var(--sci-menu-item-size);--\\275sci-toolbar-item-padding-block: 0}\n"] }]
|
|
1772
|
+
}], ctorParameters: () => [], propDecorators: { menuItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "menuItems", required: true }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], filter: [{ type: i0.Input, args: [{ isSignal: true, alias: "filter", required: false }] }], group: [{ type: i0.Input, args: [{ isSignal: true, alias: "group", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], minWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "minWidth", required: false }] }], maxWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxWidth", required: false }] }], maxHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxHeight", required: false }] }], glyphArea: [{ type: i0.Input, args: [{ isSignal: true, alias: "glyphArea", required: false }] }], anchorWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "anchorWidth", required: false }] }], cssClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "cssClass", required: false }] }], _filterField: [{ type: i0.ViewChild, args: [i0.forwardRef(() => SciMenuFilterComponent), { isSignal: true }] }], _viewport: [{ type: i0.ViewChild, args: [i0.forwardRef(() => SciViewportComponent), { isSignal: true }] }], popoverViewContainerRef: [{ type: i0.ViewChild, args: ['popover_view_container_ref', { ...{ read: ViewContainerRef }, isSignal: true }] }] } });
|
|
1773
|
+
/**
|
|
1774
|
+
* Evaluates whether the given menu items (or any child groups) require a glyph area for displaying icons and checkmarks.
|
|
1775
|
+
*/
|
|
1776
|
+
function requiresGlyphArea(menuItems) {
|
|
1777
|
+
return menuItems.some(menuItem => {
|
|
1778
|
+
switch (menuItem.type) {
|
|
1779
|
+
case 'menu-item':
|
|
1780
|
+
return !!menuItem.iconLigature || !!menuItem.iconComponent || !!menuItem.checked;
|
|
1781
|
+
case 'group':
|
|
1782
|
+
return !!menuItem.collapsible || requiresGlyphArea(menuItem.children);
|
|
1783
|
+
}
|
|
1784
|
+
});
|
|
1785
|
+
}
|
|
1786
|
+
/**
|
|
1787
|
+
* Provides {@link SCI_MENU_COMPONENT_ROLE} for injection.
|
|
1788
|
+
*
|
|
1789
|
+
* Returns a host directive instead of a static provider to support component creation via {@link ViewContainerRef.createComponent}.
|
|
1790
|
+
*/
|
|
1791
|
+
function provideMenuComponentRole(role) {
|
|
1792
|
+
class RoleProvider {
|
|
1793
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: RoleProvider, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1794
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.7", type: RoleProvider, isStandalone: true, providers: [{ provide: SCI_MENU_COMPONENT_ROLE, useValue: role }], ngImport: i0 });
|
|
1795
|
+
}
|
|
1796
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: RoleProvider, decorators: [{
|
|
1797
|
+
type: Directive,
|
|
1798
|
+
args: [{ providers: [{ provide: SCI_MENU_COMPONENT_ROLE, useValue: role }] }]
|
|
1799
|
+
}] });
|
|
1800
|
+
return RoleProvider;
|
|
1801
|
+
}
|
|
1802
|
+
/**
|
|
1803
|
+
* Provides {@link PopoverRef} for injection.
|
|
1804
|
+
*/
|
|
1805
|
+
function providePopoverRef() {
|
|
1806
|
+
return {
|
|
1807
|
+
provide: PopoverRef,
|
|
1808
|
+
useFactory: () => {
|
|
1809
|
+
switch (inject(SCI_MENU_COMPONENT_ROLE)) {
|
|
1810
|
+
case 'menu':
|
|
1811
|
+
return new PopoverRef({ isSubmenu: false }); // top-level menu or menu in action toolbar
|
|
1812
|
+
case 'submenu':
|
|
1813
|
+
return new PopoverRef({ isSubmenu: true });
|
|
1814
|
+
case 'group':
|
|
1815
|
+
return inject(PopoverRef, { skipSelf: true });
|
|
1816
|
+
}
|
|
1817
|
+
},
|
|
1818
|
+
};
|
|
1819
|
+
}
|
|
1820
|
+
/**
|
|
1821
|
+
* Provides {@link MenuFilter} for injection.
|
|
1822
|
+
*/
|
|
1823
|
+
function provideMenuFilter() {
|
|
1824
|
+
return {
|
|
1825
|
+
provide: MenuFilter,
|
|
1826
|
+
useFactory: () => {
|
|
1827
|
+
switch (inject(SCI_MENU_COMPONENT_ROLE)) {
|
|
1828
|
+
case 'menu':
|
|
1829
|
+
return new MenuFilter(null); // top-level menu or menu in action toolbar
|
|
1830
|
+
case 'submenu':
|
|
1831
|
+
return new MenuFilter(inject(MenuFilter, { skipSelf: true }));
|
|
1832
|
+
case 'group':
|
|
1833
|
+
return inject(MenuFilter, { skipSelf: true });
|
|
1834
|
+
}
|
|
1835
|
+
},
|
|
1836
|
+
};
|
|
1837
|
+
}
|
|
1838
|
+
/**
|
|
1839
|
+
* Indicates whether {@link SciMenuComponent} is acting as a menu, submenu, or group.
|
|
1840
|
+
*/
|
|
1841
|
+
const SCI_MENU_COMPONENT_ROLE = new InjectionToken('SCI_MENU_COMPONENT_ROLE');
|
|
1842
|
+
|
|
1843
|
+
/*
|
|
1844
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
1845
|
+
*
|
|
1846
|
+
* This program and the accompanying materials are made
|
|
1847
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
1848
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
1849
|
+
*
|
|
1850
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
1851
|
+
*/
|
|
1852
|
+
class SciMenuOpener {
|
|
1853
|
+
_injector = inject(Injector);
|
|
1854
|
+
/**
|
|
1855
|
+
* Opens the specified menu at the specified position.
|
|
1856
|
+
*/
|
|
1857
|
+
openMenu(menu, options) {
|
|
1858
|
+
const injector = createDestroyableInjector({ parent: this._injector });
|
|
1859
|
+
const menuItems = Array.isArray(menu) ? signal(menu) : this.menuService.menuItems(menu, options.context ?? new Map(), { injector, metadata: options.metadata });
|
|
1860
|
+
return runInInjectionContext(injector, () => {
|
|
1861
|
+
// Get or create anchor at specified origin.
|
|
1862
|
+
const anchorElement = options.anchor instanceof ElementRef || options.anchor instanceof HTMLElement ? coerceElement(options.anchor) : this.createVirtualAnchor(options.anchor, { viewContainerRef: options.viewContainerRef });
|
|
1863
|
+
// Create menu popover.
|
|
1864
|
+
const componentRef = this.createMenuPopover(menuItems, anchorElement, options);
|
|
1865
|
+
componentRef.onDestroy(() => injector.destroy());
|
|
1866
|
+
return {
|
|
1867
|
+
close: () => componentRef.destroy(),
|
|
1868
|
+
onClose: onClose => componentRef.hostView.destroyed ? onClose() : componentRef.onDestroy(onClose), // Call callback immediately if already destroyed.
|
|
1869
|
+
};
|
|
1870
|
+
});
|
|
1871
|
+
}
|
|
1872
|
+
/**
|
|
1873
|
+
* Creates a menu popover displaying the specified menu items.
|
|
1874
|
+
*/
|
|
1875
|
+
createMenuPopover(menuItems, anchorElement, options) {
|
|
1876
|
+
const anchorSize = dimension(anchorElement);
|
|
1877
|
+
const align = options.align ?? 'vertical';
|
|
1878
|
+
const bindings = [
|
|
1879
|
+
inputBinding('menuItems', menuItems),
|
|
1880
|
+
inputBinding('filter', () => coerceFilterConfig(options.filter)),
|
|
1881
|
+
inputBinding('width', () => options.width),
|
|
1882
|
+
inputBinding('minWidth', () => options.minWidth),
|
|
1883
|
+
inputBinding('maxWidth', () => options.maxWidth),
|
|
1884
|
+
inputBinding('maxHeight', () => options.maxHeight),
|
|
1885
|
+
inputBinding('anchorWidth', () => align === 'vertical' ? anchorSize().offsetWidth : undefined),
|
|
1886
|
+
inputBinding('cssClass', () => Arrays.coerce(options.cssClass)),
|
|
1887
|
+
inputBinding('attributes', () => options.attributes),
|
|
1888
|
+
];
|
|
1889
|
+
// Create menu component and attach it to the DOM.
|
|
1890
|
+
const componentRef = (() => {
|
|
1891
|
+
const role = options.submenu ? 'submenu' : 'menu';
|
|
1892
|
+
if (options.viewContainerRef) {
|
|
1893
|
+
return options.viewContainerRef.createComponent(SciMenuComponent, {
|
|
1894
|
+
directives: [provideMenuComponentRole(role)],
|
|
1895
|
+
bindings,
|
|
1896
|
+
});
|
|
1897
|
+
}
|
|
1898
|
+
else {
|
|
1899
|
+
const componentRef = createComponent(SciMenuComponent, {
|
|
1900
|
+
environmentInjector: inject(EnvironmentInjector),
|
|
1901
|
+
directives: [provideMenuComponentRole(role)],
|
|
1902
|
+
bindings,
|
|
1903
|
+
});
|
|
1904
|
+
// Insert popover after the anchor node.
|
|
1905
|
+
anchorElement.after(componentRef.location.nativeElement);
|
|
1906
|
+
// Attach component to Angular component tree for change detection.
|
|
1907
|
+
inject(ApplicationRef).attachView(componentRef.hostView);
|
|
1908
|
+
return componentRef;
|
|
1909
|
+
}
|
|
1910
|
+
})();
|
|
1911
|
+
// Bind popover to anchor.
|
|
1912
|
+
const popoverElement = componentRef.location.nativeElement;
|
|
1913
|
+
this.bindPopoverToAnchor({ popoverElement, anchorElement, align, offset: options.offset });
|
|
1914
|
+
// Destroy component when closing the popover.
|
|
1915
|
+
popoverElement.addEventListener('toggle', (event) => {
|
|
1916
|
+
if (event.newState === 'closed') {
|
|
1917
|
+
componentRef.destroy();
|
|
1918
|
+
}
|
|
1919
|
+
});
|
|
1920
|
+
// Run change detection.
|
|
1921
|
+
componentRef.changeDetectorRef.detectChanges();
|
|
1922
|
+
// Show popover.
|
|
1923
|
+
popoverElement.showPopover();
|
|
1924
|
+
return componentRef;
|
|
1925
|
+
}
|
|
1926
|
+
/**
|
|
1927
|
+
* Positions the popover relative to the anchor element.
|
|
1928
|
+
*/
|
|
1929
|
+
bindPopoverToAnchor(binding) {
|
|
1930
|
+
const { popoverElement, anchorElement, align } = binding;
|
|
1931
|
+
const popoverId = `scimenu-${UUID.randomUUID().substring(0, 8)}`;
|
|
1932
|
+
// Connect anchor to popover.
|
|
1933
|
+
setAttributes(anchorElement, {
|
|
1934
|
+
'popovertarget': popoverId,
|
|
1935
|
+
'popovertargetaction': 'show',
|
|
1936
|
+
});
|
|
1937
|
+
setStyles(anchorElement, {
|
|
1938
|
+
'anchor-name': `--${popoverId}`,
|
|
1939
|
+
});
|
|
1940
|
+
// Connect popover to anchor.
|
|
1941
|
+
setAttributes(popoverElement, {
|
|
1942
|
+
'id': popoverId,
|
|
1943
|
+
'popover': 'auto',
|
|
1944
|
+
});
|
|
1945
|
+
// Position popover relative to anchor.
|
|
1946
|
+
if (align === 'horizontal') {
|
|
1947
|
+
setStyles(popoverElement, {
|
|
1948
|
+
'position-anchor': `--${popoverId}`,
|
|
1949
|
+
'position-try-fallbacks': 'flip-inline, flip-block, flip-inline flip-block',
|
|
1950
|
+
'top': `calc(anchor(top) - var(--ɵsci-menu-padding-block) + ${binding.offset?.y ?? 0}px)`,
|
|
1951
|
+
'left': `calc(anchor(right) + ${binding.offset?.x ?? 0}px)`,
|
|
1952
|
+
});
|
|
1953
|
+
}
|
|
1954
|
+
else {
|
|
1955
|
+
setStyles(popoverElement, {
|
|
1956
|
+
'position-anchor': `--${popoverId}`,
|
|
1957
|
+
'position-try-fallbacks': 'flip-block',
|
|
1958
|
+
'top': `calc(anchor(bottom) + ${binding.offset?.y ?? 0}px)`,
|
|
1959
|
+
'left': `min(calc(anchor(left) + ${binding.offset?.x ?? 0}px), calc(100% - var(--ɵsci-menu-width)))`, // `calc(100% - var(--ɵsci-menu-width))` prevents pushing the popover out of the page viewport on the right
|
|
1960
|
+
});
|
|
1961
|
+
}
|
|
1962
|
+
// Mark the anchor as open to support opening the menu from a toolbar button, not a toolbar menu button, e.g., to support opening a menu via accelerator.
|
|
1963
|
+
if (anchorElement.classList.contains('toolbar-button')) {
|
|
1964
|
+
anchorElement.classList.add('menu-open');
|
|
1965
|
+
}
|
|
1966
|
+
// Remove attributes and styles from the anchor element when the popover is closed, but only if not displaying another popover in the meantime.
|
|
1967
|
+
// No cleanup is required for the popover element because it is destroyed when closed.
|
|
1968
|
+
inject(DestroyRef).onDestroy(() => {
|
|
1969
|
+
if (anchorElement.getAttribute('popovertarget') !== popoverId) {
|
|
1970
|
+
return;
|
|
1971
|
+
}
|
|
1972
|
+
setAttributes(anchorElement, {
|
|
1973
|
+
'popovertarget': null,
|
|
1974
|
+
'popovertargetaction': null,
|
|
1975
|
+
});
|
|
1976
|
+
setStyles(anchorElement, {
|
|
1977
|
+
'anchor-name': null,
|
|
1978
|
+
});
|
|
1979
|
+
if (anchorElement.classList.contains('toolbar-button')) {
|
|
1980
|
+
anchorElement.classList.remove('menu-open');
|
|
1981
|
+
}
|
|
1982
|
+
});
|
|
1983
|
+
}
|
|
1984
|
+
/**
|
|
1985
|
+
* Creates a DOM element at the given pointer position or coordinate, optionally with a given dimension.
|
|
1986
|
+
*/
|
|
1987
|
+
createVirtualAnchor(anchor, options) {
|
|
1988
|
+
// Coerce coordinates.
|
|
1989
|
+
const { x, y, width, height } = anchor instanceof MouseEvent ? ({ x: anchor.x, y: anchor.y }) : anchor;
|
|
1990
|
+
// Create virtual anchor element at anchor bounds.
|
|
1991
|
+
const virtualAnchorElement = inject(DOCUMENT).createElement('div');
|
|
1992
|
+
inject(DestroyRef).onDestroy(() => virtualAnchorElement.remove());
|
|
1993
|
+
// Position the anchor element.
|
|
1994
|
+
setStyles(virtualAnchorElement, {
|
|
1995
|
+
'position': 'fixed',
|
|
1996
|
+
'left': `${x}px`,
|
|
1997
|
+
'top': `${y}px`,
|
|
1998
|
+
'width': width ? `${width}px` : '0',
|
|
1999
|
+
'height': height ? `${height}px` : '0',
|
|
2000
|
+
'pointer-events': 'none',
|
|
2001
|
+
});
|
|
2002
|
+
// Attach the anchor element to the DOM.
|
|
2003
|
+
if (options.viewContainerRef) {
|
|
2004
|
+
options.viewContainerRef.element.nativeElement.after(virtualAnchorElement);
|
|
2005
|
+
}
|
|
2006
|
+
else {
|
|
2007
|
+
inject(DOCUMENT).body.appendChild(virtualAnchorElement);
|
|
2008
|
+
}
|
|
2009
|
+
return virtualAnchorElement;
|
|
2010
|
+
}
|
|
2011
|
+
/**
|
|
2012
|
+
* Provides a reference to {@link ɵSciMenuService}.
|
|
2013
|
+
*
|
|
2014
|
+
* Note:
|
|
2015
|
+
* - {@link ɵSciMenuService} cannot be injected during construction as this would lead to an injection cycle.
|
|
2016
|
+
* - Do not use {@link SciMenuRegistry} directly to proxy calls through the adapter chain.
|
|
2017
|
+
*/
|
|
2018
|
+
get menuService() {
|
|
2019
|
+
return this._injector.get(ɵSciMenuService);
|
|
2020
|
+
}
|
|
2021
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciMenuOpener, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2022
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciMenuOpener, providedIn: 'root' });
|
|
2023
|
+
}
|
|
2024
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciMenuOpener, decorators: [{
|
|
2025
|
+
type: Injectable,
|
|
2026
|
+
args: [{ providedIn: 'root' }]
|
|
2027
|
+
}] });
|
|
2028
|
+
/**
|
|
2029
|
+
* Sets the specified attributes on the given element. Passing `null` for an attribute removes it.
|
|
2030
|
+
*/
|
|
2031
|
+
function setAttributes(element, attributes) {
|
|
2032
|
+
Object.entries(attributes).forEach(([name, value]) => {
|
|
2033
|
+
if (value === null) {
|
|
2034
|
+
element.removeAttribute(name);
|
|
2035
|
+
}
|
|
2036
|
+
else {
|
|
2037
|
+
element.setAttribute(name, value);
|
|
2038
|
+
}
|
|
2039
|
+
});
|
|
2040
|
+
}
|
|
2041
|
+
/**
|
|
2042
|
+
* Sets the specified styles on the given element. Passing `null` for a style removes it.
|
|
2043
|
+
*/
|
|
2044
|
+
function setStyles(element, styles) {
|
|
2045
|
+
Object.entries(styles).forEach(([name, value]) => {
|
|
2046
|
+
if (value === null) {
|
|
2047
|
+
element.style.removeProperty(name);
|
|
2048
|
+
}
|
|
2049
|
+
else {
|
|
2050
|
+
element.style.setProperty(name, value);
|
|
2051
|
+
}
|
|
2052
|
+
});
|
|
2053
|
+
}
|
|
2054
|
+
/**
|
|
2055
|
+
* Coerces passed filter into a {@link SciMenuFilterConfig}, or `undefined` if a menu without filtering.
|
|
2056
|
+
*/
|
|
2057
|
+
function coerceFilterConfig(filter) {
|
|
2058
|
+
if (typeof filter === 'object') {
|
|
2059
|
+
return {
|
|
2060
|
+
placeholder: coerceSignal(filter.placeholder),
|
|
2061
|
+
notFoundMessage: coerceSignal(filter.notFoundMessage),
|
|
2062
|
+
focus: filter.focus,
|
|
2063
|
+
};
|
|
2064
|
+
}
|
|
2065
|
+
return filter === true ? {} : undefined;
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
/*
|
|
2069
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
2070
|
+
*
|
|
2071
|
+
* This program and the accompanying materials are made
|
|
2072
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
2073
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
2074
|
+
*
|
|
2075
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
2076
|
+
*/
|
|
2077
|
+
/**
|
|
2078
|
+
* Translation note: Texts are translated when added (not lazily when rendered) to simplify filtering.
|
|
2079
|
+
*/
|
|
2080
|
+
class ɵSciMenubarFactory {
|
|
2081
|
+
menuItems = [];
|
|
2082
|
+
/** @inheritDoc */
|
|
2083
|
+
addMenu(descriptor, menuFactoryFn) {
|
|
2084
|
+
this.menuItems.push(prune({
|
|
2085
|
+
type: 'menu-item',
|
|
2086
|
+
name: descriptor.name,
|
|
2087
|
+
labelText: translate(descriptor.label),
|
|
2088
|
+
visible: coerceSignal(descriptor.visible),
|
|
2089
|
+
cssClass: Arrays.coerce(descriptor.cssClass),
|
|
2090
|
+
attributes: descriptor.attributes,
|
|
2091
|
+
menu: constructMenu(menuFactoryFn, descriptor.menu),
|
|
2092
|
+
}));
|
|
2093
|
+
return this;
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
/*
|
|
2098
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
2099
|
+
*
|
|
2100
|
+
* This program and the accompanying materials are made
|
|
2101
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
2102
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
2103
|
+
*
|
|
2104
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
2105
|
+
*/
|
|
2106
|
+
class ɵSciMenuRegistry {
|
|
2107
|
+
_contributions = new Map();
|
|
2108
|
+
_menuItemsCache = new MenuItemsCache();
|
|
2109
|
+
_contributionInstantProvider = inject(SciMenuContributionInstantProvider);
|
|
2110
|
+
_menuOpener = inject(SciMenuOpener);
|
|
2111
|
+
_menuPopovers = inject(POPOVER_REFS);
|
|
2112
|
+
_injector = inject(Injector);
|
|
2113
|
+
/** @inheritDoc */
|
|
2114
|
+
contributeMenu(locationLike, factoryFn, options) {
|
|
2115
|
+
const { location, before, after, position } = locationLike;
|
|
2116
|
+
const contribution = {
|
|
2117
|
+
type: parseMenuLocationType(location),
|
|
2118
|
+
factoryFn: factoryFn,
|
|
2119
|
+
position: prune({ before, after, position }, { pruneIfEmpty: true }),
|
|
2120
|
+
requiredContext: prune(options.requiredContext) ?? new Map(), // prune cleared context values
|
|
2121
|
+
contributionInstant: options.contributionInstant ?? this._contributionInstantProvider.next(),
|
|
2122
|
+
metadata: options.metadata ?? {},
|
|
2123
|
+
};
|
|
2124
|
+
if (this._contributions.has(location)) {
|
|
2125
|
+
this._contributions.get(location).update(contributions => contributions.concat(contribution));
|
|
2126
|
+
}
|
|
2127
|
+
else {
|
|
2128
|
+
this._contributions.set(location, signal([contribution]));
|
|
2129
|
+
}
|
|
2130
|
+
return {
|
|
2131
|
+
dispose: () => {
|
|
2132
|
+
// Retain the signal in the contribution map even if empty. Consumers may hold a reference and expect updates
|
|
2133
|
+
// when a new contribution is registered for this location.
|
|
2134
|
+
this._contributions.get(location).update(contributions => contributions.filter(it => it !== contribution));
|
|
2135
|
+
this._menuItemsCache.remove(contribution);
|
|
2136
|
+
},
|
|
2137
|
+
};
|
|
2138
|
+
}
|
|
2139
|
+
/** @inheritDoc */
|
|
2140
|
+
menuContributions(location, context, options) {
|
|
2141
|
+
assertNotInReactiveContext(this.menuContributions, 'Call menuContributions() in a non-reactive (non-tracking) context, such as within the untracked() function.');
|
|
2142
|
+
return computed(() => {
|
|
2143
|
+
// Ensure signal is registered in the contribution map to get notified when a contribution is added to this location.
|
|
2144
|
+
if (!this._contributions.has(location())) {
|
|
2145
|
+
this._contributions.set(location(), signal([]));
|
|
2146
|
+
}
|
|
2147
|
+
const contributions = this._contributions.get(location())();
|
|
2148
|
+
const callingContext = prune(context()); // prune cleared context values
|
|
2149
|
+
return untracked(() => contributions
|
|
2150
|
+
// Filter contributions not matching the calling context.
|
|
2151
|
+
.filter(contribution => {
|
|
2152
|
+
const requiredContext = contribution.requiredContext;
|
|
2153
|
+
for (const [requiredContextName, requiredContextValue] of requiredContext.entries()) {
|
|
2154
|
+
// Do not match contribution if required entry is missing.
|
|
2155
|
+
if (requiredContextValue === '*' && !callingContext.has(requiredContextName)) {
|
|
2156
|
+
return false;
|
|
2157
|
+
}
|
|
2158
|
+
// Do not match contribution if required value is different.
|
|
2159
|
+
if (requiredContextValue !== '*' && !Objects.isEqual(callingContext.get(requiredContextName), requiredContextValue, { ignoreArrayOrder: true })) {
|
|
2160
|
+
return false;
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
return true;
|
|
2164
|
+
})
|
|
2165
|
+
// Sort by contribution instant to maintain a stable order when contributions are replaced.
|
|
2166
|
+
.sort((a, b) => a.contributionInstant - b.contributionInstant));
|
|
2167
|
+
}, { equal: (a, b) => Objects.isEqual(a, b) });
|
|
2168
|
+
}
|
|
2169
|
+
/** @inheritDoc */
|
|
2170
|
+
menuItems(location, context, options) {
|
|
2171
|
+
assertNotInReactiveContext(this.menuItems, 'Call menuItems() in a non-reactive (non-tracking) context, such as within the untracked() function.');
|
|
2172
|
+
if (!options.injector) {
|
|
2173
|
+
assertInInjectionContext(this.menuItems, 'Call menuItems() in an injection context. It may allocate resources that are not released until the injection context is destroyed.');
|
|
2174
|
+
}
|
|
2175
|
+
const callingContextInjector = options.injector ?? inject(Injector);
|
|
2176
|
+
const menuService = this.menuService;
|
|
2177
|
+
const menuItemsCache = this._menuItemsCache;
|
|
2178
|
+
// Get contributions matching the location.
|
|
2179
|
+
const menuContributions = menuService.menuContributions(location, context, options); // Do not invoke method directly to go through the adapter chain.
|
|
2180
|
+
// Get menu items of contributions.
|
|
2181
|
+
return computed(() => {
|
|
2182
|
+
if (!menuContributions().length) {
|
|
2183
|
+
return [];
|
|
2184
|
+
}
|
|
2185
|
+
const callingContext = prune(context()); // prune cleared context values
|
|
2186
|
+
const menuItems = menuContributions()
|
|
2187
|
+
// Instantiate contribution, recursively (submenus and menus in action toolbars).
|
|
2188
|
+
.flatMap(menuContribution => untracked(() => computeMenuItems(menuContribution, callingContext))())
|
|
2189
|
+
// Remove invisible menu items, recursively.
|
|
2190
|
+
.flatMap(filterInvisibleMenuItems)
|
|
2191
|
+
// Federate menu items, recursively.
|
|
2192
|
+
.map(menuItem => federateMenuItems(menuItem, callingContext, { metadata: options.metadata }))
|
|
2193
|
+
// Remove empty groups and submenus, recursively. Filtering must be after federation to not remove named placeholder groups or submenus.
|
|
2194
|
+
.flatMap(filterEmptyMenuItems);
|
|
2195
|
+
return sortMenuItems(menuItems);
|
|
2196
|
+
});
|
|
2197
|
+
/**
|
|
2198
|
+
* Gets the menu items of the given contribution used in the given context.
|
|
2199
|
+
*/
|
|
2200
|
+
function computeMenuItems(menuContribution, context) {
|
|
2201
|
+
assertNotInReactiveContext(computeMenuItems, 'Call computeMenuItems() in a non-reactive (non-tracking) context, such as within the untracked() function.');
|
|
2202
|
+
// Instantiate the contribution if absent, recursively (submenus and menus in action toolbars).
|
|
2203
|
+
return menuItemsCache.computeIfAbsent(menuContribution, context, () => {
|
|
2204
|
+
// Note: Function invoked in root injection context, which is destroyed when the last consumer (location) is disposed.
|
|
2205
|
+
// Inject dependency injection providers for the given menu context.
|
|
2206
|
+
const menuEnvironmentInjectionContextProviders = injectMenuInjectionContextProviders(context, { injector: callingContextInjector });
|
|
2207
|
+
const menuEnvironmentInjectionContext = createDestroyableInjector({ parent: inject(Injector), providers: menuEnvironmentInjectionContextProviders });
|
|
2208
|
+
return computed(() => {
|
|
2209
|
+
const menuItems = (() => {
|
|
2210
|
+
switch (menuContribution.type) {
|
|
2211
|
+
case 'menu': {
|
|
2212
|
+
const menuFactory = new ɵSciMenuFactory();
|
|
2213
|
+
const menuFactoryFn = menuContribution.factoryFn;
|
|
2214
|
+
runInInjectionContext(menuEnvironmentInjectionContext, () => menuFactoryFn(menuFactory, context));
|
|
2215
|
+
return menuFactory.menuItems;
|
|
2216
|
+
}
|
|
2217
|
+
case 'toolbar': {
|
|
2218
|
+
const toolbarFactory = new ɵSciToolbarFactory();
|
|
2219
|
+
const toolbarFactoryFn = menuContribution.factoryFn;
|
|
2220
|
+
runInInjectionContext(menuEnvironmentInjectionContext, () => toolbarFactoryFn(toolbarFactory, context));
|
|
2221
|
+
return toolbarFactory.menuItems;
|
|
2222
|
+
}
|
|
2223
|
+
case 'menubar': {
|
|
2224
|
+
const menubarFactory = new ɵSciMenubarFactory();
|
|
2225
|
+
const menubarFactoryFn = menuContribution.factoryFn;
|
|
2226
|
+
runInInjectionContext(menuEnvironmentInjectionContext, () => menubarFactoryFn(menubarFactory, context));
|
|
2227
|
+
return menubarFactory.menuItems;
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
})();
|
|
2231
|
+
return menuItems
|
|
2232
|
+
// Add contribution's position to top-level menu items.
|
|
2233
|
+
.map(menuItem => ({ ...menuItem, position: menuContribution.position }))
|
|
2234
|
+
// Add providers to components (label, icon, control), recursively.
|
|
2235
|
+
.map(menuItem => provideProviders(menuItem, menuEnvironmentInjectionContextProviders));
|
|
2236
|
+
});
|
|
2237
|
+
}, { injector: callingContextInjector });
|
|
2238
|
+
}
|
|
2239
|
+
/**
|
|
2240
|
+
* Federates passed menu or group with contributions based on its name. Federation is recursive.
|
|
2241
|
+
*/
|
|
2242
|
+
function federateMenuItems(menuItem, context, options) {
|
|
2243
|
+
const federatedItem = { ...menuItem };
|
|
2244
|
+
// Federate actions.
|
|
2245
|
+
if (federatedItem.actions?.length) {
|
|
2246
|
+
federatedItem.actions = sortMenuItems(federatedItem.actions.map(action => federateMenuItems(action, context, options)));
|
|
2247
|
+
}
|
|
2248
|
+
// Federate group.
|
|
2249
|
+
if (federatedItem.type === 'group') {
|
|
2250
|
+
const contributions = federatedItem.name ? untracked(() => menuService.menuItems(federatedItem.name, context, { injector: callingContextInjector, metadata: options?.metadata }))() : [];
|
|
2251
|
+
federatedItem.children = sortMenuItems([
|
|
2252
|
+
...federatedItem.children.map(child => federateMenuItems(child, context, options)),
|
|
2253
|
+
...contributions,
|
|
2254
|
+
]);
|
|
2255
|
+
}
|
|
2256
|
+
// Federate submenu.
|
|
2257
|
+
if (federatedItem.type === 'menu-item' && federatedItem.menu) {
|
|
2258
|
+
const contributions = federatedItem.menu.name ? untracked(() => menuService.menuItems(federatedItem.menu.name, context, { injector: callingContextInjector, metadata: options?.metadata }))() : [];
|
|
2259
|
+
federatedItem.menu = {
|
|
2260
|
+
...federatedItem.menu,
|
|
2261
|
+
children: sortMenuItems([
|
|
2262
|
+
...federatedItem.menu.children.map(child => federateMenuItems(child, context, options)),
|
|
2263
|
+
...contributions,
|
|
2264
|
+
]),
|
|
2265
|
+
};
|
|
2266
|
+
}
|
|
2267
|
+
return federatedItem;
|
|
2268
|
+
}
|
|
2269
|
+
/**
|
|
2270
|
+
* Registers given providers in components (label, icon, control) of the menu item, recursively.
|
|
2271
|
+
*/
|
|
2272
|
+
function provideProviders(menuItem, providers) {
|
|
2273
|
+
switch (menuItem.type) {
|
|
2274
|
+
case 'menu-item': {
|
|
2275
|
+
return {
|
|
2276
|
+
...menuItem,
|
|
2277
|
+
iconComponent: menuItem.iconComponent && {
|
|
2278
|
+
...menuItem.iconComponent,
|
|
2279
|
+
providers: providers.concat(menuItem.iconComponent.providers ?? []),
|
|
2280
|
+
},
|
|
2281
|
+
labelComponent: menuItem.labelComponent && {
|
|
2282
|
+
...menuItem.labelComponent,
|
|
2283
|
+
providers: providers.concat(menuItem.labelComponent.providers ?? []),
|
|
2284
|
+
},
|
|
2285
|
+
control: menuItem.control && {
|
|
2286
|
+
...menuItem.control,
|
|
2287
|
+
providers: providers.concat(menuItem.control.providers ?? []),
|
|
2288
|
+
},
|
|
2289
|
+
actions: menuItem.actions?.map(menuItem => provideProviders(menuItem, providers)),
|
|
2290
|
+
menu: menuItem.menu && {
|
|
2291
|
+
...menuItem.menu,
|
|
2292
|
+
children: menuItem.menu.children.map(menuItem => provideProviders(menuItem, providers)),
|
|
2293
|
+
},
|
|
2294
|
+
};
|
|
2295
|
+
}
|
|
2296
|
+
case 'group': {
|
|
2297
|
+
return {
|
|
2298
|
+
...menuItem,
|
|
2299
|
+
children: menuItem.children.map(menuItem => provideProviders(menuItem, providers)),
|
|
2300
|
+
actions: menuItem.actions?.map(menuItem => provideProviders(menuItem, providers)),
|
|
2301
|
+
};
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
}
|
|
2306
|
+
/** @inheritDoc */
|
|
2307
|
+
openMenu(menu, options) {
|
|
2308
|
+
return this._menuOpener.openMenu(menu, options);
|
|
2309
|
+
}
|
|
2310
|
+
/** @inheritDoc */
|
|
2311
|
+
closeMenus() {
|
|
2312
|
+
this._menuPopovers().forEach(popover => popover.close());
|
|
2313
|
+
}
|
|
2314
|
+
/** @inheritDoc */
|
|
2315
|
+
accelerators(context) {
|
|
2316
|
+
return this._menuItemsCache.accelerators(context);
|
|
2317
|
+
}
|
|
2318
|
+
/**
|
|
2319
|
+
* Reference to the menu service.
|
|
2320
|
+
*
|
|
2321
|
+
* Use to call methods like {@link ɵSciMenuService.menuContributions} or {@link ɵSciMenuService.menuItems} through the adapter chain.
|
|
2322
|
+
*/
|
|
2323
|
+
get menuService() {
|
|
2324
|
+
return this._injector.get(ɵSciMenuService);
|
|
2325
|
+
}
|
|
2326
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ɵSciMenuRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2327
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ɵSciMenuRegistry, providedIn: 'root' });
|
|
2328
|
+
}
|
|
2329
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ɵSciMenuRegistry, decorators: [{
|
|
2330
|
+
type: Injectable,
|
|
2331
|
+
args: [{ providedIn: 'root' }]
|
|
2332
|
+
}] });
|
|
2333
|
+
/**
|
|
2334
|
+
* Recursively removes groups and submenus that contain no menu items.
|
|
2335
|
+
*
|
|
2336
|
+
* Returns a single-entry array containing the filtered menu item, or an empty array if it has no children, enabling usage in `flatMap`.
|
|
2337
|
+
*/
|
|
2338
|
+
function filterEmptyMenuItems(menuItem) {
|
|
2339
|
+
switch (menuItem.type) {
|
|
2340
|
+
case 'menu-item': {
|
|
2341
|
+
const children = menuItem.menu?.children.flatMap(filterEmptyMenuItems) ?? [];
|
|
2342
|
+
const actions = menuItem.actions?.flatMap(filterEmptyMenuItems);
|
|
2343
|
+
return children.length || menuItem.onSelect || menuItem.control ? [{ ...menuItem, actions, menu: menuItem.menu && { ...menuItem.menu, children } }] : [];
|
|
2344
|
+
}
|
|
2345
|
+
case 'group': {
|
|
2346
|
+
const children = menuItem.children.flatMap(filterEmptyMenuItems);
|
|
2347
|
+
const actions = menuItem.actions?.flatMap(filterEmptyMenuItems);
|
|
2348
|
+
return children.length ? [{ ...menuItem, actions, children }] : [];
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
/**
|
|
2353
|
+
* Recursively removes invisible groups and submenus.
|
|
2354
|
+
*
|
|
2355
|
+
* Returns a single-entry array containing the filtered menu item, or an empty array if not visible, enabling usage in `flatMap`.
|
|
2356
|
+
*/
|
|
2357
|
+
function filterInvisibleMenuItems(menuItem) {
|
|
2358
|
+
if (!(menuItem.visible?.() ?? true)) {
|
|
2359
|
+
return [];
|
|
2360
|
+
}
|
|
2361
|
+
switch (menuItem.type) {
|
|
2362
|
+
case 'menu-item': {
|
|
2363
|
+
const children = menuItem.menu?.children.flatMap(filterInvisibleMenuItems) ?? [];
|
|
2364
|
+
const actions = menuItem.actions?.flatMap(filterInvisibleMenuItems);
|
|
2365
|
+
return [{ ...menuItem, actions, menu: menuItem.menu && { ...menuItem.menu, children } }];
|
|
2366
|
+
}
|
|
2367
|
+
case 'group': {
|
|
2368
|
+
const actions = menuItem.actions?.flatMap(filterInvisibleMenuItems);
|
|
2369
|
+
const children = menuItem.children.flatMap(filterInvisibleMenuItems);
|
|
2370
|
+
return [{ ...menuItem, actions, children }];
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
/**
|
|
2375
|
+
* Parses the type of given location. One of `menu`, `toolbar`,`menubar`.
|
|
2376
|
+
*/
|
|
2377
|
+
function parseMenuLocationType(location) {
|
|
2378
|
+
return /^(?<type>(menu|toolbar|menubar)):(?<name>.+)$/.exec(location).groups['type'];
|
|
2379
|
+
}
|
|
2380
|
+
/**
|
|
2381
|
+
* Cache for instantiated contributions in a specific context.
|
|
2382
|
+
*/
|
|
2383
|
+
class MenuItemsCache {
|
|
2384
|
+
_cache = signal(new Set(), /* @ts-ignore */
|
|
2385
|
+
...(ngDevMode ? [{ debugName: "_cache" }] : /* istanbul ignore next */ []));
|
|
2386
|
+
_cacheByContribution = computed(() => {
|
|
2387
|
+
return Array.from(this._cache()).reduce((map, cacheEntry) => Maps.addListValue(map, cacheEntry.contribution, cacheEntry), new Map());
|
|
2388
|
+
}, /* @ts-ignore */
|
|
2389
|
+
...(ngDevMode ? [{ debugName: "_cacheByContribution" }] : /* istanbul ignore next */ []));
|
|
2390
|
+
/**
|
|
2391
|
+
* Gets the menu items of the given contribution used in the given context. Menu items are constructed via the passed factory function if absent (on cache miss).
|
|
2392
|
+
*
|
|
2393
|
+
* Menu items are cached. Cached menu items are evicted when no longer used, tracked via a reference count based on the provided injector;
|
|
2394
|
+
* once all tracked injectors for a cache entry are destroyed, the cache entry is evicted.
|
|
2395
|
+
*
|
|
2396
|
+
* The factory function is invoked in the root injection context. The injection context is destroyed when the last consumer (location) is disposed.
|
|
2397
|
+
*/
|
|
2398
|
+
computeIfAbsent(contribution, context, computeFn, options) {
|
|
2399
|
+
assertNotInReactiveContext(this.computeIfAbsent, 'Call computeIfAbsent() in a non-reactive (non-tracking) context, such as within the untracked() function.');
|
|
2400
|
+
const cacheEntry = this._cacheByContribution().get(contribution)?.find(cacheEntry => Objects.isEqual(cacheEntry.context, context)) ?? (() => {
|
|
2401
|
+
const rootInjector = options.injector.get(ApplicationRef).injector;
|
|
2402
|
+
const cacheEntryInjector = createDestroyableInjector({ parent: rootInjector });
|
|
2403
|
+
const cacheEntry = {
|
|
2404
|
+
injector: cacheEntryInjector,
|
|
2405
|
+
contribution,
|
|
2406
|
+
menuItems: runInInjectionContext(cacheEntryInjector, computeFn),
|
|
2407
|
+
context,
|
|
2408
|
+
refCount: 0,
|
|
2409
|
+
dispose: () => cacheEntryInjector.destroy(),
|
|
2410
|
+
};
|
|
2411
|
+
this._cache.update(cache => new Set(cache).add(cacheEntry));
|
|
2412
|
+
return cacheEntry;
|
|
2413
|
+
})();
|
|
2414
|
+
cacheEntry.refCount++;
|
|
2415
|
+
// Decrement reference count when tracked injector is destroyed.
|
|
2416
|
+
options.injector.get(DestroyRef).onDestroy(() => {
|
|
2417
|
+
if (--cacheEntry.refCount === 0) {
|
|
2418
|
+
// Dispose when no longer used.
|
|
2419
|
+
cacheEntry.dispose();
|
|
2420
|
+
this._cache.update(cache => {
|
|
2421
|
+
const cacheCopy = new Set(cache);
|
|
2422
|
+
cacheCopy.delete(cacheEntry);
|
|
2423
|
+
return cacheCopy;
|
|
2424
|
+
});
|
|
2425
|
+
}
|
|
2426
|
+
});
|
|
2427
|
+
return cacheEntry.menuItems;
|
|
2428
|
+
}
|
|
2429
|
+
/**
|
|
2430
|
+
* Removes menu items of the given contribution from the cache.
|
|
2431
|
+
*/
|
|
2432
|
+
remove(contribution) {
|
|
2433
|
+
assertNotInReactiveContext(this.remove, 'Call remove() in a non-reactive (non-tracking) context, such as within the untracked() function.');
|
|
2434
|
+
const cacheEntriesToRemove = this._cacheByContribution().get(contribution) ?? [];
|
|
2435
|
+
this._cache.update(cache => {
|
|
2436
|
+
const cacheCopy = new Set(cache);
|
|
2437
|
+
cacheEntriesToRemove.forEach(cacheEntry => {
|
|
2438
|
+
cacheEntry.dispose();
|
|
2439
|
+
cacheCopy.delete(cacheEntry);
|
|
2440
|
+
});
|
|
2441
|
+
return cacheCopy;
|
|
2442
|
+
});
|
|
2443
|
+
}
|
|
2444
|
+
/**
|
|
2445
|
+
* Gets installed keyboard accelerators for menu items that match the given context.
|
|
2446
|
+
*
|
|
2447
|
+
* A match does not require an identical context. All common context keys must have identical context values.
|
|
2448
|
+
*/
|
|
2449
|
+
accelerators(context) {
|
|
2450
|
+
return computed(() => {
|
|
2451
|
+
const accelerators = Array.from(this._cache().values())
|
|
2452
|
+
.filter(cacheEntry => {
|
|
2453
|
+
for (const [name, value] of prune(context().entries())) { // prune cleared context values
|
|
2454
|
+
// Do not match contribution if required entry is missing.
|
|
2455
|
+
if (value === '*' && !cacheEntry.context.has(name)) {
|
|
2456
|
+
return false;
|
|
2457
|
+
}
|
|
2458
|
+
// Do not match if context key maps to a different value.
|
|
2459
|
+
if (value !== '*' && cacheEntry.context.has(name) && !Objects.isEqual(cacheEntry.context.get(name), value, { ignoreArrayOrder: true })) {
|
|
2460
|
+
return false;
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
return true;
|
|
2464
|
+
})
|
|
2465
|
+
.flatMap(cacheEntry => collectMenuItemsWithAccelerator(cacheEntry.menuItems()))
|
|
2466
|
+
.filter(menuItem => !menuItem.disabled?.() && (menuItem.visible?.() ?? true))
|
|
2467
|
+
.map(menuItem => menuItem.accelerator);
|
|
2468
|
+
// Remove duplicate accelerators.
|
|
2469
|
+
return Arrays.distinct(accelerators, accelerator => `${accelerator.shift}/${accelerator.ctrl}/${accelerator.alt}/${accelerator.key.toLowerCase()}/${accelerator.location}`);
|
|
2470
|
+
}, { equal: (a, b) => Objects.isEqual(a, b, { ignoreArrayOrder: true }) });
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
|
|
2474
|
+
// TODO [menu] documentation
|
|
2475
|
+
class SciMenuRegistry {
|
|
2476
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciMenuRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2477
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciMenuRegistry, providedIn: 'root', useExisting: ɵSciMenuRegistry });
|
|
2478
|
+
}
|
|
2479
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciMenuRegistry, decorators: [{
|
|
2480
|
+
type: Injectable,
|
|
2481
|
+
args: [{ providedIn: 'root', useExisting: ɵSciMenuRegistry }]
|
|
2482
|
+
}] });
|
|
2483
|
+
|
|
2484
|
+
/*
|
|
2485
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
2486
|
+
*
|
|
2487
|
+
* This program and the accompanying materials are made
|
|
2488
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
2489
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
2490
|
+
*
|
|
2491
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
2492
|
+
*/
|
|
2493
|
+
/**
|
|
2494
|
+
* Enables integrators to intercept the registration and retrieval of menu contributions and to control where menu popovers open.
|
|
2495
|
+
*
|
|
2496
|
+
* Adapters can handle or augment calls. Multiple adapters form a chain and are called one by one in registration order.
|
|
2497
|
+
*
|
|
2498
|
+
* Calling 'next' passes the call to the next adapter, or to the registry if it is the last adapter in the chain.
|
|
2499
|
+
*
|
|
2500
|
+
* TODO [menu] documentation
|
|
2501
|
+
*
|
|
2502
|
+
* @experimental since 22.3.0; API and behavior may change in any version without notice.
|
|
2503
|
+
*/
|
|
2504
|
+
class SciMenuAdapter {
|
|
2505
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciMenuAdapter, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2506
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciMenuAdapter });
|
|
2507
|
+
}
|
|
2508
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciMenuAdapter, decorators: [{
|
|
2509
|
+
type: Injectable
|
|
2510
|
+
}] });
|
|
2511
|
+
|
|
2512
|
+
/**
|
|
2513
|
+
* // TODO [menu] documentation
|
|
2514
|
+
*
|
|
2515
|
+
* @docs-private Not public API. Used by frameworks integrating the SCION Menu API. Applications should not use this service.
|
|
2516
|
+
*/
|
|
2517
|
+
class ɵSciMenuService {
|
|
2518
|
+
_menuRegistry = interceptMenuRegistry(inject(SciMenuRegistry));
|
|
2519
|
+
_environmentContext = injectMenuContext();
|
|
2520
|
+
/** @inheritDoc */
|
|
2521
|
+
open(menu, options) {
|
|
2522
|
+
const context = new Map([...this._environmentContext(), ...options.context ?? new Map()]);
|
|
2523
|
+
// Prevent user agent defaults when opening context menu.
|
|
2524
|
+
if (options.anchor instanceof MouseEvent && options.anchor.type === 'contextmenu') {
|
|
2525
|
+
options.anchor.preventDefault();
|
|
2526
|
+
options.anchor.stopPropagation();
|
|
2527
|
+
}
|
|
2528
|
+
return this._menuRegistry.openMenu(menu, { ...options, context });
|
|
2529
|
+
}
|
|
2530
|
+
/** @inheritDoc */
|
|
2531
|
+
closeAll() {
|
|
2532
|
+
this._menuRegistry.closeMenus();
|
|
2533
|
+
}
|
|
2534
|
+
/**
|
|
2535
|
+
* The function:
|
|
2536
|
+
* - Must be called within an injection context, or an explicit {@link Injector} passed.
|
|
2537
|
+
* - Must be called in a non-reactive (non-tracking) context.
|
|
2538
|
+
*
|
|
2539
|
+
* @docs-private Not public API. Used by frameworks integrating the SCION Menu API. Applications should not use this method.
|
|
2540
|
+
*/
|
|
2541
|
+
menuItems(location, context, options) {
|
|
2542
|
+
return this._menuRegistry.menuItems(coerceSignal(location), coerceSignal(context), options ?? {});
|
|
2543
|
+
}
|
|
2544
|
+
/** @docs-private Not public API. Used by frameworks integrating the SCION Menu API. Applications should not use this method. */
|
|
2545
|
+
menuContributions(location, context, options) {
|
|
2546
|
+
return this._menuRegistry.menuContributions(coerceSignal(location), coerceSignal(context), options ?? {});
|
|
2547
|
+
}
|
|
2548
|
+
/** @docs-private Not public API. Used by frameworks integrating the SCION Menu API. Applications should not use this method. */
|
|
2549
|
+
contributeMenu(location, factoryFn, options) {
|
|
2550
|
+
return this._menuRegistry.contributeMenu(location, factoryFn, options ?? {});
|
|
2551
|
+
}
|
|
2552
|
+
/**
|
|
2553
|
+
* Gets installed keyboard accelerators for menu items that match the given context.
|
|
2554
|
+
*
|
|
2555
|
+
* A match does not require an identical context. All common context keys must have identical context values.
|
|
2556
|
+
*
|
|
2557
|
+
* Inherits the context from the current injector hierarchy, but can be overridden via `context` options. See {@link provideMenuContextProvider}.
|
|
2558
|
+
*
|
|
2559
|
+
* @docs-private Not public API. Used by frameworks integrating the SCION Menu API. Applications should not use this method.
|
|
2560
|
+
*/
|
|
2561
|
+
accelerators(options) {
|
|
2562
|
+
const context = computed(() => new Map([...this._environmentContext(), ...coerceSignal(options?.context)?.() ?? new Map()]), /* @ts-ignore */
|
|
2563
|
+
...(ngDevMode ? [{ debugName: "context" }] : /* istanbul ignore next */ []));
|
|
2564
|
+
return this._menuRegistry.accelerators(context);
|
|
2565
|
+
}
|
|
2566
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ɵSciMenuService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2567
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ɵSciMenuService, providedIn: 'root' });
|
|
2568
|
+
}
|
|
2569
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ɵSciMenuService, decorators: [{
|
|
2570
|
+
type: Injectable,
|
|
2571
|
+
args: [{ providedIn: 'root' }]
|
|
2572
|
+
}] });
|
|
2573
|
+
/**
|
|
2574
|
+
* Intercepts calls to the menu registry by chaining registered menu adapters.
|
|
2575
|
+
*
|
|
2576
|
+
* Each adapter can handle the call, modify it, or pass it to the next.
|
|
2577
|
+
*/
|
|
2578
|
+
function interceptMenuRegistry(menuRegistry) {
|
|
2579
|
+
// TODO [Angular 22] Remove cast when Angular supports type safety for multi-injection with abstract class DI tokens. See https://github.com/angular/angular/issues/55555
|
|
2580
|
+
const menuAdapters = inject(SciMenuAdapter, { optional: true }) ?? [];
|
|
2581
|
+
return menuAdapters.reduceRight((next, adapter) => ({
|
|
2582
|
+
contributeMenu: (location, factoryFn, options) => adapter.contributeMenu ? adapter.contributeMenu(location, factoryFn, options, next) : next.contributeMenu(location, factoryFn, options),
|
|
2583
|
+
menuContributions: (location, context, options) => adapter.menuContributions ? adapter.menuContributions(location, context, options, next) : next.menuContributions(location, context, options),
|
|
2584
|
+
menuItems: (location, context, options) => adapter.menuItems ? adapter.menuItems(location, context, options, next) : next.menuItems(location, context, options),
|
|
2585
|
+
openMenu: (menu, options) => adapter.openMenu ? adapter.openMenu(menu, options, next) : next.openMenu(menu, options),
|
|
2586
|
+
closeMenus: () => adapter.closeMenus ? adapter.closeMenus(next) : next.closeMenus(),
|
|
2587
|
+
accelerators: context => adapter.accelerators ? adapter.accelerators(context, next) : next.accelerators(context),
|
|
2588
|
+
}), menuRegistry);
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2591
|
+
/*
|
|
2592
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
2593
|
+
*
|
|
2594
|
+
* This program and the accompanying materials are made
|
|
2595
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
2596
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
2597
|
+
*
|
|
2598
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
2599
|
+
*/
|
|
2600
|
+
/**
|
|
2601
|
+
* Opens a menu with items contributed via {@link contributeMenu}.
|
|
2602
|
+
*
|
|
2603
|
+
* A menu displays a list of related items in a popover, organized into groups and submenus.
|
|
2604
|
+
* It closes when a menu item is selected or on an outside click. Menu items can define a keyboard accelerator for quick access.
|
|
2605
|
+
*
|
|
2606
|
+
* ## Menu Item Size
|
|
2607
|
+
* The menu item size is based on the `--sci-menu-item-size` CSS variable and defaults to `16px`. It determines the icon size and is used to compute the font size and padding.
|
|
2608
|
+
*
|
|
2609
|
+
* A custom size can be defined globally using the `:root` selector:
|
|
2610
|
+
*
|
|
2611
|
+
* ```css
|
|
2612
|
+
* sci-menu {
|
|
2613
|
+
* --sci-menu-item-size: 18px;
|
|
2614
|
+
* }
|
|
2615
|
+
* ```
|
|
2616
|
+
*
|
|
2617
|
+
* Instead of computing the font size based on `--sci-menu-item-size`, an explicit font size can be defined using the `--sci-menu-font-size` CSS variable:
|
|
2618
|
+
*
|
|
2619
|
+
* ```css
|
|
2620
|
+
* sci-menu {
|
|
2621
|
+
* --sci-menu-item-size: 14px;
|
|
2622
|
+
* }
|
|
2623
|
+
* ```
|
|
2624
|
+
*
|
|
2625
|
+
* ## Custom Styling
|
|
2626
|
+
* The appearance of the menu can be customized using the following CSS variables:
|
|
2627
|
+
*
|
|
2628
|
+
* - `--sci-menu-font-size`: Font size of menu items.
|
|
2629
|
+
* - `--sci-menu-border-radius`: Border radius of a menu popover.
|
|
2630
|
+
* - `--sci-menu-min-width`: Minimum width of a menu popover.
|
|
2631
|
+
* - `--sci-menu-submenu-min-width`: Minimum width of a submenu popover.
|
|
2632
|
+
* - `--sci-menu-item-size`: Size of menu items; used as the menu item icon size and to compute font size and padding.
|
|
2633
|
+
* - `--sci-menu-item-cursor`: Cursor style when hovering over a menu item.
|
|
2634
|
+
* - `--sci-menu-item-text-color`: Text and icon color of menu items.
|
|
2635
|
+
* - `--sci-menu-item-text-color-disabled`: Text and icon color of disabled menu items.
|
|
2636
|
+
* - `--sci-menu-item-background-color-hover`: Background color of a menu item when hovered.
|
|
2637
|
+
* - `--sci-menu-item-background-color-active`: Background color of a menu item when pressed.
|
|
2638
|
+
* - `--sci-menu-item-border-radius`: Border radius of menu items.
|
|
2639
|
+
* - `--sci-menu-item-outline-width`: Outline width of menu items when focused.
|
|
2640
|
+
* - `--sci-menu-item-accelerator-text-color`: Text color of menu item accelerators.
|
|
2641
|
+
* - `--sci-menu-item-active-indicator-size`: Size of the visual indicator for active menu items.
|
|
2642
|
+
* - `--sci-menu-item-active-indicator-background-color`: Background color of the visual indicator for active menu items.
|
|
2643
|
+
* - `--sci-menu-item-active-indicator-border-radius`: Border radius of the visual indicator for active menu items.
|
|
2644
|
+
* - `--sci-menu-group-header-font-family`: Font family for group headers.
|
|
2645
|
+
* - `--sci-menu-group-header-font-size`: Font size for group headers.
|
|
2646
|
+
* - `--sci-menu-group-header-font-weight`: Font weight for group headers.
|
|
2647
|
+
* - `--sci-menu-group-header-text-color`: Text color for group headers.
|
|
2648
|
+
* - `--sci-menu-filter-outline-width`: Outline width of the filter field.
|
|
2649
|
+
* - `--sci-menu-filter-outline-radius`: Outline radius of the filter field.
|
|
2650
|
+
*
|
|
2651
|
+
* @experimental since 22.3.0; API and behavior may change in any version without notice.
|
|
2652
|
+
*/
|
|
2653
|
+
class SciMenuService {
|
|
2654
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciMenuService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2655
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciMenuService, providedIn: 'root', useExisting: ɵSciMenuService });
|
|
2656
|
+
}
|
|
2657
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciMenuService, decorators: [{
|
|
2658
|
+
type: Injectable,
|
|
2659
|
+
args: [{ providedIn: 'root', useExisting: ɵSciMenuService }]
|
|
2660
|
+
}] });
|
|
2661
|
+
|
|
2662
|
+
/*
|
|
2663
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
2664
|
+
*
|
|
2665
|
+
* This program and the accompanying materials are made
|
|
2666
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
2667
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
2668
|
+
*
|
|
2669
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
2670
|
+
*/
|
|
2671
|
+
/** @internal */
|
|
2672
|
+
function contributeMenu(locationLike, factoryFn, options) {
|
|
2673
|
+
assertNotInReactiveContext(contributeMenu, 'Call contributeMenu in a non-reactive (non-tracking) context, such as within the untracked() function.');
|
|
2674
|
+
if (!options?.injector) {
|
|
2675
|
+
assertInInjectionContext(contributeMenu, 'Must be called within an injection context, or an explicit `Injector` passed via options. Disposes the contribution when the injection context is destroyed.');
|
|
2676
|
+
}
|
|
2677
|
+
const injector = createDestroyableInjector({ parent: options?.injector ?? inject(Injector) });
|
|
2678
|
+
const location = typeof locationLike === 'string' ? { location: locationLike } : locationLike;
|
|
2679
|
+
const menuService = injector.get(ɵSciMenuService);
|
|
2680
|
+
const environmentContext = injectMenuContext({ injector });
|
|
2681
|
+
const contributionContext = computed(() => new Map([...environmentContext(), ...options?.requiredContext ?? new Map()]), /* @ts-ignore */
|
|
2682
|
+
...(ngDevMode ? [{ debugName: "contributionContext" }] : /* istanbul ignore next */ []));
|
|
2683
|
+
// Each contribution is assigned an instant to maintain a stable order when the contribution context changes.
|
|
2684
|
+
const contributionInstant = options?.contributionInstant ?? injector.get(SciMenuContributionInstantProvider).next();
|
|
2685
|
+
effect(onCleanup => {
|
|
2686
|
+
const requiredContext = contributionContext();
|
|
2687
|
+
untracked(() => {
|
|
2688
|
+
const contributionRef = menuService.contributeMenu(location, factoryFn, { ...options, requiredContext, contributionInstant });
|
|
2689
|
+
onCleanup(() => contributionRef.dispose());
|
|
2690
|
+
});
|
|
2691
|
+
}, { injector });
|
|
2692
|
+
return {
|
|
2693
|
+
dispose: () => injector.destroy(),
|
|
2694
|
+
};
|
|
2695
|
+
}
|
|
2696
|
+
|
|
2697
|
+
/*
|
|
2698
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
2699
|
+
*
|
|
2700
|
+
* This program and the accompanying materials are made
|
|
2701
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
2702
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
2703
|
+
*
|
|
2704
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
2705
|
+
*/
|
|
2706
|
+
/**
|
|
2707
|
+
* Displays items contributed via the {@link contributeMenu} function matching the toolbar's name.
|
|
2708
|
+
*
|
|
2709
|
+
* A toolbar is a horizontal or vertical container providing quick access to context-related tools.
|
|
2710
|
+
* It contains buttons, toggles, menus, and other controls, with related items grouped together.
|
|
2711
|
+
*
|
|
2712
|
+
* The toolbar must be assigned a name, used as the location when contributing to the toolbar.
|
|
2713
|
+
* The name must start with the `toolbar:` prefix.
|
|
2714
|
+
*
|
|
2715
|
+
* ```html
|
|
2716
|
+
* <sci-toolbar name="toolbar:main"/>
|
|
2717
|
+
* ```
|
|
2718
|
+
*
|
|
2719
|
+
* Use the {@link contributeMenu} function to contribute to the toolbar by passing the toolbar name and a factory function.
|
|
2720
|
+
* The toolbar calls the factory function with a {@link SciToolbarFactory}, providing methods for populating the toolbar.
|
|
2721
|
+
*
|
|
2722
|
+
* ```ts
|
|
2723
|
+
* import {contributeMenu} from '@scion/components/menu';
|
|
2724
|
+
*
|
|
2725
|
+
* contributeMenu('toolbar:main', toolbar => toolbar
|
|
2726
|
+
* .addToolbarButton({icon: 'undo', accelerator: {ctrl: true, key: 'Z'}, tooltip: 'Undo', onSelect: () => console.log('Undo')})
|
|
2727
|
+
* .addToolbarButton({icon: 'redo', accelerator: {ctrl: true, key: 'Y'}, tooltip: 'Redo', onSelect: () => console.log('Redo')})
|
|
2728
|
+
* .addGroup(group => group
|
|
2729
|
+
* .addToolbarButton({icon: 'content_copy', accelerator: {ctrl: true, key: 'C'}, tooltip: 'Copy', onSelect: () => console.log('Copy')})
|
|
2730
|
+
* .addToolbarButton({icon: 'content_paste', accelerator: {ctrl: true, key: 'V'}, tooltip: 'Paste', onSelect: () => console.log('Paste')}),
|
|
2731
|
+
* )
|
|
2732
|
+
* .addToolbarMenu({icon: 'folder', tooltip: 'File'}, menu => menu
|
|
2733
|
+
* .addMenuItem({icon: 'save', label: 'Save', accelerator: {ctrl: true, key: 'S'}, onSelect: () => console.log('Save')})
|
|
2734
|
+
* .addMenuItem({icon: 'print', label: 'Print', accelerator: {ctrl: true, key: 'P'}, onSelect: () => console.log('Print')})
|
|
2735
|
+
* .addMenu({icon: 'file_download', label: 'Export As...'}, menu => menu
|
|
2736
|
+
* .addMenuItem({icon: 'picture_as_pdf', label: 'PDF Document', onSelect: () => console.log('PDF Document')})
|
|
2737
|
+
* .addMenuItem({icon: 'table_view', label: 'Excel Spreadsheet', onSelect: () => console.log('Excel Spreadsheet')}),
|
|
2738
|
+
* ),
|
|
2739
|
+
* ),
|
|
2740
|
+
* );
|
|
2741
|
+
* ```
|
|
2742
|
+
*
|
|
2743
|
+
* Multiple contributions to the same toolbar can populate it from different places in the application. Passing a {@link SciToolbarContributionLocation}
|
|
2744
|
+
* gives exact control over contribution placement within the toolbar. By default, toolbar items are added in contribution order.
|
|
2745
|
+
*
|
|
2746
|
+
* Menus and groups can be named to allow extension from other contributions.
|
|
2747
|
+
*
|
|
2748
|
+
* ## Context
|
|
2749
|
+
* A toolbar can have a context, a key/value map that describes its environment. Contributions can declare a minimal required context and read the toolbar context.
|
|
2750
|
+
*
|
|
2751
|
+
* A context can be set on the toolbar with the `context` input:
|
|
2752
|
+
*
|
|
2753
|
+
* ```html
|
|
2754
|
+
* <sci-toolbar name="toolbar:main" [context]="..."/>
|
|
2755
|
+
* ```
|
|
2756
|
+
*
|
|
2757
|
+
* ## Accelerator Target
|
|
2758
|
+
* Tools can have an accelerator for quick access using a keyboard shortcut. By default, the toolbar uses the {@link Document} as the accelerator target.
|
|
2759
|
+
*
|
|
2760
|
+
* The accelerator target can be changed via the `acceleratorTarget` input:
|
|
2761
|
+
*
|
|
2762
|
+
* ```html
|
|
2763
|
+
* <sci-toolbar name="toolbar:main" [acceleratorTarget]="..."/>
|
|
2764
|
+
* ```
|
|
2765
|
+
*
|
|
2766
|
+
* ## Toolbar Size
|
|
2767
|
+
* The toolbar size is based on the `--sci-toolbar-item-size` CSS variable and defaults to 16px. It determines the icon size and is used to compute the font size and padding.
|
|
2768
|
+
*
|
|
2769
|
+
* A custom size can be defined globally using the `:root` selector or scoped to a specific toolbar.
|
|
2770
|
+
*
|
|
2771
|
+
* ```css
|
|
2772
|
+
* sci-toolbar {
|
|
2773
|
+
* --sci-toolbar-item-size: 18px;
|
|
2774
|
+
* }
|
|
2775
|
+
* ```
|
|
2776
|
+
*
|
|
2777
|
+
* Instead of computing the font size based on `--sci-toolbar-item-size`, an explicit font size can be defined using the `--sci-toolbar-font-size` CSS variable, either globally or at the toolbar level.
|
|
2778
|
+
*
|
|
2779
|
+
* ```css
|
|
2780
|
+
* sci-toolbar {
|
|
2781
|
+
* --sci-toolbar-font-size: 14px;
|
|
2782
|
+
* }
|
|
2783
|
+
* ```
|
|
2784
|
+
*
|
|
2785
|
+
* ## Hiding an Empty Toolbar
|
|
2786
|
+
* If the toolbar has no tools, it can be hidden using the CSS `:empty` pseudo-class:
|
|
2787
|
+
*
|
|
2788
|
+
* ```css
|
|
2789
|
+
* sci-toolbar:empty {
|
|
2790
|
+
* display: none;
|
|
2791
|
+
* }
|
|
2792
|
+
* ```
|
|
2793
|
+
*
|
|
2794
|
+
* ## Custom Styling
|
|
2795
|
+
* The appearance of the toolbar can be customized using the following CSS variables, either globally in the `:root` or on the toolbar level.
|
|
2796
|
+
*
|
|
2797
|
+
* - `--sci-toolbar-font-size`: Font size of toolbar items.
|
|
2798
|
+
* - `--sci-toolbar-item-size`: Size of toolbar items; used as the toolbar item icon size and to compute font size and padding.
|
|
2799
|
+
* - `--sci-toolbar-item-cursor`: Cursor style when hovering over a toolbar item.
|
|
2800
|
+
* - `--sci-toolbar-item-text-color`: Text and icon color of toolbar items.
|
|
2801
|
+
* - `--sci-toolbar-item-text-color-disabled`: Text and icon color of disabled toolbar items.
|
|
2802
|
+
* - `--sci-toolbar-item-background-color`: Background color of toolbar items.
|
|
2803
|
+
* - `--sci-toolbar-item-background-color-hover`: Background color of a toolbar item when hovered.
|
|
2804
|
+
* - `--sci-toolbar-item-background-color-active`: Background color of a toolbar item when pressed.
|
|
2805
|
+
* - `--sci-toolbar-item-background-color-checked`: Background color of toggled toolbar items.
|
|
2806
|
+
* - `--sci-toolbar-item-border-radius`: Border radius of toolbar items.
|
|
2807
|
+
* - `--sci-toolbar-item-border-color-hover`: Border color of toolbar items when hovered.
|
|
2808
|
+
* - `--sci-toolbar-item-border-color-checked`: Border color of toggled toolbar items.
|
|
2809
|
+
* - `--sci-toolbar-item-outline-width`: Outline width of toolbar items when focused.
|
|
2810
|
+
* - `--sci-toolbar-item-menu-indicator-color`: Color of the menu indicator for toolbar items without a label.
|
|
2811
|
+
* - `--sci-toolbar-item-menu-indicator-color-disabled`: Color of the menu indicator if disabled.
|
|
2812
|
+
* - `--sci-toolbar-item-menu-indicator-size`: Size of the menu indicator.
|
|
2813
|
+
*
|
|
2814
|
+
* @experimental since 22.3.0; API and behavior may change in any version without notice.
|
|
2815
|
+
*/
|
|
2816
|
+
class SciToolbarComponent {
|
|
2817
|
+
/**
|
|
2818
|
+
* Defines the name of the toolbar. The name must start with the `toolbar:` prefix.
|
|
2819
|
+
*
|
|
2820
|
+
* Use this name to contribute tools to the toolbar using the {@link contributeMenu} function.
|
|
2821
|
+
*/
|
|
2822
|
+
name = input.required(/* @ts-ignore */
|
|
2823
|
+
...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
|
|
2824
|
+
/**
|
|
2825
|
+
* Specifies the orientation of the toolbar. Defaults to `horizontal`.
|
|
2826
|
+
*/
|
|
2827
|
+
orientation = input('horizontal', /* @ts-ignore */
|
|
2828
|
+
...(ngDevMode ? [{ debugName: "orientation" }] : /* istanbul ignore next */ []));
|
|
2829
|
+
/**
|
|
2830
|
+
* Specifies a context to describe the environment of the toolbar.
|
|
2831
|
+
*
|
|
2832
|
+
* Toolbar contributions may declare a required context. Only contributions matching this context are used.
|
|
2833
|
+
*
|
|
2834
|
+
* A context can also be provided at the injector level using {@link provideMenuContextProvider}, for example, at the component, route, or application level,
|
|
2835
|
+
* and is available to toolbars used in the scope of this injector. The inherited context can be overridden or extended. Setting a context entry to `undefined` clears it.
|
|
2836
|
+
*
|
|
2837
|
+
* ```ts
|
|
2838
|
+
* import {provideMenuContextProvider} from '@scion/components/menu';
|
|
2839
|
+
*
|
|
2840
|
+
* providers: [
|
|
2841
|
+
* provideMenuContextProvider(() => new Map().set('key', 'value')),
|
|
2842
|
+
* ];
|
|
2843
|
+
* ```
|
|
2844
|
+
*
|
|
2845
|
+
* @see provideMenuContextProvider
|
|
2846
|
+
*/
|
|
2847
|
+
context = input(/* @ts-ignore */
|
|
2848
|
+
...(ngDevMode ? [undefined, { debugName: "context" }] : /* istanbul ignore next */ []));
|
|
2849
|
+
/**
|
|
2850
|
+
* Specifies the accelerator targets for the toolbar. Defaults to {@link Document}.
|
|
2851
|
+
*
|
|
2852
|
+
* Alternatively, accelerator targets can be provided at the injector level using {@link provideMenuAcceleratorTargetProvider}, for example, at the component, route, or application level,
|
|
2853
|
+
* and are available to toolbars used in the scope of this injector. Inherited targets can be overridden.
|
|
2854
|
+
*
|
|
2855
|
+
* ```ts
|
|
2856
|
+
* import {provideMenuAcceleratorTargetProvider} from '@scion/components/menu';
|
|
2857
|
+
* import {ElementRef, inject} from '@angular/core';
|
|
2858
|
+
*
|
|
2859
|
+
* providers: [
|
|
2860
|
+
* provideMenuAcceleratorTargetProvider(() => inject(ElementRef)),
|
|
2861
|
+
* ];
|
|
2862
|
+
* ```
|
|
2863
|
+
*/
|
|
2864
|
+
acceleratorTarget = input(/* @ts-ignore */
|
|
2865
|
+
...(ngDevMode ? [undefined, { debugName: "acceleratorTarget" }] : /* istanbul ignore next */ []));
|
|
2866
|
+
/**
|
|
2867
|
+
* Controls where to attach menu popovers in the DOM. By default, popovers are added as a direct sibling of the toolbar.
|
|
2868
|
+
*/
|
|
2869
|
+
popoverViewContainerRef = input(inject(ViewContainerRef), /* @ts-ignore */
|
|
2870
|
+
...(ngDevMode ? [{ debugName: "popoverViewContainerRef" }] : /* istanbul ignore next */ []));
|
|
2871
|
+
/**
|
|
2872
|
+
* Indicates whether any menu within the toolbar is currently open.
|
|
2873
|
+
*
|
|
2874
|
+
* TODO [dwi][API] How to name that metod? Cannot be internal because used by table.
|
|
2875
|
+
*/
|
|
2876
|
+
menuOpen = computed(() => this._virtualToolbarGroupComponent.menuOpen(), /* @ts-ignore */
|
|
2877
|
+
...(ngDevMode ? [{ debugName: "menuOpen" }] : /* istanbul ignore next */ []));
|
|
2878
|
+
_environmentContext = injectMenuContext();
|
|
2879
|
+
_context = computed(() => new Map([...this._environmentContext(), ...this.context() ?? new Map()]), /* @ts-ignore */
|
|
2880
|
+
...(ngDevMode ? [{ debugName: "_context" }] : /* istanbul ignore next */ []));
|
|
2881
|
+
_virtualToolbarGroupComponent;
|
|
2882
|
+
menuItems = inject(ɵSciMenuService).menuItems(this.name, this._context);
|
|
2883
|
+
constructor() {
|
|
2884
|
+
this._virtualToolbarGroupComponent = this.attachVirtualToolbarGroup();
|
|
2885
|
+
this.installAccelerators();
|
|
2886
|
+
}
|
|
2887
|
+
/**
|
|
2888
|
+
* Creates and attaches a {@link SciToolbarGroupComponent} using this component as host,
|
|
2889
|
+
* rendering top-level toolbar items and groups as direct children inside `<sci-toolbar/>`.
|
|
2890
|
+
*/
|
|
2891
|
+
attachVirtualToolbarGroup() {
|
|
2892
|
+
const componentRef = createComponent(SciToolbarGroupComponent, {
|
|
2893
|
+
elementInjector: inject(Injector),
|
|
2894
|
+
environmentInjector: inject(EnvironmentInjector),
|
|
2895
|
+
hostElement: inject(ElementRef).nativeElement,
|
|
2896
|
+
bindings: [
|
|
2897
|
+
inputBinding('menuItems', this.menuItems),
|
|
2898
|
+
inputBinding('orientation', this.orientation),
|
|
2899
|
+
inputBinding('popoverViewContainerRef', this.popoverViewContainerRef),
|
|
2900
|
+
],
|
|
2901
|
+
});
|
|
2902
|
+
// Attach component to Angular component tree for change detection.
|
|
2903
|
+
inject(ApplicationRef).attachView(componentRef.hostView);
|
|
2904
|
+
// Destroy component when host is destroyed.
|
|
2905
|
+
inject(DestroyRef).onDestroy(() => componentRef.destroy());
|
|
2906
|
+
return componentRef.instance;
|
|
2907
|
+
}
|
|
2908
|
+
/**
|
|
2909
|
+
* Installs accelerators of menu items in this toolbar, recursively for menu items in submenus and groups.
|
|
2910
|
+
*/
|
|
2911
|
+
installAccelerators() {
|
|
2912
|
+
const injector = inject(Injector);
|
|
2913
|
+
const environmentTargets = injectMenuAcceleratorTargets();
|
|
2914
|
+
effect(onCleanup => {
|
|
2915
|
+
const menuItems = this.menuItems();
|
|
2916
|
+
const options = {
|
|
2917
|
+
targets: this.acceleratorTarget(),
|
|
2918
|
+
environmentTargets: environmentTargets(),
|
|
2919
|
+
injector,
|
|
2920
|
+
};
|
|
2921
|
+
untracked(() => {
|
|
2922
|
+
const accelerators = ɵinstallMenuAccelerators(menuItems, options);
|
|
2923
|
+
onCleanup(() => accelerators.dispose());
|
|
2924
|
+
});
|
|
2925
|
+
});
|
|
2926
|
+
}
|
|
2927
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciToolbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2928
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.7", type: SciToolbarComponent, isStandalone: true, selector: "sci-toolbar", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, context: { classPropertyName: "context", publicName: "context", isSignal: true, isRequired: false, transformFunction: null }, acceleratorTarget: { classPropertyName: "acceleratorTarget", publicName: "acceleratorTarget", isSignal: true, isRequired: false, transformFunction: null }, popoverViewContainerRef: { classPropertyName: "popoverViewContainerRef", publicName: "popoverViewContainerRef", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.name": "name()" } }, ngImport: i0, template: '', isInline: true });
|
|
2929
|
+
}
|
|
2930
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciToolbarComponent, decorators: [{
|
|
2931
|
+
type: Component,
|
|
2932
|
+
args: [{
|
|
2933
|
+
selector: 'sci-toolbar',
|
|
2934
|
+
template: '', // Template provided by `SciToolbarGroupComponent`; see `attachVirtualToolbarGroup()`.
|
|
2935
|
+
host: {
|
|
2936
|
+
'[attr.name]': 'name()', // Public API: Enables selecting the toolbar by name in CSS (also if the toolbar has a dynamic name input binding)
|
|
2937
|
+
},
|
|
2938
|
+
}]
|
|
2939
|
+
}], ctorParameters: () => [], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], context: [{ type: i0.Input, args: [{ isSignal: true, alias: "context", required: false }] }], acceleratorTarget: [{ type: i0.Input, args: [{ isSignal: true, alias: "acceleratorTarget", required: false }] }], popoverViewContainerRef: [{ type: i0.Input, args: [{ isSignal: true, alias: "popoverViewContainerRef", required: false }] }] } });
|
|
2940
|
+
|
|
2941
|
+
/*
|
|
2942
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
2943
|
+
*
|
|
2944
|
+
* This program and the accompanying materials are made
|
|
2945
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
2946
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
2947
|
+
*
|
|
2948
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
2949
|
+
*/
|
|
2950
|
+
|
|
2951
|
+
/*
|
|
2952
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
2953
|
+
*
|
|
2954
|
+
* This program and the accompanying materials are made
|
|
2955
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
2956
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
2957
|
+
*
|
|
2958
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
2959
|
+
*/
|
|
2960
|
+
/**
|
|
2961
|
+
* TODO [menu]: Explain how to size the menubar. (height can be set)
|
|
2962
|
+
*
|
|
2963
|
+
* @experimental since 22.3.0; API and behavior may change in any version without notice.
|
|
2964
|
+
*/
|
|
2965
|
+
class SciMenubarComponent {
|
|
2966
|
+
name = input.required(/* @ts-ignore */
|
|
2967
|
+
...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
|
|
2968
|
+
context = input(/* @ts-ignore */
|
|
2969
|
+
...(ngDevMode ? [undefined, { debugName: "context" }] : /* istanbul ignore next */ []));
|
|
2970
|
+
acceleratorTarget = input(/* @ts-ignore */
|
|
2971
|
+
...(ngDevMode ? [undefined, { debugName: "acceleratorTarget" }] : /* istanbul ignore next */ []));
|
|
2972
|
+
viewContainerRef = input(inject(ViewContainerRef), /* @ts-ignore */
|
|
2973
|
+
...(ngDevMode ? [{ debugName: "viewContainerRef" }] : /* istanbul ignore next */ []));
|
|
2974
|
+
_environmentContext = injectMenuContext();
|
|
2975
|
+
_context = computed(() => new Map([...this._environmentContext(), ...this.context() ?? new Map()]), /* @ts-ignore */
|
|
2976
|
+
...(ngDevMode ? [{ debugName: "_context" }] : /* istanbul ignore next */ []));
|
|
2977
|
+
menuItems = inject(ɵSciMenuService).menuItems(this.name, this._context);
|
|
2978
|
+
activeMenuItem = linkedSignal({ ...(ngDevMode ? { debugName: "activeMenuItem" } : /* istanbul ignore next */ {}), source: this.menuItems, // Unset when menu items change.
|
|
2979
|
+
computation: () => null,
|
|
2980
|
+
equal: (a, b) => a?.menuItem === b?.menuItem });
|
|
2981
|
+
constructor() {
|
|
2982
|
+
this.installMenuOpener();
|
|
2983
|
+
this.installAccelerators();
|
|
2984
|
+
}
|
|
2985
|
+
/**
|
|
2986
|
+
* Method invoked when clicking on a menu item.
|
|
2987
|
+
*/
|
|
2988
|
+
onMenuItemClick(menuItem, element) {
|
|
2989
|
+
this.activeMenuItem.update(activeMenuItem => activeMenuItem?.menuItem === menuItem ? null : { menuItem, element });
|
|
2990
|
+
}
|
|
2991
|
+
/**
|
|
2992
|
+
* Method invoked before clicking on a menu item.
|
|
2993
|
+
*
|
|
2994
|
+
* When clicking on the active menu item, mark it as closing to prevent immediate re-opening race condition on subsequent click event.
|
|
2995
|
+
*/
|
|
2996
|
+
onMenuItemMouseDown(menuItem) {
|
|
2997
|
+
const activeMenuItem = this.activeMenuItem();
|
|
2998
|
+
if (activeMenuItem?.menuItem === menuItem) {
|
|
2999
|
+
activeMenuItem.closing = true;
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
/**
|
|
3003
|
+
* Method invoked when moving the pointer over a menu item.
|
|
3004
|
+
*/
|
|
3005
|
+
onMenuItemMouseEnter(menuItem, element) {
|
|
3006
|
+
this.activeMenuItem.update(activeMenuItem => activeMenuItem ? { menuItem, element } : null);
|
|
3007
|
+
}
|
|
3008
|
+
/**
|
|
3009
|
+
* Method invoked when moving the pointer out of a menu item.
|
|
3010
|
+
*/
|
|
3011
|
+
onMenuItemMouseLeave(menuItem) {
|
|
3012
|
+
const activeMenuItem = this.activeMenuItem();
|
|
3013
|
+
if (activeMenuItem?.menuItem !== menuItem) {
|
|
3014
|
+
return;
|
|
3015
|
+
}
|
|
3016
|
+
if (activeMenuItem.closing === 'prevented') {
|
|
3017
|
+
this.activeMenuItem.set(null); // unset active menu
|
|
3018
|
+
}
|
|
3019
|
+
else if (activeMenuItem.closing) {
|
|
3020
|
+
delete activeMenuItem.closing; // unset closing state
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
/**
|
|
3024
|
+
* Opens a menu based on {@link activeMenuItem}.
|
|
3025
|
+
*/
|
|
3026
|
+
installMenuOpener() {
|
|
3027
|
+
const menuService = inject(ɵSciMenuService);
|
|
3028
|
+
effect(onCleanup => {
|
|
3029
|
+
if (!this.activeMenuItem()) {
|
|
3030
|
+
return;
|
|
3031
|
+
}
|
|
3032
|
+
const { menuItem, element } = this.activeMenuItem();
|
|
3033
|
+
const menu = menuItem.menu;
|
|
3034
|
+
untracked(() => {
|
|
3035
|
+
const menuRef = menuService.open(menu.children, {
|
|
3036
|
+
anchor: element,
|
|
3037
|
+
viewContainerRef: this.viewContainerRef(),
|
|
3038
|
+
filter: menu.filter,
|
|
3039
|
+
width: menu.width,
|
|
3040
|
+
minWidth: menu.minWidth,
|
|
3041
|
+
maxWidth: menu.maxWidth,
|
|
3042
|
+
maxHeight: menu.maxHeight,
|
|
3043
|
+
cssClass: menuItem.cssClass,
|
|
3044
|
+
attributes: menuItem.attributes,
|
|
3045
|
+
align: 'vertical',
|
|
3046
|
+
offset: { y: 1 },
|
|
3047
|
+
});
|
|
3048
|
+
// Close when opening another menu.
|
|
3049
|
+
onCleanup(() => menuRef.close());
|
|
3050
|
+
// Unset when closing menu.
|
|
3051
|
+
menuRef.onClose(() => {
|
|
3052
|
+
const activeMenuItem = this.activeMenuItem();
|
|
3053
|
+
// Do not unset if another menu was opened in the meantime.
|
|
3054
|
+
if (activeMenuItem?.menuItem !== menuItem) {
|
|
3055
|
+
return;
|
|
3056
|
+
}
|
|
3057
|
+
// Prevent immediate re-opening race condition when toolbar button is clicked to close.
|
|
3058
|
+
if (activeMenuItem.closing) {
|
|
3059
|
+
activeMenuItem.closing = 'prevented';
|
|
3060
|
+
return;
|
|
3061
|
+
}
|
|
3062
|
+
this.activeMenuItem.set(null);
|
|
3063
|
+
});
|
|
3064
|
+
});
|
|
3065
|
+
});
|
|
3066
|
+
}
|
|
3067
|
+
/**
|
|
3068
|
+
* Installs accelerators of menu items in this menubar, recursively for menu items in submenus and groups.
|
|
3069
|
+
*/
|
|
3070
|
+
installAccelerators() {
|
|
3071
|
+
const injector = inject(Injector);
|
|
3072
|
+
const environmentTargets = injectMenuAcceleratorTargets();
|
|
3073
|
+
effect(onCleanup => {
|
|
3074
|
+
const menuItems = this.menuItems();
|
|
3075
|
+
const options = {
|
|
3076
|
+
targets: this.acceleratorTarget(),
|
|
3077
|
+
environmentTargets: environmentTargets(),
|
|
3078
|
+
injector,
|
|
3079
|
+
};
|
|
3080
|
+
untracked(() => {
|
|
3081
|
+
const accelerators = ɵinstallMenuAccelerators(menuItems, options);
|
|
3082
|
+
onCleanup(() => accelerators.dispose());
|
|
3083
|
+
});
|
|
3084
|
+
});
|
|
3085
|
+
}
|
|
3086
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciMenubarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3087
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: SciMenubarComponent, isStandalone: true, selector: "sci-menubar", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, context: { classPropertyName: "context", publicName: "context", isSignal: true, isRequired: false, transformFunction: null }, acceleratorTarget: { classPropertyName: "acceleratorTarget", publicName: "acceleratorTarget", isSignal: true, isRequired: false, transformFunction: null }, viewContainerRef: { classPropertyName: "viewContainerRef", publicName: "viewContainerRef", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.name": "name()" } }, ngImport: i0, template: "@for (menuItem of menuItems(); track $index) {\r\n <button [class.open]=\"activeMenuItem()?.menuItem === menuItem\"\r\n [class]=\"menuItem.cssClass\" class=\"menu-item e2e-menu-item\"\r\n [sciAttributes]=\"menuItem.attributes\"\r\n (mousedown)=\"onMenuItemMouseDown(menuItem)\"\r\n (click)=\"onMenuItemClick(menuItem, element)\"\r\n (mouseenter)=\"onMenuItemMouseEnter(menuItem, element)\"\r\n (mouseleave)=\"onMenuItemMouseLeave(menuItem)\"\r\n #element>\r\n {{menuItem.labelText!()}}\r\n </button>\r\n}\r\n", styles: ["@charset \"UTF-8\";:host{--\\275sci-menubar-item-padding-inline: calc(.5 * var(--sci-menubar-item-size));--\\275sci-menubar-item-padding-block: calc(.25 * var(--sci-menubar-item-size));--\\275sci-menubar-font-size: calc(.8125 * var(--sci-menubar-item-size));font-size:var(--sci-menubar-font-size, var(--\\275sci-menubar-font-size));display:flex}:host>button:is(.menu-item,#sci-reset){all:unset;display:inline-flex;align-items:center;text-wrap:nowrap;padding-block:var(--\\275sci-menubar-item-padding-block);padding-inline:var(--\\275sci-menubar-item-padding-inline);border-radius:var(--sci-menubar-item-border-radius);-webkit-user-select:none;user-select:none;overflow:hidden;cursor:var(--sci-menubar-item-cursor)}:host>button:is(.menu-item,#sci-reset):hover{background-color:var(--sci-menubar-item-background-color-hover)}:host>button:is(.menu-item,#sci-reset):active{background-color:var(--sci-menubar-item-background-color-active)}:host>button:is(.menu-item,#sci-reset):focus:not(:focus-visible){outline:none}:host>button:is(.menu-item,#sci-reset):focus-visible{outline:var(--sci-menubar-item-outline-width) solid var(--sci-color-accent)}:host>button:is(.menu-item,#sci-reset).open{background-color:var(--sci-menubar-item-background-color-hover)}\n"], dependencies: [{ kind: "directive", type: SciAttributesDirective, selector: "[sciAttributes]", inputs: ["sciAttributes"] }] });
|
|
3088
|
+
}
|
|
3089
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: SciMenubarComponent, decorators: [{
|
|
3090
|
+
type: Component,
|
|
3091
|
+
args: [{ selector: 'sci-menubar', imports: [
|
|
3092
|
+
SciAttributesDirective,
|
|
3093
|
+
], host: {
|
|
3094
|
+
'[attr.name]': 'name()', // Public API: Enables selecting the menubar by name in CSS (also if the menubar has a dynamic name input binding)
|
|
3095
|
+
}, template: "@for (menuItem of menuItems(); track $index) {\r\n <button [class.open]=\"activeMenuItem()?.menuItem === menuItem\"\r\n [class]=\"menuItem.cssClass\" class=\"menu-item e2e-menu-item\"\r\n [sciAttributes]=\"menuItem.attributes\"\r\n (mousedown)=\"onMenuItemMouseDown(menuItem)\"\r\n (click)=\"onMenuItemClick(menuItem, element)\"\r\n (mouseenter)=\"onMenuItemMouseEnter(menuItem, element)\"\r\n (mouseleave)=\"onMenuItemMouseLeave(menuItem)\"\r\n #element>\r\n {{menuItem.labelText!()}}\r\n </button>\r\n}\r\n", styles: ["@charset \"UTF-8\";:host{--\\275sci-menubar-item-padding-inline: calc(.5 * var(--sci-menubar-item-size));--\\275sci-menubar-item-padding-block: calc(.25 * var(--sci-menubar-item-size));--\\275sci-menubar-font-size: calc(.8125 * var(--sci-menubar-item-size));font-size:var(--sci-menubar-font-size, var(--\\275sci-menubar-font-size));display:flex}:host>button:is(.menu-item,#sci-reset){all:unset;display:inline-flex;align-items:center;text-wrap:nowrap;padding-block:var(--\\275sci-menubar-item-padding-block);padding-inline:var(--\\275sci-menubar-item-padding-inline);border-radius:var(--sci-menubar-item-border-radius);-webkit-user-select:none;user-select:none;overflow:hidden;cursor:var(--sci-menubar-item-cursor)}:host>button:is(.menu-item,#sci-reset):hover{background-color:var(--sci-menubar-item-background-color-hover)}:host>button:is(.menu-item,#sci-reset):active{background-color:var(--sci-menubar-item-background-color-active)}:host>button:is(.menu-item,#sci-reset):focus:not(:focus-visible){outline:none}:host>button:is(.menu-item,#sci-reset):focus-visible{outline:var(--sci-menubar-item-outline-width) solid var(--sci-color-accent)}:host>button:is(.menu-item,#sci-reset).open{background-color:var(--sci-menubar-item-background-color-hover)}\n"] }]
|
|
3096
|
+
}], ctorParameters: () => [], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], context: [{ type: i0.Input, args: [{ isSignal: true, alias: "context", required: false }] }], acceleratorTarget: [{ type: i0.Input, args: [{ isSignal: true, alias: "acceleratorTarget", required: false }] }], viewContainerRef: [{ type: i0.Input, args: [{ isSignal: true, alias: "viewContainerRef", required: false }] }] } });
|
|
3097
|
+
|
|
3098
|
+
/*
|
|
3099
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
3100
|
+
*
|
|
3101
|
+
* This program and the accompanying materials are made
|
|
3102
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
3103
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
3104
|
+
*
|
|
3105
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
3106
|
+
*/
|
|
3107
|
+
|
|
3108
|
+
/*
|
|
3109
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
3110
|
+
*
|
|
3111
|
+
* This program and the accompanying materials are made
|
|
3112
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
3113
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
3114
|
+
*
|
|
3115
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
3116
|
+
*/
|
|
3117
|
+
|
|
3118
|
+
/*
|
|
3119
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
3120
|
+
*
|
|
3121
|
+
* This program and the accompanying materials are made
|
|
3122
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
3123
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
3124
|
+
*
|
|
3125
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
3126
|
+
*/
|
|
3127
|
+
/**
|
|
3128
|
+
* Provides {@link SciMenuService} for dependency injection.
|
|
3129
|
+
*/
|
|
3130
|
+
function provideMenuService() {
|
|
3131
|
+
return [
|
|
3132
|
+
ɵSciMenuService,
|
|
3133
|
+
{ provide: SciMenuService, useExisting: ɵSciMenuService },
|
|
3134
|
+
];
|
|
3135
|
+
}
|
|
3136
|
+
|
|
3137
|
+
/*
|
|
3138
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
3139
|
+
*
|
|
3140
|
+
* This program and the accompanying materials are made
|
|
3141
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
3142
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
3143
|
+
*
|
|
3144
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
3145
|
+
*/
|
|
3146
|
+
/**
|
|
3147
|
+
* Registers a {@link SciMenuContextProviderFn} to provide context to menu locations and contributions.
|
|
3148
|
+
*
|
|
3149
|
+
* Provided context is applied to menu locations and contributions in the scope of the injector providing this function.
|
|
3150
|
+
* Individual locations and contributions can extend or override the provided context.
|
|
3151
|
+
*
|
|
3152
|
+
* Multiple providers can be registered. Providers at different injector tree levels form a hierarchy.
|
|
3153
|
+
*
|
|
3154
|
+
* @see SciMenuContextProviderFn
|
|
3155
|
+
*/
|
|
3156
|
+
function provideMenuContextProvider(providerFn) {
|
|
3157
|
+
return [
|
|
3158
|
+
{
|
|
3159
|
+
provide: SCI_MENU_CONTEXT_PROVIDER,
|
|
3160
|
+
useValue: providerFn,
|
|
3161
|
+
multi: true,
|
|
3162
|
+
},
|
|
3163
|
+
{
|
|
3164
|
+
provide: SCI_MENU_CONTEXT_PROVIDERS,
|
|
3165
|
+
useFactory: () => {
|
|
3166
|
+
const injector = inject(Injector);
|
|
3167
|
+
return [
|
|
3168
|
+
// Collect providers registered in parent injectors.
|
|
3169
|
+
...inject(SCI_MENU_CONTEXT_PROVIDERS, { skipSelf: true, optional: true }) ?? [],
|
|
3170
|
+
// Collect providers registered in this injector.
|
|
3171
|
+
// Wrap function to execute in the injector of this injector, not in the calling context.
|
|
3172
|
+
...inject(SCI_MENU_CONTEXT_PROVIDER).map(fn => (callingInjector) => runInInjectionContext(injector, () => fn(callingInjector))),
|
|
3173
|
+
];
|
|
3174
|
+
},
|
|
3175
|
+
},
|
|
3176
|
+
// Provide `MenuService` to inherit provided menu context when opening a menu or looking up installed accelerators.
|
|
3177
|
+
provideMenuService(),
|
|
3178
|
+
];
|
|
3179
|
+
}
|
|
3180
|
+
/**
|
|
3181
|
+
* Registers a {@link SciMenuInjectionContextProviderFn} to provide dependency injection tokens for a given menu context.
|
|
3182
|
+
*
|
|
3183
|
+
* Provided tokens are available for dependency injection for contributions registered in the scope of the injector providing this function.
|
|
3184
|
+
* Tokens can be injected in the factory passed to {@link contributeMenu}.
|
|
3185
|
+
*
|
|
3186
|
+
* Multiple providers can be registered. Providers at different injector tree levels form a hierarchy.
|
|
3187
|
+
*
|
|
3188
|
+
* @see SciMenuInjectionContextProviderFn
|
|
3189
|
+
*/
|
|
3190
|
+
function provideMenuInjectionContextProvider(providerFn) {
|
|
3191
|
+
return [
|
|
3192
|
+
{
|
|
3193
|
+
provide: SCI_MENU_INJECTION_CONTEXT_PROVIDER,
|
|
3194
|
+
useValue: providerFn,
|
|
3195
|
+
multi: true,
|
|
3196
|
+
},
|
|
3197
|
+
{
|
|
3198
|
+
provide: SCI_MENU_INJECTION_CONTEXT_PROVIDERS,
|
|
3199
|
+
useFactory: () => {
|
|
3200
|
+
const injector = inject(Injector);
|
|
3201
|
+
return [
|
|
3202
|
+
// Collect providers registered in parent injectors.
|
|
3203
|
+
...inject(SCI_MENU_INJECTION_CONTEXT_PROVIDERS, { skipSelf: true, optional: true }) ?? [],
|
|
3204
|
+
// Collect providers registered in this injector.
|
|
3205
|
+
// Wrap function to execute in the injector of this injector, not in the calling context.
|
|
3206
|
+
...inject(SCI_MENU_INJECTION_CONTEXT_PROVIDER).map(fn => (context) => runInInjectionContext(injector, () => fn(context))),
|
|
3207
|
+
];
|
|
3208
|
+
},
|
|
3209
|
+
},
|
|
3210
|
+
];
|
|
3211
|
+
}
|
|
3212
|
+
/**
|
|
3213
|
+
* Registers a {@link SciMenuAcceleratorTargetProviderFn} to provide accelerator targets to menu locations.
|
|
3214
|
+
*
|
|
3215
|
+
* Provided accelerator targets are available to menu locations in the scope of the injector providing this function.
|
|
3216
|
+
* Individual locations can override the provided targets.
|
|
3217
|
+
*
|
|
3218
|
+
* Multiple providers can be registered. Providers at different injector tree levels form a hierarchy.
|
|
3219
|
+
*
|
|
3220
|
+
* @see SciMenuAcceleratorTargetProviderFn
|
|
3221
|
+
*/
|
|
3222
|
+
function provideMenuAcceleratorTargetProvider(providerFn) {
|
|
3223
|
+
return [
|
|
3224
|
+
{
|
|
3225
|
+
provide: SCI_MENU_ACCELERATOR_TARGET_PROVIDER,
|
|
3226
|
+
useValue: providerFn,
|
|
3227
|
+
multi: true,
|
|
3228
|
+
},
|
|
3229
|
+
{
|
|
3230
|
+
provide: SCI_MENU_ACCELERATOR_TARGET_PROVIDERS,
|
|
3231
|
+
useFactory: () => {
|
|
3232
|
+
// Collect providers registered in current and parent injectors.
|
|
3233
|
+
// Wrap function to execute in the injector where the function is provided, not in the injector of the consumer.
|
|
3234
|
+
const injector = inject(Injector);
|
|
3235
|
+
return [
|
|
3236
|
+
// Collect providers registered in parent injectors.
|
|
3237
|
+
...inject(SCI_MENU_ACCELERATOR_TARGET_PROVIDERS, { skipSelf: true, optional: true }) ?? [],
|
|
3238
|
+
// Collect providers registered in this injector.
|
|
3239
|
+
// Wrap function to execute in the injector of this injector, not in the calling context.
|
|
3240
|
+
...inject(SCI_MENU_ACCELERATOR_TARGET_PROVIDER).map(fn => (callingInjector) => runInInjectionContext(injector, () => fn(callingInjector))),
|
|
3241
|
+
];
|
|
3242
|
+
},
|
|
3243
|
+
},
|
|
3244
|
+
];
|
|
3245
|
+
}
|
|
3246
|
+
|
|
3247
|
+
/*
|
|
3248
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
3249
|
+
*
|
|
3250
|
+
* This program and the accompanying materials are made
|
|
3251
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
3252
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
3253
|
+
*
|
|
3254
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
3255
|
+
*/
|
|
3256
|
+
|
|
3257
|
+
/*
|
|
3258
|
+
* Copyright (c) 2018-2026 Swiss Federal Railways
|
|
3259
|
+
*
|
|
3260
|
+
* This program and the accompanying materials are made
|
|
3261
|
+
* available under the terms of the Eclipse Public License 2.0
|
|
3262
|
+
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
3263
|
+
*
|
|
3264
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
3265
|
+
*/
|
|
3266
|
+
/*
|
|
3267
|
+
* Secondary entrypoint: '@scion/components/menu'
|
|
3268
|
+
*
|
|
3269
|
+
* @see https://github.com/ng-packagr/ng-packagr/blob/master/docs/secondary-entrypoints.md
|
|
3270
|
+
*/
|
|
3271
|
+
|
|
3272
|
+
/**
|
|
3273
|
+
* Generated bundle index. Do not edit.
|
|
3274
|
+
*/
|
|
3275
|
+
|
|
3276
|
+
export { SciMenuAdapter, SciMenuService, SciMenubarComponent, SciToolbarComponent, contributeMenu, installMenuAccelerators, provideMenuAcceleratorTargetProvider, provideMenuContextProvider, provideMenuInjectionContextProvider, ɵSciMenuService, injectMenuInjectionContextProviders as ɵinjectMenuInjectionContextProviders };
|
|
3277
|
+
//# sourceMappingURL=scion-components-menu.mjs.map
|