diff --git a/src/AllCheckerCheckbox.tsx b/src/AllCheckerCheckbox.tsx index 4b594f4..eb07d3f 100644 --- a/src/AllCheckerCheckbox.tsx +++ b/src/AllCheckerCheckbox.tsx @@ -1,83 +1,86 @@ import React, { - ForwardRefExoticComponent, - ReactElement, - useContext, - useEffect, - useState, - forwardRef, - RefAttributes + forwardRef, + ForwardRefExoticComponent, + ReactElement, + RefAttributes, + useContext, + useEffect, + useState, } from 'react'; + import CheckboxGroupContext from './CheckboxGroupContext'; -import uuid from "./uuid"; +import uuid from './uuid'; type AllCheckerCheckboxProps = Omit, HTMLInputElement> & RefAttributes, 'checked'> const AllCheckerCheckbox: ForwardRefExoticComponent = forwardRef((props, ref): ReactElement => { - const { - disabled, - onChange, - } = props; + const { disabled, onChange } = props; - const [id] = useState(uuid()); - const checkboxGroup = useContext(CheckboxGroupContext); + const [id] = useState(uuid()); + const checkboxGroup = useContext(CheckboxGroupContext); - const [initialized, setInitialized] = useState(false); - const [shouldTriggerCheckboxContextChange, setShouldTriggerCheckboxContextChange] = useState(true); - const [isChecked, setIsChecked] = useState(checkboxGroup.defaultChecked); - const [isDisabled, setIsDisabled] = useState(disabled !== undefined ? disabled : checkboxGroup.defaultDisabled); + const [initialized, setInitialized] = useState(false); + const [shouldTriggerCheckboxContextChange, setShouldTriggerCheckboxContextChange] = useState(false); + const [isChecked, setIsChecked] = useState(checkboxGroup.defaultChecked); + const [isDisabled, setIsDisabled] = useState(disabled !== undefined ? disabled : checkboxGroup.defaultDisabled); - useEffect((): () => void => { - checkboxGroup.assertIdDoesNotExist(id); - return (): void => { - checkboxGroup.allCheckerCheckboxes.delete(id); - }; - }, []); + useEffect((): () => void => { + checkboxGroup.assertIdDoesNotExist(id); - useEffect((): void => { - checkboxGroup.allCheckerCheckboxes.set(id, { - isChecked, - isDisabled, - props, - setIsChecked, - setIsDisabled, - }); + return (): void => { + checkboxGroup.allCheckerCheckboxes.delete(id); + }; + }, []); - if (shouldTriggerCheckboxContextChange) { - checkboxGroup.onAllCheckerCheckboxChange(id, initialized); - setShouldTriggerCheckboxContextChange(false); - } + useEffect((): void => { + checkboxGroup.allCheckerCheckboxes.set(id, { + isChecked, + isDisabled, + props, + setIsChecked, + setIsDisabled, + }); - if (!initialized) { - setInitialized(true); - } - }, [ - id, isChecked, isDisabled, setIsChecked, setIsDisabled, initialized, - setShouldTriggerCheckboxContextChange, checkboxGroup, shouldTriggerCheckboxContextChange, - ]); + if (shouldTriggerCheckboxContextChange) { + checkboxGroup.onAllCheckerCheckboxChange(id, initialized); + setShouldTriggerCheckboxContextChange(false); + } - const handleChange = (event: React.ChangeEvent): void => { - event.persist(); + if (!initialized) { + setInitialized(true); + } + }, [ + id, + isChecked, + isDisabled, + initialized, + checkboxGroup, + shouldTriggerCheckboxContextChange, + ]); - if (!isDisabled) { - setShouldTriggerCheckboxContextChange(true); - setIsChecked(event.target.checked); - } + const handleChange = (event: React.ChangeEvent): void => { + event.persist(); - if (onChange !== undefined) { - onChange(event); - } - }; + if (!isDisabled) { + setShouldTriggerCheckboxContextChange(true); + setIsChecked(event.target.checked); + } + + if (onChange !== undefined) { + onChange(event); + } + }; - return ( - - ); + return ( + + ); }); export default AllCheckerCheckbox; diff --git a/src/Checkbox.tsx b/src/Checkbox.tsx index fbfee79..882b1c2 100644 --- a/src/Checkbox.tsx +++ b/src/Checkbox.tsx @@ -1,87 +1,85 @@ import React, { - forwardRef, - ForwardRefExoticComponent, - ReactElement, - RefAttributes, - useContext, - useEffect, - useState, + forwardRef, + ForwardRefExoticComponent, + ReactElement, + RefAttributes, + useContext, + useEffect, + useState, } from 'react'; + import CheckboxGroupContext from './CheckboxGroupContext'; -import uuid from "./uuid"; +import uuid from './uuid'; type CheckboxProps = React.DetailedHTMLProps, HTMLInputElement> & RefAttributes; const Checkbox: ForwardRefExoticComponent = forwardRef((props, ref): ReactElement => { - const { - checked, - disabled, - onChange, - } = props; + const { checked, disabled, onChange } = props; - const [id] = useState(uuid()); - const checkboxGroup = useContext(CheckboxGroupContext); + const [id] = useState(uuid()); + const checkboxGroup = useContext(CheckboxGroupContext); - const [shouldTriggerCheckboxContextChange, setShouldTriggerCheckboxContextChange] = useState(true); - const [isChecked, setIsChecked] = useState(checked !== undefined ? checked : checkboxGroup.defaultChecked); - const [isDisabled, setIsDisabled] = useState(disabled !== undefined ? disabled : checkboxGroup.defaultDisabled); + const [shouldTriggerCheckboxContextChange, setShouldTriggerCheckboxContextChange] = useState(true); + const [isChecked, setIsChecked] = useState(checked !== undefined ? checked : checkboxGroup.defaultChecked); + const [isDisabled, setIsDisabled] = useState(disabled !== undefined ? disabled : checkboxGroup.defaultDisabled); - useEffect((): () => void => { - checkboxGroup.assertIdDoesNotExist(id); - return (): void => { - checkboxGroup.checkboxes.delete(id); - checkboxGroup.onCheckboxChange(); - }; - }, []); + useEffect((): () => void => { + checkboxGroup.assertIdDoesNotExist(id); - useEffect((): void => { - if (checked !== undefined) { - setShouldTriggerCheckboxContextChange(true); - setIsChecked(checked); - } - }, [checked, setIsChecked, setShouldTriggerCheckboxContextChange]); + return (): void => { + checkboxGroup.checkboxes.delete(id); + checkboxGroup.onCheckboxChange(); + }; + }, []); - useEffect((): void => { - checkboxGroup.checkboxes.set(id, { - isChecked, - isDisabled, - props, - setIsChecked, - setIsDisabled, - }); + useEffect(() => { + setIsChecked(checked); + }, [checked]); - if (shouldTriggerCheckboxContextChange) { - checkboxGroup.onCheckboxChange(); - setShouldTriggerCheckboxContextChange(false); - } - }, [ - id, isChecked, isDisabled, setIsChecked, setIsDisabled, - setShouldTriggerCheckboxContextChange, checkboxGroup, shouldTriggerCheckboxContextChange, - ]); + useEffect((): void => { + checkboxGroup.checkboxes.set(id, { + isChecked, + isDisabled, + props, + setIsChecked, + setIsDisabled, + }); - const handleChange = (event: React.ChangeEvent) => { - event.persist(); + if (shouldTriggerCheckboxContextChange) { + checkboxGroup.onCheckboxChange(); + setShouldTriggerCheckboxContextChange(false); + } + }, [ + id, + isChecked, + isDisabled, + checkboxGroup, + shouldTriggerCheckboxContextChange, + ]); - if (!isDisabled) { - setShouldTriggerCheckboxContextChange(true); - setIsChecked(event.target.checked); - } + const handleChange = (event: React.ChangeEvent) => { + event.persist(); - if (onChange !== undefined) { - onChange(event); - } - }; + if (!isDisabled) { + setShouldTriggerCheckboxContextChange(true); + setIsChecked(event.target.checked); + } + + if (onChange !== undefined) { + onChange(event); + } + }; - return ( - - ); + return ( + + ); }); export default Checkbox; diff --git a/src/CheckboxGroup.tsx b/src/CheckboxGroup.tsx index f3d813b..6d0cee2 100644 --- a/src/CheckboxGroup.tsx +++ b/src/CheckboxGroup.tsx @@ -1,151 +1,154 @@ -import debounce from "lodash.debounce"; import React, { - FC, - PropsWithChildren, - ReactElement, - useState, + FC, + PropsWithChildren, + ReactElement, + useMemo, + useState, } from 'react'; -import CheckboxGroupContext, { CheckboxEntry } from "./CheckboxGroupContext"; + +import debounce from 'lodash.debounce'; + +import CheckboxGroupContext, { CheckboxEntry } from './CheckboxGroupContext'; export interface CheckboxChange extends React.DetailedHTMLProps, HTMLInputElement> { - checked: boolean; - disabled: boolean; + checked: boolean; + disabled: boolean; } interface CheckboxGroupProps { - defaultChecked?: boolean; - defaultDisabled?: boolean; - onChange?: (checkboxes: CheckboxChange[]) => void; + defaultChecked?: boolean; + defaultDisabled?: boolean; + onChange?: (checkboxes: CheckboxChange[]) => void; } const ON_CHANGE_DEBOUNCE_TIMEOUT = 100; const CheckboxGroup: FC> = ({ - defaultChecked, - defaultDisabled, - onChange, - children, -}): ReactElement => { - const [checkboxes] = useState(new Map()); - const [allCheckerCheckboxes] = useState(new Map()); - const [noneCheckerCheckboxes] = useState(new Map()); - - const dispatchOnChange = (): void => { - if (onChange === undefined) { - return; - } - - const checkboxChangeArray: CheckboxChange[] = []; - - checkboxes.forEach((checkbox): void => { - checkboxChangeArray.push({ - ...checkbox.props, - checked: checkbox.isChecked || false, - disabled: checkbox.isDisabled || false, - }); - }); - - onChange(checkboxChangeArray); - }; - - const debouncedOnChange = debounce(dispatchOnChange, ON_CHANGE_DEBOUNCE_TIMEOUT); - - const setAllCheckboxesChecked = (state: boolean): void => { - allCheckerCheckboxes.forEach((checkbox): void => checkbox.setIsChecked(state)); - noneCheckerCheckboxes.forEach((checkbox): void => checkbox.setIsChecked(!state)); - checkboxes.forEach((checkbox, key): void => { - const clone = checkbox; - checkbox.setIsChecked(state); - clone.isChecked = state; - checkboxes.set(key, clone); - }); - }; - - const amountChecked = (): number => { - let count = 0; - - checkboxes.forEach((checkbox): void => { - if (checkbox.isChecked === true) { - count += 1; - } - }); - - return count; - }; - - const allCheckboxesAreChecked = (): boolean => { - const checkedCount = amountChecked(); - - return checkedCount > 0 && checkedCount === checkboxes.size; - }; - - const allCheckboxesAreNotChecked = (): boolean => amountChecked() === 0; - - const onCheckboxChange = (): void => { - const allChecked = allCheckboxesAreChecked(); - allCheckerCheckboxes.forEach((checkbox): void => checkbox.setIsChecked(allChecked)); - - const noneChecked = allCheckboxesAreNotChecked(); - noneCheckerCheckboxes.forEach((checkbox): void => checkbox.setIsChecked(noneChecked)); - - debouncedOnChange(); - }; - - const onAllCheckerCheckboxChange = (key: string, initialized: boolean): void => { - const allCheckerCheckbox = allCheckerCheckboxes.get(key); - - if (!allCheckerCheckbox) { - return; - } - - if (initialized) { - setAllCheckboxesChecked(allCheckerCheckbox.isChecked === true); - debouncedOnChange(); - } else { - setAllCheckboxesChecked(defaultChecked || allCheckboxesAreChecked()); - } - }; - - const onNoneCheckerCheckboxChange = (key: string, initialized: boolean): void => { - const noneCheckerCheckbox = noneCheckerCheckboxes.get(key); - - if (!noneCheckerCheckbox) { - return; - } - - if (initialized && noneCheckerCheckbox.isChecked) { - setAllCheckboxesChecked(false); - debouncedOnChange(); - } else if (!noneCheckerCheckbox.isChecked && allCheckboxesAreNotChecked()) { - noneCheckerCheckbox.setIsChecked(true); - } - }; - - const hasCheckbox = (id: string) => checkboxes.has(id) || allCheckerCheckboxes.has(id) || noneCheckerCheckboxes.has(id); - - const assertIdDoesNotExist = (subject: string): void => { - if (hasCheckbox(subject)) { - throw new Error(`Duplicate id ${subject} in CheckboxGroup`); - } - }; - - const contextValue = { - allCheckerCheckboxes, - assertIdDoesNotExist, - checkboxes, defaultChecked, defaultDisabled, - noneCheckerCheckboxes, - onAllCheckerCheckboxChange, - onCheckboxChange, - onNoneCheckerCheckboxChange, - }; - - return ( - - {children} - - ); + onChange, + children, +}): ReactElement => { + const [checkboxes] = useState(new Map()); + const [allCheckerCheckboxes] = useState(new Map()); + const [noneCheckerCheckboxes] = useState(new Map()); + + const dispatchOnChange = (): void => { + if (onChange === undefined) { + return; + } + + const checkboxChangeArray: CheckboxChange[] = []; + + checkboxes.forEach((checkbox): void => { + checkboxChangeArray.push({ + ...checkbox.props, + checked: checkbox.isChecked || false, + disabled: checkbox.isDisabled || false, + }); + }); + + onChange(checkboxChangeArray); + }; + + const debouncedOnChange = debounce(dispatchOnChange, ON_CHANGE_DEBOUNCE_TIMEOUT); + + const setAllCheckboxesChecked = (state: boolean): void => { + allCheckerCheckboxes.forEach((checkbox): void => checkbox.setIsChecked(state)); + noneCheckerCheckboxes.forEach((checkbox): void => checkbox.setIsChecked(!state)); + checkboxes.forEach((checkbox, key): void => { + const clone = checkbox; + checkbox.setIsChecked(state); + clone.isChecked = state; + checkboxes.set(key, clone); + }); + }; + + const amountChecked = (): number => { + let count = 0; + + checkboxes.forEach((checkbox): void => { + if (checkbox.isChecked === true) { + count += 1; + } + }); + + return count; + }; + + const allCheckboxesAreChecked = (): boolean => { + const checkedCount = amountChecked(); + + return checkedCount > 0 && checkedCount === checkboxes.size; + }; + + const allCheckboxesAreNotChecked = (): boolean => amountChecked() === 0; + + const onCheckboxChange = (): void => { + const allChecked = allCheckboxesAreChecked(); + allCheckerCheckboxes.forEach((checkbox): void => checkbox.setIsChecked(allChecked)); + + const noneChecked = allCheckboxesAreNotChecked(); + noneCheckerCheckboxes.forEach((checkbox): void => checkbox.setIsChecked(noneChecked)); + + debouncedOnChange(); + }; + + const onAllCheckerCheckboxChange = (key: string, initialized: boolean): void => { + const allCheckerCheckbox = allCheckerCheckboxes.get(key); + + if (!allCheckerCheckbox) { + return; + } + + if (initialized) { + setAllCheckboxesChecked(allCheckerCheckbox.isChecked === true); + debouncedOnChange(); + } else { + setAllCheckboxesChecked(defaultChecked || allCheckboxesAreChecked()); + } + }; + + const onNoneCheckerCheckboxChange = (key: string, initialized: boolean): void => { + const noneCheckerCheckbox = noneCheckerCheckboxes.get(key); + + if (!noneCheckerCheckbox) { + return; + } + + if (initialized && noneCheckerCheckbox.isChecked) { + setAllCheckboxesChecked(false); + debouncedOnChange(); + } else if (!noneCheckerCheckbox.isChecked && allCheckboxesAreNotChecked()) { + noneCheckerCheckbox.setIsChecked(true); + } + }; + + const hasCheckbox = (id: string) => checkboxes.has(id) || allCheckerCheckboxes.has(id) || noneCheckerCheckboxes.has(id); + + const assertIdDoesNotExist = (subject: string): void => { + if (hasCheckbox(subject)) { + throw new Error(`Duplicate id ${subject} in CheckboxGroup`); + } + }; + + const contextValue = useMemo(() => ({ + allCheckerCheckboxes, + assertIdDoesNotExist, + checkboxes, + defaultChecked, + defaultDisabled, + noneCheckerCheckboxes, + onAllCheckerCheckboxChange, + onCheckboxChange, + onNoneCheckerCheckboxChange, + }), [children, checkboxes]); + + return ( + + {children} + + ); }; export default CheckboxGroup; diff --git a/src/CheckboxGroupContext.ts b/src/CheckboxGroupContext.ts index e189d78..f90fcbe 100644 --- a/src/CheckboxGroupContext.ts +++ b/src/CheckboxGroupContext.ts @@ -2,39 +2,37 @@ import React from 'react'; export interface CheckboxEntry { - isChecked?: boolean; - setIsChecked: (checked: boolean) => void; - isDisabled?: boolean; - setIsDisabled: (disabled: boolean) => void; - props: React.DetailedHTMLProps, HTMLInputElement>; + isChecked?: boolean; + setIsChecked: (checked: boolean) => void; + isDisabled?: boolean; + setIsDisabled: (disabled: boolean) => void; + props: React.DetailedHTMLProps, HTMLInputElement>; } -export default React.createContext<{ - allCheckerCheckboxes: Map; - assertIdDoesNotExist: (id: string) => void; - checkboxes: Map; - defaultChecked?: boolean; - defaultDisabled?: boolean; - noneCheckerCheckboxes: Map; - onAllCheckerCheckboxChange: (key: string, initialized: boolean) => void; - onCheckboxChange: () => void; - onNoneCheckerCheckboxChange: (key: string, initialized: boolean) => void; -}>({ - allCheckerCheckboxes: new Map(), - assertIdDoesNotExist: (): void => { - - }, - checkboxes: new Map(), - defaultChecked: false, - defaultDisabled: false, - noneCheckerCheckboxes: new Map(), - onAllCheckerCheckboxChange: (): void => { - - }, - onCheckboxChange: (): void => { - - }, - onNoneCheckerCheckboxChange: (): void => { +interface CheckboxGroupContextProps { + allCheckerCheckboxes: Map; + assertIdDoesNotExist:(id: string) => void; + checkboxes: Map; + defaultChecked?: boolean; + defaultDisabled?: boolean; + noneCheckerCheckboxes: Map; + onAllCheckerCheckboxChange: (key: string, initialized: boolean) => void; + onCheckboxChange: () => void; + onNoneCheckerCheckboxChange: (key: string, initialized: boolean) => void; +} - }, - }); +export default React.createContext({ + allCheckerCheckboxes: new Map(), + // eslint-disable-next-line @typescript-eslint/no-empty-function + assertIdDoesNotExist: (): void => {}, + checkboxes: new Map(), + defaultChecked: false, + defaultDisabled: false, + noneCheckerCheckboxes: new Map(), + // eslint-disable-next-line @typescript-eslint/no-empty-function + onAllCheckerCheckboxChange: (): void => {}, + // eslint-disable-next-line @typescript-eslint/no-empty-function + onCheckboxChange: (): void => {}, + // eslint-disable-next-line @typescript-eslint/no-empty-function + onNoneCheckerCheckboxChange: (): void => {}, +}); diff --git a/src/NoneCheckerCheckbox.tsx b/src/NoneCheckerCheckbox.tsx index 3e18a8d..f539f6f 100644 --- a/src/NoneCheckerCheckbox.tsx +++ b/src/NoneCheckerCheckbox.tsx @@ -1,83 +1,88 @@ import React, { - forwardRef, - ForwardRefExoticComponent, - ReactElement, - RefAttributes, - useContext, - useEffect, - useState, + forwardRef, + ForwardRefExoticComponent, + ReactElement, + RefAttributes, + useContext, + useEffect, + useState, } from 'react'; + import CheckboxGroupContext from './CheckboxGroupContext'; -import uuid from "./uuid"; +import uuid from './uuid'; type NoneCheckerCheckboxProps = Omit, HTMLInputElement> & RefAttributes, 'checked'> const NoneCheckerCheckbox: ForwardRefExoticComponent = forwardRef((props, ref): ReactElement => { - const { - disabled, - onChange, - } = props; + const { disabled, onChange } = props; - const [id] = useState(uuid()); - const checkboxGroup = useContext(CheckboxGroupContext); + const [id] = useState(uuid()); + const checkboxGroup = useContext(CheckboxGroupContext); - const [initialized, setInitialized] = useState(false); - const [shouldTriggerCheckboxContextChange, setShouldTriggerCheckboxContextChange] = useState(true); - const [isChecked, setIsChecked] = useState(checkboxGroup.defaultChecked !== undefined ? !checkboxGroup.defaultChecked : undefined); - const [isDisabled, setIsDisabled] = useState(disabled !== undefined ? disabled : checkboxGroup.defaultDisabled); + const [initialized, setInitialized] = useState(false); + const [shouldTriggerCheckboxContextChange, setShouldTriggerCheckboxContextChange] = useState(true); + const [isChecked, setIsChecked] = useState(checkboxGroup.defaultChecked !== undefined ? !checkboxGroup.defaultChecked : undefined); + const [isDisabled, setIsDisabled] = useState(disabled !== undefined ? disabled : checkboxGroup.defaultDisabled); - useEffect((): () => void => { - checkboxGroup.assertIdDoesNotExist(id); - return (): void => { - checkboxGroup.noneCheckerCheckboxes.delete(id); - }; - }, []); + useEffect((): () => void => { + checkboxGroup.assertIdDoesNotExist(id); + return (): void => { + checkboxGroup.noneCheckerCheckboxes.delete(id); + }; + }, []); - useEffect((): void => { - checkboxGroup.noneCheckerCheckboxes.set(id, { - isChecked, - isDisabled, - props, - setIsChecked, - setIsDisabled, - }); + useEffect((): void => { + checkboxGroup.noneCheckerCheckboxes.set(id, { + isChecked, + isDisabled, + props, + setIsChecked, + setIsDisabled, + }); - if (shouldTriggerCheckboxContextChange) { - checkboxGroup.onNoneCheckerCheckboxChange(id, initialized); - setShouldTriggerCheckboxContextChange(false); - } + if (shouldTriggerCheckboxContextChange) { + checkboxGroup.onNoneCheckerCheckboxChange(id, initialized); + setShouldTriggerCheckboxContextChange(false); + } - if (!initialized) { - setInitialized(true); - } - }, [ - id, isChecked, isDisabled, setIsChecked, setIsDisabled, initialized, - setShouldTriggerCheckboxContextChange, checkboxGroup, shouldTriggerCheckboxContextChange, - ]); + if (!initialized) { + setInitialized(true); + } + }, [ + id, + isChecked, + isDisabled, + setIsChecked, + setIsDisabled, + initialized, + setShouldTriggerCheckboxContextChange, + checkboxGroup, + shouldTriggerCheckboxContextChange, + ]); - const handleChange = (event: React.ChangeEvent): void => { - event.persist(); + const handleChange = (event: React.ChangeEvent): void => { + event.persist(); - if (!isDisabled) { - setShouldTriggerCheckboxContextChange(true); - setIsChecked(event.target.checked); - } + if (!isDisabled) { + setShouldTriggerCheckboxContextChange(true); + setIsChecked(event.target.checked); + } - if (onChange !== undefined) { - onChange(event); - } - }; + if (onChange !== undefined) { + onChange(event); + } + }; - return ( - - ); + return ( + + ); }); export default NoneCheckerCheckbox; diff --git a/src/index.ts b/src/index.ts index be55ca1..cee297a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,8 +4,8 @@ import CheckboxGroup from './CheckboxGroup'; import NoneCheckerCheckbox from './NoneCheckerCheckbox'; export { - AllCheckerCheckbox, - Checkbox, - CheckboxGroup, - NoneCheckerCheckbox, + AllCheckerCheckbox, + Checkbox, + CheckboxGroup, + NoneCheckerCheckbox, }; diff --git a/src/uuid.ts b/src/uuid.ts index e36206d..16772bf 100644 --- a/src/uuid.ts +++ b/src/uuid.ts @@ -6,7 +6,7 @@ export default (): string => { return Array.from( // @ts-ignore // Modern Browser - (window.crypto || window.msCrypto).getRandomValues(new Uint8Array(16)) + (window.crypto || window.msCrypto).getRandomValues(new Uint8Array(16)), ); } catch (error) { // Legacy Browser, fallback to Math.random @@ -18,8 +18,8 @@ export default (): string => { const m = (v: number): string => { let vString = v.toString(16); - if (vString.length < 2){ - vString = "0" + v; + if (vString.length < 2) { + vString = `0${v}`; } return vString; @@ -31,5 +31,5 @@ export default (): string => { return rnd .map(m) - .join(""); -} + .join(''); +};