import {type CSSResultGroup, html, unsafeCSS} from 'lit'; import {eventOptions, property} from 'lit/decorators.js'; import {on} from "../../utilities/on"; import {Store} from "../../internal/storage"; import ZincElement from '../../internal/zinc-element'; import styles from './split-pane.scss'; /** * @summary Short summary of the component's intended use. * @documentation https://zinc.style/components/split-pane * @status experimental * @since 1.0 * * @dependency zn-example * * @event zn-event-name - Emitted as an example. * * @slot - The default slot. * @slot example - An example slot. * * @csspart base - The component's base wrapper. * * @cssproperty --example - An example CSS custom property. */ export default class ZnSplitPane extends ZincElement { static styles: CSSResultGroup = unsafeCSS(styles); storage: Storage; mouseMoveHandler: null | EventListener = null; mouseUpHandler: null | EventListener = null; private currentPixelSize: number = 0; private currentPercentSize: number = 0; private currentContainerSize: number = 0; private primaryFull: string; @property({attribute: 'pixels', type: Boolean, reflect: true}) calculatePixels = false; @property({attribute: 'secondary', type: Boolean, reflect: true}) preferSecondarySize = false; @property({attribute: 'min-size', type: Number, reflect: true}) minimumPaneSize = 10; @property({attribute: 'max-size', type: Number, reflect: true}) maximumPaneSize: number; @property({attribute: 'initial-size', type: Number, reflect: true}) initialSize = 50; @property({attribute: 'store-key', reflect: true}) storeKey: string = ""; @property({attribute: 'bordered', type: Boolean, reflect: true}) border = false; @property({attribute: 'vertical', type: Boolean, reflect: true}) vertical = false; @property({attribute: 'primary-caption', reflect: true}) primaryCaption = 'Primary'; @property({attribute: 'secondary-caption', reflect: true}) secondaryCaption = 'Secondary'; @property({attribute: 'focus-pane', type: Number, reflect: true}) _focusPane = 0; @property({attribute: 'padded', type: Boolean, reflect: true}) padded = false; @property({attribute: 'padded-right', type: Boolean, reflect: true}) paddedRight = false; // session storage if not local @property({attribute: 'local-storage', type: Boolean, reflect: true}) localStorage: boolean; @property({attribute: 'store-ttl', type: Number, reflect: true}) storeTtl = 0; protected _store: Store; connectedCallback() { super.connectedCallback(); this._store = new Store(this.localStorage ? window.localStorage : window.sessionStorage, "znsp:", this.storeTtl); this.primaryFull = this.calculatePixels ? this.initialSize + 'px' : this.initialSize + '%'; on(this, 'click', '[split-pane-focus]', (e: Event & { selectedTarget: EventTarget }) => { e.preventDefault(); e.stopPropagation(); this._setFocusPane(parseInt((e.selectedTarget as HTMLElement).getAttribute('split-pane-focus')!)); }); } firstUpdated(changedProperties: any) { setTimeout(this.applyStoredSize.bind(this), 100); super.firstUpdated(changedProperties); } applyStoredSize() { this.currentContainerSize = (this.vertical ? this.getBoundingClientRect().height : this.getBoundingClientRect().width); let applyPixels = this.preferSecondarySize ? this.currentContainerSize - this.initialSize : this.initialSize; let applyPercent = this.preferSecondarySize ? 100 - this.initialSize : this.initialSize; const storedValue = this._store.get(this.storeKey); if (storedValue != null) { const parts = storedValue.split(","); if (parts.length >= 3) { applyPixels = parseInt(parts[0]); applyPercent = parseInt(parts[1]); const storedBasis = parseInt(parts[2]); if (this.preferSecondarySize && this.calculatePixels) { applyPixels = (this.currentContainerSize / storedBasis) * applyPixels; } } } this.setSize(this.calculatePixels ? applyPixels : (this.currentContainerSize / 100) * applyPercent); } @eventOptions({passive: true}) resize(e: any) { if (this.mouseUpHandler != null) { this.mouseUpHandler(e); } this.classList.add('resizing'); this.currentContainerSize = this.vertical ? this.getBoundingClientRect().height : this.getBoundingClientRect().width; const pageOffset = this.vertical ? this.getBoundingClientRect().top : this.getBoundingClientRect().left; this.mouseMoveHandler = function (e: any) { let offset = (this.vertical ? e.y : e.x); //'touches' in e fixes Safari if ('touches' in e && e instanceof TouchEvent) { offset = (this.vertical ? e.touches[0].clientY : e.touches[0].clientX); } this.setSize(offset - pageOffset); }.bind(this); this.mouseUpHandler = function () { this._store.set(this.storeKey, Math.round(this.currentPixelSize) + "," + Math.round(this.currentPercentSize) + "," + this.currentContainerSize); this.classList.remove('resizing'); window.removeEventListener('touchmove', this.mouseMoveHandler); window.removeEventListener('mousemove', this.mouseMoveHandler); window.removeEventListener('touchend', this.mouseUpHandler); window.removeEventListener('mouseup', this.mouseUpHandler); }.bind(this); window.addEventListener('touchmove', this.mouseMoveHandler); window.addEventListener('touchend', this.mouseUpHandler); window.addEventListener('mousemove', this.mouseMoveHandler); window.addEventListener('mouseup', this.mouseUpHandler); } setSize(primaryPanelPixels: number) { let pixelSize = primaryPanelPixels; let percentSize = (primaryPanelPixels / this.currentContainerSize) * 100; if (this.calculatePixels) { if (this.maximumPaneSize) { pixelSize = Math.max(this.minimumPaneSize, Math.min(this.maximumPaneSize, pixelSize)); } else { pixelSize = Math.max(this.minimumPaneSize, pixelSize); } this.initialSize = pixelSize; } else { if (this.maximumPaneSize) { percentSize = Math.max(this.minimumPaneSize, Math.min(this.maximumPaneSize, percentSize)); } else { percentSize = Math.max(this.minimumPaneSize, percentSize); } this.initialSize = percentSize; } this.currentPixelSize = pixelSize; this.currentPercentSize = percentSize; this.primaryFull = this.calculatePixels ? (this.currentPixelSize + 'px') : (this.currentPercentSize + '%'); } _togglePane(e: any) { this._setFocusPane(e.target.getAttribute('idx')); } _setFocusPane(idx: number) { this._focusPane = idx; this.querySelectorAll('ul#split-nav li').forEach((el) => { el.classList.toggle('active', parseInt(el.getAttribute('idx')!) == idx); }); } protected render(): unknown { const resizeWidth = '2px'; const resizeMargin = '5px'; return html`