@tabler/core 1.6.0 → 1.6.1

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.
Files changed (115) hide show
  1. package/dist/css/tabler-flags.css +1 -1
  2. package/dist/css/tabler-flags.css.map +1 -1
  3. package/dist/css/tabler-flags.min.css +1 -1
  4. package/dist/css/tabler-flags.min.css.map +1 -1
  5. package/dist/css/tabler-flags.rtl.css +1 -1
  6. package/dist/css/tabler-flags.rtl.css.map +1 -1
  7. package/dist/css/tabler-flags.rtl.min.css +1 -1
  8. package/dist/css/tabler-flags.rtl.min.css.map +1 -1
  9. package/dist/css/tabler-marketing.css +1 -1
  10. package/dist/css/tabler-marketing.css.map +1 -1
  11. package/dist/css/tabler-marketing.min.css +1 -1
  12. package/dist/css/tabler-marketing.min.css.map +1 -1
  13. package/dist/css/tabler-marketing.rtl.css +1 -1
  14. package/dist/css/tabler-marketing.rtl.css.map +1 -1
  15. package/dist/css/tabler-marketing.rtl.min.css +1 -1
  16. package/dist/css/tabler-marketing.rtl.min.css.map +1 -1
  17. package/dist/css/tabler-payments.css +1 -1
  18. package/dist/css/tabler-payments.css.map +1 -1
  19. package/dist/css/tabler-payments.min.css +1 -1
  20. package/dist/css/tabler-payments.min.css.map +1 -1
  21. package/dist/css/tabler-payments.rtl.css +1 -1
  22. package/dist/css/tabler-payments.rtl.css.map +1 -1
  23. package/dist/css/tabler-payments.rtl.min.css +1 -1
  24. package/dist/css/tabler-payments.rtl.min.css.map +1 -1
  25. package/dist/css/tabler-props.css +1 -1
  26. package/dist/css/tabler-props.css.map +1 -1
  27. package/dist/css/tabler-props.min.css +1 -1
  28. package/dist/css/tabler-props.min.css.map +1 -1
  29. package/dist/css/tabler-props.rtl.css +1 -1
  30. package/dist/css/tabler-props.rtl.css.map +1 -1
  31. package/dist/css/tabler-props.rtl.min.css +1 -1
  32. package/dist/css/tabler-props.rtl.min.css.map +1 -1
  33. package/dist/css/tabler-socials.css +1 -1
  34. package/dist/css/tabler-socials.css.map +1 -1
  35. package/dist/css/tabler-socials.min.css +1 -1
  36. package/dist/css/tabler-socials.min.css.map +1 -1
  37. package/dist/css/tabler-socials.rtl.css +1 -1
  38. package/dist/css/tabler-socials.rtl.css.map +1 -1
  39. package/dist/css/tabler-socials.rtl.min.css +1 -1
  40. package/dist/css/tabler-socials.rtl.min.css.map +1 -1
  41. package/dist/css/tabler-themes.css +1 -1
  42. package/dist/css/tabler-themes.css.map +1 -1
  43. package/dist/css/tabler-themes.min.css +1 -1
  44. package/dist/css/tabler-themes.min.css.map +1 -1
  45. package/dist/css/tabler-themes.rtl.css +1 -1
  46. package/dist/css/tabler-themes.rtl.css.map +1 -1
  47. package/dist/css/tabler-themes.rtl.min.css +1 -1
  48. package/dist/css/tabler-themes.rtl.min.css.map +1 -1
  49. package/dist/css/tabler-vendors.css +1 -1
  50. package/dist/css/tabler-vendors.css.map +1 -1
  51. package/dist/css/tabler-vendors.min.css +1 -1
  52. package/dist/css/tabler-vendors.min.css.map +1 -1
  53. package/dist/css/tabler-vendors.rtl.css +1 -1
  54. package/dist/css/tabler-vendors.rtl.css.map +1 -1
  55. package/dist/css/tabler-vendors.rtl.min.css +1 -1
  56. package/dist/css/tabler-vendors.rtl.min.css.map +1 -1
  57. package/dist/css/tabler.css +12 -2
  58. package/dist/css/tabler.css.map +1 -1
  59. package/dist/css/tabler.min.css +2 -2
  60. package/dist/css/tabler.min.css.map +1 -1
  61. package/dist/css/tabler.rtl.css +12 -2
  62. package/dist/css/tabler.rtl.css.map +1 -1
  63. package/dist/css/tabler.rtl.min.css +2 -2
  64. package/dist/css/tabler.rtl.min.css.map +1 -1
  65. package/dist/js/tabler-theme.esm.js +21 -6
  66. package/dist/js/tabler-theme.esm.js.map +1 -1
  67. package/dist/js/tabler-theme.esm.min.js +2 -2
  68. package/dist/js/tabler-theme.esm.min.js.map +1 -1
  69. package/dist/js/tabler-theme.js +21 -6
  70. package/dist/js/tabler-theme.js.map +1 -1
  71. package/dist/js/tabler-theme.min.js +2 -2
  72. package/dist/js/tabler-theme.min.js.map +1 -1
  73. package/dist/js/tabler.esm.js +332 -131
  74. package/dist/js/tabler.esm.js.map +1 -1
  75. package/dist/js/tabler.esm.min.js +2 -2
  76. package/dist/js/tabler.esm.min.js.map +1 -1
  77. package/dist/js/tabler.js +332 -131
  78. package/dist/js/tabler.js.map +1 -1
  79. package/dist/js/tabler.min.js +2 -2
  80. package/dist/js/tabler.min.js.map +1 -1
  81. package/dist/libs/vanilla-calendar-pro/index.js +2 -2
  82. package/dist/types/src/clipboard.d.ts +3 -0
  83. package/dist/types/src/confetti.d.ts +2 -0
  84. package/dist/types/src/countup.d.ts +2 -0
  85. package/dist/types/src/datepicker.d.ts +24 -6
  86. package/dist/types/src/input-mask.d.ts +2 -1
  87. package/dist/types/src/otp-input.d.ts +6 -2
  88. package/dist/types/src/strength.d.ts +1 -0
  89. package/dist/types/src/switch-icon.d.ts +1 -0
  90. package/js/src/autosize.ts +8 -3
  91. package/js/src/bootstrap/base-component.ts +12 -2
  92. package/js/src/bootstrap/dom/manipulator.ts +1 -1
  93. package/js/src/bootstrap/dropdown.ts +3 -1
  94. package/js/src/bootstrap/util/index.ts +14 -1
  95. package/js/src/clipboard.ts +26 -2
  96. package/js/src/confetti.ts +42 -7
  97. package/js/src/countup.ts +30 -12
  98. package/js/src/datepicker.ts +147 -27
  99. package/js/src/dropdown.ts +12 -5
  100. package/js/src/global.d.ts +4 -2
  101. package/js/src/input-mask.ts +7 -7
  102. package/js/src/otp-input.ts +99 -12
  103. package/js/src/popover.ts +13 -7
  104. package/js/src/sidebar.ts +14 -6
  105. package/js/src/sortable.ts +5 -1
  106. package/js/src/sparkline.ts +15 -6
  107. package/js/src/strength.ts +17 -4
  108. package/js/src/switch-icon.ts +15 -0
  109. package/js/src/tab.ts +17 -7
  110. package/js/src/toast.ts +19 -11
  111. package/js/src/tooltip.ts +16 -7
  112. package/js/tabler-theme.ts +28 -4
  113. package/package.json +2 -2
  114. package/scss/layout/_navbar.scss +1 -2
  115. package/scss/ui/_cards.scss +14 -2
@@ -92,7 +92,7 @@ class Autosize extends BaseComponent {
92
92
  // Public
93
93
  update(): void {
94
94
  const element = this._element
95
- if (!element.isConnected || element.offsetParent === null) {
95
+ if (!element.isConnected || element.getClientRects().length === 0) {
96
96
  return
97
97
  }
98
98
 
@@ -100,11 +100,16 @@ class Autosize extends BaseComponent {
100
100
  // position is put back afterwards.
101
101
  const { scrollTop } = document.documentElement
102
102
  const style = getComputedStyle(element)
103
- const border = style.boxSizing === 'border-box' ? Number.parseFloat(style.borderTopWidth) + Number.parseFloat(style.borderBottomWidth) : 0
103
+ // scrollHeight is the padding box: a border-box field adds its borders, a
104
+ // content-box field drops its padding.
105
+ const extra = style.boxSizing === 'border-box' ? Number.parseFloat(style.borderTopWidth) + Number.parseFloat(style.borderBottomWidth) : -(Number.parseFloat(style.paddingTop) + Number.parseFloat(style.paddingBottom))
104
106
  const before = element.style.height
105
107
 
108
+ // Measured without a scrollbar: one left over from the previous size
109
+ // would wrap the text and add a phantom line.
110
+ element.style.overflowY = 'hidden'
106
111
  element.style.height = 'auto'
107
- const height = `${element.scrollHeight + border}px`
112
+ const height = `${element.scrollHeight + extra}px`
108
113
  element.style.height = height
109
114
 
110
115
  // Past max-height the content no longer fits, so let the field scroll.
@@ -25,10 +25,12 @@ class BaseComponent extends Config {
25
25
  return
26
26
  }
27
27
 
28
+ const ctor = this.constructor as unknown as BaseComponentStatic
29
+ Data.get<BaseComponent>(resolved, ctor.DATA_KEY)?.dispose()
30
+
28
31
  this._element = resolved
29
32
  this._config = this._getConfig(config)
30
33
 
31
- const ctor = this.constructor as unknown as BaseComponentStatic
32
34
  Data.set(this._element, ctor.DATA_KEY, this)
33
35
  }
34
36
 
@@ -43,7 +45,15 @@ class BaseComponent extends Config {
43
45
  }
44
46
 
45
47
  _queueCallback(callback: () => void, element: HTMLElement, isAnimated = true): void {
46
- executeAfterTransition(callback, element, isAnimated)
48
+ executeAfterTransition(
49
+ () => {
50
+ if (this._element) {
51
+ callback()
52
+ }
53
+ },
54
+ element,
55
+ isAnimated,
56
+ )
47
57
  }
48
58
 
49
59
  _getConfig(config?: ComponentConfig): ComponentConfig {
@@ -43,7 +43,7 @@ const PREFIXES = ['tblr', 'bs'] as const
43
43
 
44
44
  const Manipulator = {
45
45
  setDataAttribute(element: HTMLElement, key: string, value: string): void {
46
- element.setAttribute(`data-tblr-${normalizeDataKey(key)}`, value)
46
+ element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value)
47
47
  },
48
48
 
49
49
  removeDataAttribute(element: HTMLElement, key: string): void {
@@ -58,7 +58,9 @@ const CLASS_NAME_DROPUP_CENTER = 'dropup-center'
58
58
  const CLASS_NAME_DROPDOWN_CENTER = 'dropdown-center'
59
59
 
60
60
  const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="dropdown"]:not(.disabled):not(:disabled), [data-tblr-toggle="dropdown"]:not(.disabled):not(:disabled)'
61
- const SELECTOR_DATA_TOGGLE_SHOWN = `.${CLASS_NAME_SHOW}[data-bs-toggle="dropdown"], .${CLASS_NAME_SHOW}[data-tblr-toggle="dropdown"]`
61
+ const SELECTOR_DATA_TOGGLE_SHOWN = SELECTOR_DATA_TOGGLE.split(', ')
62
+ .map((selector) => `${selector}.${CLASS_NAME_SHOW}`)
63
+ .join(', ')
62
64
  const SELECTOR_MENU = '.dropdown-menu'
63
65
  const SELECTOR_NAVBAR = '.navbar'
64
66
  const SELECTOR_NAVBAR_NAV = '.navbar-nav'
@@ -62,7 +62,18 @@ const triggerTransitionEnd = (element: HTMLElement): void => {
62
62
  element.dispatchEvent(new Event(TRANSITION_END))
63
63
  }
64
64
 
65
+ // A jQuery collection stands in for its first element, as in upstream Bootstrap.
66
+ const unwrapJQuery = (object: unknown): unknown => {
67
+ if (object && typeof object === 'object' && typeof (object as { jquery?: unknown }).jquery !== 'undefined') {
68
+ return (object as { 0?: unknown })[0]
69
+ }
70
+
71
+ return object
72
+ }
73
+
65
74
  const isElement = (object: unknown): object is HTMLElement => {
75
+ object = unwrapJQuery(object)
76
+
66
77
  if (!object || typeof object !== 'object') {
67
78
  return false
68
79
  }
@@ -71,6 +82,8 @@ const isElement = (object: unknown): object is HTMLElement => {
71
82
  }
72
83
 
73
84
  const getElement = (object: unknown): HTMLElement | null => {
85
+ object = unwrapJQuery(object)
86
+
74
87
  if (isElement(object)) {
75
88
  return object
76
89
  }
@@ -159,7 +172,7 @@ const reflow = (element: HTMLElement): void => {
159
172
  const isRTL = (): boolean => document.documentElement.dir === 'rtl'
160
173
 
161
174
  const getjQuery = (): JQueryStaticLike | null => {
162
- if (window.jQuery && !document.body.hasAttribute('data-bs-no-jquery')) {
175
+ if (window.jQuery && !document.body.hasAttribute('data-bs-no-jquery') && !document.body.hasAttribute('data-tblr-no-jquery')) {
163
176
  return window.jQuery
164
177
  }
165
178
 
@@ -9,6 +9,7 @@ import BaseComponent from './bootstrap/base-component'
9
9
  import EventHandler from './bootstrap/dom/event-handler'
10
10
  import SelectorEngine from './bootstrap/dom/selector-engine'
11
11
  import { initAll } from './bootstrap/util/component-functions'
12
+ import { getElement } from './bootstrap/util/index'
12
13
  import type { ElementSelector } from './bootstrap/types'
13
14
 
14
15
  type ComponentConfig = {
@@ -38,6 +39,8 @@ const CLASS_NAME_COPIED = 'copied'
38
39
  const SELECTOR_DATA_TOGGLE = `[data-bs-toggle="${NAME}"], [data-tblr-toggle="${NAME}"]`
39
40
  const SELECTOR_LABEL = `.${NAME}-label`
40
41
  const SELECTOR_FEEDBACK = `.${NAME}-feedback`
42
+ const ATTRIBUTE_TEXT = 'data-bs-text'
43
+ const ATTRIBUTE_TEXT_ALIAS = 'data-tblr-text'
41
44
 
42
45
  const Default: ComponentConfig = {
43
46
  target: null,
@@ -130,12 +133,18 @@ class Clipboard extends BaseComponent {
130
133
  return String(text)
131
134
  }
132
135
 
133
- const source = target ? SelectorEngine.findOne(target) : null
136
+ const source = target ? getElement(target) : null
134
137
  if (!source) {
135
138
  return ''
136
139
  }
137
140
 
138
- return source instanceof HTMLInputElement || source instanceof HTMLTextAreaElement ? source.value : (source.textContent ?? '').trim()
141
+ if (source instanceof HTMLInputElement || source instanceof HTMLTextAreaElement) {
142
+ return source.value
143
+ }
144
+
145
+ // innerText keeps line breaks and skips hidden text such as a
146
+ // visually-hidden label; textContent is the fallback for SVG.
147
+ return (source instanceof HTMLElement ? source.innerText : ((source as Element).textContent ?? '')).trim()
139
148
  }
140
149
 
141
150
  // Public
@@ -158,12 +167,27 @@ class Clipboard extends BaseComponent {
158
167
  EventHandler.trigger(this._element, EVENT_COPIED)
159
168
  }
160
169
 
170
+ /** Ends the copied state, for a `delay` of 0 or an early reset. */
171
+ reset(): void {
172
+ window.clearTimeout(this._timeout)
173
+ this._toggleCopied(false)
174
+ }
175
+
161
176
  dispose(): void {
162
177
  window.clearTimeout(this._timeout)
163
178
  super.dispose()
164
179
  }
165
180
 
166
181
  // Private
182
+ _mergeConfigObj(config?: Record<string, unknown>, element?: HTMLElement): Record<string, unknown> {
183
+ // The text is copied as written: `data-bs-text="007"` or `"true"` must not
184
+ // be read as a number or a boolean the way other data attributes are.
185
+ const raw = element?.getAttribute(ATTRIBUTE_TEXT) ?? element?.getAttribute(ATTRIBUTE_TEXT_ALIAS)
186
+ const dataOptions: Record<string, unknown> = raw === null || raw === undefined ? {} : { text: raw }
187
+
188
+ return super._mergeConfigObj({ ...dataOptions, ...config }, element)
189
+ }
190
+
167
191
  _showCopied(): void {
168
192
  window.clearTimeout(this._timeout)
169
193
  this._toggleCopied(true)
@@ -7,6 +7,7 @@
7
7
 
8
8
  import BaseComponent from './bootstrap/base-component'
9
9
  import EventHandler from './bootstrap/dom/event-handler'
10
+ import Manipulator from './bootstrap/dom/manipulator'
10
11
  import SelectorEngine from './bootstrap/dom/selector-engine'
11
12
  import { isDisabled } from './bootstrap/util/index'
12
13
  import type { ComponentConfig as BaseConfig, ElementSelector } from './bootstrap/types'
@@ -58,6 +59,8 @@ type Emitter = {
58
59
  stopped: boolean
59
60
  /** every piece has landed and `end` was fired */
60
61
  done: boolean
62
+ /** the instance was disposed: the pieces still land, but `end` is not fired */
63
+ detached: boolean
61
64
  }
62
65
 
63
66
  /**
@@ -133,14 +136,21 @@ const stage = {
133
136
  frame: 0,
134
137
  lastTick: 0,
135
138
 
136
- add(emitter: Emitter): void {
137
- this.emitters.push(emitter)
139
+ add(emitter: Emitter): boolean {
138
140
  this._mount()
139
141
 
142
+ if (!this.context) {
143
+ return false
144
+ }
145
+
146
+ this.emitters.push(emitter)
147
+
140
148
  if (!this.frame) {
141
149
  this.lastTick = performance.now()
142
150
  this.frame = requestAnimationFrame((now) => this._tick(now))
143
151
  }
152
+
153
+ return true
144
154
  },
145
155
 
146
156
  _mount(): void {
@@ -161,9 +171,14 @@ const stage = {
161
171
  display: 'block',
162
172
  })
163
173
 
174
+ const context = canvas.getContext('2d')
175
+ if (!context) {
176
+ return
177
+ }
178
+
164
179
  document.body.append(canvas)
165
180
  this.canvas = canvas
166
- this.context = canvas.getContext('2d')
181
+ this.context = context
167
182
  this._resize()
168
183
  window.addEventListener('resize', this._onResize)
169
184
  },
@@ -191,7 +206,7 @@ const stage = {
191
206
  // The backing store is scaled for HiDPI screens; the CSS size above keeps
192
207
  // the element at the viewport size, so the drawing stays sharp and 1:1.
193
208
  const dpr = Math.min(window.devicePixelRatio || 1, MAX_DPR)
194
- this.canvas.width = window.innerWidth * dpr
209
+ this.canvas.width = document.documentElement.clientWidth * dpr
195
210
  this.canvas.height = window.innerHeight * dpr
196
211
  this.context.setTransform(dpr, 0, 0, dpr, 0, 0)
197
212
  },
@@ -312,7 +327,10 @@ const stage = {
312
327
 
313
328
  this.emitters.splice(i, 1)
314
329
  emitter.done = true
315
- EventHandler.trigger(emitter.element, EVENT_END)
330
+
331
+ if (!emitter.detached) {
332
+ EventHandler.trigger(emitter.element, EVENT_END)
333
+ }
316
334
  }
317
335
  },
318
336
  }
@@ -372,9 +390,13 @@ class Confetti extends BaseComponent {
372
390
  emitted: 0,
373
391
  stopped: false,
374
392
  done: false,
393
+ detached: false,
375
394
  }
376
395
 
377
- stage.add(this._emitter)
396
+ if (!stage.add(this._emitter)) {
397
+ this._emitter = null
398
+ EventHandler.trigger(this._element, EVENT_END)
399
+ }
378
400
  }
379
401
 
380
402
  stop(): void {
@@ -386,6 +408,10 @@ class Confetti extends BaseComponent {
386
408
  }
387
409
 
388
410
  dispose(): void {
411
+ if (this._emitter) {
412
+ this._emitter.detached = true
413
+ }
414
+
389
415
  this.stop()
390
416
  super.dispose()
391
417
  }
@@ -432,7 +458,16 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (
432
458
  }
433
459
 
434
460
  const target = SelectorEngine.getElementFromSelector(this) || this
435
- const instance = Confetti.getOrCreateInstance(target) as Confetti
461
+ const config = Manipulator.getDataAttributes(this)
462
+ const instance = Confetti.getOrCreateInstance(target, config) as Confetti
463
+
464
+ // The instance lives on the target and keeps the config it was created
465
+ // with, so re-read the trigger's options on every click; several triggers
466
+ // can then share one target with their own look.
467
+ if (target !== this) {
468
+ instance._config = instance._getConfig(config) as ComponentConfig
469
+ }
470
+
436
471
  instance.burst()
437
472
  })
438
473
 
package/js/src/countup.ts CHANGED
@@ -147,6 +147,7 @@ class CountUp extends BaseComponent {
147
147
  _elapsed = 0
148
148
  _running = false
149
149
  _paused = false
150
+ _started = false
150
151
 
151
152
  constructor(element: ElementSelector, config?: ComponentConfigInput) {
152
153
  super(element, config)
@@ -175,13 +176,7 @@ class CountUp extends BaseComponent {
175
176
  return
176
177
  }
177
178
 
178
- this._observer = new IntersectionObserver((entries) => {
179
- if (entries.some((entry) => entry.isIntersecting)) {
180
- this._observer?.disconnect()
181
- this._observer = null
182
- this.start()
183
- }
184
- })
179
+ this._observer = new IntersectionObserver((entries) => this._observerCallback(entries))
185
180
  this._observer.observe(this._element)
186
181
  }
187
182
 
@@ -205,6 +200,8 @@ class CountUp extends BaseComponent {
205
200
 
206
201
  reset(): void {
207
202
  this._stop()
203
+ // Lets the observer start the animation again when the element comes into view.
204
+ this._started = false
208
205
  this._value = this._config.startVal
209
206
  this._print(this._value)
210
207
  }
@@ -244,7 +241,11 @@ class CountUp extends BaseComponent {
244
241
 
245
242
  if (raw) {
246
243
  try {
247
- dataOptions = JSON.parse(raw)
244
+ const parsed: unknown = JSON.parse(raw)
245
+ // Only an object carries options; `null`, a number or an array is ignored.
246
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
247
+ dataOptions = parsed as Record<string, unknown>
248
+ }
248
249
  } catch {
249
250
  // ignore invalid JSON
250
251
  }
@@ -253,8 +254,21 @@ class CountUp extends BaseComponent {
253
254
  return super._mergeConfigObj({ ...coerceOptions(dataOptions), ...config }, element)
254
255
  }
255
256
 
257
+ _observerCallback(entries: IntersectionObserverEntry[]): void {
258
+ if (entries.some((entry) => entry.isIntersecting)) {
259
+ this._observer?.disconnect()
260
+ this._observer = null
261
+
262
+ // update() may have already animated it while it was off screen.
263
+ if (!this._started) {
264
+ this.start()
265
+ }
266
+ }
267
+ }
268
+
256
269
  _animate(from: number, to: number): void {
257
270
  this._stop()
271
+ this._started = true
258
272
 
259
273
  // Users who asked for less motion get the final number at once.
260
274
  if (from === to || this._config.duration <= 0 || reducedMotion()) {
@@ -317,14 +331,18 @@ class CountUp extends BaseComponent {
317
331
  }
318
332
 
319
333
  if (format === 'time') {
320
- const minutes = Math.round(Math.abs(value))
321
- return `${value < 0 ? '-' : ''}${prefix}${Math.floor(minutes / 60)}:${String(minutes % 60).padStart(2, '0')}${suffix}`
334
+ // Rounded away from zero on both sides, so -90.5 and 90.5 give the same
335
+ // minutes; a value that rounds to 0 stays unsigned (-0 < 0 is false).
336
+ const rounded = Math.sign(value) * Math.round(Math.abs(value))
337
+ const minutes = Math.abs(rounded)
338
+ return `${rounded < 0 ? '-' : ''}${prefix}${Math.floor(minutes / 60)}:${String(minutes % 60).padStart(2, '0')}${suffix}`
322
339
  }
323
340
 
324
- const [integer, fraction] = Math.abs(value).toFixed(decimalPlaces).split('.') as [string, string | undefined]
341
+ const fixed = value.toFixed(decimalPlaces)
342
+ const [integer, fraction] = fixed.replace('-', '').split('.') as [string, string | undefined]
325
343
  const grouped = useGrouping ? integer.replace(/\B(?=(\d{3})+(?!\d))/g, separator) : integer
326
344
 
327
- return `${value < 0 ? '-' : ''}${prefix}${grouped}${fraction ? decimal + fraction : ''}${suffix}`
345
+ return `${Number(fixed) < 0 ? '-' : ''}${prefix}${grouped}${fraction ? decimal + fraction : ''}${suffix}`
328
346
  }
329
347
  }
330
348