+ function diff(source, target, ins, del, sub) {
+ /** Create a list of edits to turn source into target
+
+ ins, del, and sub are as passed to costMatrix.
+ */
+ return editPath(costMatrix(source, target, ins, del, sub), target);
+ }
+
+ function patchArray(diff, source) {
+ for (var i = 0; i < diff.length; ++i) {
+ var edit = diff[i];
+ source.splice(edit[1], edit[0], edit[2]);
+ }
+ return source;
+ }
+
+ function patchString(diff, source) {
+ for (var i = 0; i < diff.length; ++i) {
+ var edit = diff[i];
+ var head = source.slice(0, edit[1]);
+ var tail = source.slice(edit[1] + edit[0]);
+ source = head + edit[2] + tail;
+ }
+ return source;
+ }
+
+ function patch(diff, source) {
+ /** Apply a list of edits to source */
+ var patcher = Array.isArray(source) ? patchArray : patchString;
+ return patcher(diff, source);
+ }
+
+ // Matches if a string contains combining characters or astral
+ // codepoints (technically, the first half surrogate of an astral
+ // codepoint).
+ var MULTI = /[\u0300-\u036F\u1DC0-\u1DFF\u20D0-\u20FF\uD800-\uDBFF\uFE20-\uFE2F]/;
+
+ // Match an entire (potentially astral) codepoint and any
+ // combining characters following it.
+ var GLYPH = /[\0-\u02FF\u0370-\u1DBF\u1E00-\u20CF\u2100-\uD7FF\uD800-\uFE1F\uFE30-\uFFFF][\u0300-\u036F\u1DC0-\u1DFF\u20D0-\u20FF\uDC00-\uDFFF\uFE20-\uFE2F]*/g;
+
+ function diffLerpAstral(source, target, amount) {
+ // This split is not perfect for all languages, but at least
+ // it won't create invalid surrogate pairs or orphaned
+ // combining characters.
+ var sourceGlyphs = source.match(GLYPH) || [];
+ var targetGlyphs = target.match(GLYPH) || [];
+ var edits = diff(targetGlyphs, sourceGlyphs, 2, 2, 3);
+ // The edit path works from the string end, forwards, because
+ // that's how Levenshtein edits work. To match LTR reading
+ // direction (and the behavior of fastLerp), swap the strings
+ // and invert the parameter when editing.
+ var partial = edits.slice(0, Math.round((1 - amount) * edits.length));
+ return patchArray(partial, targetGlyphs).join("");
+ }
+
+ function diffLerpBasic(source, target, amount) {
+ var edits = diff(target, source, 2, 2, 3);
+ // The edit path works from the string end, forwards, because
+ // that's how Levenshtein edits work. To match LTR reading
+ // direction (and the behavior of fastLerp), swap the strings
+ // and invert the parameter when editing.
+ var partial = edits.slice(0, Math.round((1 - amount) * edits.length));
+ return patchString(partial, target);
+ }
+
+ function diffLerp(source, target, amount) {
+ /** Interpolate between two strings using edit operations
+
+ This interpolation algorithm applys a partial edit of one
+ string into the other. This produces nice looking results,
+ but can take a significant amount of time and memory to
+ compute the edits. It is not recommended for strings
+ longer than a few hundred characters.
+ */
+
+ if (source.match(MULTI) || target.match(MULTI))
+ return diffLerpAstral(source, target, amount);
+ else
+ return diffLerpBasic(source, target, amount);
+ }
+
+ var NUMBERS = /(-?\d{1,20}(?:\.\d{1,20})?)/g;
+
+ function areNumericTwins(source, target) {
+ /** Check if a and b differ only in numerals */
+ return source.replace(NUMBERS, "0") === target.replace(NUMBERS, "0");
+ }
+
+ function nlerp(source, target, amount) {
+ return source + (target - source) * amount;
+ }
+
+ function numericLerp(source, target, amount) {
+ /** Interpolate numerically between strings containing numbers
+
+ Numbers may have a leading "-" and a single "." to mark
+ the decimal point, but something must be after the ".".
+ No other floating point syntax (e.g. 1e6) is supported.
+ They are treated as fixed-point values, with the point's
+ position itself interpolating.
+
+ For example, numericLerp("0.0", "100".0, 0.123) === "12.3"
+ because the "." in "0.0" is interpreted as a decimal
+ point. But numericLerp("0.", "100.", 0.123) === "12."
+ because the strings are interpreted as integers followed
+ by a full stop.
+
+ Calling this functions on strings that differ in more than
+ numerals gives undefined results.
+ */
+
+ var targetParts = target.split(NUMBERS);
+ var match;
+ var i = 1;
+ while ((match = NUMBERS.exec(source))) {
+ var sourcePart = match[0];
+ var targetPart = targetParts[i];
+ var part = nlerp(+sourcePart, +targetPart, amount);
+ var sourcePoint = sourcePart.indexOf(".");
+ var targetPoint = targetPart.indexOf(".");
+ var point = Math.round(nlerp(
+ sourcePoint >= 0 ? (sourcePart.length - 1) - sourcePoint : 0,
+ targetPoint >= 0 ? (targetPart.length - 1) - targetPoint : 0,
+ amount));
+ targetParts[i] = part.toFixed(point);
+ i += 2;