Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Popup menu cleanup #697

Merged
merged 7 commits into from
Nov 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions lib/features/popup-menu/PopupMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
import {
domify,
remove as domRemove,
closest as domClosest,
attr as domAttr
} from 'min-dom';

Expand Down Expand Up @@ -102,10 +103,12 @@ PopupMenu.prototype._render = function() {
const scale = this._updateScale(this._current.container);

const onClose = result => this.close(result);
const onSelect = (event, entry) => this.trigger(event, entry);

render(html`
<${PopupMenuComponent}
onClose=${ onClose }
onSelect=${ onSelect }
position=${ position }
className=${ className }
entries=${ entriesArray }
Expand Down Expand Up @@ -474,18 +477,21 @@ PopupMenu.prototype.isOpen = function() {
* Trigger an action associated with an entry.
*
* @param {Object} event
* @param {Object} entry
*
* @return the result of the action callback, if any
*/
PopupMenu.prototype.trigger = function(event) {
PopupMenu.prototype.trigger = function(event, entry) {

// silence other actions
event.preventDefault();

var element = event.delegateTarget || event.target,
entryId = domAttr(element, DATA_REF);
if (!entry) {
let element = domClosest(event.delegateTarget || event.target, '.entry', true);
let entryId = domAttr(element, DATA_REF);

var entry = this._getEntry(entryId);
entry = this._getEntry(entryId);
}

if (entry.action) {
return entry.action.call(null, event, entry);
Expand Down
120 changes: 44 additions & 76 deletions lib/features/popup-menu/PopupMenuComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import {
useState,
useLayoutEffect,
html,
useMemo,
useCallback
useMemo
} from '../../ui';

import PopupMenuList from './PopupMenuList';
import classNames from 'clsx';
import { isDefined } from 'min-dash';


/**
* A component that renders the popup menus.
*
Expand All @@ -28,6 +28,7 @@ import { isDefined } from 'min-dash';
export default function PopupMenuComponent(props) {
const {
onClose,
onSelect,
className,
headerEntries,
position,
Expand All @@ -37,12 +38,6 @@ export default function PopupMenuComponent(props) {
entries: originalEntries
} = props;

const onSelect = useCallback((event, entry, shouldClose = true) => {
entry.action(event, entry);

shouldClose && onClose();
}, [ onClose ]);

const searchable = useMemo(() => {
if (isDefined(search)) {
return !!search;
Expand All @@ -52,7 +47,6 @@ export default function PopupMenuComponent(props) {
}, [ search, originalEntries ]);

const inputRef = useRef();
const resultsRef = useRef();

const [ value, setValue ] = useState('');

Expand Down Expand Up @@ -118,20 +112,6 @@ export default function PopupMenuComponent(props) {
inputRef.current && inputRef.current.focus();
}, []);

// scroll to keyboard selected result
useLayoutEffect(() => {
const containerEl = resultsRef.current;

if (!containerEl)
return;

const selectedEl = containerEl.querySelector('.selected');

if (selectedEl) {
scrollIntoView(selectedEl);
}
}, [ selectedEntry ]);

// handle keyboard seleciton
const keyboardSelect = direction => {
const idx = entries.indexOf(selectedEntry);
Expand Down Expand Up @@ -188,52 +168,51 @@ export default function PopupMenuComponent(props) {
width=${ width }
scale=${ scale }
>
${displayHeader && html`
${ displayHeader && html`
<div class="djs-popup-header">
<h3 class="title">${props.title}</h3>
${Object.entries(headerEntries).map(([ key, value ]) => html`
<h3 class="title">${ props.title }</h3>
${ Object.entries(headerEntries).map(([ id, entry ]) => html`
<span
class=${getHeaderClasses(value, headerEntries[key] === selectedEntry)}
onClick=${ event => onSelect(event, value, false) }
title=${ `Toggle ${value.title}` }
data-id=${ key }
onMouseEnter=${ () => setSelectedEntry(headerEntries[key]) }
class=${ getHeaderClasses(entry, entry === selectedEntry) }
onClick=${ event => onSelect(event, entry) }
title=${ `Toggle ${entry.title}` }
data-id=${ id }
onMouseEnter=${ () => setSelectedEntry(entry) }
>
${value.imageUrl ? html`<img src=${ value.imageUrl } />` : null}
${value.label ? value.label : null}
</span>`
)}
</div>`
}
${originalEntries.length && html`
<div class="djs-popup-body">

${ searchable && html`
<div class="search">
<svg class="search-icon" width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.0325 8.5H9.625L13.3675 12.25L12.25 13.3675L8.5 9.625V9.0325L8.2975 8.8225C7.4425 9.5575 6.3325 10 5.125 10C2.4325 10 0.25 7.8175 0.25 5.125C0.25 2.4325 2.4325 0.25 5.125 0.25C7.8175 0.25 10 2.4325 10 5.125C10 6.3325 9.5575 7.4425 8.8225 8.2975L9.0325 8.5ZM1.75 5.125C1.75 6.9925 3.2575 8.5 5.125 8.5C6.9925 8.5 8.5 6.9925 8.5 5.125C8.5 3.2575 6.9925 1.75 5.125 1.75C3.2575 1.75 1.75 3.2575 1.75 5.125Z" fill="#22242A"/>
</svg>
<input
ref=${ inputRef }
type="text"
onKeyup=${ handleKey }
onKeydown=${ handleKeyDown }
/>
${entry.imageUrl ? html`<img src=${ entry.imageUrl } />` : null}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing that I found is that we have to be careful with the way we format html template strings. Best practice is to have start and end on separate, individual lines.

Should we not have inline html`` like here then?

Copy link
Member Author

@nikku nikku Nov 17, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does not screw up the markup, hence I'd be OK with this snippet.

What breaks the markup entirely is stuff like this:

html`
<div>
  <a>...</a>
</div>`

=> Asymetric use of tagged template literal open + close.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it!

${entry.label ? entry.label : null}
</span>
`) }
</div>
` }

<${PopupMenuList}
entries=${ entries }
selectedEntry=${ selectedEntry }
setSelectedEntry=${ setSelectedEntry }
onSelect=${ onSelect }
resultsRef=${ resultsRef }
/>
</div>
`}
${entries.length === 0 && html`
<div class="no-results">No matching entries found.</div>
`}
` }
${ originalEntries.length && html`
<div class="djs-popup-body">

${ searchable && html`
<div class="search">
<svg class="search-icon" width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.0325 8.5H9.625L13.3675 12.25L12.25 13.3675L8.5 9.625V9.0325L8.2975 8.8225C7.4425 9.5575 6.3325 10 5.125 10C2.4325 10 0.25 7.8175 0.25 5.125C0.25 2.4325 2.4325 0.25 5.125 0.25C7.8175 0.25 10 2.4325 10 5.125C10 6.3325 9.5575 7.4425 8.8225 8.2975L9.0325 8.5ZM1.75 5.125C1.75 6.9925 3.2575 8.5 5.125 8.5C6.9925 8.5 8.5 6.9925 8.5 5.125C8.5 3.2575 6.9925 1.75 5.125 1.75C3.2575 1.75 1.75 3.2575 1.75 5.125Z" fill="#22242A"/>
</svg>
<input
ref=${ inputRef }
type="text"
onKeyup=${ handleKey }
onKeydown=${ handleKeyDown }
/>
</div>
` }

<${PopupMenuList}
entries=${ entries }
selectedEntry=${ selectedEntry }
setSelectedEntry=${ setSelectedEntry }
onClick=${ onSelect }
/>
</div>
` }
${ entries.length === 0 && html`
<div class="no-results">No matching entries found.</div>
` }
</${PopupMenuWrapper}>`
);
}
Expand Down Expand Up @@ -273,7 +252,6 @@ function PopupMenuWrapper(props) {
<div
class="overlay"
ref=${ overlayRef }
onClick=${ e => e.stopPropagation() }
style=${ getOverlayStyle(props) }
>
${children}
Expand All @@ -284,16 +262,6 @@ function PopupMenuWrapper(props) {
}

// helpers //////////////////////
function scrollIntoView(el) {
if (typeof el.scrollIntoViewIfNeeded === 'function') {
el.scrollIntoViewIfNeeded();
} else {
el.scrollIntoView({
scrollMode: 'if-needed',
block: 'nearest'
});
}
}

function getOverlayStyle(props) {
return {
Expand Down
33 changes: 20 additions & 13 deletions lib/features/popup-menu/PopupMenuItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,32 +15,40 @@ import {
* @param {function} onClick
*/
export default function PopupMenuItem(props) {
const { entry, selected, ...restProps } = props;
const {
entry,
selected,
onMouseEnter,
onClick
} = props;

const createImage = imageUrl => {
return html`<img src=${ imageUrl } class="icon" />`;
};

return (html`
return html`
<li
class=${ classNames('entry', { selected }) }
data-id=${ entry.id }
...${restProps }
onClick=${ onClick }
onMouseEnter=${ onMouseEnter }
>
<div class="content">
<span
class=${ classNames('name', entry.className) }
title=${ entry.label || entry.name }
>
${entry.imageUrl ? createImage(entry.imageUrl) : null}
${entry.label || entry.name}
</span>
<span
class="description"
title=${ entry.description }
>
${ entry.description }
${ entry.imageUrl ? createImage(entry.imageUrl) : null }
${ entry.label || entry.name }
</span>
${ entry.description && html`
<span
class="description"
title=${ entry.description }
>
${ entry.description }
</span>
` }
</div>
${ entry.documentationRef && html`
<div class="docs">
Expand All @@ -58,6 +66,5 @@ export default function PopupMenuItem(props) {
</div>
` }
</li>
`
);
`;
}
Loading