- Consolidated duplicate UndoManagers to single instance - Fixed connection promise to only resolve on 'connected' status - Fixed WebSocketProvider import (WebsocketProvider) - Added proper doc.destroy() cleanup - Renamed isPresenceInitialized property to avoid conflict Co-Authored-By: Paperclip <noreply@paperclip.ing>
253 lines
6.3 KiB
JavaScript
253 lines
6.3 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow strict-local
|
|
* @format
|
|
*/
|
|
|
|
import type {ProcessedColorValue} from './processColor';
|
|
import type {BoxShadowValue} from './StyleSheetTypes';
|
|
|
|
import {enableOptimizedBoxShadowParsing} from '../../src/private/featureflags/ReactNativeFeatureFlags';
|
|
import processColor from './processColor';
|
|
|
|
// Pre-compiled regex patterns for performance - avoids regex compilation on each call
|
|
const COMMA_SPLIT_REGEX = /,(?![^()]*\))/;
|
|
const WHITESPACE_SPLIT_REGEX = /\s+(?![^(]*\))/;
|
|
const LENGTH_PARSE_REGEX = /^([+-]?\d*\.?\d+)(px)?$/;
|
|
const NEWLINE_REGEX = /\n/g;
|
|
|
|
export type ParsedBoxShadow = {
|
|
offsetX: number,
|
|
offsetY: number,
|
|
color?: ProcessedColorValue,
|
|
blurRadius?: number,
|
|
spreadDistance?: number,
|
|
inset?: boolean,
|
|
};
|
|
|
|
export default function processBoxShadow(
|
|
rawBoxShadows: ?(ReadonlyArray<BoxShadowValue> | string),
|
|
): Array<ParsedBoxShadow> {
|
|
const result: Array<ParsedBoxShadow> = [];
|
|
if (rawBoxShadows == null) {
|
|
return result;
|
|
}
|
|
|
|
const boxShadowList =
|
|
typeof rawBoxShadows === 'string'
|
|
? parseBoxShadowString(
|
|
rawBoxShadows.replace(
|
|
enableOptimizedBoxShadowParsing() ? NEWLINE_REGEX : /\n/g,
|
|
' ',
|
|
),
|
|
)
|
|
: rawBoxShadows;
|
|
|
|
for (const rawBoxShadow of boxShadowList) {
|
|
const parsedBoxShadow: ParsedBoxShadow = {
|
|
offsetX: 0,
|
|
offsetY: 0,
|
|
};
|
|
|
|
let value;
|
|
for (const arg in rawBoxShadow) {
|
|
switch (arg) {
|
|
case 'offsetX':
|
|
value =
|
|
typeof rawBoxShadow.offsetX === 'string'
|
|
? parseLength(rawBoxShadow.offsetX)
|
|
: rawBoxShadow.offsetX;
|
|
if (value == null) {
|
|
return [];
|
|
}
|
|
|
|
parsedBoxShadow.offsetX = value;
|
|
break;
|
|
case 'offsetY':
|
|
value =
|
|
typeof rawBoxShadow.offsetY === 'string'
|
|
? parseLength(rawBoxShadow.offsetY)
|
|
: rawBoxShadow.offsetY;
|
|
if (value == null) {
|
|
return [];
|
|
}
|
|
|
|
parsedBoxShadow.offsetY = value;
|
|
break;
|
|
case 'spreadDistance':
|
|
value =
|
|
typeof rawBoxShadow.spreadDistance === 'string'
|
|
? parseLength(rawBoxShadow.spreadDistance)
|
|
: rawBoxShadow.spreadDistance;
|
|
if (value == null) {
|
|
return [];
|
|
}
|
|
|
|
parsedBoxShadow.spreadDistance = value;
|
|
break;
|
|
case 'blurRadius':
|
|
value =
|
|
typeof rawBoxShadow.blurRadius === 'string'
|
|
? parseLength(rawBoxShadow.blurRadius)
|
|
: rawBoxShadow.blurRadius;
|
|
if (value == null || value < 0) {
|
|
return [];
|
|
}
|
|
|
|
parsedBoxShadow.blurRadius = value;
|
|
break;
|
|
case 'color':
|
|
const color = processColor(rawBoxShadow.color);
|
|
if (color == null) {
|
|
return [];
|
|
}
|
|
|
|
parsedBoxShadow.color = color;
|
|
break;
|
|
case 'inset':
|
|
parsedBoxShadow.inset = rawBoxShadow.inset;
|
|
}
|
|
}
|
|
result.push(parsedBoxShadow);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function parseBoxShadowString(rawBoxShadows: string): Array<BoxShadowValue> {
|
|
let result: Array<BoxShadowValue> = [];
|
|
|
|
for (const rawBoxShadow of rawBoxShadows
|
|
.split(
|
|
enableOptimizedBoxShadowParsing() ? COMMA_SPLIT_REGEX : /,(?![^()]*\))/,
|
|
) // split by comma that is not in parenthesis
|
|
.map(bS => bS.trim())
|
|
.filter(bS => bS !== '')) {
|
|
const boxShadow: BoxShadowValue = {
|
|
offsetX: 0,
|
|
offsetY: 0,
|
|
};
|
|
let offsetX: number | string;
|
|
let offsetY: number | string;
|
|
let keywordDetectedAfterLength = false;
|
|
|
|
let lengthCount = 0;
|
|
|
|
// split rawBoxShadow string by all whitespaces that are not in parenthesis
|
|
const args = rawBoxShadow.split(
|
|
enableOptimizedBoxShadowParsing()
|
|
? WHITESPACE_SPLIT_REGEX
|
|
: /\s+(?![^(]*\))/,
|
|
);
|
|
for (const arg of args) {
|
|
const processedColor = processColor(arg);
|
|
if (processedColor != null) {
|
|
if (boxShadow.color != null) {
|
|
return [];
|
|
}
|
|
if (offsetX != null) {
|
|
keywordDetectedAfterLength = true;
|
|
}
|
|
boxShadow.color = arg;
|
|
continue;
|
|
}
|
|
|
|
if (arg === 'inset') {
|
|
if (boxShadow.inset != null) {
|
|
return [];
|
|
}
|
|
if (offsetX != null) {
|
|
keywordDetectedAfterLength = true;
|
|
}
|
|
boxShadow.inset = true;
|
|
continue;
|
|
}
|
|
|
|
switch (lengthCount) {
|
|
case 0:
|
|
offsetX = arg;
|
|
lengthCount++;
|
|
break;
|
|
case 1:
|
|
if (keywordDetectedAfterLength) {
|
|
return [];
|
|
}
|
|
offsetY = arg;
|
|
lengthCount++;
|
|
break;
|
|
case 2:
|
|
if (keywordDetectedAfterLength) {
|
|
return [];
|
|
}
|
|
boxShadow.blurRadius = arg;
|
|
lengthCount++;
|
|
break;
|
|
case 3:
|
|
if (keywordDetectedAfterLength) {
|
|
return [];
|
|
}
|
|
boxShadow.spreadDistance = arg;
|
|
lengthCount++;
|
|
break;
|
|
default:
|
|
return [];
|
|
}
|
|
}
|
|
|
|
if (offsetX == null || offsetY == null) {
|
|
return [];
|
|
}
|
|
|
|
boxShadow.offsetX = offsetX;
|
|
boxShadow.offsetY = offsetY;
|
|
|
|
result.push(boxShadow);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function parseLength(length: string): ?number {
|
|
if (enableOptimizedBoxShadowParsing()) {
|
|
// Use pre-compiled regex for performance
|
|
const match = LENGTH_PARSE_REGEX.exec(length);
|
|
|
|
if (!match) {
|
|
return null;
|
|
}
|
|
|
|
const value = parseFloat(match[1]);
|
|
if (Number.isNaN(value)) {
|
|
return null;
|
|
}
|
|
|
|
// match[2] is 'px' or undefined
|
|
// If no unit and value is not 0, return null
|
|
if (match[2] == null && value !== 0) {
|
|
return null;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
// matches on args with units like "1.5 5% -80deg"
|
|
const argsWithUnitsRegex = /([+-]?\d*(\.\d+)?)([\w\W]+)?/g;
|
|
const match = argsWithUnitsRegex.exec(length);
|
|
|
|
if (!match || Number.isNaN(match[1])) {
|
|
return null;
|
|
}
|
|
|
|
if (match[3] != null && match[3] !== 'px') {
|
|
return null;
|
|
}
|
|
|
|
if (match[3] == null && match[1] !== '0') {
|
|
return null;
|
|
}
|
|
|
|
return Number(match[1]);
|
|
}
|