animejs 4.1.4 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/README.md +17 -8
  2. package/{lib → dist/bundles}/anime.esm.js +6476 -6106
  3. package/dist/bundles/anime.esm.min.js +7 -0
  4. package/dist/bundles/anime.umd.js +8809 -0
  5. package/dist/bundles/anime.umd.min.js +7 -0
  6. package/dist/modules/animatable/animatable.cjs +150 -0
  7. package/dist/modules/animatable/animatable.d.ts +28 -0
  8. package/dist/modules/animatable/animatable.js +147 -0
  9. package/dist/modules/animatable/index.cjs +15 -0
  10. package/dist/modules/animatable/index.d.ts +1 -0
  11. package/dist/modules/animatable/index.js +8 -0
  12. package/dist/modules/animation/additive.cjs +82 -0
  13. package/dist/modules/animation/additive.d.ts +15 -0
  14. package/dist/modules/animation/additive.js +79 -0
  15. package/dist/modules/animation/animation.cjs +667 -0
  16. package/dist/modules/animation/animation.d.ts +45 -0
  17. package/dist/modules/animation/animation.js +664 -0
  18. package/dist/modules/animation/composition.cjs +383 -0
  19. package/dist/modules/animation/composition.d.ts +10 -0
  20. package/dist/modules/animation/composition.js +377 -0
  21. package/dist/modules/animation/index.cjs +15 -0
  22. package/dist/modules/animation/index.d.ts +1 -0
  23. package/dist/modules/animation/index.js +8 -0
  24. package/dist/modules/core/clock.cjs +110 -0
  25. package/dist/modules/core/clock.d.ts +51 -0
  26. package/dist/modules/core/clock.js +108 -0
  27. package/dist/modules/core/colors.cjs +102 -0
  28. package/dist/modules/core/colors.d.ts +2 -0
  29. package/dist/modules/core/colors.js +100 -0
  30. package/dist/modules/core/consts.cjs +159 -0
  31. package/dist/modules/core/consts.d.ts +61 -0
  32. package/dist/modules/core/consts.js +124 -0
  33. package/dist/modules/core/globals.cjs +78 -0
  34. package/dist/modules/core/globals.d.ts +29 -0
  35. package/dist/modules/core/globals.js +73 -0
  36. package/dist/modules/core/helpers.cjs +304 -0
  37. package/dist/modules/core/helpers.d.ts +43 -0
  38. package/dist/modules/core/helpers.js +261 -0
  39. package/dist/modules/core/render.cjs +391 -0
  40. package/dist/modules/core/render.d.ts +4 -0
  41. package/dist/modules/core/render.js +388 -0
  42. package/dist/modules/core/styles.cjs +116 -0
  43. package/dist/modules/core/styles.d.ts +5 -0
  44. package/dist/modules/core/styles.js +113 -0
  45. package/dist/modules/core/targets.cjs +136 -0
  46. package/dist/modules/core/targets.d.ts +118 -0
  47. package/dist/modules/core/targets.js +132 -0
  48. package/dist/modules/core/transforms.cjs +49 -0
  49. package/dist/modules/core/transforms.d.ts +2 -0
  50. package/dist/modules/core/transforms.js +47 -0
  51. package/dist/modules/core/units.cjs +67 -0
  52. package/dist/modules/core/units.d.ts +3 -0
  53. package/dist/modules/core/units.js +65 -0
  54. package/dist/modules/core/values.cjs +226 -0
  55. package/dist/modules/core/values.d.ts +14 -0
  56. package/dist/modules/core/values.js +216 -0
  57. package/dist/modules/draggable/draggable.cjs +1226 -0
  58. package/dist/modules/draggable/draggable.d.ts +272 -0
  59. package/dist/modules/draggable/draggable.js +1223 -0
  60. package/dist/modules/draggable/index.cjs +15 -0
  61. package/dist/modules/draggable/index.d.ts +1 -0
  62. package/dist/modules/draggable/index.js +8 -0
  63. package/dist/modules/easings/cubic-bezier/index.cjs +64 -0
  64. package/dist/modules/easings/cubic-bezier/index.d.ts +2 -0
  65. package/dist/modules/easings/cubic-bezier/index.js +62 -0
  66. package/dist/modules/easings/eases/index.cjs +14 -0
  67. package/dist/modules/easings/eases/index.d.ts +1 -0
  68. package/dist/modules/easings/eases/index.js +8 -0
  69. package/dist/modules/easings/eases/parser.cjs +191 -0
  70. package/dist/modules/easings/eases/parser.d.ts +125 -0
  71. package/dist/modules/easings/eases/parser.js +185 -0
  72. package/dist/modules/easings/index.cjs +26 -0
  73. package/dist/modules/easings/index.d.ts +6 -0
  74. package/dist/modules/easings/index.js +13 -0
  75. package/dist/modules/easings/irregular/index.cjs +41 -0
  76. package/dist/modules/easings/irregular/index.d.ts +2 -0
  77. package/dist/modules/easings/irregular/index.js +39 -0
  78. package/dist/modules/easings/linear/index.cjs +59 -0
  79. package/dist/modules/easings/linear/index.d.ts +2 -0
  80. package/dist/modules/easings/linear/index.js +57 -0
  81. package/dist/modules/easings/none.cjs +19 -0
  82. package/dist/modules/easings/none.d.ts +8 -0
  83. package/dist/modules/easings/none.js +17 -0
  84. package/dist/modules/easings/spring/index.cjs +255 -0
  85. package/dist/modules/easings/spring/index.d.ts +53 -0
  86. package/dist/modules/easings/spring/index.js +251 -0
  87. package/dist/modules/easings/steps/index.cjs +30 -0
  88. package/dist/modules/easings/steps/index.d.ts +2 -0
  89. package/dist/modules/easings/steps/index.js +28 -0
  90. package/dist/modules/engine/engine.cjs +168 -0
  91. package/dist/modules/engine/engine.d.ts +21 -0
  92. package/dist/modules/engine/engine.js +166 -0
  93. package/dist/modules/engine/index.cjs +14 -0
  94. package/dist/modules/engine/index.d.ts +1 -0
  95. package/dist/modules/engine/index.js +8 -0
  96. package/dist/modules/events/index.cjs +16 -0
  97. package/dist/modules/events/index.d.ts +1 -0
  98. package/dist/modules/events/index.js +8 -0
  99. package/dist/modules/events/scroll.cjs +940 -0
  100. package/dist/modules/events/scroll.d.ts +191 -0
  101. package/dist/modules/events/scroll.js +936 -0
  102. package/dist/modules/index.cjs +102 -0
  103. package/dist/modules/index.d.ts +18 -0
  104. package/dist/modules/index.js +41 -0
  105. package/dist/modules/scope/index.cjs +15 -0
  106. package/dist/modules/scope/index.d.ts +1 -0
  107. package/dist/modules/scope/index.js +8 -0
  108. package/dist/modules/scope/scope.cjs +254 -0
  109. package/dist/modules/scope/scope.d.ts +115 -0
  110. package/dist/modules/scope/scope.js +251 -0
  111. package/dist/modules/svg/drawable.cjs +119 -0
  112. package/dist/modules/svg/drawable.d.ts +3 -0
  113. package/dist/modules/svg/drawable.js +117 -0
  114. package/dist/modules/svg/helpers.cjs +30 -0
  115. package/dist/modules/svg/helpers.d.ts +2 -0
  116. package/dist/modules/svg/helpers.js +28 -0
  117. package/dist/modules/svg/index.cjs +18 -0
  118. package/dist/modules/svg/index.d.ts +3 -0
  119. package/dist/modules/svg/index.js +10 -0
  120. package/dist/modules/svg/morphto.cjs +58 -0
  121. package/dist/modules/svg/morphto.d.ts +3 -0
  122. package/dist/modules/svg/morphto.js +56 -0
  123. package/dist/modules/svg/motionpath.cjs +79 -0
  124. package/dist/modules/svg/motionpath.d.ts +7 -0
  125. package/dist/modules/svg/motionpath.js +77 -0
  126. package/dist/modules/text/index.cjs +16 -0
  127. package/dist/modules/text/index.d.ts +1 -0
  128. package/dist/modules/text/index.js +8 -0
  129. package/dist/modules/text/split.cjs +488 -0
  130. package/dist/modules/text/split.d.ts +62 -0
  131. package/dist/modules/text/split.js +484 -0
  132. package/dist/modules/timeline/index.cjs +15 -0
  133. package/dist/modules/timeline/index.d.ts +1 -0
  134. package/dist/modules/timeline/index.js +8 -0
  135. package/dist/modules/timeline/position.cjs +72 -0
  136. package/dist/modules/timeline/position.d.ts +3 -0
  137. package/dist/modules/timeline/position.js +70 -0
  138. package/dist/modules/timeline/timeline.cjs +312 -0
  139. package/dist/modules/timeline/timeline.d.ts +163 -0
  140. package/dist/modules/timeline/timeline.js +309 -0
  141. package/dist/modules/timer/index.cjs +15 -0
  142. package/dist/modules/timer/index.d.ts +1 -0
  143. package/dist/modules/timer/index.js +8 -0
  144. package/dist/modules/timer/timer.cjs +491 -0
  145. package/dist/modules/timer/timer.d.ts +141 -0
  146. package/dist/modules/timer/timer.js +488 -0
  147. package/dist/modules/types/index.d.ts +404 -0
  148. package/dist/modules/utils/chainable.cjs +190 -0
  149. package/dist/modules/utils/chainable.d.ts +135 -0
  150. package/dist/modules/utils/chainable.js +177 -0
  151. package/dist/modules/utils/index.cjs +43 -0
  152. package/dist/modules/utils/index.d.ts +5 -0
  153. package/dist/modules/utils/index.js +14 -0
  154. package/dist/modules/utils/number.cjs +97 -0
  155. package/dist/modules/utils/number.d.ts +9 -0
  156. package/dist/modules/utils/number.js +85 -0
  157. package/dist/modules/utils/random.cjs +77 -0
  158. package/dist/modules/utils/random.d.ts +22 -0
  159. package/dist/modules/utils/random.js +72 -0
  160. package/dist/modules/utils/stagger.cjs +122 -0
  161. package/dist/modules/utils/stagger.d.ts +30 -0
  162. package/dist/modules/utils/stagger.js +120 -0
  163. package/dist/modules/utils/target.cjs +130 -0
  164. package/dist/modules/utils/target.d.ts +126 -0
  165. package/dist/modules/utils/target.js +125 -0
  166. package/dist/modules/utils/time.cjs +57 -0
  167. package/dist/modules/utils/time.d.ts +5 -0
  168. package/dist/modules/utils/time.js +54 -0
  169. package/dist/modules/waapi/composition.cjs +95 -0
  170. package/dist/modules/waapi/composition.d.ts +4 -0
  171. package/dist/modules/waapi/composition.js +92 -0
  172. package/dist/modules/waapi/index.cjs +15 -0
  173. package/dist/modules/waapi/index.d.ts +1 -0
  174. package/dist/modules/waapi/index.js +8 -0
  175. package/dist/modules/waapi/waapi.cjs +492 -0
  176. package/dist/modules/waapi/waapi.d.ts +116 -0
  177. package/dist/modules/waapi/waapi.js +489 -0
  178. package/package.json +166 -33
  179. package/lib/anime.cjs +0 -9
  180. package/lib/anime.esm.min.js +0 -9
  181. package/lib/anime.iife.js +0 -9
  182. package/lib/anime.iife.min.js +0 -9
  183. package/lib/anime.min.cjs +0 -9
  184. package/lib/anime.umd.js +0 -9
  185. package/lib/anime.umd.min.js +0 -9
  186. package/lib/gui/index.js +0 -6341
  187. package/types/index.d.ts +0 -1127
  188. package/types/index.js +0 -7395
@@ -0,0 +1,488 @@
1
+ /**
2
+ * Anime.js - text - CJS
3
+ * @version v4.2.0
4
+ * @license MIT
5
+ * @copyright 2025 - Julian Garnier
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ var consts = require('../core/consts.cjs');
11
+ var globals = require('../core/globals.cjs');
12
+ var helpers = require('../core/helpers.cjs');
13
+ var targets = require('../core/targets.cjs');
14
+ var values = require('../core/values.cjs');
15
+ var time = require('../utils/time.cjs');
16
+
17
+ /**
18
+ * @import {
19
+ * Tickable,
20
+ * DOMTarget,
21
+ * SplitTemplateParams,
22
+ * SplitFunctionValue,
23
+ * TextSplitterParams,
24
+ * } from '../types/index.js'
25
+ */
26
+
27
+ const segmenter = (typeof Intl !== 'undefined') && Intl.Segmenter;
28
+ const valueRgx = /\{value\}/g;
29
+ const indexRgx = /\{i\}/g;
30
+ const whiteSpaceGroupRgx = /(\s+)/;
31
+ const whiteSpaceRgx = /^\s+$/;
32
+ const lineType = 'line';
33
+ const wordType = 'word';
34
+ const charType = 'char';
35
+ const dataLine = `data-line`;
36
+
37
+ /**
38
+ * @typedef {Object} Segment
39
+ * @property {String} segment
40
+ * @property {Boolean} [isWordLike]
41
+ */
42
+
43
+ /**
44
+ * @typedef {Object} Segmenter
45
+ * @property {function(String): Iterable<Segment>} segment
46
+ */
47
+
48
+ /** @type {Segmenter} */
49
+ let wordSegmenter = null;
50
+ /** @type {Segmenter} */
51
+ let graphemeSegmenter = null;
52
+ let $splitTemplate = null;
53
+
54
+ /**
55
+ * @param {Segment} seg
56
+ * @return {Boolean}
57
+ */
58
+ const isSegmentWordLike = seg => {
59
+ return seg.isWordLike ||
60
+ seg.segment === ' ' || // Consider spaces as words first, then handle them diffrently later
61
+ helpers.isNum(+seg.segment); // Safari doesn't considers numbers as words
62
+ };
63
+
64
+ /**
65
+ * @param {HTMLElement} $el
66
+ */
67
+ const setAriaHidden = $el => $el.setAttribute('aria-hidden', 'true');
68
+
69
+ /**
70
+ * @param {DOMTarget} $el
71
+ * @param {String} type
72
+ * @return {Array<HTMLElement>}
73
+ */
74
+ const getAllTopLevelElements = ($el, type) => [.../** @type {*} */($el.querySelectorAll(`[data-${type}]:not([data-${type}] [data-${type}])`))];
75
+
76
+ const debugColors = { line: '#00D672', word: '#FF4B4B', char: '#5A87FF' };
77
+
78
+ /**
79
+ * @param {HTMLElement} $el
80
+ */
81
+ const filterEmptyElements = $el => {
82
+ if (!$el.childElementCount && !$el.textContent.trim()) {
83
+ const $parent = $el.parentElement;
84
+ $el.remove();
85
+ if ($parent) filterEmptyElements($parent);
86
+ }
87
+ };
88
+
89
+ /**
90
+ * @param {HTMLElement} $el
91
+ * @param {Number} lineIndex
92
+ * @param {Set<HTMLElement>} bin
93
+ * @returns {Set<HTMLElement>}
94
+ */
95
+ const filterLineElements = ($el, lineIndex, bin) => {
96
+ const dataLineAttr = $el.getAttribute(dataLine);
97
+ if (dataLineAttr !== null && +dataLineAttr !== lineIndex || $el.tagName === 'BR') bin.add($el);
98
+ let i = $el.childElementCount;
99
+ while (i--) filterLineElements(/** @type {HTMLElement} */($el.children[i]), lineIndex, bin);
100
+ return bin;
101
+ };
102
+
103
+ /**
104
+ * @param {'line'|'word'|'char'} type
105
+ * @param {SplitTemplateParams} params
106
+ * @return {String}
107
+ */
108
+ const generateTemplate = (type, params = {}) => {
109
+ let template = ``;
110
+ const classString = helpers.isStr(params.class) ? ` class="${params.class}"` : '';
111
+ const cloneType = values.setValue(params.clone, false);
112
+ const wrapType = values.setValue(params.wrap, false);
113
+ const overflow = wrapType ? wrapType === true ? 'clip' : wrapType : cloneType ? 'clip' : false;
114
+ if (wrapType) template += `<span${overflow ? ` style="overflow:${overflow};"` : ''}>`;
115
+ template += `<span${classString}${cloneType ? ` style="position:relative;"` : ''} data-${type}="{i}">`;
116
+ if (cloneType) {
117
+ const left = cloneType === 'left' ? '-100%' : cloneType === 'right' ? '100%' : '0';
118
+ const top = cloneType === 'top' ? '-100%' : cloneType === 'bottom' ? '100%' : '0';
119
+ template += `<span>{value}</span>`;
120
+ template += `<span inert style="position:absolute;top:${top};left:${left};white-space:nowrap;">{value}</span>`;
121
+ } else {
122
+ template += `{value}`;
123
+ }
124
+ template += `</span>`;
125
+ if (wrapType) template += `</span>`;
126
+ return template;
127
+ };
128
+
129
+ /**
130
+ * @param {String|SplitFunctionValue} htmlTemplate
131
+ * @param {Array<HTMLElement>} store
132
+ * @param {Node|HTMLElement} node
133
+ * @param {DocumentFragment} $parentFragment
134
+ * @param {'line'|'word'|'char'} type
135
+ * @param {Boolean} debug
136
+ * @param {Number} lineIndex
137
+ * @param {Number} [wordIndex]
138
+ * @param {Number} [charIndex]
139
+ * @return {HTMLElement}
140
+ */
141
+ const processHTMLTemplate = (htmlTemplate, store, node, $parentFragment, type, debug, lineIndex, wordIndex, charIndex) => {
142
+ const isLine = type === lineType;
143
+ const isChar = type === charType;
144
+ const className = `_${type}_`;
145
+ const template = helpers.isFnc(htmlTemplate) ? htmlTemplate(node) : htmlTemplate;
146
+ const displayStyle = isLine ? 'block' : 'inline-block';
147
+ $splitTemplate.innerHTML = template
148
+ .replace(valueRgx, `<i class="${className}"></i>`)
149
+ .replace(indexRgx, `${isChar ? charIndex : isLine ? lineIndex : wordIndex}`);
150
+ const $content = $splitTemplate.content;
151
+ const $highestParent = /** @type {HTMLElement} */($content.firstElementChild);
152
+ const $split = /** @type {HTMLElement} */($content.querySelector(`[data-${type}]`)) || $highestParent;
153
+ const $replacables = /** @type {NodeListOf<HTMLElement>} */($content.querySelectorAll(`i.${className}`));
154
+ const replacablesLength = $replacables.length;
155
+ if (replacablesLength) {
156
+ $highestParent.style.display = displayStyle;
157
+ $split.style.display = displayStyle;
158
+ $split.setAttribute(dataLine, `${lineIndex}`);
159
+ if (!isLine) {
160
+ $split.setAttribute('data-word', `${wordIndex}`);
161
+ if (isChar) $split.setAttribute('data-char', `${charIndex}`);
162
+ }
163
+ let i = replacablesLength;
164
+ while (i--) {
165
+ const $replace = $replacables[i];
166
+ const $closestParent = $replace.parentElement;
167
+ $closestParent.style.display = displayStyle;
168
+ if (isLine) {
169
+ $closestParent.innerHTML = /** @type {HTMLElement} */(node).innerHTML;
170
+ } else {
171
+ $closestParent.replaceChild(node.cloneNode(true), $replace);
172
+ }
173
+ }
174
+ store.push($split);
175
+ $parentFragment.appendChild($content);
176
+ } else {
177
+ console.warn(`The expression "{value}" is missing from the provided template.`);
178
+ }
179
+ if (debug) $highestParent.style.outline = `1px dotted ${debugColors[type]}`;
180
+ return $highestParent;
181
+ };
182
+
183
+ /**
184
+ * A class that splits text into words and wraps them in span elements while preserving the original HTML structure.
185
+ * @class
186
+ */
187
+ class TextSplitter {
188
+ /**
189
+ * @param {HTMLElement|NodeList|String|Array<HTMLElement>} target
190
+ * @param {TextSplitterParams} [parameters]
191
+ */
192
+ constructor(target, parameters = {}) {
193
+ // Only init segmenters when needed
194
+ if (!wordSegmenter) wordSegmenter = segmenter ? new segmenter([], { granularity: wordType }) : {
195
+ segment: (text) => {
196
+ const segments = [];
197
+ const words = text.split(whiteSpaceGroupRgx);
198
+ for (let i = 0, l = words.length; i < l; i++) {
199
+ const segment = words[i];
200
+ segments.push({
201
+ segment,
202
+ isWordLike: !whiteSpaceRgx.test(segment), // Consider non-whitespace as word-like
203
+ });
204
+ }
205
+ return segments;
206
+ }
207
+ };
208
+ if (!graphemeSegmenter) graphemeSegmenter = segmenter ? new segmenter([], { granularity: 'grapheme' }) : {
209
+ segment: text => [...text].map(char => ({ segment: char }))
210
+ };
211
+ if (!$splitTemplate && consts.isBrowser) $splitTemplate = consts.doc.createElement('template');
212
+ if (globals.scope.current) globals.scope.current.register(this);
213
+ const { words, chars, lines, accessible, includeSpaces, debug } = parameters;
214
+ const $target = /** @type {HTMLElement} */((target = helpers.isArr(target) ? target[0] : target) && /** @type {Node} */(target).nodeType ? target : (targets.getNodeList(target) || [])[0]);
215
+ const lineParams = lines === true ? {} : lines;
216
+ const wordParams = words === true || helpers.isUnd(words) ? {} : words;
217
+ const charParams = chars === true ? {} : chars;
218
+ this.debug = values.setValue(debug, false);
219
+ this.includeSpaces = values.setValue(includeSpaces, false);
220
+ this.accessible = values.setValue(accessible, true);
221
+ this.linesOnly = lineParams && (!wordParams && !charParams);
222
+ /** @type {String|false|SplitFunctionValue} */
223
+ this.lineTemplate = helpers.isObj(lineParams) ? generateTemplate(lineType, /** @type {SplitTemplateParams} */(lineParams)) : lineParams;
224
+ /** @type {String|false|SplitFunctionValue} */
225
+ this.wordTemplate = helpers.isObj(wordParams) || this.linesOnly ? generateTemplate(wordType, /** @type {SplitTemplateParams} */(wordParams)) : wordParams;
226
+ /** @type {String|false|SplitFunctionValue} */
227
+ this.charTemplate = helpers.isObj(charParams) ? generateTemplate(charType, /** @type {SplitTemplateParams} */(charParams)) : charParams;
228
+ this.$target = $target;
229
+ this.html = $target && $target.innerHTML;
230
+ this.lines = [];
231
+ this.words = [];
232
+ this.chars = [];
233
+ this.effects = [];
234
+ this.effectsCleanups = [];
235
+ this.cache = null;
236
+ this.ready = false;
237
+ this.width = 0;
238
+ this.resizeTimeout = null;
239
+ const handleSplit = () => this.html && (lineParams || wordParams || charParams) && this.split();
240
+ // Make sure this is declared before calling handleSplit() in case revert() is called inside an effect callback
241
+ this.resizeObserver = new ResizeObserver(() => {
242
+ // Use a setTimeout instead of a Timer for better tree shaking
243
+ clearTimeout(this.resizeTimeout);
244
+ this.resizeTimeout = setTimeout(() => {
245
+ const currentWidth = /** @type {HTMLElement} */($target).offsetWidth;
246
+ if (currentWidth === this.width) return;
247
+ this.width = currentWidth;
248
+ handleSplit();
249
+ }, 150);
250
+ });
251
+ // Only declare the font ready promise when splitting by lines and not alreay split
252
+ if (this.lineTemplate && !this.ready) {
253
+ consts.doc.fonts.ready.then(handleSplit);
254
+ } else {
255
+ handleSplit();
256
+ }
257
+ $target ? this.resizeObserver.observe($target) : console.warn('No Text Splitter target found.');
258
+ }
259
+
260
+ /**
261
+ * @param {(...args: any[]) => Tickable | (() => void)} effect
262
+ * @return this
263
+ */
264
+ addEffect(effect) {
265
+ if (!helpers.isFnc(effect)) return console.warn('Effect must return a function.');
266
+ const refreshableEffect = time.keepTime(effect);
267
+ this.effects.push(refreshableEffect);
268
+ if (this.ready) this.effectsCleanups[this.effects.length - 1] = refreshableEffect(this);
269
+ return this;
270
+ }
271
+
272
+ revert() {
273
+ clearTimeout(this.resizeTimeout);
274
+ this.lines.length = this.words.length = this.chars.length = 0;
275
+ this.resizeObserver.disconnect();
276
+ // Make sure to revert the effects after disconnecting the resizeObserver to avoid triggering it in the process
277
+ this.effectsCleanups.forEach(cleanup => helpers.isFnc(cleanup) ? cleanup(this) : cleanup.revert && cleanup.revert());
278
+ this.$target.innerHTML = this.html;
279
+ return this;
280
+ }
281
+
282
+ /**
283
+ * Recursively processes a node and its children
284
+ * @param {Node} node
285
+ */
286
+ splitNode(node) {
287
+ const wordTemplate = this.wordTemplate;
288
+ const charTemplate = this.charTemplate;
289
+ const includeSpaces = this.includeSpaces;
290
+ const debug = this.debug;
291
+ const nodeType = node.nodeType;
292
+ if (nodeType === 3) {
293
+ const nodeText = node.nodeValue;
294
+ // If the nodeText is only whitespace, leave it as is
295
+ if (nodeText.trim()) {
296
+ const tempWords = [];
297
+ const words = this.words;
298
+ const chars = this.chars;
299
+ const wordSegments = wordSegmenter.segment(nodeText);
300
+ const $wordsFragment = consts.doc.createDocumentFragment();
301
+ let prevSeg = null;
302
+ for (const wordSegment of wordSegments) {
303
+ const segment = wordSegment.segment;
304
+ const isWordLike = isSegmentWordLike(wordSegment);
305
+ // Determine if this segment should be a new word, first segment always becomes a new word
306
+ if (!prevSeg || (isWordLike && (prevSeg && (isSegmentWordLike(prevSeg))))) {
307
+ tempWords.push(segment);
308
+ } else {
309
+ // Only concatenate if both current and previous are non-word-like and don't contain spaces
310
+ const lastWordIndex = tempWords.length - 1;
311
+ const lastWord = tempWords[lastWordIndex];
312
+ if (!lastWord.includes(' ') && !segment.includes(' ')) {
313
+ tempWords[lastWordIndex] += segment;
314
+ } else {
315
+ tempWords.push(segment);
316
+ }
317
+ }
318
+ prevSeg = wordSegment;
319
+ }
320
+
321
+ for (let i = 0, l = tempWords.length; i < l; i++) {
322
+ const word = tempWords[i];
323
+ if (!word.trim()) {
324
+ // Preserve whitespace only if includeSpaces is false and if the current space is not the first node
325
+ if (i && includeSpaces) continue;
326
+ $wordsFragment.appendChild(consts.doc.createTextNode(word));
327
+ } else {
328
+ const nextWord = tempWords[i + 1];
329
+ const hasWordFollowingSpace = includeSpaces && nextWord && !nextWord.trim();
330
+ const wordToProcess = word;
331
+ const charSegments = charTemplate ? graphemeSegmenter.segment(wordToProcess) : null;
332
+ const $charsFragment = charTemplate ? consts.doc.createDocumentFragment() : consts.doc.createTextNode(hasWordFollowingSpace ? word + '\xa0' : word);
333
+ if (charTemplate) {
334
+ const charSegmentsArray = [...charSegments];
335
+ for (let j = 0, jl = charSegmentsArray.length; j < jl; j++) {
336
+ const charSegment = charSegmentsArray[j];
337
+ const isLastChar = j === jl - 1;
338
+ // If this is the last character and includeSpaces is true with a following space, append the space
339
+ const charText = isLastChar && hasWordFollowingSpace ? charSegment.segment + '\xa0' : charSegment.segment;
340
+ const $charNode = consts.doc.createTextNode(charText);
341
+ processHTMLTemplate(charTemplate, chars, $charNode, /** @type {DocumentFragment} */($charsFragment), charType, debug, -1, words.length, chars.length);
342
+ }
343
+ }
344
+ if (wordTemplate) {
345
+ processHTMLTemplate(wordTemplate, words, $charsFragment, $wordsFragment, wordType, debug, -1, words.length, chars.length);
346
+ // Chars elements must be re-parsed in the split() method if both words and chars are parsed
347
+ } else if (charTemplate) {
348
+ $wordsFragment.appendChild($charsFragment);
349
+ } else {
350
+ $wordsFragment.appendChild(consts.doc.createTextNode(word));
351
+ }
352
+ // Skip the next iteration if we included a space
353
+ if (hasWordFollowingSpace) i++;
354
+ }
355
+ }
356
+ node.parentNode.replaceChild($wordsFragment, node);
357
+ }
358
+ } else if (nodeType === 1) {
359
+ // Converting to an array is necessary to work around childNodes pottential mutation
360
+ const childNodes = /** @type {Array<Node>} */([.../** @type {*} */(node.childNodes)]);
361
+ for (let i = 0, l = childNodes.length; i < l; i++) this.splitNode(childNodes[i]);
362
+ }
363
+ }
364
+
365
+ /**
366
+ * @param {Boolean} clearCache
367
+ * @return {this}
368
+ */
369
+ split(clearCache = false) {
370
+ const $el = this.$target;
371
+ const isCached = !!this.cache && !clearCache;
372
+ const lineTemplate = this.lineTemplate;
373
+ const wordTemplate = this.wordTemplate;
374
+ const charTemplate = this.charTemplate;
375
+ const fontsReady = consts.doc.fonts.status !== 'loading';
376
+ const canSplitLines = lineTemplate && fontsReady;
377
+ this.ready = !lineTemplate || fontsReady;
378
+ if (canSplitLines || clearCache) {
379
+ // No need to revert effects animations here since it's already taken care by the refreshable
380
+ this.effectsCleanups.forEach(cleanup => helpers.isFnc(cleanup) && cleanup(this));
381
+ }
382
+ if (!isCached) {
383
+ if (clearCache) {
384
+ $el.innerHTML = this.html;
385
+ this.words.length = this.chars.length = 0;
386
+ }
387
+ this.splitNode($el);
388
+ this.cache = $el.innerHTML;
389
+ }
390
+ if (canSplitLines) {
391
+ if (isCached) $el.innerHTML = this.cache;
392
+ this.lines.length = 0;
393
+ if (wordTemplate) this.words = getAllTopLevelElements($el, wordType);
394
+ }
395
+ // Always reparse characters after a line reset or if both words and chars are activated
396
+ if (charTemplate && (canSplitLines || wordTemplate)) {
397
+ this.chars = getAllTopLevelElements($el, charType);
398
+ }
399
+ // Words are used when lines only and prioritized over chars
400
+ const elementsArray = this.words.length ? this.words : this.chars;
401
+ let y, linesCount = 0;
402
+ for (let i = 0, l = elementsArray.length; i < l; i++) {
403
+ const $el = elementsArray[i];
404
+ const { top, height } = $el.getBoundingClientRect();
405
+ if (y && top - y > height * .5) linesCount++;
406
+ $el.setAttribute(dataLine, `${linesCount}`);
407
+ const nested = $el.querySelectorAll(`[${dataLine}]`);
408
+ let c = nested.length;
409
+ while (c--) nested[c].setAttribute(dataLine, `${linesCount}`);
410
+ y = top;
411
+ }
412
+ if (canSplitLines) {
413
+ const linesFragment = consts.doc.createDocumentFragment();
414
+ const parents = new Set();
415
+ const clones = [];
416
+ for (let lineIndex = 0; lineIndex < linesCount + 1; lineIndex++) {
417
+ const $clone = /** @type {HTMLElement} */($el.cloneNode(true));
418
+ filterLineElements($clone, lineIndex, new Set()).forEach($el => {
419
+ const $parent = $el.parentElement;
420
+ if ($parent) parents.add($parent);
421
+ $el.remove();
422
+ });
423
+ clones.push($clone);
424
+ }
425
+ parents.forEach(filterEmptyElements);
426
+ for (let cloneIndex = 0, clonesLength = clones.length; cloneIndex < clonesLength; cloneIndex++) {
427
+ processHTMLTemplate(lineTemplate, this.lines, clones[cloneIndex], linesFragment, lineType, this.debug, cloneIndex);
428
+ }
429
+ $el.innerHTML = '';
430
+ $el.appendChild(linesFragment);
431
+ if (wordTemplate) this.words = getAllTopLevelElements($el, wordType);
432
+ if (charTemplate) this.chars = getAllTopLevelElements($el, charType);
433
+ }
434
+ // Remove the word wrappers and clear the words array if lines split only
435
+ if (this.linesOnly) {
436
+ const words = this.words;
437
+ let w = words.length;
438
+ while (w--) {
439
+ const $word = words[w];
440
+ $word.replaceWith($word.textContent);
441
+ }
442
+ words.length = 0;
443
+ }
444
+ if (this.accessible && (canSplitLines || !isCached)) {
445
+ const $accessible = consts.doc.createElement('span');
446
+ // Make the accessible element visually-hidden (https://www.scottohara.me/blog/2017/04/14/inclusively-hidden.html)
447
+ $accessible.style.cssText = `position:absolute;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);width:1px;height:1px;white-space:nowrap;`;
448
+ // $accessible.setAttribute('tabindex', '-1');
449
+ $accessible.innerHTML = this.html;
450
+ $el.insertBefore($accessible, $el.firstChild);
451
+ this.lines.forEach(setAriaHidden);
452
+ this.words.forEach(setAriaHidden);
453
+ this.chars.forEach(setAriaHidden);
454
+ }
455
+ this.width = /** @type {HTMLElement} */($el).offsetWidth;
456
+ if (canSplitLines || clearCache) {
457
+ this.effects.forEach((effect, i) => this.effectsCleanups[i] = effect(this));
458
+ }
459
+ return this;
460
+ }
461
+
462
+ refresh() {
463
+ this.split(true);
464
+ }
465
+ }
466
+
467
+ /**
468
+ * @param {HTMLElement|NodeList|String|Array<HTMLElement>} target
469
+ * @param {TextSplitterParams} [parameters]
470
+ * @return {TextSplitter}
471
+ */
472
+ const splitText = (target, parameters) => new TextSplitter(target, parameters);
473
+
474
+ /**
475
+ * @deprecated text.split() is deprecated, import splitText() directly, or text.splitText()
476
+ *
477
+ * @param {HTMLElement|NodeList|String|Array<HTMLElement>} target
478
+ * @param {TextSplitterParams} [parameters]
479
+ * @return {TextSplitter}
480
+ */
481
+ const split = (target, parameters) => {
482
+ console.warn('text.split() is deprecated, import splitText() directly, or text.splitText()');
483
+ return new TextSplitter(target, parameters);
484
+ };
485
+
486
+ exports.TextSplitter = TextSplitter;
487
+ exports.split = split;
488
+ exports.splitText = splitText;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * A class that splits text into words and wraps them in span elements while preserving the original HTML structure.
3
+ * @class
4
+ */
5
+ export class TextSplitter {
6
+ /**
7
+ * @param {HTMLElement|NodeList|String|Array<HTMLElement>} target
8
+ * @param {TextSplitterParams} [parameters]
9
+ */
10
+ constructor(target: HTMLElement | NodeList | string | Array<HTMLElement>, parameters?: TextSplitterParams);
11
+ debug: boolean;
12
+ includeSpaces: boolean;
13
+ accessible: boolean;
14
+ linesOnly: boolean;
15
+ /** @type {String|false|SplitFunctionValue} */
16
+ lineTemplate: string | false | SplitFunctionValue;
17
+ /** @type {String|false|SplitFunctionValue} */
18
+ wordTemplate: string | false | SplitFunctionValue;
19
+ /** @type {String|false|SplitFunctionValue} */
20
+ charTemplate: string | false | SplitFunctionValue;
21
+ $target: HTMLElement;
22
+ html: string;
23
+ lines: any[];
24
+ words: any[];
25
+ chars: any[];
26
+ effects: any[];
27
+ effectsCleanups: any[];
28
+ cache: string;
29
+ ready: boolean;
30
+ width: number;
31
+ resizeTimeout: NodeJS.Timeout;
32
+ resizeObserver: ResizeObserver;
33
+ /**
34
+ * @param {(...args: any[]) => Tickable | (() => void)} effect
35
+ * @return this
36
+ */
37
+ addEffect(effect: (...args: any[]) => Tickable | (() => void)): void | this;
38
+ revert(): this;
39
+ /**
40
+ * Recursively processes a node and its children
41
+ * @param {Node} node
42
+ */
43
+ splitNode(node: Node): void;
44
+ /**
45
+ * @param {Boolean} clearCache
46
+ * @return {this}
47
+ */
48
+ split(clearCache?: boolean): this;
49
+ refresh(): void;
50
+ }
51
+ export function splitText(target: HTMLElement | NodeList | string | Array<HTMLElement>, parameters?: TextSplitterParams): TextSplitter;
52
+ export function split(target: HTMLElement | NodeList | string | Array<HTMLElement>, parameters?: TextSplitterParams): TextSplitter;
53
+ export type Segment = {
54
+ segment: string;
55
+ isWordLike?: boolean;
56
+ };
57
+ export type Segmenter = {
58
+ segment: (arg0: string) => Iterable<Segment>;
59
+ };
60
+ import type { SplitFunctionValue } from '../types/index.js';
61
+ import type { Tickable } from '../types/index.js';
62
+ import type { TextSplitterParams } from '../types/index.js';