diff --git a/package.json b/package.json index 6a8b51f..21b6180 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "sinon": "^1.17.6" }, "peerDependencies": { - "react": "^0.14.7 || ^15.0.0" + "react": "^0.14.7 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" }, "dependencies": { "babel-runtime": "^6.11.6" diff --git a/src/index.d.ts b/src/index.d.ts new file mode 100644 index 0000000..dc68b6d --- /dev/null +++ b/src/index.d.ts @@ -0,0 +1 @@ +export declare module "react-taggy" \ No newline at end of file diff --git a/src/index.js b/src/index.js index 6b388de..68692e5 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,14 @@ import React from 'react' // Define functional component. Destructure the props. -const Taggy = ({ text = '', spans = [], ents = []}) => { +const Taggy = ({ + text = '', + spans = [], + ents = [], + onClick = (event, tag, elemIndex) => {}, + onMouseOver = (event, tag, elemIndex) => {}, + onHighlight = (event, text, spanIndex, start, end) => {}, +}) => { // Find the correct color of the given entity type. If the given entity is not found, set the color to grey. const findRed = (type) => { @@ -29,9 +36,21 @@ const Taggy = ({ text = '', spans = [], ents = []}) => { return 220 } + const highlightCallback = (e, spanText, i) => { + // Start and end are relative to the current element, not the whole text + const start = window.getSelection().anchorOffset + const end = window.getSelection().focusOffset + const text = spanText.substring(start, end) + onHighlight(e, text, i, start, end) + } + + // Initialize an empty array that will hold the text and entities let jsx = [] + // Make sure spans are ordered by they start index + spans.sort((a,b) => (a.start > b.start) ? 1 : ((b.start > a.start) ? -1 : 0)) + // METHOD 1 - STRING if (typeof text === 'string') { // Initialize an empty array. The contents of 'elements' will eventually get pushed to the 'jsx' array, and will be converted to jsx markup in the process. @@ -39,19 +58,17 @@ const Taggy = ({ text = '', spans = [], ents = []}) => { // Keep track of location in the string of text let offset = 0 // Loop through the spans, using the span data to construct the 'elements' array - spans.forEach(({ type, start, end }) => { + spans.forEach((span) => { // Create a string of text that does not contain any entities - const fragment = text.slice(offset, start) + const fragment = text.slice(offset, span.start) // Create an entity - const entity = text.slice(start, end) + const entity = text.slice(span.start, span.end) // Push the both of them to the elements array elements.push(fragment) - elements.push({ - token: entity, - type: type.toLowerCase() - }) + span.token = entity + elements.push(span) // Update our position within the string of text - offset = end + offset = span.end }) // After pushing all of the entities to the 'elements' array, push the remaining text to the 'elements' array. Elements should now consist of strings and objects/entities. elements.push(text.slice(offset, text.length)) @@ -78,13 +95,18 @@ const Taggy = ({ text = '', spans = [], ents = []}) => { // Filter out the consecutive entities that were marked as duplicates elements = elements.filter(val => !!val) // Loop through our 'elements' array. Push strings directly to the 'jsx' array. Convert entity objects to jsx markup, then push to the 'jsx' array. - elements.forEach(t => { + elements.forEach((t, i) => { if (typeof t === 'string') { - jsx.push(t) + jsx.push( {highlightCallback(e, t, i)}} + onDoubleClick={(e) => {highlightCallback(e, t, i)}} + >{t}) } else { jsx.push( onClick(e, t, i)} + onMouseOver={(e) => onMouseOver(e, t, i)} style={{ padding: '0.25em 0.35em', margin: '0px 0.25em', @@ -113,7 +135,6 @@ const Taggy = ({ text = '', spans = [], ents = []}) => { lineHeight: '1', padding: '0.35em', borderRadius: '0.35em', - textTransform: 'uppercase', display: 'inline-block', verticalAlign: 'middle', margin: '0px 0px 0.1rem 0.5rem', @@ -138,11 +159,11 @@ const Taggy = ({ text = '', spans = [], ents = []}) => { let tokens = text // Loop through the 'spans' array. Use the span data to update our 'tokens' array with entities for (let s = 0; s < spans.length; s++) { - tokens[spans[s].index] = { - token: tokens[spans[s].index], - type: spans[s].type.toLowerCase() - } + tokens[spans[s].index] = spans[s] + tokens[spans[s].index].token = tokens[spans[s].index] + tokens[spans[s].index].type = spans[s].type } + // Loop through the tokens array, looking for multi-word entities for (let t = 0; t < tokens.length; t++) { // Check if we've stopped at an entity @@ -171,13 +192,18 @@ const Taggy = ({ text = '', spans = [], ents = []}) => { return t }) // Loop through our 'tokens' array. Push strings directly to the 'jsx' array. Convert entity objects to jsx markup, then push to the 'jsx' array. - tokensWithSpaces.forEach(t => { + tokensWithSpaces.forEach((t, i) => { if (typeof t === 'string') { - jsx.push(t) + jsx.push( {highlightCallback(e, t, i)}} + onDoubleClick={(e) => {highlightCallback(e, t, i)}} + >{t}) } else { jsx.push( onClick(e, t, i)} + onMouseOver={(e) => onMouseOver(e, t, i)} style={{ padding: '0.25em 0.35em', margin: '0px 0.25em', @@ -206,7 +232,6 @@ const Taggy = ({ text = '', spans = [], ents = []}) => { lineHeight: '1', padding: '0.35em', borderRadius: '0.35em', - textTransform: 'uppercase', display: 'inline-block', verticalAlign: 'middle', margin: '0px 0px 0.1rem 0.5rem', @@ -227,7 +252,7 @@ const Taggy = ({ text = '', spans = [], ents = []}) => { // Return the markup return ( -
+
{jsx.map((j, i) => ( {j} ))}