Текстовый Инпут с возможностью выделять отдельные слова

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Как, вроде бы, простая задача превратилась в головную боль

Для нетерпеливых
Для нетерпеливых

Первое, что приходит в голову, это то, что кто-то, скорее всего, уже сделал такой компонент, и нужно адаптировать готовое решение. При быстром поиске нашел один пример, сделанный на textarea, который и работал как обычный textarea, но с возможностью выделять слова. Плюс ответы на stackoverflow, дающие понять, что это не такая уж и тривиальная задача.

Очевидное решение – это сделать все обычным input’ом с абсолютно позиционированным div’ом на заднем фоне. Скрыть текст самого поля, оставив только каретку, и сделать видимым только содержимое заднего блока. Так я уже добился того, что у меня был полностью стилизуемый текст.

Стили включать не буду, так как это займет слишком много места. Принцип прост: задать одинаковые размеры, отступы и шрифты для div’а и input’а.

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

const onChange = (e) => setValue(e.target.value);
const format = (text) => // formatted jsx
<div className="container">
  <div className="text">{format(value)}</div>
  <input type="text" className="field" value={value} onChange={onChange} />
</div>

Первая проблема

При введении длинной строки поле начинает скроллиться, а текст в блоке стоит на своем месте.
При введении длинной строки поле начинает скроллиться, а текст в блоке стоит на своем месте.


После долгих хождений вокруг да около я с помощью коллеги пришел к тому, что можно брать значение скролла поля и ставить его заднему блоку.

// ...
const textBlockRef = useRef(null);
const [scrollValue, setScrollValue] = useState(0);
// ...
const handleScroll = (e) => setScrollValue(e.currentTarget.scrollLeft);

useEffect(() => {
  if (textBlockRef.current) {
    textBlockRef.current.scrollLeft = scrollValue;
  }
}, [scrollValue]);
<!-- ... -->
<div className="text" ref={textBlockRef}>{format(value)}</div>
<input
  type="text"
  className="field"
  value={value}
  onChange={onChange}
  onScroll={handleScroll}
 />
<!-- ... -->


Получился, вроде бы, хороший результат. Компонент работал, как и было задумано.

Вторая проблема

Вторая проблема заключается в том, что мне, оказывается, не удалось полностью избавиться от первой.

В браузере Safari не работает свойство onScroll у простого input’а.

Начал было я сомневаться в правильности своего подхода, как выяснилось, что safari поддерживает onScroll у textarea. Это решило проблему. Оставалось только стилизовать textarea, чтобы он выглядел и вел себя как простой input[type=”text”], и дело с концом.

<!-- ... -->
<textarea
  className="field"
  ref={ref}
  value={value}
  onChange={onChange}
  onScroll={handleScroll}
  rows={1}
/>
<!-- ... -->

Третья проблема (более очевидная)

При нажатии на Enter, в textarea происходил перенос строки. Это решается просто - нужно отловить нажатие и отменить его, плюс, если инпут находится в форме, сделать сабмит.

// ...
  const submitHandler = (e) => {
    if (e.keyCode === 13 || e.key === "Enter" || e.which === 13) {
      if (!e.repeat && e.target.form) {
        e.target.form.submit();
      }
      e.preventDefault();
    }
  };
// ...
<!-- ... -->
<textarea
  className="field"
  ref={ref}
  value={value}
  onChange={onChange}
  onScroll={handleScroll}
  onKeyDown={submitHandler}
  rows={1}
/>
<!-- ... -->

Так, путем нехитрых манипуляций, в течении дня, мне все-таки удалось реализовать желаемый компонент.

Финальный результат можно посмотреть на CodeSandbox.


Возможно, в этом решении найдутся еще проблемы, буду рад, если кто-то их найдет и озвучит в комментариях.

Источник: https://habr.com/ru/post/721322/


Интересные статьи

Интересные статьи

Сегодня я хочу поделиться рассказать вам о том, что такое NFT на основе своей работы в качестве QA на основе платформ компании Dapper. Примеры, для наглядности, буду приводить на основе печенек Oreo и...
Наверняка каждый радиолюбитель сталкивался с необходимостью самостоятельно изготавливать печатную плату, ведь не всегда целесообразно оплачивать и ждать заказ из Китая. И наверняка держа в руках утю...
Всем привет, читатели Хабра! В этой статье я хочу простыми словами рассказать про асинхронность и параллельность в Dart/Flutter. Многие новички сталкиваются с непонимаем того, что это и когда что испо...
Любите Захер? Я люблю. Но испытываю на этот счет смешанные чувства. Если бы Франц Захер запускал производство своего торта в сегодняшней России, добрые люди посоветовали бы ему подумать над сменой наз...
У некоторых бизнес-тренеров в области е-коммерса и консультантов по увеличению интернет-продаж на многие вопросы часто можно слышать универсальную отмазку — «надо тестировать» или другую (чтобы не...