Skip to content

Commit

Permalink
Port faceting control to use popup-container
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 484560689
  • Loading branch information
iftenney authored and LIT team committed Oct 28, 2022
1 parent a065a4d commit 670abeb
Show file tree
Hide file tree
Showing 4 changed files with 204 additions and 114 deletions.
12 changes: 6 additions & 6 deletions lit_nlp/client/core/faceting_control.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
}

.faceting-info {
position: relative;
display: flex;
flex-direction: row;
align-items: start;
Expand All @@ -17,23 +16,20 @@
.active-facets {
flex: 1;
line-height: 22px;
/* Revert a pointer style from <popup-container> */
cursor: text;
}

.active-facets.disabled {
color: var(--lit-neutral-400);
}

.config-panel {
position: absolute;
top: 26px;

min-width: 250px;
max-height: 150px;

flex-direction: column;
overflow-y: hidden;

z-index: 9000;
}

.panel-header {
Expand Down Expand Up @@ -92,3 +88,7 @@
.feature-options-row .no-input {
min-width: 72px;
}

popup-container {
--popup-top: 4px;
}
75 changes: 19 additions & 56 deletions lit_nlp/client/core/faceting_control.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,32 +17,23 @@
* limitations under the License.
*/

import '../elements/popup_container';
import '../elements/slider';

import {html, TemplateResult} from 'lit';
// tslint:disable:no-new-decorators
import {customElement, property} from 'lit/decorators';
import {styleMap, StyleInfo} from 'lit/directives/style-map';
import {classMap} from 'lit/directives/class-map';
import {html, TemplateResult} from 'lit';
import {observable} from 'mobx';

import {app} from '../core/app';
import {ReactiveElement} from '../lib/elements';
import {styles as sharedStyles} from '../lib/shared_styles.css';
import {getStepSizeGivenRange} from '../lib/utils';
import {FacetingConfig, FacetingMethod, GroupService, NumericFeatureBins} from '../services/group_service';

import {styles as sharedStyles} from '../lib/shared_styles.css';
import {styles} from './faceting_control.css';

const ELEMENT_VISIBLE: StyleInfo = {
display: 'flex',
visibility: 'visible'
};

const ELEMENT_HIDDEN: StyleInfo = {
display: 'none',
visibility: 'hidden'
};

const DEFAULT_QUANTILE = 4;
const DEFAULT_BIN_LIMIT = 100;

Expand All @@ -69,7 +60,6 @@ export class FacetingControl extends ReactiveElement {
private readonly featureConfigs = new Map<string, FacetingConfig>();
private readonly discreteCount = new Map<string, number>();

@observable private expanded = false;
@observable private hasExcessBins = false;
@observable private features: string[] = [];
@observable private bins: NumericFeatureBins = {};
Expand Down Expand Up @@ -125,10 +115,6 @@ export class FacetingControl extends ReactiveElement {
}
}

private toggleExpanded() {
this.expanded = !this.expanded;
}

private updateBins() {
const configs: FacetingConfig[] = this.features
.filter(f => this.groupService.numericalFeatureNames.includes(f))
Expand Down Expand Up @@ -157,27 +143,9 @@ export class FacetingControl extends ReactiveElement {
}
}

protected clickToClose(event: MouseEvent) {
const path = event.composedPath();
if (!path.some(elem => elem instanceof FacetingControl)) {
this.expanded = false;
}
}

override firstUpdated() {
const numericFeatures = () => this.groupService.denseFeatureNames;
this.reactImmediately(numericFeatures, () => {this.reset();});

const onBodyClick = (event: MouseEvent) => {this.clickToClose(event);};
this.reactImmediately(() => this.expanded, () => {
if (this.expanded) {
document.body.addEventListener(
'click', onBodyClick, {passive: true, capture: true});
} else {
document.body.removeEventListener(
'click', onBodyClick, {capture: true});
}
});
}

/**
Expand Down Expand Up @@ -349,48 +317,43 @@ export class FacetingControl extends ReactiveElement {
}

override render() {
const configPanelStyles = styleMap(this.expanded ? ELEMENT_VISIBLE :
ELEMENT_HIDDEN);

const facetsList = this.features.length ?
`${this.features.join(', ')} (${
this.groupService.numIntersectionsLabel(this.bins, this.features)})` :
'None';

const forContext = this.contextName ? ` for ${this.contextName}` : '';
const title =
`${this.expanded ? 'Hide' :
'Show'} the faceting configuration${forContext}`;
const title = `Faceting configuration${forContext}`;

const activeFacetsClass = classMap({
'active-facets': true,
'disabled': this.disabled
});

const closeButtonClick = () => {this.expanded = false;};

// clang-format off
return html`
<div class="faceting-info">
<button class="hairline-button" title=${title}
?disabled=${this.disabled} @click=${this.toggleExpanded}>
<span class="material-icon">dashboard</span>
Facets
</button>
<span class=${activeFacetsClass}>: ${facetsList}</span>
<div class="config-panel popup-container" style=${configPanelStyles}>
<popup-container>
<div class="faceting-info" slot='toggle-anchor'>
<button class="hairline-button" title=${title}
?disabled=${this.disabled}>
<span class="material-icon">dashboard</span>
Facets
</button>
<div class=${activeFacetsClass}
@click=${(e: Event) => { e.stopPropagation(); }}>
: ${facetsList}
</div>
</div>
<div class='config-panel'>
<div class="panel-header">
<span class="panel-label">Faceting Config${forContext}</span>
<span class="choice-limit">(Limit ${this.binLimit} bins ${
this.choiceLimit != null ? `, ${this.choiceLimit} features` : ''
})</span>
<mwc-icon class="icon-button min-button" @click=${closeButtonClick}>
close
</mwc-icon>
</div>
<div class="panel-options">${this.renderFeatureOptions()}</div>
</div>
</div>`;
</popup-container>`;
// clang-format on
}
}
Expand Down
70 changes: 18 additions & 52 deletions lit_nlp/client/core/faceting_control_test.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import 'jasmine';
import {LitElement} from 'lit';
import {FacetingControl, FacetsChange} from './faceting_control';

import {Checkbox} from '@material/mwc-checkbox';
import {LitElement} from 'lit';

import {LitApp} from '../core/app';
import {LitCheckbox} from '../elements/checkbox';
import {PopupContainer} from '../elements/popup_container';
import {mockMetadata} from '../lib/testing_utils';
import {AppState, DataService, GroupService} from '../services/services';

import {FacetingControl, FacetsChange} from './faceting_control';


describe('faceting control test', () => {
let facetCtrl: FacetingControl;
let popupContainer: PopupContainer;
let facetButton: HTMLButtonElement;
let configPanel: HTMLDivElement;
let closeButton: HTMLElement;

beforeEach(async () => {
// Set up.
Expand All @@ -31,12 +34,9 @@ describe('faceting control test', () => {
document.body.appendChild(facetCtrl);
await facetCtrl.updateComplete;

facetButton =
facetCtrl.renderRoot.children[0].children[0] as HTMLButtonElement;
configPanel =
facetCtrl.renderRoot.children[0].children[2] as HTMLDivElement;
closeButton =
configPanel.querySelector('.icon-button') as HTMLElement;
popupContainer = facetCtrl.renderRoot.children[0] as PopupContainer;
facetButton = popupContainer.children[0].children[0] as HTMLButtonElement;
configPanel = popupContainer.children[1] as HTMLDivElement;
});

afterEach(() => {
Expand All @@ -51,52 +51,18 @@ describe('faceting control test', () => {
it('comprises a div with a button, span, and div as children', () => {
expect(facetCtrl.renderRoot.children.length).toEqual(1);

const innerDiv = facetCtrl.renderRoot.children[0];
expect(innerDiv instanceof HTMLDivElement).toBeTrue();
expect((innerDiv as HTMLDivElement).className).toEqual('faceting-info');
expect(innerDiv.children.length).toEqual(3);
expect(popupContainer instanceof PopupContainer).toBeTrue();
expect(popupContainer.children.length).toEqual(2);

const [facetButton, facetList, configPanel] = innerDiv.children;
const [facetingInfo, configPanel] = popupContainer.children;
expect(facetingInfo instanceof HTMLDivElement).toBeTrue();
expect((facetingInfo as HTMLDivElement).className).toEqual('faceting-info');
const [facetButton, facetList] = facetingInfo.children;
expect(facetButton instanceof HTMLButtonElement).toBeTrue();
expect(facetList instanceof HTMLSpanElement).toBeTrue();
expect((facetList as HTMLSpanElement).className).toEqual(' active-facets ');
expect(facetList instanceof HTMLDivElement).toBeTrue();
expect((facetList as HTMLDivElement).className).toEqual(' active-facets ');
expect(configPanel instanceof HTMLDivElement).toBeTrue();
expect((configPanel as HTMLDivElement).className)
.toEqual('config-panel popup-container');
});

it('shows configPanel after facet button click', async () => {
facetButton.click();
await facetCtrl.updateComplete;
expect(configPanel.style.display).toEqual('flex');
expect(configPanel.style.visibility).toEqual('visible');
});

it('hides configPanel after second facet button click', async () => {
facetButton.click();
await facetCtrl.updateComplete;
facetButton.click();
await facetCtrl.updateComplete;
expect(configPanel.style.display).toEqual('none');
expect(configPanel.style.visibility).toEqual('hidden');
});

it('hides configPanel after closeButton click', async () => {
facetButton.click();
await facetCtrl.updateComplete;
closeButton.click();
await facetCtrl.updateComplete;
expect(configPanel.style.display).toEqual('none');
expect(configPanel.style.visibility).toEqual('hidden');
});

it('hides configPanel by clicking anything else', async () => {
facetButton.click();
await facetCtrl.updateComplete;
document.body.click();
await facetCtrl.updateComplete;
expect(configPanel.style.display).toEqual('none');
expect(configPanel.style.visibility).toEqual('hidden');
expect((configPanel as HTMLDivElement).className).toEqual('config-panel');
});

it('emits a custom facets-change event after checkbox click', async () => {
Expand Down
Loading

0 comments on commit 670abeb

Please sign in to comment.