UNPKG

@fireng/core

Version:

Core utilities for Fireng Angular responsive library.

193 lines (186 loc) 8.06 kB
import { DOCUMENT } from '@angular/common'; import * as i0 from '@angular/core'; import { InjectionToken, inject, signal, computed, Injectable } from '@angular/core'; import { fromEvent, auditTime } from 'rxjs'; const DEFAULT_BREAKPOINTS = { xs: 0, sm: 576, md: 768, lg: 992, xl: 1200, xxl: 1400, }; const FIRENG_BREAKPOINTS = new InjectionToken('FIRENG_BREAKPOINTS_TOKEN', { factory: () => DEFAULT_BREAKPOINTS, }); class FirengScreenService { document = inject(DOCUMENT); window = this.document.defaultView || undefined; // signal to store the current window width _windowWidth = signal(0); windowWidth = this._windowWidth.asReadonly(); // signal to store the current window height _windowHeight = signal(0); windowHeight = this._windowHeight.asReadonly(); // Signal to store breakpoint and this allows for runtime updates to breakpoints _breakpoint; // Computed signal to get sorted breakpoints _sortedBreakpoints = computed(() => { const breakpointsEntries = Object.entries(this._breakpoint() || {}).sort((a, b) => a[1] - b[1]); return Object.fromEntries(breakpointsEntries); }); // Computed signal for current breakpoint currentBreakpoint = computed(() => { const width = this._windowWidth(); const breakpoints = this._sortedBreakpoints(); // taking first first key of breakpoints as default active breakpoint let activeBreakpointKey = Object.keys(breakpoints)[0]; for (const [breakpointKey, breakpointValue] of Object.entries(breakpoints)) { if (width >= breakpointValue) { activeBreakpointKey = breakpointKey; } else { break; // Exit loop once we find the first breakpoint that is larger than the width } } return activeBreakpointKey; }); // Computed signal for cascading breakpoints activeBreakpoints = computed(() => { const currentBreakpoint = this.currentBreakpoint(); const breakpoints = this._sortedBreakpoints(); const activeNames = []; for (const [breakpointKey, breakpointValue] of Object.entries(breakpoints)) { activeNames.push(breakpointKey); if (breakpointKey === currentBreakpoint) { break; } } return activeNames; }); constructor() { this._breakpoint = signal(inject(FIRENG_BREAKPOINTS)); if (this.window) { this._windowWidth.set(this.window.innerWidth); this._windowHeight.set(this.window.innerHeight); fromEvent(this.window, 'resize') .pipe(auditTime(100)) .subscribe(() => { this._windowWidth.set(this.window?.innerWidth || 0); this._windowHeight.set(this.window?.innerHeight || 0); }); } else { console.warn('FirengScreenService: No window object found. Screen service may not work as expected.'); } } /** * Checks if the current screen width matches the specified breakpoint. * @param breakpointName The name of the breakpoint (e.g., 'xs', 'md'). * @returns A signal indicating if the current screen width matches the specified breakpoint. */ isBreakpoint(breakpointName) { return computed(() => breakpointName === this.currentBreakpoint()); } /** * Checks if the current screen size is at or above a specific breakpoint. * @param breakpointName The name of the breakpoint (e.g., 'sm', 'md'). * @returns A signal indicating if the current screen is at or above the breakpoint. */ isBreakpointUp(breakpointName) { return computed(() => { const breakpoints = this._sortedBreakpoints(); const targetIndex = Object.keys(breakpoints).indexOf(breakpointName); const currentIndex = Object.keys(breakpoints).indexOf(this.currentBreakpoint()); return (targetIndex >= currentIndex && targetIndex >= 0 && currentIndex >= 0); }); } /** * Checks if the current screen size is below a specific breakpoint. * @param breakpointName The name of the breakpoint (e.g., 'sm', 'md'). * @returns A signal indicating if the current screen is below the breakpoint. */ isBreakpointDown(breakpointName) { return computed(() => { const breakpoints = this._sortedBreakpoints(); const targetIndex = Object.keys(breakpoints).indexOf(breakpointName); const currentIndex = Object.keys(breakpoints).indexOf(this.currentBreakpoint()); return (currentIndex < targetIndex && targetIndex >= 0 && currentIndex >= 0); }); } /** * Allows users to update breakpoints dynamically at runtime. * This will immediately update the `currentBreakpoint` and `activeBreakpoints` signals. * @param newBreakpoints An array of new breakpoint definitions. */ setBreakpoints(newBreakpoints) { const keys = new Set(); for (const [key, value] of Object.entries(newBreakpoints)) { if (typeof value !== 'number' || isNaN(value) || value < 0) { console.warn(`Invalid breakpoint value for "${key}": ${value}`); return; } else if (keys.has(key)) { console.warn(`Duplicate breakpoint key found: "${key}"`); return; } keys.add(key); } this._breakpoint.set(newBreakpoints); console.log('FirengScreenService: Breakpoints updated successfully.'); } isPortrait = computed(() => { return this._windowHeight() > this._windowWidth(); }); /** * Resolves a responsive value from a map based on current active breakpoints. * * It applies cascading logic: the value for the largest active breakpoint in `valueMap` is returned. * If no match is found, the `fallback` value is returned. * * @template T The type of the value (e.g., `number`, `string`). * @param valueMap An object mapping breakpoint names to values (e.g., `{ xs: 5, md: 10 }`). * @param fallback An optional default value if no matching breakpoint is found. * @returns A signal emitting the resolved value, or `fallback`, or `undefined`. * * @example * // Get items per page: 5 for xs, 10 for md and up, default 5 * const itemsPerPage = this.screenService.resolveBreakpointValue({ xs: 5, md: 10 }, 5); */ resolveBreakpointValue(valueMap, fallback) { return computed(() => { const activeBreakpoints = this.activeBreakpoints(); let selectedValue; for (const breakpoint of activeBreakpoints) { if (valueMap.hasOwnProperty(breakpoint)) { selectedValue = valueMap[breakpoint]; } } if (selectedValue === undefined) { return fallback; } return selectedValue; }); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FirengScreenService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FirengScreenService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FirengScreenService, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }], ctorParameters: () => [] }); function provideFirengBreakpoints(breakpoints) { return [ { provide: FIRENG_BREAKPOINTS, useValue: breakpoints, }, ]; } /** * Generated bundle index. Do not edit. */ export { FIRENG_BREAKPOINTS, FirengScreenService, provideFirengBreakpoints }; //# sourceMappingURL=fireng-core.mjs.map