diff --git a/src/Umbraco.Web.UI.Client/src/packages/elements/item/index.ts b/src/Umbraco.Web.UI.Client/src/packages/elements/item/index.ts
index fd536b101113..ee85c07b7286 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/elements/item/index.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/elements/item/index.ts
@@ -1 +1,2 @@
export { UmbElementItemRepository } from './repository/index.js';
+export * from './data-resolver/element-item-data-resolver.js';
diff --git a/src/Umbraco.Web.UI.Client/src/packages/ufm/components/element-name/element-name.component.ts b/src/Umbraco.Web.UI.Client/src/packages/ufm/components/element-name/element-name.component.ts
new file mode 100644
index 000000000000..4fa51bf7c612
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/ufm/components/element-name/element-name.component.ts
@@ -0,0 +1,14 @@
+import type { UfmToken } from '../../plugins/types.js';
+import { UmbUfmComponentBase } from '../ufm-component-base.js';
+
+import './element-name.element.js';
+
+export class UmbUfmElementNameComponent extends UmbUfmComponentBase {
+ render(token: UfmToken) {
+ if (!token.text) return;
+ const attributes = super.getAttributes(token.text);
+ return ``;
+ }
+}
+
+export { UmbUfmElementNameComponent as api };
diff --git a/src/Umbraco.Web.UI.Client/src/packages/ufm/components/element-name/element-name.element.ts b/src/Umbraco.Web.UI.Client/src/packages/ufm/components/element-name/element-name.element.ts
new file mode 100644
index 000000000000..23f253c70942
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/ufm/components/element-name/element-name.element.ts
@@ -0,0 +1,73 @@
+import { UmbUfmElementBase } from '../ufm-element-base.js';
+import { UMB_UFM_RENDER_CONTEXT } from '../ufm-render/ufm-render.context.js';
+import { customElement, property } from '@umbraco-cms/backoffice/external/lit';
+import { UmbElementItemDataResolver, UmbElementItemRepository } from '@umbraco-cms/backoffice/element';
+import { UmbId } from '@umbraco-cms/backoffice/id';
+
+@customElement('ufm-element-name')
+export class UmbUfmElementNameElement extends UmbUfmElementBase {
+ #repository?: UmbElementItemRepository;
+
+ @property()
+ alias?: string;
+
+ constructor() {
+ super();
+
+ this.consumeContext(UMB_UFM_RENDER_CONTEXT, (context) => {
+ this.observe(
+ context?.value,
+ async (value) => {
+ const temp =
+ this.alias && typeof value === 'object'
+ ? (value as Record)[this.alias]
+ : (value as unknown);
+
+ if (!temp) {
+ this.value = '';
+ return;
+ }
+
+ const uniques = this.#getUniques(temp);
+ this.value = await this.#getNames(uniques);
+ },
+ 'observeValue',
+ );
+ });
+ }
+
+ #getUniques(value: unknown) {
+ if (Array.isArray(value)) {
+ return value.map((x) => x.unique ?? x).filter((x) => UmbId.validate(x));
+ }
+ return typeof value === 'string' && UmbId.validate(value) ? [value] : [];
+ }
+
+ async #getNames(uniques?: Array) {
+ if (uniques?.length) {
+ if (!this.#repository) this.#repository = new UmbElementItemRepository(this);
+ const { data } = await this.#repository.requestItems(uniques);
+
+ if (Array.isArray(data) && data.length > 0) {
+ const namePromises = data.map(async (item) => {
+ const resolver = new UmbElementItemDataResolver(this);
+ resolver.setData(item);
+ const name = await resolver.getName();
+ resolver.destroy();
+ return name;
+ });
+ const names = await Promise.all(namePromises);
+ return names.filter(Boolean).join(', ');
+ }
+ }
+ return '';
+ }
+}
+
+export { UmbUfmElementNameElement as element };
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'ufm-element-name': UmbUfmElementNameElement;
+ }
+}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/ufm/components/element-name/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/ufm/components/element-name/manifests.ts
new file mode 100644
index 000000000000..77f1c9b3effe
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/packages/ufm/components/element-name/manifests.ts
@@ -0,0 +1,11 @@
+import type { ManifestUfmComponent } from '../../extensions/ufm-component.extension.js';
+
+export const manifests: Array = [
+ {
+ type: 'ufmComponent',
+ alias: 'Umb.Markdown.ElementName',
+ name: 'Element Name UFM Component',
+ api: () => import('./element-name.component.js'),
+ meta: { alias: 'umbElementName' },
+ },
+];
diff --git a/src/Umbraco.Web.UI.Client/src/packages/ufm/components/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/ufm/components/manifests.ts
index fc4dae305161..54c53d22791b 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/ufm/components/manifests.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/ufm/components/manifests.ts
@@ -1,4 +1,5 @@
import type { ManifestUfmComponent } from '../extensions/ufm-component.extension.js';
+import { manifests as elementNameManifests } from './element-name/manifests.js';
export const manifests: Array = [
{
@@ -22,6 +23,7 @@ export const manifests: Array = [
api: () => import('./content-name/content-name.component.js'),
meta: { alias: 'umbContentName' },
},
+ ...elementNameManifests,
{
type: 'ufmComponent',
alias: 'Umb.Markdown.Link',
diff --git a/src/Umbraco.Web.UI.Client/src/packages/ufm/plugins/marked-ufm.test.ts b/src/Umbraco.Web.UI.Client/src/packages/ufm/plugins/marked-ufm.test.ts
index ae9be9452b2e..7239496f0c58 100644
--- a/src/Umbraco.Web.UI.Client/src/packages/ufm/plugins/marked-ufm.test.ts
+++ b/src/Umbraco.Web.UI.Client/src/packages/ufm/plugins/marked-ufm.test.ts
@@ -2,6 +2,7 @@ import { expect } from '@open-wc/testing';
import { ufm } from './marked-ufm.plugin.js';
import { UmbMarked } from '../contexts/ufm.context.js';
import { UmbUfmContentNameComponent } from '../components/content-name/content-name.component.js';
+import { UmbUfmElementNameComponent } from '../components/element-name/element-name.component.js';
import { UmbUfmLabelValueComponent } from '../components/label-value/label-value.component.js';
import { UmbUfmLocalizeComponent } from '../components/localize/localize.component.js';
import { UmbUfmMemberNameComponent } from '../components/member-name/member-name.component.js';
@@ -27,7 +28,7 @@ describe('UmbMarkedUfm', () => {
ufm: '{umbContentName: contentPicker}',
expected: '',
},
- { ufm: '{umbMemberName:memberPicker}', expected: '' },
+ { ufm: '{umbElementName:elementPicker}', expected: '' },
{
ufm: '{umbMemberName: memberPicker}',
expected: '',
@@ -38,6 +39,7 @@ describe('UmbMarkedUfm', () => {
UmbMarked.use(
ufm([
{ alias: 'umbContentName', marker: '~', render: new UmbUfmContentNameComponent().render },
+ { alias: 'umbElementName', render: new UmbUfmElementNameComponent().render },
{ alias: 'umbValue', marker: '=', render: new UmbUfmLabelValueComponent().render },
{ alias: 'umbLocalize', marker: '#', render: new UmbUfmLocalizeComponent().render },
{ alias: 'umbMemberName', render: new UmbUfmMemberNameComponent().render },