@tombcato/smart-ticker 1.0.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.
- package/LICENSE +21 -0
- package/README.md +164 -0
- package/dist/TickerCore-DDdYkrsq.js +244 -0
- package/dist/TickerCore-DDdYkrsq.js.map +1 -0
- package/dist/TickerCore-DSrG8V7Z.cjs +243 -0
- package/dist/TickerCore-DSrG8V7Z.cjs.map +1 -0
- package/dist/index.cjs +153 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +72 -0
- package/dist/index.js +154 -0
- package/dist/index.js.map +1 -0
- package/dist/logo-dark.svg +7 -0
- package/dist/logo.svg +7 -0
- package/dist/style.css +53 -0
- package/dist/vue-demo.html +542 -0
- package/dist/vue.cjs +148 -0
- package/dist/vue.cjs.map +1 -0
- package/dist/vue.d.ts +122 -0
- package/dist/vue.js +149 -0
- package/dist/vue.js.map +1 -0
- package/package.json +75 -0
package/dist/vue.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vue.cjs","sources":["../src/components/vue/Ticker.vue"],"sourcesContent":["<template>\r\n <div class=\"ticker\" :class=\"className\">\r\n <div\r\n v-for=\"(col, i) in renderedColumns\"\r\n :key=\"i\"\r\n class=\"ticker-column\"\r\n :style=\"{ width: `${col.width}em` }\"\r\n >\r\n <div\r\n v-for=\"charObj in col.chars\"\r\n :key=\"charObj.key\"\r\n class=\"ticker-char\"\r\n :style=\"{ transform: `translateY(${charObj.offset}em)` }\"\r\n >\r\n {{ charObj.char === EMPTY_CHAR ? '\\u00A0' : charObj.char }}\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { ref, computed, watch, onUnmounted } from 'vue';\r\nimport {\r\n TickerCharacterList,\r\n ScrollingDirection,\r\n ColumnState,\r\n EMPTY_CHAR,\r\n computeColumnActions,\r\n createColumn,\r\n setTarget,\r\n applyProgress,\r\n easingFunctions,\r\n ACTION_INSERT,\r\n ACTION_SAME,\r\n} from '../../core/TickerCore';\r\n\r\n// ============================================================================\r\n// Vue Component Setup\r\n// ============================================================================\r\n\r\nconst props = defineProps({\r\n value: { type: String, required: true },\r\n characterLists: { type: Array as () => string[], default: () => ['0123456789'] },\r\n duration: { type: Number, default: 500 },\r\n direction: { type: String as () => ScrollingDirection, default: 'ANY' },\r\n easing: { type: String, default: 'easeInOut' },\r\n className: { type: String, default: '' },\r\n});\r\n\r\nconst columns = ref<ColumnState[]>([]);\r\nconst progress = ref(1);\r\nlet animId: number | undefined;\r\n\r\n// Initialization\r\nconst lists = computed(() => props.characterLists.map(s => new TickerCharacterList(s)));\r\nconst supported = computed(() => {\r\n const set = new Set<string>();\r\n lists.value.forEach(l => l.getSupportedCharacters().forEach(c => set.add(c)));\r\n return set;\r\n});\r\n\r\n// Watch for value changes\r\nwatch(() => props.value, (newValue, oldValue) => {\r\n if (newValue === oldValue) return;\r\n\r\n if (animId) {\r\n cancelAnimationFrame(animId);\r\n animId = undefined;\r\n }\r\n\r\n // Update current columns with current progress if mid-animation\r\n let currentCols = columns.value;\r\n if (progress.value < 1 && progress.value > 0) {\r\n currentCols = currentCols.map(c => applyProgress(c, progress.value, true).col);\r\n }\r\n\r\n const targetChars = newValue.split('');\r\n const sourceChars = currentCols.map(c => c.currentChar);\r\n const actions = computeColumnActions(sourceChars, targetChars, supported.value);\r\n\r\n let ci = 0, ti = 0;\r\n const result: ColumnState[] = [];\r\n const validCols = currentCols.filter(c => c.currentWidth > 0);\r\n\r\n for (const action of actions) {\r\n if (action === ACTION_INSERT) {\r\n result.push(setTarget(createColumn(), targetChars[ti++], lists.value, props.direction));\r\n } else if (action === ACTION_SAME) {\r\n const existing = validCols[ci++] || createColumn();\r\n result.push(setTarget(existing, targetChars[ti++], lists.value, props.direction));\r\n } else {\r\n const existing = validCols[ci++] || createColumn();\r\n result.push(setTarget(existing, EMPTY_CHAR, lists.value, props.direction));\r\n }\r\n }\r\n\r\n columns.value = result;\r\n\r\n // Start Animation\r\n progress.value = 0;\r\n const start = performance.now();\r\n const dur = props.duration;\r\n const easeFn = easingFunctions[props.easing] || easingFunctions.linear;\r\n let lastUpdate = 0;\r\n\r\n const animate = (now: number) => {\r\n const linearP = Math.min((now - start) / dur, 1);\r\n const p = easeFn(linearP);\r\n \r\n // Throttled update\r\n const shouldUpdate = now - lastUpdate >= 16 || linearP >= 1;\r\n \r\n if (shouldUpdate) {\r\n lastUpdate = now;\r\n progress.value = p;\r\n }\r\n\r\n if (linearP < 1) {\r\n animId = requestAnimationFrame(animate);\r\n } else {\r\n const final = columns.value\r\n .map(c => applyProgress(c, 1).col)\r\n .filter(c => c.currentWidth > 0);\r\n columns.value = final;\r\n progress.value = 1;\r\n animId = undefined;\r\n }\r\n };\r\n animId = requestAnimationFrame(animate);\r\n\r\n}, { immediate: true }); \r\n\r\nonUnmounted(() => {\r\n if (animId) cancelAnimationFrame(animId);\r\n});\r\n\r\n// Derived View Data\r\nconst charHeight = 1.2;\r\n\r\nconst renderedColumns = computed(() => {\r\n return columns.value.map(col => {\r\n const { charIdx, delta } = applyProgress(col, progress.value);\r\n const width = col.sourceWidth + (col.targetWidth - col.sourceWidth) * progress.value;\r\n if (width <= 0) return { width, chars: [] };\r\n\r\n const chars: Array<{ key: string, char: string, offset: number }> = [];\r\n const list = col.charList || [];\r\n const deltaEm = delta * charHeight;\r\n\r\n // Helper to add char\r\n const add = (idx: number, offsetMod: number, keyPrefix: string) => {\r\n if (idx >= 0 && idx < list.length) {\r\n chars.push({\r\n key: `${keyPrefix}-${idx}`,\r\n char: list[idx],\r\n offset: deltaEm + offsetMod\r\n });\r\n }\r\n };\r\n\r\n add(charIdx, 0, 'c');\r\n add(charIdx + 1, -charHeight, 'n');\r\n add(charIdx - 1, charHeight, 'p');\r\n\r\n return { width, chars };\r\n }).filter(c => c.width > 0);\r\n});\r\n\r\n</script>\r\n\r\n<style scoped>\r\n.ticker {\r\n display: inline-flex;\r\n overflow: hidden;\r\n font-family: 'JetBrains Mono', monospace;\r\n font-weight: 600;\r\n line-height: 1.2;\r\n}\r\n\r\n.ticker-column {\r\n display: inline-block;\r\n height: 1.2em;\r\n overflow: hidden;\r\n position: relative;\r\n clip-path: inset(0);\r\n}\r\n\r\n.ticker-char {\r\n position: absolute;\r\n top: 0;\r\n left: 0;\r\n right: 0;\r\n height: 1.2em;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n white-space: pre;\r\n}\r\n</style>\r\n"],"names":["ref","computed","TickerCharacterList","watch","applyProgress","computeColumnActions","ACTION_INSERT","setTarget","createColumn","ACTION_SAME","EMPTY_CHAR","easingFunctions","onUnmounted","_createElementBlock","_normalizeClass","_openBlock","_Fragment","_renderList","_normalizeStyle","_toDisplayString","_unref"],"mappings":"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"}
|
package/dist/vue.d.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { ComponentOptionsMixin } from 'vue';
|
|
2
|
+
import { ComponentProvideOptions } from 'vue';
|
|
3
|
+
import { DefineComponent } from 'vue';
|
|
4
|
+
import { ExtractPropTypes } from 'vue';
|
|
5
|
+
import { PublicProps } from 'vue';
|
|
6
|
+
|
|
7
|
+
export declare const ACTION_DELETE = 2;
|
|
8
|
+
|
|
9
|
+
export declare const ACTION_INSERT = 1;
|
|
10
|
+
|
|
11
|
+
export declare const ACTION_SAME = 0;
|
|
12
|
+
|
|
13
|
+
export declare function applyProgress(col: ColumnState, progress: number, forceUpdate?: boolean): {
|
|
14
|
+
col: ColumnState;
|
|
15
|
+
charIdx: number;
|
|
16
|
+
delta: number;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export declare interface ColumnState {
|
|
20
|
+
currentChar: string;
|
|
21
|
+
targetChar: string;
|
|
22
|
+
charList: string[] | null;
|
|
23
|
+
startIndex: number;
|
|
24
|
+
endIndex: number;
|
|
25
|
+
sourceWidth: number;
|
|
26
|
+
currentWidth: number;
|
|
27
|
+
targetWidth: number;
|
|
28
|
+
directionAdj: number;
|
|
29
|
+
prevDelta: number;
|
|
30
|
+
currDelta: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export declare function computeColumnActions(source: string[], target: string[], supported: Set<string>): number[];
|
|
34
|
+
|
|
35
|
+
export declare function createColumn(): ColumnState;
|
|
36
|
+
|
|
37
|
+
export declare const easingFunctions: Record<string, (t: number) => number>;
|
|
38
|
+
|
|
39
|
+
export declare const EMPTY_CHAR = "\0";
|
|
40
|
+
|
|
41
|
+
export declare type ScrollingDirection = 'ANY' | 'UP' | 'DOWN';
|
|
42
|
+
|
|
43
|
+
export declare function setTarget(col: ColumnState, target: string, lists: TickerCharacterList[], dir: ScrollingDirection): ColumnState;
|
|
44
|
+
|
|
45
|
+
export declare const Ticker: DefineComponent<ExtractPropTypes< {
|
|
46
|
+
value: {
|
|
47
|
+
type: StringConstructor;
|
|
48
|
+
required: true;
|
|
49
|
+
};
|
|
50
|
+
characterLists: {
|
|
51
|
+
type: () => string[];
|
|
52
|
+
default: () => string[];
|
|
53
|
+
};
|
|
54
|
+
duration: {
|
|
55
|
+
type: NumberConstructor;
|
|
56
|
+
default: number;
|
|
57
|
+
};
|
|
58
|
+
direction: {
|
|
59
|
+
type: () => ScrollingDirection;
|
|
60
|
+
default: string;
|
|
61
|
+
};
|
|
62
|
+
easing: {
|
|
63
|
+
type: StringConstructor;
|
|
64
|
+
default: string;
|
|
65
|
+
};
|
|
66
|
+
className: {
|
|
67
|
+
type: StringConstructor;
|
|
68
|
+
default: string;
|
|
69
|
+
};
|
|
70
|
+
}>, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, PublicProps, Readonly<ExtractPropTypes< {
|
|
71
|
+
value: {
|
|
72
|
+
type: StringConstructor;
|
|
73
|
+
required: true;
|
|
74
|
+
};
|
|
75
|
+
characterLists: {
|
|
76
|
+
type: () => string[];
|
|
77
|
+
default: () => string[];
|
|
78
|
+
};
|
|
79
|
+
duration: {
|
|
80
|
+
type: NumberConstructor;
|
|
81
|
+
default: number;
|
|
82
|
+
};
|
|
83
|
+
direction: {
|
|
84
|
+
type: () => ScrollingDirection;
|
|
85
|
+
default: string;
|
|
86
|
+
};
|
|
87
|
+
easing: {
|
|
88
|
+
type: StringConstructor;
|
|
89
|
+
default: string;
|
|
90
|
+
};
|
|
91
|
+
className: {
|
|
92
|
+
type: StringConstructor;
|
|
93
|
+
default: string;
|
|
94
|
+
};
|
|
95
|
+
}>> & Readonly<{}>, {
|
|
96
|
+
characterLists: string[];
|
|
97
|
+
duration: number;
|
|
98
|
+
direction: ScrollingDirection;
|
|
99
|
+
easing: string;
|
|
100
|
+
className: string;
|
|
101
|
+
}, {}, {}, {}, string, ComponentProvideOptions, true, {}, any>;
|
|
102
|
+
|
|
103
|
+
export declare class TickerCharacterList {
|
|
104
|
+
private numOriginalCharacters;
|
|
105
|
+
private characterList;
|
|
106
|
+
private characterIndicesMap;
|
|
107
|
+
constructor(characterList: string);
|
|
108
|
+
getCharacterIndices(start: string, end: string, direction: ScrollingDirection): {
|
|
109
|
+
startIndex: number;
|
|
110
|
+
endIndex: number;
|
|
111
|
+
} | null;
|
|
112
|
+
getSupportedCharacters(): Set<string>;
|
|
113
|
+
getCharacterList(): string[];
|
|
114
|
+
private getIndexOfChar;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export declare const TickerUtils: {
|
|
118
|
+
provideNumberList: () => string;
|
|
119
|
+
provideAlphabeticalList: () => string;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export { }
|
package/dist/vue.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { defineComponent, ref, computed, watch, onUnmounted, createElementBlock, openBlock, normalizeClass, Fragment, renderList, normalizeStyle, toDisplayString, unref } from "vue";
|
|
2
|
+
import { a as TickerCharacterList, b as applyProgress, c as computeColumnActions, A as ACTION_INSERT, s as setTarget, d as createColumn, e as ACTION_SAME, E as EMPTY_CHAR, f as easingFunctions } from "./TickerCore-DDdYkrsq.js";
|
|
3
|
+
import { g, T } from "./TickerCore-DDdYkrsq.js";
|
|
4
|
+
const charHeight = 1.2;
|
|
5
|
+
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
6
|
+
__name: "Ticker",
|
|
7
|
+
props: {
|
|
8
|
+
value: { type: String, required: true },
|
|
9
|
+
characterLists: { type: Array, default: () => ["0123456789"] },
|
|
10
|
+
duration: { type: Number, default: 500 },
|
|
11
|
+
direction: { type: String, default: "ANY" },
|
|
12
|
+
easing: { type: String, default: "easeInOut" },
|
|
13
|
+
className: { type: String, default: "" }
|
|
14
|
+
},
|
|
15
|
+
setup(__props) {
|
|
16
|
+
const props = __props;
|
|
17
|
+
const columns = ref([]);
|
|
18
|
+
const progress = ref(1);
|
|
19
|
+
let animId;
|
|
20
|
+
const lists = computed(() => props.characterLists.map((s) => new TickerCharacterList(s)));
|
|
21
|
+
const supported = computed(() => {
|
|
22
|
+
const set = /* @__PURE__ */ new Set();
|
|
23
|
+
lists.value.forEach((l) => l.getSupportedCharacters().forEach((c) => set.add(c)));
|
|
24
|
+
return set;
|
|
25
|
+
});
|
|
26
|
+
watch(() => props.value, (newValue, oldValue) => {
|
|
27
|
+
if (newValue === oldValue) return;
|
|
28
|
+
if (animId) {
|
|
29
|
+
cancelAnimationFrame(animId);
|
|
30
|
+
animId = void 0;
|
|
31
|
+
}
|
|
32
|
+
let currentCols = columns.value;
|
|
33
|
+
if (progress.value < 1 && progress.value > 0) {
|
|
34
|
+
currentCols = currentCols.map((c) => applyProgress(c, progress.value, true).col);
|
|
35
|
+
}
|
|
36
|
+
const targetChars = newValue.split("");
|
|
37
|
+
const sourceChars = currentCols.map((c) => c.currentChar);
|
|
38
|
+
const actions = computeColumnActions(sourceChars, targetChars, supported.value);
|
|
39
|
+
let ci = 0, ti = 0;
|
|
40
|
+
const result = [];
|
|
41
|
+
const validCols = currentCols.filter((c) => c.currentWidth > 0);
|
|
42
|
+
for (const action of actions) {
|
|
43
|
+
if (action === ACTION_INSERT) {
|
|
44
|
+
result.push(setTarget(createColumn(), targetChars[ti++], lists.value, props.direction));
|
|
45
|
+
} else if (action === ACTION_SAME) {
|
|
46
|
+
const existing = validCols[ci++] || createColumn();
|
|
47
|
+
result.push(setTarget(existing, targetChars[ti++], lists.value, props.direction));
|
|
48
|
+
} else {
|
|
49
|
+
const existing = validCols[ci++] || createColumn();
|
|
50
|
+
result.push(setTarget(existing, EMPTY_CHAR, lists.value, props.direction));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
columns.value = result;
|
|
54
|
+
progress.value = 0;
|
|
55
|
+
const start = performance.now();
|
|
56
|
+
const dur = props.duration;
|
|
57
|
+
const easeFn = easingFunctions[props.easing] || easingFunctions.linear;
|
|
58
|
+
let lastUpdate = 0;
|
|
59
|
+
const animate = (now) => {
|
|
60
|
+
const linearP = Math.min((now - start) / dur, 1);
|
|
61
|
+
const p = easeFn(linearP);
|
|
62
|
+
const shouldUpdate = now - lastUpdate >= 16 || linearP >= 1;
|
|
63
|
+
if (shouldUpdate) {
|
|
64
|
+
lastUpdate = now;
|
|
65
|
+
progress.value = p;
|
|
66
|
+
}
|
|
67
|
+
if (linearP < 1) {
|
|
68
|
+
animId = requestAnimationFrame(animate);
|
|
69
|
+
} else {
|
|
70
|
+
const final = columns.value.map((c) => applyProgress(c, 1).col).filter((c) => c.currentWidth > 0);
|
|
71
|
+
columns.value = final;
|
|
72
|
+
progress.value = 1;
|
|
73
|
+
animId = void 0;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
animId = requestAnimationFrame(animate);
|
|
77
|
+
}, { immediate: true });
|
|
78
|
+
onUnmounted(() => {
|
|
79
|
+
if (animId) cancelAnimationFrame(animId);
|
|
80
|
+
});
|
|
81
|
+
const renderedColumns = computed(() => {
|
|
82
|
+
return columns.value.map((col) => {
|
|
83
|
+
const { charIdx, delta } = applyProgress(col, progress.value);
|
|
84
|
+
const width = col.sourceWidth + (col.targetWidth - col.sourceWidth) * progress.value;
|
|
85
|
+
if (width <= 0) return { width, chars: [] };
|
|
86
|
+
const chars = [];
|
|
87
|
+
const list = col.charList || [];
|
|
88
|
+
const deltaEm = delta * charHeight;
|
|
89
|
+
const add = (idx, offsetMod, keyPrefix) => {
|
|
90
|
+
if (idx >= 0 && idx < list.length) {
|
|
91
|
+
chars.push({
|
|
92
|
+
key: `${keyPrefix}-${idx}`,
|
|
93
|
+
char: list[idx],
|
|
94
|
+
offset: deltaEm + offsetMod
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
add(charIdx, 0, "c");
|
|
99
|
+
add(charIdx + 1, -charHeight, "n");
|
|
100
|
+
add(charIdx - 1, charHeight, "p");
|
|
101
|
+
return { width, chars };
|
|
102
|
+
}).filter((c) => c.width > 0);
|
|
103
|
+
});
|
|
104
|
+
return (_ctx, _cache) => {
|
|
105
|
+
return openBlock(), createElementBlock("div", {
|
|
106
|
+
class: normalizeClass(["ticker", __props.className])
|
|
107
|
+
}, [
|
|
108
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList(renderedColumns.value, (col, i) => {
|
|
109
|
+
return openBlock(), createElementBlock("div", {
|
|
110
|
+
key: i,
|
|
111
|
+
class: "ticker-column",
|
|
112
|
+
style: normalizeStyle({ width: `${col.width}em` })
|
|
113
|
+
}, [
|
|
114
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList(col.chars, (charObj) => {
|
|
115
|
+
return openBlock(), createElementBlock("div", {
|
|
116
|
+
key: charObj.key,
|
|
117
|
+
class: "ticker-char",
|
|
118
|
+
style: normalizeStyle({ transform: `translateY(${charObj.offset}em)` })
|
|
119
|
+
}, toDisplayString(charObj.char === unref(EMPTY_CHAR) ? " " : charObj.char), 5);
|
|
120
|
+
}), 128))
|
|
121
|
+
], 4);
|
|
122
|
+
}), 128))
|
|
123
|
+
], 2);
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
const _export_sfc = (sfc, props) => {
|
|
128
|
+
const target = sfc.__vccOpts || sfc;
|
|
129
|
+
for (const [key, val] of props) {
|
|
130
|
+
target[key] = val;
|
|
131
|
+
}
|
|
132
|
+
return target;
|
|
133
|
+
};
|
|
134
|
+
const Ticker = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-d7db53e5"]]);
|
|
135
|
+
export {
|
|
136
|
+
g as ACTION_DELETE,
|
|
137
|
+
ACTION_INSERT,
|
|
138
|
+
ACTION_SAME,
|
|
139
|
+
EMPTY_CHAR,
|
|
140
|
+
Ticker,
|
|
141
|
+
TickerCharacterList,
|
|
142
|
+
T as TickerUtils,
|
|
143
|
+
applyProgress,
|
|
144
|
+
computeColumnActions,
|
|
145
|
+
createColumn,
|
|
146
|
+
easingFunctions,
|
|
147
|
+
setTarget
|
|
148
|
+
};
|
|
149
|
+
//# sourceMappingURL=vue.js.map
|
package/dist/vue.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vue.js","sources":["../src/components/vue/Ticker.vue"],"sourcesContent":["<template>\r\n <div class=\"ticker\" :class=\"className\">\r\n <div\r\n v-for=\"(col, i) in renderedColumns\"\r\n :key=\"i\"\r\n class=\"ticker-column\"\r\n :style=\"{ width: `${col.width}em` }\"\r\n >\r\n <div\r\n v-for=\"charObj in col.chars\"\r\n :key=\"charObj.key\"\r\n class=\"ticker-char\"\r\n :style=\"{ transform: `translateY(${charObj.offset}em)` }\"\r\n >\r\n {{ charObj.char === EMPTY_CHAR ? '\\u00A0' : charObj.char }}\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { ref, computed, watch, onUnmounted } from 'vue';\r\nimport {\r\n TickerCharacterList,\r\n ScrollingDirection,\r\n ColumnState,\r\n EMPTY_CHAR,\r\n computeColumnActions,\r\n createColumn,\r\n setTarget,\r\n applyProgress,\r\n easingFunctions,\r\n ACTION_INSERT,\r\n ACTION_SAME,\r\n} from '../../core/TickerCore';\r\n\r\n// ============================================================================\r\n// Vue Component Setup\r\n// ============================================================================\r\n\r\nconst props = defineProps({\r\n value: { type: String, required: true },\r\n characterLists: { type: Array as () => string[], default: () => ['0123456789'] },\r\n duration: { type: Number, default: 500 },\r\n direction: { type: String as () => ScrollingDirection, default: 'ANY' },\r\n easing: { type: String, default: 'easeInOut' },\r\n className: { type: String, default: '' },\r\n});\r\n\r\nconst columns = ref<ColumnState[]>([]);\r\nconst progress = ref(1);\r\nlet animId: number | undefined;\r\n\r\n// Initialization\r\nconst lists = computed(() => props.characterLists.map(s => new TickerCharacterList(s)));\r\nconst supported = computed(() => {\r\n const set = new Set<string>();\r\n lists.value.forEach(l => l.getSupportedCharacters().forEach(c => set.add(c)));\r\n return set;\r\n});\r\n\r\n// Watch for value changes\r\nwatch(() => props.value, (newValue, oldValue) => {\r\n if (newValue === oldValue) return;\r\n\r\n if (animId) {\r\n cancelAnimationFrame(animId);\r\n animId = undefined;\r\n }\r\n\r\n // Update current columns with current progress if mid-animation\r\n let currentCols = columns.value;\r\n if (progress.value < 1 && progress.value > 0) {\r\n currentCols = currentCols.map(c => applyProgress(c, progress.value, true).col);\r\n }\r\n\r\n const targetChars = newValue.split('');\r\n const sourceChars = currentCols.map(c => c.currentChar);\r\n const actions = computeColumnActions(sourceChars, targetChars, supported.value);\r\n\r\n let ci = 0, ti = 0;\r\n const result: ColumnState[] = [];\r\n const validCols = currentCols.filter(c => c.currentWidth > 0);\r\n\r\n for (const action of actions) {\r\n if (action === ACTION_INSERT) {\r\n result.push(setTarget(createColumn(), targetChars[ti++], lists.value, props.direction));\r\n } else if (action === ACTION_SAME) {\r\n const existing = validCols[ci++] || createColumn();\r\n result.push(setTarget(existing, targetChars[ti++], lists.value, props.direction));\r\n } else {\r\n const existing = validCols[ci++] || createColumn();\r\n result.push(setTarget(existing, EMPTY_CHAR, lists.value, props.direction));\r\n }\r\n }\r\n\r\n columns.value = result;\r\n\r\n // Start Animation\r\n progress.value = 0;\r\n const start = performance.now();\r\n const dur = props.duration;\r\n const easeFn = easingFunctions[props.easing] || easingFunctions.linear;\r\n let lastUpdate = 0;\r\n\r\n const animate = (now: number) => {\r\n const linearP = Math.min((now - start) / dur, 1);\r\n const p = easeFn(linearP);\r\n \r\n // Throttled update\r\n const shouldUpdate = now - lastUpdate >= 16 || linearP >= 1;\r\n \r\n if (shouldUpdate) {\r\n lastUpdate = now;\r\n progress.value = p;\r\n }\r\n\r\n if (linearP < 1) {\r\n animId = requestAnimationFrame(animate);\r\n } else {\r\n const final = columns.value\r\n .map(c => applyProgress(c, 1).col)\r\n .filter(c => c.currentWidth > 0);\r\n columns.value = final;\r\n progress.value = 1;\r\n animId = undefined;\r\n }\r\n };\r\n animId = requestAnimationFrame(animate);\r\n\r\n}, { immediate: true }); \r\n\r\nonUnmounted(() => {\r\n if (animId) cancelAnimationFrame(animId);\r\n});\r\n\r\n// Derived View Data\r\nconst charHeight = 1.2;\r\n\r\nconst renderedColumns = computed(() => {\r\n return columns.value.map(col => {\r\n const { charIdx, delta } = applyProgress(col, progress.value);\r\n const width = col.sourceWidth + (col.targetWidth - col.sourceWidth) * progress.value;\r\n if (width <= 0) return { width, chars: [] };\r\n\r\n const chars: Array<{ key: string, char: string, offset: number }> = [];\r\n const list = col.charList || [];\r\n const deltaEm = delta * charHeight;\r\n\r\n // Helper to add char\r\n const add = (idx: number, offsetMod: number, keyPrefix: string) => {\r\n if (idx >= 0 && idx < list.length) {\r\n chars.push({\r\n key: `${keyPrefix}-${idx}`,\r\n char: list[idx],\r\n offset: deltaEm + offsetMod\r\n });\r\n }\r\n };\r\n\r\n add(charIdx, 0, 'c');\r\n add(charIdx + 1, -charHeight, 'n');\r\n add(charIdx - 1, charHeight, 'p');\r\n\r\n return { width, chars };\r\n }).filter(c => c.width > 0);\r\n});\r\n\r\n</script>\r\n\r\n<style scoped>\r\n.ticker {\r\n display: inline-flex;\r\n overflow: hidden;\r\n font-family: 'JetBrains Mono', monospace;\r\n font-weight: 600;\r\n line-height: 1.2;\r\n}\r\n\r\n.ticker-column {\r\n display: inline-block;\r\n height: 1.2em;\r\n overflow: hidden;\r\n position: relative;\r\n clip-path: inset(0);\r\n}\r\n\r\n.ticker-char {\r\n position: absolute;\r\n top: 0;\r\n left: 0;\r\n right: 0;\r\n height: 1.2em;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n white-space: pre;\r\n}\r\n</style>\r\n"],"names":["_createElementBlock","_normalizeClass","_openBlock","_Fragment","_renderList","_normalizeStyle","_toDisplayString","_unref"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tombcato/smart-ticker",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Smart text animation component with diff-based character scrollin",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"require": "./dist/index.cjs",
|
|
13
|
+
"types": "./dist/index.d.ts"
|
|
14
|
+
},
|
|
15
|
+
"./vue": {
|
|
16
|
+
"import": "./dist/vue.js",
|
|
17
|
+
"require": "./dist/vue.cjs",
|
|
18
|
+
"types": "./dist/vue.d.ts"
|
|
19
|
+
},
|
|
20
|
+
"./style.css": "./dist/style.css"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"dev": "vite",
|
|
28
|
+
"build": "vite build",
|
|
29
|
+
"build:lib": "vite build --config vite.lib.config.ts",
|
|
30
|
+
"preview": "vite preview",
|
|
31
|
+
"prepublishOnly": "npm run build:lib"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"ticker",
|
|
35
|
+
"animation",
|
|
36
|
+
"react",
|
|
37
|
+
"vue",
|
|
38
|
+
"text-animation",
|
|
39
|
+
"counter",
|
|
40
|
+
"odometer",
|
|
41
|
+
"flip-clock"
|
|
42
|
+
],
|
|
43
|
+
"author": "",
|
|
44
|
+
"license": "MIT",
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "https://github.com/tombcato/smart-ticker"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"react": ">=17.0.0",
|
|
51
|
+
"react-dom": ">=17.0.0",
|
|
52
|
+
"vue": ">=3.0.0"
|
|
53
|
+
},
|
|
54
|
+
"peerDependenciesMeta": {
|
|
55
|
+
"react": {
|
|
56
|
+
"optional": true
|
|
57
|
+
},
|
|
58
|
+
"react-dom": {
|
|
59
|
+
"optional": true
|
|
60
|
+
},
|
|
61
|
+
"vue": {
|
|
62
|
+
"optional": true
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"@types/node": "^25.0.3",
|
|
67
|
+
"@types/react": "^18.2.0",
|
|
68
|
+
"@types/react-dom": "^18.2.0",
|
|
69
|
+
"@vitejs/plugin-react": "^4.2.0",
|
|
70
|
+
"@vitejs/plugin-vue": "^6.0.3",
|
|
71
|
+
"typescript": "^5.3.0",
|
|
72
|
+
"vite": "^5.0.0",
|
|
73
|
+
"vite-plugin-dts": "^3.9.1"
|
|
74
|
+
}
|
|
75
|
+
}
|