Browse Source

Fix tagsInput event handling

Properly handle events for focus, blur, and change.
master
TheoryOfNekomata 11 months ago
parent
commit
ad8e62c499
5 changed files with 113 additions and 21 deletions
  1. +13
    -0
      categories/multichoice/react/src/components/TagInput/TagInput.css
  2. +78
    -3
      categories/multichoice/react/src/components/TagInput/index.tsx
  3. +5
    -12
      packages/react-utils/src/event.ts
  4. +16
    -5
      showcases/web-kitchensink-reactnext/src/pages/categories/option/index.tsx
  5. +1
    -1
      showcases/web-kitchensink-reactnext/src/pages/categories/presentation/index.tsx

+ 13
- 0
categories/multichoice/react/src/components/TagInput/TagInput.css View File

@@ -80,6 +80,10 @@
background-color: rgb(var(--color-positive) / 25%);
}

.tesseract-design-tag-input textarea + div > span:focus-within {
background-color: rgb(var(--color-secondary) / 25%);
}

.tesseract-design-tag-input textarea + div > span span {
pointer-events: none;
}
@@ -91,6 +95,15 @@
margin-left: 0.25rem;
}

.tesseract-design-tag-input textarea + div > span button:focus {
outline: none;
color: rgb(var(--color-secondary));
}

.tesseract-design-tag-input textarea + div > span button:focus:-moz-focusring {
display: none;
}

.tesseract-design-tag-input textarea + div > span button:hover {
color: rgb(var(--color-primary));
}

+ 78
- 3
categories/multichoice/react/src/components/TagInput/index.tsx View File

@@ -55,6 +55,7 @@ export interface TagInputProps extends Omit<React.HTMLProps<TagInputDerivedEleme
* Separators for splitting the input value into multiple tags.
*/
separator?: TagInputSeparator[],
editOnRemove?: boolean,
}

/**
@@ -78,6 +79,10 @@ export const TagInput = React.forwardRef<TagInputDerivedElement, TagInputProps>(
defaultValue,
disabled,
id: idProp,
onFocus,
onBlur,
editOnRemove = false,
placeholder,
...etcProps
},
forwardedRef,
@@ -119,7 +124,19 @@ export const TagInput = React.forwardRef<TagInputDerivedElement, TagInputProps>(
});
};

const handleBlur = () => {
const handleFocus: React.FocusEventHandler<HTMLTextAreaElement> = (e) => {
if (!clientSide) {
onFocus?.(e);
}
};

const handleBlur: React.FocusEventHandler<HTMLTextAreaElement> = (e) => {
if (!clientSide) {
onBlur?.(e);
}
};

const handleRemoveTag = () => {
if (!(typeof ref === 'object' && ref)) {
return;
}
@@ -128,10 +145,59 @@ export const TagInput = React.forwardRef<TagInputDerivedElement, TagInputProps>(
return;
}
setTimeout(() => {
delegateTriggerEvent('blur', input);
const sibling = input.nextElementSibling as HTMLDivElement;
const tagsInput = sibling.children[sibling.children.length - 1] as HTMLInputElement;
tagsInput.focus();
});
};

const handleInputBlur: React.FocusEventHandler<HTMLInputElement> = (e) => {
if (!(typeof ref === 'object' && ref)) {
return;
}
const { current: input } = ref;
if (!input) {
return;
}
onBlur?.({
...e,
target: input,
currentTarget: input,
});
};

const handleFocusCapture: React.FocusEventHandler<HTMLDivElement> = (e) => {
const { currentTarget } = e;
if (!clientSide) {
return;
}
const { activeElement } = window.document;
if (!activeElement) {
return;
}
const tagInputWrapper = currentTarget.children[1] as HTMLDivElement;
const tagInput = (
tagInputWrapper.children[tagInputWrapper.children.length - 1] as HTMLInputElement
);
if (activeElement !== tagInput) {
return;
}
if (!(typeof ref === 'object' && ref)) {
return;
}
const { current: input } = ref;
if (!input) {
return;
}
if (activeElement.tagName === 'INPUT') {
onFocus?.({
...e,
target: input,
currentTarget: input,
});
}
};

return (
<div
className={clsx(
@@ -155,18 +221,23 @@ export const TagInput = React.forwardRef<TagInputDerivedElement, TagInputProps>(
variant === 'alternate' && 'tag-input-alternate',
className,
)}
onFocusCapture={handleFocusCapture}
>
<textarea
{...etcProps}
placeholder={placeholder}
disabled={disabled}
ref={ref}
id={id}
aria-labelledby={labelId}
data-testid="input"
defaultValue={defaultValue}
onFocus={handleFocus}
onBlur={handleBlur}
style={{
height: clientSide ? undefined : 0,
}}
tabIndex={clientSide ? -1 : undefined}
className={clsx(
'bg-negative rounded-inherit peer block',
'focus:outline-0',
@@ -219,9 +290,12 @@ export const TagInput = React.forwardRef<TagInputDerivedElement, TagInputProps>(
input: 'peer bg-transparent',
tag: 'text-xs p-2 select-none',
}}
isEditOnRemove={editOnRemove}
placeHolder={placeholder}
disabled={disabled}
onBlur={handleInputBlur}
onChange={handleTagsInputChange}
onBlur={handleBlur}
onRemoved={handleRemoveTag}
separators={separator.map((s) => TAG_INPUT_SEPARATOR_MAP[s])}
/>
)}
@@ -322,4 +396,5 @@ TagInput.defaultProps = {
block: false,
hiddenLabel: false,
enhanced: false,
editOnRemove: false,
};

+ 5
- 12
packages/react-utils/src/event.ts View File

@@ -1,4 +1,8 @@
export const delegateTriggerEvent = <T extends HTMLElement>(eventName: string, target: T, value?: unknown) => {
export const delegateTriggerEvent = <T extends HTMLElement>(
eventName: string,
target: T,
value?: unknown,
) => {
if (typeof window === 'undefined') {
return;
}
@@ -29,15 +33,4 @@ export const delegateTriggerEvent = <T extends HTMLElement>(eventName: string, t
}
return;
}

if (eventName === 'blur') {
target.focus({
preventScroll: true,
});
setTimeout(() => {
target.blur();
});
}
const simulatedEvent = new Event(eventName, {bubbles: true});
target.dispatchEvent(simulatedEvent);
};

+ 16
- 5
showcases/web-kitchensink-reactnext/src/pages/categories/option/index.tsx View File

@@ -1493,11 +1493,19 @@ const OptionPage: NextPage<Props> = ({
<div>
<MultiChoice.TagInput
size="large"
label="MultilineTextInput"
label="not enhanced"
hint="Type anything here&hellip;"
indicator="A"
block
enhanced
onFocus={(e) => {
console.log('focus', e.currentTarget);
}}
onBlur={(e) => {
console.log('blur', e.currentTarget);
}}
onChange={(e) => {
console.log('change', e.currentTarget.value);
}}
/>
</div>
<div>
@@ -1509,12 +1517,15 @@ const OptionPage: NextPage<Props> = ({
indicator="A"
block
enhanced
separator={['\n', ',']}
separator={['newline', 'comma']}
onFocus={(e) => {
console.log('focus', e.currentTarget);
}}
onBlur={(e) => {
console.log(e.currentTarget);
console.log('blur', e.currentTarget);
}}
onChange={(e) => {
console.log(e.currentTarget.value);
console.log('change', e.currentTarget.value);
}}
/>
</div>


+ 1
- 1
showcases/web-kitchensink-reactnext/src/pages/categories/presentation/index.tsx View File

@@ -1,5 +1,5 @@
import { NextPage } from 'next';
import { DefaultLayout } from 'src/components/DefaultLayout';
import { DefaultLayout } from '@/components/DefaultLayout';

const PresentationPage: NextPage = () => {
return (


Loading…
Cancel
Save