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:
@ -73,9 +73,10 @@ export class CodeMirrorTextarea<T> extends AKElement {
|
||||
}
|
||||
|
||||
@property()
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/explicit-module-boundary-types
|
||||
set value(v: T | string) {
|
||||
if (v === null || v === undefined) return;
|
||||
if (v === null || v === undefined) {
|
||||
return;
|
||||
}
|
||||
// Value might be an object if within an iron-form, as that calls the getter of value
|
||||
// in the beginning and the calls this setter on reset
|
||||
let textValue = v;
|
||||
@ -114,7 +115,7 @@ export class CodeMirrorTextarea<T> extends AKElement {
|
||||
default:
|
||||
return this.getInnerValue();
|
||||
}
|
||||
} catch (e) {
|
||||
} catch (_e: unknown) {
|
||||
return this.getInnerValue();
|
||||
}
|
||||
}
|
||||
|
||||
@ -136,7 +136,6 @@ export class CheckboxGroup extends AkElementWithCustomEvents {
|
||||
constructor() {
|
||||
super();
|
||||
this.onClick = this.onClick.bind(this);
|
||||
this.dataset.akControl = "true";
|
||||
}
|
||||
|
||||
onClick(ev: Event) {
|
||||
@ -173,6 +172,7 @@ export class CheckboxGroup extends AkElementWithCustomEvents {
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.dataset.akControl = "true";
|
||||
if (this.name && !this.internals) {
|
||||
this.internals = this.attachInternals();
|
||||
}
|
||||
|
||||
@ -56,7 +56,6 @@ const container = (testItem: TemplateResult) =>
|
||||
const displayMessage = (result: any) => {
|
||||
const doc = new DOMParser().parseFromString(`<li><i>Event</i>: ${result}</li>`, "text/xml");
|
||||
const target = document.querySelector("#action-button-message-pad");
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
target!.appendChild(doc.firstChild!);
|
||||
};
|
||||
|
||||
|
||||
@ -42,7 +42,6 @@ const container = (testItem: TemplateResult) =>
|
||||
const displayMessage = (result: any) => {
|
||||
const doc = new DOMParser().parseFromString(`<p><i>Content</i>: ${result}</p>`, "text/xml");
|
||||
const target = document.querySelector("#action-button-message-pad");
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
target!.replaceChildren(doc.firstChild!);
|
||||
};
|
||||
|
||||
@ -51,7 +50,6 @@ const displayMessage2 = (result: any) => {
|
||||
console.debug("Huh.");
|
||||
const doc = new DOMParser().parseFromString(`<p><i>Behavior</i>: ${result}</p>`, "text/xml");
|
||||
const target = document.querySelector("#action-button-message-pad-2");
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
target!.replaceChildren(doc.firstChild!);
|
||||
};
|
||||
|
||||
|
||||
@ -52,7 +52,6 @@ const displayMessage = (result: any) => {
|
||||
"text/xml",
|
||||
);
|
||||
const target = document.querySelector("#action-button-message-pad");
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
target!.appendChild(doc.firstChild!);
|
||||
};
|
||||
|
||||
|
||||
@ -6,25 +6,33 @@ import { customElement } from "lit/decorators.js";
|
||||
|
||||
@customElement("ak-dropdown")
|
||||
export class DropdownButton extends AKElement {
|
||||
menu: HTMLElement | null;
|
||||
menu: HTMLElement | null = null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.menu = this.querySelector<HTMLElement>(".pf-c-dropdown__menu");
|
||||
this.querySelectorAll("button.pf-c-dropdown__toggle").forEach((btn) => {
|
||||
btn.addEventListener("click", () => {
|
||||
if (!this.menu) return;
|
||||
this.menu.hidden = !this.menu.hidden;
|
||||
});
|
||||
});
|
||||
window.addEventListener(EVENT_REFRESH, this.clickHandler);
|
||||
}
|
||||
|
||||
clickHandler = (): void => {
|
||||
if (!this.menu) return;
|
||||
if (!this.menu) {
|
||||
return;
|
||||
}
|
||||
this.menu.hidden = true;
|
||||
};
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.menu = this.querySelector<HTMLElement>(".pf-c-dropdown__menu");
|
||||
this.querySelectorAll("button.pf-c-dropdown__toggle").forEach((btn) => {
|
||||
btn.addEventListener("click", () => {
|
||||
if (!this.menu) {
|
||||
return;
|
||||
}
|
||||
this.menu.hidden = !this.menu.hidden;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
window.removeEventListener(EVENT_REFRESH, this.clickHandler);
|
||||
|
||||
@ -51,7 +51,6 @@ const displayMessage = (result: any) => {
|
||||
"text/xml",
|
||||
);
|
||||
const target = document.querySelector("#action-button-message-pad");
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
target!.appendChild(doc.firstChild!);
|
||||
};
|
||||
|
||||
|
||||
@ -98,7 +98,6 @@ export class ModalOrchestrationController implements ReactiveController {
|
||||
// Pop off modals until you find the first live one, schedule it to be closed, and make that
|
||||
// cleaned list the current state. Since this is our *only* state object, this has the
|
||||
// effect of creating a new "knownModals" collection with some semantics.
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
const modal = knownModals.pop();
|
||||
if (!modal) {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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 = "";
|
||||
|
||||
16
web/src/elements/forms/SearchSelect/utils.ts
Normal file
16
web/src/elements/forms/SearchSelect/utils.ts
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user