UNPKG

@ribajs/bs5

Version:

Bootstrap 5 module for Riba.js

186 lines (165 loc) 5.57 kB
import { BasicComponent, Binder, View } from "@ribajs/core"; import { Bs5Service } from "../services/bs5.service.js"; import { parseJsonString, jsonStringify, camelCase } from "@ribajs/utils"; export abstract class Bs5AbstractBreakpointBinder< E extends HTMLElement = HTMLElement, > extends Binder<string, E> { protected abstract defaultAttributeBinder: Binder< any, HTMLElement | BasicComponent >; protected bs5: Bs5Service; protected breakpoint: string; protected attributeName: string; protected val?: any; constructor( view: View, el: E, type: string | null, name: string, keypath: string | undefined, formatters: string[] | null, identifier: string | null, ) { super(view, el, type, name, keypath, formatters, identifier); this.bs5 = Bs5Service.getSingleton(); if (this.args.length !== 2) { throw new Error( "The Bs5AttributeBreakpointBinder was not initialized correctly!", ); } const breakpoint = this.args[0].toString(); const attributeName = this.args[1].toString(); if (!this.bs5.breakpointNames.includes(breakpoint)) { throw new Error( `Unknown breakpoint "${breakpoint}"! You can define breakpoints at the initialization of the Bs5Module.`, ); } this.breakpoint = breakpoint; this.attributeName = attributeName; } protected onBreakpointChanges(/* activeBreakpoint: Breakpoint */) { this.setAttributeOnMatch(); } /** * Sets the attribute if the current breakpoint matches * @returns */ protected setAttributeOnMatch() { if (!this.bs5.activeBreakpoint) return; if (this.isBreakpointMatch(this.breakpoint)) { return this.defaultAttributeBinder.routine(this.el, this.val); } if (this.breakpointUnhandled(this.bs5.activeBreakpoint.name)) { // Remove attribute this.defaultAttributeBinder.routine(this.el, undefined); } } /** * Returns `true` if the current active breakpoint matches the breakpoints handled by this binder * @param breakpoint * @returns */ protected isBreakpointMatch(breakpoint = this.breakpoint) { if (!this.bs5.activeBreakpoint) { return false; } if (this.bs5.activeBreakpoint.name === breakpoint) { return true; } const myBreakpoints = this.myBreakpoints(breakpoint); if (myBreakpoints.includes(this.bs5.activeBreakpoint.name)) { return true; } return false; } /** * Returns all breakpoints handled by this binder. * This binder handles multiple breakpoints if other larger breakpoints exists but are not handled by another binder. * @returns */ protected myBreakpoints(breakpoint = this.breakpoint) { const myBreakpoints: string[] = [breakpoint]; let nextBreakpoint: string | null = breakpoint; while (nextBreakpoint) { nextBreakpoint = this.bs5.getNextBreakpointByName(nextBreakpoint); if (!nextBreakpoint || this.breakpointHandledByAnother(nextBreakpoint)) { break; } myBreakpoints.push(nextBreakpoint); } return myBreakpoints; } /** * Check if another breakpoint (mostly the next breakpoint) is handled by another binder */ protected breakpointHandledByAnother(name: string) { const handledBreakpoints = this.getHandledBreakpoints(); if (handledBreakpoints.includes(name)) { return true; } return false; } /** * Check if another breakpoint (mostly the previous breakpoint) is handled by no other breakpoint binders. * These are all breakpoints that are smaller than the minimum breakpoint. * I.e. for these breakpoints there is no binder, neither this one nor another one. * @returns */ protected breakpointUnhandled(breakpoint: string) { let prevBreakpoint: string | null = breakpoint; // Check if all smaller breakpoints are unhandled let unhandled = true; while (prevBreakpoint && unhandled) { if (this.breakpointHandledByAnother(prevBreakpoint)) { unhandled = false; break; } prevBreakpoint = this.bs5.getPrevBreakpointByName(prevBreakpoint); if (!prevBreakpoint) { break; } } return unhandled; } /** * Used to check if there are more binders for other breakpoints **/ protected addToHandledBreakpoints() { const handledBreakpoints = this.getHandledBreakpoints(); handledBreakpoints.push(this.breakpoint); this.el.dataset[camelCase(this.attributeName)] = jsonStringify(handledBreakpoints); } /** * Get all handled breakpoints. * These are the own but also those of other binders. * @returns */ protected getHandledBreakpoints() { const handledBreakpoints: string[] = parseJsonString( this.el.dataset[camelCase(this.attributeName)] || "[]", ); if (!Array.isArray(handledBreakpoints)) { throw new Error("breakpoints dataset has unsupported values!"); } return handledBreakpoints; } bind(el: HTMLElement) { this.bs5.on("breakpoint:changed", this.onBreakpointChanges, this); if (typeof this.defaultAttributeBinder.bind === "function") { this.defaultAttributeBinder.bind(el); } } routine(el: HTMLElement, newValue: any) { this.addToHandledBreakpoints(); this.val = newValue; this.setAttributeOnMatch(); } unbind(el: HTMLElement) { this.bs5?.off("breakpoint:changed", this.onBreakpointChanges, this); if (typeof this.defaultAttributeBinder.unbind === "function") { this.defaultAttributeBinder.unbind(el); } } }