mirror of
https://github.com/nasa/openmct.git
synced 2025-06-24 18:25:19 +00:00
Compare commits
13 Commits
master
...
filterable
Author | SHA1 | Date | |
---|---|---|---|
2cf5e738c1 | |||
8d434e30d4 | |||
2b24033ae1 | |||
93093667cd | |||
8dff9f1a25 | |||
f68fdb03bc | |||
3ecb9d4355 | |||
f665c5e71b | |||
2b78e7ce04 | |||
cded8fc4d0 | |||
1326693643 | |||
128f4827df | |||
e1969585f9 |
@ -129,6 +129,7 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('keydown', this.handleKeyDown);
|
||||
this.formSections = this.model.sections.map((section) => {
|
||||
section.id = uuid();
|
||||
|
||||
@ -141,6 +142,9 @@ export default {
|
||||
return section;
|
||||
});
|
||||
},
|
||||
unmounted() {
|
||||
document.removeEventListener('keydown', this.handleKeyDown);
|
||||
},
|
||||
methods: {
|
||||
onChange(data) {
|
||||
this.invalidProperties[data.model.key] = data.invalid;
|
||||
@ -152,6 +156,13 @@ export default {
|
||||
},
|
||||
onSave() {
|
||||
this.$emit('on-save');
|
||||
},
|
||||
handleKeyDown({ key }) {
|
||||
if (key === 'Enter' && !this.isInvalid) {
|
||||
this.onSave();
|
||||
} else if (key === 'Escape') {
|
||||
this.onCancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -26,53 +26,66 @@
|
||||
:class="[options.menuClass, 'c-super-menu']"
|
||||
:style="styleObject"
|
||||
>
|
||||
<ul
|
||||
v-if="options.actions.length && options.actions[0].length"
|
||||
role="menu"
|
||||
class="c-super-menu__menu"
|
||||
>
|
||||
<template v-for="(actionGroups, index) in options.actions" :key="index">
|
||||
<div role="group">
|
||||
<li
|
||||
v-for="action in actionGroups"
|
||||
:key="action.name"
|
||||
role="menuitem"
|
||||
:aria-disabled="action.isDisabled"
|
||||
aria-describedby="item-description"
|
||||
:class="action.cssClass"
|
||||
@click="action.onItemClicked"
|
||||
@mouseover="toggleItemDescription(action)"
|
||||
@mouseleave="toggleItemDescription()"
|
||||
>
|
||||
{{ action.name }}
|
||||
</li>
|
||||
<div
|
||||
v-if="index !== options.actions.length - 1"
|
||||
:key="index"
|
||||
role="separator"
|
||||
class="c-menu__section-separator"
|
||||
></div>
|
||||
<li v-if="actionGroups.length === 0" :key="index">No actions defined.</li>
|
||||
</div></template
|
||||
<div class="c-super-menu__left-col">
|
||||
<div v-if="options.filterable" class="c-super-menu__filter l-input-lg">
|
||||
<input
|
||||
ref="filterInput"
|
||||
v-model="searchTerm"
|
||||
type="text"
|
||||
placeholder="Filter..."
|
||||
@input="filterItems"
|
||||
@keydown.stop="handleKeyDown"
|
||||
@click.stop
|
||||
/>
|
||||
</div>
|
||||
<ul
|
||||
v-if="filteredActions.length && filteredActions[0].length"
|
||||
role="menu"
|
||||
class="c-super-menu__menu"
|
||||
>
|
||||
</ul>
|
||||
<template v-for="(actionGroups, index) in filteredActions" :key="index">
|
||||
<div role="group">
|
||||
<li
|
||||
v-for="action in actionGroups"
|
||||
:key="action.name"
|
||||
role="menuitem"
|
||||
:aria-disabled="action.isDisabled"
|
||||
aria-describedby="item-description"
|
||||
:class="action.cssClass"
|
||||
@click="action.onItemClicked"
|
||||
@mouseover="toggleItemDescription(action)"
|
||||
@mouseleave="toggleItemDescription()"
|
||||
>
|
||||
{{ action.name }}
|
||||
</li>
|
||||
<div
|
||||
v-if="index !== filteredActions.length - 1"
|
||||
:key="index"
|
||||
role="separator"
|
||||
class="c-menu__section-separator"
|
||||
></div>
|
||||
<li v-if="actionGroups.length === 0" :key="index">No actions defined.</li>
|
||||
</div></template
|
||||
>
|
||||
</ul>
|
||||
|
||||
<ul v-else class="c-super-menu__menu" role="menu">
|
||||
<li
|
||||
v-for="action in options.actions"
|
||||
:key="action.name"
|
||||
role="menuitem"
|
||||
:class="action.cssClass"
|
||||
:aria-label="action.name"
|
||||
aria-describedby="item-description"
|
||||
@click="action.onItemClicked"
|
||||
@mouseover="toggleItemDescription(action)"
|
||||
@mouseleave="toggleItemDescription()"
|
||||
>
|
||||
{{ action.name }}
|
||||
</li>
|
||||
<li v-if="options.actions.length === 0">No actions defined.</li>
|
||||
</ul>
|
||||
<ul v-else class="c-super-menu__menu" role="menu">
|
||||
<li
|
||||
v-for="action in filteredActions"
|
||||
:key="action.name"
|
||||
role="menuitem"
|
||||
:class="action.cssClass"
|
||||
:aria-label="action.name"
|
||||
aria-describedby="item-description"
|
||||
@click="action.onItemClicked"
|
||||
@mouseover="toggleItemDescription(action)"
|
||||
@mouseleave="toggleItemDescription()"
|
||||
>
|
||||
{{ action.name }}
|
||||
</li>
|
||||
<li v-if="filteredActions.length === 0">No actions defined.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div aria-live="polite" class="c-super-menu__item-description">
|
||||
<div :class="itemDescriptionIconClass"></div>
|
||||
@ -89,10 +102,12 @@
|
||||
import popupMenuMixin from '../mixins/popupMenuMixin.js';
|
||||
export default {
|
||||
mixins: [popupMenuMixin],
|
||||
inject: ['options'],
|
||||
inject: ['options', 'dismiss'],
|
||||
data() {
|
||||
return {
|
||||
hoveredItem: null
|
||||
hoveredItem: null,
|
||||
filteredActions: [],
|
||||
searchTerm: ''
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@ -114,6 +129,15 @@ export default {
|
||||
return this.hoveredItem?.description ?? '';
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.filteredActions = this.options.actions;
|
||||
|
||||
if (this.options.filterable) {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.filterInput.focus();
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleItemDescription(action = null) {
|
||||
const hoveredItem = {
|
||||
@ -123,6 +147,42 @@ export default {
|
||||
};
|
||||
|
||||
this.hoveredItem = hoveredItem;
|
||||
},
|
||||
filterItems() {
|
||||
const term = this.searchTerm.toLowerCase();
|
||||
|
||||
if (!term) {
|
||||
this.filteredActions = this.options.actions;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.isArray(this.options.actions[0])) {
|
||||
// Handle grouped actions
|
||||
this.filteredActions = this.options.actions
|
||||
.map((group) => group.filter((action) => action.name.toLowerCase().includes(term)))
|
||||
.filter((group) => group.length > 0);
|
||||
} else {
|
||||
// Handle flat actions list
|
||||
this.filteredActions = this.options.actions.filter((action) =>
|
||||
action.name.toLowerCase().includes(term)
|
||||
);
|
||||
}
|
||||
},
|
||||
handleKeyDown({ key }) {
|
||||
if (key === 'Enter') {
|
||||
// if there is only one action, select it immediately on enter
|
||||
const flattenedActions = Array.isArray(this.filteredActions[0])
|
||||
? this.filteredActions.flat()
|
||||
: this.filteredActions;
|
||||
|
||||
if (flattenedActions.length === 1) {
|
||||
flattenedActions[0].onItemClicked();
|
||||
this.dismiss();
|
||||
}
|
||||
} else if (key === 'Escape') {
|
||||
this.dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -114,7 +114,8 @@ class Menu extends EventEmitter {
|
||||
return h(SuperMenuComponent);
|
||||
},
|
||||
provide: {
|
||||
options: this.options
|
||||
options: this.options,
|
||||
dismiss: this.dismiss
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -64,6 +64,7 @@ export default {
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('keydown', this.handleKeyDown);
|
||||
const element = this.$refs.element;
|
||||
element.appendChild(this.element);
|
||||
const elementForFocus = this.getElementForFocus() || element;
|
||||
@ -73,6 +74,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
destroy() {
|
||||
document.removeEventListener('keydown', this.handleKeyDown);
|
||||
if (this.dismissible) {
|
||||
this.dismiss();
|
||||
}
|
||||
@ -100,6 +102,20 @@ export default {
|
||||
}
|
||||
|
||||
return focusButton[0];
|
||||
},
|
||||
handleKeyDown({ key }) {
|
||||
if (key === 'Enter') {
|
||||
if (this.focusIndex >= 0 && this.focusIndex < this.buttons.length) {
|
||||
this.buttonClickHandler(this.buttons[this.focusIndex].callback);
|
||||
} else {
|
||||
const okButton = this.buttons?.find((button) => button.label.toLowerCase() === 'ok');
|
||||
if (okButton) {
|
||||
this.buttonClickHandler(okButton.callback);
|
||||
}
|
||||
}
|
||||
} else if (key === 'Escape') {
|
||||
this.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -182,13 +182,22 @@ export default {
|
||||
this.handleNewBounds = _.throttle(this.handleNewBounds, 300);
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('keydown', this.handleKeyDown);
|
||||
this.setTimeSystem(JSON.parse(JSON.stringify(this.openmct.time.getTimeSystem())));
|
||||
this.setViewFromBounds(this.bounds);
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.clearAllValidation();
|
||||
document.removeEventListener('keydown', this.handleKeyDown);
|
||||
},
|
||||
methods: {
|
||||
handleKeyDown({ key }) {
|
||||
if (key === 'Enter' && !this.hasInputValidityError) {
|
||||
this.handleFormSubmission(true);
|
||||
} else if (key === 'Escape') {
|
||||
this.dismiss();
|
||||
}
|
||||
},
|
||||
handleNewBounds(bounds) {
|
||||
this.setBounds(bounds);
|
||||
this.setViewFromBounds(bounds);
|
||||
@ -322,8 +331,11 @@ export default {
|
||||
},
|
||||
hide($event) {
|
||||
if ($event.target.className.indexOf('c-button icon-x') > -1) {
|
||||
this.$emit('dismiss');
|
||||
this.dismiss();
|
||||
}
|
||||
},
|
||||
dismiss() {
|
||||
this.$emit('dismiss');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -172,11 +172,20 @@ export default {
|
||||
mounted() {
|
||||
this.setOffsets();
|
||||
document.addEventListener('click', this.hide);
|
||||
document.addEventListener('keydown', this.handleKeyDown);
|
||||
},
|
||||
beforeUnmount() {
|
||||
document.removeEventListener('click', this.hide);
|
||||
document.removeEventListener('keydown', this.handleKeyDown);
|
||||
},
|
||||
methods: {
|
||||
handleKeyDown({ key }) {
|
||||
if (key === 'Enter' && !this.isDisabled) {
|
||||
this.submit();
|
||||
} else if (key === 'Escape') {
|
||||
this.dismiss();
|
||||
}
|
||||
},
|
||||
format(ref) {
|
||||
const curVal = this[ref];
|
||||
this[ref] = curVal.toString().padStart(2, '0');
|
||||
@ -218,13 +227,16 @@ export default {
|
||||
seconds: this.endInputSecs
|
||||
}
|
||||
});
|
||||
this.$emit('dismiss');
|
||||
this.dismiss();
|
||||
},
|
||||
hide($event) {
|
||||
if ($event.target.className.indexOf('c-button icon-x') > -1) {
|
||||
this.$emit('dismiss');
|
||||
this.dismiss();
|
||||
}
|
||||
},
|
||||
dismiss() {
|
||||
this.$emit('dismiss');
|
||||
},
|
||||
increment($ev, ref) {
|
||||
$ev.preventDefault();
|
||||
const step = ref === 'startInputHrs' || ref === 'endInputHrs' ? 1 : 5;
|
||||
|
@ -348,7 +348,7 @@ $colorMenuElementHilite: pullForward($colorMenuBg, 10%);
|
||||
$shdwMenu: rgba(black, 0.8) 0 2px 10px;
|
||||
$shdwMenuInner: inset 0 0 0 1px rgba(white, 0.2);
|
||||
$shdwMenuText: none;
|
||||
$menuItemPad: $interiorMargin, floor($interiorMargin * 1.25);
|
||||
$menuItemPad: 4px, 6px;
|
||||
|
||||
// Palettes and Swatches
|
||||
$paletteItemBorderOuterColorSelected: black;
|
||||
|
@ -317,7 +317,7 @@ $colorMenuElementHilite: pullForward($colorMenuBg, 10%);
|
||||
$shdwMenu: rgba(black, 0.8) 0 2px 10px;
|
||||
$shdwMenuInner: inset 0 0 0 1px rgba(white, 0.2);
|
||||
$shdwMenuText: none;
|
||||
$menuItemPad: $interiorMargin, floor($interiorMargin * 1.25);
|
||||
$menuItemPad: 4px, 6px;
|
||||
|
||||
// Palettes and Swatches
|
||||
$paletteItemBorderOuterColorSelected: black;
|
||||
|
@ -333,7 +333,7 @@ $colorMenuElementHilite: pullForward($colorMenuBg, 10%);
|
||||
$shdwMenu: rgba(black, 0.8) 0 2px 10px;
|
||||
$shdwMenuInner: inset 0 0 0 1px rgba(white, 0.2);
|
||||
$shdwMenuText: none;
|
||||
$menuItemPad: $interiorMargin, floor($interiorMargin * 1.25);
|
||||
$menuItemPad: 4px, 6px;
|
||||
|
||||
// Palettes and Swatches
|
||||
$paletteItemBorderOuterColorSelected: black;
|
||||
|
@ -316,7 +316,7 @@ $colorMenuElementHilite: darken($colorMenuBg, 10%);
|
||||
$shdwMenu: rgba(black, 0.8) 0 2px 10px;
|
||||
$shdwMenuInner: none;
|
||||
$shdwMenuText: none;
|
||||
$menuItemPad: $interiorMargin, floor($interiorMargin * 1.25);
|
||||
$menuItemPad: 4px, 6px;
|
||||
|
||||
// Palettes and Swatches
|
||||
$paletteItemBorderOuterColorSelected: black;
|
||||
|
@ -58,9 +58,11 @@
|
||||
@mixin menuInner() {
|
||||
li {
|
||||
@include cControl();
|
||||
align-items: baseline;
|
||||
justify-content: start;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
line-height: 1.2em;
|
||||
padding: nth($menuItemPad, 1) nth($menuItemPad, 2);
|
||||
white-space: nowrap;
|
||||
|
||||
@ -651,22 +653,21 @@ select {
|
||||
padding: $interiorMarginLg;
|
||||
flex-direction: row;
|
||||
|
||||
> [class*='__'] {
|
||||
//flex: 1 1 50%;
|
||||
//&:first-child {
|
||||
// margin-right: $m;
|
||||
//}
|
||||
&__left-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 1 50%;
|
||||
gap: $interiorMargin;
|
||||
margin-right: $m;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
//border-left: 1px solid $colorInteriorBorder;
|
||||
//padding-left: $m;
|
||||
}
|
||||
&__filter {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
&__menu {
|
||||
@include menuInner();
|
||||
flex: 1 1 50%;
|
||||
margin-right: $m;
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
|
||||
ul {
|
||||
@ -675,6 +676,7 @@ select {
|
||||
|
||||
li {
|
||||
border-radius: $controlCr;
|
||||
white-space: normal; // Let long names wrap
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,7 +92,8 @@ export default {
|
||||
const y = elementBoundingClientRect.y + elementBoundingClientRect.height;
|
||||
|
||||
const menuOptions = {
|
||||
menuClass: 'c-create-menu'
|
||||
menuClass: 'c-create-menu',
|
||||
filterable: true
|
||||
};
|
||||
|
||||
this.openmct.menus.showSuperMenu(x, y, this.sortedItems, menuOptions);
|
||||
|
Reference in New Issue
Block a user