diff --git a/README.md b/README.md index cd5c317..98ec854 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,20 @@ # [Splitting.js](https://splitting.js.org) +### Options + +- `rtl` (boolean, default: `false`) + When `true`, **each word** is left intact in a *single* `.char` span—rather than splitting into letters—so that Persian/Arabic ligatures remain connected. + +#### Usage + +```js +Splitting({ + target: ".my-text", + by: "words chars", + whitespace: false, + rtl: true // ← enable full-word spans for RTL scripts +}); + ### _CSS Vars for split words, chars & more!_ ![The current build status based on whether tests are passing](https://api.travis-ci.org/shshaw/Splitting.svg?branch=1.0.0) diff --git a/dist/splitting-lite.js b/dist/splitting-lite.js index a03b469..83b4b28 100644 --- a/dist/splitting-lite.js +++ b/dist/splitting-lite.js @@ -1,383 +1,405 @@ (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : - typeof define === 'function' && define.amd ? define(factory) : - (global.Splitting = factory()); -}(this, (function () { 'use strict'; - -var root = document; -var createText = root.createTextNode.bind(root); - -/** - * # setProperty - * Apply a CSS var - * @param {HTMLElement} el - * @param {string} varName - * @param {string|number} value - */ -function setProperty(el, varName, value) { - el.style.setProperty(varName, value); -} - -/** - * - * @param {!HTMLElement} el - * @param {!HTMLElement} child - */ -function appendChild(el, child) { - return el.appendChild(child); -} - -/** - * - * @param {!HTMLElement} parent - * @param {string} key - * @param {string} text - * @param {boolean} whitespace - */ -function createElement(parent, key, text, whitespace) { - var el = root.createElement('span'); - key && (el.className = key); - if (text) { - !whitespace && el.setAttribute("data-" + key, text); - el.textContent = text; - } - return (parent && appendChild(parent, el)) || el; -} - -/** - * - * @param {!HTMLElement} el - * @param {string} key - */ -function getData(el, key) { - return el.getAttribute("data-" + key) -} - -/** - * - * @param {import('../types').Target} e - * @param {!HTMLElement} parent - * @returns {!Array} - */ -function $(e, parent) { - return !e || e.length == 0 - ? // null or empty string returns empty array - [] - : e.nodeName - ? // a single element is wrapped in an array - [e] - : // selector and NodeList are converted to Element[] - [].slice.call(e[0].nodeName ? e : (parent || root).querySelectorAll(e)); -} - -/** - * Creates and fills an array with the value provided - * @param {number} len - * @param {() => T} valueProvider - * @return {T} - * @template T - */ - - -/** - * A for loop wrapper used to reduce js minified size. - * @param {!Array} items - * @param {function(T):void} consumer - * @template T - */ -function each(items, consumer) { - items && items.some(consumer); -} - -/** - * @param {T} obj - * @return {function(string):*} - * @template T - */ -function selectFrom(obj) { - return function (key) { - return obj[key]; + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global.Splitting = factory()); +}(this, (function () { + 'use strict'; + + var root = document; + var createText = root.createTextNode.bind(root); + + /** + * # setProperty + * Apply a CSS var + * @param {HTMLElement} el + * @param {string} varName + * @param {string|number} value + */ + function setProperty(el, varName, value) { + el.style.setProperty(varName, value); } -} - -/** - * # Splitting.index - * Index split elements and add them to a Splitting instance. - * - * @param {HTMLElement} element - * @param {string} key - * @param {!Array | !Array>} items - */ -function index(element, key, items) { - var prefix = '--' + key; - var cssVar = prefix + "-index"; - - each(items, function (items, i) { - if (Array.isArray(items)) { - each(items, function(item) { - setProperty(item, cssVar, i); + + /** + * + * @param {!HTMLElement} el + * @param {!HTMLElement} child + */ + function appendChild(el, child) { + return el.appendChild(child); + } + + /** + * + * @param {!HTMLElement} parent + * @param {string} key + * @param {string} text + * @param {boolean} whitespace + */ + function createElement(parent, key, text, whitespace) { + var el = root.createElement('span'); + key && (el.className = key); + if (text) { + !whitespace && el.setAttribute("data-" + key, text); + el.textContent = text; + } + return (parent && appendChild(parent, el)) || el; + } + + /** + * + * @param {!HTMLElement} el + * @param {string} key + */ + function getData(el, key) { + return el.getAttribute("data-" + key) + } + + /** + * + * @param {import('../types').Target} e + * @param {!HTMLElement} parent + * @returns {!Array} + */ + function $(e, parent) { + return !e || e.length == 0 + ? // null or empty string returns empty array + [] + : e.nodeName + ? // a single element is wrapped in an array + [e] + : // selector and NodeList are converted to Element[] + [].slice.call(e[0].nodeName ? e : (parent || root).querySelectorAll(e)); + } + + /** + * Creates and fills an array with the value provided + * @param {number} len + * @param {() => T} valueProvider + * @return {T} + * @template T + */ + + + /** + * A for loop wrapper used to reduce js minified size. + * @param {!Array} items + * @param {function(T):void} consumer + * @template T + */ + function each(items, consumer) { + items && items.some(consumer); + } + + /** + * @param {T} obj + * @return {function(string):*} + * @template T + */ + function selectFrom(obj) { + return function (key) { + return obj[key]; + } + } + + /** + * # Splitting.index + * Index split elements and add them to a Splitting instance. + * + * @param {HTMLElement} element + * @param {string} key + * @param {!Array | !Array>} items + */ + function index(element, key, items) { + var prefix = '--' + key; + var cssVar = prefix + "-index"; + + each(items, function (items, i) { + if (Array.isArray(items)) { + each(items, function (item) { + setProperty(item, cssVar, i); + }); + } else { + setProperty(items, cssVar, i); + } + }); + + setProperty(element, prefix + "-total", items.length); + } + + /** + * @type {Record} + */ + var plugins = {}; + + /** + * @param {string} by + * @param {string} parent + * @param {!Array} deps + * @return {!Array} + */ + function resolvePlugins(by, parent, deps) { + // skip if already visited this dependency + var index = deps.indexOf(by); + if (index == -1) { + // if new to dependency array, add to the beginning + deps.unshift(by); + + // recursively call this function for all dependencies + var plugin = plugins[by]; + if (!plugin) { + throw new Error("plugin not loaded: " + by); + } + each(plugin.depends, function (p) { + resolvePlugins(p, by, deps); }); } else { - setProperty(items, cssVar, i); + // if this dependency was added already move to the left of + // the parent dependency so it gets loaded in order + var indexOfParent = deps.indexOf(parent); + deps.splice(index, 1); + deps.splice(indexOfParent, 0, by); } - }); - - setProperty(element, prefix + "-total", items.length); -} - -/** - * @type {Record} - */ -var plugins = {}; - -/** - * @param {string} by - * @param {string} parent - * @param {!Array} deps - * @return {!Array} - */ -function resolvePlugins(by, parent, deps) { - // skip if already visited this dependency - var index = deps.indexOf(by); - if (index == -1) { - // if new to dependency array, add to the beginning - deps.unshift(by); - - // recursively call this function for all dependencies - var plugin = plugins[by]; - if (!plugin) { - throw new Error("plugin not loaded: " + by); + return deps; + } + + /** + * Internal utility for creating plugins... essentially to reduce + * the size of the library + * @param {string} by + * @param {string} key + * @param {string[]} depends + * @param {Function} split + * @returns {import('./types').ISplittingPlugin} + */ + function createPlugin(by, depends, key, split) { + return { + by: by, + depends: depends, + key: key, + split: split } - each(plugin.depends, function(p) { - resolvePlugins(p, by, deps); - }); - } else { - // if this dependency was added already move to the left of - // the parent dependency so it gets loaded in order - var indexOfParent = deps.indexOf(parent); - deps.splice(index, 1); - deps.splice(indexOfParent, 0, by); } - return deps; -} - -/** - * Internal utility for creating plugins... essentially to reduce - * the size of the library - * @param {string} by - * @param {string} key - * @param {string[]} depends - * @param {Function} split - * @returns {import('./types').ISplittingPlugin} - */ -function createPlugin(by, depends, key, split) { - return { - by: by, - depends: depends, - key: key, - split: split + + /** + * + * @param {string} by + * @returns {import('./types').ISplittingPlugin[]} + */ + function resolve(by) { + return resolvePlugins(by, 0, []).map(selectFrom(plugins)); } -} - -/** - * - * @param {string} by - * @returns {import('./types').ISplittingPlugin[]} - */ -function resolve(by) { - return resolvePlugins(by, 0, []).map(selectFrom(plugins)); -} - -/** - * Adds a new plugin to splitting - * @param {import('./types').ISplittingPlugin} opts - */ -function add(opts) { - plugins[opts.by] = opts; -} - -/** - * # Splitting.split - * Split an element's textContent into individual elements - * @param {!HTMLElement} el Element to split - * @param {string} key - * @param {string} splitOn - * @param {boolean} includePrevious - * @param {boolean} preserveWhitespace - * @return {!Array} - */ -function splitText(el, key, splitOn, includePrevious, preserveWhitespace) { - // Combine any strange text nodes or empty whitespace. - el.normalize(); - - // Use fragment to prevent unnecessary DOM thrashing. - var elements = []; - var F = document.createDocumentFragment(); - - if (includePrevious) { - elements.push(el.previousSibling); + + /** + * Adds a new plugin to splitting + * @param {import('./types').ISplittingPlugin} opts + */ + function add(opts) { + plugins[opts.by] = opts; } - var allElements = []; - $(el.childNodes).some(function(next) { - if (next.tagName && !next.hasChildNodes()) { - // keep elements without child nodes (no text and no children) - allElements.push(next); - return; - } - // Recursively run through child nodes - if (next.childNodes && next.childNodes.length) { - allElements.push(next); - elements.push.apply(elements, splitText(next, key, splitOn, includePrevious, preserveWhitespace)); - return; + /** + * # Splitting.split + * Split an element's textContent into individual elements + * @param {!HTMLElement} el Element to split + * @param {string} key + * @param {string} splitOn + * @param {boolean} includePrevious + * @param {boolean} preserveWhitespace + * @return {!Array} + */ + function splitText(el, key, splitOn, includePrevious, preserveWhitespace) { + // Combine any strange text nodes or empty whitespace. + el.normalize(); + + // Use fragment to prevent unnecessary DOM thrashing. + var elements = []; + var F = document.createDocumentFragment(); + + if (includePrevious) { + elements.push(el.previousSibling); } - // Get the text to split, trimming out the whitespace - /** @type {string} */ - var wholeText = next.wholeText || ''; - var contents = wholeText.trim(); - - // If there's no text left after trimming whitespace, continue the loop - if (contents.length) { - // insert leading space if there was one - if (wholeText[0] === ' ') { - allElements.push(createText(' ')); + var allElements = []; + $(el.childNodes).some(function (next) { + if (next.tagName && !next.hasChildNodes()) { + // keep elements without child nodes (no text and no children) + allElements.push(next); + return; + } + // Recursively run through child nodes + if (next.childNodes && next.childNodes.length) { + allElements.push(next); + elements.push.apply(elements, splitText(next, key, splitOn, includePrevious, preserveWhitespace)); + return; } - // Concatenate the split text children back into the full array - var useSegmenter = splitOn === "" && typeof Intl.Segmenter === "function"; - each(useSegmenter ? Array.from(new Intl.Segmenter().segment(contents)).map(function(x){return x.segment}) : contents.split(splitOn), function (splitText, i) { - if (i && preserveWhitespace) { - allElements.push(createElement(F, "whitespace", " ", preserveWhitespace)); + + // Get the text to split, trimming out the whitespace + /** @type {string} */ + var wholeText = next.wholeText || ''; + var contents = wholeText.trim(); + + // If there's no text left after trimming whitespace, continue the loop + if (contents.length) { + // insert leading space if there was one + if (wholeText[0] === ' ') { + allElements.push(createText(' ')); + } + // Concatenate the split text children back into the full array + var useSegmenter = splitOn === "" && typeof Intl.Segmenter === "function"; + each(useSegmenter ? Array.from(new Intl.Segmenter().segment(contents)).map(function (x) { return x.segment }) : contents.split(splitOn), function (splitText, i) { + if (i && preserveWhitespace) { + allElements.push(createElement(F, "whitespace", " ", preserveWhitespace)); + } + var splitEl = createElement(F, key, splitText); + elements.push(splitEl); + allElements.push(splitEl); + }); + // insert trailing space if there was one + if (wholeText[wholeText.length - 1] === ' ') { + allElements.push(createText(' ')); } - var splitEl = createElement(F, key, splitText); - elements.push(splitEl); - allElements.push(splitEl); - }); - // insert trailing space if there was one - if (wholeText[wholeText.length - 1] === ' ') { - allElements.push(createText(' ')); } - } - }); + }); - each(allElements, function(el) { - appendChild(F, el); - }); + each(allElements, function (el) { + appendChild(F, el); + }); - // Clear out the existing element - el.innerHTML = ""; - appendChild(el, F); - return elements; -} + // Clear out the existing element + el.innerHTML = ""; + appendChild(el, F); + return elements; + } -/** an empty value */ -var _ = 0; + /** an empty value */ + var _ = 0; -function copy(dest, src) { - for (var k in src) { - dest[k] = src[k]; + function copy(dest, src) { + for (var k in src) { + dest[k] = src[k]; + } + return dest; } - return dest; -} -var WORDS = 'words'; + var WORDS = 'words'; -var wordPlugin = createPlugin( + var wordPlugin = createPlugin( /* by= */ WORDS, /* depends= */ _, - /* key= */ 'word', - /* split= */ function(el) { - return splitText(el, 'word', /\s+/, 0, 1) - } -); - -var CHARS = "chars"; + /* key= */ 'word', + /* split= */ function (el) { + return splitText(el, 'word', /\s+/, 0, 1) + } + ); + + var CHARS = "chars"; + + var charPlugin = createPlugin( + /* by= */ CHARS, + /* depends= */[WORDS], + /* key= */ "char", + /* split= */ function (el, options, ctx) { + // RTL support: if the rtl flag is true, wrap each word in a single .char span + if (options.rtl) { + return ctx[WORDS].map(function (wordEl, idx) { + // get the full word text + var full = wordEl.getAttribute("data-word") || wordEl.textContent; + // clear existing chars + wordEl.innerHTML = ""; + // create one with the entire word + var ch = document.createElement("span"); + ch.className = "char"; + ch.setAttribute("data-char", full); + ch.style.setProperty("--char-index", idx); + ch.textContent = full; + wordEl.appendChild(ch); + return ch; + }); + } -var charPlugin = createPlugin( - /* by= */ CHARS, - /* depends= */ [WORDS], - /* key= */ "char", - /* split= */ function(el, options, ctx) { - var results = []; + // Default behavior: split each word into individual characters + var results = []; + each(ctx[WORDS], function (word, i) { + results.push.apply( + results, + splitText(word, "char", "", options.whitespace && i) + ); + }); + return results; + } + ); + + + /** + * # Splitting + * + * @param {import('./types').ISplittingOptions} opts + * @return {!Array<*>} + */ + function Splitting(opts) { + opts = opts || {}; + var key = opts.key; + + return $(opts.target || '[data-splitting]').map(function (el) { + var ctx = el['🍌']; + if (!opts.force && ctx) { + return ctx; + } - each(ctx[WORDS], function(word, i) { - results.push.apply(results, splitText(word, "char", "", options.whitespace && i)); - }); + ctx = el['🍌'] = { el: el }; + var by = opts.by || getData(el, 'splitting'); + if (!by || by == 'true') { + by = CHARS; + } + var items = resolve(by); + var opts2 = copy({}, opts); + each(items, function (plugin) { + if (plugin.split) { + var pluginBy = plugin.by; + var key2 = (key ? '-' + key : '') + plugin.key; + var results = plugin.split(el, opts2, ctx); + key2 && index(el, key2, results); + ctx[pluginBy] = results; + el.classList.add(pluginBy); + } + }); - return results; - } -); - -/** - * # Splitting - * - * @param {import('./types').ISplittingOptions} opts - * @return {!Array<*>} - */ -function Splitting (opts) { - opts = opts || {}; - var key = opts.key; - - return $(opts.target || '[data-splitting]').map(function(el) { - var ctx = el['🍌']; - if (!opts.force && ctx) { - return ctx; + el.classList.add('splitting'); + return ctx; + }) } - ctx = el['🍌'] = { el: el }; - var by = opts.by || getData(el, 'splitting'); - if (!by || by == 'true') { - by = CHARS; + /** + * # Splitting.html + * + * @param {import('./types').ISplittingOptions} opts + */ + function html(opts) { + opts = opts || {}; + var parent = opts.target = createElement(); + parent.innerHTML = opts.content; + Splitting(opts); + return parent.outerHTML } - var items = resolve(by); - var opts2 = copy({}, opts); - each(items, function(plugin) { - if (plugin.split) { - var pluginBy = plugin.by; - var key2 = (key ? '-' + key : '') + plugin.key; - var results = plugin.split(el, opts2, ctx); - key2 && index(el, key2, results); - ctx[pluginBy] = results; - el.classList.add(pluginBy); - } - }); - - el.classList.add('splitting'); - return ctx; - }) -} - -/** - * # Splitting.html - * - * @param {import('./types').ISplittingOptions} opts - */ -function html(opts) { - opts = opts || {}; - var parent = opts.target = createElement(); - parent.innerHTML = opts.content; - Splitting(opts); - return parent.outerHTML -} - -Splitting.html = html; -Splitting.add = add; - -// import { linePlugin } from "./plugins/lines"; -// import { itemPlugin } from "./plugins/items"; -// import { rowPlugin } from "./plugins/rows"; -// import { columnPlugin } from "./plugins/columns"; -// import { gridPlugin } from "./plugins/grid"; -// import { layoutPlugin } from "./plugins/layout"; -// import { cellRowPlugin } from "./plugins/cellRows"; -// import { cellColumnPlugin } from "./plugins/cellColumns"; -// import { cellPlugin } from "./plugins/cells"; - -// install plugins -// word/char plugins -add(wordPlugin); -add(charPlugin); - -return Splitting; + + Splitting.html = html; + Splitting.add = add; + + // import { linePlugin } from "./plugins/lines"; + // import { itemPlugin } from "./plugins/items"; + // import { rowPlugin } from "./plugins/rows"; + // import { columnPlugin } from "./plugins/columns"; + // import { gridPlugin } from "./plugins/grid"; + // import { layoutPlugin } from "./plugins/layout"; + // import { cellRowPlugin } from "./plugins/cellRows"; + // import { cellColumnPlugin } from "./plugins/cellColumns"; + // import { cellPlugin } from "./plugins/cells"; + + // install plugins + // word/char plugins + add(wordPlugin); + add(charPlugin); + + return Splitting; }))); diff --git a/dist/splitting-lite.min.js b/dist/splitting-lite.min.js index 1b55664..fc9c926 100644 --- a/dist/splitting-lite.min.js +++ b/dist/splitting-lite.min.js @@ -1,23 +1 @@ -!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):t.Splitting=n()}(this,function(){"use strict" -var u=document,c=u.createTextNode.bind(u) -function f(t,n,e){t.style.setProperty(n,e)}function l(t,n){return t.appendChild(n)}function d(t,n,e,r){var i=u.createElement("span") -return n&&(i.className=n),e&&(r||i.setAttribute("data-"+n,e),i.textContent=e),t&&l(t,i)||i}function n(t,n){return t&&0!=t.length?t.nodeName?[t]:[].slice.call(t[0].nodeName?t:(n||u).querySelectorAll(t)):[]}function p(t,n){t&&t.some(n)}var o={} -function t(t,n,e,r){return{by:t,depends:n,key:e,split:r}}function r(t){return function n(e,t,r){var i=r.indexOf(e) -if(-1==i){r.unshift(e) -var u=o[e] -if(!u)throw new Error("plugin not loaded: "+e) -p(u.depends,function(t){n(t,e,r)})}else u=r.indexOf(t),r.splice(i,1),r.splice(u,0,e) -return r}(t,0,[]).map((n=o,function(t){return n[t]})) -var n}function e(t){o[t.by]=t}function h(t,e,r,i,u){t.normalize() -var o=[],a=document.createDocumentFragment(),s=(i&&o.push(t.previousSibling),[]) -return n(t.childNodes).some(function(t){var n -t.tagName&&!t.hasChildNodes()?s.push(t):t.childNodes&&t.childNodes.length?(s.push(t),o.push.apply(o,h(t,e,r,i,u))):(n=(t=t.wholeText||"").trim()).length&&(" "===t[0]&&s.push(c(" ")),p(""===r&&"function"==typeof Intl.Segmenter?Array.from((new Intl.Segmenter).segment(n)).map(function(t){return t.segment}):n.split(r),function(t,n){n&&u&&s.push(d(a,"whitespace"," ",u)) -n=d(a,e,t) -o.push(n),s.push(n)})," "===t[t.length-1])&&s.push(c(" "))}),p(s,function(t){l(a,t)}),t.innerHTML="",l(t,a),o}var i="words",a=t(i,0,"word",function(t){return h(t,"word",/\s+/,0,1)}),m="chars",s=t(m,[i],"char",function(t,e,n){var r=[] -return p(n[i],function(t,n){r.push.apply(r,h(t,"char","",e.whitespace&&n))}),r}) -function g(e){var c=(e=e||{}).key -return n(e.target||"[data-splitting]").map(function(o){var t,n,a,s=o["🍌"] -return!e.force&&s||(s=o["🍌"]={el:o},n=r(t=(t=e.by||(t="splitting",o.getAttribute("data-"+t)))&&"true"!=t?t:m),a=function(t,n){for(var e in n)t[e]=n[e] -return t}({},e),p(n,function(t){var n,e,r,i,u -t.split&&(n=t.by,r=(c?"-"+c:"")+t.key,t=t.split(o,a,s),r&&(e=o,u=(r="--"+(r=r))+"-index",p(i=t,function(t,n){Array.isArray(t)?p(t,function(t){f(t,u,n)}):f(t,u,n)}),f(e,r+"-total",i.length)),s[n]=t,o.classList.add(n))}),o.classList.add("splitting")),s})}return g.html=function(t){var n=(t=t||{}).target=d() -return n.innerHTML=t.content,g(t),n.outerHTML},(g.add=e)(a),e(s),g}) +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.Splitting=e()}(this,(function(){"use strict";var t=document,e=t.createTextNode.bind(t);function n(t,e,n){t.style.setProperty(e,n)}function r(t,e){return t.appendChild(e)}function i(e,n,i,u){var o=t.createElement("span");return n&&(o.className=n),i&&(u||o.setAttribute("data-"+n,i),o.textContent=i),e&&r(e,o)||o}function u(e,n){return e&&0!=e.length?e.nodeName?[e]:[].slice.call(e[0].nodeName?e:(n||t).querySelectorAll(e)):[]}function o(t,e){t&&t.some(e)}var a={};function c(t,e,n,r){return{by:t,depends:e,key:n,split:r}}function s(t){return function t(e,n,r){var i=r.indexOf(e);if(-1==i){r.unshift(e);var u=a[e];if(!u)throw new Error("plugin not loaded: "+e);o(u.depends,(function(n){t(n,e,r)}))}else u=r.indexOf(n),r.splice(i,1),r.splice(u,0,e);return r}(t,0,[]).map((e=a,function(t){return e[t]}));var e}function d(t){a[t.by]=t}function l(t,n,a,c,s){t.normalize();var d=[],f=document.createDocumentFragment(),p=(c&&d.push(t.previousSibling),[]);return u(t.childNodes).some((function(t){var r;t.tagName&&!t.hasChildNodes()?p.push(t):t.childNodes&&t.childNodes.length?(p.push(t),d.push.apply(d,l(t,n,a,c,s))):(r=(t=t.wholeText||"").trim()).length&&(" "===t[0]&&p.push(e(" ")),o(""===a&&"function"==typeof Intl.Segmenter?Array.from((new Intl.Segmenter).segment(r)).map((function(t){return t.segment})):r.split(a),(function(t,e){e&&s&&p.push(i(f,"whitespace"," ",s)),e=i(f,n,t),d.push(e),p.push(e)}))," "===t[t.length-1])&&p.push(e(" "))})),o(p,(function(t){r(f,t)})),t.innerHTML="",r(t,f),d}var f="words",p=c(f,0,"word",(function(t){return l(t,"word",/\s+/,0,1)})),h="chars",m=c(h,[f],"char",(function(t,e,n){if(e.rtl)return n[f].map((function(t,e){var n=t.getAttribute("data-word")||t.textContent;t.innerHTML="";var r=document.createElement("span");return r.className="char",r.setAttribute("data-char",n),r.style.setProperty("--char-index",e),r.textContent=n,t.appendChild(r),r}));var r=[];return o(n[f],(function(t,n){r.push.apply(r,l(t,"char","",e.whitespace&&n))})),r}));function g(t){var e=(t=t||{}).key;return u(t.target||"[data-splitting]").map((function(r){var i,u,a,c=r["🍌"];return!t.force&&c||(c=r["🍌"]={el:r},u=s(i=(i=t.by||(i="splitting",r.getAttribute("data-"+i)))&&"true"!=i?i:h),a=function(t,e){for(var n in e)t[n]=e[n];return t}({},t),o(u,(function(t){var i,u,s,d,l;t.split&&(i=t.by,s=(e?"-"+e:"")+t.key,t=t.split(r,a,c),s&&(u=r,l=(s="--"+s)+"-index",o(d=t,(function(t,e){Array.isArray(t)?o(t,(function(t){n(t,l,e)})):n(t,l,e)})),n(u,s+"-total",d.length)),c[i]=t,r.classList.add(i))})),r.classList.add("splitting")),c}))}return g.html=function(t){var e=(t=t||{}).target=i();return e.innerHTML=t.content,g(t),e.outerHTML},(g.add=d)(p),d(m),g})); \ No newline at end of file diff --git a/dist/splitting.js b/dist/splitting.js index 9ebc6a5..12fb373 100644 --- a/dist/splitting.js +++ b/dist/splitting.js @@ -300,20 +300,44 @@ var wordPlugin = createPlugin( var CHARS = "chars"; var charPlugin = createPlugin( - /* by= */ CHARS, + /* by= */ CHARS, /* depends= */ [WORDS], - /* key= */ "char", - /* split= */ function(el, options, ctx) { - var results = []; - - each(ctx[WORDS], function(word, i) { - results.push.apply(results, splitText(word, "char", "", options.whitespace && i)); + /* key= */ "char", + /* split= */ function(el, options, ctx) { + // If RTL support is enabled, keep each word intact + if (options.rtl) { + return ctx[WORDS].map(function(wordEl, idx) { + // Get the full word text (from data-word or element content) + var full = wordEl.getAttribute("data-word") || wordEl.textContent; + // Clear any existing children (individual char spans) + wordEl.innerHTML = ""; + // Create a new that holds the entire word + var ch = document.createElement("span"); + ch.className = "char"; + ch.setAttribute("data-char", full); + // Set the char-index custom property for ordering/animation + ch.style.setProperty("--char-index", idx); + // Put the full word inside the span + ch.textContent = full; + // Append this new span back into the word element + wordEl.appendChild(ch); + return ch; }); - - return results; + } + + // Default behavior: split into individual characters + var results = []; + each(ctx[WORDS], function(word, i) { + // splitText() will wrap each letter in its own span.char + results.push.apply( + results, + splitText(word, "char", "", options.whitespace && i) + ); + }); + return results; } -); - + ); + /** * # Splitting * diff --git a/dist/splitting.min.js b/dist/splitting.min.js index 8216171..7e107b4 100644 --- a/dist/splitting.min.js +++ b/dist/splitting.min.js @@ -1,30 +1 @@ -!function(n,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):n.Splitting=t()}(this,function(){"use strict" -var u=document,a=u.createTextNode.bind(u) -function l(n,t,e){n.style.setProperty(t,e)}function f(n,t){return n.appendChild(t)}function d(n,t,e,r){var o=u.createElement("span") -return t&&(o.className=t),e&&(r||o.setAttribute("data-"+t,e),o.textContent=e),n&&f(n,o)||o}function p(n,t){return n.getAttribute("data-"+t)}function m(n,t){return n&&0!=n.length?n.nodeName?[n]:[].slice.call(n[0].nodeName?n:(t||u).querySelectorAll(n)):[]}function i(n){for(var t=[];n--;)t[n]=[] -return t}function h(n,t){n&&n.some(t)}function o(t){return function(n){return t[n]}}var c={} -function n(n,t,e,r){return{by:n,depends:t,key:e,split:r}}function e(n){return function t(e,n,r){var o=r.indexOf(e) -if(-1==o){r.unshift(e) -var u=c[e] -if(!u)throw new Error("plugin not loaded: "+e) -h(u.depends,function(n){t(n,e,r)})}else u=r.indexOf(n),r.splice(o,1),r.splice(u,0,e) -return r}(n,0,[]).map(o(c))}function t(n){c[n.by]=n}function g(n,e,r,o,u){n.normalize() -var i=[],c=document.createDocumentFragment(),s=(o&&i.push(n.previousSibling),[]) -return m(n.childNodes).some(function(n){var t -n.tagName&&!n.hasChildNodes()?s.push(n):n.childNodes&&n.childNodes.length?(s.push(n),i.push.apply(i,g(n,e,r,o,u))):(t=(n=n.wholeText||"").trim()).length&&(" "===n[0]&&s.push(a(" ")),h(""===r&&"function"==typeof Intl.Segmenter?Array.from((new Intl.Segmenter).segment(t)).map(function(n){return n.segment}):t.split(r),function(n,t){t&&u&&s.push(d(c,"whitespace"," ",u)) -t=d(c,e,n) -i.push(t),s.push(t)})," "===n[n.length-1])&&s.push(a(" "))}),h(s,function(n){f(c,n)}),n.innerHTML="",f(n,c),i}var v=0 -var s="words",r=n(s,v,"word",function(n){return g(n,"word",/\s+/,0,1)}),y="chars",w=n(y,[s],"char",function(n,e,t){var r=[] -return h(t[s],function(n,t){r.push.apply(r,g(n,"char","",e.whitespace&&t))}),r}) -function b(t){var a=(t=t||{}).key -return m(t.target||"[data-splitting]").map(function(i){var n,c,s=i["🍌"] -return!t.force&&s||(s=i["🍌"]={el:i},n=e(n=(n=t.by||p(i,"splitting"))&&"true"!=n?n:y),c=function(n,t){for(var e in t)n[e]=t[e] -return n}({},t),h(n,function(n){var t,e,r,o,u -n.split&&(t=n.by,r=(a?"-"+a:"")+n.key,n=n.split(i,c,s),r&&(e=i,u=(r="--"+(r=r))+"-index",h(o=n,function(n,t){Array.isArray(n)?h(n,function(n){l(n,u,t)}):l(n,u,t)}),l(e,r+"-total",o.length)),s[t]=n,i.classList.add(t))}),i.classList.add("splitting")),s})}function N(n,t,e){var t=m(t.matching||n.children,n),r={} -return h(t,function(n){var t=Math.round(n[e]);(r[t]||(r[t]=[])).push(n)}),Object.keys(r).map(Number).sort(x).map(o(r))}function x(n,t){return n-t}b.html=function(n){var t=(n=n||{}).target=d() -return t.innerHTML=n.content,b(n),t.outerHTML} -var S=n("lines",[s],"line",function(n,t,e){return N(n,{matching:e[s]},"offsetTop")}),T=n("items",v,"item",function(n,t){return m(t.matching||n.children,n)}),A=n("rows",v,"row",function(n,t){return N(n,t,"offsetTop")}),L=n("cols",v,"col",function(n,t){return N(n,t,"offsetLeft")}),k=n("grid",["rows","cols"]),C="layout",M=n(C,v,v,function(n,t){for(var e,r=t.rows=+(t.rows||p(n,"rows")||1),o=t.columns=+(t.columns||p(n,"columns")||1),u=(t.image=t.image||p(n,"image")||n.currentSrc||n.src,t.image&&(e=m("img",n)[0],t.image=e&&(e.currentSrc||e.src)),t.image&&l(n,"background-image","url("+t.image+")"),r*o),i=[],c=d(v,"cell-grid");u--;){var s=d(c,"cell") -d(s,"cell-inner"),i.push(s)}return f(n,c),i}),H=n("cellRows",[C],"row",function(n,t,e){var r=t.rows,o=i(r) -return h(e[C],function(n,t,e){o[Math.floor(t/(e.length/r))].push(n)}),o}),O=n("cellColumns",[C],"col",function(n,t,e){var r=t.columns,o=i(r) -return h(e[C],function(n,t){o[t%r].push(n)}),o}),j=n("cells",["cellRows","cellColumns"],"cell",function(n,t,e){return e[C]}) -return(b.add=t)(r),t(w),t(S),t(T),t(A),t(L),t(k),t(M),t(H),t(O),t(j),b}) +!function(n,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):n.Splitting=t()}(this,(function(){"use strict";var n=document,t=n.createTextNode.bind(n);function e(n,t,e){n.style.setProperty(t,e)}function r(n,t){return n.appendChild(t)}function o(t,e,o,i){var u=n.createElement("span");return e&&(u.className=e),o&&(i||u.setAttribute("data-"+e,o),u.textContent=o),t&&r(t,u)||u}function i(n,t){return n.getAttribute("data-"+t)}function u(t,e){return t&&0!=t.length?t.nodeName?[t]:[].slice.call(t[0].nodeName?t:(e||n).querySelectorAll(t)):[]}function c(n){for(var t=[];n--;)t[n]=[];return t}function a(n,t){n&&n.some(t)}function s(n){return function(t){return n[t]}}var l={};function f(n,t,e,r){return{by:n,depends:t,key:e,split:r}}function d(n){return function n(t,e,r){var o=r.indexOf(t);if(-1==o){r.unshift(t);var i=l[t];if(!i)throw new Error("plugin not loaded: "+t);a(i.depends,(function(e){n(e,t,r)}))}else i=r.indexOf(e),r.splice(o,1),r.splice(i,0,t);return r}(n,0,[]).map(s(l))}function p(n){l[n.by]=n}function m(n,e,i,c,s){n.normalize();var l=[],f=document.createDocumentFragment(),d=(c&&l.push(n.previousSibling),[]);return u(n.childNodes).some((function(n){var r;n.tagName&&!n.hasChildNodes()?d.push(n):n.childNodes&&n.childNodes.length?(d.push(n),l.push.apply(l,m(n,e,i,c,s))):(r=(n=n.wholeText||"").trim()).length&&(" "===n[0]&&d.push(t(" ")),a(""===i&&"function"==typeof Intl.Segmenter?Array.from((new Intl.Segmenter).segment(r)).map((function(n){return n.segment})):r.split(i),(function(n,t){t&&s&&d.push(o(f,"whitespace"," ",s)),t=o(f,e,n),l.push(t),d.push(t)}))," "===n[n.length-1])&&d.push(t(" "))})),a(d,(function(n){r(f,n)})),n.innerHTML="",r(n,f),l}var h="words",g=f(h,0,"word",(function(n){return m(n,"word",/\s+/,0,1)})),v="chars",y=f(v,[h],"char",(function(n,t,e){if(t.rtl)return e[h].map((function(n,t){var e=n.getAttribute("data-word")||n.textContent;n.innerHTML="";var r=document.createElement("span");return r.className="char",r.setAttribute("data-char",e),r.style.setProperty("--char-index",t),r.textContent=e,n.appendChild(r),r}));var r=[];return a(e[h],(function(n,e){r.push.apply(r,m(n,"char","",t.whitespace&&e))})),r}));function w(n){var t=(n=n||{}).key;return u(n.target||"[data-splitting]").map((function(r){var o,u,c=r["🍌"];return!n.force&&c||(c=r["🍌"]={el:r},o=d(o=(o=n.by||i(r,"splitting"))&&"true"!=o?o:v),u=function(n,t){for(var e in t)n[e]=t[e];return n}({},n),a(o,(function(n){var o,i,s,l,f;n.split&&(o=n.by,s=(t?"-"+t:"")+n.key,n=n.split(r,u,c),s&&(i=r,f=(s="--"+s)+"-index",a(l=n,(function(n,t){Array.isArray(n)?a(n,(function(n){e(n,f,t)})):e(n,f,t)})),e(i,s+"-total",l.length)),c[o]=n,r.classList.add(o))})),r.classList.add("splitting")),c}))}function b(n,t,e){t=u(t.matching||n.children,n);var r={};return a(t,(function(n){var t=Math.round(n[e]);(r[t]||(r[t]=[])).push(n)})),Object.keys(r).map(Number).sort(x).map(s(r))}function x(n,t){return n-t}w.html=function(n){var t=(n=n||{}).target=o();return t.innerHTML=n.content,w(n),t.outerHTML};var N=f("lines",[h],"line",(function(n,t,e){return b(n,{matching:e[h]},"offsetTop")})),A=f("items",0,"item",(function(n,t){return u(t.matching||n.children,n)})),C=f("rows",0,"row",(function(n,t){return b(n,t,"offsetTop")})),T=f("cols",0,"col",(function(n,t){return b(n,t,"offsetLeft")})),L=f("grid",["rows","cols"]),S="layout",M=f(S,0,0,(function(n,t){for(var c,a=t.rows=+(t.rows||i(n,"rows")||1),s=t.columns=+(t.columns||i(n,"columns")||1),l=(t.image=t.image||i(n,"image")||n.currentSrc||n.src,t.image&&(c=u("img",n)[0],t.image=c&&(c.currentSrc||c.src)),t.image&&e(n,"background-image","url("+t.image+")"),a*s),f=[],d=o(0,"cell-grid");l--;){var p=o(d,"cell");o(p,"cell-inner"),f.push(p)}return r(n,d),f})),k=f("cellRows",[S],"row",(function(n,t,e){var r=t.rows,o=c(r);return a(e[S],(function(n,t,e){o[Math.floor(t/(e.length/r))].push(n)})),o})),H=f("cellColumns",[S],"col",(function(n,t,e){var r=t.columns,o=c(r);return a(e[S],(function(n,t){o[t%r].push(n)})),o})),E=f("cells",["cellRows","cellColumns"],"cell",(function(n,t,e){return e[S]}));return(w.add=p)(g),p(y),p(N),p(A),p(C),p(T),p(L),p(M),p(k),p(H),p(E),w})); \ No newline at end of file diff --git a/tests/rtl-example.html b/tests/rtl-example.html new file mode 100644 index 0000000..0c6126c --- /dev/null +++ b/tests/rtl-example.html @@ -0,0 +1,144 @@ + + + + + + Word-by-Word Animation – Splitting.js Demo + + + + + + + + + +
+ +
+ این نمایشی از انیمیشن کلمه به کلمه برای متن فارسی است که با استفاده از کتابخانه اسپلیتینگ جی‌اس ایجاد شده + است. +
+ +
+ This is a demonstration of word-by-word animation for English text using the Splitting.js library. +
+
+ + + + Developed by Farham Aghdasi + + + + + + + \ No newline at end of file