From 05297d0a9d1e7d1dcc3d76462acd0f2c40b2698d Mon Sep 17 00:00:00 2001 From: t <28008958+taeh98@users.noreply.github.com> Date: Thu, 31 Aug 2023 00:01:40 +0100 Subject: [PATCH] chore: add ci checks, order fields, fix types, and simplify logic --- .github/workflows/ci.yml | 23 +- .typos.toml | 19 ++ index.d.ts | 357 ++++++++++++++------------- package-lock.json | 134 +++++----- package.json | 12 +- src/components/Picker.js | 515 ++++++++++++++++++++------------------- 6 files changed, 560 insertions(+), 500 deletions(-) create mode 100644 .typos.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 33bdbfd..1df2cc5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -85,5 +85,26 @@ jobs: - run: npm outdated - run: npx npm-check-updates + typos_spell_check: + name: Spell Check with Typos + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Check spelling with typos + uses: crate-ci/typos@master + with: + config: .typos.toml + + commitlint: + name: Check the commit conforms to the Conventional Commits specification with commitlint + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - uses: wagoid/commitlint-github-action@v5 + +# TODO: check commits squashed into one (i.e. all changes in a PR are in one commit after last one in primary branch) +# TODO: use release-please to increment version number, update changelog, and publish releases automatically if CI passes # TODO: check project builds/compiles correctly -# TODO: add tests +# TODO: add tests including coverage \ No newline at end of file diff --git a/.typos.toml b/.typos.toml new file mode 100644 index 0000000..c3da8ec --- /dev/null +++ b/.typos.toml @@ -0,0 +1,19 @@ +# docs at: https://github.com/crate-ci/typos/blob/master/docs/reference.md + +files.extend-exclude = [ + ".typos.toml" +] + +default.extend-ignore-re = [ + # false positives + "alguns itens selecionados", + "elementi sono stati selezionati", + "Nada a ser mostrado", + "prevProps", + "prevState", +] + +default.locale = "en-us" + +# note: add false negatives with corrections in default.extend-identifiers or default.extend-words +# todo: check mistakes over multiple words, e.g. "the the" instead of "the" \ No newline at end of file diff --git a/index.d.ts b/index.d.ts index a8cc255..e5250ef 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,6 +1,6 @@ declare module 'react-native-dropdown-picker' { - import type { Dispatch, PropsWithoutRef } from 'react'; - import type { + import { Dispatch, JSX, PropsWithoutRef, ReactElement } from 'react'; + import { FlatListProps, LayoutChangeEvent, ModalProps, @@ -19,24 +19,24 @@ declare module 'react-native-dropdown-picker' { export type ValueType = string | number | boolean; - export type ItemType = { - label?: string; - value?: T; + export type ItemType = { + containerStyle?: StyleProp; + disabled?: boolean; icon?: () => JSX.Element; + label?: string; + labelStyle?: StyleProp; parent?: T; selectable?: boolean; - disabled?: boolean; testID?: string; - containerStyle?: StyleProp; - labelStyle?: StyleProp; + value?: T; }; export type ModeType = 'DEFAULT' | 'SIMPLE' | 'BADGE'; export interface ModeInterface { + BADGE: string; DEFAULT: string; SIMPLE: string; - BADGE: string; } export type ListModeType = 'DEFAULT' | 'FLATLIST' | 'SCROLLVIEW' | 'MODAL'; @@ -44,278 +44,275 @@ declare module 'react-native-dropdown-picker' { export interface ListModeInterface { DEFAULT: string; FLATLIST: string; - SCROLLVIEW: string; MODAL: string; + SCROLLVIEW: string; } export interface SchemaInterface { - label: string; - value: string; + containerStyle: string; + disabled: string; icon: string; + label: string; + labelStyle: string; parent: string; selectable: string; - disabled: string; testID: string; - containerStyle: string; - labelStyle: string; + value: string; } export type LanguageType = | 'DEFAULT' | 'FALLBACK' - | 'EN' | 'AR' - | 'FA' - | 'TR' - | 'RU' + | 'EN' | 'ES' + | 'FA' + | 'FR' | 'ID' | 'IT' | 'PT' - | 'FR'; + | 'RU' + | 'TR'; export interface TranslationInterface { + NOTHING_TO_SHOW: string; PLACEHOLDER: string; SEARCH_PLACEHOLDER: string; - SELECTED_ITEMS_COUNT_TEXT: - | string - | { - [key in number | 'n']: string; - }; - NOTHING_TO_SHOW: string; + SELECTED_ITEMS_COUNT_TEXT: string | { [key in number | 'n']: string }; } - export interface RenderBadgeItemPropsInterface { - label: string; - value: T; - props: TouchableOpacityProps; - IconComponent: () => JSX.Element; - textStyle: StyleProp; + export type ThemeNameType = 'DEFAULT' | 'LIGHT' | 'DARK'; + export type ThemeType = object; // TODO: specify ThemeType further: currently any object + + export interface RenderBadgeItemPropsInterface { + badgeDotStyle: StyleProp; badgeStyle: StyleProp; badgeTextStyle: StyleProp; - badgeDotStyle: StyleProp; getBadgeColor: (value: string) => string; getBadgeDotColor: (value: string) => string; - showBadgeDot: boolean; + IconComponent: () => JSX.Element; + label: string; onPress: (value: T) => void; + props: TouchableOpacityProps; rtl: boolean; + showBadgeDot: boolean; + textStyle: StyleProp; THEME: ThemeType; + value: T; } - export interface RenderListItemPropsInterface { - rtl: boolean; - item: ItemType; - label: string; - value: T; - parent: T; - selectable: boolean; - disabled: boolean; - props: ViewProps; + export interface RenderListItemPropsInterface { + categorySelectable: boolean; + containerStyle: StyleProp; custom: boolean; - isSelected: boolean; + customItemContainerStyle: StyleProp; + customItemLabelStyle: StyleProp; + disabled: boolean; + disabledItemContainerStyle: StyleProp; + disabledItemLabelStyle: StyleProp; IconComponent: () => JSX.Element; - TickIconComponent: () => JSX.Element; + isSelected: boolean; + item: ItemType; + label: string; + labelStyle: StyleProp; + listChildContainerStyle: StyleProp; + listChildLabelStyle: StyleProp; listItemContainerStyle: StyleProp; listItemLabelStyle: StyleProp; - listChildContainerStyle: StyleProp; listParentContainerStyle: StyleProp; - listChildLabelStyle: StyleProp; listParentLabelStyle: StyleProp; - customItemContainerStyle: StyleProp; - customItemLabelStyle: StyleProp; + onPress: (value: T) => void; + parent: T; + props: ViewProps; + rtl: boolean; + selectable: boolean; selectedItemContainerStyle: StyleProp; selectedItemLabelStyle: StyleProp; - disabledItemContainerStyle: StyleProp; - disabledItemLabelStyle: StyleProp; - containerStyle: StyleProp; - labelStyle: StyleProp; - categorySelectable: boolean; - onPress: (value: T) => void; setPosition: (value: T, y: number) => void; theme: ThemeNameType; THEME: ThemeType; + TickIconComponent: () => JSX.Element; + value: T; } export interface ActivityIndicatorComponentPropsInterface { - size: number; color: string; + size: number; } export interface ListEmptyComponentPropsInterface { - listMessageContainer: StyleProp; - listMessageTextStyle: StyleProp; ActivityIndicatorComponent: ( props: ActivityIndicatorComponentPropsInterface, ) => JSX.Element; + listMessageContainer: StyleProp; + listMessageTextStyle: StyleProp; loading: boolean; message: string; } export type DropDownDirectionType = 'DEFAULT' | 'TOP' | 'BOTTOM' | 'AUTO'; - export type ThemeNameType = 'DEFAULT' | 'LIGHT' | 'DARK'; - export type ThemeType = object; - interface DropDownPickerBaseProps { - items: ItemType[]; + export interface DropDownPickerBaseProps { + items: Array>; + setItems?: Dispatch>>>; open: boolean; - placeholder?: string; - closeAfterSelecting?: boolean; - labelProps?: TextProps; - disabled?: boolean; - disabledStyle?: StyleProp; - placeholderStyle?: StyleProp; - containerStyle?: StyleProp; - style?: StyleProp; - textStyle?: StyleProp; - labelStyle?: StyleProp; + setOpen: Dispatch>; + activityIndicatorColor?: string; + ActivityIndicatorComponent?: ( + props: ActivityIndicatorComponentPropsInterface, + ) => JSX.Element; + activityIndicatorSize?: number; + addCustomItem?: boolean; + ArrowDownIconComponent?: (props: { + style: StyleProp; + }) => JSX.Element; + arrowIconContainerStyle?: StyleProp; arrowIconStyle?: StyleProp; - tickIconStyle?: StyleProp; - closeIconStyle?: StyleProp; + ArrowUpIconComponent?: (props: { + style: StyleProp; + }) => JSX.Element; + autoScroll?: boolean; + badgeColors?: Array | string; + badgeDotColors?: Array | string; + badgeDotStyle?: StyleProp; + badgeProps?: TouchableOpacityProps; + badgeSeparatorStyle?: StyleProp; badgeStyle?: StyleProp; badgeTextStyle?: StyleProp; - badgeDotStyle?: StyleProp; - iconContainerStyle?: StyleProp; - searchContainerStyle?: StyleProp; - searchTextInputStyle?: StyleProp; - searchPlaceholderTextColor?: string; - searchWithRegionalAccents?: boolean; - dropDownContainerStyle?: StyleProp; - modalContentContainerStyle?: StyleProp; - modalAnimationType?: 'none' | 'slide' | 'fade'; - arrowIconContainerStyle?: StyleProp; + bottomOffset?: number; + categorySelectable?: boolean; + closeAfterSelecting?: boolean; + CloseIconComponent?: (props: { + style: StyleProp; + }) => JSX.Element; closeIconContainerStyle?: StyleProp; - tickIconContainerStyle?: StyleProp; - listItemContainerStyle?: StyleProp; - listItemLabelStyle?: StyleProp; - listChildContainerStyle?: StyleProp; - listChildLabelStyle?: StyleProp; - listParentContainerStyle?: StyleProp; - listParentLabelStyle?: StyleProp; - selectedItemContainerStyle?: StyleProp; - selectedItemLabelStyle?: StyleProp; - disabledItemContainerStyle?: StyleProp; - disabledItemLabelStyle?: StyleProp; + closeIconStyle?: StyleProp; + closeOnBackPressed?: boolean; + containerProps?: ViewProps; + containerStyle?: StyleProp; customItemContainerStyle?: StyleProp; customItemLabelStyle?: StyleProp; - listMessageContainerStyle?: StyleProp; - listMessageTextStyle?: StyleProp; - itemSeparatorStyle?: StyleProp; - badgeSeparatorStyle?: StyleProp; - listMode?: ListModeType; - categorySelectable?: boolean; - searchable?: boolean; - searchPlaceholder?: string; - schema?: Partial; - language?: LanguageType; - translation?: Partial; - multipleText?: string; - mode?: ModeType; + disableBorderRadius?: boolean; + disabledItemContainerStyle?: StyleProp; + disabledItemLabelStyle?: StyleProp; + disabled?: boolean; + disabledStyle?: StyleProp; + disableLocalSearch?: boolean; + dropDownContainerStyle?: StyleProp; + dropDownDirection?: DropDownDirectionType; + extendableBadgeContainer?: boolean; + flatListProps?: Partial>>; + hideSelectedItemIcon?: boolean; + iconContainerStyle?: StyleProp; itemKey?: string; - maxHeight?: number; - renderBadgeItem?: (props: RenderBadgeItemPropsInterface) => JSX.Element; - renderListItem?: (props: RenderListItemPropsInterface) => JSX.Element; + itemProps?: TouchableOpacityProps; itemSeparator?: boolean; - bottomOffset?: number; - badgeColors?: string[] | string; - badgeDotColors?: string[] | string; - showArrowIcon?: boolean; - showBadgeDot?: boolean; - showTickIcon?: boolean; - stickyHeader?: boolean; - autoScroll?: boolean; - ArrowUpIconComponent?: (props: { - style: StyleProp; - }) => JSX.Element; - ArrowDownIconComponent?: (props: { - style: StyleProp; - }) => JSX.Element; - TickIconComponent?: (props: { style: StyleProp }) => JSX.Element; - CloseIconComponent?: (props: { - style: StyleProp; - }) => JSX.Element; + itemSeparatorStyle?: StyleProp; + labelProps?: TextProps; + labelStyle?: StyleProp; + language?: LanguageType; + listChildContainerStyle?: StyleProp; + listChildLabelStyle?: StyleProp; ListEmptyComponent?: ( props: ListEmptyComponentPropsInterface, ) => JSX.Element; - ActivityIndicatorComponent?: ( - props: ActivityIndicatorComponentPropsInterface, - ) => JSX.Element; - activityIndicatorSize?: number; - activityIndicatorColor?: string; - props?: TouchableOpacityProps; - itemProps?: TouchableOpacityProps; - badgeProps?: TouchableOpacityProps; + listItemContainerStyle?: StyleProp; + listItemLabelStyle?: StyleProp; + listMessageContainerStyle?: StyleProp; + listMessageTextStyle?: StyleProp; + listMode?: ListModeType; + listParentContainerStyle?: StyleProp; + listParentLabelStyle?: StyleProp; + loading?: boolean; + maxHeight?: number; + max?: number; + min?: number; + modalAnimationType?: 'none' | 'slide' | 'fade'; + modalContentContainerStyle?: StyleProp; modalProps?: ModalProps; - flatListProps?: Partial>>; - scrollViewProps?: ScrollViewProps; - searchTextInputProps?: TextInputProps; modalTitle?: string; modalTitleStyle?: StyleProp; - loading?: boolean; - min?: number; - max?: number; - addCustomItem?: boolean; - setOpen: Dispatch>; - setItems?: Dispatch>; - disableBorderRadius?: boolean; - containerProps?: ViewProps; - onLayout?: (e: LayoutChangeEvent) => void; - onPress?: (open: boolean) => void; - onOpen?: () => void; - onClose?: () => void; + mode?: ModeType; + multipleText?: string; onChangeSearchText?: (text: string) => void; + onClose?: () => void; onDirectionChanged?: (direction: DropDownDirectionType) => void; - zIndex?: number; - zIndexInverse?: number; - disableLocalSearch?: boolean; - dropDownDirection?: DropDownDirectionType; - theme?: ThemeNameType; + onLayout?: (e: LayoutChangeEvent) => void; + onOpen?: () => void; + onPress?: (open: boolean) => void; + placeholder?: string; + placeholderStyle?: StyleProp; + props?: TouchableOpacityProps; + renderBadgeItem?: (props: RenderBadgeItemPropsInterface) => JSX.Element; + renderListItem?: (props: RenderListItemPropsInterface) => JSX.Element; rtl?: boolean; + schema?: Partial; + scrollViewProps?: ScrollViewProps; + searchable?: boolean; + searchContainerStyle?: StyleProp; + searchPlaceholder?: string; + searchPlaceholderTextColor?: string; + searchTextInputProps?: TextInputProps; + searchTextInputStyle?: StyleProp; + searchWithRegionalAccents?: boolean; + selectedItemContainerStyle?: StyleProp; + selectedItemLabelStyle?: StyleProp; + showArrowIcon?: boolean; + showBadgeDot?: boolean; + showTickIcon?: boolean; + stickyHeader?: boolean; + style?: StyleProp; testID?: string; - closeOnBackPressed?: boolean; - hideSelectedItemIcon?: boolean; - extendableBadgeContainer?: boolean; + textStyle?: StyleProp; + theme?: ThemeNameType; + TickIconComponent?: (props: { style: StyleProp }) => JSX.Element; + tickIconContainerStyle?: StyleProp; + tickIconStyle?: StyleProp; + translation?: Partial; + zIndexInverse?: number; + zIndex?: number; } - interface DropDownPickerSingleProps { + interface DropDownPickerSingleProps { multiple?: false; onChangeValue?: (value: T | null) => void; onSelectItem?: (item: ItemType) => void; - setValue: Dispatch>; + setValue: Dispatch>; value: T | null; } - interface DropDownPickerMultipleProps { + interface DropDownPickerMultipleProps { multiple: true; - onChangeValue?: (value: T[] | null) => void; - onSelectItem?: (items: ItemType[]) => void; - setValue: Dispatch>; - value: T[] | null; + onChangeValue?: (value: Array | null) => void; + onSelectItem?: (items: Array>) => void; + setValue: Dispatch | null>>; + value: Array | null; } interface DropDownPickerInterface { - MODE: ModeInterface; - LIST_MODE: ListModeInterface; - DROPDOWN_DIRECTION: DropDownDirectionType; - SCHEMA: SchemaInterface; - LANGUAGE: LanguageType; - THEMES: ThemeNameType; - setMode: (mode: string) => void; - setListMode: (mode: string) => void; - setDropDownDirection: (direction: DropDownDirectionType) => void; - setTheme: (name: string) => void; addTheme: (name: string, theme: ThemeNameType) => void; - setLanguage: (language: string) => void; addTranslation: ( language: string, translation: TranslationInterface, ) => void; + DROPDOWN_DIRECTION: DropDownDirectionType; + LANGUAGE: LanguageType; + LIST_MODE: ListModeInterface; + MODE: ModeInterface; modifyTranslation: ( language: string, translation: TranslationInterface, ) => void; + SCHEMA: SchemaInterface; + setDropDownDirection: (direction: DropDownDirectionType) => void; + setLanguage: (language: string) => void; + setListMode: (mode: string) => void; + setMode: (mode: string) => void; + setTheme: (name: string) => void; + THEMES: ThemeNameType; } - export type DropDownPickerProps = ( + export type DropDownPickerProps = ( | DropDownPickerSingleProps | DropDownPickerMultipleProps ) & @@ -323,7 +320,7 @@ declare module 'react-native-dropdown-picker' { const DropDownPicker: (( props: PropsWithoutRef>, - ) => React.ReactElement) & + ) => ReactElement) & DropDownPickerInterface; export default DropDownPicker; diff --git a/package-lock.json b/package-lock.json index 72b7439..5bd147a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,22 +14,22 @@ "devDependencies": { "@types/react": "^18.2.21", "@types/react-native": "^0.72.2", - "@typescript-eslint/eslint-plugin": "^6.4.1", - "@typescript-eslint/parser": "^6.4.1", - "eslint": "^8.47.0", + "@typescript-eslint/eslint-plugin": "^6.5.0", + "@typescript-eslint/parser": "^6.5.0", + "eslint": "^8.48.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb-typescript": "^17.1.0", "eslint-config-prettier": "^9.0.0", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-import": "^2.28.1", - "eslint-plugin-jsdoc": "^46.5.0", + "eslint-plugin-jsdoc": "^46.5.1", "eslint-plugin-json": "^3.1.0", "eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-react": "7.33.2", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-native": "^4.0.0", - "prettier": "3.0.2", - "typescript": "^5.1.6" + "prettier": "3.0.3", + "typescript": "^5.2.2" }, "peerDependencies": { "react": "^18.2.0", @@ -2321,9 +2321,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.47.0.tgz", - "integrity": "sha512-P6omY1zv5MItm93kLM8s2vr1HICJH8v0dvddDhysbIuZ+vcjOHg5Zbkf1mTkcmi2JA9oBG2anOkRnW8WJTS8Og==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.48.0.tgz", + "integrity": "sha512-ZSjtmelB7IJfWD2Fvb7+Z+ChTIKWq6kjda95fLcQKNS5aheVHn4IkfgRQE3sIIzTcSLwLcLZUD9UBt+V7+h+Pw==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3016,9 +3016,9 @@ "dev": true }, "node_modules/@types/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-cJRQXpObxfNKkFAZbJl2yjWtJCqELQIdShsogr1d2MilP8dKD9TE/nEKHkJgUNHdGKCQaf9HbIynuV2csLGVLg==", "dev": true }, "node_modules/@types/stack-utils": { @@ -3043,16 +3043,16 @@ "peer": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.4.1.tgz", - "integrity": "sha512-3F5PtBzUW0dYlq77Lcqo13fv+58KDwUib3BddilE8ajPJT+faGgxmI9Sw+I8ZS22BYwoir9ZhNXcLi+S+I2bkw==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.5.0.tgz", + "integrity": "sha512-2pktILyjvMaScU6iK3925uvGU87E+N9rh372uGZgiMYwafaw9SXq86U04XPq3UH6tzRvNgBsub6x2DacHc33lw==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.4.1", - "@typescript-eslint/type-utils": "6.4.1", - "@typescript-eslint/utils": "6.4.1", - "@typescript-eslint/visitor-keys": "6.4.1", + "@typescript-eslint/scope-manager": "6.5.0", + "@typescript-eslint/type-utils": "6.5.0", + "@typescript-eslint/utils": "6.5.0", + "@typescript-eslint/visitor-keys": "6.5.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -3078,15 +3078,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.4.1.tgz", - "integrity": "sha512-610G6KHymg9V7EqOaNBMtD1GgpAmGROsmfHJPXNLCU9bfIuLrkdOygltK784F6Crboyd5tBFayPB7Sf0McrQwg==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.5.0.tgz", + "integrity": "sha512-LMAVtR5GN8nY0G0BadkG0XIe4AcNMeyEy3DyhKGAh9k4pLSMBO7rF29JvDBpZGCmp5Pgz5RLHP6eCpSYZJQDuQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.4.1", - "@typescript-eslint/types": "6.4.1", - "@typescript-eslint/typescript-estree": "6.4.1", - "@typescript-eslint/visitor-keys": "6.4.1", + "@typescript-eslint/scope-manager": "6.5.0", + "@typescript-eslint/types": "6.5.0", + "@typescript-eslint/typescript-estree": "6.5.0", + "@typescript-eslint/visitor-keys": "6.5.0", "debug": "^4.3.4" }, "engines": { @@ -3106,13 +3106,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.4.1.tgz", - "integrity": "sha512-p/OavqOQfm4/Hdrr7kvacOSFjwQ2rrDVJRPxt/o0TOWdFnjJptnjnZ+sYDR7fi4OimvIuKp+2LCkc+rt9fIW+A==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.5.0.tgz", + "integrity": "sha512-A8hZ7OlxURricpycp5kdPTH3XnjG85UpJS6Fn4VzeoH4T388gQJ/PGP4ole5NfKt4WDVhmLaQ/dBLNDC4Xl/Kw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.4.1", - "@typescript-eslint/visitor-keys": "6.4.1" + "@typescript-eslint/types": "6.5.0", + "@typescript-eslint/visitor-keys": "6.5.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -3123,13 +3123,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.4.1.tgz", - "integrity": "sha512-7ON8M8NXh73SGZ5XvIqWHjgX2f+vvaOarNliGhjrJnv1vdjG0LVIz+ToYfPirOoBi56jxAKLfsLm40+RvxVVXA==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.5.0.tgz", + "integrity": "sha512-f7OcZOkRivtujIBQ4yrJNIuwyCQO1OjocVqntl9dgSIZAdKqicj3xFDqDOzHDlGCZX990LqhLQXWRnQvsapq8A==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.4.1", - "@typescript-eslint/utils": "6.4.1", + "@typescript-eslint/typescript-estree": "6.5.0", + "@typescript-eslint/utils": "6.5.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -3150,9 +3150,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.4.1.tgz", - "integrity": "sha512-zAAopbNuYu++ijY1GV2ylCsQsi3B8QvfPHVqhGdDcbx/NK5lkqMnCGU53amAjccSpk+LfeONxwzUhDzArSfZJg==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.5.0.tgz", + "integrity": "sha512-eqLLOEF5/lU8jW3Bw+8auf4lZSbbljHR2saKnYqON12G/WsJrGeeDHWuQePoEf9ro22+JkbPfWQwKEC5WwLQ3w==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -3163,13 +3163,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.4.1.tgz", - "integrity": "sha512-xF6Y7SatVE/OyV93h1xGgfOkHr2iXuo8ip0gbfzaKeGGuKiAnzS+HtVhSPx8Www243bwlW8IF7X0/B62SzFftg==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.5.0.tgz", + "integrity": "sha512-q0rGwSe9e5Kk/XzliB9h2LBc9tmXX25G0833r7kffbl5437FPWb2tbpIV9wAATebC/018pGa9fwPDuvGN+LxWQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.4.1", - "@typescript-eslint/visitor-keys": "6.4.1", + "@typescript-eslint/types": "6.5.0", + "@typescript-eslint/visitor-keys": "6.5.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -3190,17 +3190,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.4.1.tgz", - "integrity": "sha512-F/6r2RieNeorU0zhqZNv89s9bDZSovv3bZQpUNOmmQK1L80/cV4KEu95YUJWi75u5PhboFoKUJBnZ4FQcoqhDw==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.5.0.tgz", + "integrity": "sha512-9nqtjkNykFzeVtt9Pj6lyR9WEdd8npPhhIPM992FWVkZuS6tmxHfGVnlUcjpUP2hv8r4w35nT33mlxd+Be1ACQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.4.1", - "@typescript-eslint/types": "6.4.1", - "@typescript-eslint/typescript-estree": "6.4.1", + "@typescript-eslint/scope-manager": "6.5.0", + "@typescript-eslint/types": "6.5.0", + "@typescript-eslint/typescript-estree": "6.5.0", "semver": "^7.5.4" }, "engines": { @@ -3215,12 +3215,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.4.1.tgz", - "integrity": "sha512-y/TyRJsbZPkJIZQXrHfdnxVnxyKegnpEvnRGNam7s3TRR2ykGefEWOhaef00/UUN3IZxizS7BTO3svd3lCOJRQ==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.5.0.tgz", + "integrity": "sha512-yCB/2wkbv3hPsh02ZS8dFQnij9VVQXJMN/gbQsaaY+zxALkZnxa/wagvLEFsAWMPv7d7lxQmNsIzGU1w/T/WyA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.4.1", + "@typescript-eslint/types": "6.5.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -4647,15 +4647,15 @@ } }, "node_modules/eslint": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.47.0.tgz", - "integrity": "sha512-spUQWrdPt+pRVP1TTJLmfRNJJHHZryFmptzcafwSvHsceV81djHOdnEeDmkdotZyLNjDhrOasNK8nikkoG1O8Q==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.48.0.tgz", + "integrity": "sha512-sb6DLeIuRXxeM1YljSe1KEx9/YYeZFQWcV8Rq9HfigmdDEugjLEVEa1ozDjL6YDjBpQHPJxJzze+alxi4T3OLg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "^8.47.0", + "@eslint/js": "8.48.0", "@humanwhocodes/config-array": "^0.11.10", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -4912,9 +4912,9 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "46.5.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.5.0.tgz", - "integrity": "sha512-aulXdA4I1dyWpzyS1Nh/GNoS6PavzeucxEapnMR4JUERowWvaEk2Y4A5irpHAcdXtBBHLVe8WIhdXNjoAlGQgA==", + "version": "46.5.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.5.1.tgz", + "integrity": "sha512-CPbvKprmEuJYoxMj5g8gXfPqUGgcqMM6jpH06Kp4pn5Uy5MrPkFKzoD7UFp2E4RBzfXbJz1+TeuEivwFVMkXBg==", "dev": true, "dependencies": { "@es-joy/jsdoccomment": "~0.40.1", @@ -8376,9 +8376,9 @@ } }, "node_modules/prettier": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.2.tgz", - "integrity": "sha512-o2YR9qtniXvwEZlOKbveKfDQVyqxbEIWn48Z8m3ZJjBjcCmUy3xZGIv+7AkaeuaTr6yPXJjwv07ZWlsWbEy1rQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", + "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -9814,9 +9814,9 @@ } }, "node_modules/typescript": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", - "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "dev": true, "bin": { "tsc": "bin/tsc", diff --git a/package.json b/package.json index b27ef25..28210c2 100644 --- a/package.json +++ b/package.json @@ -49,22 +49,22 @@ "devDependencies": { "@types/react": "^18.2.21", "@types/react-native": "^0.72.2", - "@typescript-eslint/eslint-plugin": "^6.4.1", - "@typescript-eslint/parser": "^6.4.1", - "eslint": "^8.47.0", + "@typescript-eslint/eslint-plugin": "^6.5.0", + "@typescript-eslint/parser": "^6.5.0", + "eslint": "^8.48.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb-typescript": "^17.1.0", "eslint-config-prettier": "^9.0.0", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-import": "^2.28.1", - "eslint-plugin-jsdoc": "^46.5.0", + "eslint-plugin-jsdoc": "^46.5.1", "eslint-plugin-json": "^3.1.0", "eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-react": "7.33.2", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-native": "^4.0.0", - "prettier": "3.0.2", - "typescript": "^5.1.6" + "prettier": "3.0.3", + "typescript": "^5.2.2" }, "peerDependencies": { "react": "^18.2.0", diff --git a/src/components/Picker.js b/src/components/Picker.js index 000fb33..8549329 100644 --- a/src/components/Picker.js +++ b/src/components/Picker.js @@ -1,5 +1,6 @@ import React, { Fragment, + JSX, memo, useCallback, useEffect, @@ -11,6 +12,7 @@ import React, { import { ActivityIndicator, BackHandler, + Dimensions, Image, Modal, Platform, @@ -45,121 +47,144 @@ import ListEmpty from './ListEmpty'; import RenderBadgeItem from './RenderBadgeItem'; import RenderListItem from './RenderListItem'; +const { height: WINDOW_HEIGHT } = Dimensions.get('window'); + +/** + * Find whether two value arrays (value prop and memoryRef.current.value) have the same values. + * === will just check if arrays are the same reference, not if their values are the same. + * @param valueOne first array + * @param valueTwo second array + * @returns boolean representing whether the two values are the same. + */ +function areValueArraysEqual(valueOne, valueTwo) { + if (Array.isArray(valueOne) !== Array.isArray(valueTwo)) return false; + + if (!Array.isArray(valueOne)) return valueOne === valueTwo; + + if (valueOne.length !== valueTwo.length) return false; + + for (let i = 0; i < valueOne.length; i += 1) { + if (valueOne[i] !== valueTwo[i]) return false; + } + + return true; +} + function Picker({ - value = null, items = [], + setItems = () => {}, open, - placeholder = null, + setOpen = () => {}, + value = null, + setValue = (callback) => {}, + activityIndicatorColor = Colors.GREY, + ActivityIndicatorComponent = null, + activityIndicatorSize = 30, + addCustomItem = false, + ArrowDownIconComponent = null, + arrowIconContainerStyle = {}, + arrowIconStyle = {}, + ArrowUpIconComponent = null, + autoScroll = false, + badgeColors = BADGE_COLORS, + badgeDotColors = BADGE_DOT_COLORS, + badgeDotStyle = {}, + badgeProps = {}, + badgeSeparatorStyle = {}, + badgeStyle = {}, + badgeTextStyle = {}, + bottomOffset = 0, + categorySelectable = true, closeAfterSelecting = true, - labelProps = {}, + CloseIconComponent = null, + closeIconContainerStyle = {}, + closeIconStyle = {}, + closeOnBackPressed = false, + containerProps = {}, + containerStyle = {}, + customItemContainerStyle = {}, + customItemLabelStyle = {}, + disableBorderRadius = true, disabled = false, + disabledItemContainerStyle = {}, + disabledItemLabelStyle = {}, disabledStyle = {}, - placeholderStyle = {}, - containerStyle = {}, - style = {}, - textStyle = {}, - labelStyle = {}, - arrowIconStyle = {}, - tickIconStyle = {}, - closeIconStyle = {}, + disableLocalSearch = false, + dropDownContainerStyle = {}, + dropDownDirection = DROPDOWN_DIRECTION.DEFAULT, + extendableBadgeContainer = false, + flatListProps = {}, hideSelectedItemIcon = false, - badgeStyle = {}, - badgeTextStyle = {}, - badgeDotStyle = {}, iconContainerStyle = {}, - searchContainerStyle = {}, - searchTextInputStyle = {}, - searchPlaceholderTextColor = Colors.GREY, - dropDownContainerStyle = {}, - modalContentContainerStyle = {}, - modalAnimationType = 'none', - arrowIconContainerStyle = {}, - closeIconContainerStyle = {}, - tickIconContainerStyle = {}, - listItemContainerStyle = {}, - listItemLabelStyle = {}, + itemKey = null, + itemLabelProps = {}, + itemProps = {}, + itemSeparator = false, + itemSeparatorStyle = {}, + labelProps = {}, + labelStyle = {}, + language = LANGUAGE.DEFAULT, listChildContainerStyle = {}, listChildLabelStyle = {}, - listParentContainerStyle = {}, - listParentLabelStyle = {}, - selectedItemContainerStyle = {}, - selectedItemLabelStyle = {}, - disabledItemContainerStyle = {}, - disabledItemLabelStyle = {}, - customItemContainerStyle = {}, - customItemLabelStyle = {}, + ListEmptyComponent = null, + listItemContainerStyle = {}, + listItemLabelStyle = {}, listMessageContainerStyle = {}, listMessageTextStyle = {}, - itemSeparatorStyle = {}, - badgeSeparatorStyle = {}, - modalTitleStyle = {}, listMode = LIST_MODE.DEFAULT, - categorySelectable = true, - searchable = false, - searchWithRegionalAccents = false, - searchPlaceholder = null, + listParentContainerStyle = {}, + listParentLabelStyle = {}, + loading = false, + max = null, + maxHeight = 200, + min = null, + modalAnimationType = 'none', + modalContentContainerStyle = {}, + modalProps = {}, modalTitle, - schema = {}, - language = LANGUAGE.DEFAULT, - translation = {}, + modalTitleStyle = {}, + mode = MODE.DEFAULT, multiple = false, multipleText = null, - mode = MODE.DEFAULT, - itemKey = null, - maxHeight = 200, + onChangeSearchText = (text) => {}, + onChangeValue = (value) => {}, + onClose = () => {}, + onDirectionChanged = (direction) => {}, + onLayout = (e) => {}, + onOpen = () => {}, + onPress = (open) => {}, + onSelectItem = (item) => {}, + placeholder = null, + placeholderStyle = {}, + props = {}, renderBadgeItem = null, renderListItem = null, - itemSeparator = false, - bottomOffset = 0, - badgeColors = BADGE_COLORS, - badgeDotColors = BADGE_DOT_COLORS, + rtl = false, + schema = {}, + scrollViewProps = {}, + searchable = false, + searchContainerStyle = {}, + searchPlaceholder = null, + searchPlaceholderTextColor = Colors.GREY, + searchTextInputProps = {}, + searchTextInputStyle = {}, + searchWithRegionalAccents = false, + selectedItemContainerStyle = {}, + selectedItemLabelStyle = {}, showArrowIcon = true, showBadgeDot = true, showTickIcon = true, stickyHeader = false, - autoScroll = false, - ArrowUpIconComponent = null, - ArrowDownIconComponent = null, + style = {}, + testID, + textStyle = {}, + theme = THEMES.DEFAULT, TickIconComponent = null, - CloseIconComponent = null, - ListEmptyComponent = null, - ActivityIndicatorComponent = null, - activityIndicatorSize = 30, - activityIndicatorColor = Colors.GREY, - props = {}, - itemProps = {}, - itemLabelProps = {}, - badgeProps = {}, - modalProps = {}, - flatListProps = {}, - scrollViewProps = {}, - searchTextInputProps = {}, - loading = false, - min = null, - max = null, - addCustomItem = false, - setOpen = () => {}, - setItems = () => {}, - disableBorderRadius = true, - containerProps = {}, - onLayout = (e) => {}, - onPress = (open) => {}, - onOpen = () => {}, - onClose = () => {}, - setValue = (callback) => {}, - onChangeValue = (value) => {}, - onChangeSearchText = (text) => {}, - onDirectionChanged = (direction) => {}, + tickIconContainerStyle = {}, + tickIconStyle = {}, + translation = {}, zIndex = 5000, zIndexInverse = 6000, - rtl = false, - dropDownDirection = DROPDOWN_DIRECTION.DEFAULT, - disableLocalSearch = false, - theme = THEMES.DEFAULT, - testID, - closeOnBackPressed = false, - extendableBadgeContainer = false, - onSelectItem = (item) => {}, }) { const [necessaryItems, setNecessaryItems] = useState([]); const [searchText, setSearchText] = useState(''); @@ -175,8 +200,8 @@ function Picker({ const flatListRef = useRef(); const scrollViewRef = useRef(); const memoryRef = useRef({ - value: null, items: [], + value: null, }); const THEME = useMemo(() => THEMES[theme].default, [theme]); @@ -186,19 +211,17 @@ function Picker({ * The item schema. * @returns {object} */ - const _schema = useMemo(() => ({ ...SCHEMA, ...schema }), [schema]); + const ITEM_SCHEMA = useMemo(() => ({ ...SCHEMA, ...schema }), [schema]); /** * componentDidMount. */ useEffect(() => { - memoryRef.current.value = multiple - ? Array.isArray(value) - ? value - : [] - : value; + if (multiple) { + memoryRef.current.value = Array.isArray(value) ? value : []; + } else memoryRef.current.value = value; - // Get initial seleted items + // Get initial selected items let initialSelectedItems = []; const valueNotNull = value !== null && Array.isArray(value) && value.length !== 0; @@ -206,11 +229,11 @@ function Picker({ if (valueNotNull) { if (multiple) { initialSelectedItems = items.filter((item) => - value.includes(item[_schema.value]), + value.includes(item[ITEM_SCHEMA.value]), ); } else { initialSelectedItems = items.find( - (item) => item[_schema.value] === value, + (item) => item[ITEM_SCHEMA.value] === value, ); } } @@ -241,7 +264,7 @@ function Picker({ setNecessaryItems((state) => [...state].map((item) => { const _item = items.find( - (x) => x[_schema.value] === item[_schema.value], + (x) => x[ITEM_SCHEMA.value] === item[ITEM_SCHEMA.value], ); if (_item) { @@ -262,18 +285,18 @@ function Picker({ if (value === null || (Array.isArray(value) && value.length === 0)) return []; - const _state = [...state].filter((item) => - value.includes(item[_schema.value]), + const newState = [...state].filter((item) => + value.includes(item[ITEM_SCHEMA.value]), ); const newItems = value.reduce((accumulator, currentValue) => { - const index = _state.findIndex( - (item) => item[_schema.value] === currentValue, + const itemIndex = newState.findIndex( + (item) => item[ITEM_SCHEMA.value] === currentValue, ); - if (index === -1) { + if (itemIndex === -1) { const item = items.find( - (item) => item[_schema.value] === currentValue, + (item) => item[ITEM_SCHEMA.value] === currentValue, ); if (item) { @@ -286,13 +309,13 @@ function Picker({ return accumulator; }, []); - return [..._state, ...newItems]; + return [...newState, ...newItems]; }); } else { const state = []; if (value !== null) { - const item = items.find((item) => item[_schema.value] === value); + const item = items.find((item) => item[ITEM_SCHEMA.value] === value); if (item) { state.push(item); @@ -385,18 +408,20 @@ function Picker({ const sortedItems = useMemo(() => { const sortedItems = items.filter( (item) => - item[_schema.parent] === undefined || item[_schema.parent] === null, + item[ITEM_SCHEMA.parent] === undefined || + item[ITEM_SCHEMA.parent] === null, ); const children = items.filter( (item) => - item[_schema.parent] !== undefined && item[_schema.parent] !== null, + item[ITEM_SCHEMA.parent] !== undefined && + item[ITEM_SCHEMA.parent] !== null, ); children.forEach((child) => { const index = sortedItems.findIndex( (item) => - item[_schema.parent] === child[_schema.parent] || - item[_schema.value] === child[_schema.parent], + item[ITEM_SCHEMA.parent] === child[ITEM_SCHEMA.parent] || + item[ITEM_SCHEMA.value] === child[ITEM_SCHEMA.parent], ); if (index > -1) { @@ -405,10 +430,10 @@ function Picker({ }); return sortedItems; - }, [items, _schema]); + }, [items, ITEM_SCHEMA]); /** - * Scroll the the first selected item. + * Scroll to the first selected item. */ const scroll = useCallback(() => { setTimeout(() => { @@ -436,7 +461,7 @@ function Picker({ }); } else { const index = sortedItems.findIndex( - (item) => item[_schema.value] === value, + (item) => item[ITEM_SCHEMA.value] === value, ); if (index > -1) @@ -468,11 +493,12 @@ function Picker({ if (stickyHeader) { const parents = sortedItems.filter( (item) => - item[_schema.parent] === undefined || item[_schema.parent] === null, + item[ITEM_SCHEMA.parent] === undefined || + item[ITEM_SCHEMA.parent] === null, ); parents.forEach((parent) => { const index = sortedItems.findIndex( - (item) => item[_schema.value] === parent[_schema.value], + (item) => item[ITEM_SCHEMA.value] === parent[ITEM_SCHEMA.value], ); if (index > -1) stickyHeaderIndices.push(index); }); @@ -495,14 +521,14 @@ function Picker({ text.normalize('NFD').replace(/[\u0300-\u036f]/g, ''); const results = sortedItems.filter((item) => { - const label = String(item[_schema.label]).toLowerCase(); + const label = String(item[ITEM_SCHEMA.label]).toLowerCase(); if ( label.includes(searchText.toLowerCase()) || (searchWithRegionalAccents && normalizeText(label).includes(searchText.toLowerCase())) ) { - values.push(item[_schema.value]); + values.push(item[ITEM_SCHEMA.value]); return true; } @@ -511,16 +537,16 @@ function Picker({ results.forEach((item, index) => { if ( - item[_schema.parent] === undefined || - item[_schema.parent] === null || - values.includes(item[_schema.parent]) + item[ITEM_SCHEMA.parent] === undefined || + item[ITEM_SCHEMA.parent] === null || + values.includes(item[ITEM_SCHEMA.parent]) ) return; const parent = sortedItems.find( - (x) => x[_schema.value] === item[_schema.parent], + (x) => x[ITEM_SCHEMA.value] === item[ITEM_SCHEMA.parent], ); - values.push(item[_schema.parent]); + values.push(item[ITEM_SCHEMA.parent]); results.splice(index, 0, parent); }); @@ -529,20 +555,20 @@ function Picker({ (results.length === 0 || results.findIndex( (item) => - String(item[_schema.label]).toLowerCase() === + String(item[ITEM_SCHEMA.label]).toLowerCase() === searchText.toLowerCase(), ) === -1) && addCustomItem ) { results.push({ - [_schema.label]: searchText, - [_schema.value]: searchText.replace(' ', '-'), + [ITEM_SCHEMA.label]: searchText, + [ITEM_SCHEMA.value]: searchText.replace(' ', '-'), custom: true, }); } return results; - }, [sortedItems, _schema, searchText, addCustomItem]); + }, [sortedItems, ITEM_SCHEMA, searchText, addCustomItem]); /** * The value. @@ -550,9 +576,7 @@ function Picker({ */ const _value = useMemo(() => { if (multiple) { - if (value === null) return []; - - return [...new Set(value)]; + return value === null ? [] : [...new Set(value)]; } return value; @@ -566,9 +590,9 @@ function Picker({ if (!multiple) return []; return necessaryItems.filter((item) => - _value.includes(item[_schema.value]), + _value.includes(item[ITEM_SCHEMA.value]), ); - }, [necessaryItems, _value, _schema, multiple]); + }, [necessaryItems, _value, ITEM_SCHEMA, multiple]); /** * The language. @@ -639,7 +663,7 @@ function Picker({ if (isNull) return null; try { - return necessaryItems.find((item) => item[_schema.value] === _value); + return necessaryItems.find((item) => item[ITEM_SCHEMA.value] === _value); } catch (e) { return null; } @@ -666,12 +690,12 @@ function Picker({ } else return fallback; try { - return item[_schema.label]; + return item[ITEM_SCHEMA.label]; } catch (e) { return fallback; } }, - [getSelectedItem, multiple, _multipleText, _schema], + [getSelectedItem, multiple, _multipleText, ITEM_SCHEMA], ); /** @@ -691,11 +715,11 @@ function Picker({ const item = getSelectedItem(); try { - return item[_schema.icon] ?? null; + return item[ITEM_SCHEMA.icon] ?? null; } catch (e) { return null; } - }, [getSelectedItem, multiple, _schema]); + }, [getSelectedItem, multiple, ITEM_SCHEMA]); /** * onPress. @@ -871,13 +895,13 @@ function Picker({ dropDownNoBorderRadius, ], [ + direction, dropDownContainerStyle, - pickerHeight, - maxHeight, dropDownNoBorderRadius, - direction, - _zIndex, + maxHeight, + pickerHeight, THEME, + _zIndex, ], ); @@ -1001,13 +1025,11 @@ function Picker({ * onPress badge. */ const onPressBadge = useCallback( - (value) => { + (badgeValue) => { setValue((state) => { - const _state = [...state]; - const index = _state.findIndex((item) => item === value); - _state.splice(index, 1); - - return _state; + const newState = [...state]; + newState.filter((nsItem) => nsItem !== badgeValue); + return newState; }); }, [setValue], @@ -1081,9 +1103,9 @@ function Picker({ ), [ - rtl, - _schema, - textStyle, + badgeDotStyle, badgeStyle, badgeTextStyle, - badgeDotStyle, getBadgeColor, getBadgeDotColor, - showBadgeDot, + ITEM_SCHEMA, onPressBadge, - theme, + rtl, + showBadgeDot, + textStyle, THEME, + theme, ], ); @@ -1117,10 +1139,10 @@ function Picker({ * @returns {string} */ const _itemKey = useMemo(() => { - if (itemKey === null) return _schema.value; + if (itemKey === null) return ITEM_SCHEMA.value; return itemKey; - }, [itemKey, _schema]); + }, [itemKey, ITEM_SCHEMA]); /** * The key extractor. @@ -1403,16 +1425,28 @@ function Picker({ */ const onPressItem = useCallback( (item, customItem = false) => { + if (!areValueArraysEqual(value, memoryRef.current.value)) { + throw new Error( + `${new Date().toString()} The arrays of the value prop and memoryRef.current.value were not equal in the callback for when items are pressed in the Picker component. value was: ${JSON.stringify( + value, + )} and memoryRef.current.value was: ${JSON.stringify( + memoryRef.current.value, + )}`, + ); + } + + // if pressed item was a custom item by the user, add it to the list of items (?) if (customItem !== false) { item.custom = false; setItems((state) => [...state, item]); } + // call onSelectItem() callback for item/s now selected after item press. // Not a reliable method for external value changes. if (multiple) { - if (memoryRef.current.value?.includes(item[_schema.value])) { + if (memoryRef.current.value?.includes(item[ITEM_SCHEMA.value])) { const index = memoryRef.current.items.findIndex( - (x) => x[_schema.value] === item[_schema.value], + (x) => x[ITEM_SCHEMA.value] === item[ITEM_SCHEMA.value], ); if (index > -1) { @@ -1427,77 +1461,67 @@ function Picker({ } setValue((state) => { + // call setValue() callback to change selected value/s after item press. if (multiple) { - const _state = - state !== null && state !== undefined ? [...state] : []; - - if (_state.includes(item[_schema.value])) { - // Remove the value - if (Number.isInteger(min) && min >= _state.length) { - return state; - } + const newState = + state === null || state === undefined ? [] : [...state]; - const index = _state.findIndex((x) => x === item[_schema.value]); - _state.splice(index, 1); - } else { - // Add the value - if (Number.isInteger(max) && max <= _state.length) { - return state; + if (newState.includes(item[ITEM_SCHEMA.value])) { + // if value already included, remove it if doing so wouldn't go under min number + if (!Number.isInteger(min) || min < newState.length) { + newState.splice(newState.indexOf(item[ITEM_SCHEMA.value]), 1); } - - _state.push(item[_schema.value]); + } else if (!Number.isInteger(max) || max > newState.length) { + // if value not already included, add it if doing so wouldn't go above max number + newState.push(item[ITEM_SCHEMA.value]); } - return _state; + return newState; } - return item[_schema.value]; + + return item[ITEM_SCHEMA.value]; // single-value picker }); + // adjust necessary items after item press. + // if single-item picker, set necessary items with array whose only element is the item pressed. + // if multi-item picker, if item in necessary items remove it or if not then add it, within min/max constraints setNecessaryItems((state) => { if (multiple) { - const _state = [...state]; - - if ( - _state.findIndex((x) => x[_schema.value] === item[_schema.value]) > - -1 - ) { - // Remove the item - if (Number.isInteger(min) && min >= _state.length) { - return state; - } + const newState = [...state]; - const index = _state.findIndex( - (x) => x[_schema.value] === item[_schema.value], - ); - _state.splice(index, 1); + const itemIndex = newState.findIndex( + (x) => x[ITEM_SCHEMA.value] === item[ITEM_SCHEMA.value], + ); - return _state; - } - // Add the item - if (Number.isInteger(max) && max <= _state.length) { - return state; + if (itemIndex > -1) { + // If pressed item already in necessary items, remove it if doing so doesn't go below min number of items + if (!Number.isInteger(min) || min < newState.length) { + newState.splice(itemIndex, 1); + } + } else if (!Number.isInteger(max) || max > newState.length) { + // If pressed item not already in necessary items, add it if doing so doesn't go above max number of items + newState.push(item); } - _state.push(item); - - return _state; + return newState; } + // if a single-item picker, set pressed item as array of only necessary item return [item]; }); + // if picker is a single-item picker and to close after an item gets selected, close it since press selected item. if (closeAfterSelecting && !multiple) onPressClose(); }, [ - setValue, - multiple, - min, + closeAfterSelecting, + ITEM_SCHEMA, max, + min, + multiple, onPressClose, onSelectItem, - closeAfterSelecting, - multiple, setItems, - _schema, + setValue, ], ); @@ -1573,7 +1597,7 @@ function Picker({ */ const __renderListItem = useCallback( ({ item }) => { - let IconComponent = item[_schema.icon] ?? null; + let IconComponent = item[ITEM_SCHEMA.icon] ?? null; if (IconComponent) { IconComponent = ( @@ -1585,20 +1609,20 @@ function Picker({ let isSelected; if (multiple) { - isSelected = _value.includes(item[_schema.value]); + isSelected = _value.includes(item[ITEM_SCHEMA.value]); } else { - isSelected = _value === item[_schema.value]; + isSelected = _value === item[ITEM_SCHEMA.value]; } return ( ), [ - searchable, + isSearchComponentVisible, listMode, modalTitle, - isSearchComponentVisible, - _onChangeSearchText, - _modalTitleStyle, - _searchContainerStyle, - _searchTextInputStyle, - _searchPlaceholder, + searchable, searchPlaceholderTextColor, searchText, searchTextInputProps, + _modalTitleStyle, + _onChangeSearchText, + _searchContainerStyle, + _searchPlaceholder, + _searchTextInputStyle, ], ); @@ -1886,14 +1909,14 @@ function Picker({ /> ), [ + flatListProps, + ItemSeparatorComponent, + keyExtractor, + THEME, _items, + _ListEmptyComponent, _value, __renderListItem, - keyExtractor, - ItemSeparatorComponent, - flatListProps, - _ListEmptyComponent, - THEME, ], );