diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/field-dropdown-list/field-dropdown-list.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/field-dropdown-list/field-dropdown-list.element.ts new file mode 100644 index 0000000000..f469b3953d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/field-dropdown-list/field-dropdown-list.element.ts @@ -0,0 +1,216 @@ +import type { UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type'; +import { UmbDocumentTypeDetailRepository } from '@umbraco-cms/backoffice/document-type'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; +import { + css, + html, + customElement, + property, + state, + repeat, + ifDefined, + nothing, + query, +} from '@umbraco-cms/backoffice/external/lit'; +import type { UUIComboboxEvent, UUIComboboxElement } from '@umbraco-cms/backoffice/external/uui'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { UmbMediaTypeDetailRepository } from '@umbraco-cms/backoffice/media-type'; +import { + UMB_DOCUMENT_TYPE_PICKER_MODAL, + UMB_MEDIA_TYPE_PICKER_MODAL, + UMB_MODAL_MANAGER_CONTEXT, + type UmbModalManagerContext, +} from '@umbraco-cms/backoffice/modal'; + +interface FieldPickerValue { + alias: string; + label: string; +} + +enum FieldType { + MEDIA_TYPE = 'media-type', + DOCUMENT_TYPE = 'document-type', + SYSTEM = 'system', +} + +@customElement('umb-field-dropdown-list') +export class UmbFieldDropdownListElement extends UmbLitElement { + @property({ type: Boolean, attribute: 'exclude-media-type', reflect: true }) + public excludeMediaType = false; + + private _value: FieldPickerValue | undefined; + @property({ type: Object }) + public get value(): FieldPickerValue | undefined { + return this._value; + } + public set value(val: FieldPickerValue | undefined) { + const oldVal = this._value; + this._value = val; + this.requestUpdate('value', oldVal); + this.dispatchEvent(new UmbChangeEvent()); + } + + @state() + private _type?: FieldType; + + @state() + private _uniqueName?: string; + + @state() + private _unique?: string; + + @query('#value') + private _valueElement?: UUIComboboxElement; + + #documentTypeDetailRepository = new UmbDocumentTypeDetailRepository(this); + #mediaTypeDetailRepository = new UmbMediaTypeDetailRepository(this); + #modalManager?: UmbModalManagerContext; + + @state() + private _customFields: Array> = []; + + private _systemFields: Array> = [ + { alias: 'sortOrder', name: this.localize.term('general_sort') }, + { alias: 'updateDate', name: this.localize.term('content_updateDate') }, + { alias: 'updater', name: this.localize.term('content_updatedBy') }, + { alias: 'createDate', name: this.localize.term('content_createDate') }, + { alias: 'owner', name: this.localize.term('content_createBy') }, + { alias: 'published', name: this.localize.term('content_isPublished') }, + { alias: 'contentTypeAlias', name: this.localize.term('content_documentType') }, + ]; + + constructor() { + super(); + this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (modalManager) => { + this.#modalManager = modalManager; + }); + } + + async #getDocumentTypeFields() { + if (!this.#modalManager) return; + const modalContext = this.#modalManager.open(UMB_DOCUMENT_TYPE_PICKER_MODAL, { + data: { + hideTreeRoot: true, + multiple: false, + }, + }); + + const modalValue = await modalContext.onSubmit(); + const unique = modalValue.selection[0] ?? ''; + + const { data } = await this.#documentTypeDetailRepository.requestByUnique(unique); + if (!data) return; + + this._unique = data.unique; + this._uniqueName = data.name; + this._customFields = data.properties; + } + + async #getMediaTypeFields() { + if (!this.#modalManager) return; + const modalContext = this.#modalManager.open(UMB_MEDIA_TYPE_PICKER_MODAL, { + data: { + hideTreeRoot: true, + multiple: false, + }, + }); + + const modalValue = await modalContext.onSubmit(); + const unique = modalValue.selection[0] ?? ''; + + const { data } = await this.#mediaTypeDetailRepository.requestByUnique(unique); + if (!data) return; + + this._unique = data.unique; + this._uniqueName = data.name; + this._customFields = data.properties; + } + + #onChange(e: UUIComboboxEvent) { + this._type = (e.composedPath()[0] as UUIComboboxElement).value as FieldType; + this.value = undefined; + if (this._valueElement) this._valueElement.value = ''; + + switch (this._type) { + case FieldType.DOCUMENT_TYPE: + this.#getDocumentTypeFields(); + break; + case FieldType.MEDIA_TYPE: + this.#getMediaTypeFields(); + break; + default: + this._uniqueName = ''; + this._unique = ''; + this._customFields = this._systemFields; + break; + } + } + + #onChangeValue(e: UUIComboboxEvent) { + e.stopPropagation(); + const alias = (e.composedPath()[0] as UUIComboboxElement).value as FieldType; + this.value = this._customFields.find((field) => field.alias === alias) as FieldPickerValue; + } + + render() { + return html` + + + + ${this.localize.term('formSettings_systemFields')} + + + ${this.localize.term('content_documentType')} + ${this.localize.term('defaultdialogs_treepicker')} + + ${!this.excludeMediaType + ? html` + ${this.localize.term('content_mediatype')} + ${this.localize.term('defaultdialogs_treepicker')} + ` + : nothing} + + + ${this.#renderAliasDropdown()} + `; + } + + #renderAliasDropdown() { + if (this._type !== FieldType.SYSTEM && !this._unique) return; + return html`${this._uniqueName} + + + ${repeat( + this._customFields, + (field) => field.alias, + (field) => + html`${field.alias}`, + )} + + `; + } + + static styles = [ + css` + uui-combobox { + width: 100%; + } + strong { + display: block; + } + uui-combobox-list-option { + padding: calc(var(--uui-size-2, 6px) + 1px); + } + `, + ]; +} + +export default UmbFieldDropdownListElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-field-dropdown-list': UmbFieldDropdownListElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/field-dropdown-list/field-dropdown-list.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/field-dropdown-list/field-dropdown-list.stories.ts new file mode 100644 index 0000000000..f9e84c4665 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/field-dropdown-list/field-dropdown-list.stories.ts @@ -0,0 +1,17 @@ +import type { Meta, StoryObj } from '@storybook/web-components'; +import './field-dropdown-list.element.js'; +import type { UmbFieldDropdownListElement } from './field-dropdown-list.element.js'; + +const meta: Meta = { + title: 'Components/Inputs/Field Dropdown List', + component: 'umb-field-dropdown-list', +}; + +export default meta; +type Story = StoryObj; + +export const Overview: Story = { + args: { + excludeMediaType: false, + }, +}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/field-dropdown-list/field-dropdown-list.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/field-dropdown-list/field-dropdown-list.test.ts new file mode 100644 index 0000000000..2f741562a7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/field-dropdown-list/field-dropdown-list.test.ts @@ -0,0 +1,20 @@ +import { expect, fixture, html } from '@open-wc/testing'; +import { UmbFieldDropdownListElement } from './field-dropdown-list.element.js'; +import { type UmbTestRunnerWindow, defaultA11yConfig } from '@umbraco-cms/internal/test-utils'; +describe('UmbInputDateElement', () => { + let element: UmbFieldDropdownListElement; + + beforeEach(async () => { + element = await fixture(html` `); + }); + + it('is defined with its own instance', () => { + expect(element).to.be.instanceOf(UmbFieldDropdownListElement); + }); + + if ((window as UmbTestRunnerWindow).__UMBRACO_TEST_RUN_A11Y_TEST) { + it('passes the a11y audit', async () => { + await expect(element).shadowDom.to.be.accessible(defaultA11yConfig); + }); + } +}); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/field-dropdown-list/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/field-dropdown-list/index.ts new file mode 100644 index 0000000000..2864ccf8fc --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/field-dropdown-list/index.ts @@ -0,0 +1 @@ +export * from './field-dropdown-list.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/index.ts index dc4b249b61..ea96ecaaaa 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/index.ts @@ -7,6 +7,7 @@ export * from './code-block/index.js'; export * from './dropdown/index.js'; export * from './entity-actions-bundle/index.js'; export * from './extension-slot/index.js'; +export * from './field-dropdown-list/index.js'; export * from './footer-layout/index.js'; export * from './header-app/index.js'; export * from './history/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/modals/templating-page-field-builder/templating-page-field-builder-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/modals/templating-page-field-builder/templating-page-field-builder-modal.element.ts index 37afe148ca..da381042ae 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/modals/templating-page-field-builder/templating-page-field-builder-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/modals/templating-page-field-builder/templating-page-field-builder-modal.element.ts @@ -6,6 +6,7 @@ import type { import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; +import type { UmbFieldDropdownListElement } from '@umbraco-cms/backoffice/components'; @customElement('umb-templating-page-field-builder-modal') export class UmbTemplatingPageFieldBuilderModalElement extends UmbModalBaseElement< @@ -36,6 +37,10 @@ export class UmbTemplatingPageFieldBuilderModalElement extends UmbModalBaseEleme /** TODO: Implement "Choose field" */ + #onChangeFieldValue(e: Event) { + this._field = (e.target as UmbFieldDropdownListElement).value?.alias; + } + render() { return html` @@ -44,7 +49,7 @@ export class UmbTemplatingPageFieldBuilderModalElement extends UmbModalBaseEleme Choose field - (Not implemented yet) + Default value