-
Notifications
You must be signed in to change notification settings - Fork 27
[looking for input] feat: List item draggable: Support interactive content in items #5742
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Thanks for the PR! 🎉 We've deployed an automatic preview for this PR - you can see your changes here:
Note The build needs to finish before your changes are deployed. |
components/list/list-item-mixin.js
Outdated
const draggableContentAreaContet = renderDraggable ? html` | ||
<div slot="content" | ||
class="d2l-list-item-content" | ||
id="${this._contentId}" | ||
@mouseenter="${this._onMouseEnter}" | ||
@mouseleave="${this._onMouseLeave}" | ||
style="${styleMap({ paddingInlineStart: this._contentPaddingInlineStart })}"> | ||
<slot name="illustration" class="d2l-list-item-illustration">${illustration}</slot> | ||
<slot>${content}</slot> | ||
</div> | ||
` : nothing; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just so I can add the styleMap
to the content. Ignore for now - I'll clean this up more so that there can be only one content block.
@@ -773,7 +791,8 @@ export const ListItemMixin = superclass => class extends composeMixins( | |||
@mouseleave="${this._onMouseLeavePrimaryAction}"> | |||
${primaryAction} | |||
</div>` : nothing} | |||
${!this._listItemInteractiveEnabled || (!primaryAction && !renderExpandableActionContent && !renderCheckboxActionContent) ? contentAreaContent : nothing} | |||
${renderDraggable ? html`<div slot="content" class="d2l-draggable-content-hidden" style="${styleMap({ width: this._hiddenContentWidth })}"></div>` : nothing} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The general strategy is that this renders a content slot when draggable (which wouldn't have been rendered here) in order to be able to properly have the grid size all the other grid columns.
What happens is:
- after initial render, this hidden content gets its width set to that of the actual visible content
this triggers the grid to update sizes and we get theoffsetLeft
of the hidden content- we set the
padding-inline-start
of the visible content tothe value of thea value dependant on if expandable and/or color slots are present.offsetLeft
of the hidden content (since it's in theoutside-content-action
slot which is full list item width)
This does generally trigger another update of the hidden content width because the width of the content gets updated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the extra rendering is the opposite direction I was hoping list-items to go. 😢
components/list/list-item-mixin.js
Outdated
@@ -744,7 +762,7 @@ export const ListItemMixin = superclass => class extends composeMixins( | |||
<div slot="before-content"></div> | |||
${this._renderDropTarget()} | |||
${this._renderDragHandle(this._renderOutsideControl)} | |||
${this._renderDragTarget(this.dragTargetHandleOnly ? this._renderOutsideControlHandleOnly : this._renderOutsideControlAction)} | |||
${this._renderDragTarget(this.dragTargetHandleOnly ? this._renderOutsideControlHandleOnly : this._renderOutsideControlAction, renderDraggable ? draggableContentAreaContet : null)} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This otherwise follows the same strategy as used in the other clickable places - the content is rendered within the clickable draggable area in order to be able to click the interactive elements.
if (!hiddenItem) return; | ||
|
||
const offsetLeft = hiddenItem.offsetLeft; | ||
this._contentPaddingInlineStart = this.#isRTL() ? `${-1 * offsetLeft}px` : `${offsetLeft}px`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rtl on the demo page is entering an infinite loop which sets the padding to 0 and back to the expected value.
} | ||
|
||
#updateContentPadding() { | ||
setTimeout(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
using setTimeout
rather than requestAnimationFrame
seemed to allow enough time for things to be sized such that we can avoid an extra render.
this._hiddenContentWidth = `${width}px`; | ||
}); | ||
|
||
this._contentResizeObserver.observe(content); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is some tweaking that can be done such that after firstUpdated
we can get the padding and width and avoid the resize observer being necessary initially.
However, in practice I found that there were a lot of cases where things sized too late for that and we still needed the resized observer to get the spacing all correct.
components/list/list-item-mixin.js
Outdated
_hiddenContentWidth: { state: true }, | ||
_contentPaddingInlineStart: { state: true }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These could get moved to drag-drop-mixin.
[slot="outside-control-action"] [slot="content"] { | ||
padding-inline-start: 1.5rem; /* left and right margins of 0.3rem + drag handle width of 0.9rem */ | ||
} | ||
:host([_render-expand-collapse-slot]) [slot="outside-control-action"] [slot="content"] { | ||
padding-inline-start: 3rem; /* draggable padding + 1.2rem wide + 0.3rem padding */ | ||
} | ||
:host([_has-color-slot]) [slot="outside-control-action"] [slot="content"] { | ||
padding-inline-start: calc(1.5rem + 12px + var(--d2l-list-item-color-width, 6px)); /* draggable padding + 12px color padding + color width */ | ||
} | ||
:host([_render-expand-collapse-slot][_has-color-slot]) [slot="outside-control-action"] [slot="content"] { | ||
padding-inline-start: calc(3rem + 12px + var(--d2l-list-item-color-width, 6px) - 6px); /* all of the above - 6px when color + expand */ | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This hard codes the inline-start spacing the content would need to line up with its siblings/be properly placed under a parent.
The previous strategy was to calculate this based on the hidden content item. However, that could cause infinite loops.
This method is fragile in the event those other paddings change, but I believe that would get caught by vdiffs.
padding-block: 0.55rem; | ||
padding-inline: 0 0.55rem; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated this in order to be able to more easily override it in draggable mixin.
@@ -773,7 +791,8 @@ export const ListItemMixin = superclass => class extends composeMixins( | |||
@mouseleave="${this._onMouseLeavePrimaryAction}"> | |||
${primaryAction} | |||
</div>` : nothing} | |||
${!this._listItemInteractiveEnabled || (!primaryAction && !renderExpandableActionContent && !renderCheckboxActionContent) ? contentAreaContent : nothing} | |||
${renderDraggable ? html`<div slot="content" class="d2l-draggable-content-hidden" style="${styleMap({ width: this._hiddenContentWidth })}"></div>` : nothing} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -883,7 +928,7 @@ export const ListItemDragDropMixin = superclass => class extends superclass { | |||
return this._dragMultiple && this.draggable && (this.selectable || this.expandable) ? html`<d2l-list-item-drag-image></d2l-list-item-drag-image>` : nothing; | |||
} | |||
|
|||
_renderDragTarget(templateMethod) { | |||
_renderDragTarget(templateMethod, content) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This piece is the same as the other cases, where content goes in the clickable area in order to be able to interact with it.
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Just a brief update on this: Margaree and I spoke about this issue and the changes. Moving the content slot such that the grid no longer works as we'd expect isn't super desirable, nor is adding the extra observer to each draggable item. I proposed the idea of not supporting "drag anywhere" capability when the items contain interactive content with the hope that might avoid the need for this. Margaree to bounce the idea off Jeff. |
At this point we will just be recommending that for cases where there is interactive content within a list item and where the list item is only I will be creating a separate PR with this documentation. |
Jira ticket
This is to allow a user to interact with interactive elements within a list item when the only clickable functionality is that it is
draggable
.I'll add comments on the general strategy. It's working well in ltr but not in rtl so looking for the following: