UNPKG

@dialpad/dialtone-vue

Version:

Vue component library for Dialpad's design system Dialtone

343 lines (342 loc) 10.6 kB
import { MOTION_TEXT_SPEEDS as d, MOTION_TEXT_ANIMATION_MODES as m, MOTION_TEXT_TIMING_PRESETS as l } from "./motion-text-constants.js"; import { n as c } from "../../_plugin-vue2_normalizer-DSLOjnn3.js"; const u = { compatConfig: { MODE: 3 }, name: "DtRecipeMotionText", inheritAttrs: !1, props: { /** * The text content to animate. * @type {string} */ text: { type: String, default: "" }, /** * The animation mode to use for the text reveal. * @values gradient-in, fade-in, slide-in, gradient-sweep, shimmer, none */ animationMode: { type: String, default: "gradient-in", validator: (e) => m.includes(e) }, /** * Animation speed using t-shirt sizing. * @values sm, md, lg */ speed: { type: String, default: "md", validator: (e) => d.includes(e) }, /** * Whether to start animation automatically when component is mounted. * @values true, false */ autoStart: { type: Boolean, default: !0 }, /** * Whether to loop the animation continuously. * @values true, false */ loop: { type: Boolean, default: !1 }, /** * Whether to respect the user's prefers-reduced-motion system setting. * @values true, false */ respectsReducedMotion: { type: Boolean, default: !0 }, /** * Alternative text for screen readers. If provided, this will be announced * instead of the animated text. * @type {string} */ screenReaderText: { type: String, default: "" } }, emits: [ /** * Emitted when the animation starts. * @event start */ "start", /** * Emitted when the animation completes. * @event complete */ "complete", /** * Emitted during animation progress. * @event progress * @type {{ wordsComplete: number, totalWords: number, progress: number }} */ "progress", /** * Emitted when the animation is paused. * @event pause */ "pause", /** * Emitted when the animation resumes. * @event resume */ "resume" ], data() { return { words: [], visibleWordCount: 0, visibleCharsPerWord: [], isAnimating: !1, isPaused: !1, isLooped: !1, animationTimeouts: [], prefersReducedMotion: !1, animationKey: 0 }; }, computed: { /** * Get timing preset based on speed prop */ timing() { return l[this.speed]; }, /** * Computed styles with timing CSS variables */ componentStyles() { return { "--dt-recipe-motion-text-duration": `${this.timing.duration}ms`, "--dt-recipe-motion-text-char-duration": `${this.timing.duration}ms`, "--dt-recipe-motion-text-word-duration": `${this.timing.duration * 2}ms` }; }, /** * Check if current animation mode is static (gradient-sweep or shimmer) */ isStaticAnimationMode() { return this.animationMode === "gradient-sweep" || this.animationMode === "shimmer"; }, /** * Computed classes for the motion text element */ motionTextClasses() { return [ "dt-recipe-motion-text", `dt-recipe-motion-text--${this.animationMode}`, { "dt-recipe-motion-text--animating": this.isAnimating, "dt-recipe-motion-text--paused": this.isPaused, "dt-recipe-motion-text--looped": this.isLooped }, this.$attrs.class ]; } }, watch: { text() { this.reset(), this.initializeContent(); }, loop: { handler(e) { this.isLooped = e; }, immediate: !0 } }, mounted() { this.checkReducedMotion(), this.initializeContent(); }, beforeUnmount() { this.clearTimeouts(); }, methods: { /** * Self-contained text processing from DOM nodes */ processTextToChars(e) { const t = [], i = (s, n = 0) => { var r; if (s.nodeType === Node.TEXT_NODE) { const o = ((r = s.textContent) == null ? void 0 : r.match(/\S+\s*/g)) || []; return t.push(...o.map((a, h) => ({ text: a, chars: a.split(""), index: n + h }))), n + o.length; } else if (s.nodeType === Node.ELEMENT_NODE) { let o = n; return Array.from(s.childNodes).forEach((a) => { o = i(a, o); }), o; } return n; }; return i(e), t; }, /** * Process direct text prop into word/character data */ processDirectText(e) { return e ? (e.match(/\S+\s*/g) || []).map((i, s) => ({ text: i, chars: i.split(""), index: s })) : []; }, /** * Check for reduced motion preference */ checkReducedMotion() { typeof window < "u" && window.matchMedia && (this.prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches); }, /** * Clear all animation timeouts */ clearTimeouts() { this.animationTimeouts.forEach((e) => clearTimeout(e)), this.animationTimeouts = []; }, /** * Start the animation * @public */ start() { if (!this.isAnimating) { if (this.isAnimating = !0, this.isPaused = !1, this.$emit("start"), this.respectsReducedMotion && this.prefersReducedMotion) { this.showAllContent(); return; } if (this.animationMode === "none") { this.showAllContent(); return; } this.isStaticAnimationMode || this.showNextWord(); } }, /** * Pause the animation * @public */ pause() { !this.isAnimating || this.isPaused || (this.isPaused = !0, this.clearTimeouts(), this.$emit("pause")); }, /** * Resume the animation * @public */ resume() { this.isPaused && (this.isPaused = !1, this.$emit("resume"), this.showNextWord()); }, /** * Reset the animation to initial state * @public */ reset() { this.clearTimeouts(), this.isAnimating = !1, this.isPaused = !1, this.visibleWordCount = 0, this.visibleCharsPerWord = Array(this.words.length).fill(0), this.animationKey++; }, /** * Skip to the end of the animation * @public */ skipToEnd() { this.showAllContent(); }, /** * Show all content immediately */ showAllContent() { this.visibleWordCount = this.words.length, this.visibleCharsPerWord = this.words.map((e) => e.chars.length), setTimeout(() => { this.isAnimating = !1, this.$emit("complete"); }, 0); }, /** * Show next word in sequence */ showNextWord() { if (this.isPaused || this.visibleWordCount >= this.words.length) { this.visibleWordCount >= this.words.length && this.completeAnimation(); return; } const e = setTimeout(() => { this.visibleWordCount++, this.$emit("progress", { wordsComplete: this.visibleWordCount, totalWords: this.words.length, progress: this.visibleWordCount / this.words.length }), this.animateCharsForWord(this.visibleWordCount - 1); }, this.timing.wordDelay); this.animationTimeouts.push(e); }, /** * Animate characters for a specific word */ animateCharsForWord(e) { if (this.isPaused || e >= this.words.length) return; this.visibleCharsPerWord[e] = 0; const t = this.words[e].chars.length, i = () => { if (this.isPaused || this.visibleCharsPerWord[e] >= t) { this.visibleCharsPerWord[e] >= t && this.showNextWord(); return; } this.visibleCharsPerWord[e]++; const s = setTimeout(i, this.timing.characterDelay); this.animationTimeouts.push(s); }; i(); }, /** * Complete the animation */ completeAnimation() { if (this.isAnimating = !1, this.clearTimeouts(), this.$emit("complete"), this.loop) { const e = setTimeout(() => { this.reset(), this.$nextTick(() => { this.start(); }); }, 500); this.animationTimeouts.push(e); } }, /** * Initialize content based on text prop or slot content */ initializeContent() { if (this.isStaticAnimationMode) { this.autoStart && this.$nextTick(() => this.start()); return; } this.text ? this.words = this.processDirectText(this.text) : this.$refs.contentRef && (this.words = this.processTextToChars(this.$refs.contentRef)), this.visibleCharsPerWord = Array(this.words.length).fill(0), this.visibleWordCount = 0, this.autoStart && this.words.length > 0 && this.$nextTick(() => this.start()); } } }; var p = function() { var t = this, i = t._self._c; return i("span", { ref: "contentRef", class: t.motionTextClasses, style: t.componentStyles, attrs: { "data-text-content": t.isStaticAnimationMode ? t.text : void 0, "aria-live": t.isAnimating ? "polite" : "off", "aria-label": t.screenReaderText || void 0 } }, [t.screenReaderText ? i("span", { staticClass: "dt-recipe-motion-text__sr-only" }, [t._v(" " + t._s(t.screenReaderText) + " ")]) : t._e(), t.isStaticAnimationMode ? [t._v(" " + t._s(t.text) + " "), t.text ? t._e() : t._t("default")] : i("span", { key: t.animationKey, staticClass: "dt-recipe-motion-text__content", attrs: { "aria-hidden": t.isAnimating } }, [t._l(t.words, function(s, n) { return [i("Transition", { key: `${t.animationKey}-${n}`, attrs: { name: `dt-recipe-motion-text-word-${t.animationMode}` } }, [n < t.visibleWordCount ? i("span", { staticClass: "dt-recipe-motion-text__word", style: { "--word-index": n }, attrs: { "data-text-content": s.text } }, [t._l(s.chars, function(r, o) { return [i("Transition", { key: `${t.animationKey}-${n}-${o}`, attrs: { name: `dt-recipe-motion-text-char-${t.animationMode}` } }, [o < t.visibleCharsPerWord[n] ? i("span", { staticClass: "dt-recipe-motion-text__char", style: { "--char-index": o, "--char-delay": `${o * t.timing.characterDelay}ms` } }, [t._v(t._s(r))]) : t._e()])]; })], 2) : t._e()])]; })], 2), !t.words.length && !t.text && !t.isStaticAnimationMode ? i("span", { staticClass: "dt-recipe-motion-text__fallback" }, [t._t("default")], 2) : t._e()], 2); }, f = [], T = /* @__PURE__ */ c( u, p, f ); const C = T.exports; export { C as default }; //# sourceMappingURL=motion-text.js.map