From bc2108933b330f4ad797feb0327e656854869964 Mon Sep 17 00:00:00 2001 From: Vladyslav Ilchenko Date: Thu, 4 Dec 2025 19:18:01 +0200 Subject: [PATCH 1/2] add spacer slot to align content --- components/list/demo/demo-list-nav.js | 7 +- components/list/demo/list-demo-scenarios.js | 4 +- .../list/list-item-expand-collapse-mixin.js | 2 +- components/list/list-item-generic-layout.js | 9 ++- components/list/list-item-mixin.js | 5 ++ components/list/list-item-nav-mixin.js | 3 + components/list/list.js | 64 ++++++++++++++++++- 7 files changed, 84 insertions(+), 10 deletions(-) diff --git a/components/list/demo/demo-list-nav.js b/components/list/demo/demo-list-nav.js index a38d0161317..535064d280d 100644 --- a/components/list/demo/demo-list-nav.js +++ b/components/list/demo/demo-list-nav.js @@ -45,9 +45,9 @@ class ListDemoNav extends LitElement { render() { return html`
- ${repeat(this.#list, (item) => item.key, (item) => this._renderItem(item))} @@ -149,8 +149,9 @@ class ListDemoNav extends LitElement { drop-nested label="${item.primaryText}" prevent-navigation> + ${item.hasIcon ? html`` : nothing} -
${item.hasIcon ? html`` : nothing}${item.primaryText}
+
${item.primaryText}
${item.tooltipOpenerText && item.tooltipText ? html`
${item.tooltipText}
` : nothing diff --git a/components/list/demo/list-demo-scenarios.js b/components/list/demo/list-demo-scenarios.js index 099f58adb77..326086de835 100644 --- a/components/list/demo/list-demo-scenarios.js +++ b/components/list/demo/list-demo-scenarios.js @@ -372,9 +372,9 @@ export const listDemos = { primaryText: 'Wetland Engineering #2', dropNested: true, items: [], - hasIcon: true + hasIcon: false }], - hasIcon: true + hasIcon: false }] }], }; diff --git a/components/list/list-item-expand-collapse-mixin.js b/components/list/list-item-expand-collapse-mixin.js index d4026c85c51..8543d57a0d2 100644 --- a/components/list/list-item-expand-collapse-mixin.js +++ b/components/list/list-item-expand-collapse-mixin.js @@ -91,7 +91,7 @@ export const ListItemExpandCollapseMixin = superclass => class extends SkeletonM updated(changedProperties) { if (changedProperties.has('_siblingHasNestedItems') || changedProperties.has('expandable')) { - this._renderExpandCollapseSlot = this.expandable || this._siblingHasNestedItems; + this._renderExpandCollapseSlot = this.expandable; } if (changedProperties.has('_draggingOver') && this._draggingOver && this.dropNested && !this.expanded && this.expandable) { let elapsedHoverTime = 0; diff --git a/components/list/list-item-generic-layout.js b/components/list/list-item-generic-layout.js index 4aabb238175..6ff27288e62 100644 --- a/components/list/list-item-generic-layout.js +++ b/components/list/list-item-generic-layout.js @@ -82,7 +82,8 @@ class ListItemGenericLayout extends LitElement { [color-start outside-control-end] minmax(0, min-content) [expand-collapse-start color-end] minmax(0, min-content) [control-start expand-collapse-end] minmax(0, min-content) - [control-end content-start] minmax(0, auto) + [spacer-start control-end] minmax(0, min-content) + [spacer-end content-start] minmax(0, auto) [content-end actions-start] minmax(0, min-content) [end actions-end]; grid-template-rows: @@ -144,8 +145,9 @@ class ListItemGenericLayout extends LitElement { grid-column: color-start / color-end; } - ::slotted([slot="before-content"]) { - grid-column: color-start / content-start; + ::slotted([slot="spacer"]) { + grid-column: spacer-start / spacer-end; + grid-row: 2 / 3; } ::slotted([slot="control-action"]) ~ ::slotted([slot="content"]), @@ -293,6 +295,7 @@ class ListItemGenericLayout extends LitElement { + diff --git a/components/list/list-item-mixin.js b/components/list/list-item-mixin.js index 507960e0e24..301de65d5b3 100644 --- a/components/list/list-item-mixin.js +++ b/components/list/list-item-mixin.js @@ -266,6 +266,10 @@ export const ListItemMixin = superclass => class extends composeMixins( padding-inline-start: 2.2rem; /* width of "control" slot set in generic-layout */ } + .d2l-list-item-spacer { + width: var(--d2l-list-item-spacer-width, 0); + } + [slot="content"] ::slotted([slot="illustration"]), [slot="content"] .d2l-list-item-illustration > * { border-radius: 6px; @@ -818,6 +822,7 @@ export const ListItemMixin = superclass => class extends composeMixins( ${this._renderExpandCollapse()}
${this.selectable ? html`
${this._renderCheckbox()}
` : nothing} +
${this.selectable || this.expandable ? html`
class extends ListItemLinkMixin(su .d2l-list-item-content ::slotted(*) { width: 100%; } + .d2l-list-item-content ::slotted([slot="illustration"]) { + width: auto; + } :host([current]) [slot="outside-control-container"] { background-color: var(--d2l-color-regolith); border: 3px solid var(--d2l-color-celestine); diff --git a/components/list/list.js b/components/list/list.js index 18c3ffb88eb..dcfa3b752da 100644 --- a/components/list/list.js +++ b/components/list/list.js @@ -164,6 +164,7 @@ class List extends PageableMixin(SelectionMixin(LitElement)) { this._breakpoint = 0; this._slimColor = false; this._width = 0; + this._spacerMeasurementScheduled = false; this._listChildrenUpdatedSubscribers = new SubscriberRegistryController(this, 'list-child-status', { onSubscribe: this._updateActiveSubscriber.bind(this), @@ -258,7 +259,7 @@ class List extends PageableMixin(SelectionMixin(LitElement)) { } getItems(slot) { - if (!this.shadowRoot) return; + if (!this.shadowRoot) return []; if (!slot) slot = this.shadowRoot.querySelector('slot:not([name])'); if (!slot) return []; return slot.assignedNodes({ flatten: true }).filter((node) => { @@ -334,6 +335,7 @@ class List extends PageableMixin(SelectionMixin(LitElement)) { return true; } }); + this._updateSpacerSlotWidth(); } setSelectionForAll(selected, selectAllPages) { @@ -360,6 +362,27 @@ class List extends PageableMixin(SelectionMixin(LitElement)) { return items.length > 0 ? items[0]._getFlattenedListItems().lazyLoadListItems : new Map(); } + _getSpacerWidth(item) { + const shadowRoot = item.shadowRoot; + if (!shadowRoot) return 0; + + const layout = shadowRoot.querySelector('d2l-list-item-generic-layout'); + const content = shadowRoot.querySelector('[slot="content"]'); + const spacer = shadowRoot.querySelector('[slot="spacer"]'); + if (!layout || !content || layout.offsetParent === null || content.offsetParent === null) return 0; + + const layoutRect = layout.getBoundingClientRect(); + const contentRect = content.getBoundingClientRect(); + const spacerWidth = spacer ? spacer.getBoundingClientRect().width : 0; + let totalWidth = Math.max(0, contentRect.left - layoutRect.left - spacerWidth); + + const illustrationSlot = content.querySelector('.d2l-list-item-illustration'); + const illustration = illustrationSlot?.assignedElements({ flatten: true })?.[0]; + totalWidth += this._measureWithMargins(illustration); + + return totalWidth; + } + _handleKeyDown(e) { if (!this.grid || this.slot === 'nested' || e.keyCode !== keyCodes.TAB) return; e.preventDefault(); @@ -394,6 +417,7 @@ class List extends PageableMixin(SelectionMixin(LitElement)) { this._childHasColor = aChildHasColor; this._childHasExpandCollapseToggle = aChildHasToggleEnabled; this._listChildrenUpdatedSubscribers.updateSubscribers(); + this._updateSpacerSlotWidth(); } _handleListItemPropertyChange(e) { @@ -491,6 +515,7 @@ class List extends PageableMixin(SelectionMixin(LitElement)) { }); this._updateItemLayouts(items); + this._updateSpacerSlotWidth(); /** @ignore */ this.dispatchEvent(new CustomEvent('d2l-list-item-showing-count-change', { @@ -499,6 +524,14 @@ class List extends PageableMixin(SelectionMixin(LitElement)) { })); } + _measureWithMargins(element) { + if (!element || element.offsetParent === null) return 0; + const rect = element.getBoundingClientRect(); + const styles = getComputedStyle(element); + const margin = (parseFloat(styles.marginInlineStart) || 0) + (parseFloat(styles.marginInlineEnd) || 0); + return rect.width + margin; + } + _updateActiveSubscriber(subscriber) { subscriber.updateSiblingHasChildren(this._childHasExpandCollapseToggle); subscriber.updateSiblingHasColor(this._childHasColor); @@ -518,6 +551,35 @@ class List extends PageableMixin(SelectionMixin(LitElement)) { items.forEach(item => item.layout = (this.layout === listLayouts.tiles ? 'tile' : 'normal')); } + _updateSpacerSlotWidth() { + if (this._spacerMeasurementScheduled) return; + this._spacerMeasurementScheduled = true; + + requestAnimationFrame(() => { + this._spacerMeasurementScheduled = false; + + const items = this.getItems() || []; + if (items.length === 0) return; + + let maxWidth = 0; + const measurements = []; + + for (const item of items) { + const totalWidth = this._getSpacerWidth(item); + if (totalWidth === 0) continue; + measurements.push({ item, width: totalWidth }); + if (totalWidth > maxWidth) maxWidth = totalWidth; + } + + if (measurements.length === 0) return; + + for (const { item, width } of measurements) { + if (!item?.style) continue; + item.style.setProperty('--d2l-list-item-spacer-width', `${Math.max(0, maxWidth - width)}px`); + } + }); + } + } customElements.define('d2l-list', List); From a9cb28613159cf5c7d0cf62ae224b73910fd26cf Mon Sep 17 00:00:00 2001 From: Vladyslav Ilchenko Date: Thu, 4 Dec 2025 19:50:57 +0200 Subject: [PATCH 2/2] rtl --- components/list/list.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/list/list.js b/components/list/list.js index dcfa3b752da..b8c4fdd5bfe 100644 --- a/components/list/list.js +++ b/components/list/list.js @@ -374,7 +374,9 @@ class List extends PageableMixin(SelectionMixin(LitElement)) { const layoutRect = layout.getBoundingClientRect(); const contentRect = content.getBoundingClientRect(); const spacerWidth = spacer ? spacer.getBoundingClientRect().width : 0; - let totalWidth = Math.max(0, contentRect.left - layoutRect.left - spacerWidth); + const isRtl = getComputedStyle(layout).direction === 'rtl'; + const inlineStartWidth = isRtl ? layoutRect.right - contentRect.right : contentRect.left - layoutRect.left; + let totalWidth = Math.max(0, inlineStartWidth - spacerWidth); const illustrationSlot = content.querySelector('.d2l-list-item-illustration'); const illustration = illustrationSlot?.assignedElements({ flatten: true })?.[0];