@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
package/js/src/popover.ts CHANGED
@@ -1,13 +1,19 @@
1
1
  import { Popover } from './bootstrap'
2
+ import Manipulator from './bootstrap/dom/manipulator'
3
+ import { onDOMContentLoaded } from './bootstrap/util/index'
2
4
 
3
5
  // js-docs-start popover-init
4
- const popoverTriggerList: HTMLElement[] = [].slice.call(document.querySelectorAll<HTMLElement>('[data-bs-toggle="popover"]'))
5
- popoverTriggerList.map(function (popoverTriggerEl: HTMLElement) {
6
- const options = {
7
- delay: { show: 50, hide: 50 },
8
- html: popoverTriggerEl.getAttribute('data-bs-html') === 'true',
9
- placement: popoverTriggerEl.getAttribute('data-bs-placement') ?? 'auto',
6
+ onDOMContentLoaded(() => {
7
+ for (const popoverTriggerEl of document.querySelectorAll<HTMLElement>('[data-bs-toggle="popover"], [data-tblr-toggle="popover"]')) {
8
+ try {
9
+ const options = {
10
+ delay: Manipulator.getDataAttribute(popoverTriggerEl, 'delay') ?? { show: 50, hide: 50 },
11
+ placement: Manipulator.getDataAttribute(popoverTriggerEl, 'placement') ?? 'auto',
12
+ }
13
+ Popover.getOrCreateInstance(popoverTriggerEl, options)
14
+ } catch (error) {
15
+ console.error(error)
16
+ }
10
17
  }
11
- return new Popover(popoverTriggerEl, options)
12
18
  })
13
19
  // js-docs-end popover-init
package/js/src/sidebar.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import { Dropdown } from './bootstrap'
2
+ import { onDOMContentLoaded } from './bootstrap/util/index'
2
3
 
3
4
  /*
4
5
  Folded sidebar toggle
5
6
  */
6
7
  // js-docs-start sidebar-folded-toggle
7
8
  const syncSidebarToggles = (folded: boolean): void => {
8
- for (const toggle of document.querySelectorAll('[data-bs-toggle="sidebar-folded"]')) {
9
+ for (const toggle of document.querySelectorAll('[data-bs-toggle="sidebar-folded"], [data-tblr-toggle="sidebar-folded"]')) {
9
10
  toggle.setAttribute('aria-pressed', String(folded))
10
11
  }
11
12
  }
@@ -28,8 +29,9 @@ document.addEventListener('click', (event: MouseEvent) => {
28
29
  return
29
30
  }
30
31
 
31
- const target = event.target as Element
32
- if (target.closest('.navbar-vertical .dropdown-menu')) {
32
+ // A synthetic click dispatched on the document has no Element target.
33
+ const target = event.target
34
+ if (!(target instanceof Element) || target.closest('.navbar-vertical .dropdown-menu')) {
33
35
  return
34
36
  }
35
37
 
@@ -60,7 +62,8 @@ document.addEventListener(
60
62
  // The folded state may come pre-render from localStorage (tabler-theme.js), so
61
63
  // align the toggle buttons with it once the DOM is available. Server-rendered
62
64
  // open submenus are also closed, so they don't hang as open flyouts.
63
- document.addEventListener('DOMContentLoaded', () => {
65
+ // onDOMContentLoaded runs at once when the script loads after the event.
66
+ onDOMContentLoaded(() => {
64
67
  const folded = (document.documentElement.getAttribute('data-bs-sidebar') ?? '').startsWith('folded')
65
68
  syncSidebarToggles(folded)
66
69
 
@@ -73,7 +76,7 @@ document.addEventListener('DOMContentLoaded', () => {
73
76
  })
74
77
 
75
78
  document.addEventListener('click', (event: MouseEvent) => {
76
- const trigger = (event.target as Element).closest('[data-bs-toggle="sidebar-folded"]')
79
+ const trigger = event.target instanceof Element ? event.target.closest('[data-bs-toggle="sidebar-folded"], [data-tblr-toggle="sidebar-folded"]') : null
77
80
  if (!trigger) {
78
81
  return
79
82
  }
@@ -89,7 +92,12 @@ document.addEventListener('click', (event: MouseEvent) => {
89
92
  html.removeAttribute('data-bs-sidebar')
90
93
  }
91
94
 
92
- localStorage.setItem('tabler-sidebar', willFold ? 'folded-hover' : 'default')
95
+ // Storage may be blocked by the browser; the fold still applies to this page.
96
+ try {
97
+ localStorage.setItem('tabler-sidebar', willFold ? 'folded-hover' : 'default')
98
+ } catch {
99
+ // ignore
100
+ }
93
101
  syncSidebarToggles(willFold)
94
102
 
95
103
  // Close open sidebar dropdowns so an inline submenu doesn't turn into a
@@ -101,7 +101,11 @@ class Sortable extends BaseComponent {
101
101
 
102
102
  if (raw) {
103
103
  try {
104
- dataOptions = JSON.parse(raw)
104
+ const parsed: unknown = JSON.parse(raw)
105
+ // Only an object carries options; `null`, a number or an array is ignored.
106
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
107
+ dataOptions = parsed as Record<string, unknown>
108
+ }
105
109
  } catch {
106
110
  // ignore invalid JSON
107
111
  }
@@ -262,7 +262,9 @@ class Sparkline extends BaseComponent {
262
262
  this._element.querySelector(`.${CLASS_NAME_LABEL}`)?.remove()
263
263
 
264
264
  if (this._config.values.length === 0) {
265
- this._element.innerHTML = ''
265
+ cancelAnimationFrame(this._frame)
266
+ previous?.remove()
267
+ EventHandler.trigger(this._element, EVENT_RENDERED)
266
268
  return
267
269
  }
268
270
 
@@ -308,7 +310,8 @@ class Sparkline extends BaseComponent {
308
310
 
309
311
  dispose(): void {
310
312
  cancelAnimationFrame(this._frame)
311
- this._element.innerHTML = ''
313
+ this._element.querySelector('svg')?.remove()
314
+ this._element.querySelector(`.${CLASS_NAME_LABEL}`)?.remove()
312
315
  super.dispose()
313
316
  }
314
317
 
@@ -365,6 +368,12 @@ class Sparkline extends BaseComponent {
365
368
  const pairs = Array.from(to.children).map((target, i) => {
366
369
  const source = from.children[i]
367
370
  const tweens: [string, string, string][] = []
371
+ for (const { name } of Array.from(source.attributes)) {
372
+ if (!target.hasAttribute(name)) {
373
+ source.removeAttribute(name)
374
+ }
375
+ }
376
+
368
377
  for (const { name, value } of Array.from(target.attributes)) {
369
378
  const start = source.getAttribute(name)
370
379
  if (start === null || start === value) {
@@ -431,8 +440,8 @@ class Sparkline extends BaseComponent {
431
440
 
432
441
  const { min, span } = rangeOf(cfg.values, cfg.min, cfg.max, ...(cfg.threshold === null ? [] : [cfg.threshold]))
433
442
  const yOf = (value: number): number => cfg.pad + (1 - (value - min) / span) * (cfg.height - cfg.pad * 2)
434
- const ys = cfg.values.map(yOf)
435
- const step = ys.length > 1 ? cfg.width / (ys.length - 1) : cfg.width
443
+ const ys = cfg.values.length === 1 ? [yOf(cfg.values[0]), yOf(cfg.values[0])] : cfg.values.map(yOf)
444
+ const step = cfg.width / (ys.length - 1)
436
445
 
437
446
  if (cfg.threshold !== null) {
438
447
  this._hairline(svg, yOf(cfg.threshold), 'threshold', true)
@@ -463,7 +472,7 @@ class Sparkline extends BaseComponent {
463
472
  if (spotIndex >= 0) {
464
473
  const spot = svgEl('circle')
465
474
  setAttr(spot, {
466
- cx: spotIndex * step,
475
+ cx: cfg.values.length === 1 ? cfg.width / 2 : spotIndex * step,
467
476
  cy: ys[spotIndex],
468
477
  r: getCssNumber(this._element, 'spot-size', 2),
469
478
  fill: cssVar('spot'),
@@ -560,7 +569,7 @@ class Sparkline extends BaseComponent {
560
569
  const cfg = this._config
561
570
  const svg = this._createSvg()
562
571
 
563
- const strokeWidth = getCssNumber(this._element, 'stroke-width', 3)
572
+ const strokeWidth = getCssNumber(this._element, 'stroke-width', 2)
564
573
  const radius = Math.max(0, Math.min(cfg.width, cfg.height) / 2 - strokeWidth / 2)
565
574
  const cx = cfg.width / 2
566
575
  const cy = cfg.height / 2
@@ -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 StrengthLevel = 'weak' | 'fair' | 'good' | 'strong'
@@ -164,17 +165,29 @@ class Strength extends BaseComponent {
164
165
  }
165
166
 
166
167
  // Private
168
+ _configAfterMerge(config: ComponentConfig): ComponentConfig {
169
+ // A partial `messages` or `weights` overrides only the keys it names.
170
+ return {
171
+ ...config,
172
+ messages: { ...Default.messages, ...config.messages },
173
+ weights: { ...Default.weights, ...config.weights },
174
+ }
175
+ }
176
+
167
177
  _getInput(): HTMLInputElement | null {
168
178
  const { input } = this._config
169
179
  if (input) {
170
- return SelectorEngine.findOne(input) as HTMLInputElement | null
180
+ return getElement(input) as HTMLInputElement | null
171
181
  }
172
182
 
173
- // Without an explicit selector only the meter's own parent is searched, so
174
- // a form with several password fields cannot bind the wrong one.
183
+ // Without an explicit selector only the meter's own parent is searched.
184
+ // The nearest field before the meter wins, so a confirm-password field
185
+ // placed after it is never mistaken for the one being rated; a meter
186
+ // placed above its field falls back to the first field after it.
175
187
  const parent = this._element.parentElement
176
188
  const fields = parent ? (SelectorEngine.find(SELECTOR_PASSWORD, parent) as HTMLInputElement[]) : []
177
- return fields[fields.length - 1] ?? null
189
+ const precedingFields = fields.filter((field) => Boolean(field.compareDocumentPosition(this._element) & Node.DOCUMENT_POSITION_FOLLOWING))
190
+ return precedingFields[precedingFields.length - 1] ?? fields[0] ?? null
178
191
  }
179
192
 
180
193
  _getText(): HTMLElement | null {
@@ -138,8 +138,19 @@ class SwitchIcon extends BaseComponent {
138
138
  .finally(() => this.#setLoading(false))
139
139
  }
140
140
 
141
+ dispose(): void {
142
+ // A wait() still pending must not leave the spinner on the button.
143
+ this.#setLoading(false)
144
+ super.dispose()
145
+ }
146
+
141
147
  // Private
142
148
  #setActive(active: boolean): void {
149
+ // A pending wait() may settle after dispose().
150
+ if (!this._element) {
151
+ return
152
+ }
153
+
143
154
  this.#iconElement.classList.toggle(CLASS_NAME_ACTIVE, active)
144
155
  this._element.setAttribute('aria-pressed', String(active))
145
156
 
@@ -147,6 +158,10 @@ class SwitchIcon extends BaseComponent {
147
158
  }
148
159
 
149
160
  #setLoading(loading: boolean): void {
161
+ if (!this._element) {
162
+ return
163
+ }
164
+
150
165
  this.#iconElement.classList.toggle(CLASS_NAME_LOADING, loading)
151
166
  if (loading) {
152
167
  this._element.setAttribute('aria-busy', 'true')
package/js/src/tab.ts CHANGED
@@ -1,18 +1,28 @@
1
1
  import { Tab } from './bootstrap'
2
+ import { onDOMContentLoaded } from './bootstrap/util/index'
2
3
 
3
4
  // js-docs-start tab-init
4
5
  export const EnableActivationTabsFromLocationHash = (): void => {
5
6
  const locationHash: string = window.location.hash
6
7
 
7
- if (locationHash) {
8
- const tabsList: HTMLAnchorElement[] = [].slice.call(document.querySelectorAll<HTMLAnchorElement>('[data-bs-toggle="tab"]'))
9
- const matchedTabs = tabsList.filter((tab: HTMLAnchorElement) => tab.hash === locationHash)
8
+ if (!locationHash) {
9
+ return
10
+ }
11
+
12
+ for (const tab of document.querySelectorAll<HTMLAnchorElement>('[data-bs-toggle="tab"], [data-tblr-toggle="tab"]')) {
13
+ if (tab.hash !== locationHash) {
14
+ continue
15
+ }
10
16
 
11
- matchedTabs.map((tab: HTMLAnchorElement) => {
12
- new Tab(tab).show()
13
- })
17
+ // A tab link outside a .nav/.list-group has no parent to switch in;
18
+ // show() would throw and stop the rest of the bundle.
19
+ try {
20
+ ;(Tab.getOrCreateInstance(tab) as InstanceType<typeof Tab>).show()
21
+ } catch (error) {
22
+ console.error(error)
23
+ }
14
24
  }
15
25
  }
16
26
 
17
- EnableActivationTabsFromLocationHash()
27
+ onDOMContentLoaded(EnableActivationTabsFromLocationHash)
18
28
  // js-docs-end tab-init
package/js/src/toast.ts CHANGED
@@ -1,17 +1,25 @@
1
1
  import { Toast } from './bootstrap'
2
+ import SelectorEngine from './bootstrap/dom/selector-engine'
3
+ import { onDOMContentLoaded } from './bootstrap/util/index'
2
4
 
3
5
  // js-docs-start toast-init
4
- const toastsTriggerList: HTMLElement[] = [].slice.call(document.querySelectorAll<HTMLElement>('[data-bs-toggle="toast"]'))
5
- toastsTriggerList.map(function (toastTriggerEl: HTMLElement) {
6
- const target = toastTriggerEl.getAttribute('data-bs-target')
7
- if (target === null) {
8
- return
9
- }
10
-
11
- const toastEl = new Toast(target)
6
+ onDOMContentLoaded(() => {
7
+ for (const toastTriggerEl of document.querySelectorAll<HTMLElement>('[data-bs-toggle="toast"], [data-tblr-toggle="toast"]')) {
8
+ try {
9
+ // A trigger whose target is missing is left alone instead of getting a
10
+ // click handler that throws.
11
+ const toastEl = SelectorEngine.getElementFromSelector(toastTriggerEl)
12
+ if (!toastEl) {
13
+ continue
14
+ }
12
15
 
13
- toastTriggerEl.addEventListener('click', () => {
14
- toastEl.show()
15
- })
16
+ const toast = Toast.getOrCreateInstance(toastEl) as InstanceType<typeof Toast>
17
+ toastTriggerEl.addEventListener('click', () => {
18
+ toast.show()
19
+ })
20
+ } catch (error) {
21
+ console.error(error)
22
+ }
23
+ }
16
24
  })
17
25
  // js-docs-end toast-init
package/js/src/tooltip.ts CHANGED
@@ -1,13 +1,22 @@
1
1
  import { Tooltip } from './bootstrap'
2
+ import Manipulator from './bootstrap/dom/manipulator'
3
+ import { onDOMContentLoaded } from './bootstrap/util/index'
2
4
 
3
5
  // js-docs-start tooltip-init
4
- const tooltipTriggerList: HTMLElement[] = [].slice.call(document.querySelectorAll<HTMLElement>('[data-bs-toggle="tooltip"]'))
5
- tooltipTriggerList.map(function (tooltipTriggerEl: HTMLElement) {
6
- const options = {
7
- delay: { show: 50, hide: 50 },
8
- html: tooltipTriggerEl.getAttribute('data-bs-html') === 'true',
9
- placement: tooltipTriggerEl.getAttribute('data-bs-placement') ?? 'auto',
6
+ // One element with a broken config must not stop the rest of the bundle, so
7
+ // every instance is created on its own. `data-bs-delay` on the element wins
8
+ // over the shorter default delay.
9
+ onDOMContentLoaded(() => {
10
+ for (const tooltipTriggerEl of document.querySelectorAll<HTMLElement>('[data-bs-toggle="tooltip"], [data-tblr-toggle="tooltip"]')) {
11
+ try {
12
+ const options = {
13
+ delay: Manipulator.getDataAttribute(tooltipTriggerEl, 'delay') ?? { show: 50, hide: 50 },
14
+ placement: Manipulator.getDataAttribute(tooltipTriggerEl, 'placement') ?? 'auto',
15
+ }
16
+ Tooltip.getOrCreateInstance(tooltipTriggerEl, options)
17
+ } catch (error) {
18
+ console.error(error)
19
+ }
10
20
  }
11
- return new Tooltip(tooltipTriggerEl, options)
12
21
  })
13
22
  // js-docs-end tooltip-init
@@ -9,16 +9,40 @@ const params = new URLSearchParams(window.location.search)
9
9
 
10
10
  const prefersDark = window.matchMedia('(prefers-color-scheme: dark)')
11
11
 
12
+ // A setting value is a short token: it ends up as a `data-bs-*` attribute on
13
+ // <html> and in localStorage, so anything else from the URL is ignored.
14
+ const VALUE_PATTERN = /^[a-z0-9-]{1,32}$/
15
+
16
+ // Accessing localStorage throws when site data is blocked (Chrome "block all
17
+ // cookies", Safari with storage disabled, partitioned third-party frames), so
18
+ // every access is guarded and the switcher falls back to the page defaults.
19
+ const storage = {
20
+ get(key: string): string | null {
21
+ try {
22
+ return localStorage.getItem(key)
23
+ } catch {
24
+ return null
25
+ }
26
+ },
27
+ set(key: string, value: string): void {
28
+ try {
29
+ localStorage.setItem(key, value)
30
+ } catch {
31
+ // ignore: the choice applies to this page only
32
+ }
33
+ },
34
+ }
35
+
12
36
  for (const key in themeDefaults) {
13
37
  const param = params.get(key)
14
38
  let selectedValue: string
15
39
 
16
- if (!!param) {
17
- localStorage.setItem('tabler-' + key, param)
40
+ if (param && VALUE_PATTERN.test(param)) {
41
+ storage.set('tabler-' + key, param)
18
42
  selectedValue = param
19
43
  } else {
20
44
  // A stored choice wins; otherwise a server-rendered attribute is the starting value.
21
- const storedTheme = localStorage.getItem('tabler-' + key)
45
+ const storedTheme = storage.get('tabler-' + key)
22
46
  const serverValue = document.documentElement.getAttribute('data-bs-' + key)
23
47
  selectedValue = storedTheme ?? serverValue ?? themeDefaults[key as ThemeKey]
24
48
  }
@@ -36,7 +60,7 @@ for (const key in themeDefaults) {
36
60
 
37
61
  prefersDark.addEventListener('change', (event) => {
38
62
  // No stored choice means the default, which is auto.
39
- if ((localStorage.getItem('tabler-theme') ?? 'auto') === 'auto') {
63
+ if ((storage.get('tabler-theme') ?? 'auto') === 'auto') {
40
64
  if (event.matches) {
41
65
  document.documentElement.setAttribute('data-bs-theme', 'dark')
42
66
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tabler/core",
3
- "version": "1.6.0",
3
+ "version": "1.6.1",
4
4
  "description": "Premium and Open Source dashboard template with responsive and high quality UI.",
5
5
  "homepage": "https://tabler.io",
6
6
  "repository": {
@@ -250,7 +250,7 @@
250
250
  "tom-select": "^2.6.2",
251
251
  "typed.js": "^3.0.0",
252
252
  "typescript": "^7.0.2",
253
- "vanilla-calendar-pro": "^3.3.2",
253
+ "vanilla-calendar-pro": "^3.4.0",
254
254
  "vitest": "4.1.11"
255
255
  },
256
256
  "scripts": {
@@ -660,8 +660,7 @@ Navbar vertical
660
660
  // The sidebar is rendered for the expanded state, so the active submenu can
661
661
  // arrive open (.show). Folded, it would paint as a flyout until sidebar.ts
662
662
  // closes it on DOMContentLoaded — a flicker on every reload. Menus opened by
663
- // the Dropdown plugin carry a popper data attribute (data-tblr-popper from
664
- // tabler.js, data-bs-popper from Bootstrap's own js), so only the
663
+ // the Dropdown plugin carry a data-bs-popper attribute, so only the
665
664
  // server-rendered ones hide.
666
665
  .navbar-vertical .dropdown-menu.show:not([data-bs-popper]):not([data-tblr-popper]) {
667
666
  display: none;
@@ -302,9 +302,21 @@ Stacked card
302
302
  }
303
303
 
304
304
  // Card progress
305
- // `.card >` outranks ui/_progress.scss's later `.progress { position: relative; }`.
306
- .card > .card-progress {
305
+ .card-progress {
307
306
  --card-progress-height: #{$card-progress-height};
307
+ height: var(--card-progress-height);
308
+
309
+ &:last-child {
310
+ @include border-radius(0 0 2px 2px);
311
+ }
312
+
313
+ &:first-child {
314
+ @include border-radius(2px 2px 0 0);
315
+ }
316
+ }
317
+
318
+ // `.card >` outranks the fallback above and ui/_progress.scss's later `.progress { position: relative; }`.
319
+ .card > .card-progress {
308
320
  --card-progress-offset: calc(-1 * var(--card-border-width));
309
321
  position: absolute;
310
322
  inset: var(--card-progress-offset);