);
};
/** A template field: text plus the variables menu, the rendered preview and the length bar. */
export const TemplateField = ({
label,
tip,
value,
onChange,
placeholder,
variables,
rendered,
min,
max,
multiline,
font,
maxPixels,
}: {
label: string;
tip?: string;
value: string;
onChange: (value: string) => void;
placeholder: string;
variables: VariableOption[];
rendered: string;
min: number;
max: number;
multiline?: boolean;
/** Measure the rendered text in this font against maxPixels, like Google does. */
font?: string;
maxPixels?: number;
}): JSX.Element => {
const id = useId();
const pixels = font ? textWidth(rendered, font) : undefined;
const insert = (token: string): void => {
const element = document.getElementById(id) as
| HTMLInputElement
| HTMLTextAreaElement
| null;
// An empty field prints its template: the variable goes into that, at
// its end, not in its place (a title that became just %focuskw%).
const text = value === '' ? placeholder : value;
const start =
value === '' ? text.length : (element?.selectionStart ?? text.length);
const end = value === '' ? start : (element?.selectionEnd ?? start);
const before = text.slice(0, start);
const after = text.slice(end);
onChange(
`${before && !before.endsWith(' ') ? `${before} ` : before}${token}${
after && !after.startsWith(' ') ? ` ${after}` : after
}`
);
requestAnimationFrame(() => element?.focus());
};
return (
{multiline ? (
);
};
export const TextField = ({
label,
tip,
value,
onChange,
placeholder,
type = 'text',
multiline,
warning,
}: {
label: string;
tip?: string;
value: string;
onChange: (value: string) => void;
placeholder?: string;
type?: 'text' | 'url';
multiline?: boolean;
/** A problem with the value, shown under the field. */
warning?: string;
}): JSX.Element => {
const id = useId();
return (