diff --git a/src/components/output/custom-els/MultiPanel/index.ts b/src/components/compress/custom-els/MultiPanel/index.ts similarity index 82% rename from src/components/output/custom-els/MultiPanel/index.ts rename to src/components/compress/custom-els/MultiPanel/index.ts index 95a06dcd..3a5cef8f 100644 --- a/src/components/output/custom-els/MultiPanel/index.ts +++ b/src/components/compress/custom-els/MultiPanel/index.ts @@ -1,8 +1,10 @@ -import './styles.css'; +import * as style from './styles.css'; + +const openOneOnlyAttr = 'open-one-only'; function getClosestHeading(el: Element) { const closestEl = el.closest('multi-panel > *'); - if (closestEl && closestEl.classList.contains('panel-heading')) { + if (closestEl && closestEl.classList.contains(style.panelHeading)) { return closestEl; } return undefined; @@ -14,6 +16,7 @@ function getClosestHeading(el: Element) { * and odd index element becomes the expandable content. */ export default class MultiPanel extends HTMLElement { + static get observedAttributes() { return [openOneOnlyAttr]; } constructor() { super(); @@ -31,12 +34,18 @@ export default class MultiPanel extends HTMLElement { this._childrenChange(); } + attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null) { + if (name === openOneOnlyAttr && newValue === null) { + // TODO + } + } + // Click event handler private _onClick(event: MouseEvent) { const el = event.target as Element; const heading = getClosestHeading(el); if (!heading) return; - this._expand(heading); + this._toggle(heading); } // KeyDown event handler @@ -77,7 +86,7 @@ export default class MultiPanel extends HTMLElement { case 'Enter': case ' ': case 'Spacebar': - this._expand(heading); + this._toggle(heading); break; // 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; const content = heading.nextElementSibling; @@ -105,11 +114,21 @@ export default class MultiPanel extends HTMLElement { content.removeAttribute('expanded'); content.setAttribute('aria-expanded', 'false'); } else { + if (this.openOneOnly) this._closeAll(); content.setAttribute('expanded', ''); 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) private _childrenChange() { let preserveTabIndex : boolean = false; @@ -130,10 +149,10 @@ export default class MultiPanel extends HTMLElement { // When odd number of elements were inserted in the middle, // what was heading before may become content after the insertion. // 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')) { - content.classList.remove('panel-heading'); + if (content.classList.contains(style.panelHeading)) { + content.classList.remove(style.panelHeading); } if (heading.hasAttribute('expanded') && heading.hasAttribute('aria-expanded')) { heading.removeAttribute('expanded'); @@ -146,8 +165,8 @@ export default class MultiPanel extends HTMLElement { } // Assign heading and content classes - heading.classList.add('panel-heading'); - content.classList.add('panel-content'); + heading.classList.add(style.panelHeading); + content.classList.add(style.panelContent); // Assign ids and aria-X for heading/content pair. heading.id = `panel-heading-${randomId}`; @@ -208,7 +227,7 @@ export default class MultiPanel extends HTMLElement { private _lastHeading() { // if the last element is heading, return last element const lastEl = this.lastElementChild as HTMLElement; - if (lastEl && lastEl.classList.contains('panel-heading')) { + if (lastEl && lastEl.classList.contains(style.panelHeading)) { return lastEl; } // otherwise return 2nd from the last @@ -217,6 +236,21 @@ export default class MultiPanel extends 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); diff --git a/src/components/compress/custom-els/MultiPanel/missing-types.d.ts b/src/components/compress/custom-els/MultiPanel/missing-types.d.ts new file mode 100644 index 00000000..b775fa8a --- /dev/null +++ b/src/components/compress/custom-els/MultiPanel/missing-types.d.ts @@ -0,0 +1,9 @@ +interface MultiPanelAttributes extends JSX.HTMLAttributes { + 'open-one-only'?: boolean; +} + +declare namespace JSX { + interface IntrinsicElements { + 'multi-panel': MultiPanelAttributes; + } +} diff --git a/src/components/compress/custom-els/MultiPanel/styles.css b/src/components/compress/custom-els/MultiPanel/styles.css new file mode 100644 index 00000000..286c5fa7 --- /dev/null +++ b/src/components/compress/custom-els/MultiPanel/styles.css @@ -0,0 +1,11 @@ +.panel-heading { + background:gray; +} +.panel-content { + height:0px; + overflow:scroll; + transition: height 1s; +} +.panel-content[expanded] { + height:auto; +} diff --git a/src/components/compress/index.tsx b/src/components/compress/index.tsx index 83e4ab58..76ca993a 100644 --- a/src/components/compress/index.tsx +++ b/src/components/compress/index.tsx @@ -24,16 +24,15 @@ import { EncoderOptions, encoderMap, } from '../../codecs/encoders'; - import { PreprocessorState, defaultPreprocessorState, } from '../../codecs/preprocessors'; - import { decodeImage } from '../../codecs/decoders'; import { cleanMerge, cleanSet } from '../../lib/clean-modify'; import Processor from '../../codecs/processor'; import { VectorResizeOptions, BitmapResizeOptions } from '../../codecs/resize/processor-meta'; +import './custom-els/MultiPanel'; export interface SourceImage { file: File | Fileish; @@ -397,6 +396,23 @@ export default class Compress extends Component { const [leftImage, rightImage] = images; const [leftImageData, rightImageData] = images.map(i => i.data); + const options = images.map((image, index) => ( + + )); + return (
{ leftImgContain={leftImage.preprocessorState.resize.fitMethod === 'cover'} rightImgContain={rightImage.preprocessorState.resize.fitMethod === 'cover'} /> - {images.map((image, index) => ( - - ))} + {mobileView + ? ( + +
Top
+ {options[0]} +
Bottom
+ {options[1]} +
+ ) : options + }
); } diff --git a/src/components/compress/style.scss b/src/components/compress/style.scss index 211e6813..23ae1061 100644 --- a/src/components/compress/style.scss +++ b/src/components/compress/style.scss @@ -16,3 +16,7 @@ grid-template-rows: 100%; } } + +.multi-panel { + position: relative; +} diff --git a/src/components/output/custom-els/MultiPanel/styles.css b/src/components/output/custom-els/MultiPanel/styles.css deleted file mode 100644 index 97047454..00000000 --- a/src/components/output/custom-els/MultiPanel/styles.css +++ /dev/null @@ -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; -}