<commit_before>import {
  useEffect,
  useRef,
  useState,
  useLayoutEffect,
  html,
  useMemo,
  useCallback
} from '../../ui';

import {
  closest as domClosest,
  matches as domMatches
} from 'min-dom';

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


/**
 * A component that renders the popup menus.
 *
 * @param {function} onClose
 * @param {function} position
 * @param {string} className
 * @param {Array} entries
 * @param {Array} headerEntries
 * @param {number} scale
 * @param {string} [title]
 * @param {boolean} [search]
 * @param {number} [width]
 */
export default function PopupMenuComponent(props) {
  const {
    onClose,
    onSelect,
    className,
    headerEntries,
    position,
    width,
    scale,
    search,
    entries: originalEntries
  } = props;

  const searchable = useMemo(() => {
    if (!isDefined(search)) {
      return false;
    }

    return originalEntries.length > 5;
  }, [ search, originalEntries ]);

  const inputRef = useRef();

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

  const [ entries, setEntries ] = useState(originalEntries);
  const [ selectedEntry, setSelectedEntry ] = useState(entries[0]);

  // filter entries on value change
  useEffect(() => {
    const filter = entry => {
      if (!value) {
        return true;
      }

      const search = [
        entry.name,
        entry.description || '',
        entry.label || '',
        entry.id || ''
      ]
        .join('---')
        .toLowerCase();

      return value
        .toLowerCase()
        .split(/\s/g)
        .every(term => search.includes(term));
    };

    if (!searchable) {
      return;
    }

    const entries = originalEntries.filter(filter);

    if (!entries.includes(selectedEntry) || !selectedEntry) {
      setSelectedEntry(entries[0]);
    }

    setEntries(entries);

  }, [ value, selectedEntry, setSelectedEntry, originalEntries, searchable ]);

  // register global <Escape> handler
  useEffect(() => {
    const handleKeyDown = event => {
      if (event.key === 'Escape') {
        event.preventDefault();

        return onClose();
      }
    };

    document.documentElement.addEventListener('keydown', handleKeyDown);

    return () => {
      document.documentElement.removeEventListener('keydown', handleKeyDown);
    };
  }, []);

  // focus input on initial mount
  useLayoutEffect(() => {
    inputRef.current && inputRef.current.focus();
  }, []);

  // handle keyboard seleciton
  const keyboardSelect = useCallback(direction => {
    const idx = entries.indexOf(selectedEntry);

    let nextIdx = idx + direction;

    if (nextIdx < 0) {
      nextIdx = entries.length - 1;
    }

    if (nextIdx >= entries.length) {
      nextIdx = 0;
    }

    setSelectedEntry(entries[nextIdx]);
  }, [ entries, selectedEntry, setSelectedEntry ]);

  const handleKeyDown = useCallback(event => {
    if (event.key === 'Enter' && selectedEntry) {
      return onSelect(event, selectedEntry);
    }

    if (event.key === 'Escape') {
      return onClose();
    }

    // ARROW_UP or SHIFT + TAB navigation
    if (event.key === 'ArrowUp' || (event.key === 'Tab' && event.shiftKey)) {
      keyboardSelect(-1);

      return event.preventDefault();
    }

    // ARROW_DOWN or TAB navigation
    if (event.key === 'ArrowDown' || event.key === 'Tab') {
      keyboardSelect(1);

      return event.preventDefault();
    }
  }, [ onSelect, onClose, selectedEntry, keyboardSelect ]);

  const handleKey = useCallback(event => {
    if (domMatches(event.target, 'input')) {
      setValue(() => event.target.value);
    }
  }, [ setValue ]);

  const displayHeader = props.title || headerEntries.length > 0;

  return html`
    <${PopupMenuWrapper}
      onClose=${ onClose }
      onKeyup=${ handleKey }
      onKeydown=${ handleKeyDown }
      className=${ className }
      position=${position}
      width=${ width }
      scale=${ scale }
    >
      ${ displayHeader && html`
        <div class="djs-popup-header">
          <h3 class="djs-popup-title">${ props.title }</h3>
          ${ headerEntries.map(entry => html`
            <span
              class=${ getHeaderClasses(entry, entry === selectedEntry) }
              onClick=${ event => onSelect(event, entry) }
              title=${ `Toggle ${entry.title}` }
              data-id=${ entry.id }
              onMouseEnter=${ () => setSelectedEntry(entry) }
              onMouseOut=${ () => setSelectedEntry(null) }
            >
              ${entry.imageUrl ? html`<img src=${ entry.imageUrl } />` : null}
              ${entry.label ? entry.label : null}
            </span>
          `) }<commit_msg>chore(popup-menu): simplify title<commit_after>import {
  useEffect,
  useRef,
  useState,
  useLayoutEffect,
  html,
  useMemo,
  useCallback
} from '../../ui';

import {
  closest as domClosest,
  matches as domMatches
} from 'min-dom';

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


/**
 * A component that renders the popup menus.
 *
 * @param {function} onClose
 * @param {function} position
 * @param {string} className
 * @param {Array} entries
 * @param {Array} headerEntries
 * @param {number} scale
 * @param {string} [title]
 * @param {boolean} [search]
 * @param {number} [width]
 */
export default function PopupMenuComponent(props) {
  const {
    onClose,
    onSelect,
    className,
    headerEntries,
    position,
    title,
    width,
    scale,
    search,
    entries: originalEntries
  } = props;

  const searchable = useMemo(() => {
    if (!isDefined(search)) {
      return false;
    }

    return originalEntries.length > 5;
  }, [ search, originalEntries ]);

  const inputRef = useRef();

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

  const [ entries, setEntries ] = useState(originalEntries);
  const [ selectedEntry, setSelectedEntry ] = useState(entries[0]);

  // filter entries on value change
  useEffect(() => {
    const filter = entry => {
      if (!value) {
        return true;
      }

      const search = [
        entry.name,
        entry.description || '',
        entry.label || '',
        entry.id || ''
      ]
        .join('---')
        .toLowerCase();

      return value
        .toLowerCase()
        .split(/\s/g)
        .every(term => search.includes(term));
    };

    if (!searchable) {
      return;
    }

    const entries = originalEntries.filter(filter);

    if (!entries.includes(selectedEntry) || !selectedEntry) {
      setSelectedEntry(entries[0]);
    }

    setEntries(entries);

  }, [ value, selectedEntry, setSelectedEntry, originalEntries, searchable ]);

  // register global <Escape> handler
  useEffect(() => {
    const handleKeyDown = event => {
      if (event.key === 'Escape') {
        event.preventDefault();

        return onClose();
      }
    };

    document.documentElement.addEventListener('keydown', handleKeyDown);

    return () => {
      document.documentElement.removeEventListener('keydown', handleKeyDown);
    };
  }, []);

  // focus input on initial mount
  useLayoutEffect(() => {
    inputRef.current && inputRef.current.focus();
  }, []);

  // handle keyboard seleciton
  const keyboardSelect = useCallback(direction => {
    const idx = entries.indexOf(selectedEntry);

    let nextIdx = idx + direction;

    if (nextIdx < 0) {
      nextIdx = entries.length - 1;
    }

    if (nextIdx >= entries.length) {
      nextIdx = 0;
    }

    setSelectedEntry(entries[nextIdx]);
  }, [ entries, selectedEntry, setSelectedEntry ]);

  const handleKeyDown = useCallback(event => {
    if (event.key === 'Enter' && selectedEntry) {
      return onSelect(event, selectedEntry);
    }

    if (event.key === 'Escape') {
      return onClose();
    }

    // ARROW_UP or SHIFT + TAB navigation
    if (event.key === 'ArrowUp' || (event.key === 'Tab' && event.shiftKey)) {
      keyboardSelect(-1);

      return event.preventDefault();
    }

    // ARROW_DOWN or TAB navigation
    if (event.key === 'ArrowDown' || event.key === 'Tab') {
      keyboardSelect(1);

      return event.preventDefault();
    }
  }, [ onSelect, onClose, selectedEntry, keyboardSelect ]);

  const handleKey = useCallback(event => {
    if (domMatches(event.target, 'input')) {
      setValue(() => event.target.value);
    }
  }, [ setValue ]);

  const displayHeader = useMemo(() => title || headerEntries.length > 0, [ title, headerEntries ]);

  return html`
    <${PopupMenuWrapper}
      onClose=${ onClose }
      onKeyup=${ handleKey }
      onKeydown=${ handleKeyDown }
      className=${ className }
      position=${position}
      width=${ width }
      scale=${ scale }
    >
      ${ displayHeader && html`
        <div class="djs-popup-header">
          <h3 class="djs-popup-title">${ title }</h3>
          ${ headerEntries.map(entry => html`
            <span
              class=${ getHeaderClasses(entry, entry === selectedEntry) }
              onClick=${ event => onSelect(event, entry) }
              title=${ `Toggle ${entry.title}` }
              data-id=${ entry.id }
              onMouseEnter=${ () => setSelectedEntry(entry) }
              onMouseOut=${ () => setSelectedEntry(null) }
            >
              ${entry.imageUrl ? html`<img src=${ entry.imageUrl } />` : null}
              ${entry.label ? entry.label : null}
            </span>
          `) }
{'fraction_non_alphanumeric': 0.14907819453274, 'fraction_numerical': 0.002331002331002331, 'mean_word_length': 2.497221193034457, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 58, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4930144', 'n_tokens_mistral': 2812, 'n_tokens_neox': 2766, 'n_words': 642}