Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91586e69ec |
+1
@@ -1 +1,2 @@
|
||||
export * from './media-picker-folder-path.element.js';
|
||||
export * from './media-picker-table-column-name.element.js';
|
||||
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
import { css, customElement, html, nothing, property } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
|
||||
import type { UmbTableColumn, UmbTableColumnLayoutElement, UmbTableItem } from '@umbraco-cms/backoffice/components';
|
||||
|
||||
export interface UmbMediaPickerTableColumnNameValue {
|
||||
name: string;
|
||||
ancestorPath?: string;
|
||||
/** When set, the name renders as a button that invokes this on click (e.g. to open a folder). */
|
||||
navigate?: () => void;
|
||||
}
|
||||
|
||||
@customElement('umb-media-picker-table-column-name')
|
||||
export class UmbMediaPickerTableColumnNameElement extends UmbLitElement implements UmbTableColumnLayoutElement {
|
||||
column!: UmbTableColumn;
|
||||
item!: UmbTableItem;
|
||||
|
||||
@property({ attribute: false })
|
||||
value!: UmbMediaPickerTableColumnNameValue;
|
||||
|
||||
#onClick(event: Event) {
|
||||
event.stopPropagation();
|
||||
this.value?.navigate?.();
|
||||
}
|
||||
|
||||
override render() {
|
||||
if (!this.value) return nothing;
|
||||
return html`
|
||||
${this.value.navigate
|
||||
? html`<uui-button look="default" compact label=${this.value.name} @click=${this.#onClick}
|
||||
>${this.value.name}</uui-button
|
||||
>`
|
||||
: html`<span class="name">${this.value.name}</span>`}
|
||||
${this.value.ancestorPath
|
||||
? html`<span class="ancestor-path">${this.value.ancestorPath}</span>`
|
||||
: nothing}
|
||||
`;
|
||||
}
|
||||
|
||||
static override styles = [
|
||||
UmbTextStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
uui-button {
|
||||
--uui-button-padding-left-factor: 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.name {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ancestor-path {
|
||||
display: block;
|
||||
font-size: 0.8em;
|
||||
opacity: 0.6;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
/* Truncate from the start so the deepest (most disambiguating) ancestor stays visible. */
|
||||
direction: rtl;
|
||||
text-align: left;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
export default UmbMediaPickerTableColumnNameElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-media-picker-table-column-name': UmbMediaPickerTableColumnNameElement;
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
import { UmbMediaPickerTableColumnNameElement } from './media-picker-table-column-name.element.js';
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { type UmbTestRunnerWindow, defaultA11yConfig } from '@umbraco-cms/internal/test-utils';
|
||||
|
||||
describe('UmbMediaPickerTableColumnNameElement', () => {
|
||||
let element: UmbMediaPickerTableColumnNameElement;
|
||||
|
||||
beforeEach(async () => {
|
||||
element = await fixture(html`<umb-media-picker-table-column-name></umb-media-picker-table-column-name>`);
|
||||
});
|
||||
|
||||
it('is defined with its own instance', () => {
|
||||
expect(element).to.be.instanceOf(UmbMediaPickerTableColumnNameElement);
|
||||
});
|
||||
|
||||
it('renders the name', async () => {
|
||||
element.value = { name: 'My image' };
|
||||
await element.updateComplete;
|
||||
expect(element.shadowRoot?.textContent).to.contain('My image');
|
||||
});
|
||||
|
||||
it('renders the ancestor path when provided', async () => {
|
||||
element.value = { name: 'My image', ancestorPath: 'Media / Holiday' };
|
||||
await element.updateComplete;
|
||||
const path = element.shadowRoot?.querySelector('.ancestor-path');
|
||||
expect(path).to.exist;
|
||||
expect(path?.textContent).to.contain('Media / Holiday');
|
||||
});
|
||||
|
||||
it('does not render an ancestor path when not provided', async () => {
|
||||
element.value = { name: 'My image' };
|
||||
await element.updateComplete;
|
||||
expect(element.shadowRoot?.querySelector('.ancestor-path')).to.not.exist;
|
||||
});
|
||||
|
||||
it('renders the name as a plain span when not navigable', async () => {
|
||||
element.value = { name: 'My image' };
|
||||
await element.updateComplete;
|
||||
expect(element.shadowRoot?.querySelector('uui-button')).to.not.exist;
|
||||
expect(element.shadowRoot?.querySelector('span.name')).to.exist;
|
||||
});
|
||||
|
||||
it('renders the name as a button and invokes navigate on click', async () => {
|
||||
let navigated = false;
|
||||
element.value = { name: 'A folder', navigate: () => (navigated = true) };
|
||||
await element.updateComplete;
|
||||
|
||||
const button = element.shadowRoot?.querySelector('uui-button');
|
||||
expect(button).to.exist;
|
||||
|
||||
button!.dispatchEvent(new MouseEvent('click', { bubbles: true, composed: true }));
|
||||
expect(navigated).to.be.true;
|
||||
});
|
||||
|
||||
it('stops the click from bubbling so it does not trigger row selection', async () => {
|
||||
element.value = { name: 'A folder', navigate: () => {} };
|
||||
await element.updateComplete;
|
||||
|
||||
const button = element.shadowRoot?.querySelector('uui-button');
|
||||
|
||||
let bubbledToHost = false;
|
||||
element.addEventListener('click', () => (bubbledToHost = true));
|
||||
button!.dispatchEvent(new MouseEvent('click', { bubbles: true, composed: true }));
|
||||
|
||||
expect(bubbledToHost).to.be.false;
|
||||
});
|
||||
|
||||
if ((window as UmbTestRunnerWindow).__UMBRACO_TEST_RUN_A11Y_TEST) {
|
||||
it('passes the a11y audit', async () => {
|
||||
element.value = { name: 'My image', ancestorPath: 'Media / Holiday' };
|
||||
await element.updateComplete;
|
||||
await expect(element).shadowDom.to.be.accessible(defaultA11yConfig);
|
||||
});
|
||||
}
|
||||
});
|
||||
+13
-20
@@ -7,6 +7,7 @@ import type { UmbMediaTreeItemModel, UmbMediaSearchItemModel, UmbMediaItemModel
|
||||
import { UmbMediaPickerContext } from './media-picker.context.js';
|
||||
import type { UmbMediaPathModel } from './types.js';
|
||||
import type { UmbMediaPickerFolderPathElement } from './components/media-picker-folder-path.element.js';
|
||||
import type { UmbMediaPickerTableColumnNameValue } from './components/media-picker-table-column-name.element.js';
|
||||
import type { UmbMediaPickerModalData, UmbMediaPickerModalValue } from './media-picker-modal.token.js';
|
||||
import {
|
||||
css,
|
||||
@@ -397,6 +398,11 @@ export class UmbMediaPickerModalElement extends UmbPickerModalBaseElement<
|
||||
return this.#folderTypeUniques.has(item.mediaType.unique) || item.hasChildren;
|
||||
}
|
||||
|
||||
#getAncestorPath(item: UmbMediaTreeItemModel | UmbMediaSearchItemModel): string {
|
||||
const ancestors = 'ancestors' in item ? item.ancestors : undefined;
|
||||
return ancestors?.length ? ancestors.map((a) => a.name || '(Untitled)').join(' / ') : '';
|
||||
}
|
||||
|
||||
// TODO: move to search manager in context
|
||||
#onSearchFromChange(e: CustomEvent) {
|
||||
const checked = (e.target as HTMLInputElement).checked;
|
||||
@@ -597,6 +603,11 @@ export class UmbMediaPickerModalElement extends UmbPickerModalBaseElement<
|
||||
// When not in selection mode, navigable items open on click (matching card behaviour).
|
||||
// Mark them as non-selectable so the row doesn't intercept the click for selection.
|
||||
const selectableInTable = this._isSelectionMode ? selectable : !canNavigate && selectable;
|
||||
const nameColumnValue: UmbMediaPickerTableColumnNameValue = {
|
||||
name: item.name,
|
||||
ancestorPath: this.#getAncestorPath(item) || undefined,
|
||||
navigate: canNavigate && !this._isSelectionMode ? () => this.#onOpen(item) : undefined,
|
||||
};
|
||||
return {
|
||||
id: item.unique,
|
||||
icon: item.mediaType.icon,
|
||||
@@ -604,19 +615,7 @@ export class UmbMediaPickerModalElement extends UmbPickerModalBaseElement<
|
||||
data: [
|
||||
{
|
||||
columnAlias: 'name',
|
||||
value:
|
||||
canNavigate && !this._isSelectionMode
|
||||
? html`<uui-button
|
||||
look="default"
|
||||
compact
|
||||
label=${item.name}
|
||||
@click=${(e: Event) => {
|
||||
e.stopPropagation();
|
||||
this.#onOpen(item);
|
||||
}}
|
||||
>${item.name}</uui-button
|
||||
>`
|
||||
: html`<span class="table-name">${item.name}</span>`,
|
||||
value: nameColumnValue,
|
||||
},
|
||||
{
|
||||
columnAlias: 'createDate',
|
||||
@@ -644,6 +643,7 @@ export class UmbMediaPickerModalElement extends UmbPickerModalBaseElement<
|
||||
{
|
||||
name: this.localize.term('general_name'),
|
||||
alias: 'name',
|
||||
elementName: 'umb-media-picker-table-column-name',
|
||||
},
|
||||
{
|
||||
name: this.localize.term('content_createDate'),
|
||||
@@ -743,13 +743,6 @@ export class UmbMediaPickerModalElement extends UmbPickerModalBaseElement<
|
||||
margin-top: var(--uui-size-layout-1);
|
||||
}
|
||||
|
||||
.table-name {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#view-dropdown {
|
||||
padding: var(--uui-size-space-3);
|
||||
--uui-button-content-align: left;
|
||||
|
||||
Reference in New Issue
Block a user