light-odometer 0.1.1 → 0.1.3

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/README.md CHANGED
@@ -1,7 +1,12 @@
1
- # light-odometer ![NPM Version](https://img.shields.io/npm/v/light-odometer)
1
+ <div align="center">
2
+
3
+ # light-odometer
4
+ ![NPM Version](https://img.shields.io/npm/v/light-odometer) ![NPM Downloads](https://img.shields.io/npm/dt/light-odometer) [![More info](https://img.shields.io/badge/npmx-More_info-orange?logo=npm)](https://npmx.dev/light-odometer)
5
+
6
+ </div>
2
7
 
3
8
  This project is made only for personal use, as a small and lightweight build of [tm-odometer](https://github.com/mtmarco87/tm-odometer), which is itself a fork of [HubSpot's odometer](https://github.com/HubSpot/odometer).
4
- Do not use this, use [@mtmarco87](https://github.com/mtmarco87)'s package instead.
9
+ ⚠️ Do not use this, use [@mtmarco87](https://github.com/mtmarco87)'s package instead.
5
10
  Huge props to him for this TypeScript refactor !
6
11
 
7
12
  No theme is shipped here, no docs either.
@@ -9,24 +14,20 @@ No theme is shipped here, no docs either.
9
14
  ## What's changed ?
10
15
  For a quick overview in a real-world usage, see https://github.com/EDM115/website/blob/master/app/components/ui/Odometer.vue
11
16
 
17
+ ### Breaking changes
18
+ - Renamed from `TmOdometer` to `LightOdometer`
12
19
  - Removal of all built artifacts, themes, demo, screenshots and CoffeeScript code
13
20
  - Switch from Rollup to tsdown (Rolldown)
14
- - Added linting and formatting (OxLint + ESLint Stylistic)
15
- - Keep only the ESM build and switch to a default export
16
- - Better TS config and stricter types (no more `any` nor `as`)
17
- - Target ES2016 instead of ES2015
18
21
  - Remove compatibility layers (Internet Explorer, jQuery)
19
- - Stabilize and simplify the interfaces
20
- - Use overall more optimized methods
21
- - Renamed from `TmOdometer` to `LightOdometer`
22
- - No more const functions
23
- - SSR friendly (you can import it top-level without blowing up) and use safe fallbacks fror browser-land functions
24
- - Multiple performance improvements
25
- - `value` is now stripped from spaces
22
+ - Keep only the ESM build and switch to a default export
23
+
24
+ ### New features
25
+ - SSR friendly (you can import it top-level without blowing up) and use safe fallbacks for browser-land functions
26
26
  - Each instance can now have an `id`
27
27
  - Ability to customize the framerate
28
28
  ```ts
29
- const odo = new LightOdometer({ ..., framerate: 20 }) // default is 30, going above isn't recommended
29
+ const odo = new LightOdometer({ ..., framerate: 20 })
30
+ // default is 30, going above isn't recommended
30
31
  // use countFramerate if you use the `count` mode instead of `slide`
31
32
  ```
32
33
  - Ability to render once and destroy the instance
@@ -44,21 +45,21 @@ For a quick overview in a real-world usage, see https://github.com/EDM115/websit
44
45
  const odo = new LightOdometer({ ... })
45
46
  console.log(odo.getOptions().duration)
46
47
  odo.setOptions({ duration: 2000 })
47
-
48
+
48
49
  console.log(LightOdometer.getGlobalOptions.selector)
49
50
  LightOdometer.setGlobalOptions({ selector: ".my-odometer" })
50
51
  ```
51
- - Add per-instance subscriptable animation start/end events. They give back the instance id, el, instance, value, oldValue, options
52
+ - Add per-instance subscriptable animation start/end events, they give back the instance id, el, instance, value, oldValue, options
52
53
  ```ts
53
54
  const odo = new LightOdometer({ ... })
54
55
  console.log(odo.isAnimating)
55
-
56
+
56
57
  function onStart(e: Event) {
57
58
  const { detail } = e as CustomEvent<LightOdometerEventDetail>
58
59
  // detail.id, detail.value, detail.instance.isAnimating, ...
59
60
  console.log(`started animating to ${detail.value}`)
60
61
  }
61
-
62
+
62
63
  function onDone(e: Event) {
63
64
  const { detail } = e as CustomEvent<LightOdometerEventDetail>
64
65
  console.log(`finished animating to ${detail.value}`)
@@ -71,8 +72,44 @@ For a quick overview in a real-world usage, see https://github.com/EDM115/websit
71
72
  odo.off("odometerstart", onStart)
72
73
  odo.off("odometerdone", onDone)
73
74
  ```
74
- - Print the instance in a JSON-friendly string
75
+ - Print the instance as a JSON-friendly string
75
76
  ```ts
76
77
  const odo = new LightOdometer({ ... })
77
- console.log(odo.toString()) // {"id":2,"value":157,"options":{"id":2,"value":0,"animation":"slide","duration":8000,"format":"( ddd)","framerate":20},"globalOptions":{},"watchMutations":false,"transitionEndBound":true,"destroyed":false,"format":{"repeating":" ","precision":0},"isAnimating":false}
78
+ console.log(odo.toString())
78
79
  ```
80
+ ```json
81
+ {
82
+ "id": 2,
83
+ "value": 157,
84
+ "options": {
85
+ "id": 2,
86
+ "value": 0,
87
+ "animation": "slide",
88
+ "duration": 8000,
89
+ "format": "( ddd)",
90
+ "framerate": 20
91
+ },
92
+ "globalOptions": {},
93
+ "watchMutations": false,
94
+ "transitionEndBound": true,
95
+ "destroyed": false,
96
+ "format": {
97
+ "repeating": " ",
98
+ "precision": 0
99
+ },
100
+ "isAnimating": false
101
+ }
102
+ ```
103
+
104
+ ### Improvements
105
+ - `value` is now stripped from spaces
106
+ - Multiple performance improvements
107
+ - Use overall more optimized methods
108
+ - Stabilize and simplify the interfaces
109
+
110
+ ### Other changes
111
+ - No more const functions
112
+ - Better TS config and stricter types (no more `any` nor `as`)
113
+ - Use classes private elements
114
+ - Added linting and formatting (OxLint + ESLint Stylistic)
115
+ - Target ES2023 instead of ES2015
@@ -1,24 +1,24 @@
1
1
  //#region src/shared/interfaces.d.ts
2
2
  /**
3
- * LightOdometer global options interface
4
- * @property {string} [selector] - The selector for the odometer elements.
5
- * @property {boolean} [auto] - Whether to automatically initialize odometers.
6
- */
3
+ * LightOdometer global options interface
4
+ * @property {string} [selector] - The selector for the odometer elements.
5
+ * @property {boolean} [auto] - Whether to automatically initialize odometers.
6
+ */
7
7
  interface LightOdometerGlobalOptions {
8
8
  selector?: string;
9
9
  auto?: boolean;
10
10
  }
11
11
  /**
12
- * LightOdometer config interface
13
- * @property {HTMLElement} el - The HTML element to attach the odometer to.
14
- * @property {string | number | null} [value] - The initial value of the odometer.
15
- * @property {string} [format] - The format string for the odometer.
16
- * @property {number} [duration] - The duration of the animation in milliseconds.
17
- * @property {number} [framerate] - Target framerate for slide animation.
18
- * @property {number} [countFramerate] - Target framerate for count animation.
19
- * @property {'count' | 'slide'} [animation] - The animation type ('count' or 'slide').
20
- * @property {(value: number) => string} [formatFunction] - A custom format function.
21
- */
12
+ * LightOdometer config interface
13
+ * @property {HTMLElement} el - The HTML element to attach the odometer to.
14
+ * @property {string | number | null} [value] - The initial value of the odometer.
15
+ * @property {string} [format] - The format string for the odometer.
16
+ * @property {number} [duration] - The duration of the animation in milliseconds.
17
+ * @property {number} [framerate] - Target framerate for slide animation.
18
+ * @property {number} [countFramerate] - Target framerate for count animation.
19
+ * @property {'count' | 'slide'} [animation] - The animation type ('count' or 'slide').
20
+ * @property {(value: number) => string} [formatFunction] - A custom format function.
21
+ */
22
22
  interface LightOdometerOptions {
23
23
  el: HTMLElement;
24
24
  /** Optional identifier for this instance; propagated in event details */
@@ -33,11 +33,11 @@ interface LightOdometerOptions {
33
33
  }
34
34
  type LightOdometerEventName = "odometerstart" | "odometerdone";
35
35
  /**
36
- * FormatObject interface
37
- * @property {string} repeating - The repeating part of the format. (i.e. '(,ddd)')
38
- * @property {string} [radix] - The radix separator. (i.e. '.')
39
- * @property {number} precision - The number of decimal places. (i.e. 'dd')
40
- */
36
+ * FormatObject interface
37
+ * @property {string} repeating - The repeating part of the format. (i.e. '(,ddd)')
38
+ * @property {string} [radix] - The radix separator. (i.e. '.')
39
+ * @property {number} precision - The number of decimal places. (i.e. 'dd')
40
+ */
41
41
  interface FormatObject {
42
42
  repeating: string;
43
43
  radix?: string;
@@ -54,8 +54,8 @@ declare global {
54
54
  //#endregion
55
55
  //#region src/core/odometer.d.ts
56
56
  declare class LightOdometer {
57
+ #private;
57
58
  static options: LightOdometerGlobalOptions;
58
- private _isAnimating;
59
59
  options: LightOdometerOptions;
60
60
  el: HTMLElement;
61
61
  value: number;
@@ -70,215 +70,210 @@ declare class LightOdometer {
70
70
  MAX_VALUES: number;
71
71
  digits: HTMLElement[];
72
72
  ribbons: Record<number, HTMLElement>;
73
- private _rafId?;
74
- private _countRafId?;
75
- private _msPerFrame;
76
- private _countMsPerFrame;
77
- private _onTransitionEnd?;
78
73
  /**
79
- * Initializes a new instance of the LightOdometer class.
80
- * Sets up the odometer's options, formats, and DOM structure.
81
- * If an odometer instance already exists on the element, it returns the existing instance.
82
- * @param {LightOdometerOptions} options - Configuration options for the odometer.
83
- */
74
+ * Initializes a new instance of the LightOdometer class.
75
+ * Sets up the odometer's options, formats, and DOM structure.
76
+ * If an odometer instance already exists on the element, it returns the existing instance.
77
+ * @param {LightOdometerOptions} options - Configuration options for the odometer.
78
+ */
84
79
  constructor(options: LightOdometerOptions);
85
80
  /**
86
- * Renders the inner container of the odometer.
87
- * Clears the root element (`this.el`) and appends a new child element with the class `odometer-inside`.
88
- * @returns {void}
89
- */
81
+ * Renders the inner container of the odometer.
82
+ * Clears the root element (`this.el`) and appends a new child element with the class `odometer-inside`.
83
+ * @returns {void}
84
+ */
90
85
  renderInside(): void;
91
86
  /**
92
- * Observes changes to the root element's content and updates the odometer accordingly.
93
- * This is a fallback for environments like Safari where `.innerHTML` cannot be wrapped.
94
- * @returns {void}
95
- */
87
+ * Observes changes to the root element's content and updates the odometer accordingly.
88
+ * This is a fallback for environments like Safari where `.innerHTML` cannot be wrapped.
89
+ * @returns {void}
90
+ */
96
91
  watchForMutations(): void;
97
92
  /**
98
- * Starts observing mutations on the root element (`this.el`).
99
- * Listens for changes to the element's child nodes (e.g., additions or removals).
100
- * Requires `this.watchMutations` to be `true` and a `MutationObserver` to be initialized.
101
- * @returns {void}
102
- */
93
+ * Starts observing mutations on the root element (`this.el`).
94
+ * Listens for changes to the element's child nodes (e.g., additions or removals).
95
+ * Requires `this.watchMutations` to be `true` and a `MutationObserver` to be initialized.
96
+ * @returns {void}
97
+ */
103
98
  startWatchingMutations(): void;
104
99
  /**
105
- * Stops observing mutations on the root element (`this.el`).
106
- * Disconnects the `MutationObserver` if it is initialized.
107
- * @returns {void}
108
- */
100
+ * Stops observing mutations on the root element (`this.el`).
101
+ * Disconnects the `MutationObserver` if it is initialized.
102
+ * @returns {void}
103
+ */
109
104
  stopWatchingMutations(): void;
110
105
  /**
111
- * Cleans and normalizes a value to ensure it can be processed as a number.
112
- * Converts formatted strings into numeric values by handling radix symbols and removing unnecessary characters.
113
- * @param {string | number} val - The value to clean and normalize.
114
- * @returns {number} The cleaned and rounded numeric value.
115
- */
106
+ * Cleans and normalizes a value to ensure it can be processed as a number.
107
+ * Converts formatted strings into numeric values by handling radix symbols and removing unnecessary characters.
108
+ * @param {string | number} val - The value to clean and normalize.
109
+ * @returns {number} The cleaned and rounded numeric value.
110
+ */
116
111
  cleanValue(val: string | number): number;
117
112
  /**
118
- * Binds transition end events to the root element (`this.el`).
119
- * Ensures that the odometer re-renders only once per transition, even if multiple transition end events are triggered. After rendering, it dispatches the `odometerdone` custom event.
120
- * @returns {void}
121
- */
113
+ * Binds transition end events to the root element (`this.el`).
114
+ * Ensures that the odometer re-renders only once per transition, even if multiple transition end events are triggered. After rendering, it dispatches the `odometerdone` custom event.
115
+ * @returns {void}
116
+ */
122
117
  bindTransitionEnd(): void;
123
118
  /**
124
- * Resets and parses the odometer's format configuration.
125
- * Extracts the repeating pattern, radix symbol, and precision from the format string.
126
- * Throws an error if the format string is invalid or unparsable.
127
- * @returns {void}
128
- */
119
+ * Resets and parses the odometer's format configuration.
120
+ * Extracts the repeating pattern, radix symbol, and precision from the format string.
121
+ * Throws an error if the format string is invalid or unparsable.
122
+ * @returns {void}
123
+ */
129
124
  resetFormat(): void;
130
125
  /**
131
- * Renders the odometer with the specified value.
132
- * Updates the DOM structure, applies the appropriate classes, and formats the digits for display.
133
- * @param {number} [value] - The value to render. Defaults to the current value (`this.value`).
134
- * @returns {void}
135
- */
126
+ * Renders the odometer with the specified value.
127
+ * Updates the DOM structure, applies the appropriate classes, and formats the digits for display.
128
+ * @param {number} [value] - The value to render. Defaults to the current value (`this.value`).
129
+ * @returns {void}
130
+ */
136
131
  render(value?: number): void;
137
132
  /**
138
- * Formats the given value into individual digits and renders them.
139
- * If a custom format function is provided, it uses that to format the value.
140
- * Otherwise, it preserves the precision and formats the value based on the odometer's configuration.
141
- * @param {number} value - The value to format and render as digits.
142
- * @returns {void}
143
- */
133
+ * Formats the given value into individual digits and renders them.
134
+ * If a custom format function is provided, it uses that to format the value.
135
+ * Otherwise, it preserves the precision and formats the value based on the odometer's configuration.
136
+ * @param {number} value - The value to format and render as digits.
137
+ * @returns {void}
138
+ */
144
139
  formatDigits(value: number): void;
145
140
  /**
146
- * Ensures the value maintains the specified precision by adding trailing zeros if necessary.
147
- * This is used to keep the decimal places consistent at the end of the animation.
148
- * @param {number} value - The numeric value to format with preserved precision.
149
- * @returns {string} The value as a string with the required precision.
150
- */
141
+ * Ensures the value maintains the specified precision by adding trailing zeros if necessary.
142
+ * This is used to keep the decimal places consistent at the end of the animation.
143
+ * @param {number} value - The numeric value to format with preserved precision.
144
+ * @returns {string} The value as a string with the required precision.
145
+ */
151
146
  preservePrecision(value: number): string;
152
147
  /**
153
- * Updates the odometer to display a new value.
154
- * Cleans and normalizes the input value, determines the difference from the current value, and triggers the appropriate animations and DOM updates.
155
- * @param {string | number} newValue - The new value to update the odometer to.
156
- * @returns {number} The updated value of the odometer.
157
- */
148
+ * Updates the odometer to display a new value.
149
+ * Cleans and normalizes the input value, determines the difference from the current value, and triggers the appropriate animations and DOM updates.
150
+ * @param {string | number} newValue - The new value to update the odometer to.
151
+ * @returns {number} The updated value of the odometer.
152
+ */
158
153
  update(newValue: string | number): number;
159
154
  /**
160
- * Creates and returns a new digit element for the odometer.
161
- * The digit element is generated from the predefined `DIGIT_HTML` template.
162
- * @returns {HTMLElement} The newly created digit element.
163
- */
155
+ * Creates and returns a new digit element for the odometer.
156
+ * The digit element is generated from the predefined `DIGIT_HTML` template.
157
+ * @returns {HTMLElement} The newly created digit element.
158
+ */
164
159
  renderDigit(): HTMLElement;
165
160
  /**
166
- * Inserts a digit element into the odometer's inner container.
167
- * If a reference element (`before`) is provided, the digit is inserted before it.
168
- * Otherwise, the digit is appended to the container or inserted at the beginning if other children exist.
169
- * @param {HTMLElement} digit - The digit element to insert.
170
- * @param {HTMLElement | null} [before] - The reference element to insert the digit before. Defaults to `null`.
171
- * @returns {HTMLElement} The inserted digit element.
172
- */
161
+ * Inserts a digit element into the odometer's inner container.
162
+ * If a reference element (`before`) is provided, the digit is inserted before it.
163
+ * Otherwise, the digit is appended to the container or inserted at the beginning if other children exist.
164
+ * @param {HTMLElement} digit - The digit element to insert.
165
+ * @param {HTMLElement | null} [before] - The reference element to insert the digit before. Defaults to `null`.
166
+ * @returns {HTMLElement} The inserted digit element.
167
+ */
173
168
  insertDigit(digit: HTMLElement, before?: HTMLElement | null): HTMLElement;
174
169
  /**
175
- * Creates and inserts a spacer element into the odometer's inner container.
176
- * A spacer is a non-digit element (e.g., a comma or decimal point) used for formatting.
177
- * @param {string} chr - The character to display in the spacer.
178
- * @param {HTMLElement | null} [before] - The reference element to insert the spacer before. Defaults to `null`.
179
- * @param {string} [extraClasses] - Additional CSS classes to apply to the spacer element.
180
- * @returns {HTMLElement} The inserted spacer element.
181
- */
170
+ * Creates and inserts a spacer element into the odometer's inner container.
171
+ * A spacer is a non-digit element (e.g., a comma or decimal point) used for formatting.
172
+ * @param {string} chr - The character to display in the spacer.
173
+ * @param {HTMLElement | null} [before] - The reference element to insert the spacer before. Defaults to `null`.
174
+ * @param {string} [extraClasses] - Additional CSS classes to apply to the spacer element.
175
+ * @returns {HTMLElement} The inserted spacer element.
176
+ */
182
177
  addSpacer(chr: string, before?: HTMLElement | null, extraClasses?: string): HTMLElement;
183
178
  /**
184
- * Adds a digit or spacer element to the odometer's inner container.
185
- * Handles special cases for negation (`-`) and radix (`.`) characters, and ensures the format's repeating pattern is respected.
186
- * @param {string} value - The digit or character to add.
187
- * @param {boolean} [repeating=true] - Whether to use the repeating format pattern. Defaults to `true`.
188
- * @returns {HTMLElement} The inserted digit or spacer element.
189
- * @throws {Error} If the format string is invalid or lacks digits.
190
- */
179
+ * Adds a digit or spacer element to the odometer's inner container.
180
+ * Handles special cases for negation (`-`) and radix (`.`) characters, and ensures the format's repeating pattern is respected.
181
+ * @param {string} value - The digit or character to add.
182
+ * @param {boolean} [repeating=true] - Whether to use the repeating format pattern. Defaults to `true`.
183
+ * @returns {HTMLElement} The inserted digit or spacer element.
184
+ * @throws {Error} If the format string is invalid or lacks digits.
185
+ */
191
186
  addDigit(value: string, repeating?: boolean): HTMLElement;
192
187
  /**
193
- * Animates the odometer to transition to a new value.
194
- * Chooses the appropriate animation method (`count` or `slide`) based on the configuration and browser support.
195
- * @param {number} newValue - The new value to animate the odometer to.
196
- * @returns {void}
197
- */
188
+ * Animates the odometer to transition to a new value.
189
+ * Chooses the appropriate animation method (`count` or `slide`) based on the configuration and browser support.
190
+ * @param {number} newValue - The new value to animate the odometer to.
191
+ * @returns {void}
192
+ */
198
193
  animate(newValue: number): void;
199
194
  /**
200
- * Animates the odometer by incrementing or decrementing the value over time.
201
- * Uses a "counting" animation to transition smoothly to the new value.
202
- * @param {number} newValue - The new value to animate the odometer to.
203
- * @returns {void}
204
- */
195
+ * Animates the odometer by incrementing or decrementing the value over time.
196
+ * Uses a "counting" animation to transition smoothly to the new value.
197
+ * @param {number} newValue - The new value to animate the odometer to.
198
+ * @returns {void}
199
+ */
205
200
  animateCount(newValue: number): void;
206
201
  /**
207
- * Calculates the number of digits in the largest absolute value from the provided numbers.
208
- * @param {...number} values - A list of numbers to evaluate.
209
- * @returns {number} The number of digits in the largest absolute value.
210
- */
202
+ * Calculates the number of digits in the largest absolute value from the provided numbers.
203
+ * @param {...number} values - A list of numbers to evaluate.
204
+ * @returns {number} The number of digits in the largest absolute value.
205
+ */
211
206
  getDigitCount(...values: number[]): number;
212
207
  /**
213
- * Calculates the maximum number of fractional digits (decimal places) among the provided numbers.
214
- * Assumes the values have already been rounded to the specified precision.
215
- * @param {...number} values - A list of numbers to evaluate.
216
- * @returns {number} The maximum number of fractional digits.
217
- */
208
+ * Calculates the maximum number of fractional digits (decimal places) among the provided numbers.
209
+ * Assumes the values have already been rounded to the specified precision.
210
+ * @param {...number} values - A list of numbers to evaluate.
211
+ * @returns {number} The maximum number of fractional digits.
212
+ */
218
213
  getFractionalDigitCount(...values: number[]): number;
219
214
  /**
220
- * Resets the odometer's digits and ribbons.
221
- * Clears the inner container, resets the format configuration, and prepares the odometer for re-rendering.
222
- * @returns {void}
223
- */
215
+ * Resets the odometer's digits and ribbons.
216
+ * Clears the inner container, resets the format configuration, and prepares the odometer for re-rendering.
217
+ * @returns {void}
218
+ */
224
219
  resetDigits(): void;
225
220
  /**
226
- * Creates an array of numbers between two values
227
- * @param start The starting value of the range
228
- * @param end The ending value of the range
229
- * @param inclusive Whether to include the end value in the range
230
- * @returns An array containing the range of numbers
231
- */
221
+ * Creates an array of numbers between two values
222
+ * @param start The starting value of the range
223
+ * @param end The ending value of the range
224
+ * @param inclusive Whether to include the end value in the range
225
+ * @returns An array containing the range of numbers
226
+ */
232
227
  createRange(start: number, end: number, inclusive: boolean): number[];
233
228
  /**
234
- * Animates the odometer to transition to a new value using a sliding animation.
235
- * Breaks the value into individual digits, calculates the frames for each digit's animation, and updates the DOM to reflect the sliding effect.
236
- * @param {number} newValue - The new value to animate the odometer to.
237
- * @returns {void}
238
- */
229
+ * Animates the odometer to transition to a new value using a sliding animation.
230
+ * Breaks the value into individual digits, calculates the frames for each digit's animation, and updates the DOM to reflect the sliding effect.
231
+ * @param {number} newValue - The new value to animate the odometer to.
232
+ * @returns {void}
233
+ */
239
234
  animateSlide(newValue: number): void;
240
235
  /**
241
- * Initializes all odometer elements on the page.
242
- * Selects elements matching the configured selector or the default `.odometer` class, and creates a `LightOdometer` instance for each element.
243
- * @returns {LightOdometer[]} An array of initialized `LightOdometer` instances.
244
- */
236
+ * Initializes all odometer elements on the page.
237
+ * Selects elements matching the configured selector or the default `.odometer` class, and creates a `LightOdometer` instance for each element.
238
+ * @returns {LightOdometer[]} An array of initialized `LightOdometer` instances.
239
+ */
245
240
  static init(): LightOdometer[];
246
241
  /**
247
- * Mutate this instance's options on-the-fly.
248
- * Recomputes timing fields and applies changes immediately.
249
- * If a new value is provided, it triggers update() with the new configuration.
250
- */
242
+ * Mutate this instance's options on-the-fly.
243
+ * Recomputes timing fields and applies changes immediately.
244
+ * If a new value is provided, it triggers update() with the new configuration.
245
+ */
251
246
  setOptions(newOptions: Partial<LightOdometerOptions>): void;
252
247
  /**
253
- * Update global default options at runtime. Does not retroactively affect existing instances.
254
- */
248
+ * Update global default options at runtime. Does not retroactively affect existing instances.
249
+ */
255
250
  static setGlobalOptions(newOptions: Partial<LightOdometerGlobalOptions>): void;
256
251
  /** Subscribe to odometer events ("odometerstart" | "odometerdone") for this instance */
257
252
  on(event: LightOdometerEventName, handler: EventListener): void;
258
253
  /** Unsubscribe from odometer events for this instance */
259
254
  off(event: LightOdometerEventName, handler: EventListener): void;
260
255
  /**
261
- * Get a shallow copy snapshot of this instance's current options.
262
- * Mutating the returned object will not affect the instance.
263
- */
256
+ * Get a shallow copy snapshot of this instance's current options.
257
+ * Mutating the returned object will not affect the instance.
258
+ */
264
259
  getOptions(): Readonly<LightOdometerOptions>;
265
260
  /**
266
- * Get a shallow copy snapshot of global default options.
267
- */
261
+ * Get a shallow copy snapshot of global default options.
262
+ */
268
263
  static getGlobalOptions(): Readonly<LightOdometerGlobalOptions>;
269
264
  /**
270
- * Animate to a value exactly once and then disconnect listeners/observers.
271
- * Useful for static numbers that only animate on first reveal.
272
- */
265
+ * Animate to a value exactly once and then disconnect listeners/observers.
266
+ * Useful for static numbers that only animate on first reveal.
267
+ */
273
268
  animateOnceAndDisconnect(toValue?: number | string): void;
274
269
  /**
275
- * Disconnect all observers and cancel animation frames. Remove transition listener flag.
276
- */
270
+ * Disconnect all observers and cancel animation frames. Remove transition listener flag.
271
+ */
277
272
  disconnect(): void;
278
273
  /**
279
- * Returns the current odometer object along with the global options.
280
- * @returns {string} A JSON-parseable string representation of the current odometer state.
281
- */
274
+ * Returns the current odometer object along with the global options.
275
+ * @returns {string} A JSON-parseable string representation of the current odometer state.
276
+ */
282
277
  toString(): string;
283
278
  }
284
279
  //#endregion
@@ -1 +1 @@
1
- const e=/^\(?([^)]*)\)?(?:(.)(d+))?$/,t=2e3;function n(e){let t=document.createElement(`div`);if(t.innerHTML=e,!(t.firstElementChild instanceof HTMLElement))throw Error(`Invalid HTML: No valid root element found.`);return t.firstElementChild}function r(e,t){let n=t.split(` `);for(let t of n)e.classList.remove(t);return e.className}function i(e,t){let n=t.split(` `);for(let t of n)t&&e.classList.add(t);return e.className}function a(e,t,n){let r=new CustomEvent(t,{bubbles:!0,cancelable:!0,detail:n});e.dispatchEvent(r)}function o(){return typeof performance<`u`&&typeof performance.now==`function`?performance.now():Date.now()}function s(e,t){return t??=0,t?(e*=10**t,e+=.5,e=Math.floor(e),e/=10**t):Math.round(e)}function c(e){return e<0?Math.ceil(e):Math.floor(e)}function l(){return typeof window<`u`&&typeof document<`u`}function u(e){return l()&&typeof requestAnimationFrame==`function`?requestAnimationFrame(e):setTimeout(()=>e(o()),16)}function d(e){e!=null&&(l()&&typeof cancelAnimationFrame==`function`&&typeof e==`number`?cancelAnimationFrame(e):clearTimeout(e))}function f(e){l()&&(document.readyState===`complete`||document.readyState===`interactive`?setTimeout(e,0):document.addEventListener(`DOMContentLoaded`,e,{once:!0}))}function p(e){l()&&setTimeout(()=>{window.odometerOptions&&(e.options={...e.options,...window.odometerOptions})},0)}function m(e){f(()=>{e.options.auto!==!1&&e.init()})}var h=class f{static options=typeof window<`u`?window.odometerOptions??{}:{};_isAnimating=!1;options;el;value=0;inside;observer;watchMutations=!1;transitionEndBound=!1;destroyed=!1;format={repeating:``,precision:0};get isAnimating(){return this._isAnimating}MAX_VALUES;digits=[];ribbons={};_rafId;_countRafId;_msPerFrame;_countMsPerFrame;_onTransitionEnd;constructor(e){if(this.options=e,this.el=this.options.el,this.el.odometer){let e=this.el.odometer;e.options={...e.options,...this.options},e.options.duration??=t;let n=e.options.framerate??30,r=e.options.countFramerate??20;return e._msPerFrame=1e3/n,e._countMsPerFrame=1e3/r,e.MAX_VALUES=e.options.duration/e._msPerFrame/2|0,e.resetFormat(),this.options.value==null?l()&&e.render():e.update(this.options.value),e}this.el.odometer=this,this.options={...f.options,...this.options},this.options.duration??=t;let n=this.options.framerate??30,r=this.options.countFramerate??20;this._msPerFrame=1e3/n,this._countMsPerFrame=1e3/r,this.MAX_VALUES=this.options.duration/this._msPerFrame/2|0,this.resetFormat(),this.value=this.cleanValue(this.options.value??``),l()&&(this.renderInside(),this.render());try{for(let e of[`innerHTML`,`innerText`,`textContent`])Object.defineProperty(this.el,e,{get:()=>e===`innerHTML`?this.inside?.outerHTML??``:this.inside?.innerText??this.inside?.textContent??``,set:e=>this.update(e)})}catch{l()&&this.watchForMutations()}}renderInside(){l()&&(this.inside=document.createElement(`div`),this.inside.className=`odometer-inside`,this.el.textContent=``,this.el.appendChild(this.inside))}watchForMutations(){try{this.observer??=new MutationObserver(e=>{let t=this.el.innerText||``;this.renderInside(),this.render(this.value),this.update(t)}),this.watchMutations=!0,this.startWatchingMutations()}catch{}}startWatchingMutations(){this.watchMutations&&l()&&this.observer?.observe(this.el,{childList:!0})}stopWatchingMutations(){this.observer?.disconnect()}cleanValue(e){return typeof e==`string`&&(e=e.replace(this.format.radix??`.`,`<radix>`),e=e.replace(/[.,\s\u00A0\u202F]/g,``),e=e.replace(`<radix>`,`.`),e=parseFloat(e)||0),s(e,this.format.precision)}bindTransitionEnd(){if(this.transitionEndBound)return;this.transitionEndBound=!0;let e=!1;this._onTransitionEnd=()=>e?!0:(e=!0,setTimeout(()=>{this.render(),e=!1,this._isAnimating=!1,a(this.el,`odometerdone`,{id:this.options.id,el:this.el,instance:this,value:this.value,options:this.getOptions()})},0),!0),this.el.addEventListener(`transitionend`,this._onTransitionEnd,!1)}resetFormat(){let t=this.options.format??`(,ddd).dd`;t||=`d`;let n=e.exec(t);if(!n)throw Error(`LightOdometer: Unparsable digit format`);let[r,i,a,o]=n;this.format={repeating:i,radix:a,precision:o?.length||0}}render(e){if(l()){e??=this.value,this.stopWatchingMutations(),this.resetFormat(),this.inside.textContent=``;for(let e of Array.from(this.el.classList))/^odometer(-|$)/.test(e)&&this.el.classList.remove(e);this.el.classList.add(`odometer`,`odometer-auto-theme`),this.el.style.setProperty(`--odometer-duration`,`${this.options.duration??t}ms`),this.ribbons={},this.formatDigits(e),this.startWatchingMutations()}}formatDigits(e){if(this.digits=[],this.options.formatFunction){let t=this.options.formatFunction(e);for(let e of t.split(``).toReversed())if(/\d/.test(e)){let t=this.renderDigit(),n=t.querySelector(`.odometer-value`);n.textContent=e,i(n,`odometer-first-value odometer-last-value`),this.digits.push(t),this.insertDigit(t)}else this.addSpacer(e)}else{let t=this.preservePrecision(e),n=!this.format.precision;for(let e of t.split(``).toReversed())e===`.`&&(n=!0),this.addDigit(e,n)}}preservePrecision(e){let t=e.toString();if(this.format.precision){let e=t.split(`.`);e.length===1&&(t+=`.`,e[1]=``);for(let n=0;n<this.format.precision;n++)e[1][n]||(t+=`0`)}return t}update(e){if(!l())return this.value=this.cleanValue(e),this.value;e=this.cleanValue(e);let t=e-this.value;return t?(r(this.el,`odometer-animating-up odometer-animating-down odometer-animating`),t>0?i(this.el,`odometer-animating-up`):i(this.el,`odometer-animating-down`),this._isAnimating=!0,a(this.el,`odometerstart`,{id:this.options.id,el:this.el,instance:this,value:e,oldValue:this.value,options:this.getOptions()}),this.stopWatchingMutations(),this.animate(e),this.startWatchingMutations(),setTimeout(()=>{this.el.offsetHeight,i(this.el,`odometer-animating`)},0),this.value=e,this.value):this.value}renderDigit(){return n(`<span class="odometer-digit"><span class="odometer-digit-spacer">8</span><span class="odometer-digit-inner"><span class="odometer-ribbon"><span class="odometer-ribbon-inner"><span class="odometer-value"></span></span></span></span></span>`)}insertDigit(e,t){return t?this.inside.insertBefore(e,t):this.inside.children.length?this.inside.insertBefore(e,this.inside.children[0]):this.inside.appendChild(e)}addSpacer(e,t,r){let a=n(`<span class="odometer-formatting-mark"></span>`);return a.textContent=e,r&&i(a,r),this.insertDigit(a,t)}addDigit(e,t){if(t??=!0,e===`-`)return this.addSpacer(e,null,`odometer-negation-mark`);if(e===`.`)return this.addSpacer(this.format.radix??`.`,null,`odometer-radix-mark`);if(t){let e=!1;for(;;){if(!this.format.repeating.length){if(e)throw Error(`Bad odometer format without digits`);this.resetFormat(),e=!0}let t=this.format.repeating[this.format.repeating.length-1];if(this.format.repeating=this.format.repeating.substring(0,this.format.repeating.length-1),t===`d`)break;this.addSpacer(t)}}let n=this.renderDigit(),r=n.querySelector(`.odometer-value`);return r.textContent=e,i(r,`odometer-first-value odometer-last-value`),this.digits.push(n),this.insertDigit(n)}animate(e){this.options.animation===`count`?this.animateCount(e):this.animateSlide(e)}animateCount(e){if(!l())return;let t=e-this.value;if(!t)return;let n=o(),r=n,i=this.value,s=()=>{if(o()-n>(this.options.duration||0)){this.value=e,this.render(),this._isAnimating=!1,a(this.el,`odometerdone`,{id:this.options.id,el:this.el,instance:this,value:this.value,options:this.getOptions()});return}let c=o()-r;if(c>this._countMsPerFrame){r=o();let e=t*(c/(this.options.duration||0));i+=e,this.render(Math.round(i))}this._countRafId=u(s)};this._countRafId=u(s)}getDigitCount(...e){for(let t=0;t<e.length;t++)e[t]=Math.abs(e[t]);let t=Math.max(...e);return Math.ceil(Math.log(t+1)/Math.log(10))}getFractionalDigitCount(...e){let t=/^-?\d*\.(\d*?)0*$/;for(let n=0;n<e.length;n++){let r=e[n].toString(),i=t.exec(r);e[n]=i?i[1].length:0}return Math.max(...e)}resetDigits(){this.digits=[],this.ribbons={},this.inside.textContent=``,this.resetFormat()}createRange(e,t,n){let r=e<t,i=Math.abs(t-e)+(n?1:0);return Array.from({length:i},(t,n)=>r?e+n:e-n)}animateSlide(e){if(!l())return;let t=this.value,n=this.format.precision;n&&(e*=10**n,t*=10**n);let r=e-t;if(!r)return;this.bindTransitionEnd();let a=[],o=this.getDigitCount(t,e),s=0,u=t;for(let n=0;n<o;n++){u=c(t/10**(o-n-1));let r=c(e/10**(o-n-1)),i=r-u,l;if(Math.abs(i)>this.MAX_VALUES){l=[];let e=i/(this.MAX_VALUES*(1+s*.5)),t=u;for(;i>0&&t<r||i<0&&t>r;)l.push(Math.round(t)),t+=e;l[l.length-1]!==r&&l.push(r),s++}else l=this.createRange(u,r,!0);for(let e=0;e<l.length;e++)l[e]=Math.abs(l[e]%10);a.push(l)}this.resetDigits();let d=a.toReversed();for(let e=0;e<d.length;e++){let t=d[e];if(this.digits[e]||this.addDigit(` `,e>=n),this.ribbons[e]===void 0){let t=this.digits[e].querySelector(`.odometer-ribbon-inner`);t&&(this.ribbons[e]=t)}this.ribbons[e].textContent=``,r<0&&(t=t.toReversed());for(let n=0;n<t.length;n++){let r=t[n],a=document.createElement(`div`);a.className=`odometer-value`,a.textContent=r.toString(),this.ribbons[e].appendChild(a),n===t.length-1&&i(a,`odometer-last-value`),n===0&&i(a,`odometer-first-value`)}}u<0&&this.addDigit(`-`);let f=this.inside.querySelector(`.odometer-radix-mark`);f&&f.parentNode.removeChild(f),n&&this.addSpacer(this.format.radix??`.`,this.digits[n-1],`odometer-radix-mark`)}static init(){if(!l())return[];let e=document.querySelectorAll(f.options.selector||`.odometer`);return Array.from(e,e=>e.odometer=new f({el:e,value:e.innerText??e.textContent}))}setOptions(e){if(!e||typeof e!=`object`)return;`el`in e&&delete e.el;let n=Object.prototype.hasOwnProperty.call(e,`value`),r=Object.prototype.hasOwnProperty.call(e,`format`)||Object.prototype.hasOwnProperty.call(e,`formatFunction`),i=Object.prototype.hasOwnProperty.call(e,`duration`)||Object.prototype.hasOwnProperty.call(e,`framerate`)||Object.prototype.hasOwnProperty.call(e,`countFramerate`);this.options={...this.options,...e},this.options.duration??=t;let a=this.options.framerate??30,o=this.options.countFramerate??20;if(this._msPerFrame=1e3/a,this._countMsPerFrame=1e3/o,this.MAX_VALUES=this.options.duration/this._msPerFrame/2|0,l()&&this.el.style.setProperty(`--odometer-duration`,`${this.options.duration}ms`),r&&this.resetFormat(),n){this.update(this.options.value??0);return}(r||i)&&l()&&(this.stopWatchingMutations(),this.render(),this.startWatchingMutations())}static setGlobalOptions(e){f.options={...f.options,...e}}on(e,t){this.el.addEventListener(e,t)}off(e,t){this.el.removeEventListener(e,t)}getOptions(){return{...this.options}}static getGlobalOptions(){return{...f.options}}animateOnceAndDisconnect(e){e!=null&&this.update(e);let n=()=>this.disconnect(),r=e=>{this.el.removeEventListener(`odometerdone`,r),n()};this.el.addEventListener(`odometerdone`,r,{once:!0}),setTimeout(n,(this.options.duration??t)+100)}disconnect(){this.destroyed||=(this.stopWatchingMutations(),d(this._rafId),d(this._countRafId),this._onTransitionEnd&&=(this.el.removeEventListener(`transitionend`,this._onTransitionEnd),void 0),this.transitionEndBound=!1,!0)}toString(){let{el:e,...t}={...this.getOptions()},n={id:this.options.id,value:this.value,options:t,globalOptions:f.getGlobalOptions(),watchMutations:this.watchMutations,transitionEndBound:this.transitionEndBound,destroyed:this.destroyed,format:this.format,isAnimating:this.isAnimating};return JSON.stringify(n)}};p(h),m(h);export{h as LightOdometer,h as default};
1
+ const e=/^\(?([^)]*)\)?(?:(.)(d+))?$/,t=2e3;function n(e){let t=document.createElement(`div`);if(t.innerHTML=e,!(t.firstElementChild instanceof HTMLElement))throw Error(`Invalid HTML: No valid root element found.`);return t.firstElementChild}function r(e,t){let n=t.split(` `);for(let t of n)e.classList.remove(t);return e.className}function i(e,t){let n=t.split(` `);for(let t of n)t&&e.classList.add(t);return e.className}function a(e,t,n){let r=new CustomEvent(t,{bubbles:!0,cancelable:!0,detail:n});e.dispatchEvent(r)}function o(){return typeof performance<`u`&&typeof performance.now==`function`?performance.now():Date.now()}function s(e,t){return t??=0,t?(e*=10**t,e+=.5,e=Math.floor(e),e/=10**t):Math.round(e)}function c(e){return e<0?Math.ceil(e):Math.floor(e)}function l(){return typeof window<`u`&&typeof document<`u`}function u(e){return l()&&typeof requestAnimationFrame==`function`?requestAnimationFrame(e):setTimeout(()=>e(o()),16)}function d(e){e!=null&&(l()&&typeof cancelAnimationFrame==`function`&&typeof e==`number`?cancelAnimationFrame(e):clearTimeout(e))}function f(e){l()&&(document.readyState===`complete`||document.readyState===`interactive`?setTimeout(e,0):document.addEventListener(`DOMContentLoaded`,e,{once:!0}))}function p(e){l()&&setTimeout(()=>{window.odometerOptions&&(e.options={...e.options,...window.odometerOptions})},0)}function m(e){f(()=>{e.options.auto!==!1&&e.init()})}var h=class f{static options=typeof window<`u`?window.odometerOptions??{}:{};#e=!1;options;el;value=0;inside;observer;watchMutations=!1;transitionEndBound=!1;destroyed=!1;format={repeating:``,precision:0};get isAnimating(){return this.#e}MAX_VALUES;digits=[];ribbons={};#t;#n;#r;#i;#a;constructor(e){if(this.options=e,this.el=this.options.el,this.el.odometer){let e=this.el.odometer;e.options={...e.options,...this.options},e.options.duration??=t;let n=e.options.framerate??30,r=e.options.countFramerate??20;return e.#r=1e3/n,e.#i=1e3/r,e.MAX_VALUES=e.options.duration/e.#r/2|0,e.resetFormat(),this.options.value==null?l()&&e.render():e.update(this.options.value),e}this.el.odometer=this,this.options={...f.options,...this.options},this.options.duration??=t;let n=this.options.framerate??30,r=this.options.countFramerate??20;this.#r=1e3/n,this.#i=1e3/r,this.MAX_VALUES=this.options.duration/this.#r/2|0,this.resetFormat(),this.value=this.cleanValue(this.options.value??``),l()&&(this.renderInside(),this.render());try{for(let e of[`innerHTML`,`innerText`,`textContent`])Object.defineProperty(this.el,e,{get:()=>e===`innerHTML`?this.inside?.outerHTML??``:this.inside?.innerText??this.inside?.textContent??``,set:e=>this.update(e)})}catch{l()&&this.watchForMutations()}}renderInside(){l()&&(this.inside=document.createElement(`div`),this.inside.className=`odometer-inside`,this.el.textContent=``,this.el.appendChild(this.inside))}watchForMutations(){try{this.observer??=new MutationObserver(e=>{let t=this.el.innerText||``;this.renderInside(),this.render(this.value),this.update(t)}),this.watchMutations=!0,this.startWatchingMutations()}catch{}}startWatchingMutations(){this.watchMutations&&l()&&this.observer?.observe(this.el,{childList:!0})}stopWatchingMutations(){this.observer?.disconnect()}cleanValue(e){return typeof e==`string`&&(e=e.replace(this.format.radix??`.`,`<radix>`),e=e.replace(/[.,\s\u00A0\u202F]/g,``),e=e.replace(`<radix>`,`.`),e=parseFloat(e)||0),s(e,this.format.precision)}bindTransitionEnd(){if(this.transitionEndBound)return;this.transitionEndBound=!0;let e=!1;this.#a=()=>e?!0:(e=!0,setTimeout(()=>{this.render(),e=!1,this.#e=!1,a(this.el,`odometerdone`,{id:this.options.id,el:this.el,instance:this,value:this.value,options:this.getOptions()})},0),!0),this.el.addEventListener(`transitionend`,this.#a,!1)}resetFormat(){let t=this.options.format??`(,ddd).dd`;t||=`d`;let n=e.exec(t);if(!n)throw Error(`LightOdometer: Unparsable digit format`);let r=n[1]??``,i=n[2],a=(n[3]??``).length;this.format={repeating:r,radix:i,precision:a}}render(e){if(l()){e??=this.value,this.stopWatchingMutations(),this.resetFormat(),this.inside.textContent=``;for(let e of Array.from(this.el.classList))/^odometer(-|$)/.test(e)&&this.el.classList.remove(e);this.el.classList.add(`odometer`,`odometer-auto-theme`),this.el.style.setProperty(`--odometer-duration`,`${this.options.duration??2e3}ms`),this.ribbons={},this.formatDigits(e),this.startWatchingMutations()}}formatDigits(e){if(this.digits=[],this.options.formatFunction){let t=this.options.formatFunction(e);for(let e of t.split(``).toReversed())if(/\d/.test(e)){let t=this.renderDigit(),n=t.querySelector(`.odometer-value`);n.textContent=e,i(n,`odometer-first-value odometer-last-value`),this.digits.push(t),this.insertDigit(t)}else this.addSpacer(e)}else{let t=this.preservePrecision(e),n=!this.format.precision;for(let e of t.split(``).toReversed())e===`.`&&(n=!0),this.addDigit(e,n)}}preservePrecision(e){let t=e.toString();if(this.format.precision){let e=t.split(`.`);e.length===1&&(t+=`.`,e[1]=``);let n=e[1]??``;for(let e=0;e<this.format.precision;e++)n[e]||(t+=`0`)}return t}update(e){if(!l())return this.value=this.cleanValue(e),this.value;e=this.cleanValue(e);let t=e-this.value;return t?(r(this.el,`odometer-animating-up odometer-animating-down odometer-animating`),t>0?i(this.el,`odometer-animating-up`):i(this.el,`odometer-animating-down`),this.#e=!0,a(this.el,`odometerstart`,{id:this.options.id,el:this.el,instance:this,value:e,oldValue:this.value,options:this.getOptions()}),this.stopWatchingMutations(),this.animate(e),this.startWatchingMutations(),setTimeout(()=>{this.el.offsetHeight,i(this.el,`odometer-animating`)},0),this.value=e,this.value):this.value}renderDigit(){return n(`<span class="odometer-digit"><span class="odometer-digit-spacer">8</span><span class="odometer-digit-inner"><span class="odometer-ribbon"><span class="odometer-ribbon-inner"><span class="odometer-value"></span></span></span></span></span>`)}insertDigit(e,t){return t?this.inside.insertBefore(e,t):this.inside.children.length?this.inside.insertBefore(e,this.inside.firstElementChild):this.inside.appendChild(e)}addSpacer(e,t,r){let a=n(`<span class="odometer-formatting-mark"></span>`);return a.textContent=e,r&&i(a,r),this.insertDigit(a,t)}addDigit(e,t){if(t??=!0,e===`-`)return this.addSpacer(e,null,`odometer-negation-mark`);if(e===`.`)return this.addSpacer(this.format.radix??`.`,null,`odometer-radix-mark`);if(t){let e=!1;for(;;){if(!this.format.repeating.length){if(e)throw Error(`Bad odometer format without digits`);this.resetFormat(),e=!0}let t=this.format.repeating[this.format.repeating.length-1];if(t!=null){if(this.format.repeating=this.format.repeating.substring(0,this.format.repeating.length-1),t===`d`)break;this.addSpacer(t)}}}let n=this.renderDigit(),r=n.querySelector(`.odometer-value`);return r.textContent=e,i(r,`odometer-first-value odometer-last-value`),this.digits.push(n),this.insertDigit(n)}animate(e){this.options.animation===`count`?this.animateCount(e):this.animateSlide(e)}animateCount(e){if(!l())return;let t=e-this.value;if(!t)return;let n=o(),r=n,i=this.value,s=()=>{if(o()-n>(this.options.duration||0)){this.value=e,this.render(),this.#e=!1,a(this.el,`odometerdone`,{id:this.options.id,el:this.el,instance:this,value:this.value,options:this.getOptions()});return}let c=o()-r;if(c>this.#i){r=o();let e=c/(this.options.duration||0),n=t*e;i+=n,this.render(Math.round(i))}this.#n=u(s)};this.#n=u(s)}getDigitCount(...e){for(let t=0;t<e.length;t++){let n=e[t]??0;e[t]=Math.abs(n)}let t=Math.max(...e);return Math.ceil(Math.log(t+1)/Math.log(10))}getFractionalDigitCount(...e){let t=/^-?\d*\.(\d*?)0*$/;for(let n=0;n<e.length;n++){let r=(e[n]??0).toString(),i=t.exec(r),a=i?.[1]??``;e[n]=i?a.length:0}return Math.max(...e)}resetDigits(){this.digits=[],this.ribbons={},this.inside.textContent=``,this.resetFormat()}createRange(e,t,n){let r=e<t,i=Math.abs(t-e)+ +!!n;return Array.from({length:i},(t,n)=>r?e+n:e-n)}animateSlide(e){if(!l())return;let t=this.value,n=this.format.precision;n&&(e*=10**n,t*=10**n);let r=e-t;if(!r)return;this.bindTransitionEnd();let a=[],o=this.getDigitCount(t,e),s=0,u=t;for(let n=0;n<o;n++){u=c(t/10**(o-n-1));let r=c(e/10**(o-n-1)),i=r-u,l;if(Math.abs(i)>this.MAX_VALUES){l=[];let e=i/(this.MAX_VALUES*(1+s*.5)),t=u;for(;i>0&&t<r||i<0&&t>r;)l.push(Math.round(t)),t+=e;l[l.length-1]!==r&&l.push(r),s++}else l=this.createRange(u,r,!0);for(let e=0;e<l.length;e++){let t=l[e]??0;l[e]=Math.abs(t%10)}a.push(l)}this.resetDigits();let d=a.toReversed();for(let e=0;e<d.length;e++){let t=d[e]??[];this.digits[e]||this.addDigit(` `,e>=n);let a=this.digits[e];if(!a)continue;if(this.ribbons[e]===void 0){let t=a.querySelector(`.odometer-ribbon-inner`);t&&(this.ribbons[e]=t)}let o=this.ribbons[e];if(o){o.textContent=``,r<0&&(t=t.toReversed());for(let e=0;e<t.length;e++){let n=t[e]??0,r=document.createElement(`div`);r.className=`odometer-value`,r.textContent=n.toString(),o.appendChild(r),e===t.length-1&&i(r,`odometer-last-value`),e===0&&i(r,`odometer-first-value`)}}}u<0&&this.addDigit(`-`);let f=this.inside.querySelector(`.odometer-radix-mark`);f&&f.parentNode.removeChild(f),n&&this.addSpacer(this.format.radix??`.`,this.digits[n-1],`odometer-radix-mark`)}static init(){if(!l())return[];let e=document.querySelectorAll(f.options.selector||`.odometer`);return Array.from(e,e=>e.odometer=new f({el:e,value:e.innerText??e.textContent}))}setOptions(e){if(!e||typeof e!=`object`)return;`el`in e&&delete e.el;let n=Object.prototype.hasOwnProperty.call(e,`value`),r=Object.prototype.hasOwnProperty.call(e,`format`)||Object.prototype.hasOwnProperty.call(e,`formatFunction`),i=Object.prototype.hasOwnProperty.call(e,`duration`)||Object.prototype.hasOwnProperty.call(e,`framerate`)||Object.prototype.hasOwnProperty.call(e,`countFramerate`);this.options={...this.options,...e},this.options.duration??=t;let a=this.options.framerate??30,o=this.options.countFramerate??20;if(this.#r=1e3/a,this.#i=1e3/o,this.MAX_VALUES=this.options.duration/this.#r/2|0,l()&&this.el.style.setProperty(`--odometer-duration`,`${this.options.duration}ms`),r&&this.resetFormat(),n){this.update(this.options.value??0);return}(r||i)&&l()&&(this.stopWatchingMutations(),this.render(),this.startWatchingMutations())}static setGlobalOptions(e){f.options={...f.options,...e}}on(e,t){this.el.addEventListener(e,t)}off(e,t){this.el.removeEventListener(e,t)}getOptions(){return{...this.options}}static getGlobalOptions(){return{...f.options}}animateOnceAndDisconnect(e){e!=null&&this.update(e);let t=()=>this.disconnect(),n=e=>{this.el.removeEventListener(`odometerdone`,n),t()};this.el.addEventListener(`odometerdone`,n,{once:!0}),setTimeout(t,(this.options.duration??2e3)+100)}disconnect(){this.destroyed||=(this.stopWatchingMutations(),d(this.#t),d(this.#n),this.#a&&=(this.el.removeEventListener(`transitionend`,this.#a),void 0),this.transitionEndBound=!1,!0)}toString(){let{el:e,...t}={...this.getOptions()},n={id:this.options.id,value:this.value,options:t,globalOptions:f.getGlobalOptions(),watchMutations:this.watchMutations,transitionEndBound:this.transitionEndBound,destroyed:this.destroyed,format:this.format,isAnimating:this.isAnimating};return JSON.stringify(n)}};p(h),m(h);export{h as LightOdometer,h as default};
package/package.json CHANGED
@@ -11,17 +11,17 @@
11
11
  ],
12
12
  "description": "for personal use only, see EDM115/website",
13
13
  "devDependencies": {
14
- "@stylistic/eslint-plugin": "~5.8.0",
15
- "@types/node": "latest",
16
- "@typescript-eslint/parser": "~8.56.0",
17
- "@typescript/native-preview": "latest",
18
- "edm115-lint": "~0.1.6",
19
- "eslint": "~10.0.0",
20
- "jiti": "~2.6.1",
21
- "oxlint": "~1.48.0",
22
- "oxlint-tsgolint": "~0.14.0",
23
- "tsdown": "~0.20.3",
24
- "unplugin-unused": "~0.5.7"
14
+ "@stylistic/eslint-plugin": "~5.10.0",
15
+ "@typescript-eslint/parser": "~8.67.0",
16
+ "@typescript/native": "npm:typescript@^7.0.2",
17
+ "edm115-lint": "~0.2.1",
18
+ "eslint": "~10.8.1",
19
+ "jiti": "~2.7.0",
20
+ "oxlint": "~1.78.0",
21
+ "oxlint-tsgolint": "~7.0.2001",
22
+ "tsdown": "~0.22.14",
23
+ "typescript": "npm:@typescript/typescript6@^6.0.2",
24
+ "unplugin-unused": "~0.6.0"
25
25
  },
26
26
  "exports": {
27
27
  ".": {
@@ -62,13 +62,13 @@
62
62
  "url": "git+https://github.com/EDM115/light-odometer.git"
63
63
  },
64
64
  "type": "module",
65
- "version": "0.1.1",
65
+ "version": "0.1.3",
66
66
  "scripts": {
67
67
  "build": "tsdown",
68
68
  "format": "eslint -c eslint.stylistic.config.ts --concurrency=auto --fix .",
69
- "lint": "oxlint --type-aware .",
70
- "lint:fix": "oxlint --fix --type-aware .",
69
+ "lint": "oxlint",
70
+ "lint:fix": "oxlint --fix",
71
71
  "release": "pnpm publish",
72
- "typecheck": "tsgo --noEmit"
72
+ "typecheck": "tsc --noEmit"
73
73
  }
74
74
  }