Skip to content

Commit 3846c8e

Browse files
committed
upgrade @types/react
somewhat of an experiment with vibecoding (Gemini CLI) - it didn't go amazing so most of this got manually edited. - it seems JSX.InstrinicElements now needs to be provided by a framework or something to go into the nitty gritty of each attribute allowed on an element - i think the decision to just type things as Record<string, unknown> is more or less reasonable - it seems like Fragment needs to be explicitly required now - the h function gets a lot more complicated. not sure if this is required but a little too hairy for me to care about
1 parent 33bd423 commit 3846c8e

4 files changed

Lines changed: 53 additions & 37 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@
110110
"@types/minimist": "^1.2.5",
111111
"@types/mustache": "^4.2.6",
112112
"@types/node": "^25.0.6",
113-
"@types/react": "<=18",
113+
"@types/react": "^19.2.13",
114114
"@types/semver": "^7.7.1",
115115
"@types/vscode": "^1.108.1",
116116
"@vitest/coverage-v8": "^4.0.17",

src/tools/display/dom.ts

Lines changed: 43 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,18 @@
11
/** @license MIT modified from Vadim Demedes's https://github.com/vadimdemedes/dom-chef */
2-
type Attributes = JSX.IntrinsicElements['div'];
3-
type DocumentFragmentFunction = (props?: any) => DocumentFragment;
4-
type ElementFunction = (props?: any) => HTMLElement;
52

63
declare global {
74
namespace JSX {
8-
interface Element extends HTMLElement, DocumentFragment {
9-
addEventListener: HTMLElement['addEventListener'];
10-
removeEventListener: HTMLElement['removeEventListener'];
11-
className: HTMLElement['className'];
5+
type Element = HTMLElement | DocumentFragment;
6+
interface IntrinsicElements {
7+
[elemName: string]: Record<string, unknown>;
128
}
139
}
1410
}
1511

16-
interface JSXElementClassDocumentFragment extends DocumentFragment, JSX.ElementClass { }
17-
interface Fragment {
18-
prototype: JSXElementClassDocumentFragment;
19-
new(): JSXElementClassDocumentFragment;
20-
}
12+
type Child = Node | string | number | boolean | null | undefined | Child[];
13+
14+
type DocumentFragmentFunction = (props?: any) => DocumentFragment;
15+
type ElementFunction = (props?: any) => HTMLElement;
2116

2217
// https://github.com/preactjs/preact/blob/1bbd687c/src/constants.js#L3
2318
const IS_NON_DIMENSIONAL = /acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;
@@ -38,35 +33,55 @@ const setAttribute = (element: HTMLElement, name: string, value: string) => {
3833
if (value !== undefined && value !== null) element.setAttribute(name, value);
3934
};
4035

41-
const addChildren = (parent: Element | DocumentFragment, children: Node[]) => {
36+
const addChildren = (parent: Node, children: Child[]) => {
4237
for (const child of children) {
4338
if (child instanceof Node) {
4439
parent.appendChild(child);
4540
} else if (Array.isArray(child)) {
4641
addChildren(parent, child);
4742
} else if (typeof child !== 'boolean' && typeof child !== 'undefined' && child !== null) {
48-
parent.appendChild(document.createTextNode(child));
43+
parent.appendChild(document.createTextNode(String(child)));
4944
}
5045
}
5146
};
5247

5348
// https://github.com/facebook/react/blob/3f899089/packages/react-dom/src/shared/DOMProperty.js#L288-L322
5449
const FALSIFIABLE_ATTRIBUTES = ['contentEditable', 'draggable', 'spellCheck', 'value'];
5550

56-
export const h = (
51+
export function h<K extends keyof HTMLElementTagNameMap>(
52+
type: K,
53+
attributes?: any,
54+
...children: Child[]
55+
): HTMLElementTagNameMap[K];
56+
export function h(
57+
type: DocumentFragmentFunction,
58+
attributes?: any,
59+
...children: Child[]
60+
): DocumentFragment;
61+
export function h<T extends HTMLElement>(
62+
type: (props: any) => T,
63+
attributes?: any,
64+
...children: Child[]
65+
): T;
66+
export function h(
67+
type: string,
68+
attributes?: any,
69+
...children: Child[]
70+
): HTMLElement;
71+
export function h(
5772
type: DocumentFragmentFunction | ElementFunction | string,
58-
attributes?: Attributes,
59-
...children: Node[]
60-
) => {
73+
attributes?: any,
74+
...children: Child[]
75+
): JSX.Element {
6176
if (typeof type !== 'string') {
6277
const element = type(attributes);
6378
addChildren(element, children);
64-
return element;
79+
return element as JSX.Element;
6580
}
6681

6782
const element = document.createElement(type);
6883
addChildren(element, children);
69-
if (!attributes) return element;
84+
if (!attributes) return element as JSX.Element;
7085

7186
for (let [name, value] of Object.entries(attributes)) {
7287
if (name === 'htmlFor') name = 'for';
@@ -75,19 +90,19 @@ export const h = (
7590
const existingClassname = element.getAttribute('class') ?? '';
7691
setAttribute(element, 'class', (existingClassname + ' ' + String(value)).trim());
7792
} else if (name === 'style') {
78-
setCSSProps(element, value);
93+
setCSSProps(element, value as CSSStyleDeclaration);
7994
} else if (name.startsWith('on')) {
8095
const eventName = name.slice(2).toLowerCase().replace(/^-/, '');
81-
element.addEventListener(eventName, value);
82-
} else if (name === 'dangerouslySetInnerHTML' && '__html' in value) {
83-
element.innerHTML = value.__html;
96+
element.addEventListener(eventName, value as EventListenerOrEventListenerObject);
97+
} else if (name === 'dangerouslySetInnerHTML' && value && (value as any).__html) {
98+
element.innerHTML = (value as any).__html;
8499
} else if (name !== 'key' && (FALSIFIABLE_ATTRIBUTES.includes(name) || value !== false)) {
85-
setAttribute(element, name, value === true ? '' : value);
100+
setAttribute(element, name, value === true ? '' : String(value));
86101
}
87102
}
88103

89-
return element;
90-
};
104+
return element as JSX.Element;
105+
}
91106

92107
// eslint-disable-next-line @typescript-eslint/no-unused-vars
93-
export const Fragment = (unused: any) => document.createDocumentFragment();
108+
export const Fragment = (unused?: any) => document.createDocumentFragment();

src/tools/display/select.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ export const Select = ({options, unmount, placeholder, render}: {
1515
name='select'
1616
className='select'
1717
autoComplete='off'
18-
placeholder={placeholder} /> as any as HTMLInputElement;
19-
const container = <div className='select options' />;
18+
placeholder={placeholder} /> as HTMLInputElement;
19+
const container = <div className='select options' /> as HTMLDivElement;
2020

2121
render ||= option => input.value
2222
// ? <span dangerouslySetInnerHTML={{__html: searcher.highlight(option)}}></span>
@@ -73,11 +73,11 @@ export const Select = ({options, unmount, placeholder, render}: {
7373
const val = input.value;
7474
cache[val] = data;
7575
if (data.length) {
76-
const children: JSX.Element[] = [];
76+
const children: Node[] = [];
7777
for (const option of data) {
7878
children.push(<div className='select option' data-value={option}>
7979
{render(option, val)}
80-
</div>);
80+
</div> as HTMLElement);
8181
}
8282
container.replaceChildren(...children);
8383
console.debug('suggest');
@@ -87,7 +87,7 @@ export const Select = ({options, unmount, placeholder, render}: {
8787
}
8888
};
8989

90-
const live = (e: MouseEvent, cls: string) => {
90+
const live = (e: Event, cls: string) => {
9191
let found = false;
9292
let element = e.target as HTMLElement | null;
9393
while (element && !(found = element.classList.contains(cls))) element = element.parentElement;
@@ -97,15 +97,15 @@ export const Select = ({options, unmount, placeholder, render}: {
9797
container.addEventListener('mouseleave', e => {
9898
if (!live(e, '.option')) return;
9999
console.debug('mouseleave');
100-
const selected = container.querySelector('.select.option.selected');
100+
const selected = container.querySelector('.select.option.selected') as HTMLElement;
101101
if (selected) setTimeout(() => selected.classList.remove('selected'), FRAME_MS);
102102
});
103103

104104
container.addEventListener('mouseover', e => {
105105
const element = live(e, '.option');
106106
if (!element) return;
107107
console.debug('mouseover');
108-
const selected = container.querySelector('.select.option.selected');
108+
const selected = container.querySelector('.select.option.selected') as HTMLElement;
109109
if (selected) setTimeout(() => selected.classList.remove('selected'), FRAME_MS);
110110
element.classList.add('selected');
111111
});

src/tools/display/ui.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as adaptable from '@pkmn/img/adaptable';
33

44
import * as engine from '../../pkg';
55

6+
import {Fragment} from './dom';
67
import * as util from './util';
78

89
declare const Sprites: adaptable.Sprites;
@@ -301,7 +302,7 @@ const HPBar = ({pokemon, last}: {
301302
};
302303

303304
export const Status = ({pokemon}: {pokemon: engine.Data<engine.Pokemon>}) => {
304-
if (!pokemon.status) return '';
305+
if (!pokemon.status) return <></>;
305306
const classes = `status ${pokemon.status === 'tox' ? 'psn' : pokemon.status}`;
306307
let title = '';
307308
if (pokemon.statusData.sleep) title += `Sleep: ${pokemon.statusData.sleep}`;

0 commit comments

Comments
 (0)