mirror of
https://github.com/GoogleChromeLabs/squoosh.git
synced 2025-11-14 17:49:52 +00:00
Allow multi-panel to keep one open only
This commit is contained in:
@@ -1,8 +1,10 @@
|
|||||||
import './styles.css';
|
import * as style from './styles.css';
|
||||||
|
|
||||||
|
const openOneOnlyAttr = 'open-one-only';
|
||||||
|
|
||||||
function getClosestHeading(el: Element) {
|
function getClosestHeading(el: Element) {
|
||||||
const closestEl = el.closest('multi-panel > *');
|
const closestEl = el.closest('multi-panel > *');
|
||||||
if (closestEl && closestEl.classList.contains('panel-heading')) {
|
if (closestEl && closestEl.classList.contains(style.panelHeading)) {
|
||||||
return closestEl;
|
return closestEl;
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -14,6 +16,7 @@ function getClosestHeading(el: Element) {
|
|||||||
* and odd index element becomes the expandable content.
|
* and odd index element becomes the expandable content.
|
||||||
*/
|
*/
|
||||||
export default class MultiPanel extends HTMLElement {
|
export default class MultiPanel extends HTMLElement {
|
||||||
|
static get observedAttributes() { return [openOneOnlyAttr]; }
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
@@ -31,12 +34,18 @@ export default class MultiPanel extends HTMLElement {
|
|||||||
this._childrenChange();
|
this._childrenChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null) {
|
||||||
|
if (name === openOneOnlyAttr && newValue === null) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Click event handler
|
// Click event handler
|
||||||
private _onClick(event: MouseEvent) {
|
private _onClick(event: MouseEvent) {
|
||||||
const el = event.target as Element;
|
const el = event.target as Element;
|
||||||
const heading = getClosestHeading(el);
|
const heading = getClosestHeading(el);
|
||||||
if (!heading) return;
|
if (!heading) return;
|
||||||
this._expand(heading);
|
this._toggle(heading);
|
||||||
}
|
}
|
||||||
|
|
||||||
// KeyDown event handler
|
// KeyDown event handler
|
||||||
@@ -77,7 +86,7 @@ export default class MultiPanel extends HTMLElement {
|
|||||||
case 'Enter':
|
case 'Enter':
|
||||||
case ' ':
|
case ' ':
|
||||||
case 'Spacebar':
|
case 'Spacebar':
|
||||||
this._expand(heading);
|
this._toggle(heading);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Any other key press is ignored and passed back to the browser.
|
// Any other key press is ignored and passed back to the browser.
|
||||||
@@ -93,7 +102,7 @@ export default class MultiPanel extends HTMLElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _expand(heading: Element) {
|
private _toggle(heading: Element) {
|
||||||
if (!heading) return;
|
if (!heading) return;
|
||||||
const content = heading.nextElementSibling;
|
const content = heading.nextElementSibling;
|
||||||
|
|
||||||
@@ -105,11 +114,21 @@ export default class MultiPanel extends HTMLElement {
|
|||||||
content.removeAttribute('expanded');
|
content.removeAttribute('expanded');
|
||||||
content.setAttribute('aria-expanded', 'false');
|
content.setAttribute('aria-expanded', 'false');
|
||||||
} else {
|
} else {
|
||||||
|
if (this.openOneOnly) this._closeAll();
|
||||||
content.setAttribute('expanded', '');
|
content.setAttribute('expanded', '');
|
||||||
content.setAttribute('aria-expanded', 'true');
|
content.setAttribute('aria-expanded', 'true');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _closeAll(): void {
|
||||||
|
const els = [...this.children].filter(el => el.matches('[expanded]'));
|
||||||
|
|
||||||
|
for (const el of els) {
|
||||||
|
el.removeAttribute('expanded');
|
||||||
|
el.setAttribute('aria-expanded', 'false');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// children of multi-panel should always be even number (heading/content pair)
|
// children of multi-panel should always be even number (heading/content pair)
|
||||||
private _childrenChange() {
|
private _childrenChange() {
|
||||||
let preserveTabIndex : boolean = false;
|
let preserveTabIndex : boolean = false;
|
||||||
@@ -130,10 +149,10 @@ export default class MultiPanel extends HTMLElement {
|
|||||||
// When odd number of elements were inserted in the middle,
|
// When odd number of elements were inserted in the middle,
|
||||||
// what was heading before may become content after the insertion.
|
// what was heading before may become content after the insertion.
|
||||||
// Remove classes and attributes to prepare for this change.
|
// Remove classes and attributes to prepare for this change.
|
||||||
heading.classList.remove('panel-content');
|
heading.classList.remove(style.panelContent);
|
||||||
|
|
||||||
if (content.classList.contains('panel-heading')) {
|
if (content.classList.contains(style.panelHeading)) {
|
||||||
content.classList.remove('panel-heading');
|
content.classList.remove(style.panelHeading);
|
||||||
}
|
}
|
||||||
if (heading.hasAttribute('expanded') && heading.hasAttribute('aria-expanded')) {
|
if (heading.hasAttribute('expanded') && heading.hasAttribute('aria-expanded')) {
|
||||||
heading.removeAttribute('expanded');
|
heading.removeAttribute('expanded');
|
||||||
@@ -146,8 +165,8 @@ export default class MultiPanel extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Assign heading and content classes
|
// Assign heading and content classes
|
||||||
heading.classList.add('panel-heading');
|
heading.classList.add(style.panelHeading);
|
||||||
content.classList.add('panel-content');
|
content.classList.add(style.panelContent);
|
||||||
|
|
||||||
// Assign ids and aria-X for heading/content pair.
|
// Assign ids and aria-X for heading/content pair.
|
||||||
heading.id = `panel-heading-${randomId}`;
|
heading.id = `panel-heading-${randomId}`;
|
||||||
@@ -208,7 +227,7 @@ export default class MultiPanel extends HTMLElement {
|
|||||||
private _lastHeading() {
|
private _lastHeading() {
|
||||||
// if the last element is heading, return last element
|
// if the last element is heading, return last element
|
||||||
const lastEl = this.lastElementChild as HTMLElement;
|
const lastEl = this.lastElementChild as HTMLElement;
|
||||||
if (lastEl && lastEl.classList.contains('panel-heading')) {
|
if (lastEl && lastEl.classList.contains(style.panelHeading)) {
|
||||||
return lastEl;
|
return lastEl;
|
||||||
}
|
}
|
||||||
// otherwise return 2nd from the last
|
// otherwise return 2nd from the last
|
||||||
@@ -217,6 +236,21 @@ export default class MultiPanel extends HTMLElement {
|
|||||||
return lastContent.previousElementSibling as HTMLElement;
|
return lastContent.previousElementSibling as HTMLElement;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, only one panel can be open at once. When one opens, others close.
|
||||||
|
*/
|
||||||
|
get openOneOnly() {
|
||||||
|
return this.hasAttribute(openOneOnlyAttr);
|
||||||
|
}
|
||||||
|
|
||||||
|
set openOneOnly(val: boolean) {
|
||||||
|
if (val) {
|
||||||
|
this.setAttribute(openOneOnlyAttr, '');
|
||||||
|
} else {
|
||||||
|
this.removeAttribute(openOneOnlyAttr);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define('multi-panel', MultiPanel);
|
customElements.define('multi-panel', MultiPanel);
|
||||||
9
src/components/compress/custom-els/MultiPanel/missing-types.d.ts
vendored
Normal file
9
src/components/compress/custom-els/MultiPanel/missing-types.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
interface MultiPanelAttributes extends JSX.HTMLAttributes {
|
||||||
|
'open-one-only'?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare namespace JSX {
|
||||||
|
interface IntrinsicElements {
|
||||||
|
'multi-panel': MultiPanelAttributes;
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/components/compress/custom-els/MultiPanel/styles.css
Normal file
11
src/components/compress/custom-els/MultiPanel/styles.css
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
.panel-heading {
|
||||||
|
background:gray;
|
||||||
|
}
|
||||||
|
.panel-content {
|
||||||
|
height:0px;
|
||||||
|
overflow:scroll;
|
||||||
|
transition: height 1s;
|
||||||
|
}
|
||||||
|
.panel-content[expanded] {
|
||||||
|
height:auto;
|
||||||
|
}
|
||||||
@@ -24,16 +24,15 @@ import {
|
|||||||
EncoderOptions,
|
EncoderOptions,
|
||||||
encoderMap,
|
encoderMap,
|
||||||
} from '../../codecs/encoders';
|
} from '../../codecs/encoders';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
PreprocessorState,
|
PreprocessorState,
|
||||||
defaultPreprocessorState,
|
defaultPreprocessorState,
|
||||||
} from '../../codecs/preprocessors';
|
} from '../../codecs/preprocessors';
|
||||||
|
|
||||||
import { decodeImage } from '../../codecs/decoders';
|
import { decodeImage } from '../../codecs/decoders';
|
||||||
import { cleanMerge, cleanSet } from '../../lib/clean-modify';
|
import { cleanMerge, cleanSet } from '../../lib/clean-modify';
|
||||||
import Processor from '../../codecs/processor';
|
import Processor from '../../codecs/processor';
|
||||||
import { VectorResizeOptions, BitmapResizeOptions } from '../../codecs/resize/processor-meta';
|
import { VectorResizeOptions, BitmapResizeOptions } from '../../codecs/resize/processor-meta';
|
||||||
|
import './custom-els/MultiPanel';
|
||||||
|
|
||||||
export interface SourceImage {
|
export interface SourceImage {
|
||||||
file: File | Fileish;
|
file: File | Fileish;
|
||||||
@@ -397,6 +396,23 @@ export default class Compress extends Component<Props, State> {
|
|||||||
const [leftImage, rightImage] = images;
|
const [leftImage, rightImage] = images;
|
||||||
const [leftImageData, rightImageData] = images.map(i => i.data);
|
const [leftImageData, rightImageData] = images.map(i => i.data);
|
||||||
|
|
||||||
|
const options = images.map((image, index) => (
|
||||||
|
<Options
|
||||||
|
loading={loading || image.loading}
|
||||||
|
source={source}
|
||||||
|
mobileView={mobileView}
|
||||||
|
imageIndex={index}
|
||||||
|
imageFile={image.file}
|
||||||
|
downloadUrl={image.downloadUrl}
|
||||||
|
preprocessorState={image.preprocessorState}
|
||||||
|
encoderState={image.encoderState}
|
||||||
|
onEncoderTypeChange={this.onEncoderTypeChange.bind(this, index)}
|
||||||
|
onEncoderOptionsChange={this.onEncoderOptionsChange.bind(this, index)}
|
||||||
|
onPreprocessorOptionsChange={this.onPreprocessorOptionsChange.bind(this, index)}
|
||||||
|
onCopyToOtherClick={this.onCopyToOtherClick.bind(this, index)}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={style.compress}>
|
<div class={style.compress}>
|
||||||
<Output
|
<Output
|
||||||
@@ -407,22 +423,16 @@ export default class Compress extends Component<Props, State> {
|
|||||||
leftImgContain={leftImage.preprocessorState.resize.fitMethod === 'cover'}
|
leftImgContain={leftImage.preprocessorState.resize.fitMethod === 'cover'}
|
||||||
rightImgContain={rightImage.preprocessorState.resize.fitMethod === 'cover'}
|
rightImgContain={rightImage.preprocessorState.resize.fitMethod === 'cover'}
|
||||||
/>
|
/>
|
||||||
{images.map((image, index) => (
|
{mobileView
|
||||||
<Options
|
? (
|
||||||
loading={loading || image.loading}
|
<multi-panel class={style.multiPanel} open-one-only>
|
||||||
source={source}
|
<div>Top</div>
|
||||||
mobileView={mobileView}
|
{options[0]}
|
||||||
imageIndex={index}
|
<div>Bottom</div>
|
||||||
imageFile={image.file}
|
{options[1]}
|
||||||
downloadUrl={image.downloadUrl}
|
</multi-panel>
|
||||||
preprocessorState={image.preprocessorState}
|
) : options
|
||||||
encoderState={image.encoderState}
|
}
|
||||||
onEncoderTypeChange={this.onEncoderTypeChange.bind(this, index)}
|
|
||||||
onEncoderOptionsChange={this.onEncoderOptionsChange.bind(this, index)}
|
|
||||||
onPreprocessorOptionsChange={this.onPreprocessorOptionsChange.bind(this, index)}
|
|
||||||
onCopyToOtherClick={this.onCopyToOtherClick.bind(this, index)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,3 +16,7 @@
|
|||||||
grid-template-rows: 100%;
|
grid-template-rows: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.multi-panel {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
multi-panel > .panel-heading {
|
|
||||||
background:gray;
|
|
||||||
}
|
|
||||||
multi-panel > .panel-content {
|
|
||||||
height:0px;
|
|
||||||
overflow:scroll;
|
|
||||||
transition: height 1s;
|
|
||||||
}
|
|
||||||
multi-panel > .panel-content[expanded] {
|
|
||||||
height:auto;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user