flowtoken
Version:

133 lines (132 loc) • 5.96 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const react_1 = __importStar(require("react"));
const TokenizedText = ({ input, sep, animation, animationDuration, animationTimingFunction, animationIterationCount }) => {
// Early return for no animation case
if (animation === 'none' || !animation) {
return react_1.default.createElement(react_1.default.Fragment, null, input);
}
// Track previous input to detect changes
const prevInputRef = (0, react_1.useRef)('');
// Track tokens with their source for proper keying in diff mode
const tokensWithSources = (0, react_1.useRef)([]);
// For detecting and handling duplicated content
const fullTextRef = (0, react_1.useRef)('');
const tokens = react_1.default.useMemo(() => {
if (react_1.default.isValidElement(input))
return [input];
if (typeof input !== 'string')
return null;
// For diff mode, we need to handle things differently
if (sep === 'diff') {
// If this is the first render or we've gone backward, reset everything
if (!prevInputRef.current || input.length < prevInputRef.current.length) {
tokensWithSources.current = [];
fullTextRef.current = '';
}
// Only process input if it's different from previous
if (input !== prevInputRef.current) {
// Find the true unique content by comparing with our tracked full text
// This handles cases where the input contains duplicates
// First check if we're just seeing the same content repeated
if (input.includes(fullTextRef.current)) {
const uniqueNewContent = input.slice(fullTextRef.current.length);
// Only add if there's actual new content
if (uniqueNewContent.length > 0) {
tokensWithSources.current.push({
text: uniqueNewContent,
source: tokensWithSources.current.length
});
// Update our full text tracking
fullTextRef.current = input;
}
}
else {
// Handle case when input completely changes
// Just take the whole thing as a new token
tokensWithSources.current = [{
text: input,
source: 0
}];
fullTextRef.current = input;
}
}
// Return the tokensWithSources directly
return tokensWithSources.current;
}
// Original word/char splitting logic
let splitRegex;
if (sep === 'word') {
splitRegex = /(\s+)/;
}
else if (sep === 'char') {
splitRegex = /(.)/;
}
else {
throw new Error('Invalid separator: must be "word", "char", or "diff"');
}
return input.split(splitRegex).filter(token => token.length > 0);
}, [input, sep]);
// Update previous input after processing
(0, react_1.useEffect)(() => {
if (typeof input === 'string') {
prevInputRef.current = input;
}
}, [input]);
// Helper function to check if token is a TokenWithSource type
const isTokenWithSource = (token) => {
return token !== null && typeof token === 'object' && 'text' in token && 'source' in token;
};
// Apply animation style only once instead of creating it repeatedly
const animationStyle = react_1.default.useMemo(() => ({
animationName: animation,
animationDuration,
animationTimingFunction,
animationIterationCount,
whiteSpace: 'pre-wrap',
display: 'inline-block',
}), [animation, animationDuration, animationTimingFunction, animationIterationCount]);
return (react_1.default.createElement(react_1.default.Fragment, null, tokens === null || tokens === void 0 ? void 0 : tokens.map((token, index) => {
// Determine the key and text based on token type
let key = index;
let text = '';
if (isTokenWithSource(token)) {
key = token.source;
text = token.text;
}
else if (typeof token === 'string') {
key = index;
text = token;
}
else if (react_1.default.isValidElement(token)) {
key = index;
text = '';
return react_1.default.cloneElement(token, { key });
}
return (react_1.default.createElement("span", { key: key, style: animationStyle }, text));
})));
};
exports.default = react_1.default.memo(TokenizedText); // Memoize the entire component to prevent unnecessary rerenders