From d961851a31ec33da739ba81eaffc9368664c17f7 Mon Sep 17 00:00:00 2001 From: lorenzodianni Date: Sat, 7 Jan 2023 13:54:51 +0100 Subject: [PATCH] feat: control media-query via js | #19 --- src/sidebar.core.ts | 15 ++++++++++++++- src/sidebar.element.ts | 33 +++++++++++++++++++++++++++++---- src/sidebarjs.scss | 13 ++++++++++++- 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/sidebar.core.ts b/src/sidebar.core.ts index 5a9a4bb..136c403 100644 --- a/src/sidebar.core.ts +++ b/src/sidebar.core.ts @@ -3,11 +3,24 @@ export const SIDEBARJS_FALLBACK_NAME = ''; export const SIDEBARJS_CONTENT = 'sidebarjs-content'; export const SIDEBARJS_TRANSITION_START = 'sidebarjs--transition-start'; export const SIDEBARJS_TRANSITION_END = 'sidebarjs--transition-end'; +export const SIDEBARJS_RESPONSIVE = 'sidebarjs--responsive'; export const IS_VISIBLE = `${SIDEBARJS}--is-visible`; export const IS_MOVING = `${SIDEBARJS}--is-moving`; export const POSITIONS: SidebarPosition[] = [SidebarPosition.Left, SidebarPosition.Right]; export const EVENT_LISTENER_OPTIONS: AddEventListenerOptions = {passive: true}; +export const MediaQuery = { + pattern: new RegExp(/((\d*\.?\d+\s*)(px|em|rem))/), + DEFAULT_BREAKPOINT: '1025px', + getValidBreakpoint(value: SidebarConfig['responsive']) { + if (this.pattern.test(value as any)) { + return value as string; + } + console.warn(`Provided invalid breakpoint value: ${value}, fallback to ${this.DEFAULT_BREAKPOINT}`); + return this.DEFAULT_BREAKPOINT; + }, +}; + export const enum SidebarPosition { Left = 'left', Right = 'right', @@ -51,7 +64,7 @@ export interface SidebarConfig { documentSwipeRange?: number; nativeSwipe?: boolean; nativeSwipeOpen?: boolean; - responsive?: boolean; + responsive?: boolean | string; mainContent?: HTMLElement; position?: SidebarPosition; backdropOpacity?: number; diff --git a/src/sidebar.element.ts b/src/sidebar.element.ts index 8889796..6c4c82a 100644 --- a/src/sidebar.element.ts +++ b/src/sidebar.element.ts @@ -8,6 +8,7 @@ import { IS_VISIBLE, isStyleMapSupported, MapGestureEvent, + MediaQuery, POSITIONS, shouldDefineMainContent, shouldInvokeFunction, @@ -16,6 +17,7 @@ import { SidebarConfig, SIDEBARJS, SIDEBARJS_CONTENT, + SIDEBARJS_RESPONSIVE, SIDEBARJS_TRANSITION_END, SIDEBARJS_TRANSITION_START, SidebarPosition, @@ -52,6 +54,7 @@ export class SidebarElement implements SidebarBase { private readonly _emitOnOpen?: CallableFunction; private readonly _emitOnClose?: CallableFunction; private readonly _emitOnChangeVisibility?: (changes: SidebarChangeEvent) => void; + private readonly _destroyResponsive?: () => void; constructor(options: SidebarConfig = {}) { const config = {...DEFAULT_CONFIG, ...options}; @@ -83,7 +86,7 @@ export class SidebarElement implements SidebarBase { this.setSwipeGestures(true); if (this.responsive || this.mainContent) { - this.setResponsive(); + this._destroyResponsive = this.setResponsive(config.responsive); } this.setPosition(config.position!); @@ -117,12 +120,13 @@ export class SidebarElement implements SidebarBase { this.backdrop.removeEventListener('click', this.close); this.removeNativeOpenGestures(); this.removeAttrsEventsListeners(this.component.getAttribute(SIDEBARJS)!); - this.removeComponentClassPosition(); + this.removeComponentClassPosition(true); while (this.container.firstElementChild) { this.component.appendChild(this.container.firstElementChild); } this.component.removeChild(this.container); this.component.removeChild(this.backdrop); + this._destroyResponsive?.(); Object.keys(this).forEach((key) => ((this as any)[key] = null)); } @@ -343,14 +347,35 @@ export class SidebarElement implements SidebarBase { this.applyStyle(this.backdrop, 'opacity', opacity.toString()); } - private setResponsive(): void { + private setResponsive(breakpoint?: SidebarConfig['responsive']) { if (!this.responsive && this.mainContent) { throw new Error(`You provide a [${SIDEBARJS_CONTENT}] element without set {responsive: true}`); } if (this.responsive && !this.mainContent) { throw new Error(`You have set {responsive: true} without provide a [${SIDEBARJS_CONTENT}] element`); } - this.addComponentClass('sidebarjs--responsive'); + this.addComponentClass(SIDEBARJS_RESPONSIVE); + const destroyMediaQuery = this.setMediaQuery(breakpoint); + return () => { + destroyMediaQuery(); + this.removeComponentClass(SIDEBARJS_RESPONSIVE); + }; + } + + private setMediaQuery(breakpoint?: SidebarConfig['responsive']) { + const value = MediaQuery.getValidBreakpoint(breakpoint); + const mediaQuery = window.matchMedia(`(min-width: ${value})`); + const toggleMediaQueryClass = (e: MediaQueryList | MediaQueryListEvent) => this.toggleDesktopBreakpointClass(e.matches); + mediaQuery.addEventListener('change', toggleMediaQueryClass, EVENT_LISTENER_OPTIONS); + toggleMediaQueryClass(mediaQuery); + return () => { + mediaQuery.removeEventListener('change', toggleMediaQueryClass); + this.toggleDesktopBreakpointClass(false); + } + } + + private toggleDesktopBreakpointClass(isDesktop: boolean) { + document.body.classList.toggle('sidebarjs--desktop-breakpoint', isDesktop); } private applyStyle(el: HTMLElement, prop: CSSStyleProperties, val: CSSStyleValues, vendorify?: boolean): void { diff --git a/src/sidebarjs.scss b/src/sidebarjs.scss index aafaf61..29cfdb4 100644 --- a/src/sidebarjs.scss +++ b/src/sidebarjs.scss @@ -37,6 +37,7 @@ $width: 300px; [sidebarjs].sidebarjs--left { @extend %component--left; + [sidebarjs-container] { @extend %component--left; @include shadow('left'); @@ -45,6 +46,7 @@ $width: 300px; [sidebarjs].sidebarjs--right { @extend %component--right; + [sidebarjs-container] { @extend %component--right; @include shadow('right'); @@ -87,6 +89,7 @@ $width: 300px; &.sidebarjs--is-moving { transition: none; transform: translate(0, 0); + [sidebarjs-container], [sidebarjs-backdrop] { transition: none; } @@ -98,34 +101,41 @@ $width: 300px; width: 100%; min-height: 100%; transition: width $duration $timing; + &.sidebarjs-content--left { margin-left: auto; margin-right: 0; } + &.sidebarjs-content--right { margin-left: 0; margin-right: auto; } } -@media (min-width: 1025px) { +.sidebarjs--desktop-breakpoint { [sidebarjs].sidebarjs--responsive { @include component--is-visible(!important); width: $width; + &.sidebarjs--left { left: 0; right: auto; + [sidebarjs-container] { box-shadow: 1px 0 0 rgba(black, .1); } } + &.sidebarjs--right { right: 0; left: auto; + [sidebarjs-container] { box-shadow: -1px 0 0 rgba(black, .1); } } + [sidebarjs-container] { max-width: none; width: 100%; @@ -135,6 +145,7 @@ $width: 300px; [sidebarjs-content] { width: calc(100% - #{$width}); + &.sidebarjs-content--left.sidebarjs-content--right { width: calc(100% - #{$width*2}); margin: 0 auto;