-
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "@newspaperui/utils",
|
||||
"version": "0.0.0",
|
||||
"description": "Utility functions for newspaperui",
|
||||
"type": "module",
|
||||
"main": "./dist/index.cjs",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.cjs",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"dev": "vite build --watch",
|
||||
"lint": "tsc --noEmit",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"clean": "rm -rf dist"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.7.2",
|
||||
"vite": "^5.4.11",
|
||||
"vite-plugin-dts": "^4.3.0",
|
||||
"vitest": "^2.1.8"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { calculateSpanWidth, validateSpan, calculateGutter } from '../grid';
|
||||
|
||||
describe('grid utilities', () => {
|
||||
describe('calculateSpanWidth', () => {
|
||||
it('should calculate correct width percentage for 24-column grid', () => {
|
||||
expect(calculateSpanWidth(6, 24)).toBe('25.000%');
|
||||
expect(calculateSpanWidth(12, 24)).toBe('50.000%');
|
||||
expect(calculateSpanWidth(24, 24)).toBe('100.000%');
|
||||
expect(calculateSpanWidth(1, 24)).toBe('4.167%');
|
||||
});
|
||||
|
||||
it('should calculate correct width percentage for 12-column grid', () => {
|
||||
expect(calculateSpanWidth(6, 12)).toBe('50.000%');
|
||||
expect(calculateSpanWidth(3, 12)).toBe('25.000%');
|
||||
expect(calculateSpanWidth(12, 12)).toBe('100.000%');
|
||||
});
|
||||
|
||||
it('should use 24 columns as default', () => {
|
||||
expect(calculateSpanWidth(6)).toBe('25.000%');
|
||||
expect(calculateSpanWidth(12)).toBe('50.000%');
|
||||
});
|
||||
|
||||
it('should throw error for invalid span', () => {
|
||||
expect(() => calculateSpanWidth(0, 24)).toThrow();
|
||||
expect(() => calculateSpanWidth(25, 24)).toThrow();
|
||||
expect(() => calculateSpanWidth(-1, 24)).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateSpan', () => {
|
||||
it('should return true for valid spans', () => {
|
||||
expect(validateSpan(1, 24)).toBe(true);
|
||||
expect(validateSpan(12, 24)).toBe(true);
|
||||
expect(validateSpan(24, 24)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for invalid spans', () => {
|
||||
expect(validateSpan(0, 24)).toBe(false);
|
||||
expect(validateSpan(25, 24)).toBe(false);
|
||||
expect(validateSpan(-1, 24)).toBe(false);
|
||||
expect(validateSpan(1.5, 24)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('calculateGutter', () => {
|
||||
it('should calculate gutter width correctly', () => {
|
||||
const gutter = calculateGutter(1440, 24, 0.05);
|
||||
expect(gutter).toBeGreaterThan(0);
|
||||
expect(gutter).toBeLessThan(100);
|
||||
});
|
||||
|
||||
it('should throw error for invalid inputs', () => {
|
||||
expect(() => calculateGutter(0, 24, 0.05)).toThrow();
|
||||
expect(() => calculateGutter(1440, 0, 0.05)).toThrow();
|
||||
expect(() => calculateGutter(1440, 24, -0.1)).toThrow();
|
||||
expect(() => calculateGutter(1440, 24, 1.5)).toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
calculateSpanWidth,
|
||||
calculateLayout,
|
||||
getResponsiveColumns,
|
||||
adjustSpanForScreen,
|
||||
} from '../index';
|
||||
|
||||
describe('integration tests', () => {
|
||||
it('should work together for responsive layout calculation', () => {
|
||||
// Scenario: Layout 3 items on different screen sizes
|
||||
const items = [
|
||||
{ id: 'headline', span: 12 },
|
||||
{ id: 'image', span: 8 },
|
||||
{ id: 'text', span: 4 },
|
||||
];
|
||||
|
||||
// Large screen (24 columns)
|
||||
const largeScreenColumns = getResponsiveColumns(1440);
|
||||
expect(largeScreenColumns).toBe(24);
|
||||
const largeLayout = calculateLayout(items, largeScreenColumns);
|
||||
expect(largeLayout).toHaveLength(3);
|
||||
// All items fit in one row: 12 + 8 + 4 = 24
|
||||
expect(largeLayout[0].row).toBe(0);
|
||||
expect(largeLayout[1].row).toBe(0);
|
||||
expect(largeLayout[2].row).toBe(0);
|
||||
|
||||
// Medium screen (16 columns) - adjust spans
|
||||
const mediumScreenColumns = getResponsiveColumns(900);
|
||||
expect(mediumScreenColumns).toBe(16);
|
||||
const adjustedItems = items.map((item) => ({
|
||||
...item,
|
||||
span: adjustSpanForScreen(item.span, 900),
|
||||
}));
|
||||
const mediumLayout = calculateLayout(adjustedItems, mediumScreenColumns);
|
||||
expect(mediumLayout).toHaveLength(3);
|
||||
|
||||
// Small screen (12 columns) - adjust spans
|
||||
const smallScreenColumns = getResponsiveColumns(600);
|
||||
expect(smallScreenColumns).toBe(12);
|
||||
const smallAdjustedItems = items.map((item) => ({
|
||||
...item,
|
||||
span: adjustSpanForScreen(item.span, 600),
|
||||
}));
|
||||
const smallLayout = calculateLayout(smallAdjustedItems, smallScreenColumns);
|
||||
expect(smallLayout).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('should calculate correct widths for newspaper layout', () => {
|
||||
// 6-column headline in 24-column grid
|
||||
const headlineWidth = calculateSpanWidth(6, 24);
|
||||
expect(headlineWidth).toBe('25.000%');
|
||||
|
||||
// 8-column image in 24-column grid
|
||||
const imageWidth = calculateSpanWidth(8, 24);
|
||||
expect(imageWidth).toBe('33.333%');
|
||||
|
||||
// Full-width article
|
||||
const fullWidth = calculateSpanWidth(24, 24);
|
||||
expect(fullWidth).toBe('100.000%');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
getResponsiveColumns,
|
||||
adjustSpanForScreen,
|
||||
isBreakpoint,
|
||||
getCurrentBreakpoint,
|
||||
BREAKPOINTS,
|
||||
} from '../responsive';
|
||||
|
||||
describe('responsive utilities', () => {
|
||||
describe('getResponsiveColumns', () => {
|
||||
it('should return 12 columns for small screens', () => {
|
||||
expect(getResponsiveColumns(320)).toBe(12);
|
||||
expect(getResponsiveColumns(767)).toBe(12);
|
||||
});
|
||||
|
||||
it('should return 16 columns for medium screens', () => {
|
||||
expect(getResponsiveColumns(768)).toBe(16);
|
||||
expect(getResponsiveColumns(1023)).toBe(16);
|
||||
});
|
||||
|
||||
it('should return 24 columns for large screens', () => {
|
||||
expect(getResponsiveColumns(1024)).toBe(24);
|
||||
expect(getResponsiveColumns(1440)).toBe(24);
|
||||
expect(getResponsiveColumns(1920)).toBe(24);
|
||||
});
|
||||
});
|
||||
|
||||
describe('adjustSpanForScreen', () => {
|
||||
it('should not adjust span for large screens (24 columns)', () => {
|
||||
expect(adjustSpanForScreen(12, 1440)).toBe(12);
|
||||
expect(adjustSpanForScreen(6, 1920)).toBe(6);
|
||||
expect(adjustSpanForScreen(24, 1024)).toBe(24);
|
||||
});
|
||||
|
||||
it('should proportionally adjust span for medium screens (16 columns)', () => {
|
||||
expect(adjustSpanForScreen(12, 800)).toBe(8); // 12/24 * 16 = 8
|
||||
expect(adjustSpanForScreen(6, 900)).toBe(4); // 6/24 * 16 = 4
|
||||
expect(adjustSpanForScreen(24, 1000)).toBe(16); // 24/24 * 16 = 16
|
||||
});
|
||||
|
||||
it('should proportionally adjust span for small screens (12 columns)', () => {
|
||||
expect(adjustSpanForScreen(12, 600)).toBe(6); // 12/24 * 12 = 6
|
||||
expect(adjustSpanForScreen(6, 400)).toBe(3); // 6/24 * 12 = 3
|
||||
expect(adjustSpanForScreen(24, 700)).toBe(12); // 24/24 * 12 = 12
|
||||
});
|
||||
|
||||
it('should ensure minimum span of 1', () => {
|
||||
expect(adjustSpanForScreen(1, 600)).toBe(1);
|
||||
expect(adjustSpanForScreen(2, 600)).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
it('should not exceed target columns', () => {
|
||||
expect(adjustSpanForScreen(24, 600)).toBeLessThanOrEqual(12);
|
||||
expect(adjustSpanForScreen(24, 800)).toBeLessThanOrEqual(16);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isBreakpoint', () => {
|
||||
it('should correctly identify breakpoints', () => {
|
||||
expect(isBreakpoint(768, 'sm')).toBe(true);
|
||||
expect(isBreakpoint(767, 'sm')).toBe(false);
|
||||
expect(isBreakpoint(1024, 'md')).toBe(true);
|
||||
expect(isBreakpoint(1023, 'md')).toBe(false);
|
||||
expect(isBreakpoint(1440, 'lg')).toBe(true);
|
||||
expect(isBreakpoint(1439, 'lg')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCurrentBreakpoint', () => {
|
||||
it('should return correct breakpoint names', () => {
|
||||
expect(getCurrentBreakpoint(320)).toBe('xs');
|
||||
expect(getCurrentBreakpoint(767)).toBe('xs');
|
||||
expect(getCurrentBreakpoint(768)).toBe('sm');
|
||||
expect(getCurrentBreakpoint(1023)).toBe('sm');
|
||||
expect(getCurrentBreakpoint(1024)).toBe('md');
|
||||
expect(getCurrentBreakpoint(1439)).toBe('md');
|
||||
expect(getCurrentBreakpoint(1440)).toBe('lg');
|
||||
expect(getCurrentBreakpoint(1920)).toBe('lg');
|
||||
});
|
||||
});
|
||||
|
||||
describe('BREAKPOINTS', () => {
|
||||
it('should have correct breakpoint values', () => {
|
||||
expect(BREAKPOINTS.sm).toBe(768);
|
||||
expect(BREAKPOINTS.md).toBe(1024);
|
||||
expect(BREAKPOINTS.lg).toBe(1440);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,74 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { isSpanValid, calculateLayout, type LayoutItem } from '../span';
|
||||
|
||||
describe('span utilities', () => {
|
||||
describe('isSpanValid', () => {
|
||||
it('should return true for valid spans', () => {
|
||||
expect(isSpanValid(1, 24)).toBe(true);
|
||||
expect(isSpanValid(12, 24)).toBe(true);
|
||||
expect(isSpanValid(24, 24)).toBe(true);
|
||||
expect(isSpanValid(6, 12)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for invalid spans', () => {
|
||||
expect(isSpanValid(0, 24)).toBe(false);
|
||||
expect(isSpanValid(25, 24)).toBe(false);
|
||||
expect(isSpanValid(-1, 24)).toBe(false);
|
||||
expect(isSpanValid(1.5, 24)).toBe(false);
|
||||
expect(isSpanValid(13, 12)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('calculateLayout', () => {
|
||||
it('should layout items in a single row when they fit', () => {
|
||||
const items: LayoutItem[] = [
|
||||
{ id: 'a', span: 6 },
|
||||
{ id: 'b', span: 6 },
|
||||
{ id: 'c', span: 6 },
|
||||
];
|
||||
const layout = calculateLayout(items, 24);
|
||||
|
||||
expect(layout).toHaveLength(3);
|
||||
expect(layout[0]).toEqual({ item: items[0], row: 0, col: 0 });
|
||||
expect(layout[1]).toEqual({ item: items[1], row: 0, col: 6 });
|
||||
expect(layout[2]).toEqual({ item: items[2], row: 0, col: 12 });
|
||||
});
|
||||
|
||||
it('should wrap to next row when items do not fit', () => {
|
||||
const items: LayoutItem[] = [
|
||||
{ id: 'a', span: 16 },
|
||||
{ id: 'b', span: 12 },
|
||||
];
|
||||
const layout = calculateLayout(items, 24);
|
||||
|
||||
expect(layout).toHaveLength(2);
|
||||
expect(layout[0]).toEqual({ item: items[0], row: 0, col: 0 });
|
||||
expect(layout[1]).toEqual({ item: items[1], row: 1, col: 0 });
|
||||
});
|
||||
|
||||
it('should handle exact row fills', () => {
|
||||
const items: LayoutItem[] = [
|
||||
{ id: 'a', span: 12 },
|
||||
{ id: 'b', span: 12 },
|
||||
{ id: 'c', span: 24 },
|
||||
];
|
||||
const layout = calculateLayout(items, 24);
|
||||
|
||||
expect(layout).toHaveLength(3);
|
||||
expect(layout[0]).toEqual({ item: items[0], row: 0, col: 0 });
|
||||
expect(layout[1]).toEqual({ item: items[1], row: 0, col: 12 });
|
||||
expect(layout[2]).toEqual({ item: items[2], row: 1, col: 0 });
|
||||
});
|
||||
|
||||
it('should throw error for invalid section columns', () => {
|
||||
const items: LayoutItem[] = [{ id: 'a', span: 6 }];
|
||||
expect(() => calculateLayout(items, 0)).toThrow();
|
||||
expect(() => calculateLayout(items, -1)).toThrow();
|
||||
});
|
||||
|
||||
it('should throw error for invalid item span', () => {
|
||||
const items: LayoutItem[] = [{ id: 'a', span: 25 }];
|
||||
expect(() => calculateLayout(items, 24)).toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Grid calculation utilities for 24-column newspaper layout system
|
||||
*/
|
||||
|
||||
/**
|
||||
* Calculate element width percentage in a grid system
|
||||
* @param span - Number of columns the element spans
|
||||
* @param totalColumns - Total number of columns in the grid (default: 24)
|
||||
* @returns Width as a percentage string (e.g., "33.333%")
|
||||
*/
|
||||
export function calculateSpanWidth(
|
||||
span: number,
|
||||
totalColumns: number = 24
|
||||
): string {
|
||||
if (!validateSpan(span, totalColumns)) {
|
||||
throw new Error(
|
||||
`Invalid span: ${span}. Must be between 1 and ${totalColumns}`
|
||||
);
|
||||
}
|
||||
const percentage = (span / totalColumns) * 100;
|
||||
return `${percentage.toFixed(3)}%`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate if a span value is within valid range
|
||||
* @param span - Number of columns to validate
|
||||
* @param maxColumns - Maximum number of columns allowed
|
||||
* @returns True if span is valid, false otherwise
|
||||
*/
|
||||
export function validateSpan(span: number, maxColumns: number): boolean {
|
||||
return Number.isInteger(span) && span >= 1 && span <= maxColumns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate gutter width based on container width and column count
|
||||
* @param containerWidth - Total width of the container in pixels
|
||||
* @param columns - Number of columns in the grid
|
||||
* @param gutterRatio - Ratio of gutter to column width (default: 0.05)
|
||||
* @returns Gutter width in pixels
|
||||
*/
|
||||
export function calculateGutter(
|
||||
containerWidth: number,
|
||||
columns: number,
|
||||
gutterRatio: number = 0.05
|
||||
): number {
|
||||
if (containerWidth <= 0 || columns <= 0) {
|
||||
throw new Error('Container width and columns must be positive numbers');
|
||||
}
|
||||
if (gutterRatio < 0 || gutterRatio > 1) {
|
||||
throw new Error('Gutter ratio must be between 0 and 1');
|
||||
}
|
||||
|
||||
// Calculate column width considering gutters
|
||||
// Formula: containerWidth = (columns * columnWidth) + ((columns - 1) * gutter)
|
||||
// Where gutter = columnWidth * gutterRatio
|
||||
const totalGutterRatio = (columns - 1) * gutterRatio;
|
||||
const columnWidth = containerWidth / (columns + totalGutterRatio);
|
||||
const gutterWidth = columnWidth * gutterRatio;
|
||||
|
||||
return Math.round(gutterWidth * 100) / 100;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* @newspaperui/utils
|
||||
* Utility functions for newspaperui component library
|
||||
*/
|
||||
|
||||
// Grid utilities
|
||||
export {
|
||||
calculateSpanWidth,
|
||||
validateSpan,
|
||||
calculateGutter,
|
||||
} from './grid';
|
||||
|
||||
// Span and layout utilities
|
||||
export {
|
||||
isSpanValid,
|
||||
calculateLayout,
|
||||
type LayoutItem,
|
||||
type PositionedLayoutItem,
|
||||
} from './span';
|
||||
|
||||
// Responsive utilities
|
||||
export {
|
||||
getResponsiveColumns,
|
||||
adjustSpanForScreen,
|
||||
isBreakpoint,
|
||||
getCurrentBreakpoint,
|
||||
BREAKPOINTS,
|
||||
} from './responsive';
|
||||
|
||||
export const version = '0.0.0';
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Responsive utilities for newspaper layout system
|
||||
*/
|
||||
|
||||
/**
|
||||
* Breakpoint thresholds for responsive design
|
||||
*/
|
||||
export const BREAKPOINTS = {
|
||||
sm: 768,
|
||||
md: 1024,
|
||||
lg: 1440,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Get recommended grid column count based on screen width
|
||||
* @param screenWidth - Current screen width in pixels
|
||||
* @returns Recommended number of columns (12, 16, or 24)
|
||||
*/
|
||||
export function getResponsiveColumns(screenWidth: number): number {
|
||||
if (screenWidth < BREAKPOINTS.sm) {
|
||||
return 12; // Small screens: 12 columns
|
||||
} else if (screenWidth < BREAKPOINTS.md) {
|
||||
return 16; // Medium screens: 16 columns
|
||||
} else {
|
||||
return 24; // Large screens: 24 columns
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust span value for different screen sizes
|
||||
* Proportionally scales span based on available columns
|
||||
* @param span - Original span value (based on 24 columns)
|
||||
* @param screenWidth - Current screen width in pixels
|
||||
* @returns Adjusted span value for current screen size
|
||||
*/
|
||||
export function adjustSpanForScreen(
|
||||
span: number,
|
||||
screenWidth: number
|
||||
): number {
|
||||
const targetColumns = getResponsiveColumns(screenWidth);
|
||||
const baseColumns = 24;
|
||||
|
||||
// If already at 24 columns, no adjustment needed
|
||||
if (targetColumns === baseColumns) {
|
||||
return span;
|
||||
}
|
||||
|
||||
// Calculate proportional span
|
||||
const adjustedSpan = Math.round((span / baseColumns) * targetColumns);
|
||||
|
||||
// Ensure at least 1 column and not exceeding target columns
|
||||
return Math.max(1, Math.min(adjustedSpan, targetColumns));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if current screen width matches a breakpoint
|
||||
* @param screenWidth - Current screen width in pixels
|
||||
* @param breakpoint - Breakpoint name to check
|
||||
* @returns True if screen width is at or above the breakpoint
|
||||
*/
|
||||
export function isBreakpoint(
|
||||
screenWidth: number,
|
||||
breakpoint: keyof typeof BREAKPOINTS
|
||||
): boolean {
|
||||
return screenWidth >= BREAKPOINTS[breakpoint];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current breakpoint name based on screen width
|
||||
* @param screenWidth - Current screen width in pixels
|
||||
* @returns Current breakpoint name ('sm', 'md', 'lg', or 'xs' for below sm)
|
||||
*/
|
||||
export function getCurrentBreakpoint(
|
||||
screenWidth: number
|
||||
): 'xs' | 'sm' | 'md' | 'lg' {
|
||||
if (screenWidth >= BREAKPOINTS.lg) {
|
||||
return 'lg';
|
||||
} else if (screenWidth >= BREAKPOINTS.md) {
|
||||
return 'md';
|
||||
} else if (screenWidth >= BREAKPOINTS.sm) {
|
||||
return 'sm';
|
||||
} else {
|
||||
return 'xs';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Span and layout utilities for newspaper column system
|
||||
*/
|
||||
|
||||
/**
|
||||
* Check if an object's span is valid within a section's column count
|
||||
* @param objectSpan - Number of columns the object spans
|
||||
* @param sectionColumns - Total number of columns in the section
|
||||
* @returns True if the span is valid, false otherwise
|
||||
*/
|
||||
export function isSpanValid(
|
||||
objectSpan: number,
|
||||
sectionColumns: number
|
||||
): boolean {
|
||||
return (
|
||||
Number.isInteger(objectSpan) &&
|
||||
Number.isInteger(sectionColumns) &&
|
||||
objectSpan >= 1 &&
|
||||
objectSpan <= sectionColumns
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout item interface for grid positioning
|
||||
*/
|
||||
export interface LayoutItem {
|
||||
span: number;
|
||||
id: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Positioned layout item with row and column information
|
||||
*/
|
||||
export interface PositionedLayoutItem {
|
||||
item: LayoutItem;
|
||||
row: number;
|
||||
col: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate simple flow layout for items in a section
|
||||
* Items are placed left-to-right, wrapping to new rows when needed
|
||||
* @param items - Array of items to layout
|
||||
* @param sectionColumns - Total number of columns in the section
|
||||
* @returns Array of positioned items with row and column information
|
||||
*/
|
||||
export function calculateLayout(
|
||||
items: LayoutItem[],
|
||||
sectionColumns: number
|
||||
): PositionedLayoutItem[] {
|
||||
if (sectionColumns <= 0) {
|
||||
throw new Error('Section columns must be a positive number');
|
||||
}
|
||||
|
||||
const positioned: PositionedLayoutItem[] = [];
|
||||
let currentRow = 0;
|
||||
let currentCol = 0;
|
||||
|
||||
for (const item of items) {
|
||||
// Validate item span
|
||||
if (!isSpanValid(item.span, sectionColumns)) {
|
||||
throw new Error(
|
||||
`Invalid span ${item.span} for item ${item.id}. Must be between 1 and ${sectionColumns}`
|
||||
);
|
||||
}
|
||||
|
||||
// Check if item fits in current row
|
||||
if (currentCol + item.span > sectionColumns) {
|
||||
// Move to next row
|
||||
currentRow++;
|
||||
currentCol = 0;
|
||||
}
|
||||
|
||||
// Position the item
|
||||
positioned.push({
|
||||
item,
|
||||
row: currentRow,
|
||||
col: currentCol,
|
||||
});
|
||||
|
||||
// Update current column position
|
||||
currentCol += item.span;
|
||||
|
||||
// If we've filled the row exactly, move to next row
|
||||
if (currentCol === sectionColumns) {
|
||||
currentRow++;
|
||||
currentCol = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return positioned;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import { resolve } from 'path';
|
||||
import dts from 'vite-plugin-dts';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
dts({
|
||||
insertTypesEntry: true,
|
||||
}),
|
||||
],
|
||||
build: {
|
||||
lib: {
|
||||
entry: resolve(__dirname, 'src/index.ts'),
|
||||
name: 'NewspaperUIUtils',
|
||||
formats: ['es', 'cjs'],
|
||||
fileName: (format) => `index.${format === 'es' ? 'js' : 'cjs'}`,
|
||||
},
|
||||
rollupOptions: {
|
||||
external: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
globals: true,
|
||||
environment: 'node',
|
||||
coverage: {
|
||||
provider: 'v8',
|
||||
reporter: ['text', 'json', 'html'],
|
||||
exclude: ['node_modules/', 'dist/', '**/*.test.ts'],
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user