import * as React from "react"; export interface ControlProps { id?: string; className?: string; ariaLabel?: string; ariaHidden?: boolean; ariaDescribedBy?: string; role?: string; } export interface ContainerProps extends React.PropsWithChildren { } export function jsxLF(loc: string, ...rest: JSX.Element[]) { const indices: number[] = []; loc.replace(/\{\d\}/g, match => { indices.push(parseInt(match.substr(1, 1))); return match; }); const out: JSX.Element[] = []; let parts: string[]; let i = 0; for (const index of indices) { parts = loc.split(`{${index}}`); pxt.U.assert(parts.length === 2); out.push({parts[0]}); out.push({rest[index]}); loc = parts[1] } out.push({loc}); return out; } export function fireClickOnEnter(e: React.KeyboardEvent) { const charCode = (typeof e.which == "number") ? e.which : e.keyCode; if (charCode === 13 /* enter */ || charCode === 32 /* space */) { e.preventDefault(); e.currentTarget.click(); } } export function classList(...classes: (string | undefined)[]) { return classes .filter(c => typeof c === "string") .reduce((prev, c) => prev.concat(c.split(" ")), [] as string[]) .map(c => c.trim()) .filter(c => !!c) .join(" "); } export function nodeListToArray(list: NodeListOf): U[] { const out: U[] = []; for (const node of list) { out.push(node); } return out; } export enum CheckboxStatus { Selected, Unselected, Waiting } export interface ClientCoordinates { clientX: number; clientY: number; } export function clientCoord(ev: PointerEvent | MouseEvent | TouchEvent): ClientCoordinates { if ((ev as TouchEvent).touches) { const te = ev as TouchEvent; if (te.touches.length) { return te.touches[0]; } return te.changedTouches[0]; } return (ev as PointerEvent | MouseEvent); } export function screenToSVGCoord(ref: SVGSVGElement, coord: ClientCoordinates) { const screenCoord = ref.createSVGPoint(); screenCoord.x = coord.clientX; screenCoord.y = coord.clientY; return screenCoord.matrixTransform(ref.getScreenCTM().inverse()); } export function findNextFocusableElement(elements: HTMLElement[], focusedIndex: number, index: number, forward: boolean, isFocusable?: (e: HTMLElement) => boolean): HTMLElement { const increment = forward ? 1 : -1; const element = elements[index]; // in this case, there are no focusable elements if (focusedIndex === index) { return element; } if (isFocusable ? isFocusable(element) : isVisible(element)) { return element; } else { if (index + increment >= elements.length) { index = 0; } else if (index + increment < 0) { index = elements.length - 1; } else { index += increment; } } return findNextFocusableElement(elements, focusedIndex, index, forward, isFocusable); } function isVisible(e: HTMLElement): boolean { if ((e as any).checkVisibility) { return (e as any).checkVisibility({ visibilityProperty: true }); } const style = getComputedStyle(e); return style.display !== "none" && style.visibility !== "hidden"; } export function isFocusable(e: HTMLElement) { if (e) { return (e.getAttribute("data-isfocusable") === "true" || e.tabIndex !== -1) && getComputedStyle(e).display !== "none"; } else { return false; } } export function focusLastActive(el: HTMLElement) { while (el && !isFocusable(el)) { const toFocusParent = el.parentElement; if (toFocusParent) { el = toFocusParent; } else { break; } } el.focus(); }