-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathfetchAttributes.ts
More file actions
137 lines (114 loc) · 4.5 KB
/
Copy pathfetchAttributes.ts
File metadata and controls
137 lines (114 loc) · 4.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import { DOMParser } from "https://deno.land/x/deno_dom@v0.1.12-alpha/deno-dom-wasm.ts";
import { getSpecialType } from "./util/getSpecialType.ts";
import * as typer from "./util/hypertyper.ts";
const html = await fetch("https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes").then(res => res.text());
const document = new DOMParser().parseFromString(html, "text/html")!;
type Attribute = { prop: string; elements: string[]; desc: string; type: string };
const groupByList = (xs: Attribute[]) =>
xs.reduce((acc, curr) => {
for (const element of curr.elements) {
(acc[element] || (acc[element] = [])).push({
prop: curr.prop,
elements: curr.elements,
type: "string",
desc: curr.desc,
});
}
return acc;
}, {} as Record<string, Attribute[]>);
const { "Global attribute": globalAttr, ...elements } = groupByList(
[...document.querySelector("article table.standard-table tbody")?.childNodes!]
.filter(tr => tr.nodeName === "TR")
.filter(
tr =>
// @ts-ignore: innerHTML exists, but isn't typed
!(tr.innerHTML as string).includes(`"icon icon-deprecated"`),
)
.map(tr => [...tr.childNodes!].map(each => each.textContent))
.map(([, attr, , elems, , desc]) => {
const elements = elems
.replaceAll(/<|>/g, "")
.split(/,\s+/)
.map(e => e.trim());
return {
type: "", // will be replaced after
prop: attr.trim(),
elements,
desc: desc.trim(),
};
})
.filter(each => each.prop !== "data-*")
.sort((a, b) => a.prop.localeCompare(b.prop)),
);
const customDesc: Record<string, string> = {
height: "Specifies the height of the element.",
width: "Specifies the width of the element.",
};
const composeCustom = (attr: Attribute, element?: string) => ({
prop: attr.prop,
desc: customDesc[attr.prop] || attr.desc.replace("\n\n", "\n"),
type: getSpecialType(attr.prop)(element || "global") || "string",
});
const globalType = typer.type("GlobalAttrs", typer.struct(globalAttr.map(attr => composeCustom(attr))));
const capitalise = (name: string) => name[0].toUpperCase() + name.slice(1);
const toElementAttrType = (name: string) => capitalise(name) + "Attributes";
function* elementTypes() {
const sorted = Object.keys(elements).sort((a, b) => a.localeCompare(b));
for (const element of sorted) {
yield typer.statement(
typer.type(
toElementAttrType(element),
typer.struct(elements[element].map(attr => composeCustom(attr, element))),
),
);
}
yield typer.statement(
typer.type(
"UniqueElementAttrs",
typer.struct(sorted.map(element => ({ prop: element, type: toElementAttrType(element) }))),
),
);
}
export const preamble = "// This file was generated by hypertyper. Do not manually edit this file.";
export const imports = [
`import { Element } from "./elements.ts";`,
`import { AriaRoles, AriaAttributes } from "./aria.ts";`,
`import { HTMLElement, DOMEvents } from "./dom.ts";`,
].join("\n");
const prologue = `type PropOr<T, P extends string | symbol | number, D> =
T extends Record<P, infer V> ? V : D;
type Deunionise<T> =
| ([undefined] extends [T] ? undefined : never)
| { [K in T extends unknown ? keyof T : never]: PropOr<NonNullable<T>, K, undefined>; };
export type AllAttrs = Partial<Deunionise<UniqueElementAttrs[keyof UniqueElementAttrs]>>;
export type DataAttr = ${"`data-${string}`"};
type MappedPartial<T> = {} & { [P in keyof T]?: T[P] };
export type Attr<E extends Element = Element> =
MappedPartial<
GlobalAttrs & { // [data in DataAttr]?: string } & { // TS weirdly breaks when we allow this
/**
* ref callback is called on mount of element with the DOM element.
*/
ref: (el: HTMLElement) => void,
/**
* When the element lacks suitable ARIA-semantics, authors must
* assign an ARIA-role. Addition of ARIA semantics only exposes
* extra information to a browser's accessibility API, and does
* not affect a page's DOM.
*
* @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques
*/
role: AriaRoles;
/**
* ARIA is a set of attributes that define ways to make web content
* and web applications (especially those developed with JavaScript)
* more accessible to people with disabilities.
*
* @see https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA
*/
aria: AriaAttributes;
} & (UniqueElementAttrs & { [k: string]: unknown })[E] & DOMEvents
>;
`;
// Deno.writeTextFileSync("./src/lib/attributes.ts", types);
typer.writer("./src/lib/attributes.ts", typer.program([preamble, imports, globalType, ...elementTypes(), prologue]));