mirror of
https://github.com/tabler/tabler.git
synced 2026-10-11 20:56:57 +04:00
Add ESLint with typescript-eslint and fix its findings (#3256)
This commit is contained in:
1 parent
0e90f8fd16
commit
6394af0a59
40 files changed
+935
-339
No files matched your search
@@ -27,7 +27,7 @@ import { pkgName, releasedPackage } from './released-package'
|
||||
|
||||
const fixtureDir = '.build/compat-fixture'
|
||||
const packageArg = process.argv.indexOf('--package')
|
||||
const coreDir = packageArg > -1 ? process.argv[packageArg + 1]! : 'core'
|
||||
const coreDir = packageArg > -1 ? process.argv[packageArg + 1] : 'core'
|
||||
|
||||
const MIME: Record<string, string> = { '.html': 'text/html', '.css': 'text/css', '.js': 'text/javascript', '.svg': 'image/svg+xml', '.map': 'application/json' }
|
||||
|
||||
@@ -61,7 +61,7 @@ function sassResults(pkg: string): Result {
|
||||
|
||||
function serve(pkg: string): Promise<Server> {
|
||||
const server = createServer((request, response) => {
|
||||
const path = decodeURIComponent((request.url ?? '/').split('?')[0]!)
|
||||
const path = decodeURIComponent((request.url ?? '/').split('?')[0])
|
||||
const file = path === '/' ? resolve(fixtureDir, 'page.html') : resolve(pkg, `.${path}`)
|
||||
if (!existsSync(file) || !statSync(file).isFile()) {
|
||||
response.writeHead(404).end()
|
||||
@@ -86,7 +86,7 @@ async function pageResults(pkg: string, page: Page): Promise<Result> {
|
||||
await page.goto(`http://127.0.0.1:${port}/`, { waitUntil: 'networkidle' })
|
||||
await page.addStyleTag({ content: '*, *::before, *::after { transition: none !important; }' })
|
||||
|
||||
const style = (selector: string, property: string): Promise<string> => page.evaluate(([s, p]) => getComputedStyle(document.querySelector(s!)!).getPropertyValue(p!), [selector, property])
|
||||
const style = (selector: string, property: string): Promise<string> => page.evaluate(([s, p]) => getComputedStyle(document.querySelector(s)!).getPropertyValue(p), [selector, property])
|
||||
|
||||
results['page: no script error and no 404'] = problems.length === 0 ? 'yes' : problems.join('; ')
|
||||
results['page: rgba(var(--tblr-primary-rgb), .5) paints'] = await style('#highlight', 'background-color')
|
||||
|
||||
@@ -48,7 +48,7 @@ function walk(root: string, dir = root): string[] {
|
||||
})
|
||||
}
|
||||
|
||||
const matches = (source: string, pattern: RegExp): Set<string> => new Set([...source.matchAll(pattern)].map((match) => match[1]!))
|
||||
const matches = (source: string, pattern: RegExp): Set<string> => new Set([...source.matchAll(pattern)].map((match) => match[1]))
|
||||
|
||||
// --- css -------------------------------------------------------------------
|
||||
|
||||
@@ -106,7 +106,7 @@ function splitParams(source: string | undefined): string[] {
|
||||
}
|
||||
params.push(current)
|
||||
// `$color: rgba($white, 0.15)` → `$color`; `$args...` → `$args...`
|
||||
return params.map((param) => param.trim().split(':')[0]!.trim()).filter(Boolean)
|
||||
return params.map((param) => param.trim().split(':')[0].trim()).filter(Boolean)
|
||||
}
|
||||
|
||||
function sassSurface(root: string): { variables: Set<string>; callables: Map<string, Callable> } {
|
||||
@@ -117,8 +117,8 @@ function sassSurface(root: string): { variables: Set<string>; callables: Map<str
|
||||
const source = readFileSync(join(root, 'scss', file), 'utf8').replace(/\/\/.*$/gm, '')
|
||||
for (const name of matches(source, sassVariable)) variables.add(name)
|
||||
for (const [, kind, name, params] of source.matchAll(sassCallable)) {
|
||||
if (name!.startsWith('_') || name!.startsWith('-')) continue // private by convention
|
||||
callables.set(`${kind} ${name}`, { kind: kind!, name: name!, params: splitParams(params) })
|
||||
if (name.startsWith('_') || name.startsWith('-')) continue // private by convention
|
||||
callables.set(`${kind} ${name}`, { kind: kind, name: name, params: splitParams(params) })
|
||||
}
|
||||
}
|
||||
return { variables, callables }
|
||||
@@ -160,7 +160,7 @@ function sassTypes(root: string): Map<string, string> {
|
||||
}
|
||||
}`
|
||||
const { css } = compileString(source, { loadPaths: [root, 'node_modules'], logger: { warn() {}, debug() {} } })
|
||||
return new Map([...css.matchAll(/--([\w-]+): (\w+);/g)].map((match) => [match[1]!, match[2]!]))
|
||||
return new Map([...css.matchAll(/--([\w-]+): (\w+);/g)].map((match) => [match[1], match[2]]))
|
||||
}
|
||||
|
||||
function sassTypeBreaks(released: string, current: string): string[] {
|
||||
@@ -175,7 +175,7 @@ function esmExports(file: string): Set<string> {
|
||||
const source = readFileSync(file, 'utf8')
|
||||
const names = new Set<string>()
|
||||
for (const [, list] of source.matchAll(/^export\s*\{([^}]*)\}/gm)) {
|
||||
for (const entry of list!.split(',')) {
|
||||
for (const entry of list.split(',')) {
|
||||
const name = entry
|
||||
.trim()
|
||||
.split(/\s+as\s+/)
|
||||
@@ -220,7 +220,7 @@ async function main() {
|
||||
const lines = readFileSync(baselineFile, 'utf8').split('\n')
|
||||
const againstIndex = lines.findIndex((line) => againstPattern.test(line))
|
||||
if (againstIndex === -1) throw new Error(`${baselineFile} has no \`# against: <version>\` line`)
|
||||
const against = againstPattern.exec(lines[againstIndex]!)![1]!
|
||||
const against = againstPattern.exec(lines[againstIndex])![1]
|
||||
|
||||
if (reset) {
|
||||
writeFileSync(baselineFile, [...lines.slice(0, againstIndex), `# against: ${version}`, ''].join('\n'))
|
||||
@@ -233,7 +233,7 @@ async function main() {
|
||||
const baseline = new Map<string, boolean>() // key → accepted
|
||||
for (const line of stale ? [] : lines) {
|
||||
const [entry, comment = ''] = line.split(' # ')
|
||||
const key = entry!.trim()
|
||||
const key = entry.trim()
|
||||
if (!key || key.startsWith('#')) continue
|
||||
baseline.set(key, comment.trim().startsWith('accepted:'))
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ const CONSOLE_IGNORE = [/GL Driver Message/, /Google Maps JavaScript API/, /allo
|
||||
type PageReport = { url: string; errors: string[]; warnings: string[] }
|
||||
|
||||
const resolveFile = (root: string, urlPath: string): string | null => {
|
||||
const clean = decodeURIComponent(urlPath.split('?')[0]!.split('#')[0]!)
|
||||
const clean = decodeURIComponent(urlPath.split('?')[0].split('#')[0])
|
||||
// `/x/` and `/x/index.html` also resolve to x.html: Astro's file format
|
||||
// collapses x/index.astro to x.html and the hosting serves it under both.
|
||||
const collapsed = clean.replace(/\/(index\.html)?$/, '')
|
||||
@@ -87,7 +87,7 @@ const serve = (root: string): Promise<{ server: Server; base: string }> =>
|
||||
|
||||
const sitemapPaths = async (base: string, dir?: string): Promise<string[]> => {
|
||||
const xml = dir ? readFileSync(join(dir, 'sitemap.xml'), 'utf8') : await (await fetch(`${base}/sitemap.xml`)).text()
|
||||
return [...xml.matchAll(/<loc>([^<]+)<\/loc>/g)].map((m) => m[1]!.replace(/^https?:\/\/[^/]+/, '')).filter((p) => p.startsWith('/'))
|
||||
return [...xml.matchAll(/<loc>([^<]+)<\/loc>/g)].map((m) => m[1].replace(/^https?:\/\/[^/]+/, '')).filter((p) => p.startsWith('/'))
|
||||
}
|
||||
|
||||
// Runs in the page as plain JavaScript (a string, so no bundler helpers such as
|
||||
@@ -162,14 +162,14 @@ const crawl = async (browser: Browser, base: string, paths: string[]) => {
|
||||
await page.goto(base + path, { waitUntil: 'load', timeout: 60_000 })
|
||||
await page.waitForTimeout(500)
|
||||
if (onSite()) {
|
||||
const r = (await page.evaluate(INSPECT)) as Inspection
|
||||
const r = await page.evaluate<Inspection>(INSPECT)
|
||||
for (const id of r.duplicateIds) if (!VENDOR_ID_PREFIXES.some((p) => id.startsWith(p))) report.errors.push(`duplicate id "${id}"`)
|
||||
for (const t of r.missingTargets) report.errors.push(`no element for ${t}`)
|
||||
for (const n of r.nested) report.errors.push(`nested interactive: ${n}`)
|
||||
for (const src of r.imgNoAlt) report.warnings.push(`img without alt: ${src}`)
|
||||
for (const html of r.unnamed) report.warnings.push(`no accessible name: ${html}`)
|
||||
for (const href of r.links) {
|
||||
const key = href.split(/[?#]/)[0]!
|
||||
const key = href.split(/[?#]/)[0]
|
||||
if (!links.has(key)) links.set(key, path)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ const htmlTargets: string[] = []
|
||||
const cssDirs: string[] = []
|
||||
let updateBaseline = false
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (args[i] === '--html') htmlTargets.push(args[++i]!)
|
||||
else if (args[i] === '--css') cssDirs.push(args[++i]!)
|
||||
if (args[i] === '--html') htmlTargets.push(args[++i])
|
||||
else if (args[i] === '--css') cssDirs.push(args[++i])
|
||||
else if (args[i] === '--update-baseline') updateBaseline = true
|
||||
}
|
||||
if (htmlTargets.length === 0 || cssDirs.length === 0) {
|
||||
@@ -41,14 +41,14 @@ const walk = (dir: string, ext: string): string[] =>
|
||||
const defined = new Set<string>()
|
||||
for (const dir of cssDirs) {
|
||||
for (const file of walk(resolve(dir), '.css')) {
|
||||
for (const m of readFileSync(file, 'utf8').matchAll(/\.(-?[_a-zA-Z][\w-]*)/g)) defined.add(m[1]!)
|
||||
for (const m of readFileSync(file, 'utf8').matchAll(/\.(-?[_a-zA-Z][\w-]*)/g)) defined.add(m[1])
|
||||
}
|
||||
}
|
||||
|
||||
const pagesOf = async (target: string): Promise<{ path: string; html: string }[]> => {
|
||||
if (/^https?:/.test(target)) {
|
||||
const xml = await (await fetch(`${target}/sitemap.xml`)).text()
|
||||
const paths = [...xml.matchAll(/<loc>([^<]+)<\/loc>/g)].map((m) => m[1]!.replace(/^https?:\/\/[^/]+/, ''))
|
||||
const paths = [...xml.matchAll(/<loc>([^<]+)<\/loc>/g)].map((m) => m[1].replace(/^https?:\/\/[^/]+/, ''))
|
||||
const pages = []
|
||||
for (const path of paths) pages.push({ path, html: await (await fetch(target + path)).text() })
|
||||
return pages
|
||||
@@ -56,7 +56,7 @@ const pagesOf = async (target: string): Promise<{ path: string; html: string }[]
|
||||
const root = resolve(target)
|
||||
const xml = readFileSync(join(root, 'sitemap.xml'), 'utf8')
|
||||
return [...xml.matchAll(/<loc>([^<]+)<\/loc>/g)].map((m) => {
|
||||
const path = m[1]!.replace(/^https?:\/\/[^/]+/, '')
|
||||
const path = m[1].replace(/^https?:\/\/[^/]+/, '')
|
||||
const file = [join(root, path), join(root, `${path}.html`), join(root, path, 'index.html')].find((f) => existsSync(f) && statSync(f).isFile())
|
||||
return { path, html: file ? readFileSync(file, 'utf8') : '' }
|
||||
})
|
||||
@@ -70,9 +70,9 @@ const main = async () => {
|
||||
pageCount++
|
||||
// Page-level <style> blocks define classes too (demo pages style their own markup).
|
||||
const inline = new Set<string>()
|
||||
for (const block of html.matchAll(/<style[^>]*>([\s\S]*?)<\/style>/g)) for (const m of block[1]!.matchAll(/\.(-?[_a-zA-Z][\w-]*)/g)) inline.add(m[1]!)
|
||||
for (const block of html.matchAll(/<style[^>]*>([\s\S]*?)<\/style>/g)) for (const m of block[1].matchAll(/\.(-?[_a-zA-Z][\w-]*)/g)) inline.add(m[1])
|
||||
for (const m of html.matchAll(/class="([^"]*)"/g)) {
|
||||
for (const cls of m[1]!.split(/\s+/).filter(Boolean)) {
|
||||
for (const cls of m[1].split(/\s+/).filter(Boolean)) {
|
||||
if (defined.has(cls) || inline.has(cls)) continue
|
||||
if (!used.has(cls)) used.set(cls, new Set())
|
||||
used.get(cls)!.add(`${target}${path}`)
|
||||
|
||||
@@ -98,4 +98,7 @@ const main = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
main().catch((error) => {
|
||||
console.error(error)
|
||||
process.exit(1)
|
||||
})
|
||||
+5
-2
@@ -61,7 +61,7 @@ const main = async () => {
|
||||
try {
|
||||
pages.set(name, await fetchPage(url))
|
||||
} catch (error) {
|
||||
failures.push(`${url} — ${error instanceof Error ? error.message : error}`)
|
||||
failures.push(`${url} — ${error instanceof Error ? error.message : String(error)}`)
|
||||
}
|
||||
}
|
||||
}),
|
||||
@@ -130,4 +130,7 @@ const main = async () => {
|
||||
console.log(`✓ All ${pages.size} pages are byte-identical to the baseline.`)
|
||||
}
|
||||
|
||||
main()
|
||||
main().catch((error) => {
|
||||
console.error(error)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -113,7 +113,7 @@ function lineBalance(line: string): number {
|
||||
let depth = 0
|
||||
for (const m of line.matchAll(/<(\/)?([a-zA-Z][\w.-]*)((?:"[^"]*"|'[^']*'|[^<>"'])*?)(\/)?>/g)) {
|
||||
if (m[1]) depth--
|
||||
else if (!m[4] && !voidTags.has(m[2]!.toLowerCase())) depth++
|
||||
else if (!m[4] && !voidTags.has(m[2].toLowerCase())) depth++
|
||||
}
|
||||
return depth
|
||||
}
|
||||
@@ -121,7 +121,7 @@ function lineBalance(line: string): number {
|
||||
const hasText = (line: string): boolean => line.replace(/<[^<>]*>/g, '').trim().length > 0
|
||||
|
||||
/** The tags of a fragment in order, for comparing markup before and after formatting. */
|
||||
const tagSequence = (html: string): string => [...html.matchAll(/<(\/?)([a-zA-Z][\w.-]*)/g)].map((m) => `${m[1]}${m[2]!.toLowerCase()}`).join(',')
|
||||
const tagSequence = (html: string): string => [...html.matchAll(/<(\/?)([a-zA-Z][\w.-]*)/g)].map((m) => `${m[1]}${m[2].toLowerCase()}`).join(',')
|
||||
|
||||
/**
|
||||
* Merge raw-text lines back into their markup (see formatExamples). MDX parses
|
||||
@@ -145,8 +145,8 @@ function glueTextLines(formatted: string): string {
|
||||
else out.push(line)
|
||||
// A text line can also land after a closing tag whose opener sits on an
|
||||
// earlier line (label after a multi-line svg) — pull those lines up too.
|
||||
while (out.length > 1 && hasText(out[out.length - 1]!) && lineBalance(out[out.length - 1]!) < 0) {
|
||||
const merged = `${out[out.length - 2]} ${out[out.length - 1]!.trim()}`
|
||||
while (out.length > 1 && hasText(out[out.length - 1]) && lineBalance(out[out.length - 1]) < 0) {
|
||||
const merged = `${out[out.length - 2]} ${out[out.length - 1].trim()}`
|
||||
out.splice(out.length - 2, 2, merged)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { defineConfig, type UserConfig } from 'vite'
|
||||
|
||||
interface CreateViteConfigOptions {
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@tabler/core": patch
|
||||
"@tabler/docs": patch
|
||||
"@tabler/preview": patch
|
||||
---
|
||||
|
||||
Added ESLint with `typescript-eslint` and fixed its findings in `Datepicker`, `Dropdown`, `Tooltip` and `Sparkline`.
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"rules": {
|
||||
"semi": ["error", "always"],
|
||||
"quotes": ["error", "single"],
|
||||
"no-var": ["error"]
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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 : {}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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 []
|
||||
|
||||
@@ -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,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
@@ -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"
|
||||
}
|
||||
|
||||
+3
-3
@@ -79,7 +79,7 @@ async function replaceAsync(input: string, pattern: RegExp, replacer: (match: Re
|
||||
const matches = [...input.matchAll(pattern)]
|
||||
if (!matches.length) return input
|
||||
|
||||
const replacements = await Promise.all(matches.map((match) => replacer(match as RegExpExecArray)))
|
||||
const replacements = await Promise.all(matches.map((match) => replacer(match)))
|
||||
|
||||
let result = ''
|
||||
let last = 0
|
||||
@@ -105,7 +105,7 @@ async function resolveCodeDocs(tag: string): Promise<string> {
|
||||
const load = kind.sources[`../../${file}`]
|
||||
if (!load) return ''
|
||||
|
||||
let source = (await load()) as string
|
||||
let source = await load()
|
||||
if (kind.lang === 'scss') source = source.replaceAll(' !default', '')
|
||||
|
||||
const snippet = extractMarkedSnippet(source, kind.marker, name)
|
||||
@@ -302,7 +302,7 @@ export function menuOrderedUrls(): string[] {
|
||||
return parts.length ? `/${parts.join('/')}` : '/'
|
||||
}
|
||||
const walk = (nodes: MenuNode[]): string[] => nodes.flatMap((node) => [...(node.url ? [normalize(node.url)] : []), ...walk(node.children ?? [])])
|
||||
return [...new Set(walk(docs.menu as MenuNode[]))]
|
||||
return [...new Set(walk(docs.menu))]
|
||||
}
|
||||
|
||||
/** Absolute in production, root-relative in dev — same rule as sitemap.xml.ts. */
|
||||
|
||||
@@ -30,7 +30,7 @@ function sources() {
|
||||
cachedSources ??= Promise.all(
|
||||
Object.entries(scssSources)
|
||||
.sort(([a], [b]) => a.localeCompare(b))
|
||||
.map(async ([key, load]) => ({ path: key.replace('../../', ''), source: (await load()) as string })),
|
||||
.map(async ([key, load]) => ({ path: key.replace('../../', ''), source: await load() })),
|
||||
)
|
||||
return cachedSources
|
||||
}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { includeIgnoreFile } from '@eslint/compat'
|
||||
import js from '@eslint/js'
|
||||
import tseslint from 'typescript-eslint'
|
||||
import astro from 'eslint-plugin-astro'
|
||||
import globals from 'globals'
|
||||
|
||||
// Same files Prettier formats, linted with type information
|
||||
const SCRIPTS = ['core/js/**/*.{js,ts}', 'core/.build/**/*.{ts,mts,mjs}', '.build/**/*.{ts,mts}', 'screenshots/.build/**/*.ts', '{preview,docs,screenshots}/**/*.{mjs,mts,ts}', 'shared/**/*.{js,mjs,mts,ts}']
|
||||
// Config files and SCSS tests that no tsconfig includes, linted without type information
|
||||
const TOOLING = ['*.{js,mjs,cjs}', '.changeset/*.cjs', 'core/*.{mjs,mts}', 'core/scss/tests/**/*.mjs']
|
||||
const ASTRO = ['{preview,docs,screenshots,shared}/**/*.astro']
|
||||
// The `<script>` blocks of Astro files, which the Astro processor hands over as virtual files
|
||||
const ASTRO_SCRIPTS = ['**/*.astro/*.js', '**/*.astro/*.ts']
|
||||
|
||||
// Dot directories are skipped by the `**/*` include of the package tsconfigs; `.build/tsconfig.json` covers them
|
||||
const BUILD_SCRIPTS = ['.build/**', 'core/.build/**', 'screenshots/.build/**']
|
||||
|
||||
const unsafeRulesOff = {
|
||||
'@typescript-eslint/no-unsafe-assignment': 'off',
|
||||
'@typescript-eslint/no-unsafe-member-access': 'off',
|
||||
'@typescript-eslint/no-unsafe-argument': 'off',
|
||||
'@typescript-eslint/no-unsafe-call': 'off',
|
||||
'@typescript-eslint/no-unsafe-return': 'off',
|
||||
}
|
||||
|
||||
export default tseslint.config(
|
||||
includeIgnoreFile(fileURLToPath(new URL('.gitignore', import.meta.url))),
|
||||
{
|
||||
ignores: ['.claude/**', '.cursor/**'],
|
||||
},
|
||||
{
|
||||
files: [...SCRIPTS, ...TOOLING, ...ASTRO, ...ASTRO_SCRIPTS],
|
||||
languageOptions: { globals: { ...globals.browser, ...globals.node } },
|
||||
},
|
||||
{
|
||||
files: SCRIPTS,
|
||||
ignores: ASTRO_SCRIPTS,
|
||||
extends: [js.configs.recommended, ...tseslint.configs.recommendedTypeChecked],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
projectService: { allowDefaultProject: ['docs/pages/.well-known/*.ts', 'docs/pages/.well-known/*/*.ts', 'docs/pages/.well-known/*/*/*.ts', 'preview/.build/*.mjs'] },
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [...TOOLING, ...ASTRO_SCRIPTS],
|
||||
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||
},
|
||||
{
|
||||
files: [...SCRIPTS, ...TOOLING, ...ASTRO, ...ASTRO_SCRIPTS],
|
||||
rules: {
|
||||
'prefer-const': ['error', { destructuring: 'all' }],
|
||||
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrors: 'none' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
// Only the shipped bundle is held to them. Elsewhere `.astro` imports, plain `.mjs`
|
||||
// files and JSON reach the type checker as `any`, so these rules report noise.
|
||||
files: SCRIPTS,
|
||||
ignores: ['core/js/src/**', ...ASTRO_SCRIPTS],
|
||||
rules: unsafeRulesOff,
|
||||
},
|
||||
{
|
||||
files: BUILD_SCRIPTS,
|
||||
languageOptions: { parserOptions: { projectService: false, project: './.build/tsconfig.json' } },
|
||||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/triple-slash-reference': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
// `core/tsconfig.json` leaves the specs out, so they are linted without type information
|
||||
files: ['core/js/tests/**'],
|
||||
extends: [tseslint.configs.disableTypeChecked],
|
||||
rules: { '@typescript-eslint/no-explicit-any': 'off' },
|
||||
},
|
||||
{
|
||||
// Bootstrap registers its static Data API handlers unbound, the same as upstream
|
||||
files: ['core/js/src/bootstrap/**'],
|
||||
rules: { '@typescript-eslint/unbound-method': 'off' },
|
||||
},
|
||||
{
|
||||
// Ambient declarations of globals have to use `var`
|
||||
files: ['**/*.d.ts'],
|
||||
rules: { 'no-var': 'off' },
|
||||
},
|
||||
{
|
||||
// Inline scripts ship as-is in the demo HTML: they keep the repo's init pattern
|
||||
// (`a && b()`, `ready ? init() : on(...)`) and the ES5 style some of them use
|
||||
files: ASTRO_SCRIPTS,
|
||||
rules: {
|
||||
'no-var': 'off',
|
||||
'@typescript-eslint/no-unused-expressions': ['error', { allowShortCircuit: true, allowTernary: true }],
|
||||
'@typescript-eslint/ban-ts-comment': ['error', { 'ts-nocheck': 'allow-with-description' }],
|
||||
},
|
||||
},
|
||||
...astro.configs.recommended,
|
||||
{
|
||||
files: ['**/*.astro/*.ts'],
|
||||
languageOptions: { parser: tseslint.parser },
|
||||
},
|
||||
{
|
||||
// Rules only: the configs' own parser setting would replace the Astro parser
|
||||
files: ASTRO,
|
||||
plugins: { '@typescript-eslint': tseslint.plugin },
|
||||
languageOptions: { parserOptions: { parser: tseslint.parser, extraFileExtensions: ['.astro'] } },
|
||||
rules: {
|
||||
...js.configs.recommended.rules,
|
||||
...Object.assign({}, ...tseslint.configs.recommended.map((config) => config.rules ?? {})),
|
||||
// TypeScript already checks names in the frontmatter and the template
|
||||
'no-undef': 'off',
|
||||
// Demo components take loosely shaped demo data
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
},
|
||||
},
|
||||
)
|
||||
+11
-3
@@ -34,8 +34,10 @@
|
||||
"check:open-source": "tsx .build/check-open-source.ts",
|
||||
"check:html": "tsx .build/check-html.ts",
|
||||
"check:markup-classes": "tsx .build/check-markup-classes.ts",
|
||||
"lint": "pnpm run lint:md && pnpm run check:docs-links && pnpm run lint:prettier && pnpm run lint:scss && pnpm run check:css-vars && pnpm run check:open-source && pnpm run check:tokens",
|
||||
"lint:fix": "pnpm run lint:md:fix && pnpm run lint:scss:fix && pnpm run format:prettier",
|
||||
"lint": "pnpm run lint:md && pnpm run check:docs-links && pnpm run lint:prettier && pnpm run lint:js && pnpm run lint:scss && pnpm run check:css-vars && pnpm run check:open-source && pnpm run check:tokens",
|
||||
"lint:fix": "pnpm run lint:md:fix && pnpm run lint:js:fix && pnpm run lint:scss:fix && pnpm run format:prettier",
|
||||
"lint:js": "eslint --cache --cache-location .cache/eslint/",
|
||||
"lint:js:fix": "eslint --fix --cache --cache-location .cache/eslint/",
|
||||
"lint:scss": "pnpm --filter @tabler/core lint",
|
||||
"lint:scss:fix": "pnpm --filter @tabler/core lint:scss:fix",
|
||||
"lint:md": "markdownlint --config .markdownlint-docs.json \"docs/content/**/*.mdx\"",
|
||||
@@ -56,6 +58,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@changesets/cli": "^3.0.1",
|
||||
"@eslint/compat": "^2.1.1",
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@mdx-js/mdx": "^3.1.1",
|
||||
"@stylistic/stylelint-config": "^2.0.0",
|
||||
"@tabler/icons": "^3.48.0",
|
||||
@@ -66,8 +70,11 @@
|
||||
"clean-css": "^5.3.3",
|
||||
"concurrently": "^10.0.5",
|
||||
"cross-env": "^10.1.0",
|
||||
"eslint": "^10.11.0",
|
||||
"eslint-plugin-astro": "^3.2.1",
|
||||
"github-slugger": "^2.0.0",
|
||||
"glob": "^13.0.6",
|
||||
"globals": "^17.12.0",
|
||||
"js-beautify": "^2.0.3",
|
||||
"markdownlint-cli": "^0.49.1",
|
||||
"nodemon": "^3.1.14",
|
||||
@@ -84,7 +91,8 @@
|
||||
"terser": "^5.51.2",
|
||||
"tsx": "^4.23.13",
|
||||
"turbo": "2.10.12",
|
||||
"typescript": "^7.0.2",
|
||||
"typescript": "^6.0.3",
|
||||
"typescript-eslint": "^8.71.0",
|
||||
"vite": "^8.2.2"
|
||||
}
|
||||
}
|
||||
Generated
+696
-237
File diff suppressed because it is too large.
Load diff
@@ -134,8 +134,8 @@ async function main() {
|
||||
await startPreviewServer()
|
||||
// Interrupting the script (Ctrl+C) must not leave the preview server behind
|
||||
// squatting on the port for the next run to collide with.
|
||||
process.once('SIGINT', stopPreviewServer)
|
||||
process.once('SIGTERM', stopPreviewServer)
|
||||
process.once('SIGINT', () => void stopPreviewServer())
|
||||
process.once('SIGTERM', () => void stopPreviewServer())
|
||||
|
||||
try {
|
||||
const browser = await chromium.launch()
|
||||
|
||||
@@ -145,9 +145,9 @@ function systemFfmpeg(): string | undefined {
|
||||
function resample(frames: Frame[], end: number): Buffer[] {
|
||||
const out: Buffer[] = []
|
||||
let index = 0
|
||||
for (let t = frames[0]!.time; t <= end; t += 1 / FPS) {
|
||||
while (index + 1 < frames.length && frames[index + 1]!.time <= t) index++
|
||||
out.push(frames[index]!.data)
|
||||
for (let t = frames[0].time; t <= end; t += 1 / FPS) {
|
||||
while (index + 1 < frames.length && frames[index + 1].time <= t) index++
|
||||
out.push(frames[index].data)
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -269,9 +269,9 @@ async function recordOne(page: Page, client: CDPSession, slug: string, theme: 'l
|
||||
await moveCursor(page, { x: width / 2, y: height + 40 }, 0)
|
||||
|
||||
const frames: Frame[] = []
|
||||
const onFrame = async (event: { data: string; sessionId: number }) => {
|
||||
const onFrame = (event: { data: string; sessionId: number }) => {
|
||||
frames.push({ data: Buffer.from(event.data, 'base64'), time: Date.now() / 1000 })
|
||||
await client.send('Page.screencastFrameAck', { sessionId: event.sessionId }).catch(() => {})
|
||||
void client.send('Page.screencastFrameAck', { sessionId: event.sessionId }).catch(() => {})
|
||||
}
|
||||
client.on('Page.screencastFrame', onFrame)
|
||||
await client.send('Page.startScreencast', { format: 'jpeg', quality: 92, maxWidth: width, maxHeight: height, everyNthFrame: 1 })
|
||||
@@ -303,8 +303,8 @@ async function main() {
|
||||
|
||||
console.log(`Starting preview server on port ${PORT}…`)
|
||||
await startPreviewServer()
|
||||
process.once('SIGINT', stopPreviewServer)
|
||||
process.once('SIGTERM', stopPreviewServer)
|
||||
process.once('SIGINT', () => void stopPreviewServer())
|
||||
process.once('SIGTERM', () => void stopPreviewServer())
|
||||
|
||||
try {
|
||||
const browser = await chromium.launch()
|
||||
@@ -321,7 +321,7 @@ async function main() {
|
||||
|
||||
// RECORD_FRAMES="0.5,2,4" also writes those seconds as JPEG stills, for a quick look without a player.
|
||||
for (const second of (process.env.RECORD_FRAMES ?? '').split(',').filter(Boolean).map(Number)) {
|
||||
const frame = sampled[Math.min(sampled.length - 1, Math.round(second * FPS))]!
|
||||
const frame = sampled[Math.min(sampled.length - 1, Math.round(second * FPS))]
|
||||
writeFileSync(path.join(outDir, `${name}-${second}s.jpg`), frame)
|
||||
}
|
||||
|
||||
|
||||
@@ -430,7 +430,7 @@ export function chartConfig(opts: { id: string; data: ChartData; height: number
|
||||
}
|
||||
|
||||
if (data.datetime) {
|
||||
const count = Array.isArray(series[0]?.data) ? (series[0].data as number[]).length : 0
|
||||
const count = Array.isArray(series[0]?.data) ? series[0].data.length : 0
|
||||
config.labels = datetimeLabels(data['start-date'] ?? '2020-06-20', count)
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ export interface ThemePreset extends ThemeOption {
|
||||
|
||||
export interface ThemeSetting {
|
||||
/** input name; a real switcher key, or the preset group's own name */
|
||||
key: ThemeKey | string
|
||||
key: string
|
||||
/** fieldset legend */
|
||||
legend: string
|
||||
/** help text under the legend */
|
||||
|
||||
Reference in new issue
Block a user