@dialpad/dialtone-vue
Version:
Vue component library for Dialpad's design system Dialtone
343 lines (342 loc) • 10.6 kB
JavaScript
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