Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/core/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const isFunction = (obj: unknown): obj is Function =>
export function isObject(
item: unknown,
): item is Record<string | number | symbol, unknown> {
return typeof item === 'object';
return item !== null && typeof item === 'object';
}

export function isArray(item: unknown): item is any[] {
Expand Down Expand Up @@ -99,9 +99,9 @@ export function flattenStyles(
result: Styles = {},
): Styles {
if (isArray(obj)) {
obj.forEach((item) => {
flattenStyles(item, result);
});
for (let i = 0; i < obj.length; i++) {
flattenStyles(obj[i], result);
}
} else if (obj) {
// handle the case where the object is not an array
for (const key in obj) {
Expand Down
42 changes: 31 additions & 11 deletions src/primitives/utils/chainFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,42 @@ export function chainFunctions<T extends AnyFunction>(
export function chainFunctions(
...fns: (AnyFunction | undefined | null | false)[]
): AnyFunction | undefined {
const onlyFunctions = fns.filter((func) => typeof func === 'function');
if (onlyFunctions.length === 0) {
return undefined;
// Inline filter to avoid the intermediate array allocation when most
// callers pass 2 args and one or both happen to be falsy.
let first: AnyFunction | undefined;
let onlyFunctions: AnyFunction[] | undefined;
for (let i = 0; i < fns.length; i++) {
const fn = fns[i];
if (typeof fn !== 'function') continue;
if (first === undefined) {
first = fn;
} else {
if (onlyFunctions === undefined) onlyFunctions = [first];
onlyFunctions.push(fn);
}
}

if (onlyFunctions.length === 1) {
return onlyFunctions[0];
if (first === undefined) return undefined;
if (onlyFunctions === undefined) return first;

// Fast path: exactly two functions — the common case for ref/handler
// forwarding (props.onX + local onX). Avoids the loop.
if (onlyFunctions.length === 2) {
const a = onlyFunctions[0]!;
const b = onlyFunctions[1]!;
return function (this: unknown, ...innerArgs) {
const result = a.apply(this, innerArgs);
if (result === true) return result;
return b.apply(this, innerArgs);
};
}

return function (...innerArgs) {
const chained = onlyFunctions;
return function (this: unknown, ...innerArgs) {
let result;
for (const func of onlyFunctions) {
result = func.apply(this, innerArgs);
if (result === true) {
return result;
}
for (let i = 0; i < chained.length; i++) {
result = chained[i]!.apply(this, innerArgs);
if (result === true) return result;
}
return result;
};
Expand Down
98 changes: 58 additions & 40 deletions src/primitives/utils/createBlurredImage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,32 +100,38 @@ function applyVerticalBlur(
half: number,
): void {
for (let y = 0; y < height; y++) {
const kStart = -y < -half ? -half : -y;
const kEnd = height - 1 - y < half ? height - 1 - y : half;

// Kernel sums to 1; only edge rows need a renormalization factor.
let invWeight = 1;
if (kStart !== -half || kEnd !== half) {
let weightSum = 0;
for (let k = kStart; k <= kEnd; k++) weightSum += kernel[k + half]!;
invWeight = 1 / weightSum;
}

const rowBase = y * width;
for (let x = 0; x < width; x++) {
let r = 0,
g = 0,
b = 0,
a = 0;
let weightSum = 0;

for (let ky = -half; ky <= half; ky++) {
const py = y + ky;
if (py >= 0 && py < height) {
const pixelIndex = (py * width + x) * 4;
const weight = kernel[ky + half]!;

r += input[pixelIndex]! * weight;
g += input[pixelIndex + 1]! * weight;
b += input[pixelIndex + 2]! * weight;
a += input[pixelIndex + 3]! * weight;
weightSum += weight;
}
for (let ky = kStart; ky <= kEnd; ky++) {
const pixelIndex = ((y + ky) * width + x) * 4;
const weight = kernel[ky + half]!;
r += input[pixelIndex]! * weight;
g += input[pixelIndex + 1]! * weight;
b += input[pixelIndex + 2]! * weight;
a += input[pixelIndex + 3]! * weight;
}

const outputIndex = (y * width + x) * 4;
output[outputIndex] = r / weightSum;
output[outputIndex + 1] = g / weightSum;
output[outputIndex + 2] = b / weightSum;
output[outputIndex + 3] = a / weightSum;
const outputIndex = (rowBase + x) * 4;
output[outputIndex] = r * invWeight;
output[outputIndex + 1] = g * invWeight;
output[outputIndex + 2] = b * invWeight;
output[outputIndex + 3] = a * invWeight;
}
}
}
Expand All @@ -147,33 +153,46 @@ function applyHorizontalBlur(
kernel: Readonly<GaussianKernel>,
half: number,
): void {
// Precompute per-column inverse weight sums; kernel sums to 1 in the interior.
const invWeights = new Float64Array(width);
for (let x = 0; x < width; x++) {
const kStart = -x < -half ? -half : -x;
const kEnd = width - 1 - x < half ? width - 1 - x : half;
if (kStart === -half && kEnd === half) {
invWeights[x] = 1;
} else {
let weightSum = 0;
for (let k = kStart; k <= kEnd; k++) weightSum += kernel[k + half]!;
invWeights[x] = 1 / weightSum;
}
}

for (let y = 0; y < height; y++) {
const rowBase = y * width;
for (let x = 0; x < width; x++) {
const kStart = -x < -half ? -half : -x;
const kEnd = width - 1 - x < half ? width - 1 - x : half;

let r = 0,
g = 0,
b = 0,
a = 0;
let weightSum = 0;

for (let kx = -half; kx <= half; kx++) {
const px = x + kx;
if (px >= 0 && px < width) {
const pixelIndex = (y * width + px) * 4;
const weight = kernel[kx + half]!;

r += input[pixelIndex]! * weight;
g += input[pixelIndex + 1]! * weight;
b += input[pixelIndex + 2]! * weight;
a += input[pixelIndex + 3]! * weight;
weightSum += weight;
}
for (let kx = kStart; kx <= kEnd; kx++) {
const pixelIndex = (rowBase + x + kx) * 4;
const weight = kernel[kx + half]!;
r += input[pixelIndex]! * weight;
g += input[pixelIndex + 1]! * weight;
b += input[pixelIndex + 2]! * weight;
a += input[pixelIndex + 3]! * weight;
}

const outputIndex = (y * width + x) * 4;
output[outputIndex] = r / weightSum;
output[outputIndex + 1] = g / weightSum;
output[outputIndex + 2] = b / weightSum;
output[outputIndex + 3] = a / weightSum;
const invWeight = invWeights[x]!;
const outputIndex = (rowBase + x) * 4;
output[outputIndex] = r * invWeight;
output[outputIndex + 1] = g * invWeight;
output[outputIndex + 2] = b * invWeight;
output[outputIndex + 3] = a * invWeight;
}
}
}
Expand Down Expand Up @@ -215,15 +234,14 @@ function gaussianBlurConvolution(
): ImageData {
const { data } = imageData;
const { width, height } = dimensions;
const tempData = new Uint8ClampedArray(data.length);
const output = new Uint8ClampedArray(data.length);

const kernelSize = Math.ceil(radius * 2) * 2 + 1;
const kernel = generateGaussianKernel(kernelSize, radius);
const half = Math.floor(kernelSize / 2);

applyHorizontalBlur(data, output, width, height, kernel, half);
const half = (kernelSize - 1) >> 1;

const tempData = new Uint8ClampedArray(output);
applyHorizontalBlur(data, tempData, width, height, kernel, half);
applyVerticalBlur(tempData, output, width, height, kernel, half);

return new ImageData(output, width, height);
Expand Down
14 changes: 8 additions & 6 deletions src/primitives/utils/handleNavigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,10 +220,10 @@ export function moveSelection(
return selectChild(el, selected);
}

function distanceBetweenRectCenters(a: lng.Rect, b: lng.Rect): number {
const dx = Math.abs(a.x + a.width / 2 - (b.x + b.width / 2)) / 2;
const dy = Math.abs(a.y + a.height / 2 - (b.y + b.height / 2)) / 2;
return Math.sqrt(dx * dx + dy * dy);
function squaredDistanceBetweenRectCenters(a: lng.Rect, b: lng.Rect): number {
const dx = a.x + a.width / 2 - (b.x + b.width / 2);
const dy = a.y + a.height / 2 - (b.y + b.height / 2);
return dx * dx + dy * dy;
}

function findClosestFocusableChildIdx(
Expand All @@ -238,12 +238,14 @@ function findClosestFocusableChildIdx(
let closestIdx = -1;
let closestDist = Infinity;

for (const [idx, child] of el.children.entries()) {
const children = el.children;
for (let idx = 0; idx < children.length; idx++) {
const child = children[idx]!;
if (!child.skipFocus) {
lng.getElementScreenRect(child, el, childRect);
childRect.x += elRect.x;
childRect.y += elRect.y;
const distance = distanceBetweenRectCenters(prevRect, childRect);
const distance = squaredDistanceBetweenRectCenters(prevRect, childRect);
if (distance < closestDist) {
closestDist = distance;
closestIdx = idx;
Expand Down
20 changes: 11 additions & 9 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,18 @@ export function hexColor(color: string | number = ''): number {

if (typeof color === 'string') {
// Renderer expects RGBA values
if (color.startsWith('#')) {
return Number(
color.replace('#', '0x') + (color.length === 7 ? 'ff' : ''),
);
}

if (color.startsWith('0x')) {
return Number(color);
let hex: string;
if (color.charCodeAt(0) === 35 /* '#' */) {
hex = color.length === 7 ? color.slice(1) + 'ff' : color.slice(1);
} else if (
color.charCodeAt(0) === 48 &&
color.charCodeAt(1) === 120 /* '0x' */
) {
hex = color.slice(2);
} else {
hex = color.length === 6 ? color + 'ff' : color;
}
return Number('0x' + (color.length === 6 ? color + 'ff' : color));
return parseInt(hex, 16);
}

return 0x00000000;
Expand Down
Loading