feat(custom-widgets): complete Custom JSX v2 (#6390)
This commit is contained in:
1 parent
3a1245cbc5
commit
acdfcbb18e
416 files changed
+37853
-18066
No files matched your search
@@ -10,28 +10,70 @@ export const redactPreviewUrl = (value: string): string => {
|
||||
}
|
||||
};
|
||||
|
||||
export const getPreviewNamedRequests = (displayConfig: Record<string, unknown>): PreviewNamedRequest[] => {
|
||||
if (!Array.isArray(displayConfig.requests)) return [];
|
||||
return displayConfig.requests.filter(
|
||||
(request): request is PreviewNamedRequest =>
|
||||
request !== null &&
|
||||
typeof request === "object" &&
|
||||
"id" in request &&
|
||||
typeof request.id === "string" &&
|
||||
"kind" in request &&
|
||||
(request.kind === "query" || request.kind === "action") &&
|
||||
"method" in request &&
|
||||
typeof request.method === "string" &&
|
||||
"pathTemplate" in request &&
|
||||
typeof request.pathTemplate === "string",
|
||||
);
|
||||
export const getPreviewNamedRequests = (definition: Record<string, unknown>): PreviewNamedRequest[] => {
|
||||
if (!definition.requests || typeof definition.requests !== "object" || Array.isArray(definition.requests)) return [];
|
||||
return Object.entries(definition.requests as Record<string, unknown>).flatMap(([id, candidate]) => {
|
||||
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) return [];
|
||||
const request = candidate as Record<string, unknown>;
|
||||
if (
|
||||
(request.kind !== "query" && request.kind !== "action") ||
|
||||
typeof request.method !== "string" ||
|
||||
typeof request.path !== "string"
|
||||
)
|
||||
return [];
|
||||
return [
|
||||
{
|
||||
id,
|
||||
kind: request.kind,
|
||||
method: request.method,
|
||||
path: request.path,
|
||||
minimumBoardPermission: typeof request.permission === "string" ? request.permission : undefined,
|
||||
},
|
||||
];
|
||||
});
|
||||
};
|
||||
|
||||
export const getPreviewLegacyMethods = (template: string): string[] => {
|
||||
const methods = new Set<string>();
|
||||
for (const match of template.matchAll(/<(?:SubFetch|ActionButton|ToggleSwitch)\b([^>]*)>/giu)) {
|
||||
const method = match[1]?.match(/\bmethod\s*=\s*["'](GET|POST|PUT|PATCH|DELETE)["']/iu)?.[1];
|
||||
methods.add(method?.toUpperCase() ?? (match[0].startsWith("<SubFetch") ? "GET" : "POST"));
|
||||
export type CustomWidgetAccessibilityIssue = "imageAlt" | "actionIconLabel" | "inputLabel";
|
||||
|
||||
export function analyzeCustomWidgetAccessibility(template: string): CustomWidgetAccessibilityIssue[] {
|
||||
const issues = new Set<CustomWidgetAccessibilityIssue>();
|
||||
const elements = template.matchAll(/<([A-Z][A-Za-z0-9.]*)\b([^>]*)>/gu);
|
||||
const inputComponents = new Set([
|
||||
"Autocomplete",
|
||||
"Checkbox",
|
||||
"ColorInput",
|
||||
"DateInput",
|
||||
"FileInput",
|
||||
"JsonInput",
|
||||
"MultiSelect",
|
||||
"NumberInput",
|
||||
"PasswordInput",
|
||||
"Radio",
|
||||
"Select",
|
||||
"Slider",
|
||||
"Switch",
|
||||
"TagsInput",
|
||||
"TextInput",
|
||||
"Textarea",
|
||||
"TimeInput",
|
||||
]);
|
||||
for (const match of elements) {
|
||||
const component = match[1] ?? "";
|
||||
const props = match[2] ?? "";
|
||||
if (component === "Image" && !hasProp(props, "alt")) issues.add("imageAlt");
|
||||
if (component === "ActionIcon" && !hasAnyProp(props, ["aria-label", "title"])) issues.add("actionIconLabel");
|
||||
if (inputComponents.has(component) && !hasAnyProp(props, ["label", "aria-label", "aria-labelledby"])) {
|
||||
issues.add("inputLabel");
|
||||
}
|
||||
}
|
||||
return [...methods];
|
||||
};
|
||||
return [...issues];
|
||||
}
|
||||
|
||||
function hasAnyProp(props: string, names: string[]) {
|
||||
return names.some((name) => hasProp(props, name));
|
||||
}
|
||||
|
||||
function hasProp(props: string, name: string) {
|
||||
const escapedName = name.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
||||
return new RegExp(`(?:^|\\s)${escapedName}\\s*=`, "u").test(props);
|
||||
}
|
||||
Reference in new issue
Block a user