Skip to content
Open
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
2 changes: 1 addition & 1 deletion src/components/Avatar/connected/InvoiceRoomAvatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ function InvoiceRoomAvatar({
const [invoiceReceiverPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${getNonEmptyStringOnyxID(invoiceReceiverPolicyID)}`, {selector: policyAvatarFieldsSelector});

// The unknown account, standing in for a missing receiver.
const receiverIcon = getInvoiceReceiverIcon(invoiceReceiver, invoiceReceiverPolicy, personalDetails) ?? {
const receiverIcon = getInvoiceReceiverIcon(invoiceReceiver, invoiceReceiverPolicy, personalDetails ?? undefined) ?? {
id: CONST.DEFAULT_NUMBER_ID,
type: CONST.ICON_TYPE_AVATAR,
source: defaultAvatars.FallbackAvatar,
Expand Down
2 changes: 1 addition & 1 deletion src/components/Avatar/connected/useSortedIcons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ function useSortedIcons(icons: Icon[], sort?: SortingOption | SortingOption[]):
if (sortBy.includes(CONST.REPORT_ACTION_AVATARS.SORT_BY.NAME)) {
// Read personal details only for a name sort, so other stacks don't re-render on every personal details change.
// `sortIconsByName` sorts in place, so sort a copy and leave the given array as it was
sortedIcons = sortIconsByName([...icons], use(PersonalDetailsContext), localeCompare);
sortedIcons = sortIconsByName([...icons], use(PersonalDetailsContext) ?? undefined, localeCompare);
} else if (sortBy.includes(CONST.REPORT_ACTION_AVATARS.SORT_BY.ID)) {
sortedIcons = lodashSortBy(icons, (icon) => icon.id);
}
Expand Down
12 changes: 6 additions & 6 deletions src/components/TextInput/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import Visibility from '@libs/Visibility';

import CONST from '@src/CONST';

import type {StyleProp, ViewStyle} from 'react-native';
import type {ViewStyle} from 'react-native';

import React, {useEffect, useRef} from 'react';

import type {BaseTextInputProps} from './BaseTextInput/types';
import type {BaseTextInputProps, BaseTextInputRef} from './BaseTextInput/types';

import BaseTextInput from './BaseTextInput';
import * as styleConst from './styleConst';
Expand All @@ -23,7 +23,7 @@ function getIsRestoringKeyboardFocus() {

function TextInput({ref, ...props}: BaseTextInputProps) {
const styles = useThemeStyles();
const textInputRef = useRef<HTMLFormElement | null>(null);
const textInputRef = useRef<BaseTextInputRef | null>(null);
const removeVisibilityListenerRef = useRef<(() => void) | null>(null);

useEffect(() => {
Expand Down Expand Up @@ -88,7 +88,7 @@ function TextInput({ref, ...props}: BaseTextInputProps) {
}, []);

const isLabeledMultiline = !!props.label?.length && props.multiline;
const labelAnimationStyle = {
const labelAnimationStyle: ViewStyle & Record<'--active-label-translate-y' | '--active-label-scale' | '--label-transition-duration', string> = {
// eslint-disable-next-line @typescript-eslint/naming-convention
'--active-label-translate-y': `${styleConst.ACTIVE_LABEL_TRANSLATE_Y}px`,
// eslint-disable-next-line @typescript-eslint/naming-convention
Expand All @@ -101,7 +101,7 @@ function TextInput({ref, ...props}: BaseTextInputProps) {
<BaseTextInput
{...props}
ref={(element) => {
textInputRef.current = element as HTMLFormElement;
textInputRef.current = element;

if (!ref) {
return;
Expand All @@ -116,7 +116,7 @@ function TextInput({ref, ...props}: BaseTextInputProps) {
ref.current = element;
}}
inputStyle={[styles.baseTextInput, styles.textInputDesktop, isLabeledMultiline ? styles.textInputMultiline : {}, props.inputStyle]}
textInputContainerStyles={[labelAnimationStyle as StyleProp<ViewStyle>, props.textInputContainerStyles, styles.cursorText]}
textInputContainerStyles={[labelAnimationStyle, props.textInputContainerStyles, styles.cursorText]}
/>
);
}
Expand Down
11 changes: 6 additions & 5 deletions src/components/ValidateCodeInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import {isNumeric} from '@libs/ValidationUtils';

import CONST from '@src/CONST';

import type {ComponentRef, ForwardedRef, KeyboardEvent} from 'react';
import type {FocusEvent, TextInput as RNTextInput, TextInputKeyPressEvent} from 'react-native';
import type {ForwardedRef, KeyboardEvent} from 'react';
import type {FocusEvent, TextInputKeyPressEvent} from 'react-native';
import type {ValueOf} from 'type-fest';

import React, {useEffect, useImperativeHandle, useRef, useState} from 'react';
Expand All @@ -23,6 +23,7 @@ import type {BaseTextInputRef} from './TextInput/BaseTextInput/types';
import FormHelpMessage from './FormHelpMessage';
import Text from './Text';
import TextInput from './TextInput';
import isTextInputFocused from './TextInput/BaseTextInput/isTextInputFocused';

const TEXT_INPUT_EMPTY_STATE = '';

Expand All @@ -40,7 +41,7 @@ const useValidateCodePaste = (inputRef: React.RefObject<BaseTextInputRef | null>
return;
}

const isFocused = (inputRef.current as ComponentRef<typeof RNTextInput>)?.isFocused?.() ?? false;
const isFocused = isTextInputFocused(inputRef);
if (!isFocused) {
return;
}
Expand Down Expand Up @@ -343,7 +344,7 @@ function ValidateCodeInput({
* NOTE: when using Android Emulator, this can only be tested using
* hardware keyboard inputs.
*/
const onKeyPress = (event: Partial<TextInputKeyPressEvent>) => {
const onKeyPress = (event: Partial<TextInputKeyPressEvent> & Partial<Pick<KeyboardEvent, 'shiftKey'>>) => {
const keyValue = event?.nativeEvent?.key;
if (keyValue === 'Backspace' || keyValue === '<') {
let numbers = decomposeString(value, maxLength);
Expand Down Expand Up @@ -414,7 +415,7 @@ function ValidateCodeInput({
setInput(TEXT_INPUT_EMPTY_STATE);
onFulfill(value);
} else if (keyValue === 'Tab' && focusedIndex !== undefined) {
const newFocusedIndex = (event as unknown as KeyboardEvent).shiftKey ? focusedIndex - 1 : focusedIndex + 1;
const newFocusedIndex = event.shiftKey ? focusedIndex - 1 : focusedIndex + 1;
if (newFocusedIndex >= 0 && newFocusedIndex < maxLength) {
setInputAndIndex(newFocusedIndex);
inputRef.current?.focus();
Expand Down
14 changes: 8 additions & 6 deletions src/components/createOnyxContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,28 @@ import type {OnyxKey} from '@src/ONYXKEYS';
import type ChildrenProps from '@src/types/utils/ChildrenProps';

import type {ComponentType, Context, ReactNode} from 'react';
import type {OnyxValue} from 'react-native-onyx';
import type {OnyxValue, UseOnyxResult} from 'react-native-onyx';

import {Str} from 'expensify-common';
import React, {createContext, useContext} from 'react';

type OnyxContextValue<TOnyxKey extends OnyxKey> = UseOnyxResult<OnyxValue<TOnyxKey>>[0];

// createOnyxContext return type
type CreateOnyxContext<TOnyxKey extends OnyxKey> = [ComponentType<ChildrenProps>, React.Context<OnyxValue<TOnyxKey>>, () => OnyxValue<TOnyxKey>];
type CreateOnyxContext<TOnyxKey extends OnyxKey> = [ComponentType<ChildrenProps>, Context<OnyxContextValue<TOnyxKey> | null>, () => OnyxContextValue<TOnyxKey>];

type OnyxContextProviderImplProps<TOnyxKey extends OnyxKey> = ChildrenProps & {
onyxKeyName: TOnyxKey;
Context: Context<OnyxValue<TOnyxKey>>;
Context: Context<OnyxContextValue<TOnyxKey> | null>;
};

function OnyxContextProviderImpl<TOnyxKey extends OnyxKey>({onyxKeyName, Context: OnyxContext, children}: OnyxContextProviderImplProps<TOnyxKey>): ReactNode {
const [value] = useOnyx(onyxKeyName);
return <OnyxContext.Provider value={value as OnyxValue<TOnyxKey>}>{children}</OnyxContext.Provider>;
return <OnyxContext.Provider value={value}>{children}</OnyxContext.Provider>;
}

export default <TOnyxKey extends OnyxKey>(onyxKeyName: TOnyxKey): CreateOnyxContext<TOnyxKey> => {
const Context = createContext<OnyxValue<TOnyxKey>>(null as OnyxValue<TOnyxKey>);
const Context = createContext<OnyxContextValue<TOnyxKey> | null>(null);
function Provider(props: ChildrenProps): ReactNode {
return (
<OnyxContextProviderImpl
Expand All @@ -41,7 +43,7 @@ export default <TOnyxKey extends OnyxKey>(onyxKeyName: TOnyxKey): CreateOnyxCont
if (value === null) {
throw new Error(`useOnyxContext must be used within a OnyxListItemProvider [key: ${onyxKeyName}]`);
}
return value as OnyxValue<TOnyxKey>;
return value;
};

return [Provider, Context, useOnyxContext];
Expand Down
14 changes: 7 additions & 7 deletions src/hooks/useFilterFormValues.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,32 +20,32 @@ import useCurrentUserPersonalDetails from './useCurrentUserPersonalDetails';
import useExportedToFilterOptions from './useExportedToFilterOptions';
import useOnyx from './useOnyx';

function policiesSelector(policies: OnyxCollection<Policy>): OnyxCollection<Policy> {
function policiesSelector(policies: OnyxCollection<Policy>): OnyxCollection<Pick<Policy, 'taxRates'>> {
if (!policies) {
return policies;
}
const result: OnyxCollection<Policy> = {};
const result: OnyxCollection<Pick<Policy, 'taxRates'>> = {};
for (const [key, policy] of Object.entries(policies)) {
if (!policy) {
continue;
}
result[key] = {taxRates: policy.taxRates} as Policy;
result[key] = {taxRates: policy.taxRates};
}
return result;
}

function policyCategoriesSelector(categories: OnyxCollection<PolicyCategories>): OnyxCollection<PolicyCategories> {
function policyCategoriesSelector(categories: OnyxCollection<PolicyCategories>): OnyxCollection<Record<string, Pick<PolicyCategories[string], 'name'>>> {
if (!categories) {
return categories;
}
const result: OnyxCollection<PolicyCategories> = {};
const result: OnyxCollection<Record<string, Pick<PolicyCategories[string], 'name'>>> = {};
for (const [collectionKey, policyCategories] of Object.entries(categories)) {
if (!policyCategories) {
continue;
}
const minimalCategories: PolicyCategories = {};
const minimalCategories: Record<string, Pick<PolicyCategories[string], 'name'>> = {};
for (const [catKey, category] of Object.entries(policyCategories)) {
minimalCategories[catKey] = {name: category.name} as PolicyCategories[string];
minimalCategories[catKey] = {name: category.name};
}
result[collectionKey] = minimalCategories;
}
Expand Down
5 changes: 3 additions & 2 deletions src/hooks/usePermissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,12 @@ export default function usePermissions(): UsePermissions {

return useMemo(() => {
const permissions: UsePermissions = {
isBetaEnabled: (beta: Beta) => Permissions.isBetaEnabled(beta, betas, betaConfiguration, betaOverrides, environment),
isBetaEnabled: (beta: Beta) => Permissions.isBetaEnabled(beta, betas ?? undefined, betaConfiguration ?? undefined, betaOverrides ?? undefined, environment),

// Prefer isBetaEnabled. This exists for the consumers that must tell "off" apart from "not loaded yet",
// currently the inactive vendor violation and the approval mode next steps, reached from many components
isBetaEnabledOrUnknown: (beta: Beta) => (betas === undefined ? undefined : Permissions.isBetaEnabled(beta, betas, betaConfiguration, betaOverrides, environment)),
isBetaEnabledOrUnknown: (beta: Beta) =>
betas === undefined ? undefined : Permissions.isBetaEnabled(beta, betas ?? undefined, betaConfiguration ?? undefined, betaOverrides ?? undefined, environment),
};

for (permissionKey in Permissions) {
Expand Down
10 changes: 8 additions & 2 deletions src/libs/DomUtils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ const addCSS = (css: string, styleId: string) => {
if (existingStyle) {
if ('styleSheet' in existingStyle) {
// Supports IE8 and below
(existingStyle.styleSheet as CSSStyleDeclaration).cssText = css;
const {styleSheet} = existingStyle;
if (typeof styleSheet === 'object' && styleSheet !== null && 'cssText' in styleSheet) {
styleSheet.cssText = css;
}
} else {
existingStyle.innerHTML = css;
}
Expand All @@ -19,7 +22,10 @@ const addCSS = (css: string, styleId: string) => {

if ('styleSheet' in styleElement) {
// Supports IE8 and below
(styleElement.styleSheet as CSSStyleDeclaration).cssText = css;
const {styleSheet} = styleElement;
if (typeof styleSheet === 'object' && styleSheet !== null && 'cssText' in styleSheet) {
styleSheet.cssText = css;
}
} else {
styleElement.appendChild(document.createTextNode(css));
}
Expand Down
2 changes: 1 addition & 1 deletion src/libs/PolicyUtils/tax.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ function getTaxByID(policy: OnyxEntry<Policy>, taxID: string): TaxRate | undefin

/** Get a tax rate object built like Record<TaxRateName, RelatedTaxRateKeys>.
* We want to allow user to choose over TaxRateName and there might be a situation when one TaxRateName has two possible keys in different policies */
function getAllTaxRatesNamesAndKeys(policies: OnyxCollection<Policy>): Record<string, string[]> {
function getAllTaxRatesNamesAndKeys(policies: OnyxCollection<Pick<Policy, 'taxRates'>>): Record<string, string[]> {
const allTaxRates: Record<string, string[]> = {};
const seenKeys: Record<string, Set<string>> = {};

Expand Down
26 changes: 9 additions & 17 deletions src/libs/SearchQueryUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1578,12 +1578,7 @@ function isPolicyCollection(key: OnyxCollectionKey, data: unknown): data is Onyx
return !!data;
}

function getAllPolicyValues<T extends OnyxCollectionKey>(
policyID: Filter | undefined,
key: T,
policyData: OnyxCollection<OnyxCollectionValuesMapping[T]>,
policies?: OnyxCollection<OnyxTypes.Policy>,
): Array<OnyxCollectionValuesMapping[T]> {
function getAllPolicyValues<TValue>(policyID: Filter | undefined, key: OnyxCollectionKey, policyData: OnyxCollection<TValue>, policies?: OnyxCollection<OnyxTypes.Policy>): TValue[] {
if (!policyData || !policyID || !policyID.value) {
return Object.values(policyData ?? {}).filter((data): data is NonNullable<typeof data> => !!data);
}
Expand All @@ -1592,16 +1587,13 @@ function getAllPolicyValues<T extends OnyxCollectionKey>(
const resolvedValues = policiesForResolution ? policyID.value.map((id) => resolvePolicyIDFromName(id, policiesForResolution)) : policyID.value;

if (policyID.isNegated) {
return Object.keys(policyData).reduce(
(acc, curr) => {
const id = curr.replace(key, '');
if (!resolvedValues.includes(id) && policyData[curr]) {
acc.push(policyData[curr]);
}
return acc;
},
[] as Array<OnyxCollectionValuesMapping[T]>,
);
return Object.keys(policyData).reduce<TValue[]>((acc, curr) => {
const id = curr.replace(key, '');
if (!resolvedValues.includes(id) && policyData[curr]) {
acc.push(policyData[curr]);
}
return acc;
}, []);
}

return resolvedValues.map((id) => policyData?.[`${key}${id}`]).filter((data): data is NonNullable<typeof data> => !!data);
Expand Down Expand Up @@ -1810,7 +1802,7 @@ function getDateRangeForPreset(preset: SearchDatePreset): {start: string; end: s
// Adds bankAccountList and currentUserAccountID for the new bank account and from:me filters. Refactoring this to a params object would touch every call site and is out of scope here.
function buildFilterFormValuesFromQuery(
queryJSON: SearchQueryJSON,
policyCategories: OnyxCollection<OnyxTypes.PolicyCategories>,
policyCategories: OnyxCollection<Record<string, Pick<OnyxTypes.PolicyCategory, 'name'>>>,
currencyList: OnyxTypes.CurrencyList,
personalDetails: OnyxTypes.PersonalDetailsList | undefined,
cardList: OnyxTypes.CardList | undefined,
Expand Down
Loading
Loading