| import { LitElement, html, css, PropertyValues } from 'lit'; |
| import { customElement, property, state, query } from 'lit/decorators.js'; |
| |
| export interface MultiSelectOption { |
| value: string; |
| count: number; |
| } |
| |
| export interface MultiSelectSelectionEventDetail { |
| value: string; |
| } |
| |
| export interface MultiSelectSetSelectionEventDetail { |
| values: string[]; |
| } |
| |
| export interface MultiSelectReplaceEventDetail { |
| value: string; |
| } |
| |
| @customElement('explore-multi-v2-select-sk') |
| export class MultiSelectSk extends LitElement { |
| @property({ type: String }) label = ''; |
| |
| @property({ type: Array }) options: MultiSelectOption[] = []; |
| |
| @property({ type: Array }) selected: string[] = []; |
| |
| @property({ type: String }) variant: 'default' | 'pill' = 'default'; |
| |
| @property({ type: Boolean }) isSplit = false; |
| |
| @property({ type: Boolean }) showDiffButton = false; |
| |
| @property({ type: Boolean }) showSplitButton = false; |
| |
| @property({ type: Boolean }) isHighlighted = false; |
| |
| @property({ type: Boolean }) loading = false; |
| |
| private _isOpen = false; |
| |
| @property({ type: Boolean }) |
| get isOpen() { |
| return this._isOpen; |
| } |
| |
| set isOpen(val: boolean) { |
| const oldVal = this._isOpen; |
| if (val !== oldVal) { |
| this._isOpen = val; |
| this.requestUpdate('isOpen', oldVal); |
| if (val) { |
| this._focusedIndex = 0; |
| this._searchTerm = ''; |
| this._initialSelected = new Set(this.selected); |
| this._hasClickedDuringGlob = false; |
| } |
| } |
| } |
| |
| @state() private _searchTerm = ''; |
| |
| @state() private _focusedIndex = -1; |
| |
| @state() private _initialSelected: Set<string> = new Set(); |
| |
| @state() private _hasClickedDuringGlob = false; |
| |
| @query('.multiselect-search input') private _inputElement?: HTMLInputElement; |
| |
| @query('.multiselect-list') private _listElement?: HTMLDivElement; |
| |
| static styles = css` |
| :host { |
| display: inline-block; |
| font-family: var(--font, 'Inter', system-ui, sans-serif); |
| position: relative; |
| } |
| |
| .multiselect-container { |
| position: relative; |
| } |
| |
| .multiselect-container.default { |
| display: flex; |
| flex-direction: column; |
| min-width: 200px; |
| } |
| |
| .multiselect-container.pill { |
| min-width: auto; |
| } |
| |
| .multiselect-label { |
| font-size: 11px; |
| font-weight: 700; |
| margin-bottom: 6px; |
| color: var(--on-surface); |
| text-transform: uppercase; |
| letter-spacing: 0.05em; |
| } |
| |
| .multiselect-trigger { |
| border: 1px solid var(--outline); |
| border-radius: 8px; |
| padding: 8px 12px; |
| cursor: pointer; |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| background: var(--background); |
| color: var(--on-background); |
| user-select: none; |
| transition: all 0.2s ease; |
| min-height: 36px; |
| box-sizing: border-box; |
| } |
| |
| .multiselect-trigger:hover { |
| background-color: var(--surface); |
| border-color: var(--outline); |
| } |
| |
| .multiselect-trigger.pill { |
| border-radius: 16px; |
| background: transparent; |
| border: 1px solid var(--md-sys-color-outline); |
| padding: 4px 12px; |
| gap: 6px; |
| font-size: 13px; |
| height: 28px; |
| color: var(--on-surface); |
| } |
| |
| .multiselect-trigger.pill:hover { |
| background-color: color-mix(in srgb, var(--on-surface) 20%, transparent); |
| border-color: var(--md-sys-color-outline); |
| } |
| |
| .multiselect-container.pill.highlighted .multiselect-trigger { |
| background-color: var(--md-sys-color-secondary-container); |
| color: var(--md-sys-color-on-secondary-container); |
| border-color: var(--md-sys-color-secondary-container); |
| } |
| |
| .multiselect-container.pill.highlighted .pill-key, |
| .multiselect-container.pill.highlighted .pill-remove { |
| color: var(--md-sys-color-on-secondary-container); |
| opacity: 0.8; |
| } |
| |
| .multiselect-trigger.open { |
| border-color: var(--primary); |
| box-shadow: 0 0 0 2px color-mix(in srgb, var(--primary) 20%, transparent); |
| } |
| |
| .pill-key { |
| font-weight: 600; |
| color: var(--on-surface); |
| white-space: nowrap; |
| } |
| |
| .pill-remove { |
| width: 16px; |
| height: 16px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| border-radius: 50%; |
| margin-left: 2px; |
| cursor: pointer; |
| color: var(--on-surface); |
| font-size: 14px; |
| transition: all 0.2s; |
| } |
| |
| .pill-remove:hover { |
| background-color: color-mix(in srgb, var(--on-surface) 10%, transparent); |
| color: var(--on-background); |
| } |
| |
| .multiselect-value { |
| flex: 1; |
| white-space: nowrap; |
| overflow: hidden; |
| text-overflow: ellipsis; |
| font-weight: 500; |
| } |
| |
| .multiselect-values-stack { |
| display: flex; |
| flex-direction: column; |
| font-size: 11px; |
| line-height: 1.2; |
| } |
| |
| .more-values { |
| font-style: italic; |
| color: var(--on-surface); |
| margin-top: 2px; |
| } |
| |
| .multiselect-arrow { |
| font-size: 10px; |
| margin-left: 8px; |
| opacity: 0.5; |
| transition: transform 0.2s; |
| } |
| |
| .multiselect-trigger.open .multiselect-arrow { |
| transform: rotate(180deg); |
| } |
| |
| .multiselect-dropdown { |
| position: absolute; |
| top: calc(100% + 8px); |
| left: 0; |
| min-width: 300px; |
| background: var(--surface); |
| backdrop-filter: blur(12px); |
| border-radius: 12px; |
| box-shadow: none; |
| z-index: 2000; |
| overflow: hidden; |
| display: flex; |
| flex-direction: column; |
| border: 1px solid var(--outline); |
| } |
| |
| .multiselect-search { |
| padding: 12px; |
| border-bottom: 1px solid var(--outline); |
| background-color: color-mix(in srgb, var(--surface) 30%, transparent); |
| } |
| |
| .multiselect-search input { |
| width: 100%; |
| padding: 8px 12px; |
| border: 1px solid var(--outline); |
| border-radius: 6px; |
| box-sizing: border-box; |
| font-size: 13px; |
| outline: none; |
| background: var(--background); |
| color: var(--on-background); |
| transition: border-color 0.2s; |
| } |
| |
| .multiselect-search input:focus { |
| border-color: var(--primary); |
| } |
| |
| .multiselect-list { |
| max-height: 300px; |
| overflow-y: auto; |
| padding: 6px 0; |
| } |
| |
| .multiselect-option { |
| padding: 8px 12px; |
| cursor: pointer; |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| font-size: 13px; |
| min-height: 36px; |
| color: var(--on-surface); |
| transition: all 0.1s ease; |
| } |
| |
| .multiselect-option:hover, |
| .multiselect-option.focused { |
| background-color: color-mix(in srgb, var(--on-surface) 3%, transparent); |
| color: var(--on-background); |
| } |
| |
| .multiselect-option.selected { |
| background-color: color-mix(in srgb, var(--primary) 10%, transparent); |
| color: var(--primary); |
| } |
| |
| .multiselect-option.preview { |
| color: var(--on-surface); |
| background-color: color-mix(in srgb, var(--on-surface) 1%, transparent); |
| font-style: italic; |
| } |
| |
| .ms-opt-checkbox { |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| width: 16px; |
| height: 16px; |
| } |
| |
| .ms-opt-text { |
| flex: 1; |
| display: flex; |
| justify-content: space-between; |
| align-items: baseline; |
| overflow: hidden; |
| } |
| |
| .ms-opt-value { |
| white-space: nowrap; |
| overflow: hidden; |
| text-overflow: ellipsis; |
| font-weight: 500; |
| } |
| |
| .ms-opt-count { |
| color: var(--on-surface); |
| font-size: 11px; |
| margin-left: 12px; |
| flex-shrink: 0; |
| font-family: monospace; |
| } |
| |
| .multiselect-container.loading .ms-opt-count { |
| opacity: 0.5; |
| } |
| |
| .ms-diff-btn { |
| opacity: 0; |
| padding: 4px 8px; |
| font-size: 11px; |
| border: 1px solid var(--outline); |
| border-radius: 4px; |
| background: color-mix(in srgb, var(--on-surface) 5%, transparent); |
| color: var(--on-surface); |
| cursor: pointer; |
| flex-shrink: 0; |
| margin-left: 8px; |
| transition: all 0.2s ease; |
| } |
| |
| .multiselect-option:hover .ms-diff-btn, |
| .multiselect-option.focused .ms-diff-btn { |
| opacity: 1; |
| } |
| |
| .ms-diff-btn:hover { |
| background-color: color-mix(in srgb, var(--primary) 20%, transparent); |
| border-color: var(--primary); |
| color: var(--on-background); |
| } |
| |
| .multiselect-footer { |
| padding: 12px; |
| border-top: 1px solid var(--outline); |
| background-color: color-mix(in srgb, var(--surface) 30%, transparent); |
| } |
| |
| .ms-split-btn { |
| width: 100%; |
| padding: 8px; |
| cursor: pointer; |
| background-color: color-mix(in srgb, var(--on-surface) 5%, transparent); |
| border: 1px solid var(--outline); |
| border-radius: 6px; |
| color: var(--on-surface); |
| font-weight: 600; |
| font-size: 12px; |
| transition: all 0.2s ease; |
| } |
| |
| .ms-split-btn:hover { |
| background-color: color-mix(in srgb, var(--on-surface) 10%, transparent); |
| border-color: var(--outline); |
| color: var(--on-background); |
| } |
| |
| .ms-split-btn.active { |
| background-color: color-mix(in srgb, var(--primary) 15%, transparent); |
| border-color: color-mix(in srgb, var(--primary) 30%, transparent); |
| color: var(--primary); |
| } |
| |
| .multiselect-option.empty { |
| color: var(--on-surface); |
| font-style: italic; |
| justify-content: center; |
| cursor: default; |
| padding: 24px; |
| } |
| |
| /* Custom Checkbox */ |
| .custom-checkbox { |
| position: relative; |
| display: flex; |
| align-items: center; |
| cursor: pointer; |
| user-select: none; |
| } |
| |
| .custom-checkbox input { |
| position: absolute; |
| opacity: 0; |
| cursor: pointer; |
| height: 0; |
| width: 0; |
| } |
| |
| .checkmark { |
| height: 16px; |
| width: 16px; |
| background-color: color-mix(in srgb, var(--on-surface) 5%, transparent); |
| border: 1px solid var(--outline); |
| border-radius: 4px; |
| transition: all 0.2s; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| } |
| |
| .custom-checkbox:hover input ~ .checkmark { |
| background-color: color-mix(in srgb, var(--on-surface) 10%, transparent); |
| border-color: var(--outline); |
| } |
| |
| .checkmark.checked { |
| background-color: var(--primary); |
| border-color: var(--primary); |
| } |
| |
| .checkmark::after { |
| content: ''; |
| display: none; |
| width: 4px; |
| height: 8px; |
| border: solid var(--on-primary); |
| border-width: 0 2px 2px 0; |
| transform: rotate(45deg); |
| margin-bottom: 2px; |
| } |
| |
| .checkmark.checked::after { |
| display: block; |
| } |
| `; |
| |
| connectedCallback() { |
| super.connectedCallback(); |
| document.addEventListener('mousedown', this._handleClickOutside); |
| } |
| |
| disconnectedCallback() { |
| document.removeEventListener('mousedown', this._handleClickOutside); |
| super.disconnectedCallback(); |
| } |
| |
| protected updated(changedProperties: PropertyValues) { |
| if (changedProperties.has('_isOpen') && this._isOpen) { |
| setTimeout(() => this._inputElement?.focus(), 0); |
| } |
| } |
| |
| private _handleClickOutside = (e: MouseEvent) => { |
| if (this.isOpen && !e.composedPath().includes(this)) { |
| this.isOpen = false; |
| this.dispatchEvent(new CustomEvent('close', { bubbles: true, composed: true })); |
| } |
| }; |
| |
| private _matchGlob(val: string, pattern: string): boolean { |
| try { |
| const parts = pattern |
| .split(',') |
| .map((s) => s.trim()) |
| .filter(Boolean); |
| return parts.some((part) => { |
| if (part.includes('*') || part.includes('?')) { |
| const escaped = part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); |
| const p = '^' + escaped.replace(/\\\*/g, '.*').replace(/\\\?/g, '.') + '$'; |
| const regex = new RegExp(p, 'i'); |
| return regex.test(val); |
| } |
| return val.toLowerCase() === part.toLowerCase(); |
| }); |
| } catch (_e) { |
| return false; |
| } |
| } |
| |
| private _toggleOpen(e?: Event) { |
| if (e && e instanceof MouseEvent && (e.ctrlKey || e.metaKey || e.shiftKey)) { |
| return; |
| } |
| if (this.options.length <= 1 && this.variant !== 'pill') return; |
| this.isOpen = !this.isOpen; |
| |
| if (this.isOpen) { |
| this.dispatchEvent( |
| new CustomEvent('open', { |
| bubbles: true, |
| composed: true, |
| }) |
| ); |
| } else { |
| this.dispatchEvent( |
| new CustomEvent('close', { |
| bubbles: true, |
| composed: true, |
| }) |
| ); |
| } |
| } |
| |
| private _getFilteredOptions() { |
| let result = this.options; |
| const isGlob = |
| this._searchTerm.includes('*') || |
| this._searchTerm.includes('?') || |
| this._searchTerm.includes(','); |
| |
| if (this._searchTerm) { |
| if (isGlob) { |
| try { |
| const parts = this._searchTerm |
| .split(',') |
| .map((s) => s.trim()) |
| .filter(Boolean); |
| const seen = new Set<string>(); |
| const results: MultiSelectOption[] = []; |
| |
| parts.forEach((part) => { |
| let matches: MultiSelectOption[] = []; |
| if (part.includes('*') || part.includes('?')) { |
| const escaped = part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); |
| const pattern = '^' + escaped.replace(/\\\*/g, '.*').replace(/\\\?/g, '.') + '$'; |
| const regex = new RegExp(pattern, 'i'); |
| matches = this.options.filter((opt) => regex.test(opt.value)); |
| } else { |
| const lowerPart = part.toLowerCase(); |
| matches = this.options.filter((opt) => opt.value.toLowerCase().includes(lowerPart)); |
| } |
| |
| matches.forEach((opt) => { |
| if (!seen.has(opt.value)) { |
| seen.add(opt.value); |
| results.push(opt); |
| } |
| }); |
| }); |
| result = results; |
| } catch (e) { |
| console.error(e); |
| } |
| } else { |
| const lowerSearch = this._searchTerm.toLowerCase(); |
| result = this.options.filter((opt) => opt.value.toLowerCase().includes(lowerSearch)); |
| } |
| } |
| |
| return [...result].sort((a, b) => { |
| const aSel = this._initialSelected.has(a.value); |
| const bSel = this._initialSelected.has(b.value); |
| if (aSel && !bSel) return -1; |
| if (!aSel && bSel) return 1; |
| return 0; |
| }); |
| } |
| |
| private _handleToggle(val: string) { |
| const isGlob = |
| this._searchTerm.includes('*') || |
| this._searchTerm.includes('?') || |
| this._searchTerm.includes(','); |
| const filtered = this._getFilteredOptions(); |
| |
| if (isGlob && !this._hasClickedDuringGlob) { |
| this._hasClickedDuringGlob = true; |
| const allMatches = filtered.map((o) => o.value); |
| const isCurrentlySelectedInGlob = allMatches.includes(val); |
| |
| const newSelection = isCurrentlySelectedInGlob |
| ? allMatches.filter((m) => m !== val) |
| : [...allMatches, val]; |
| |
| this.dispatchEvent( |
| new CustomEvent<MultiSelectSetSelectionEventDetail>('set-selection', { |
| detail: { values: newSelection }, |
| bubbles: true, |
| composed: true, |
| }) |
| ); |
| return; |
| } |
| |
| const matchedGlob = this.selected.find( |
| (pattern) => |
| (pattern.includes('*') || pattern.includes('?') || pattern.includes(',')) && |
| this._matchGlob(val, pattern) |
| ); |
| |
| if (matchedGlob) { |
| const expandedMatches = this.options |
| .map((o) => o.value) |
| .filter((v) => this._matchGlob(v, matchedGlob) && v !== val); |
| |
| const newSelection = [...this.selected.filter((v) => v !== matchedGlob), ...expandedMatches]; |
| |
| this.dispatchEvent( |
| new CustomEvent<MultiSelectSetSelectionEventDetail>('set-selection', { |
| detail: { values: newSelection }, |
| bubbles: true, |
| composed: true, |
| }) |
| ); |
| return; |
| } |
| |
| this.dispatchEvent( |
| new CustomEvent<MultiSelectSelectionEventDetail>('selection-change', { |
| detail: { value: val }, |
| bubbles: true, |
| composed: true, |
| }) |
| ); |
| } |
| |
| private _handleKeyDown(e: KeyboardEvent) { |
| if (!this.isOpen) { |
| if (e.key === 'Enter' || e.key === ' ' || e.key === 'ArrowDown') { |
| e.preventDefault(); |
| this.isOpen = true; |
| } |
| return; |
| } |
| |
| const filtered = this._getFilteredOptions(); |
| |
| switch (e.key) { |
| case 'ArrowDown': |
| e.preventDefault(); |
| this._focusedIndex = (this._focusedIndex + 1) % (filtered.length || 1); |
| this._scrollFocusedIntoView(); |
| break; |
| case 'ArrowUp': |
| e.preventDefault(); |
| this._focusedIndex = |
| (this._focusedIndex - 1 + (filtered.length || 1)) % (filtered.length || 1); |
| this._scrollFocusedIntoView(); |
| break; |
| case ' ': |
| e.preventDefault(); |
| if (this._focusedIndex >= 0 && this._focusedIndex < filtered.length) { |
| this._handleToggle(filtered[this._focusedIndex].value); |
| } |
| break; |
| case 'Enter': |
| e.preventDefault(); |
| const isGlob = |
| this._searchTerm.includes('*') || |
| this._searchTerm.includes('?') || |
| this._searchTerm.includes(','); |
| if (isGlob && !this._hasClickedDuringGlob && this._searchTerm.trim()) { |
| this.dispatchEvent( |
| new CustomEvent<MultiSelectReplaceEventDetail>('replace-selection', { |
| detail: { value: this._searchTerm }, |
| bubbles: true, |
| composed: true, |
| }) |
| ); |
| this.isOpen = false; |
| this.dispatchEvent(new CustomEvent('close', { bubbles: true, composed: true })); |
| return; |
| } |
| |
| if (this._focusedIndex >= 0 && this._focusedIndex < filtered.length) { |
| this._handleToggle(filtered[this._focusedIndex].value); |
| } |
| this.isOpen = false; |
| this.dispatchEvent(new CustomEvent('close', { bubbles: true, composed: true })); |
| break; |
| case 'Escape': |
| e.preventDefault(); |
| this.dispatchEvent(new CustomEvent('close-with-esc', { bubbles: true, composed: true })); |
| this.isOpen = false; |
| this.dispatchEvent(new CustomEvent('close', { bubbles: true, composed: true })); |
| break; |
| case 'Tab': |
| this.isOpen = false; |
| this.dispatchEvent(new CustomEvent('close', { bubbles: true, composed: true })); |
| break; |
| } |
| } |
| |
| private _scrollFocusedIntoView() { |
| setTimeout(() => { |
| if (this._listElement && this._focusedIndex >= 0) { |
| const item = this._listElement.children[this._focusedIndex] as HTMLElement; |
| if (item) { |
| item.scrollIntoView({ block: 'nearest' }); |
| } |
| } |
| }, 0); |
| } |
| |
| private _renderLabel() { |
| if (this.selected.length === 0) return html`<span class="placeholder">(Any)</span>`; |
| |
| if (this.variant === 'pill') { |
| if (this.selected.length === 1) return this.selected[0]; |
| |
| const displayed = this.selected.length > 4 ? this.selected.slice(0, 3) : this.selected; |
| const remaining = this.selected.length - displayed.length; |
| |
| return html` |
| <div class="multiselect-values-stack"> |
| ${displayed.map((val) => html`<div>${val}</div>`)} |
| ${remaining > 0 ? html`<div class="more-values">and ${remaining} more values</div>` : ''} |
| </div> |
| `; |
| } |
| |
| if (this.selected.length === 1) return this.selected[0]; |
| return `${this.selected.length} selected`; |
| } |
| |
| private _renderPillKey() { |
| if (this.variant !== 'pill') return ''; |
| return html`<div class="pill-key">${this.label + '='}</div>`; |
| } |
| |
| render() { |
| const filtered = this._getFilteredOptions(); |
| const isGlob = |
| this._searchTerm.includes('*') || |
| this._searchTerm.includes('?') || |
| this._searchTerm.includes(','); |
| |
| return html` |
| <div |
| class="${this.variant === 'pill' |
| ? `multiselect-container pill ${this.isHighlighted ? 'highlighted' : ''} ${this.loading ? 'loading' : ''}` |
| : `multiselect-container default ${this.loading ? 'loading' : ''}`}"> |
| ${this.variant === 'default' |
| ? html`<label class="multiselect-label">${this.label}</label>` |
| : ''} |
| |
| <div |
| class="${this._isOpen |
| ? `multiselect-trigger ${this.variant} open` |
| : `multiselect-trigger ${this.variant}`}" |
| @click=${this._toggleOpen} |
| tabindex="0" |
| @keydown=${this._handleKeyDown}> |
| ${this._renderPillKey()} |
| <div class="multiselect-value">${this._renderLabel()}</div> |
| |
| ${this.variant === 'default' ? html`<div class="multiselect-arrow">▼</div>` : ''} |
| ${this.variant === 'pill' |
| ? html` |
| <span |
| class="pill-remove" |
| @click=${(e: Event) => { |
| e.stopPropagation(); |
| this.dispatchEvent( |
| new CustomEvent('remove-key', { bubbles: true, composed: true }) |
| ); |
| }} |
| >×</span |
| > |
| ` |
| : ''} |
| </div> |
| |
| ${this._isOpen |
| ? html` |
| <div class="multiselect-dropdown"> |
| <div class="multiselect-search"> |
| <input |
| type="text" |
| placeholder="Search..." |
| .value=${this._searchTerm} |
| @input=${(e: InputEvent) => { |
| this._searchTerm = (e.target as HTMLInputElement).value; |
| this._focusedIndex = 0; |
| }} |
| @click=${(e: Event) => e.stopPropagation()} |
| @keydown=${this._handleKeyDown} /> |
| </div> |
| <div class="multiselect-list"> |
| ${filtered.length === 0 |
| ? html` |
| <div class="multiselect-option empty"> |
| No options match "${this._searchTerm}" |
| </div> |
| ` |
| : filtered.map((option, index) => { |
| const isSelected = |
| this.selected.includes(option.value) || |
| this.selected.some( |
| (p) => |
| (p.includes('*') || p.includes('?') || p.includes(',')) && |
| this._matchGlob(option.value, p) |
| ); |
| const isFocused = index === this._focusedIndex; |
| const isPreviewMatch = |
| isGlob && !this._hasClickedDuringGlob && !!this._searchTerm; |
| const isEffectivelySelected = isPreviewMatch || isSelected; |
| |
| return html` |
| <div |
| class="multiselect-option ${isSelected && !isPreviewMatch |
| ? 'selected' |
| : ''} ${isFocused ? 'focused' : ''} ${isPreviewMatch |
| ? 'preview' |
| : ''}" |
| @click=${(e: Event) => { |
| e.stopPropagation(); |
| this._handleToggle(option.value); |
| }} |
| @mouseenter=${() => { |
| if (this._focusedIndex !== index) { |
| this._focusedIndex = index; |
| } |
| }}> |
| <div class="ms-opt-checkbox"> |
| <div class="custom-checkbox"> |
| <span |
| class="checkmark ${isEffectivelySelected |
| ? 'checked' |
| : ''}"></span> |
| </div> |
| </div> |
| <span class="ms-opt-text"> |
| <span class="ms-opt-value">${option.value}</span> |
| <span class="ms-opt-count">${option.count.toLocaleString()}</span> |
| </span> |
| ${this.showDiffButton |
| ? html` |
| <button |
| class="ms-diff-btn" |
| @click=${(e: Event) => { |
| e.stopPropagation(); |
| this.dispatchEvent( |
| new CustomEvent('diff-base', { |
| detail: { key: this.label, value: option.value }, |
| bubbles: true, |
| composed: true, |
| }) |
| ); |
| this.isOpen = false; |
| this.dispatchEvent( |
| new CustomEvent('close', { bubbles: true, composed: true }) |
| ); |
| }} |
| title="Set as Diff Base"> |
| Diff |
| </button> |
| ` |
| : ''} |
| </div> |
| `; |
| })} |
| </div> |
| ${this.showSplitButton |
| ? html` |
| <div class="multiselect-footer"> |
| <button |
| class="ms-split-btn ${this.isSplit ? 'active' : ''}" |
| @click=${(e: Event) => { |
| e.stopPropagation(); |
| this.dispatchEvent( |
| new CustomEvent('split', { bubbles: true, composed: true }) |
| ); |
| this.isOpen = false; |
| this.dispatchEvent( |
| new CustomEvent('close', { bubbles: true, composed: true }) |
| ); |
| }}> |
| ${this.isSplit ? `Stop Splitting` : `Split by ${this.label}`} |
| </button> |
| </div> |
| ` |
| : ''} |
| </div> |
| ` |
| : ''} |
| </div> |
| `; |
| } |
| } |