Add ESLint with typescript-eslint and fix its findings (#3256)

This commit is contained in:
Paweł Kuna authored and Bartek committed 2026-10-02 11:32:45 +02:00
1 parent 0e90f8fd16
commit 6394af0a59
40 files changed
+935 -339

No files matched your search

+1 -1
View File
@@ -44,7 +44,7 @@ class Button extends BaseComponent {
EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, (event: Event) => {
event.preventDefault()
const target = (event.target as HTMLElement)?.closest(SELECTOR_DATA_TOGGLE) as HTMLElement | null
const target = (event.target as HTMLElement)?.closest<HTMLElement>(SELECTOR_DATA_TOGGLE)
if (!target) {
return
}
+1 -1
View File
@@ -297,7 +297,7 @@ class Carousel extends BaseComponent {
const activeElement = this._getActive()
const isNext = order === ORDER_NEXT
const nextElement = element || (getNextActiveElement(this._getItems(), activeElement!, isNext, this._config.wrap as boolean) as HTMLElement)
const nextElement = element || getNextActiveElement<HTMLElement>(this._getItems(), activeElement!, isNext, this._config.wrap)
if (nextElement === activeElement) {
return
-1
View File
@@ -16,7 +16,6 @@ const Data = {
const instanceMap = elementMap.get(element)!
if (!instanceMap.has(key) && instanceMap.size !== 0) {
// eslint-disable-next-line no-console
console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`)
return
}
+2 -2
View File
@@ -106,7 +106,7 @@ function bootstrapHandler(element: EventTarget, fn: EventCallback): BootstrapHan
}
return fn.apply(element as HTMLElement, [event])
} as BootstrapHandler
}
}
function bootstrapDelegationHandler(element: EventTarget, selector: string, fn: EventCallback): BootstrapHandler {
@@ -128,7 +128,7 @@ function bootstrapDelegationHandler(element: EventTarget, selector: string, fn:
return fn.apply(target as HTMLElement, [event])
}
}
} as BootstrapHandler
}
}
function findHandler(events: Record<string | number, BootstrapHandler>, callable: EventCallback, delegationSelector: string | null = null): BootstrapHandler | undefined {
+1 -1
View File
@@ -29,7 +29,7 @@ function normalizeData(value: string): DataValue {
}
try {
return JSON.parse(decodeURIComponent(value))
return JSON.parse(decodeURIComponent(value)) as DataValue
} catch {
return value
}
+11 -7
View File
@@ -123,7 +123,11 @@ class Dropdown extends BaseComponent {
}
toggle(): void {
this._isShown() ? this.hide() : this.show()
if (this._isShown()) {
this.hide()
} else {
this.show()
}
}
show(): void {
@@ -180,7 +184,7 @@ class Dropdown extends BaseComponent {
update(): void {
this._inNavbar = this._detectNavbar()
if (this._popper) {
this._popper.update()
void this._popper.update()
}
}
@@ -210,7 +214,7 @@ class Dropdown extends BaseComponent {
_getConfig(config?: ComponentConfigInput): ComponentConfig {
const merged = super._getConfig(config) as ComponentConfig
if (typeof merged.reference === 'object' && !isElement(merged.reference) && typeof (merged.reference as Popper.VirtualElement).getBoundingClientRect !== 'function') {
if (typeof merged.reference === 'object' && !isElement(merged.reference) && typeof merged.reference.getBoundingClientRect !== 'function') {
throw new TypeError(`${NAME.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`)
}
@@ -227,9 +231,9 @@ class Dropdown extends BaseComponent {
if (this._config.reference === 'parent') {
referenceElement = this._parent
} else if (isElement(this._config.reference)) {
referenceElement = getElement(this._config.reference as HTMLElement | string)!
referenceElement = getElement(this._config.reference)!
} else if (typeof this._config.reference === 'object') {
referenceElement = this._config.reference as Popper.VirtualElement
referenceElement = this._config.reference
}
const popperConfig = this._getPopperConfig()
@@ -383,7 +387,7 @@ class Dropdown extends BaseComponent {
const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE)[0] || SelectorEngine.next(this, SELECTOR_DATA_TOGGLE)[0] || SelectorEngine.findOne(SELECTOR_DATA_TOGGLE, (event as DelegatedEvent<KeyboardEvent>).delegateTarget.parentNode as Element)
const instance = Dropdown.getOrCreateInstance(getToggleButton!) as Dropdown
const instance = Dropdown.getOrCreateInstance(getToggleButton) as Dropdown
if (isUpOrDownEvent) {
event.stopPropagation()
@@ -395,7 +399,7 @@ class Dropdown extends BaseComponent {
if (instance._isShown()) {
event.stopPropagation()
instance.hide()
getToggleButton!.focus()
getToggleButton.focus()
}
}
+4 -4
View File
@@ -137,9 +137,9 @@ class ScrollSpy extends BaseComponent {
return
}
EventHandler.off(this._config.target as HTMLElement, EVENT_CLICK)
EventHandler.off(this._config.target, EVENT_CLICK)
EventHandler.on(this._config.target as HTMLElement, EVENT_CLICK, SELECTOR_TARGET_LINKS, (event: Event) => {
EventHandler.on(this._config.target, EVENT_CLICK, SELECTOR_TARGET_LINKS, (event: Event) => {
const observableSection = this._observableSections.get((event.target as HTMLAnchorElement).hash)
if (observableSection) {
event.preventDefault()
@@ -158,8 +158,8 @@ class ScrollSpy extends BaseComponent {
_getNewObserver(): IntersectionObserver {
const options: IntersectionObserverInit = {
root: this._rootElement,
threshold: this._config.threshold as number[],
rootMargin: this._config.rootMargin as string,
threshold: this._config.threshold,
rootMargin: this._config.rootMargin,
}
return new IntersectionObserver((entries) => this._observerCallback(entries), options)
+1 -1
View File
@@ -153,7 +153,7 @@ class Tab extends BaseComponent {
nextActiveElement = children[event.key === HOME_KEY ? 0 : children.length - 1]
} else {
const isNext = [ARROW_RIGHT_KEY, ARROW_DOWN_KEY].includes(event.key)
nextActiveElement = getNextActiveElement(children, event.target as HTMLElement, isNext, true) as HTMLElement
nextActiveElement = getNextActiveElement<HTMLElement>(children, event.target as HTMLElement, isNext, true)
}
if (nextActiveElement) {
+4 -4
View File
@@ -138,7 +138,7 @@ class Tooltip extends BaseComponent {
declare _element: HTMLElement
declare _config: ComponentConfig
_isEnabled: boolean
_timeout: ReturnType<typeof setTimeout> | number
_timeout: ReturnType<typeof setTimeout>
_isHovered: boolean | null
_activeTrigger: Record<string, boolean>
_popper: Popper.Instance | null
@@ -259,7 +259,7 @@ class Tooltip extends BaseComponent {
this._popper = this._createPopper(tip)
tip!.classList.add(CLASS_NAME_SHOW)
tip.classList.add(CLASS_NAME_SHOW)
if ('ontouchstart' in document.documentElement) {
for (const element of Array.from(document.body.children)) {
@@ -322,7 +322,7 @@ class Tooltip extends BaseComponent {
update(): void {
if (this._popper) {
this._popper.update()
void this._popper.update()
}
}
@@ -511,7 +511,7 @@ class Tooltip extends BaseComponent {
return
}
if (!this._element.getAttribute('aria-label') && !this._element.textContent!.trim()) {
if (!this._element.getAttribute('aria-label') && !this._element.textContent.trim()) {
this._element.setAttribute('aria-label', title)
}
+2 -2
View File
@@ -34,13 +34,13 @@ class Config {
}
_mergeConfigObj(config?: ComponentConfig, element?: HTMLElement): ComponentConfig {
const jsonConfig = isElement(element) ? Manipulator.getDataAttribute(element!, 'config') : {}
const jsonConfig = isElement(element) ? Manipulator.getDataAttribute(element, 'config') : {}
const ctor = this.constructor as unknown as ConfigStatic
return {
...ctor.Default,
...(typeof jsonConfig === 'object' ? jsonConfig : {}),
...(isElement(element) ? Manipulator.getDataAttributes(element!) : {}),
...(isElement(element) ? Manipulator.getDataAttributes(element) : {}),
...(typeof config === 'object' ? config : {}),
}
}
+1 -1
View File
@@ -94,7 +94,7 @@ class FocusTrap extends Config {
const elements = SelectorEngine.focusableChildren(trapElement)
if (elements.length === 0) {
trapElement!.focus()
trapElement.focus()
} else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) {
elements[elements.length - 1].focus()
} else {
+2 -2
View File
@@ -13,7 +13,7 @@ const TRANSITION_END = 'transitionend'
const parseSelector = (selector: string): string => {
if (selector && typeof CSS !== 'undefined' && typeof CSS.escape === 'function') {
selector = selector.replace(/#([^\s"#']+)/g, (match, id) => `#${CSS.escape(id)}`)
selector = selector.replace(/#([^\s"#']+)/g, (_match: string, id: string) => `#${CSS.escape(id)}`)
}
return selector
@@ -166,7 +166,7 @@ const noop = (): void => {}
* @see https://www.harrytheo.com/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation
*/
const reflow = (element: HTMLElement): void => {
element.offsetHeight // eslint-disable-line no-unused-expressions
element.offsetHeight // eslint-disable-line @typescript-eslint/no-unused-expressions
}
const isRTL = (): boolean => document.documentElement.dir === 'rtl'
+1 -1
View File
@@ -62,7 +62,7 @@ const allowedAttribute = (attribute: Attr, allowedAttributeList: (string | RegEx
return true
}
return allowedAttributeList.filter((attributeRegex) => attributeRegex instanceof RegExp).some((regex) => (regex as RegExp).test(attributeName))
return allowedAttributeList.filter((attributeRegex) => attributeRegex instanceof RegExp).some((regex) => regex.test(attributeName))
}
export function sanitizeHtml(unsafeHtml: string, allowList: AllowList, sanitizeFunction?: SanitizeFn): string {
+2
View File
@@ -81,6 +81,8 @@ class ScrollBarHelper {
}
Manipulator.removeDataAttribute(element, styleProperty)
// The attribute holds the CSS value `_saveInitialAttribute()` stored
// eslint-disable-next-line @typescript-eslint/no-base-to-string
element.style.setProperty(styleProperty, String(value))
}
+3 -3
View File
@@ -273,7 +273,7 @@ const stage = {
}
for (let i = this.particles.length - 1; i >= 0; i--) {
const p = this.particles[i]!
const p = this.particles[i]
const fadeFrom = height * (1 - p.emitter.config.fade)
p.sway += 0.05 * step
@@ -317,7 +317,7 @@ const stage = {
// is still in the air; `end` fires on its element and it leaves the stage.
_settle(now: number): void {
for (let i = this.emitters.length - 1; i >= 0; i--) {
const emitter = this.emitters[i]!
const emitter = this.emitters[i]
const pouring = !emitter.stopped && now - emitter.startedAt < emitter.config.duration
const airborne = this.particles.some((p) => p.emitter === emitter)
@@ -349,7 +349,7 @@ class Confetti extends BaseComponent {
_emitter: Emitter | null = null
constructor(element: ElementSelector, config?: ComponentConfigInput) {
super(element, config as BaseConfig | undefined)
super(element, config)
}
// Getters
+9 -6
View File
@@ -70,6 +70,7 @@ type WeekDayID = 0 | 1 | 2 | 3 | 4 | 5 | 6
*/
interface CalendarInstance {
// Loosely typed on purpose: the plugin's context is large and version-specific.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
context: Record<string, any>
init(): unknown
update(resetOptions?: object): unknown
@@ -258,6 +259,8 @@ class Datepicker extends BaseComponent {
this._isShowing = false
}
// `async` like `show()`, so a plugin error reaches the caller as a rejection
// eslint-disable-next-line @typescript-eslint/require-await
async hide(): Promise<void> {
if (this._config.inline) {
return // Inline calendars are always visible
@@ -512,7 +515,7 @@ class Datepicker extends BaseComponent {
return
}
this.hide()
void this.hide()
}
EventHandler.on(document, EVENT_FOCUSIN, this._onFocusIn)
@@ -665,7 +668,7 @@ class Datepicker extends BaseComponent {
if (shouldHide) {
window.clearTimeout(this._hideTimeout)
this._hideTimeout = window.setTimeout(() => this.hide(), HIDE_DELAY)
this._hideTimeout = window.setTimeout(() => void this.hide(), HIDE_DELAY)
}
}
@@ -699,7 +702,7 @@ class Datepicker extends BaseComponent {
}
if (dates.length === 1) {
return this._formatDate(dates[0]!)
return this._formatDate(dates[0])
}
// For date ranges, use en-dash; for multiple dates, use comma
@@ -732,7 +735,7 @@ class Datepicker extends BaseComponent {
this._calendar?.set({ selectedDates: this._selectedDates, ...this._monthOf(this._selectedDates[0]) })
}
_toIsoDate(value: DateAny | string): string {
_toIsoDate(value: Date | number | string): string {
if (typeof value === 'string' && DATE_PATTERN.test(value)) {
return value
}
@@ -772,7 +775,7 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (
}
event.preventDefault()
;(Datepicker.getOrCreateInstance(this) as Datepicker).toggle()
void (Datepicker.getOrCreateInstance(this) as Datepicker).toggle()
})
EventHandler.on(document, EVENT_FOCUSIN_DATA_API, SELECTOR_DATA_TOGGLE, function (this: HTMLElement) {
@@ -780,7 +783,7 @@ EventHandler.on(document, EVENT_FOCUSIN_DATA_API, SELECTOR_DATA_TOGGLE, function
return
}
;(Datepicker.getOrCreateInstance(this) as Datepicker).show()
void (Datepicker.getOrCreateInstance(this) as Datepicker).show()
})
// Render on load what cannot wait for a focus or a click: inline calendars,
+1 -1
View File
@@ -12,7 +12,7 @@ export const prefix: string = 'tblr-'
export const hexToRgba = (hex: string, opacity: number): string | null => {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
return result ? `rgba(${parseInt(result[1]!, 16)}, ${parseInt(result[2]!, 16)}, ${parseInt(result[3]!, 16)}, ${opacity})` : null
return result ? `rgba(${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(result[3], 16)}, ${opacity})` : null
}
export const getColor = (color: string, opacity: number = 1): string | null => {
+2 -2
View File
@@ -114,14 +114,14 @@ const clampSpan = (min: number, max: number): number => {
// Accepts "3,4,2" as well as a JSON array literal "[3,4,2]".
const parseNumberList = (input: unknown): number[] => {
const raw = String(input ?? '').trim()
const raw = typeof input === 'string' ? input.trim() : ''
if (!raw) {
return []
}
if (raw.startsWith('[')) {
try {
const arr = JSON.parse(raw)
const arr = JSON.parse(raw) as unknown
return Array.isArray(arr) ? arr.map(Number).filter(Number.isFinite) : []
} catch {
return []
+2 -1
View File
@@ -296,7 +296,7 @@ describe('Tab', () => {
fixtureEl.innerHTML = ['<ul class="nav">', ' <li class="nav-link" data-bs-toggle="tab"></li>', '</ul>'].join('')
const tabEl = fixtureEl.querySelector('.nav-link')!
const tab = new Tab(tabEl)
new Tab(tabEl)
const spyStop = vi.spyOn(Event.prototype, 'stopPropagation')
const spyPrevent = vi.spyOn(Event.prototype, 'preventDefault')
@@ -326,6 +326,7 @@ describe('Tab', () => {
tabEl1.dispatchEvent(keydown)
expect(spyFocus2).toHaveBeenCalled()
expect(spyFocus3).not.toHaveBeenCalled()
})
it('should handle left/up arrow', () => {
+1 -1
View File
@@ -1,4 +1,4 @@
import { describe, it, expect, beforeAll, beforeEach, afterEach, vi } from 'vitest'
import { describe, it, expect, beforeAll, afterEach, vi } from 'vitest'
import { execute, executeAfterTransition, findShadowRoot, getElement, getNextActiveElement, getTransitionDurationFromElement, getUID, isDisabled, isElement, isRTL, isVisible, noop, parseSelector, reflow, toType, triggerTransitionEnd } from '../../../src/bootstrap/util/index'
import { clearFixture, getFixture } from '../../helpers/fixture'
+1 -1
View File
@@ -286,7 +286,7 @@
"star-rating.js": "^4.3.1",
"tom-select": "^2.6.2",
"typed.js": "^3.0.0",
"typescript": "^7.0.2",
"typescript": "^6.0.3",
"vanilla-calendar-pro": "^3.4.0",
"vitest": "4.1.11"
}