@arif-un/react-mix-tag-input
Version:
A simple react component for inputting tags with a mix of text
1 lines • 12.2 kB
Source Map (JSON)
{"version":3,"sources":["../src/MixInput.tsx","../src/utils.ts","../src/TagExtension.ts","../src/Tag.tsx","../src/index.ts"],"sourcesContent":["import './MixInput.css'\n\nimport React, { type ForwardedRef, forwardRef, useEffect, useImperativeHandle, useRef } from 'react'\n\nimport { editorValueToMixInputValue, mixInputValueToEditorValue } from './utils'\nimport { EditorContent, useEditor } from '@tiptap/react'\nimport Document from '@tiptap/extension-document'\nimport Paragraph from '@tiptap/extension-paragraph'\nimport Text from '@tiptap/extension-text'\nimport Placeholder from '@tiptap/extension-placeholder'\nimport TagExtension from './TagExtension'\nimport { type MixInputProps, type MixInputRef, type MixInputValue } from './MixInputType'\n\nconst DEFAULT_TAG_ATTRS = {\n id: undefined,\n label: 'undefined',\n className: undefined,\n style: undefined,\n}\n\nconst MixInput = forwardRef((props: MixInputProps, ref?: ForwardedRef<MixInputRef>) => {\n const {\n onChange,\n value = [],\n placeholder,\n disabled = false,\n tagClassName = 'mi-tag',\n editorOptions,\n className,\n tagAttrs,\n tagView,\n immediatelyRender = true,\n ...restProps\n } = props\n\n const editorRef = useRef<HTMLDivElement>(null)\n const previousValueRef = useRef<string>('')\n\n const editor = useEditor({\n editable: !disabled,\n immediatelyRender,\n editorProps: {\n attributes: { class: `mix-input ${disabled ? 'mi-disabled' : ''} ${className || ''}` },\n },\n extensions: [\n Document,\n Paragraph.configure({\n HTMLAttributes: { class: 'mi-paragraph' },\n }),\n Text,\n Placeholder.configure({ placeholder }),\n TagExtension.configure({\n tagClassName,\n attrs: { ...DEFAULT_TAG_ATTRS, ...tagAttrs },\n tagView,\n }),\n ],\n onUpdate: ({ editor }) => {\n if (disabled) return\n onChange?.(editorValueToMixInputValue(editor?.getJSON()?.content || []))\n },\n ...editorOptions,\n })\n\n const insertContent = (content: MixInputValue | MixInputValue[] | MixInputValue[][]) => {\n if (disabled) return\n editor?.chain().focus().insertContent(content).run()\n }\n\n useEffect(() => {\n if (!editor) return\n\n let updatedValueFromParent = mixInputValueToEditorValue(value)\n if (updatedValueFromParent.length === 0) {\n updatedValueFromParent = [{ type: 'paragraph' }]\n }\n\n const currentValueStr = JSON.stringify(updatedValueFromParent)\n const editorContentStr = JSON.stringify(editor.getJSON().content)\n\n // Only update if content has actually changed\n if (currentValueStr !== editorContentStr && currentValueStr !== previousValueRef.current) {\n previousValueRef.current = currentValueStr\n\n // Use setTimeout to move the update out of React's rendering phase\n setTimeout(() => {\n if (editor && !editor.isDestroyed) {\n editor.commands.setContent(updatedValueFromParent)\n }\n }, 0)\n }\n }, [value, editor])\n\n useImperativeHandle(ref, () => ({\n element: editorRef.current,\n editor,\n insertContent,\n }))\n\n return (\n <EditorContent\n aria-disabled={disabled}\n editor={editor}\n innerRef={editorRef}\n {...(restProps as Omit<typeof restProps, 'ref'>)}\n />\n )\n})\n\nexport default MixInput\n","\nimport type { MixInputValue, MixInputValues, Tag } from './MixInputType'\nimport { JSONContent } from '@tiptap/core'\n\nfunction isTag(item: MixInputValue): item is Tag {\n return typeof item === 'object' && item.type === 'tag'\n}\n\nfunction createTagObj(item: JSONContent): Tag {\n return { type: 'tag', attrs: { ...item.attrs } }\n}\n\nexport function editorValueToMixInputValue(value: JSONContent[]) {\n const mixInputValues: MixInputValues = []\n value.forEach((line) => {\n if (line.type === 'paragraph') {\n mixInputValues.push([])\n line?.content?.forEach((item) => {\n if (item.type === 'text' && item.text) {\n mixInputValues.at(-1)?.push(item.text)\n }\n if (item.type === 'tag') {\n mixInputValues.at(-1)?.push(createTagObj(item))\n }\n })\n }\n })\n return mixInputValues\n}\n\nexport function mixInputValueToEditorValue(mixInputValues: MixInputValue[] | MixInputValue[][]) {\n const jsonContent: JSONContent[] = []\n mixInputValues.forEach((item, i) => {\n if (!Array.isArray(jsonContent[i])) {\n jsonContent.push({ type: 'paragraph', content: [] })\n }\n const lastItem = jsonContent.at(-1)\n if (typeof item === 'string' && lastItem?.content) {\n lastItem.content.push({\n type: 'text',\n text: item,\n })\n }\n if (!Array.isArray(item) && isTag(item) && lastItem?.content) {\n lastItem.content.push({\n type: 'tag',\n attrs: { ...item.attrs },\n })\n }\n if (Array.isArray(item)) {\n item.forEach((subItem) => {\n if (typeof subItem === 'string' && lastItem?.content) {\n lastItem.content.push({\n type: 'text',\n text: subItem,\n })\n }\n if (typeof subItem === 'object' && subItem.type === 'tag' && lastItem?.content) {\n lastItem.content.push({\n type: 'tag',\n attrs: { ...subItem.attrs },\n })\n }\n })\n }\n })\n return jsonContent\n}","import { type Attribute, mergeAttributes, Node } from \"@tiptap/core\";\nimport { ReactNodeViewRenderer } from \"@tiptap/react\";\nimport Tag from \"./Tag\";\n\nexport default Node.create({\n name: \"tag\",\n group: \"inline\",\n inline: true,\n atom: true,\n selectable: false,\n\n parseHTML() {\n return [\n {\n tag: `span[data-type=\"${this.name}\"]`,\n },\n ]\n },\n\n renderHTML({ HTMLAttributes }) {\n return [\"span\", mergeAttributes(HTMLAttributes)];\n },\n\n addNodeView() {\n return ReactNodeViewRenderer(Tag)\n },\n\n addAttributes() {\n const extraAttrs: Record<string, Attribute> = {}\n for (const key in this.options.attrs) {\n extraAttrs[key] = { default: this.options.attrs[key] }\n }\n return extraAttrs\n },\n})\n","import { type NodeViewProps, NodeViewWrapper } from '@tiptap/react'\nimport React from 'react'\n\nexport default function Tag(props: NodeViewProps) {\n const { tagView, tagClassName } = props.extension.options\n const { label, className, ...restAttrs } = props.node.attrs\n return (\n <>\n <NodeViewWrapper data-type=\"tag\" as=\"span\">\n {tagView ? (\n tagView(props)\n ) : (\n <span className={`${tagClassName || ''} ${className || ''}`} {...restAttrs}>\n {label}\n </span>\n )}\n </NodeViewWrapper>\n {'\\u200B'}\n </>\n )\n}\n","import MixInput from './MixInput'\nimport { editorValueToMixInputValue, mixInputValueToEditorValue } from './utils'\nimport type { MixInputProps, MixInputRef, MixInputValue, MixInputValues, Tag } from './MixInputType.d'\nimport { type Editor } from '@tiptap/core'\nexport default MixInput\nexport { editorValueToMixInputValue, mixInputValueToEditorValue }\n\nexport type {\n MixInputProps,\n MixInputRef,\n MixInputValue,\n MixInputValues,\n Tag,\n Editor\n}\n"],"mappings":"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","names":["React","forwardRef","useEffect","useImperativeHandle","useRef","isTag","item","createTagObj","editorValueToMixInputValue","value","mixInputValues","line","_a","_b","mixInputValueToEditorValue","jsonContent","i","lastItem","subItem","EditorContent","useEditor","Document","Paragraph","Text","Placeholder","mergeAttributes","Node","ReactNodeViewRenderer","NodeViewWrapper","React","Tag","props","tagView","tagClassName","label","className","restAttrs","TagExtension_default","Node","HTMLAttributes","mergeAttributes","ReactNodeViewRenderer","Tag","extraAttrs","key","DEFAULT_TAG_ATTRS","MixInput","forwardRef","props","ref","onChange","value","placeholder","disabled","tagClassName","editorOptions","className","tagAttrs","tagView","immediatelyRender","restProps","editorRef","useRef","previousValueRef","editor","useEditor","Document","Paragraph","Text","Placeholder","TagExtension_default","_a","editorValueToMixInputValue","insertContent","content","useEffect","updatedValueFromParent","mixInputValueToEditorValue","currentValueStr","editorContentStr","useImperativeHandle","React","EditorContent","MixInput_default","src_default","MixInput_default"]}