web: update to ESLint 9 (#10812)

* web: update to ESLint 9

ESLint 9 has been out for awhile now, and all of the plug-ins that we use have caught up, so it is
time to bite the bullet and upgrade.  This commit:

- upgrades to ESLint 9, and upgrades all associated plugins
- Replaces the `.eslintrc` and `.eslintignore` files with the new, "flat" configuration file,
  "eslint.config.mjs".
- Places the previous "precommit" and "nightmare" rules in `./scripts/eslint.precommit.mjs` and
  `./scripts/eslint.nightmare.mjs`, respectively
- Replaces the scripted wrappers for eslint (`eslint`, `eslint-precommit`) with a single executable
  that takes the arguments `--precommit`, which applies a stricter set of rules, and `--nightmare`,
  which applies an even more terrifyingly strict set of rules.
- Provides the scripted wrapper `./scripts/eslint.mjs` so that eslint can be run from `bun`, if one
  so chooses.
- Fixes *all* of the lint `eslint.config.mjs` now finds, including removing all of the `eslint`
  styling rules and overrides because Eslint now proudly leaves that entirely up to Prettier.

To shut Dependabot up about ESLint.

* Added explanation for no-console removal.

* web: did not need the old and unmaintained nightmare mode; it can be configured directly.
This commit is contained in:
Ken Sternberg
2024-08-07 15:04:18 -07:00
committed by GitHub
parent 322ae4c4ed
commit 79c01ca473
34 changed files with 5658 additions and 5076 deletions

View File

@ -48,7 +48,9 @@ function assignValue(element: HTMLNamedElement, value: unknown, json: KeyUnknown
for (let index = 0; index < nameElements.length - 1; index++) {
const nameEl = nameElements[index];
// Ensure all nested structures exist
if (!(nameEl in parent)) parent[nameEl] = {};
if (!(nameEl in parent)) {
parent[nameEl] = {};
}
parent = parent[nameEl] as { [key: string]: unknown };
}
parent[nameElements[nameElements.length - 1]] = value;
@ -103,7 +105,7 @@ export function serializeForm<T extends KeyUnknown>(
} else if (
inputElement.tagName.toLowerCase() === "input" &&
"type" in inputElement.dataset &&
inputElement.dataset["type"] === "datetime-local"
inputElement.dataset.type === "datetime-local"
) {
// Workaround for Firefox <93, since 92 and older don't support
// datetime-local fields
@ -122,6 +124,9 @@ export function serializeForm<T extends KeyUnknown>(
return json as unknown as T;
}
const HTTP_BAD_REQUEST = 400;
const HTTP_INTERNAL_SERVICE_ERROR = 500;
/**
* Form
*
@ -188,7 +193,7 @@ export abstract class Form<T> extends AKElement {
*/
get isInViewport(): boolean {
const rect = this.getBoundingClientRect();
return !(rect.x + rect.y + rect.width + rect.height === 0);
return rect.x + rect.y + rect.width + rect.height !== 0;
}
getSuccessMessage(): string {
@ -275,7 +280,6 @@ export abstract class Form<T> extends AKElement {
}
return serializeForm(elements) as T;
}
/**
* Serialize and send the form to the destination. The `send()` method must be overridden for
* this to work. If processing the data results in an error, we catch the error, distribute
@ -304,9 +308,14 @@ export abstract class Form<T> extends AKElement {
} catch (ex) {
if (ex instanceof ResponseError) {
let msg = ex.response.statusText;
if (ex.response.status > 399 && ex.response.status < 500) {
if (
ex.response.status >= HTTP_BAD_REQUEST &&
ex.response.status < HTTP_INTERNAL_SERVICE_ERROR
) {
const errorMessage = ValidationErrorFromJSON(await ex.response.json());
if (!errorMessage) return errorMessage;
if (!errorMessage) {
return errorMessage;
}
if (errorMessage instanceof Error) {
throw errorMessage;
}
@ -318,7 +327,9 @@ export abstract class Form<T> extends AKElement {
elements.forEach((element) => {
element.requestUpdate();
const elementName = element.name;
if (!elementName) return;
if (!elementName) {
return;
}
if (camelToSnake(elementName) in errorMessage) {
element.errorMessages = errorMessage[camelToSnake(elementName)];
element.invalid = true;

View File

@ -20,7 +20,8 @@ import {
SearchSelectSelectEvent,
SearchSelectSelectMenuEvent,
} from "./SearchSelectEvents.js";
import type { SearchOptions, SearchTuple } from "./types.js";
import type { SearchOptions } from "./types.js";
import { optionsToOptionsMap } from "./utils.js";
/**
* @class SearchSelectView
@ -225,8 +226,8 @@ export class SearchSelectView extends AKElement {
}
updated() {
if (!(this.inputRef?.value && this.inputRef?.value?.value === this.displayValue)) {
this.inputRef.value && (this.inputRef.value.value = this.displayValue);
if (this.inputRef?.value && this.inputRef?.value?.value !== this.displayValue) {
this.inputRef.value.value = this.displayValue;
}
}
@ -264,21 +265,6 @@ export class SearchSelectView extends AKElement {
}
}
type Pair = [string, string];
const justThePair = ([key, label]: SearchTuple): Pair => [key, label];
function optionsToOptionsMap(options: SearchOptions): Map<string, string> {
const pairs: Pair[] = Array.isArray(options)
? options.map(justThePair)
: options.grouped
? options.options.reduce(
(acc: Pair[], { options }): Pair[] => [...acc, ...options.map(justThePair)],
[] as Pair[],
)
: options.options.map(justThePair);
return new Map(pairs);
}
declare global {
interface HTMLElementTagNameMap {
"ak-search-select-view": SearchSelectView;

View File

@ -97,11 +97,6 @@ export class SearchSelect<T> extends CustomEmitterElement(AkControlElement) {
@state()
error?: APIErrorTypes;
constructor() {
super();
this.dataset.akControl = "true";
}
toForm(): unknown {
if (!this.objects) {
throw new PreventFormSubmit(msg("Loading options..."));
@ -113,9 +108,9 @@ export class SearchSelect<T> extends CustomEmitterElement(AkControlElement) {
return this.toForm();
}
updateData() {
async updateData() {
if (this.isFetchingData) {
return;
return Promise.resolve();
}
this.isFetchingData = true;
return this.fetchObjects(this.query)
@ -140,6 +135,7 @@ export class SearchSelect<T> extends CustomEmitterElement(AkControlElement) {
connectedCallback(): void {
super.connectedCallback();
this.dataset.akControl = "true";
this.updateData();
this.addEventListener(EVENT_REFRESH, this.updateData);
}

View File

@ -29,7 +29,6 @@ const metadata: Meta<SearchSelectMenu> = {
export default metadata;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const onClick = (event: SearchSelectSelectMenuEvent) => {
const target = document.querySelector("#action-button-message-pad");
target!.innerHTML = "";

View File

@ -0,0 +1,16 @@
import type { SearchOptions, SearchTuple } from "./types.js";
type Pair = [string, string];
const justThePair = ([key, label]: SearchTuple): Pair => [key, label];
export function optionsToOptionsMap(options: SearchOptions): Map<string, string> {
const pairs: Pair[] = Array.isArray(options)
? options.map(justThePair)
: options.grouped
? options.options.reduce(
(acc: Pair[], { options }): Pair[] => [...acc, ...options.map(justThePair)],
[] as Pair[],
)
: options.options.map(justThePair);
return new Map(pairs);
}