Files
twenty/packages/twenty-front-component-renderer/scripts/remote-dom/generators/remote-components.generator.ts
T
Raphaël Bosi 07be5e0892 Forward editing and clipboard events to front components (#22630)
Adds the text-editing events input-heavy front components need:
`beforeinput`, `compositionstart/update/end` and `copy/paste/cut`,
allowed on `input` and `textarea` only.

These events carry payload: `beforeinput` forwards `inputType`/`data`
through a native host listener (React synthesizes `onBeforeInput`
without them), composition events forward `data`, and paste forwards
`clipboardData.getData('text')` capped at 100k chars. Clipboard text is
read only on an explicit paste into the component's own input, never on
copy/cut, and the worker synthesizes a minimal `clipboardData` so
`onPaste` handlers work. `beforeinput` is observe-only: `preventDefault`
cannot cross the async worker boundary.

Allow-listing these events makes the host bind them, so the
`buildHostReactPropsFromRemoteProps` test that pinned them as rejected
now pins events that are still unmapped.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22630?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-07-21 14:21:53 +00:00

65 lines
1.8 KiB
TypeScript

import type { Project, SourceFile } from 'ts-morph';
import { isDefined } from 'twenty-shared/utils';
import { DOM_EVENT_TYPE_TO_REACT_PROP } from '../../../src/constants/DomEventTypeToReactProp';
import { type ComponentSchema } from './schemas';
import { addExportedConst } from './utils';
const generateComponentDefinition = (
sourceFile: SourceFile,
component: ComponentSchema,
): void => {
const hasEvents = component.events.length > 0;
let initializer: string;
if (hasEvents) {
const eventProps = component.events
.map((event) => {
const propName = DOM_EVENT_TYPE_TO_REACT_PROP[event];
if (!isDefined(propName)) {
throw new Error(
`Missing DOM_EVENT_TYPE_TO_REACT_PROP mapping for '${event}'`,
);
}
return ` ${propName}: { event: '${event}' },`;
})
.join('\n');
initializer = `createRemoteComponent('${component.customElementName}', ${component.name}Element, {
eventProps: {
${eventProps}
},
})`;
} else {
initializer = `createRemoteComponent('${component.customElementName}', ${component.name}Element)`;
}
addExportedConst(sourceFile, component.name, initializer);
};
export const generateRemoteComponents = (
project: Project,
components: ComponentSchema[],
): SourceFile => {
const sourceFile = project.createSourceFile('remote-components.ts', '', {
overwrite: true,
});
sourceFile.addImportDeclaration({
moduleSpecifier: '@remote-dom/react',
namedImports: ['createRemoteComponent'],
});
sourceFile.addImportDeclaration({
moduleSpecifier: './remote-elements',
namedImports: components.map((component) => `${component.name}Element`),
});
for (const component of components) {
generateComponentDefinition(sourceFile, component);
}
return sourceFile;
};