diff --git a/addons/web/static/src/fields/fields.js b/addons/web/static/src/fields/fields.js
new file mode 100644
index 00000000000..a21b13eadb0
--- /dev/null
+++ b/addons/web/static/src/fields/fields.js
@@ -0,0 +1,115 @@
+/** @odoo-module **/
+import { registry } from "@web/core/registry";
+import legacyFieldRegistry from "web.field_registry";
+import owlFieldRegistry from "web.field_registry_owl";
+import { ComponentAdapter } from "web.OwlCompatibility";
+import { useEffect } from "@web/core/utils/hooks";
+
+const { Component, tags, hooks } = owl;
+
+const fieldRegistry = registry.category("fields");
+
+function getFieldFromRegistry(registry, { fieldType, viewType, fieldName, fieldsDescription }) {
+ if (viewType && fieldType) {
+ const specificType = `${viewType}.${fieldType}`;
+ if (registry.contains(specificType)) {
+ return registry.get(specificType);
+ }
+ }
+ if (!registry.contains(fieldType)) {
+ const field = fieldsDescription[fieldName];
+ fieldType = field && field.type;
+ }
+ return registry.get(fieldType, null);
+}
+
+class Field extends Component {
+ static getTangibleField({ record, type, name }) {
+ const FieldClass = getFieldFromRegistry(fieldRegistry, {
+ fieldName: name,
+ fieldType: type,
+ viewType: record.viewtype,
+ fieldsDescription: record.fields,
+ });
+ return { FieldClass };
+ }
+
+ setup() {
+ const { record, type, name } = this.props;
+ this.fieldRef = hooks.useRef("fieldRef");
+ this.FieldComponent = Field.getTangibleField({ record, type, name }).FieldClass;
+ }
+}
+
+Field.template = tags.xml/* xml */ `
+
+`;
+
+class FieldSupportsLegacy extends Field {
+ static getTangibleField({ record, type, fieldName }) {
+ let { FieldClass } = super.getTangibleField(...arguments);
+ if (!FieldClass) {
+ FieldClass = getFieldFromRegistry(owlFieldRegistry, {
+ fieldName,
+ fieldType: type,
+ viewType: record.viewType,
+ fieldsDescription: record.fields,
+ });
+ if (FieldClass) {
+ return { FieldClass, isOwlLegacy: true };
+ } else {
+ FieldClass = getFieldFromRegistry(legacyFieldRegistry, {
+ fieldName,
+ fieldType: type,
+ viewType: record.viewType,
+ fieldsDescription: record.fields,
+ });
+ return { FieldClass, isLegacy: true };
+ }
+ }
+ return {};
+ }
+
+ setup() {
+ super.setup();
+ this.renderId = 0;
+ useEffect(() => {
+ this.renderId++;
+ });
+ if (!this.FieldComponent) {
+ this.env = owl.Component.env;
+ const { record, type, name } = this.props;
+ const { FieldClass, isOwlLegacy, isLegacy } = FieldSupportsLegacy.getTangibleField({
+ record,
+ type,
+ name,
+ });
+ this.FieldComponent = FieldClass;
+ this.isOwlLegacy = isOwlLegacy;
+ this.isLegacy = isLegacy;
+ }
+ }
+
+ get legacyProps() {
+ const legacyRecord = Object.assign({}, this.props.model._legacyRecord_);
+ legacyRecord.data = Object.assign({}, legacyRecord.data, this.props.record.data);
+ return {
+ fieldName: this.props.name,
+ record: legacyRecord,
+ };
+ }
+
+ get widgetArgs() {
+ const { fieldName, record } = this.legacyProps;
+ return [fieldName, record];
+ }
+}
+FieldSupportsLegacy.template = tags.xml/* xml */ `
+
+
+
+
+`;
+FieldSupportsLegacy.components = { ComponentAdapter };
+
+export { FieldSupportsLegacy as Field };
diff --git a/addons/web/static/tests/helpers/mock_env.js b/addons/web/static/tests/helpers/mock_env.js
index a89da1375df..ad999b66fb0 100644
--- a/addons/web/static/tests/helpers/mock_env.js
+++ b/addons/web/static/tests/helpers/mock_env.js
@@ -49,6 +49,7 @@ export function prepareRegistriesWithCleanup() {
cloneRegistryWithCleanup(registry.category("error_handlers"));
cloneRegistryWithCleanup(registry.category("command_provider"));
cloneRegistryWithCleanup(registry.category("view_widgets"));
+ cloneRegistryWithCleanup(registry.category("fields"));
cloneRegistryWithCleanup(registry.category("main_components"));