From 4d127a038bf768eded68aae3cfa5c1b12241bdac Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Fri, 27 Mar 2026 00:55:15 +0100 Subject: chore: normalize line endings --- apps/web/src/utils/color.ts | 364 ++++++++++++++++++++++---------------------- apps/web/src/utils/id.ts | 56 +++---- apps/web/src/utils/math.ts | 168 ++++++++++---------- 3 files changed, 294 insertions(+), 294 deletions(-) (limited to 'apps/web/src/utils') diff --git a/apps/web/src/utils/color.ts b/apps/web/src/utils/color.ts index 7b01a92e..1176ffd3 100644 --- a/apps/web/src/utils/color.ts +++ b/apps/web/src/utils/color.ts @@ -1,182 +1,182 @@ -import { converter, formatHex, formatHex8, parse, type Rgb } from "culori"; - -export type ColorFormat = "hex" | "rgb" | "hsl" | "hsv"; - -const toRgb = converter("rgb"); -const toHsv = converter("hsv"); -const toHsl = converter("hsl"); - -export function hexToHsv({ hex }: { hex: string }): [number, number, number] { - const color = toHsv(`#${hex}`); - if (!color) return [0, 0, 0]; - return [color.h ?? 0, color.s ?? 0, color.v ?? 0]; -} - -export function hsvToHex({ - h, - s, - v, -}: { - h: number; - s: number; - v: number; -}): string { - const hex = formatHex({ mode: "hsv", h, s, v }); - return hex.slice(1); -} - -export function parseHexAlpha({ hex }: { hex: string }): { - rgb: string; - alpha: number; -} { - const color = parse(`#${hex}`); - const rgbHex = color - ? formatHex(color).slice(1) - : hex.slice(0, 6).toLowerCase(); - return { - rgb: rgbHex, - alpha: color?.alpha ?? 1, - }; -} - -export function appendAlpha({ - rgbHex, - alpha, -}: { - rgbHex: string; - alpha: number; -}): string { - if (alpha >= 1) return rgbHex; - const hex8 = formatHex8({ mode: "rgb", r: 0, g: 0, b: 0, alpha }); - const alphaHex = hex8.slice(7, 9); - return rgbHex + alphaHex; -} - -function stripCssNoise({ text }: { text: string }): string { - let cleaned = text.trim(); - cleaned = cleaned - .replace(/\s*!important\s*/gi, "") - .replace(/;+\s*$/, "") - .trim(); - - const colonIndex = cleaned.indexOf(":"); - const parenIndex = cleaned.indexOf("("); - if (colonIndex !== -1 && (parenIndex === -1 || colonIndex < parenIndex)) { - cleaned = cleaned.slice(colonIndex + 1).trim(); - } - - return cleaned; -} - -function colorToHexWithAlpha({ color }: { color: Rgb }): string { - const hex = formatHex(color).slice(1); - if (color.alpha !== undefined && color.alpha < 1) { - const hex8 = formatHex8(color); - return hex8.slice(1); - } - return hex; -} - -export function extractColorFromText({ - text, -}: { - text: string; -}): string | null { - const cleaned = stripCssNoise({ text }); - - const color = toRgb(cleaned); - if (color) return colorToHexWithAlpha({ color }); - - // bare hex without # (culori needs the prefix) - const bareHexMatch = cleaned.match(/^([0-9a-fA-F]{3,8})$/); - if (bareHexMatch) { - const withHash = toRgb(`#${bareHexMatch[1]}`); - if (withHash) return colorToHexWithAlpha({ color: withHash }); - } - - // fallback: find #hex anywhere in the original text - const embeddedHexMatch = text.match(/#([0-9a-fA-F]{3,8})\b/); - if (embeddedHexMatch) { - const embedded = toRgb(`#${embeddedHexMatch[1]}`); - if (embedded) return colorToHexWithAlpha({ color: embedded }); - } - - return null; -} - -export function formatColorValue({ - hex, - format, -}: { - hex: string; - format: ColorFormat; -}): string { - switch (format) { - case "hex": - return hex; - case "rgb": { - const color = toRgb(`#${hex}`); - if (!color) return hex; - return `${Math.round(color.r * 255)}, ${Math.round(color.g * 255)}, ${Math.round(color.b * 255)}`; - } - case "hsl": { - const color = toHsl(`#${hex}`); - if (!color) return hex; - return `${Math.round(color.h ?? 0)}, ${Math.round((color.s ?? 0) * 100)}%, ${Math.round((color.l ?? 0) * 100)}%`; - } - case "hsv": { - const color = toHsv(`#${hex}`); - if (!color) return hex; - return `${Math.round(color.h ?? 0)}, ${Math.round((color.s ?? 0) * 100)}%, ${Math.round((color.v ?? 0) * 100)}%`; - } - } -} - -export function parseColorInput({ - input, - format, -}: { - input: string; - format: ColorFormat; -}): string | null { - switch (format) { - case "hex": { - const cleaned = input.replace("#", ""); - const isValidHex = /^[0-9a-fA-F]{3,8}$/.test(cleaned); - return isValidHex ? cleaned : null; - } - case "rgb": { - const parts = input.split(",").map((part) => parseInt(part.trim(), 10)); - if (parts.length < 3 || parts.some(Number.isNaN)) return null; - const color = { - mode: "rgb" as const, - r: parts[0] / 255, - g: parts[1] / 255, - b: parts[2] / 255, - }; - return formatHex(color).slice(1); - } - case "hsl": { - const parts = input.split(",").map((part) => parseFloat(part.trim())); - if (parts.length < 3 || parts.some(Number.isNaN)) return null; - const color = { - mode: "hsl" as const, - h: parts[0], - s: parts[1] / 100, - l: parts[2] / 100, - }; - return formatHex(color).slice(1); - } - case "hsv": { - const parts = input.split(",").map((part) => parseFloat(part.trim())); - if (parts.length < 3 || parts.some(Number.isNaN)) return null; - const color = { - mode: "hsv" as const, - h: parts[0], - s: parts[1] / 100, - v: parts[2] / 100, - }; - return formatHex(color).slice(1); - } - } -} +import { converter, formatHex, formatHex8, parse, type Rgb } from "culori"; + +export type ColorFormat = "hex" | "rgb" | "hsl" | "hsv"; + +const toRgb = converter("rgb"); +const toHsv = converter("hsv"); +const toHsl = converter("hsl"); + +export function hexToHsv({ hex }: { hex: string }): [number, number, number] { + const color = toHsv(`#${hex}`); + if (!color) return [0, 0, 0]; + return [color.h ?? 0, color.s ?? 0, color.v ?? 0]; +} + +export function hsvToHex({ + h, + s, + v, +}: { + h: number; + s: number; + v: number; +}): string { + const hex = formatHex({ mode: "hsv", h, s, v }); + return hex.slice(1); +} + +export function parseHexAlpha({ hex }: { hex: string }): { + rgb: string; + alpha: number; +} { + const color = parse(`#${hex}`); + const rgbHex = color + ? formatHex(color).slice(1) + : hex.slice(0, 6).toLowerCase(); + return { + rgb: rgbHex, + alpha: color?.alpha ?? 1, + }; +} + +export function appendAlpha({ + rgbHex, + alpha, +}: { + rgbHex: string; + alpha: number; +}): string { + if (alpha >= 1) return rgbHex; + const hex8 = formatHex8({ mode: "rgb", r: 0, g: 0, b: 0, alpha }); + const alphaHex = hex8.slice(7, 9); + return rgbHex + alphaHex; +} + +function stripCssNoise({ text }: { text: string }): string { + let cleaned = text.trim(); + cleaned = cleaned + .replace(/\s*!important\s*/gi, "") + .replace(/;+\s*$/, "") + .trim(); + + const colonIndex = cleaned.indexOf(":"); + const parenIndex = cleaned.indexOf("("); + if (colonIndex !== -1 && (parenIndex === -1 || colonIndex < parenIndex)) { + cleaned = cleaned.slice(colonIndex + 1).trim(); + } + + return cleaned; +} + +function colorToHexWithAlpha({ color }: { color: Rgb }): string { + const hex = formatHex(color).slice(1); + if (color.alpha !== undefined && color.alpha < 1) { + const hex8 = formatHex8(color); + return hex8.slice(1); + } + return hex; +} + +export function extractColorFromText({ + text, +}: { + text: string; +}): string | null { + const cleaned = stripCssNoise({ text }); + + const color = toRgb(cleaned); + if (color) return colorToHexWithAlpha({ color }); + + // bare hex without # (culori needs the prefix) + const bareHexMatch = cleaned.match(/^([0-9a-fA-F]{3,8})$/); + if (bareHexMatch) { + const withHash = toRgb(`#${bareHexMatch[1]}`); + if (withHash) return colorToHexWithAlpha({ color: withHash }); + } + + // fallback: find #hex anywhere in the original text + const embeddedHexMatch = text.match(/#([0-9a-fA-F]{3,8})\b/); + if (embeddedHexMatch) { + const embedded = toRgb(`#${embeddedHexMatch[1]}`); + if (embedded) return colorToHexWithAlpha({ color: embedded }); + } + + return null; +} + +export function formatColorValue({ + hex, + format, +}: { + hex: string; + format: ColorFormat; +}): string { + switch (format) { + case "hex": + return hex; + case "rgb": { + const color = toRgb(`#${hex}`); + if (!color) return hex; + return `${Math.round(color.r * 255)}, ${Math.round(color.g * 255)}, ${Math.round(color.b * 255)}`; + } + case "hsl": { + const color = toHsl(`#${hex}`); + if (!color) return hex; + return `${Math.round(color.h ?? 0)}, ${Math.round((color.s ?? 0) * 100)}%, ${Math.round((color.l ?? 0) * 100)}%`; + } + case "hsv": { + const color = toHsv(`#${hex}`); + if (!color) return hex; + return `${Math.round(color.h ?? 0)}, ${Math.round((color.s ?? 0) * 100)}%, ${Math.round((color.v ?? 0) * 100)}%`; + } + } +} + +export function parseColorInput({ + input, + format, +}: { + input: string; + format: ColorFormat; +}): string | null { + switch (format) { + case "hex": { + const cleaned = input.replace("#", ""); + const isValidHex = /^[0-9a-fA-F]{3,8}$/.test(cleaned); + return isValidHex ? cleaned : null; + } + case "rgb": { + const parts = input.split(",").map((part) => parseInt(part.trim(), 10)); + if (parts.length < 3 || parts.some(Number.isNaN)) return null; + const color = { + mode: "rgb" as const, + r: parts[0] / 255, + g: parts[1] / 255, + b: parts[2] / 255, + }; + return formatHex(color).slice(1); + } + case "hsl": { + const parts = input.split(",").map((part) => parseFloat(part.trim())); + if (parts.length < 3 || parts.some(Number.isNaN)) return null; + const color = { + mode: "hsl" as const, + h: parts[0], + s: parts[1] / 100, + l: parts[2] / 100, + }; + return formatHex(color).slice(1); + } + case "hsv": { + const parts = input.split(",").map((part) => parseFloat(part.trim())); + if (parts.length < 3 || parts.some(Number.isNaN)) return null; + const color = { + mode: "hsv" as const, + h: parts[0], + s: parts[1] / 100, + v: parts[2] / 100, + }; + return formatHex(color).slice(1); + } + } +} diff --git a/apps/web/src/utils/id.ts b/apps/web/src/utils/id.ts index e452af43..a47ef733 100644 --- a/apps/web/src/utils/id.ts +++ b/apps/web/src/utils/id.ts @@ -1,28 +1,28 @@ -export function generateUUID(): string { - if ( - typeof crypto !== "undefined" && - typeof crypto.randomUUID === "function" - ) { - return crypto.randomUUID(); - } - - const bytes = new Uint8Array(16); - crypto.getRandomValues(bytes); - - bytes[6] = (bytes[6] & 0x0f) | 0x40; - bytes[8] = (bytes[8] & 0x3f) | 0x80; - - const hex = [...bytes].map((b) => b.toString(16).padStart(2, "0")); - - return ( - hex.slice(0, 4).join("") + - "-" + - hex.slice(4, 6).join("") + - "-" + - hex.slice(6, 8).join("") + - "-" + - hex.slice(8, 10).join("") + - "-" + - hex.slice(10, 16).join("") - ); -} +export function generateUUID(): string { + if ( + typeof crypto !== "undefined" && + typeof crypto.randomUUID === "function" + ) { + return crypto.randomUUID(); + } + + const bytes = new Uint8Array(16); + crypto.getRandomValues(bytes); + + bytes[6] = (bytes[6] & 0x0f) | 0x40; + bytes[8] = (bytes[8] & 0x3f) | 0x80; + + const hex = [...bytes].map((b) => b.toString(16).padStart(2, "0")); + + return ( + hex.slice(0, 4).join("") + + "-" + + hex.slice(4, 6).join("") + + "-" + + hex.slice(6, 8).join("") + + "-" + + hex.slice(8, 10).join("") + + "-" + + hex.slice(10, 16).join("") + ); +} diff --git a/apps/web/src/utils/math.ts b/apps/web/src/utils/math.ts index 24f9eaf4..6e912b43 100644 --- a/apps/web/src/utils/math.ts +++ b/apps/web/src/utils/math.ts @@ -1,84 +1,84 @@ -export function clamp({ - value, - min, - max, -}: { - value: number; - min: number; - max: number; -}): number { - return Math.max(min, Math.min(max, value)); -} - -export function clampRound({ - value, - min, - max, -}: { - value: number; - min: number; - max: number; -}): number { - return Math.round(clamp({ value, min, max })); -} - -function getFractionDigitsForStep({ step }: { step: number }): number { - const normalizedStep = step.toString().toLowerCase(); - if (normalizedStep.includes("e-")) { - return Number(normalizedStep.split("e-")[1] ?? 0); - } - const [, fractionalPart = ""] = normalizedStep.split("."); - return fractionalPart.length; -} - -export function snapToStep({ - value, - step, -}: { - value: number; - step: number; -}): number { - if (step <= 0) return value; - const snappedValue = Math.round(value / step) * step; - return Number( - snappedValue.toFixed(getFractionDigitsForStep({ step })), - ); -} - -export function isNearlyEqual({ - leftValue, - rightValue, - epsilon = 0.0001, -}: { - leftValue: number; - rightValue: number; - epsilon?: number; -}): boolean { - return Math.abs(leftValue - rightValue) <= epsilon; -} - -export function formatNumberForDisplay({ - value, - maxFractionDigits = 6, -}: { - value: number; - maxFractionDigits?: number; -}): string { - return Number(value.toFixed(maxFractionDigits)).toString(); -} - -export function evaluateMathExpression({ - input, -}: { - input: string; -}): number | null { - const sanitized = input.trim(); - if (!/^[\d.\s+\-*/()]+$/.test(sanitized)) return null; - try { - const result = new Function(`return (${sanitized})`)(); - if (typeof result !== "number" || !Number.isFinite(result)) return null; - return result; - } catch { - return null; - } -} +export function clamp({ + value, + min, + max, +}: { + value: number; + min: number; + max: number; +}): number { + return Math.max(min, Math.min(max, value)); +} + +export function clampRound({ + value, + min, + max, +}: { + value: number; + min: number; + max: number; +}): number { + return Math.round(clamp({ value, min, max })); +} + +function getFractionDigitsForStep({ step }: { step: number }): number { + const normalizedStep = step.toString().toLowerCase(); + if (normalizedStep.includes("e-")) { + return Number(normalizedStep.split("e-")[1] ?? 0); + } + const [, fractionalPart = ""] = normalizedStep.split("."); + return fractionalPart.length; +} + +export function snapToStep({ + value, + step, +}: { + value: number; + step: number; +}): number { + if (step <= 0) return value; + const snappedValue = Math.round(value / step) * step; + return Number( + snappedValue.toFixed(getFractionDigitsForStep({ step })), + ); +} + +export function isNearlyEqual({ + leftValue, + rightValue, + epsilon = 0.0001, +}: { + leftValue: number; + rightValue: number; + epsilon?: number; +}): boolean { + return Math.abs(leftValue - rightValue) <= epsilon; +} + +export function formatNumberForDisplay({ + value, + maxFractionDigits = 6, +}: { + value: number; + maxFractionDigits?: number; +}): string { + return Number(value.toFixed(maxFractionDigits)).toString(); +} + +export function evaluateMathExpression({ + input, +}: { + input: string; +}): number | null { + const sanitized = input.trim(); + if (!/^[\d.\s+\-*/()]+$/.test(sanitized)) return null; + try { + const result = new Function(`return (${sanitized})`)(); + if (typeof result !== "number" || !Number.isFinite(result)) return null; + return result; + } catch { + return null; + } +} -- cgit v1.3.1