select-animation 1.2.5 → 1.4.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 (2) hide show
  1. package/package.json +1 -1
  2. package/select-animation.js +124 -76
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "select-animation",
3
- "version": "1.2.5",
3
+ "version": "1.4.0",
4
4
  "description": "High-performance JavaScript animation engine for organic & fluid motion",
5
5
  "main": "select-animation.js",
6
6
  "scripts": {
@@ -100,13 +100,37 @@
100
100
  // Returns flat array of matched elements given one or more selectors
101
101
  // Usage: select('.class', '#id')
102
102
  // -----------------------------
103
- const selectDom = function(...selectors) {
104
- const all = [];
105
- selectors.forEach(selector => {
106
- const nodeList = document.querySelectorAll(selector);
107
- all.push(...nodeList);
108
- });
109
- return all;
103
+ // --- Enhanced DOM Selection Utility with Explicit Error Reporting ---
104
+ const selectDom = function (selector) {
105
+ // 1. Check if the input is a DOM element directly
106
+ if (isElement(selector)) return [selector];
107
+
108
+ // 2. Handle string selectors (e.g., ".class", "#id")
109
+ if (typeof selector === "string") {
110
+ const nodes = document.querySelectorAll(selector);
111
+ if (nodes.length === 0) {
112
+ // Warning if the selector is valid string but matches nothing
113
+ console.warn(`Select-Animation: No elements found matching the selector "${selector}".`);
114
+ }
115
+ return Array.from(nodes);
116
+ }
117
+
118
+ // 3. Handle arrays or collections
119
+ if (Array.isArray(selector) || (selector && typeof selector.length === "number")) {
120
+ return Array.from(selector).filter(isElement);
121
+ }
122
+
123
+ // 4. CRITICAL: Handle invalid types (like numbers, null, undefined)
124
+ if (selector !== undefined && selector !== null) {
125
+ // Throwing a console error to stop the developer and force a fix
126
+ console.error(
127
+ `Select-Animation ERROR: Invalid input passed to selectDom().\n` +
128
+ `Expected: String (selector), HTMLElement, or Array.\n` +
129
+ `Received: ${typeof selector} (${selector})`
130
+ );
131
+ }
132
+
133
+ return [];
110
134
  };
111
135
 
112
136
  // -----------------------------
@@ -117,82 +141,87 @@
117
141
  const definitions = arguments;
118
142
  const defsCopy = copyObj(definitions);
119
143
 
120
- return function (targets, primaryParam, runtimeContext) {
121
- let currentTargets, eventCheckResult, tmp, i, propIndex, fromItem, toItem, valFrom, valTo, t;
144
+ return function () {
145
+ let tmp, i, propIndex, fromItem, toItem, valFrom, valTo, t;
122
146
  let grouped = [];
123
- let runtimeGrouped = [];
124
147
  let expectNextGroup = false;
125
148
  let staging = { color: {}, transform: {}, from: {}, to: {} };
126
149
 
150
+ // Process the definitions to map elements to their animation settings
127
151
  buildPlan();
128
152
 
129
153
  function buildPlan() {
130
- if (targets !== undefined) {
131
- if (!Array.isArray(targets)) targets = [targets];
132
- runtimeGrouped = targets;
133
- }
134
-
135
154
  let secondPass = false;
136
155
 
137
156
  for (let c = 0, total = definitions.length; c < total; c++) {
138
157
  propIndex = 0;
158
+ // If current item is an element or an array of elements
139
159
  if (expectNextGroup || Array.isArray(defsCopy[c]) || isElement(defsCopy[c])) {
140
160
  if (secondPass) {
141
161
  Array.isArray(defsCopy[c]) || (defsCopy[c] = [defsCopy[c]]);
142
162
  Array.prototype.push.apply(grouped, defsCopy[c]);
143
163
  }
144
164
  expectNextGroup = false;
145
- } else if (typeof defsCopy[c] === 'object' && defsCopy[c] !== null) {
165
+ }
166
+ // --- Handle Configuration Object with Validation ---
167
+ else if (typeof defsCopy[c] === 'object' && defsCopy[c] !== null) {
146
168
  if (secondPass) {
147
- if (c === 0) {
148
- for (let u = 0; void 0 !== targets[u]; u++) {
149
- if (typeof targets[u] !== "number") grouped.push(targets[u]);
150
- }
151
- }
152
- if (targets !== undefined) {
153
- for (let j = 0; void 0 !== runtimeGrouped[j]; j++) {
154
- if (typeof runtimeGrouped[j] !== "number") {
155
- eventCheckResult = checkValEvent(runtimeContext, primaryParam, currentTargets, runtimeGrouped[j + 1]);
156
- if (eventCheckResult[0]) currentTargets = eventCheckResult[0];
157
- bindEvent(runtimeGrouped[j], runner(grouped, definitions, defsCopy, c), eventCheckResult[1]);
158
- }
159
- }
160
- } else {
161
- runner(grouped, definitions, defsCopy, c)();
162
- }
169
+ // Execute the animation runner for the current group
170
+ runner(grouped, definitions, defsCopy, c)();
163
171
  } else {
164
172
  expectNextGroup = false;
165
- const fromIsObject = typeof defsCopy[c].from === "object";
166
- const toIsObject = typeof defsCopy[c].to === "object";
167
173
 
168
- if (defsCopy[c].typeAnimation === "vibration" && defsCopy[c].vibrationStep === undefined) {
174
+ // 1. Validate Duration and Animation Type
175
+ defsCopy[c].duration = (typeof defsCopy[c].duration === 'number' && defsCopy[c].duration > 0) ? defsCopy[c].duration : 1000;
176
+
177
+ let requestedType = defsCopy[c].typeAnimation;
178
+
179
+ if (requestedType === "vibration" && defsCopy[c].vibrationStep === undefined) {
169
180
  defsCopy[c].vibrationStep = 6;
170
- } else {
171
- t = getNumber(defsCopy[c].typeAnimation);
181
+ } else if (requestedType) {
182
+ t = getNumber(requestedType);
172
183
  if (t && t.length === 4) {
173
184
  defsCopy[c].cubicbezier = t;
174
185
  defsCopy[c].typeAnimation = "cubicbezier";
186
+ } else {
187
+ // STOP EXECUTION: If easing is not found, do not fall back to linear
188
+ if (requestedType !== "linear" && requestedType !== "vibration" && requestedType !== "cubicbezier" && (!global.selectAnimationEase || !global.selectAnimationEase[requestedType])) {
189
+ // Use Error instead of warn to make it impossible to ignore
190
+ throw new Error(`Select-Animation ERROR: The easing function "${requestedType}" does not exist. Please check your spelling or definitions.`);
191
+ }
175
192
  }
176
193
  }
177
-
178
- if (defsCopy[c].boucle && defsCopy[c].boucleType === undefined) {
179
- defsCopy[c].boucleType = "return";
180
- }
181
-
182
- if (!defsCopy[c].callback &&
194
+
195
+ // Double-check if the animation type is valid before processing
196
+ if (!defsCopy[c].typeAnimation) return;
197
+
198
+ // 2. Helper to sanitize input values (convert "100px" to 100)
199
+ const parseVal = (v) => {
200
+ if (typeof v === "number") return v;
201
+ let p = parseFloat(v);
202
+ return isNaN(p) ? 0 : p;
203
+ };
204
+
205
+ // Process properties and prepare "from" and "to" values
206
+ if (!defsCopy[c].callback &&
183
207
  (Array.isArray(defsCopy[c].property) || (defsCopy[c].property !== undefined && (defsCopy[c].property = [defsCopy[c].property])))) {
184
208
 
185
209
  defsCopy[c].property.forEach(function (propItem) {
186
- if (!fromIsObject) valFrom = defsCopy[c].from;
187
- else fromItem = defsCopy[c]["from"][propIndex];
210
+ const fromIsObject = typeof defsCopy[c].from === "object";
211
+ const toIsObject = typeof defsCopy[c].to === "object";
212
+
213
+ // Safely extract from/to values based on their types
214
+ if (!fromIsObject) valFrom = parseVal(defsCopy[c].from);
215
+ else fromItem = defsCopy[c]["from"][propIndex] || 0;
188
216
 
189
- if (!toIsObject) valTo = defsCopy[c].to;
190
- else toItem = defsCopy[c]["to"][propIndex];
217
+ if (!toIsObject) valTo = parseVal(defsCopy[c].to);
218
+ else toItem = defsCopy[c]["to"][propIndex] || 0;
191
219
 
192
220
  if (typeof propItem === "object") {
193
221
  let propName = Object.keys(propItem)[0];
194
222
  if (!Array.isArray(propItem[propName])) propItem[propName] = [propItem[propName]];
195
223
 
224
+ // Handle Colors and Transform objects
196
225
  if ((propName.toLowerCase().indexOf("color") !== -1 && (staging.color[propName] = COLOR_PROPERTIES[propName])) ||
197
226
  propName.toLowerCase().indexOf("transform") !== -1) {
198
227
 
@@ -204,65 +233,62 @@
204
233
  if (propName.toLowerCase() === "transform") staging[propName][innerProp] = 0;
205
234
  else staging.color[propName][innerProp] = 0;
206
235
 
236
+ // Validate and assign nested properties
207
237
  if (fromIsObject) {
208
- if (fromItem[propName] !== undefined) {
209
- if (typeof fromItem[propName] === "number") {
210
- valFrom = staging["from"][propName][innerProp] = fromItem[propName];
211
- } else if (Array.isArray(fromItem[propName])) {
212
- valFrom = staging["from"][propName][innerProp] = fromItem[propName][inner] !== undefined ? fromItem[propName][inner] : valFrom;
213
- } else if (fromItem[propName][innerProp] !== undefined) {
214
- valFrom = staging["from"][propName][innerProp] = fromItem[propName][innerProp];
215
- }
216
- } else {
217
- staging["from"][propName][innerProp] = valFrom;
218
- }
238
+ let raw = (fromItem[propName] !== undefined) ? (Array.isArray(fromItem[propName]) ? fromItem[propName][inner] : fromItem[propName][innerProp]) : valFrom;
239
+ staging["from"][propName][innerProp] = parseVal(raw);
219
240
  } else {
220
- staging["from"][propName][innerProp] = defsCopy[c].from;
241
+ staging["from"][propName][innerProp] = parseVal(defsCopy[c].from);
221
242
  }
222
243
 
223
244
  if (toIsObject) {
224
- if (toItem[propName] !== undefined) {
225
- if (typeof toItem[propName] === "number") {
226
- valTo = staging["to"][propName][innerProp] = toItem[propName];
227
- } else if (Array.isArray(toItem[propName])) {
228
- valTo = staging["to"][propName][innerProp] = toItem[propName][inner] !== undefined ? toItem[propName][inner] : valTo;
229
- } else if (toItem[propName][innerProp] !== undefined) {
230
- valTo = staging["to"][propName][innerProp] = toItem[propName][innerProp];
231
- }
232
- } else {
233
- staging["to"][propName][innerProp] = valTo;
234
- }
245
+ let raw = (toItem[propName] !== undefined) ? (Array.isArray(toItem[propName]) ? toItem[propName][inner] : toItem[propName][innerProp]) : valTo;
246
+ staging["to"][propName][innerProp] = parseVal(raw);
235
247
  } else {
236
- staging["to"][propName][innerProp] = defsCopy[c].to;
248
+ staging["to"][propName][innerProp] = parseVal(defsCopy[c].to);
237
249
  }
238
250
  inner++;
239
251
  });
240
252
  propIndex++;
241
253
  }
242
254
  } else {
255
+ // Handle simple numeric CSS properties (width, height, etc.)
243
256
  if (fromIsObject) {
244
- valFrom = staging["from"][propItem] = fromItem[propItem] !== undefined ? fromItem[propItem] : (fromItem !== undefined ? fromItem : valFrom);
257
+ let raw = fromItem[propItem] !== undefined ? fromItem[propItem] : (fromItem !== undefined ? fromItem : valFrom);
258
+ staging["from"][propItem] = parseVal(raw);
245
259
  } else {
246
- staging["from"][propItem] = defsCopy[c].from;
260
+ staging["from"][propItem] = parseVal(defsCopy[c].from);
247
261
  }
248
262
 
249
263
  if (toIsObject) {
250
- valTo = staging["to"][propItem] = toItem[propItem] !== undefined ? toItem[propItem] : (toItem !== undefined ? toItem : valTo);
264
+ let raw = toItem[propItem] !== undefined ? toItem[propItem] : (toItem !== undefined ? toItem : valTo);
265
+ staging["to"][propItem] = parseVal(raw);
251
266
  } else {
252
- staging["to"][propItem] = defsCopy[c].to;
267
+ staging["to"][propItem] = parseVal(defsCopy[c].to);
253
268
  }
254
269
  propIndex++;
255
270
  }
256
271
  });
257
272
  }
273
+
274
+ // 3. Callback Safety Validation
275
+ // Ensure hooks are actual functions to prevent execution errors
276
+ if (defsCopy[c].onStep && typeof defsCopy[c].onStep !== 'function') defsCopy[c].onStep = null;
277
+ if (defsCopy[c].onComplete && typeof defsCopy[c].onComplete !== 'function') defsCopy[c].onComplete = null;
278
+
279
+ // Finalize staging data and store it
258
280
  defsCopy[c].storeValueAnim = copyObj(staging);
259
281
  staging = { color: {}, transform: {}, from: {}, to: {} };
260
282
  }
283
+
284
+ // Determine if the next item starts a new sequence group
261
285
  if (definitions[c + 1] !== undefined && (Array.isArray(definitions[c + 1]) || isElement(definitions[c + 1]))) {
262
286
  expectNextGroup = true;
263
287
  grouped = [];
264
288
  }
265
289
  }
290
+
291
+ // Switch to second pass to execute the runners
266
292
  if (c === total - 1 && !secondPass) {
267
293
  expectNextGroup = false;
268
294
  secondPass = true;
@@ -276,9 +302,11 @@
276
302
  const declaredAnim = defsCopyLocal[configIndex].typeAnimation;
277
303
  let alternateAnim = declaredAnim;
278
304
 
305
+ // Sanitize timing inputs
279
306
  conf.timeline = !isNaN(Number(conf.timeline)) ? Number(conf.timeline) : 0;
280
307
  conf.startafter = !isNaN(Number(conf.startafter)) ? Number(conf.startafter) : 0;
281
308
 
309
+ // Prepare looping and easing alternates
282
310
  if (conf.boucle) {
283
311
  conf.delay = !isNaN(Number(conf.delay)) ? Number(conf.delay) : undefined;
284
312
  if (conf.boucleType === "returnRepeat" || conf.boucleType === "repeatReturn") {
@@ -291,6 +319,7 @@
291
319
  let isPaused = false;
292
320
  let pauseStart;
293
321
 
322
+ // Initialize event-based pause/resume logic if configured
294
323
  if (conf.pause && Array.isArray(conf.pause)) {
295
324
  const eventCfg = conf.pause[1] || "e:click|false";
296
325
  const parts = eventCfg.replace('e:', '').split('|');
@@ -315,6 +344,7 @@
315
344
 
316
345
  const startedAt = Date.now();
317
346
 
347
+ // Initialize state storage for each element in the group
318
348
  group.forEach(function (el, idx) {
319
349
  if (!el.storeTransform) el.storeTransform = copyObj(defsCopyLocal[configIndex].storeValueAnim.transform);
320
350
  if (!el.storeColor) {
@@ -325,16 +355,19 @@
325
355
  });
326
356
  }
327
357
 
358
+ // Start staggered animations if timeline offset exists
328
359
  if (conf.timeline !== 0) {
329
360
  frameRunner([el], idx, conf.timeline * idx + conf.startafter, conf.startafter);
330
361
  }
331
362
  });
332
363
 
364
+ // Start simultaneous animations
333
365
  if (conf.timeline === 0) {
334
366
  frameRunner(group, 0, 0 + conf.startafter, conf.startafter);
335
367
  }
336
368
 
337
369
  function frameRunner(targetArray, idx, timeOffset, startAfter) {
370
+ // Manage animation frame cycles
338
371
  if (conf.animFram) cancelAnimationFrame(conf.animFram[idx]);
339
372
  else conf.animFram = {};
340
373
 
@@ -346,6 +379,7 @@
346
379
  let skipCounter;
347
380
  const sv = iterConf.storeValueAnim;
348
381
 
382
+ // The main animation loop using requestAnimationFrame
349
383
  function loop() {
350
384
  if (isPaused) {
351
385
  conf.animFram[idx] = requestAnimationFrame(loop);
@@ -357,6 +391,7 @@
357
391
  const elapsed = Date.now() - (startedAt + timeOffset + pausedAccum);
358
392
 
359
393
  if (elapsed >= 0) {
394
+ // Logic for handling loops, delays, and reversing (yoyo) animations
360
395
  if (iterConf.boucle) {
361
396
  if (iterConf.delay !== undefined) {
362
397
  delay = iterConf.delay;
@@ -398,7 +433,9 @@
398
433
  iterConf.timeEasing = elapsed < iterConf.duration ? elapsed : iterConf.duration;
399
434
  }
400
435
 
436
+ // Update properties if not in delay phase
401
437
  if (!iterConf.skipDelay || iterConf.skip) {
438
+ // Calculate ease factor (0 to 1)
402
439
  eased = Easing[iterConf.changetypeAnim][0](iterConf.timeEasing, 0, 1, iterConf.duration, iterConf, idx);
403
440
 
404
441
  if (iterConf.callback) {
@@ -406,6 +443,7 @@
406
443
  iterConf.callback(el, eased, iterConf, idx !== index ? idx : index);
407
444
  });
408
445
  } else {
446
+ // Apply styles: Transform, Color, or standard Numeric properties
409
447
  iterConf.property.forEach(function (prop) {
410
448
  css = "";
411
449
  let key = typeof prop === "string" ? prop : Object.keys(prop)[0];
@@ -444,6 +482,7 @@
444
482
  }
445
483
  }
446
484
 
485
+ // Continue the loop if animation is still active or looping
447
486
  if (iterConf.boucle || elapsed < iterConf.duration) {
448
487
  conf.animFram[idx] = requestAnimationFrame(loop);
449
488
  }
@@ -757,8 +796,17 @@
757
796
  else return el.style[cssprop];
758
797
  }
759
798
 
799
+ // --- Robust DOM Element Validation ---
760
800
  function isElement(element) {
761
- return element instanceof Element || element instanceof HTMLDocument;
801
+ try {
802
+ // Check for standard DOM Element or HTMLDocument
803
+ return element instanceof Element || element instanceof HTMLDocument;
804
+ } catch (e) {
805
+ // Fallback for environments where Element is not defined
806
+ return (typeof element === "object") &&
807
+ (element.nodeType === 1) &&
808
+ (typeof element.nodeName === "string");
809
+ }
762
810
  }
763
811
 
764
812
  const copyObj = function(obj) {