diff --git a/lib/features/popup-menu/PopupMenu.js b/lib/features/popup-menu/PopupMenu.js
index d75bc7dde..892c20177 100644
--- a/lib/features/popup-menu/PopupMenu.js
+++ b/lib/features/popup-menu/PopupMenu.js
@@ -104,6 +104,7 @@ PopupMenu.prototype._render = function() {
className,
entries,
headerEntries,
+ noSearchResultsCallback,
options
} = this._current;
@@ -133,6 +134,7 @@ PopupMenu.prototype._render = function() {
className=${ className }
entries=${ entriesArray }
headerEntries=${ headerEntriesArray }
+ noSearchResultsCallback=${ noSearchResultsCallback }
scale=${ scale }
onOpened=${ this._onOpened.bind(this) }
onClosed=${ this._onClosed.bind(this) }
@@ -171,7 +173,8 @@ PopupMenu.prototype.open = function(target, providerId, position, options) {
const {
entries,
- headerEntries
+ headerEntries,
+ noSearchResultsCallback
} = this._getContext(target, providerId);
this._current = {
@@ -180,6 +183,7 @@ PopupMenu.prototype.open = function(target, providerId, position, options) {
target,
entries,
headerEntries,
+ noSearchResultsCallback,
container: this._createContainer({ provider: providerId }),
options
};
@@ -204,9 +208,12 @@ PopupMenu.prototype._getContext = function(target, provider) {
const headerEntries = this._getHeaderEntries(target, providers);
+ const noSearchResultsCallback = this._getNoSearchResultsCallback(providers);
+
return {
entries,
headerEntries,
+ noSearchResultsCallback,
empty: !(
Object.keys(entries).length ||
Object.keys(headerEntries).length
@@ -509,6 +516,19 @@ PopupMenu.prototype._getHeaderEntries = function(target, providers) {
};
+PopupMenu.prototype._getNoSearchResultsCallback = function(providers) {
+ var noSearchResultsCallback;
+
+ forEach(providers, function(provider) {
+ if (isFunction(provider.getNoSearchResultsCallback)) {
+ noSearchResultsCallback = provider.getNoSearchResultsCallback();
+ }
+ });
+
+ return noSearchResultsCallback;
+};
+
+
/**
* Check if the popup menu is open.
*
diff --git a/lib/features/popup-menu/PopupMenuComponent.js b/lib/features/popup-menu/PopupMenuComponent.js
index 3773d0244..eaa2abfd8 100644
--- a/lib/features/popup-menu/PopupMenuComponent.js
+++ b/lib/features/popup-menu/PopupMenuComponent.js
@@ -20,6 +20,7 @@ import { isDefined } from 'min-dash';
/**
* @typedef {import('./PopupMenuProvider').PopupMenuEntry} PopupMenuEntry
* @typedef {import('./PopupMenuProvider').PopupMenuHeaderEntry} PopupMenuHeaderEntry
+ * @typedef {import('./PopupMenuProvider').PopupMenuNoSearchResultsCallback} PopupMenuNoSearchResultsCallback
*
* @typedef {import('../../util/Types').Point} Point
*/
@@ -36,6 +37,7 @@ import { isDefined } from 'min-dash';
* @param {number} props.scale
* @param {string} [props.title]
* @param {boolean} [props.search]
+ * @param {PopupMenuNoSearchResultsCallback} [props.noSearchResultsCallback]
* @param {number} [props.width]
*/
export default function PopupMenuComponent(props) {
@@ -49,6 +51,7 @@ export default function PopupMenuComponent(props) {
width,
scale,
search,
+ noSearchResultsCallback,
entries: originalEntries,
onOpened,
onClosed
@@ -246,7 +249,7 @@ export default function PopupMenuComponent(props) {
/>
${ entries.length === 0 && html`
-
+
` }
` }
${PopupMenuWrapper}>
diff --git a/lib/features/popup-menu/PopupMenuProvider.ts b/lib/features/popup-menu/PopupMenuProvider.ts
index cbc7eeb44..c48b9f13a 100644
--- a/lib/features/popup-menu/PopupMenuProvider.ts
+++ b/lib/features/popup-menu/PopupMenuProvider.ts
@@ -1,3 +1,5 @@
+import { VNode } from '@bpmn-io/diagram-js-ui';
+
import { PopupMenuTarget } from './PopupMenu';
export type PopupMenuEntryAction = (event: Event, entry: PopupMenuEntry, action?: string) => any;
@@ -31,6 +33,8 @@ export type PopupMenuHeaderEntries = PopupMenuHeaderEntry[];
export type PopupMenuProviderHeaderEntriesCallback = (entries: PopupMenuHeaderEntries) => PopupMenuHeaderEntries;
+export type PopupMenuNoSearchResultsCallback = (value: string) => VNode;
+
/**
* An interface to be implemented by a popup menu provider.
*/
@@ -97,4 +101,17 @@ export default interface PopupMenuProvider {
* @param target
*/
getHeaderEntries?(target: PopupMenuTarget): PopupMenuProviderHeaderEntriesCallback | PopupMenuHeaderEntries;
+
+ /**
+ * Returns a callback that returns a VNode to be rendered when there are no search results.
+ *
+ * @example
+ *
+ * ```javascript
+ * getNoSearchResultsCallback() {
+ * return (value) => No results for { value };
+ * }
+ * ```
+ */
+ getNoSearchResultsCallback?(): PopupMenuNoSearchResultsCallback;
}
\ No newline at end of file
diff --git a/test/spec/features/popup-menu/PopupMenuSpec.js b/test/spec/features/popup-menu/PopupMenuSpec.js
index df88d7474..d2af4e2b4 100755
--- a/test/spec/features/popup-menu/PopupMenuSpec.js
+++ b/test/spec/features/popup-menu/PopupMenuSpec.js
@@ -22,6 +22,8 @@ import { createEvent as globalEvent } from '../../../util/MockEvents';
import popupMenuModule from 'lib/features/popup-menu';
import modelingModule from 'lib/features/modeling';
+import { html } from 'lib/ui';
+
describe('features/popup-menu', function() {
@@ -1218,44 +1220,51 @@ describe('features/popup-menu', function() {
expect(popupMenu.isOpen()).to.be.true;
}));
+ });
- describe('search rank', function() {
- var testMenuProvider = {
- getEntries: function() {
- return [
- {
- id: 'A',
- label: 'A'
- },
- {
- id: 'B',
- label: 'B'
- },
- {
- id: 'C',
- label: 'C'
- },
- {
- id: 'D',
- label: 'D',
- rank: 1
- },
- {
- id: 'E',
- label: 'E',
- rank: 0
- },
- {
- id: 'F',
- label: 'F (hide initially)',
- rank: -1
- }
- ];
- }
- };
+ describe('search', function() {
+
+ var testMenuProvider = {
+ getEntries: function() {
+ return [
+ {
+ id: 'A',
+ label: 'A'
+ },
+ {
+ id: 'B',
+ label: 'B'
+ },
+ {
+ id: 'C',
+ label: 'C'
+ },
+ {
+ id: 'D',
+ label: 'D',
+ rank: 1
+ },
+ {
+ id: 'E',
+ label: 'E',
+ rank: 0
+ },
+ {
+ id: 'F',
+ label: 'F (hide initially)',
+ rank: -1
+ }
+ ];
+ },
+ getNoSearchResultsCallback: () => {
+ return value => html`${ value }
`;
+ }
+ };
+ describe('ranking', function() {
+
it('should hide rank < 0 items', inject(async function(popupMenu) {
// given
@@ -1288,6 +1297,23 @@ describe('features/popup-menu', function() {
});
+
+ it('should render custom entry if no search results', inject(async function(popupMenu) {
+
+ // given
+ popupMenu.registerProvider('test-menu', testMenuProvider);
+ popupMenu.open({}, 'test-menu', { x: 100, y: 100 }, { search: true });
+
+ // when
+ await triggerSearch('foobar');
+
+ // then
+ var node = queryPopup('#no-search-results-foobar');
+
+ expect(node).to.exist;
+ expect(node.textContent).to.eql('foobar');
+ }));
+
});