Skip to content

Commit 87312f8

Browse files
authored
experimental: generate meta for html and aria attributes (#5149)
Here added lists of attributes from html and aria specifications for future Element component. Html provides an easy to navigate table with all types with relations to specific tags and descriptions. Aria list with types is extracted from aria-query library. Though descriptions are parsed from specification. In the future we will use more aria-query data to navigate specific roles. All attributes types are tested with react types You can see generated data in - packages/html-data/src/__generated__/attributes.ts - packages/html-data/src/__generated__/aria.ts Succeeds #2341
1 parent 395a6b9 commit 87312f8

File tree

12 files changed

+2424
-20
lines changed

12 files changed

+2424
-20
lines changed

packages/html-data/bin/aria.ts

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { aria } from "aria-query";
2+
import {
3+
findTags,
4+
getAttr,
5+
getTextContent,
6+
loadPage,
7+
parseHtml,
8+
} from "./crawler";
9+
import { mkdir, writeFile } from "node:fs/promises";
10+
import {
11+
createScope,
12+
elementComponent,
13+
Prop,
14+
type Instance,
15+
type Instances,
16+
type Props,
17+
} from "@webstudio-is/sdk";
18+
import { generateWebstudioComponent } from "@webstudio-is/react-sdk";
19+
20+
type Attribute = {
21+
name: string;
22+
description: string;
23+
type: "string" | "boolean" | "number" | "select";
24+
options?: string[];
25+
};
26+
27+
const html = await loadPage("aria1.3", "https://www.w3.org/TR/wai-aria-1.3");
28+
const document = parseHtml(html);
29+
const list = findTags(document, "dl").find(
30+
(table) => getAttr(table, "id")?.value === "index_state_prop"
31+
);
32+
const terms = findTags(list, "dt");
33+
const details = findTags(list, "dd");
34+
const descriptions = new Map<string, string>();
35+
for (let index = 0; index < terms.length; index += 1) {
36+
const term = getTextContent(terms[index]);
37+
const detail = getTextContent(details[index]);
38+
descriptions.set(term, detail);
39+
}
40+
41+
const attributes: Attribute[] = [];
42+
for (const [name, meta] of aria.entries()) {
43+
const attribute: Attribute = {
44+
name,
45+
description: descriptions.get(name) ?? "",
46+
type: "string",
47+
};
48+
if (meta.type === "string" || meta.type === "boolean") {
49+
attribute.type = meta.type;
50+
} else if (meta.type === "number" || meta.type === "integer") {
51+
attribute.type = "number";
52+
} else if (meta.type === "token" || meta.type === "tokenlist") {
53+
attribute.type = "select";
54+
attribute.options = meta.values?.map((item) => item.toString());
55+
} else if (meta.type === "tristate") {
56+
attribute.type = "select";
57+
attribute.options = ["false", "mixed", "true"];
58+
} else {
59+
meta.type satisfies "id" | "idlist" | "tristate";
60+
}
61+
attributes.push(attribute);
62+
}
63+
64+
const ariaContent = `type Attribute = {
65+
name: string,
66+
description: string,
67+
type: 'string' | 'boolean' | 'number' | 'select',
68+
options?: string[]
69+
}
70+
71+
export const ariaAttributes: Attribute[] = ${JSON.stringify(attributes, null, 2)};
72+
`;
73+
await mkdir("./src/__generated__", { recursive: true });
74+
await writeFile("./src/__generated__/aria.ts", ariaContent);
75+
76+
// generate jsx for testing react types
77+
78+
const instances: Instances = new Map();
79+
const props: Props = new Map();
80+
81+
let id = 0;
82+
const getId = () => {
83+
id += 1;
84+
return id.toString();
85+
};
86+
87+
const instance: Instance = {
88+
type: "instance",
89+
id: getId(),
90+
component: elementComponent,
91+
tag: "div",
92+
children: [],
93+
};
94+
instances.set(instance.id, instance);
95+
for (const { name, type, options } of attributes) {
96+
const id = getId();
97+
const instanceId = instance.id;
98+
if (type === "string") {
99+
const prop: Prop = { id, instanceId, type, name, value: "" };
100+
props.set(prop.id, prop);
101+
continue;
102+
}
103+
if (type === "boolean") {
104+
const prop: Prop = { id, instanceId, type, name, value: true };
105+
props.set(prop.id, prop);
106+
continue;
107+
}
108+
if (type === "select") {
109+
const prop: Prop = {
110+
id,
111+
instanceId,
112+
type: "string",
113+
name,
114+
value: options?.[0] ?? "",
115+
};
116+
props.set(prop.id, prop);
117+
continue;
118+
}
119+
if (type === "number") {
120+
const prop: Prop = {
121+
id,
122+
instanceId,
123+
type: "number",
124+
name,
125+
value: 0,
126+
};
127+
props.set(prop.id, prop);
128+
continue;
129+
}
130+
throw Error(`Unknown attribute ${name} with type ${type}`);
131+
}
132+
133+
await mkdir("./src/__generated__", { recursive: true });
134+
await writeFile(
135+
"./src/__generated__/aria-jsx-test.tsx",
136+
generateWebstudioComponent({
137+
name: "Page",
138+
scope: createScope(),
139+
instances,
140+
props,
141+
dataSources: new Map(),
142+
rootInstanceId: instance.id,
143+
classesMap: new Map(),
144+
parameters: [],
145+
metas: new Map(),
146+
}) + "export { Page }"
147+
);

0 commit comments

Comments
 (0)