UNPKG

malevic

Version:

Malevič.js - minimalistic reactive UI library

544 lines (520 loc) 20.1 kB
/* malevic@0.20.2 - Aug 10, 2024 */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('malevic/dom'), require('malevic/string')) : typeof define === 'function' && define.amd ? define(['exports', 'malevic/dom', 'malevic/string'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.Malevic = global.Malevic || {}, global.Malevic.Animation = {}), global.Malevic.DOM, global.Malevic.String)); })(this, (function (exports, malevicDOM, malevicString) { 'use strict'; function _interopNamespaceDefault(e) { var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var malevicDOM__namespace = /*#__PURE__*/_interopNamespaceDefault(malevicDOM); var malevicString__namespace = /*#__PURE__*/_interopNamespaceDefault(malevicString); /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol */ var __assign = function() { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; function isObject(value) { return value != null && typeof value === 'object'; } function isPlainObject(value) { return isObject(value) && Object.getPrototypeOf(value) === Object.prototype; } function last(items, index) { if (index === void 0) { index = 0; } var target = items.length - 1 - index; return items[target]; } function interpolate(t, from, to) { return from * (1 - t) + to * t; } var interpolateNumbers = function (from, to) { return function (t) { return interpolate(t, from, to); }; }; function createNumRegExp() { return /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[e][-+]?\d+)?/gim; } function getNumPositions(line) { var positions = []; var regexp = createNumRegExp(); var match; while ((match = regexp.exec(line))) { positions.push({ index: match.index, length: match[0].length }); } return positions; } var interpolateNumbersInString = function (from, to) { var posFrom = getNumPositions(from); var posTo = getNumPositions(to); return function (t) { var result = ''; var na, nb, n; var last = 0; for (var i = 0; i < posTo.length; i++) { result += to.substring(last, posTo[i].index); na = parseFloat(from.substr(posFrom[i].index, posFrom[i].length)); nb = parseFloat(to.substr(posTo[i].index, posTo[i].length)); n = interpolate(t, na, nb); result += n.toString(); last = posTo[i].index + posTo[i].length; } result += to.substring(last); return result; }; }; function identity(x) { return x; } var defaultTiming = { delay: 0, duration: 250, easing: 'ease', }; var AnimationDeclaration = (function () { function AnimationDeclaration() { this.$spec = { initial: null, timeline: [], interpolate: null, output: identity, done: null, }; } AnimationDeclaration.prototype.initial = function (value) { this.$spec.initial = value; return this; }; AnimationDeclaration.prototype.from = function (from) { if (this.$spec.timeline.length > 0) { throw new Error('Starting keyframe was already declared'); } this.$spec.timeline.push({ from: from, to: null, timing: __assign({}, defaultTiming), }); return this; }; AnimationDeclaration.prototype.to = function (to, timing) { if (!this.$spec.interpolate) { if (typeof to === 'number') { this.$spec.interpolate = interpolateNumbers; } else if (typeof to === 'string') { this.$spec.interpolate = interpolateNumbersInString; } } var last$1 = last(this.$spec.timeline); if (last$1 && last$1.to == null) { last$1.to = to; if (timing) { last$1.timing = __assign(__assign({}, last$1.timing), timing); } } else { this.$spec.timeline.push({ from: last$1 ? last$1.to : null, to: to, timing: __assign(__assign({}, defaultTiming), (timing ? timing : {})), }); } return this; }; AnimationDeclaration.prototype.interpolate = function (interpolate) { this.$spec.interpolate = interpolate; return this; }; AnimationDeclaration.prototype.output = function (output) { this.$spec.output = output; return this; }; AnimationDeclaration.prototype.done = function (callback) { this.$spec.done = callback; return this; }; AnimationDeclaration.prototype.spec = function () { return this.$spec; }; return AnimationDeclaration; }()); function styles(declarations) { return Object.keys(declarations) .filter(function (cssProp) { return declarations[cssProp] != null; }) .map(function (cssProp) { return "".concat(cssProp, ": ").concat(declarations[cssProp], ";"); }) .join(' '); } function setInlineCSSPropertyValue(element, prop, $value) { if ($value != null && $value !== '') { var value = String($value); var important = ''; if (value.endsWith('!important')) { value = value.substring(0, value.length - 10); important = 'important'; } element.style.setProperty(prop, value, important); } else { element.style.removeProperty(prop); } } function clamp(x, min, max) { return Math.min(max, Math.max(min, x)); } var easings = { linear: function (t) { return t; }, ease: function (t) { var t0 = (1 - Math.cos(t * Math.PI)) / 2; var t1 = Math.sqrt(1 - Math.pow(t - 1, 2)); var t2 = Math.sin((t * Math.PI) / 2); return t0 * (1 - t2) + t1 * t2; }, 'ease-in': function (t) { var r = 1 - Math.cos((t * Math.PI) / 2); return r > 1 - 1e-15 ? 1 : r; }, 'ease-out': function (t) { return Math.sin((t * Math.PI) / 2); }, 'ease-in-out': function (t) { return (1 - Math.cos(t * Math.PI)) / 2; }, }; var Animation = (function () { function Animation(spec, callback) { if (!spec.interpolate) { throw new Error('No interpolator provided'); } this.interpolate = spec.interpolate; this.output = spec.output; this.callback = callback; this.doneCallback = spec.done; var total = 0; this.timeline = spec.timeline.map(function (spec) { var standby = total; var start = standby + spec.timing.delay; var end = start + spec.timing.duration; total = end; return { standby: standby, start: start, end: end, spec: spec, interpolate: null }; }); this.isComplete = false; } Animation.prototype.tick = function (time) { if (this.startTime == null) { this.startTime = time; } var duration = time - this.startTime; var timeline = this.timeline; var interval; for (var i = timeline.length - 1; i >= 0; i--) { var _a = timeline[i], standby = _a.standby, end_1 = _a.end; if (duration >= end_1 || (duration >= standby && duration <= end_1)) { interval = timeline[i]; break; } } var start = interval.start, end = interval.end, spec = interval.spec; if (interval === last(timeline) && duration >= end) { this.isComplete = true; } if (!interval.interpolate) { interval.interpolate = this.interpolate(spec.from, spec.to); } var ease = typeof spec.timing.easing === 'string' ? easings[spec.timing.easing] : spec.timing.easing; var t = duration < start ? 0 : start === end ? 1 : clamp((duration - start) / (end - start), 0, 1); var eased = ease(t); var value = interval.interpolate.call(null, eased); this.lastValue = value; var output = this.output.call(null, value); this.callback.call(null, output); }; Animation.prototype.value = function () { return this.lastValue; }; Animation.prototype.complete = function () { return this.isComplete; }; Animation.prototype.finalize = function () { if (this.doneCallback) { this.doneCallback.call(null); } }; return Animation; }()); function createTimer() { var currentTime = null; var frameId = null; var isRunning = false; var callbacks = []; function work() { currentTime = performance.now(); callbacks.forEach(function (cb) { return cb(currentTime); }); if (isRunning) { frameId = requestAnimationFrame(work); } } function run() { isRunning = true; currentTime = performance.now(); frameId = requestAnimationFrame(work); } function stop() { cancelAnimationFrame(frameId); frameId = null; currentTime = null; isRunning = false; } function tick(callback) { callbacks.push(callback); } function time() { return currentTime; } function running() { return isRunning; } return { run: run, stop: stop, tick: tick, time: time, running: running, }; } var timer = createTimer(); timer.tick(function (time) { return Array.from(scheduledAnimations.values()).forEach(function (animation) { return animationTick(animation, time); }); }); function animationTick(animation, time) { animation.tick(time); if (animation.complete()) { cancelAnimation(animation); animation.finalize(); } } var animationsByDeclaration = new WeakMap(); var scheduledAnimations = new Set(); function scheduleAnimation(declaration, tick) { var animation = new Animation(declaration.spec(), tick); scheduledAnimations.add(animation); animationsByDeclaration.set(declaration, animation); !timer.running() && timer.run(); animationTick(animation, timer.time()); } function cancelAnimation(animation) { scheduledAnimations.delete(animation); if (scheduledAnimations.size === 0) { timer.stop(); } } function getScheduledAnimation(declaration) { var animation = animationsByDeclaration.get(declaration); if (animation && scheduledAnimations.has(animation)) { return animation; } return null; } function isAnimatedStyleObj(value) { return (isPlainObject(value) && Object.values(value).some(function (v) { return v instanceof AnimationDeclaration; })); } function handleAnimationDeclaration(value, prev, callback) { var spec = value.spec(); var specStartValue = spec.timeline[0].from; var specEndValue = last(spec.timeline).to; var prevEndValue; if (prev instanceof AnimationDeclaration) { var prevAnimation = getScheduledAnimation(prev); if (prevAnimation) { cancelAnimation(prevAnimation); prevEndValue = prevAnimation.value(); } else { var prevSpec = prev.spec(); prevEndValue = last(prevSpec.timeline).to; } } else if (typeof prev === typeof specEndValue && prev != null && specEndValue != null && prev.constructor === specEndValue.constructor) { prevEndValue = prev; } var startFrom; if (specStartValue != null) { startFrom = specStartValue; } else if (prevEndValue != null) { startFrom = prevEndValue; } else if (spec.initial != null) { startFrom = spec.initial; } if (startFrom == null) { var endValue = spec.output(specEndValue); callback(endValue); } else { spec.timeline[0].from = startFrom; scheduleAnimation(value, callback); } } function tryCancelAnimation(value) { if (value instanceof AnimationDeclaration) { var animation = getScheduledAnimation(value); if (animation) { cancelAnimation(animation); } } } var setAttributePlugin = function (_a) { var element = _a.element, attr = _a.attr, value = _a.value, prev = _a.prev; if (value instanceof AnimationDeclaration) { handleAnimationDeclaration(value, prev, function (output) { return element.setAttribute(attr, output); }); return true; } else if (isAnimatedStyleObj(prev)) { Object.values(prev).forEach(function (v) { return tryCancelAnimation(v); }); } else { tryCancelAnimation(prev); } return null; }; var setStyleAttributePlugin = function (_a) { var element = _a.element, attr = _a.attr, value = _a.value, prev = _a.prev; if (attr === 'style') { if (isAnimatedStyleObj(value)) { var newStyle_1 = value; var prevStyle_1; if (isPlainObject(prev)) { prevStyle_1 = prev; Object.keys(prevStyle_1) .filter(function (prop) { return !newStyle_1.hasOwnProperty(prop); }) .forEach(function (prop) { tryCancelAnimation(prevStyle_1[prop]); setInlineCSSPropertyValue(element, prop, null); }); } else { prevStyle_1 = {}; element.removeAttribute('style'); tryCancelAnimation(prev); } Object.entries(newStyle_1).forEach(function (_a) { var prop = _a[0], v = _a[1]; var prevValue = prevStyle_1[prop]; if (v instanceof AnimationDeclaration) { handleAnimationDeclaration(v, prevValue, function (output) { setInlineCSSPropertyValue(element, prop, output); }); } else { tryCancelAnimation(prevValue); setInlineCSSPropertyValue(element, prop, v); } }); return true; } else if (isAnimatedStyleObj(prev)) { Object.values(prev).forEach(function (v) { return tryCancelAnimation(v); }); } } return null; }; function getStartOutput(spec) { return spec.output(spec.timeline[0].from != null ? spec.timeline[0].from : spec.initial != null ? spec.initial : last(spec.timeline).to); } var stringifyAttributePlugin = function (_a) { var value = _a.value; if (value instanceof AnimationDeclaration) { var spec = value.spec(); return malevicString.escapeHTML(String(getStartOutput(spec))); } return null; }; var stringifyStyleAttrPlugin = function (_a) { var attr = _a.attr, value = _a.value; if (attr === 'style' && isAnimatedStyleObj(value)) { var style_1 = {}; Object.keys(value).forEach(function (prop) { var v = value[prop]; if (v instanceof AnimationDeclaration) { var spec = v.spec(); style_1[prop] = getStartOutput(spec); } else { style_1[prop] = v; } }); return malevicString.escapeHTML(styles(style_1)); } return null; }; function animate(to, timing) { var declaration = new AnimationDeclaration(); if (to != null) { declaration.to(to, timing); } return declaration; } function withAnimation(type) { if (malevicDOM__namespace) { var domPlugins = malevicDOM__namespace.plugins; domPlugins.setAttribute.add(type, setAttributePlugin); domPlugins.setAttribute.add(type, setStyleAttributePlugin); } if (malevicString__namespace) { var stringPlugins = malevicString__namespace.plugins; stringPlugins.stringifyAttribute.add(type, stringifyAttributePlugin); stringPlugins.stringifyAttribute.add(type, stringifyStyleAttrPlugin); } return type; } exports.animate = animate; exports.withAnimation = withAnimation; }));