Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
cdd470f
Refactor into ViewModel
brianjhanson Jul 17, 2026
384c390
Consolidate into ContentIndexViewModel
brianjhanson Jul 17, 2026
88f0e3f
Extract shared element index page pipeline
brianjhanson Jul 17, 2026
35df770
Initial rendering of Asset index page
brianjhanson Jul 17, 2026
8cc3635
Remove page param
brianjhanson Jul 17, 2026
8930f9e
Cleanup rendering a bit
brianjhanson Jul 17, 2026
01a7c5b
Cleanup rendering a bit
brianjhanson Jul 17, 2026
32876bd
Update card grid layout
brianjhanson Jul 20, 2026
9d5b622
Merge remote-tracking branch 'origin/6.x' into feature/inertia-asset-…
brianjhanson Jul 20, 2026
1da02d4
Merge branch 'feature/inertia-asset-index' of github.com:craftcms/cms…
brianjhanson Jul 20, 2026
e9d794a
Merge remote-tracking branch 'origin/6.x' into feature/inertia-asset-…
brianjhanson Jul 21, 2026
6f04c7e
Load plugins after the database is booted
brianjhanson Jul 21, 2026
ac69763
Include asset folders and thumb data in the content index payload
brianjhanson Jul 21, 2026
fa087b4
Add asset folder navigation, breadcrumbs, and thumbnail view
brianjhanson Jul 21, 2026
024a5ff
Fix actionUrls
brianjhanson Jul 21, 2026
f10bcb2
Mark actions as non-bulk
brianjhanson Jul 21, 2026
79cf24d
Render the new tooltip from craft-element-label
brianjhanson Jul 22, 2026
6c2f3e9
Add a craft-truncate component
brianjhanson Jul 22, 2026
318dad5
Replace craft-element-label with craft-truncate
brianjhanson Jul 22, 2026
c492d1d
Use craft-truncate for thumbnail labels
brianjhanson Jul 22, 2026
196d201
Better table overflow
brianjhanson Jul 22, 2026
229c283
Quick thumbnail cleanup
brianjhanson Jul 22, 2026
8c2cd99
Always visible checkbox
brianjhanson Jul 22, 2026
4d2d1a0
Include folder and breadcrumb move targets in the asset index payload
brianjhanson Jul 22, 2026
bddb37b
Register the active element index table for cross-component access
brianjhanson Jul 22, 2026
1545793
Add drag-and-drop moving of assets into folders
brianjhanson Jul 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/craftcms-legacy/cp/src/Craft.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,5 +98,8 @@ import './js/UriFormatGenerator.js';
import './js/UserIndex.js';

// Custom elements
// craft-element-label is superseded by @craftcms/ui's craft-truncate, but keep
// it registered for backwards compatibility (e.g. plugins / the yii2 adapter).
import '@craftcms/ui/components/truncate/truncate';
import './js/CraftElementLabel';
import './js/CraftProxyScrollbar';
8 changes: 4 additions & 4 deletions packages/craftcms-legacy/cp/src/js/BaseElementSelectInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -228,18 +228,18 @@ Craft.BaseElementSelectInput = Garnish.Base.extend(
onBeforeDragStart: () => {
this.elementEditor?.pause();

// Disable all craft-element-labels so connectedCallback()
// doesn't get fired constantly during drag
// Disable the labels' overflow tooltips while dragging so they
// don't recompute/pop up during the drag.
this.$elementsContainer
.find('craft-element-label')
.find('craft-truncate')
.attr('disabled', true);
},
onDragStop: () => {
this.elementEditor?.resume();

// Put things back where we found them.
this.$elementsContainer
.find('craft-element-label')
.find('craft-truncate')
.removeAttr('disabled');
},
onSortChange: () => {
Expand Down
48 changes: 26 additions & 22 deletions packages/craftcms-legacy/cp/src/js/CraftElementLabel.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
/** global: $ */
/** global: jQuery */

// Fallback for generating a unique invoker id when the element itself has none.
let elementLabelTooltipId = 0;

/**
* Element label
*
Expand Down Expand Up @@ -56,7 +59,9 @@ class CraftElementLabel extends HTMLElement {
}

update() {
this.desiredWidth = this.calculateWidth(this.innerText);
// Measure the label link's text, not `this.innerText`, so the tooltip's
// own (light-DOM) text content can't skew the overflow calculation.
this.desiredWidth = this.calculateWidth(this.labelLink.innerText);
this.hasOverflow = this.desiredWidth > this.scrollWidth;

// If the label has an overflow, add a tooltip
Expand All @@ -75,38 +80,37 @@ class CraftElementLabel extends HTMLElement {
}

createTooltip() {
this.tooltip = document.createElement('craft-tooltip');
this.tooltip.setAttribute('self-managed', 'true');
this.tooltip.setAttribute('text', this.innerText);
this.tooltip.setAttribute('aria-hidden', 'true');

// Make sure tooltips created show ellipses
Object.assign(this.tooltip.style, {
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
});
const labelLink = this.labelLink;

// The new craft-tooltip references its invoker (the label link) by id
// rather than wrapping it, so make sure the link has one. The CSS on
// `.label-link` already handles the visual ellipsis truncation.
if (!labelLink.id) {
labelLink.id = this.id
? `${this.id}-link`
: `element-label-link-${elementLabelTooltipId++}`;
}

// If there's a context label, make it a little nicer
// The tooltip shows the full, untruncated label text; the tooltip content
// lives in its default slot. Present a context label (e.g. a draft name)
// in parentheses, matching the inline label.
let text = labelLink.innerText;
const contextLabel = this.querySelector('.context-label');
if (contextLabel) {
this.tooltip.innerText = this.tooltip.innerText.replace(
text = text.replace(
contextLabel.innerText,
` (${contextLabel.innerText})`
);
}

this.insertBefore(this.tooltip, this.labelLink);
this.tooltip.appendChild(this.labelLink);
this.tooltip = document.createElement('craft-tooltip');
this.tooltip.setAttribute('for', labelLink.id);
this.tooltip.textContent = text;

this.appendChild(this.tooltip);
}

disconnectedCallback() {
// put the .label-link back into <craft-element-label>
// so that when connectedCallback() is called after insetBefore/insertAfter
// everything can get initialised as expected
// we can't use Element.moveBefore/Element.moveAfter as those are experimental at the moment and not available in Safari & FF
this.append(this.labelLink);

this.tooltip?.remove();
if (this.$tabs?.length) {
this.$tabs.data('tabs')?.off('selectTab');
Expand Down
4 changes: 2 additions & 2 deletions packages/craftcms-legacy/cp/src/js/ElementEditorSlideout.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ Craft.ElementEditorSlideout = Craft.CpScreenSlideout.extend(
if (e?.response?.data?.invalidNestedElementIds) {
const $cards = this.$content.find('.element.card').removeClass('error');
$cards
.find('craft-element-label > span[data-icon="triangle-exclamation"]')
.find('craft-truncate > span[data-icon="triangle-exclamation"]')
.remove();
if (e.response.data.invalidNestedElementIds.length) {
const $errorCards = $cards
Expand All @@ -186,7 +186,7 @@ Craft.ElementEditorSlideout = Craft.CpScreenSlideout.extend(
)
.addClass('error');
for (let i = 0; i < $errorCards.length; i++) {
const $label = $errorCards.eq(i).find('craft-element-label');
const $label = $errorCards.eq(i).find('craft-truncate');
$('<span/>', {
'data-icon': 'triangle-exclamation',
'aria-label': Craft.t('app', 'Error'),
Expand Down
2 changes: 1 addition & 1 deletion packages/craftcms-legacy/cp/src/js/TagSelectInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ Craft.TagSelectInput = Craft.BaseElementSelectInput.extend(
class: 'chip-content',
}).appendTo($element);

const $titleContainer = $('<craft-element-label/>', {
const $titleContainer = $('<craft-truncate/>', {
class: 'label',
}).appendTo($chipContent);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export default class CraftBreadcrumbItem extends LitElement {
:host {
white-space: nowrap;
display: inline-flex;
gap: var(--c-spacing-sm);
align-items: center;
color: inherit;
}
Expand All @@ -36,11 +37,11 @@ export default class CraftBreadcrumbItem extends LitElement {
text-decoration: underline;
}

slot[name='start']::slotted(*) {
slot[name='prefix']::slotted(*) {
margin-inline-end: var(--c-spacing-sm);
}

slot[name='end']::slotted(*) {
slot[name='suffix']::slotted(*) {
margin-inline-start: var(--c-spacing-sm);
}

Expand All @@ -63,9 +64,9 @@ export default class CraftBreadcrumbItem extends LitElement {
: html`<span part="label" class="label"><slot></slot></span>`;

return html`
<slot name="start" part="start"></slot>
<slot name="prefix" part="prefix"></slot>
${label}
<slot name="end" part="end"></slot>
<slot name="suffix" part="suffix"></slot>
<slot name="separator" part="separator" aria-hidden="true"></slot>
`;
}
Expand Down
14 changes: 12 additions & 2 deletions packages/craftcms-ui/src/components/chip/chip.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ export default css`
display: contents;
}

::slotted([slot='status']) {
display: inline-flex;
}

.cp-chip {
--_min-height: var(--c-chip-height, none);
--_thumb-size: calc(24rem / 16);
Expand Down Expand Up @@ -72,19 +76,25 @@ export default css`
}

.cp-chip__prefix {
position: relative;
padding-inline-end: var(--c-spacing-sm);
display: flex;
align-items: center;
flex-direction: row;
flex-wrap: nowrap;
}

.cp-chip__suffix {
padding-inline-start: var(--c-spacing-md);
}

.cp-chip__indicator {
.cp-chip__status {
display: inline-flex;
padding-inline: var(--c-spacing-sm);
padding-inline: var(--c-spacing-xs);
}

.cp-chip__thumbnail {
position: relative;
padding: var(--c-spacing-sm);
/*border-radius: calc(var(--_radius) - var(--c-spacing-xs));*/
}
Expand Down
36 changes: 23 additions & 13 deletions packages/craftcms-ui/src/components/chip/chip.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {property} from 'lit/decorators.js';
import type {CSSResultGroup, PropertyValueMap, PropertyValues} from 'lit';
import type {CSSResultGroup, PropertyValues} from 'lit';
import {html, LitElement, nothing} from 'lit';
import styles from './chip.styles.js';
import {classMap} from 'lit/directives/class-map.js';
Expand Down Expand Up @@ -52,25 +52,30 @@ export default class CraftChip extends LitElement {
/** Shortcut for adding an icon as the prefix */
@property() icon: string | null = null;

@property({attribute: 'show-indicators', type: Boolean})
showIndicators: boolean = false;
@property({attribute: 'show-status', type: Boolean})
showStatus: boolean = false;
@property({attribute: 'show-thumb', type: Boolean}) showThumb: boolean =
false;
@property({type: Boolean}) selectable: boolean = false;

#thumbLoader = new ThumbnailLoader();

renderPrefix() {
const hasThumb = !!this.querySelector('[slot="thumbnail"]');
const hasIndicator = !!this.querySelector('[slot="indicator"]');

return html` <div class="cp-chip__prefix" part="prefix">
return html`<div class="cp-chip__prefix" part="prefix">
<slot name="prefix">
${hasThumb
${this.showThumb
? html`<slot class="cp-chip__thumbnail" name="thumbnail"></slot>`
: nothing}
${hasIndicator
? html`<slot class="cp-chip__indicator" name="indicator"></slot>`
${this.icon
? html`<slot class="cp-chip__icon" name="icon"
><craft-icon name="${this.icon}"></craft-icon
></slot>`
: nothing}
${this.showStatus
? html`<slot class="cp-chip__status" name="status"></slot>`
: nothing}
<slot class="cp-chip__icon" name="icon">
${this.icon
? html` <craft-icon name="${this.icon}"></craft-icon>`
: nothing}
</slot>
</slot>
</div>`;
}
Expand Down Expand Up @@ -99,8 +104,13 @@ export default class CraftChip extends LitElement {
'cp-chip--medium': this.size === 'medium',
'cp-chip--large': this.size === 'large',
'cp-chip--plain': this.appearance === Appearance.Plain,
'cp-chip--selectable': this.selectable,
'cp-chip--show-thumb': this.showThumb,
'cp-chip--show-indicators': this.showIndicators,
'cp-chip--show-status': this.showStatus,
})}"
>
${this.selectable ? html` <input type="checkbox" />` : nothing}
${renderPrefix ? this.renderPrefix() : nothing}
<div class="cp-chip__body">
<slot></slot>
Expand Down
4 changes: 4 additions & 0 deletions packages/craftcms-ui/src/components/indicator/indicator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ export default class CraftIndicator extends LitElement {
static override styles = [
variantsStyles,
css`
:host {
display: contents;
}

.indicator {
--_fill: var(--fill, var(--c-color-fill-loud));
--_size: var(--size, 0.5em);
Expand Down
61 changes: 61 additions & 0 deletions packages/craftcms-ui/src/components/truncate/truncate.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import type {Meta, StoryObj} from '@storybook/web-components-vite';

import {html} from 'lit';

import './truncate.js';

const meta = {
title: 'Components/Truncate',
component: 'craft-truncate',
args: {
width: '200px',
content:
'This is a fairly long piece of text that will be truncated when it does not fit',
placement: 'top',
},
argTypes: {
width: {control: {type: 'text'}},
content: {control: {type: 'text'}},
placement: {
control: {type: 'select'},
options: [
'top-start',
'top',
'top-end',
'right-start',
'right',
'right-end',
'bottom-end',
'bottom',
'bottom-start',
'left-end',
'left',
'left-start',
],
},
},
parameters: {
layout: 'centered',
},
render: (args) => html`
<div
style="width: ${args.width}; border: 1px dashed var(--c-color-border-quiet, #ccc);"
>
<craft-truncate placement="${args.placement}"
>${args.content}</craft-truncate
>
</div>
`,
} satisfies Meta<any>;

export default meta;
type Story = StoryObj<any>;

export const Playground: Story = {};

export const FitsWithoutTooltip: Story = {
args: {
width: '400px',
content: 'Short label',
},
};
17 changes: 17 additions & 0 deletions packages/craftcms-ui/src/components/truncate/truncate.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {css} from 'lit';

export default css`
:host {
display: inline-flex;
/* Allow the element to shrink below its content size in flex/grid layouts
so the text actually truncates instead of forcing the container wider. */
min-width: 0;
}

.truncate {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
`;
Loading
Loading