1 /* eslint-disable jsx-a11y/no-static-element-interactions */
2 import type { KeyboardEventHandler } from 'react';
3 import { useRef, useState } from 'react';
5 import { CircleLoader } from '@proton/atoms';
6 import clsx from '@proton/utils/clsx';
8 import type { ListFieldValue } from './ListField';
10 export type ListFieldItemProps<T> = ListFieldValue<T> & {
13 onChange: (value: string) => void;
14 onMoveLeft: () => void;
15 onMoveRight: () => void;
16 renderValue: (value: T) => string;
19 const getCaretPosition = (el: HTMLElement): number => {
20 const selection = getSelection();
23 const range = selection.getRangeAt(0);
24 const clonedRange = range.cloneRange();
25 clonedRange.selectNodeContents(el);
26 clonedRange.setEnd(range.endContainer, range.endOffset);
28 return clonedRange.toString().length;
34 export const ListFieldItem = <T,>({
43 }: ListFieldItemProps<T>) => {
44 const ref = useRef<HTMLSpanElement>(null);
45 const [editing, setEditing] = useState(false);
47 const handleBlur = () => {
49 const update = ref.current?.innerText?.trim() ?? '';
50 if (update !== value) onChange(update);
53 const handleKeyDown: KeyboardEventHandler<HTMLSpanElement> = (evt) => {
54 if (!ref.current) return;
56 const el = ref.current;
57 const { innerText = '' } = el;
67 /** trigger move left if we've reached the start
68 * of the contenteditable's inner text */
69 if (getCaretPosition(el) === 0) {
76 /** trigger move right if we've reached the end
77 * of the contenteditable's inner text */
78 if (getCaretPosition(el) >= (innerText.length ?? 0)) {
89 'pass-field-text-group--item flex flex-nowrap flex-row max-w-full overflow-hidden stop-propagation border rounded',
90 error && 'pass-field-text-group--item:error',
91 loading && 'pass-field-text-group--item:loading'
95 className="pill-remove inline-flex flex-nowrap items-center px-2 py-1 max-w-full gap-2"
102 onFocus={() => setEditing(true)}
103 onClick={(e) => e.stopPropagation()}
105 onKeyDown={handleKeyDown}
108 className={clsx(!editing && 'text-ellipsis')}
109 suppressContentEditableWarning
113 {loading && <CircleLoader size="small" className="shrink-0" />}