Et2Select: Fix/re-add hidden tag flag when multiple,readonly & rows=1

This commit is contained in:
nathan 2023-11-23 13:44:45 -07:00
parent b4936c07af
commit 38dcda2a01
2 changed files with 195 additions and 8 deletions

View File

@ -19,6 +19,7 @@ import {property} from "lit/decorators/property.js";
import {SlChangeEvent, SlOption, SlSelect} from "@shoelace-style/shoelace"; import {SlChangeEvent, SlOption, SlSelect} from "@shoelace-style/shoelace";
import {repeat} from "lit/directives/repeat.js"; import {repeat} from "lit/directives/repeat.js";
import {classMap} from "lit/directives/class-map.js"; import {classMap} from "lit/directives/class-map.js";
import {state} from "lit/decorators/state.js";
// export Et2WidgetWithSelect which is used as type in other modules // export Et2WidgetWithSelect which is used as type in other modules
export class Et2WidgetWithSelect extends RowLimitedMixin(Et2WidgetWithSelectMixin(LitElement)) export class Et2WidgetWithSelect extends RowLimitedMixin(Et2WidgetWithSelectMixin(LitElement))
@ -128,10 +129,12 @@ export class Et2Select extends Et2WithSearchMixin(Et2WidgetWithSelect)
overflow: hidden; overflow: hidden;
} }
/* Keep overflow tag right-aligned. It's the only sl-tag. */ /* No rows set, default height limit about 5 rows */
::part(tags) sl-tag { :host(:not([rows])) ::part(tags) {
margin-left: auto; min-height: inherit;
max-height: 11em;
overflow-y: auto;
} }
select:hover { select:hover {
@ -140,20 +143,22 @@ export class Et2Select extends Et2WithSearchMixin(Et2WidgetWithSelect)
/* Hide dropdown trigger when multiple & readonly */ /* Hide dropdown trigger when multiple & readonly */
:host([readonly][multiple])::part(expand-icon) { :host([readonly][multiple]):not([rows="1"])::part(expand-icon) {
display: none; display: none;
} }
/* Style for tag count if rows=1 */ /* Style for tag count if rows=1 */
:host([readonly][multiple][rows])::part(tags) { .tag_limit {
position: absolute; position: absolute;
right: 0px; right: 0px;
top: 1px; top: 0px;
bottom: 0px;
box-shadow: rgb(0 0 0/50%) -1.5ex 0px 1ex -1ex, rgb(0 0 0 / 0%) 0px 0px 0px 0px; box-shadow: rgb(0 0 0/50%) -1.5ex 0px 1ex -1ex, rgb(0 0 0 / 0%) 0px 0px 0px 0px;
} }
:host([readonly][multiple][rows]) .select__tags sl-tag::part(base) { .tag_limit::part(base) {
height: 100%;
background-color: var(--sl-input-background-color); background-color: var(--sl-input-background-color);
border-top-left-radius: 0; border-top-left-radius: 0;
border-bottom-left-radius: 0; border-bottom-left-radius: 0;
@ -164,12 +169,16 @@ export class Et2Select extends Et2WithSearchMixin(Et2WidgetWithSelect)
/* Show all rows on hover if rows=1 */ /* Show all rows on hover if rows=1 */
:host([readonly][multiple][rows]):hover .select__tags { :host([ readonly ][ multiple ][ rows ]) .hover__popup {
width: -webkit-fill-available; width: -webkit-fill-available;
width: -moz-fill-available; width: -moz-fill-available;
width: fill-available; width: fill-available;
} }
:host([ readonly ][ multiple ][ rows ]) .hover__popup .select__tags {
display: flex;
flex-wrap: wrap;
}
::part(listbox) { ::part(listbox) {
z-index: 1; z-index: 1;
background: var(--sl-input-background-color); background: var(--sl-input-background-color);
@ -254,15 +263,23 @@ export class Et2Select extends Et2WithSearchMixin(Et2WidgetWithSelect)
/** The select's required attribute. */ /** The select's required attribute. */
@property({type: Boolean, reflect: true}) required = false; @property({type: Boolean, reflect: true}) required = false;
/** If the select is limited to 1 row, we show the number of tags not visible */
@state()
protected _tagsHidden = 0;
private __value : string | string[] = ""; private __value : string | string[] = "";
protected tagOverflowObserver : IntersectionObserver = null;
constructor() constructor()
{ {
super(); super();
this.hoist = true; this.hoist = true;
this._tagTemplate = this._tagTemplate.bind(this); this._tagTemplate = this._tagTemplate.bind(this);
this._handleMouseEnter = this._handleMouseEnter.bind(this);
this._handleMouseLeave = this._handleMouseLeave.bind(this);
this._handleTagOverflow = this._handleTagOverflow.bind(this);
} }
/** /**
* List of properties that get translated * List of properties that get translated
@ -287,6 +304,9 @@ export class Et2Select extends Et2WithSearchMixin(Et2WidgetWithSelect)
this.select?.requestUpdate("value"); this.select?.requestUpdate("value");
// Fixes incorrect opening position // Fixes incorrect opening position
this.select?.popup?.handleAnchorChange(); this.select?.popup?.handleAnchorChange();
// requestUpdate("value") above means we need to check tags again
this.select.updateComplete.then(() => {this.checkTagOverflow(); });
}); });
} }
@ -511,6 +531,49 @@ export class Et2Select extends Et2WithSearchMixin(Et2WidgetWithSelect)
} }
} }
/**
* After render, DOM nodes are there
*
* Check to see if tags overflow, set the counter flag
*
* @param {PropertyValues} changedProperties
*/
updated(changedProperties : PropertyValues)
{
super.updated(changedProperties);
this.checkTagOverflow();
}
protected checkTagOverflow()
{
// Create / destroy intersection observer
if(this.readonly && this.rows == "1" && this.multiple && this.tagOverflowObserver == null)
{
this.tagOverflowObserver = new IntersectionObserver(this._handleTagOverflow, {
root: this.select.shadowRoot.querySelector(".select__tags"),
threshold: 0.1
});
}
else if((!this.readonly || this.rows !== "1" || !this.multiple) && this.tagOverflowObserver !== null)
{
this.tagOverflowObserver.disconnect();
this.tagOverflowObserver = null;
}
if(this.tagOverflowObserver)
{
this.select.updateComplete.then(() =>
{
// @ts-ignore
for(const tag of this.select.shadowRoot.querySelectorAll(".select__tags *:not(div):not(sl-tag)"))
{
this.tagOverflowObserver.observe(tag);
}
});
}
}
/** /**
* Tag used for rendering tags when multiple=true * Tag used for rendering tags when multiple=true
* Used for creating, finding & filtering options. * Used for creating, finding & filtering options.
@ -602,6 +665,111 @@ export class Et2Select extends Et2WithSearchMixin(Et2WidgetWithSelect)
} }
/**
* Callback for the intersection observer so we know when tags don't fit
*
* Here we set the flag to show how many more tags are hidden, but this only happens
* when there are more tags than space.
*
* @param entries
* @protected
*/
protected _handleTagOverflow(entries : IntersectionObserverEntry[])
{
const oldCount = this._tagsHidden;
let visibleTagCount = this.value.length - this._tagsHidden;
let update = false;
// If we have all tags, start from 0, otherwise it's just a change
if(entries.length == this.value.length)
{
visibleTagCount = 0;
}
else
{
update = true;
}
for(const tag of entries)
{
if(tag.isIntersecting)
{
visibleTagCount++;
}
else if(update && !tag.isIntersecting)
{
visibleTagCount--;
}
else
{
break;
}
}
if(visibleTagCount && visibleTagCount < this.value.length)
{
this._tagsHidden = this.value.length - visibleTagCount;
}
else
{
this._tagsHidden = 0;
}
this.requestUpdate("_tagsHidden", oldCount);
}
/**
* If rows=1 and multiple=true, when they put the mouse over the widget show all tags
* @param {MouseEvent} e
* @private
*/
protected _handleMouseEnter(e : MouseEvent)
{
if(this.rows == 1 && this.multiple == true && this.value.length > 1)
{
e.stopPropagation();
let distance = (-1 * parseInt(getComputedStyle(this).height));
// Bind to turn this all off
this.addEventListener("mouseleave", this._handleMouseLeave);
// Popup - this might get wiped out next render(), might not
this.updateComplete.then(() =>
{
let tags = this.select.shadowRoot.querySelector(".select__tags");
let popup = document.createElement("sl-popup");
popup.anchor = this;
popup.distance = distance;
popup.placement = "bottom";
popup.strategy = "fixed";
popup.active = true;
popup.sync = "width";
popup.setAttribute("exportparts", "tags");
popup.classList.add("hover__popup", "details", "hoist", "details__body");
this.shadowRoot.append(popup);
popup.appendChild(tags);
tags.style.width = getComputedStyle(this).width;
tags.style.margin = 0;
});
}
}
/**
* If we're showing all rows because of _handleMouseEnter, reset when mouse leaves
* @param {MouseEvent} e
* @private
*/
protected _handleMouseLeave(e : MouseEvent)
{
let popup = this.shadowRoot.querySelector("sl-popup");
if(popup)
{
// Popup still here. Remove it
let tags = popup.firstChild;
this.select.shadowRoot.querySelector(".select__combobox").append(tags);
popup.remove();
}
this.removeEventListener("mouseleave", this._handleMouseLeave);
this.select.requestUpdate();
}
/** Shows the listbox. */ /** Shows the listbox. */
async show() async show()
{ {
@ -781,6 +949,21 @@ export class Et2Select extends Et2WithSearchMixin(Et2WidgetWithSelect)
`; `;
} }
protected _tagLimitTemplate() : TemplateResult | typeof nothing
{
if(this._tagsHidden == 0)
{
return nothing;
}
return html`
<sl-tag
part="tag__limit"
class="tag_limit"
slot="expand-icon"
>+${this._tagsHidden}
</sl-tag>`;
}
/** /**
* Additional customisation template * Additional customisation template
* Override if needed. Added after select options. * Override if needed. Added after select options.
@ -835,6 +1018,7 @@ export class Et2Select extends Et2WithSearchMixin(Et2WidgetWithSelect)
.maxOptionsVisible=${0} .maxOptionsVisible=${0}
.value=${value} .value=${value}
@sl-change=${this.handleValueChange} @sl-change=${this.handleValueChange}
@mouseenter=${this._handleMouseEnter}
@mouseup=${this.handleOptionClick} @mouseup=${this.handleOptionClick}
@mousewheel=${ @mousewheel=${
// Grab & stop mousewheel to prevent scrolling sidemenu when scrolling through options // Grab & stop mousewheel to prevent scrolling sidemenu when scrolling through options
@ -845,6 +1029,7 @@ export class Et2Select extends Et2WithSearchMixin(Et2WidgetWithSelect)
${icon} ${icon}
${this._emptyLabelTemplate()} ${this._emptyLabelTemplate()}
${this._optionsTemplate()} ${this._optionsTemplate()}
${this._tagLimitTemplate()}
${this._extraTemplate()} ${this._extraTemplate()}
<slot></slot> <slot></slot>
<div slot="help-text"> <div slot="help-text">

View File

@ -116,6 +116,8 @@ export class Et2SelectEmail extends Et2Select
updated(changedProperties : Map<string, any>) updated(changedProperties : Map<string, any>)
{ {
super.updated(changedProperties);
// Make tags draggable // Make tags draggable
if(!this.readonly && this.allowFreeEntries && this.allowDragAndDrop) if(!this.readonly && this.allowFreeEntries && this.allowDragAndDrop)
{ {