PluginProbe
Block Animations, Motion & Scroll Effects – Ghost Kit / 3.4.1
Block Animations, Motion & Scroll Effects – Ghost Kit v3.4.1
3.7.2 3.7.1 3.7.0 3.6.1 trunk 1.6.3 2.25.0 3.3.0 3.3.1 3.3.2 3.3.3 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.4.5 3.4.6 3.5.0 3.5.1 3.6.0
ghostkit / gutenberg / blocks / countdown / api.js

api.js in Block Animations, Motion & Scroll Effects – Ghost Kit 3.4.1, at gutenberg/blocks/countdown/api.js

771 lines 16.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /* eslint-disable no-bitwise */
2 /* eslint-disable no-mixed-spaces-and-tabs */
3 /* eslint-disable no-param-reassign */
4
5 import { _n } from '@wordpress/i18n';
6
7 /**
8 * API for countdown.
9 * Thanks https://github.com/mckamey/countdownjs/.
10 *
11 * @public
12 * @param {number} units the units to populate
13 *
14 * @return {Object|number}
15 */
16 export default (() => {
17 const MILLISECONDS = 0x001;
18 const SECONDS = 0x002;
19 const MINUTES = 0x004;
20 const HOURS = 0x008;
21 const DAYS = 0x010;
22 const WEEKS = 0x020;
23 const MONTHS = 0x040;
24 const YEARS = 0x080;
25 const DEFAULTS = YEARS | MONTHS | DAYS | HOURS | MINUTES | SECONDS;
26 const ALL =
27 YEARS |
28 MONTHS |
29 WEEKS |
30 DAYS |
31 HOURS |
32 MINUTES |
33 SECONDS |
34 MILLISECONDS;
35 const MILLISECONDS_PER_SECOND = 1000;
36 const SECONDS_PER_MINUTE = 60;
37 const MINUTES_PER_HOUR = 60;
38 const HOURS_PER_DAY = 24;
39 const MILLISECONDS_PER_DAY =
40 HOURS_PER_DAY *
41 MINUTES_PER_HOUR *
42 SECONDS_PER_MINUTE *
43 MILLISECONDS_PER_SECOND;
44 const DAYS_PER_WEEK = 7;
45 const MONTHS_PER_YEAR = 12;
46
47 const { ceil } = Math;
48 const { floor } = Math;
49
50 /**
51 * @private
52 * @param {Date} ref reference date
53 * @param {number} shift number of months to shift
54 * @return {number} number of days shifted
55 */
56 function borrowMonths(ref, shift) {
57 const prevTime = ref.getTime();
58
59 // increment month by shift
60 ref.setMonth(ref.getMonth() + shift);
61
62 // this is the trickiest since months vary in length
63 return Math.round((ref.getTime() - prevTime) / MILLISECONDS_PER_DAY);
64 }
65
66 /**
67 * @private
68 * @param {Date} ref reference date
69 * @return {number} number of days
70 */
71 function daysPerMonth(ref) {
72 const a = ref.getTime();
73
74 // increment month by 1
75 const b = new Date(a);
76 b.setMonth(ref.getMonth() + 1);
77
78 // this is the trickiest since months vary in length
79 return Math.round((b.getTime() - a) / MILLISECONDS_PER_DAY);
80 }
81
82 /**
83 * @private
84 * @param {Date} ref reference date
85 * @return {number} number of days
86 */
87 function daysPerYear(ref) {
88 const a = ref.getTime();
89
90 // increment year by 1
91 const b = new Date(a);
92 b.setFullYear(ref.getFullYear() + 1);
93
94 // this is the trickiest since years (periodically) vary in length
95 return Math.round((b.getTime() - a) / MILLISECONDS_PER_DAY);
96 }
97
98 /**
99 * Applies the Timespan to the given date.
100 *
101 * @private
102 * @param {Timespan} ts time span
103 * @param {Date=} date current date
104 *
105 * @return {Date} result date.
106 */
107 function addToDate(ts, date) {
108 date =
109 date instanceof Date || (date !== null && Number.isFinite(date))
110 ? new Date(+date)
111 : new Date();
112
113 if (!ts) {
114 return date;
115 }
116
117 // if there is a value field, use it directly
118 let value = +ts.value || 0;
119 if (value) {
120 date.setTime(date.getTime() + value);
121 return date;
122 }
123
124 value = +ts.milliseconds || 0;
125 if (value) {
126 date.setMilliseconds(date.getMilliseconds() + value);
127 }
128
129 value = +ts.seconds || 0;
130 if (value) {
131 date.setSeconds(date.getSeconds() + value);
132 }
133
134 value = +ts.minutes || 0;
135 if (value) {
136 date.setMinutes(date.getMinutes() + value);
137 }
138
139 value = +ts.hours || 0;
140 if (value) {
141 date.setHours(date.getHours() + value);
142 }
143
144 value = +ts.weeks || 0;
145 if (value) {
146 value *= DAYS_PER_WEEK;
147 }
148
149 value += +ts.days || 0;
150 if (value) {
151 date.setDate(date.getDate() + value);
152 }
153
154 value = +ts.months || 0;
155 if (value) {
156 date.setMonth(date.getMonth() + value);
157 }
158
159 value += +ts.years || 0;
160 if (value) {
161 date.setFullYear(date.getFullYear() + value);
162 }
163
164 return date;
165 }
166
167 /**
168 * Timespan representation of a duration of time
169 *
170 * @private
171 * @this {Timespan}
172 * @class
173 */
174 function Timespan() {}
175
176 /**
177 * Borrow any underflow units, carry any overflow units
178 *
179 * @private
180 * @param {Timespan} ts time span
181 * @param {string} toUnit unit name
182 */
183 function rippleRounded(ts, toUnit) {
184 switch (toUnit) {
185 case 'seconds':
186 if (
187 ts.seconds !== SECONDS_PER_MINUTE ||
188 Number.isNaN(ts.minutes)
189 ) {
190 return;
191 }
192 // ripple seconds up to minutes
193 ts.minutes += 1;
194 ts.seconds = 0;
195
196 /* falls through */
197 case 'minutes':
198 if (ts.minutes !== MINUTES_PER_HOUR || Number.isNaN(ts.hours)) {
199 return;
200 }
201 // ripple minutes up to hours
202 ts.hours += 1;
203 ts.minutes = 0;
204
205 /* falls through */
206 case 'hours':
207 if (ts.hours !== HOURS_PER_DAY || Number.isNaN(ts.days)) {
208 return;
209 }
210 // ripple hours up to days
211 ts.days += 1;
212 ts.hours = 0;
213
214 /* falls through */
215 case 'days':
216 if (ts.days !== DAYS_PER_WEEK || Number.isNaN(ts.weeks)) {
217 return;
218 }
219 // ripple days up to weeks
220 ts.weeks += 1;
221 ts.days = 0;
222
223 /* falls through */
224 case 'weeks':
225 if (
226 ts.weeks !== daysPerMonth(ts.refMonth) / DAYS_PER_WEEK ||
227 Number.isNaN(ts.months)
228 ) {
229 return;
230 }
231 // ripple weeks up to months
232 ts.months += 1;
233 ts.weeks = 0;
234
235 /* falls through */
236 case 'months':
237 if (ts.months !== MONTHS_PER_YEAR || Number.isNaN(ts.years)) {
238 return;
239 }
240 // ripple months up to years
241 ts.years += 1;
242 ts.months = 0;
243 // no default
244 }
245 }
246
247 /**
248 * Ripple up partial units one place
249 *
250 * @private
251 * @param {Timespan} ts timespan
252 * @param {number} frac accumulated fractional value
253 * @param {string} fromUnit source unit name
254 * @param {string} toUnit target unit name
255 * @param {number} conversion multiplier between units
256 * @param {number} digits max number of decimal digits to output
257 * @return {number} new fractional value
258 */
259 function fraction(ts, frac, fromUnit, toUnit, conversion, digits) {
260 if (ts[fromUnit] >= 0) {
261 frac += ts[fromUnit];
262 delete ts[fromUnit];
263 }
264
265 frac /= conversion;
266 if (frac + 1 <= 1) {
267 // drop if below machine epsilon
268 return 0;
269 }
270
271 if (ts[toUnit] >= 0) {
272 // ensure does not have more than specified number of digits
273 ts[toUnit] = +(ts[toUnit] + frac).toFixed(digits);
274 rippleRounded(ts, toUnit);
275 return 0;
276 }
277
278 return frac;
279 }
280
281 /**
282 * Ripple up partial units to next existing
283 *
284 * @private
285 * @param {Timespan} ts timespan
286 * @param {number} digits max number of decimal digits to output
287 */
288 function fractional(ts, digits) {
289 let frac = fraction(
290 ts,
291 0,
292 'milliseconds',
293 'seconds',
294 MILLISECONDS_PER_SECOND,
295 digits
296 );
297 if (!frac) {
298 return;
299 }
300
301 frac = fraction(
302 ts,
303 frac,
304 'seconds',
305 'minutes',
306 SECONDS_PER_MINUTE,
307 digits
308 );
309 if (!frac) {
310 return;
311 }
312
313 frac = fraction(ts, frac, 'minutes', 'hours', MINUTES_PER_HOUR, digits);
314 if (!frac) {
315 return;
316 }
317
318 frac = fraction(ts, frac, 'hours', 'days', HOURS_PER_DAY, digits);
319 if (!frac) {
320 return;
321 }
322
323 frac = fraction(ts, frac, 'days', 'weeks', DAYS_PER_WEEK, digits);
324 if (!frac) {
325 return;
326 }
327
328 frac = fraction(
329 ts,
330 frac,
331 'weeks',
332 'months',
333 daysPerMonth(ts.refMonth) / DAYS_PER_WEEK,
334 digits
335 );
336 if (!frac) {
337 return;
338 }
339
340 frac = fraction(
341 ts,
342 frac,
343 'months',
344 'years',
345 daysPerYear(ts.refMonth) / daysPerMonth(ts.refMonth),
346 digits
347 );
348 if (!frac) {
349 return;
350 }
351
352 // should never reach this with remaining fractional value
353 if (frac) {
354 throw new Error('Fractional unit overflow');
355 }
356 }
357
358 /**
359 * Borrow any underflow units, carry any overflow units
360 *
361 * @private
362 * @param {Timespan} ts timespan
363 */
364 function ripple(ts) {
365 let x;
366
367 if (ts.milliseconds < 0) {
368 // ripple seconds down to milliseconds
369 x = ceil(-ts.milliseconds / MILLISECONDS_PER_SECOND);
370 ts.seconds -= x;
371 ts.milliseconds += x * MILLISECONDS_PER_SECOND;
372 } else if (ts.milliseconds >= MILLISECONDS_PER_SECOND) {
373 // ripple milliseconds up to seconds
374 ts.seconds += floor(ts.milliseconds / MILLISECONDS_PER_SECOND);
375 ts.milliseconds %= MILLISECONDS_PER_SECOND;
376 }
377
378 if (ts.seconds < 0) {
379 // ripple minutes down to seconds
380 x = ceil(-ts.seconds / SECONDS_PER_MINUTE);
381 ts.minutes -= x;
382 ts.seconds += x * SECONDS_PER_MINUTE;
383 } else if (ts.seconds >= SECONDS_PER_MINUTE) {
384 // ripple seconds up to minutes
385 ts.minutes += floor(ts.seconds / SECONDS_PER_MINUTE);
386 ts.seconds %= SECONDS_PER_MINUTE;
387 }
388
389 if (ts.minutes < 0) {
390 // ripple hours down to minutes
391 x = ceil(-ts.minutes / MINUTES_PER_HOUR);
392 ts.hours -= x;
393 ts.minutes += x * MINUTES_PER_HOUR;
394 } else if (ts.minutes >= MINUTES_PER_HOUR) {
395 // ripple minutes up to hours
396 ts.hours += floor(ts.minutes / MINUTES_PER_HOUR);
397 ts.minutes %= MINUTES_PER_HOUR;
398 }
399
400 if (ts.hours < 0) {
401 // ripple days down to hours
402 x = ceil(-ts.hours / HOURS_PER_DAY);
403 ts.days -= x;
404 ts.hours += x * HOURS_PER_DAY;
405 } else if (ts.hours >= HOURS_PER_DAY) {
406 // ripple hours up to days
407 ts.days += floor(ts.hours / HOURS_PER_DAY);
408 ts.hours %= HOURS_PER_DAY;
409 }
410
411 while (ts.days < 0) {
412 // NOTE: never actually seen this loop more than once
413
414 // ripple months down to days
415 ts.months -= 1;
416 ts.days += borrowMonths(ts.refMonth, 1);
417 }
418
419 // weeks is always zero here
420
421 if (ts.days >= DAYS_PER_WEEK) {
422 // ripple days up to weeks
423 ts.weeks += floor(ts.days / DAYS_PER_WEEK);
424 ts.days %= DAYS_PER_WEEK;
425 }
426
427 if (ts.months < 0) {
428 // ripple years down to months
429 x = ceil(-ts.months / MONTHS_PER_YEAR);
430 ts.years -= x;
431 ts.months += x * MONTHS_PER_YEAR;
432 } else if (ts.months >= MONTHS_PER_YEAR) {
433 // ripple months up to years
434 ts.years += floor(ts.months / MONTHS_PER_YEAR);
435 ts.months %= MONTHS_PER_YEAR;
436 }
437 }
438
439 /**
440 * Remove any units not requested
441 *
442 * @private
443 * @param {Timespan} ts timespan
444 * @param {number} units the units to populate
445 * @param {number} max number of labels to output
446 * @param {number} digits max number of decimal digits to output
447 */
448 function pruneUnits(ts, units, max, digits) {
449 let count = 0;
450
451 if (!(units & YEARS) || count >= max) {
452 // ripple years down to months
453 ts.months += ts.years * MONTHS_PER_YEAR;
454 delete ts.years;
455 } else if (ts.years) {
456 count += 1;
457 }
458
459 if (!(units & MONTHS) || count >= max) {
460 // ripple months down to days
461 if (ts.months) {
462 ts.days += borrowMonths(ts.refMonth, ts.months);
463 }
464 delete ts.months;
465
466 if (ts.days >= DAYS_PER_WEEK) {
467 // ripple day overflow back up to weeks
468 ts.weeks += floor(ts.days / DAYS_PER_WEEK);
469 ts.days %= DAYS_PER_WEEK;
470 }
471 } else if (ts.months) {
472 count += 1;
473 }
474
475 if (!(units & WEEKS) || count >= max) {
476 // ripple weeks down to days
477 ts.days += ts.weeks * DAYS_PER_WEEK;
478 delete ts.weeks;
479 } else if (ts.weeks) {
480 count += 1;
481 }
482
483 if (!(units & DAYS) || count >= max) {
484 // ripple days down to hours
485 ts.hours += ts.days * HOURS_PER_DAY;
486 delete ts.days;
487 } else if (ts.days) {
488 count += 1;
489 }
490
491 if (!(units & HOURS) || count >= max) {
492 // ripple hours down to minutes
493 ts.minutes += ts.hours * MINUTES_PER_HOUR;
494 delete ts.hours;
495 } else if (ts.hours) {
496 count += 1;
497 }
498
499 if (!(units & MINUTES) || count >= max) {
500 // ripple minutes down to seconds
501 ts.seconds += ts.minutes * SECONDS_PER_MINUTE;
502 delete ts.minutes;
503 } else if (ts.minutes) {
504 count += 1;
505 }
506
507 if (!(units & SECONDS) || count >= max) {
508 // ripple seconds down to milliseconds
509 ts.milliseconds += ts.seconds * MILLISECONDS_PER_SECOND;
510 delete ts.seconds;
511 } else if (ts.seconds) {
512 count += 1;
513 }
514
515 // nothing to ripple milliseconds down to
516 // so ripple back up to smallest existing unit as a fractional value
517 if (!(units & MILLISECONDS) || count >= max) {
518 fractional(ts, digits);
519 }
520 }
521
522 /**
523 * Populates the Timespan object
524 *
525 * @private
526 * @param {Timespan} ts timespan
527 * @param {?Date} start the starting date
528 * @param {?Date} end the ending date
529 * @param {number} units the units to populate
530 * @param {number} max number of labels to output
531 * @param {number} digits max number of decimal digits to output
532 *
533 * @return {Timespan} timespan
534 */
535 function populate(ts, start, end, units, max, digits) {
536 const now = new Date();
537
538 start = start || now;
539 end = end || now;
540
541 ts.start = start;
542 ts.end = end;
543 ts.units = units;
544
545 ts.value = end.getTime() - start.getTime();
546 if (ts.value < 0) {
547 // swap if reversed
548 const tmp = end;
549 end = start;
550 start = tmp;
551 }
552
553 // reference month for determining days in month
554 ts.refMonth = new Date(
555 start.getFullYear(),
556 start.getMonth(),
557 15,
558 12,
559 0,
560 0
561 );
562 try {
563 // reset to initial deltas
564 ts.years = end.getFullYear() - start.getFullYear();
565 ts.months = end.getMonth() - start.getMonth();
566 ts.weeks = 0;
567 ts.days = end.getDate() - start.getDate();
568 ts.hours = end.getHours() - start.getHours();
569 ts.minutes = end.getMinutes() - start.getMinutes();
570 ts.seconds = end.getSeconds() - start.getSeconds();
571 ts.milliseconds = end.getMilliseconds() - start.getMilliseconds();
572
573 ripple(ts);
574 pruneUnits(ts, units, max, digits);
575 } finally {
576 delete ts.refMonth;
577 }
578
579 return ts;
580 }
581
582 /**
583 * Determine an appropriate refresh rate based upon units
584 *
585 * @private
586 * @param {number} units the units to populate
587 * @return {number} milliseconds to delay
588 */
589 function getDelay(units = []) {
590 if (units.indexOf('milliseconds') !== -1) {
591 // refresh very quickly
592 return MILLISECONDS_PER_SECOND / 30; // 30Hz
593 }
594
595 if (units.indexOf('seconds') !== -1) {
596 // refresh every second
597 return MILLISECONDS_PER_SECOND; // 1Hz
598 }
599
600 if (units.indexOf('minutes') !== -1) {
601 // refresh every minute
602 return MILLISECONDS_PER_SECOND * SECONDS_PER_MINUTE;
603 }
604
605 if (units.indexOf('hours') !== -1) {
606 // refresh hourly
607 return (
608 MILLISECONDS_PER_SECOND * SECONDS_PER_MINUTE * MINUTES_PER_HOUR
609 );
610 }
611
612 if (units.indexOf('days') !== -1) {
613 // refresh daily
614 return (
615 MILLISECONDS_PER_SECOND *
616 SECONDS_PER_MINUTE *
617 MINUTES_PER_HOUR *
618 HOURS_PER_DAY
619 );
620 }
621
622 // refresh the rest weekly
623 return (
624 MILLISECONDS_PER_SECOND *
625 SECONDS_PER_MINUTE *
626 MINUTES_PER_HOUR *
627 HOURS_PER_DAY *
628 DAYS_PER_WEEK
629 );
630 }
631
632 /**
633 * Units list to units data.
634 *
635 * @param {Array} unitsList list of all units to display
636 *
637 * @return {number} units data.
638 */
639 function unitsListToData(unitsList) {
640 let units = ~ALL;
641
642 unitsList.forEach((unit) => {
643 if (countdown[unit.toUpperCase()]) {
644 units |= countdown[unit.toUpperCase()];
645 }
646 });
647
648 // ensure some units or use defaults
649 units = +units || DEFAULTS;
650
651 return units;
652 }
653
654 /**
655 * API entry point
656 *
657 * @public
658 * @param {Date|number|Timespan} start the starting date
659 * @param {Date|number|Timespan} end the ending date
660 * @param {Array} unitsList list of all units to display
661 * @param {number} digits max number of decimal digits to output
662 *
663 * @return {Timespan} timespan
664 */
665 function countdown(start, end, unitsList, digits) {
666 // max must be positive
667 const max = unitsList.length > 0 ? unitsList.length : NaN;
668
669 // clamp digits to an integer between [0, 20]
670 if (digits > 0) {
671 digits = digits < 20 ? Math.round(digits) : 20;
672 } else {
673 digits = 0;
674 }
675
676 // ensure start date
677 let startTS = null;
678 if (!(start instanceof Date)) {
679 if (start !== null && Number.isFinite(start)) {
680 start = new Date(+start);
681 } else {
682 if (typeof startTS === 'object') {
683 startTS = start;
684 }
685 start = null;
686 }
687 }
688
689 // ensure end date
690 let endTS = null;
691 if (!(end instanceof Date)) {
692 if (end !== null && Number.isFinite(end)) {
693 end = new Date(+end);
694 } else {
695 if (typeof end === 'object') {
696 endTS = end;
697 }
698 end = null;
699 }
700 }
701
702 // must wait to interpret timespans until after resolving dates
703 if (startTS) {
704 start = addToDate(startTS, end);
705 }
706 if (endTS) {
707 end = addToDate(endTS, start);
708 }
709
710 if (!start && !end) {
711 // used for unit testing
712 return new Timespan();
713 }
714
715 const units = unitsListToData(unitsList);
716
717 return populate(new Timespan(), start, end, units, max, digits);
718 }
719
720 countdown.MILLISECONDS = MILLISECONDS;
721 countdown.SECONDS = SECONDS;
722 countdown.MINUTES = MINUTES;
723 countdown.HOURS = HOURS;
724 countdown.DAYS = DAYS;
725 countdown.WEEKS = WEEKS;
726 countdown.MONTHS = MONTHS;
727 countdown.YEARS = YEARS;
728 countdown.DEFAULTS = DEFAULTS;
729 countdown.ALL = ALL;
730
731 countdown.getDelay = getDelay;
732
733 countdown.formatUnit = (number, label) => {
734 // prepare label.
735 switch (label) {
736 case 'years':
737 label = _n('Year', 'Years', number, 'ghostkit');
738 break;
739 case 'months':
740 label = _n('Month', 'Months', number, 'ghostkit');
741 break;
742 case 'weeks':
743 label = _n('Week', 'Weeks', number, 'ghostkit');
744 break;
745 case 'days':
746 label = _n('Day', 'Days', number, 'ghostkit');
747 break;
748 case 'hours':
749 label = _n('Hour', 'Hours', number, 'ghostkit');
750 break;
751 case 'minutes':
752 label = _n('Minute', 'Minutes', number, 'ghostkit');
753 break;
754 case 'seconds':
755 label = _n('Second', 'Seconds', number, 'ghostkit');
756 break;
757 // no default
758 }
759
760 // additional 0 for number.
761 number = `${number < 10 ? '0' : ''}${number}`;
762
763 return {
764 number,
765 label,
766 };
767 };
768
769 return countdown;
770 })();
771