From 3142266f74a1ff9003ae64c9fe1bafe0d2161850 Mon Sep 17 00:00:00 2001 From: Seva Podolskiy Date: Thu, 3 Oct 2024 13:34:53 -0400 Subject: [PATCH 1/2] solution --- src/App.tsx | 149 +---------------- src/Components/TodoFilter/TodoFilter.tsx | 54 +++++++ src/Components/TodoFilter/index.ts | 1 + src/Components/TodoHead/TodoHead.tsx | 54 +++++++ src/Components/TodoHead/index.ts | 1 + src/Components/TodoItem/TodoItem.tsx | 115 +++++++++++++ src/Components/TodoItem/index.ts | 1 + src/Components/TodoList/TodoList.tsx | 15 ++ src/Components/TodoList/index.ts | 1 + src/index.tsx | 15 +- src/styles/todo.scss | 1 + src/styles/todoapp.scss | 1 + src/types/Filters.ts | 5 + src/types/Todo.ts | 5 + src/utils/GlobalContext.tsx | 196 +++++++++++++++++++++++ src/utils/LocalStorage.ts | 28 ++++ 16 files changed, 494 insertions(+), 148 deletions(-) create mode 100644 src/Components/TodoFilter/TodoFilter.tsx create mode 100644 src/Components/TodoFilter/index.ts create mode 100644 src/Components/TodoHead/TodoHead.tsx create mode 100644 src/Components/TodoHead/index.ts create mode 100644 src/Components/TodoItem/TodoItem.tsx create mode 100644 src/Components/TodoItem/index.ts create mode 100644 src/Components/TodoList/TodoList.tsx create mode 100644 src/Components/TodoList/index.ts create mode 100644 src/types/Filters.ts create mode 100644 src/types/Todo.ts create mode 100644 src/utils/GlobalContext.tsx create mode 100644 src/utils/LocalStorage.ts diff --git a/src/App.tsx b/src/App.tsx index a399287bd..c08e217e5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,7 @@ /* eslint-disable jsx-a11y/control-has-associated-label */ -import React from 'react'; +import { TodoHead } from './Components/TodoHead'; +import { TodoList } from './Components/TodoList'; +import { TodoFilter } from './Components/TodoFilter'; export const App: React.FC = () => { return ( @@ -7,150 +9,11 @@ export const App: React.FC = () => {

todos

-
- {/* this button should have `active` class only if all todos are completed */} -
+ -
- {/* This is a completed todo */} -
- - - - Completed Todo - - - {/* Remove button appears only on hover */} - -
- - {/* This todo is an active todo */} -
- - - - Not Completed Todo - - - -
- - {/* This todo is being edited */} -
- - - {/* This form is shown instead of the title and remove button */} -
- -
-
- - {/* This todo is in loadind state */} -
- - - - Todo is being saved now - - - -
-
- - {/* Hide the footer if there are no todos */} -
- - 3 items left - - - {/* Active link should have the 'selected' class */} - - - {/* this button should be disabled if there are no completed todos */} - -
+
); diff --git a/src/Components/TodoFilter/TodoFilter.tsx b/src/Components/TodoFilter/TodoFilter.tsx new file mode 100644 index 000000000..f9294ae78 --- /dev/null +++ b/src/Components/TodoFilter/TodoFilter.tsx @@ -0,0 +1,54 @@ +import React, { useContext } from 'react'; +import classNames from 'classnames'; +import { Filters } from '../../types/Filters'; +import { DispatchContext, StateContext } from '../../utils/GlobalContext'; + +export const TodoFilter: React.FC = () => { + const { todos, filteredTodos, filter } = useContext(StateContext); + const dispatch = useContext(DispatchContext); + const uncmpltTodosCount = todos.filter(todo => !todo.completed).length; + + return ( + <> + {todos.length > 0 && ( + + )} + + ); +}; diff --git a/src/Components/TodoFilter/index.ts b/src/Components/TodoFilter/index.ts new file mode 100644 index 000000000..338726b46 --- /dev/null +++ b/src/Components/TodoFilter/index.ts @@ -0,0 +1 @@ +export * from './TodoFilter'; diff --git a/src/Components/TodoHead/TodoHead.tsx b/src/Components/TodoHead/TodoHead.tsx new file mode 100644 index 000000000..5c3133cea --- /dev/null +++ b/src/Components/TodoHead/TodoHead.tsx @@ -0,0 +1,54 @@ +import { useContext, useEffect, useRef, useState } from 'react'; +import classNames from 'classnames'; +import { DispatchContext, StateContext } from '../../utils/GlobalContext'; + +export const TodoHead: React.FC = () => { + const { filteredTodos } = useContext(StateContext); + const dispatch = useContext(DispatchContext); + + const [title, setTitle] = useState(''); + const inputElem = useRef(null); + + useEffect(() => { + if (inputElem.current) { + inputElem.current.focus(); + } + }, []); + + const handleOnsubmit = (event: React.FormEvent) => { + event.preventDefault(); + const cleanTitle = title.trim(); + + if (cleanTitle.length > 0) { + dispatch({ type: 'addTodo', payload: cleanTitle }); + setTitle(''); + } + }; + + return ( +
+
+ ); +}; diff --git a/src/Components/TodoHead/index.ts b/src/Components/TodoHead/index.ts new file mode 100644 index 000000000..32a8949cb --- /dev/null +++ b/src/Components/TodoHead/index.ts @@ -0,0 +1 @@ +export * from './TodoHead'; diff --git a/src/Components/TodoItem/TodoItem.tsx b/src/Components/TodoItem/TodoItem.tsx new file mode 100644 index 000000000..abfc73b61 --- /dev/null +++ b/src/Components/TodoItem/TodoItem.tsx @@ -0,0 +1,115 @@ +/* eslint-disable jsx-a11y/label-has-associated-control */ +/* eslint-disable jsx-a11y/control-has-associated-label */ + +import classNames from 'classnames'; +import { Todo } from '../../types/Todo'; +import React, { useContext, useEffect, useRef, useState } from 'react'; +import { DispatchContext } from '../../utils/GlobalContext'; + +type Props = { + todo: Todo; +}; +type SubmitProps = + | React.FocusEvent + | React.KeyboardEvent + | React.FormEvent; + +export const TodoItem: React.FC = ({ todo }) => { + const dispatch = useContext(DispatchContext); + + const { title, completed } = todo; + const [editing, setEditing] = useState(false); + const [newTitle, setNewTitle] = useState(title); + + const inputElem = useRef(null); + + useEffect(() => { + if (inputElem.current) { + inputElem.current.focus(); + } + }, [editing]); + + const handleCancel = () => { + setEditing(false); + setNewTitle(todo.title); + }; + + const handleKeyEvent = (event: React.KeyboardEvent) => { + if (event.type === 'keyup') { + const keyEvent = event as React.KeyboardEvent; + + if (keyEvent.key === 'Escape') { + handleCancel(); + } + } + }; + + function handleSubmit(event: SubmitProps): void { + event.preventDefault(); + setEditing(false); + + if (newTitle.length === 0) { + dispatch({ type: 'deleteTodo', payload: todo.id }); + + return; + } + + if (todo.title !== newTitle) { + dispatch({ type: 'editTodo', payload: { id: todo.id, title: newTitle } }); + } + } + + return ( +
+ + + {!editing ? ( + <> + setEditing(true)} + > + {newTitle} + + + + + ) : ( +
handleSubmit(e)}> + setNewTitle(e.target.value)} + onBlur={handleSubmit} + onKeyUp={handleKeyEvent} + /> +
+ )} +
+ ); +}; diff --git a/src/Components/TodoItem/index.ts b/src/Components/TodoItem/index.ts new file mode 100644 index 000000000..21f4abac3 --- /dev/null +++ b/src/Components/TodoItem/index.ts @@ -0,0 +1 @@ +export * from './TodoItem'; diff --git a/src/Components/TodoList/TodoList.tsx b/src/Components/TodoList/TodoList.tsx new file mode 100644 index 000000000..b42545097 --- /dev/null +++ b/src/Components/TodoList/TodoList.tsx @@ -0,0 +1,15 @@ +import { useContext } from 'react'; +import { TodoItem } from '../TodoItem'; +import { StateContext } from '../../utils/GlobalContext'; + +export const TodoList: React.FC = () => { + const { filteredTodos } = useContext(StateContext); + + return ( +
+ {filteredTodos.map(todo => { + return ; + })} +
+ ); +}; diff --git a/src/Components/TodoList/index.ts b/src/Components/TodoList/index.ts new file mode 100644 index 000000000..f239f4345 --- /dev/null +++ b/src/Components/TodoList/index.ts @@ -0,0 +1 @@ +export * from './TodoList'; diff --git a/src/index.tsx b/src/index.tsx index a9689cb38..9b2c9448b 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,11 +1,16 @@ import { createRoot } from 'react-dom/client'; - -import './styles/index.css'; -import './styles/todo-list.css'; -import './styles/filters.css'; +import '../src/styles/index.scss'; +import '../src/styles/filter.scss'; +import '../src/styles/todo.scss'; +import '../src/styles/todoapp.scss'; import { App } from './App'; +import { GlobalProvider } from './utils/GlobalContext'; const container = document.getElementById('root') as HTMLDivElement; -createRoot(container).render(); +createRoot(container).render( + + + , +); diff --git a/src/styles/todo.scss b/src/styles/todo.scss index 4576af434..441e9cd8f 100644 --- a/src/styles/todo.scss +++ b/src/styles/todo.scss @@ -72,6 +72,7 @@ &__title-field { width: 100%; + box-sizing: border-box; padding: 11px 14px; font-size: inherit; diff --git a/src/styles/todoapp.scss b/src/styles/todoapp.scss index e289a9458..e60bcfff6 100644 --- a/src/styles/todoapp.scss +++ b/src/styles/todoapp.scss @@ -57,6 +57,7 @@ &__new-todo { width: 100%; + box-sizing: border-box; padding: 16px 16px 16px 60px; font-size: 24px; diff --git a/src/types/Filters.ts b/src/types/Filters.ts new file mode 100644 index 000000000..e6081071e --- /dev/null +++ b/src/types/Filters.ts @@ -0,0 +1,5 @@ +export enum Filters { + 'all' = 'All', + 'active' = 'Active', + 'completed' = 'Completed', +} diff --git a/src/types/Todo.ts b/src/types/Todo.ts new file mode 100644 index 000000000..f9e06b381 --- /dev/null +++ b/src/types/Todo.ts @@ -0,0 +1,5 @@ +export interface Todo { + id: number; + title: string; + completed: boolean; +} diff --git a/src/utils/GlobalContext.tsx b/src/utils/GlobalContext.tsx new file mode 100644 index 000000000..efc26092d --- /dev/null +++ b/src/utils/GlobalContext.tsx @@ -0,0 +1,196 @@ +import React, { useEffect, useReducer } from 'react'; +import { Todo } from '../types/Todo'; +import { LocalStorage } from './LocalStorage'; +import { Filters } from '../types/Filters'; + +function filterTodos(todos: Todo[], filter: Filters): Todo[] { + switch (filter) { + case Filters.completed: + return todos.filter(todo => todo.completed === true); + + case Filters.active: + return todos.filter(todo => todo.completed === false); + } + + return todos; +} + +function getNewId(min = 100000, max = 999999) { + return Math.floor(Math.random() * (max - min)) + min; +} + +type State = { + todos: Todo[]; + filteredTodos: Todo[]; + filter: Filters; +}; + +type Action = + | { type: 'getTodos' } + | { type: 'getFilteredTodos' } + | { type: 'setFilter'; payload: Filters } + | { type: 'addTodo'; payload: string } + | { type: 'editTodo'; payload: { id: number; title: string } } + | { type: 'deleteTodo'; payload: number } + | { type: 'toggleTodo'; payload: number } + | { type: 'deleteAllCompleted' } + | { type: 'toggleAll' }; + +function reducer(state: State, action: Action): State { + function updateAllTodos() { + const latestFromServer = LocalStorage.get(); + + return { + todos: latestFromServer, + filteredTodos: filterTodos(latestFromServer, state.filter), + }; + } + + switch (action.type) { + case 'getTodos': + return { + ...state, + todos: LocalStorage.get(), + }; + + case 'getFilteredTodos': + return { + ...state, + filteredTodos: filterTodos(LocalStorage.get(), state.filter), + }; + + case 'setFilter': + const newFilter = action.payload; + + return { + ...state, + filter: newFilter, + filteredTodos: filterTodos(LocalStorage.get(), newFilter), + }; + + case 'addTodo': + const newPost: Todo = { + id: getNewId(), + title: action.payload, + completed: false, + }; + + LocalStorage.set([...LocalStorage.get(), newPost]); + + return { + ...state, + ...updateAllTodos(), + }; + + case 'toggleAll': + const currentToToggleAll = LocalStorage.get(); + let toggledTodos: Todo[] = []; + + if (currentToToggleAll.every(todo => todo.completed)) { + toggledTodos = currentToToggleAll.map((todo: Todo) => ({ + ...todo, + completed: false, + })); + } else { + toggledTodos = currentToToggleAll.map((todo: Todo) => ({ + ...todo, + completed: true, + })); + } + + LocalStorage.set(toggledTodos); + + return { + ...state, + ...updateAllTodos(), + }; + + case 'editTodo': + const currentToDel = LocalStorage.get(); + const { id, title } = action.payload; + const editTodoIndex = currentToDel.findIndex(el => el.id === id); + + currentToDel[editTodoIndex].title = title; + + LocalStorage.set(currentToDel); + + return { + ...state, + ...updateAllTodos(), + }; + + case 'toggleTodo': + const currentToToggle = LocalStorage.get(); + const toogleTodoIndex = currentToToggle.findIndex( + el => el.id === action.payload, + ); + + currentToToggle[toogleTodoIndex].completed = + !currentToToggle[toogleTodoIndex].completed; + + LocalStorage.set(currentToToggle); + + return { + ...state, + ...updateAllTodos(), + }; + + case 'deleteTodo': + const newTodos = LocalStorage.get().filter( + (todo: Todo) => todo.id !== action.payload, + ); + + LocalStorage.set(newTodos); + + return { + ...state, + ...updateAllTodos(), + }; + + case 'deleteAllCompleted': + const allNotCompleted = LocalStorage.get().filter( + todo => !todo.completed, + ); + + LocalStorage.set(allNotCompleted); + + return { + ...state, + ...updateAllTodos(), + }; + + default: + return { ...state }; + } +} + +const initialState: State = { + todos: [], + filteredTodos: [], + filter: Filters.all, +}; + +export const StateContext = React.createContext(initialState); + +export const DispatchContext = React.createContext>( + () => {}, +); + +type Props = { + children: React.ReactNode; +}; + +export const GlobalProvider: React.FC = ({ children }) => { + const [state, dispatch] = useReducer(reducer, initialState); + + useEffect(() => { + dispatch({ type: 'getTodos' }); + dispatch({ type: 'getFilteredTodos' }); + }, []); + + return ( + + {children} + + ); +}; diff --git a/src/utils/LocalStorage.ts b/src/utils/LocalStorage.ts new file mode 100644 index 000000000..8948f95c1 --- /dev/null +++ b/src/utils/LocalStorage.ts @@ -0,0 +1,28 @@ +import { Todo } from '../types/Todo'; + +export const LocalStorage = { + key: 'todos', + get(): Todo[] { + const data = localStorage.getItem(this.key); + + try { + return data ? JSON.parse(data) : []; + } catch { + return []; + } + }, + + set(todos: Todo[]) { + try { + localStorage.setItem(this.key, JSON.stringify(todos)); + + return this.get; + } catch { + return []; + } + }, + + clear() { + localStorage.clear(); + }, +}; From a31b859cae93a29fe1da3f749abacb8cd5da105c Mon Sep 17 00:00:00 2001 From: Seva Podolskiy Date: Fri, 4 Oct 2024 12:14:38 -0400 Subject: [PATCH 2/2] fixed all comments as per PR fixed all tests --- src/{utils => Components}/GlobalContext.tsx | 90 ++++++++------------ src/Components/TodoFilter/TodoFilter.tsx | 91 +++++++++++---------- src/Components/TodoHead/TodoHead.tsx | 37 +++++---- src/Components/TodoItem/TodoItem.tsx | 32 ++++---- src/Components/TodoList/TodoList.tsx | 11 ++- src/index.tsx | 2 +- src/utils/LocalStorage.ts | 6 +- src/utils/getFilteredTodos.ts | 14 ++++ 8 files changed, 146 insertions(+), 137 deletions(-) rename src/{utils => Components}/GlobalContext.tsx (62%) create mode 100644 src/utils/getFilteredTodos.ts diff --git a/src/utils/GlobalContext.tsx b/src/Components/GlobalContext.tsx similarity index 62% rename from src/utils/GlobalContext.tsx rename to src/Components/GlobalContext.tsx index efc26092d..d8365c44c 100644 --- a/src/utils/GlobalContext.tsx +++ b/src/Components/GlobalContext.tsx @@ -1,63 +1,42 @@ import React, { useEffect, useReducer } from 'react'; import { Todo } from '../types/Todo'; -import { LocalStorage } from './LocalStorage'; +import { accessLocalStorage } from '../utils/LocalStorage'; import { Filters } from '../types/Filters'; -function filterTodos(todos: Todo[], filter: Filters): Todo[] { - switch (filter) { - case Filters.completed: - return todos.filter(todo => todo.completed === true); - - case Filters.active: - return todos.filter(todo => todo.completed === false); - } - - return todos; -} - function getNewId(min = 100000, max = 999999) { return Math.floor(Math.random() * (max - min)) + min; } type State = { todos: Todo[]; - filteredTodos: Todo[]; filter: Filters; }; type Action = | { type: 'getTodos' } - | { type: 'getFilteredTodos' } + | { type: 'setTodos'; payload: Todo[] } | { type: 'setFilter'; payload: Filters } | { type: 'addTodo'; payload: string } | { type: 'editTodo'; payload: { id: number; title: string } } | { type: 'deleteTodo'; payload: number } | { type: 'toggleTodo'; payload: number } | { type: 'deleteAllCompleted' } - | { type: 'toggleAll' }; + | { type: 'toggleAll' } + | { type: 'setInputFiled' } + | { type: 'getInputField'; payload: number }; function reducer(state: State, action: Action): State { - function updateAllTodos() { - const latestFromServer = LocalStorage.get(); - - return { - todos: latestFromServer, - filteredTodos: filterTodos(latestFromServer, state.filter), - }; - } - switch (action.type) { case 'getTodos': return { ...state, - todos: LocalStorage.get(), + todos: accessLocalStorage.get(), }; - case 'getFilteredTodos': - return { - ...state, - filteredTodos: filterTodos(LocalStorage.get(), state.filter), - }; + case 'setTodos': + accessLocalStorage.set([]); + + return { ...state }; case 'setFilter': const newFilter = action.payload; @@ -65,7 +44,6 @@ function reducer(state: State, action: Action): State { return { ...state, filter: newFilter, - filteredTodos: filterTodos(LocalStorage.get(), newFilter), }; case 'addTodo': @@ -75,15 +53,15 @@ function reducer(state: State, action: Action): State { completed: false, }; - LocalStorage.set([...LocalStorage.get(), newPost]); + accessLocalStorage.set([...accessLocalStorage.get(), newPost]); return { ...state, - ...updateAllTodos(), + todos: accessLocalStorage.get(), }; case 'toggleAll': - const currentToToggleAll = LocalStorage.get(); + const currentToToggleAll = accessLocalStorage.get(); let toggledTodos: Todo[] = []; if (currentToToggleAll.every(todo => todo.completed)) { @@ -98,29 +76,29 @@ function reducer(state: State, action: Action): State { })); } - LocalStorage.set(toggledTodos); + accessLocalStorage.set(toggledTodos); return { ...state, - ...updateAllTodos(), + todos: accessLocalStorage.get(), }; case 'editTodo': - const currentToDel = LocalStorage.get(); + const currentToDel = accessLocalStorage.get(); const { id, title } = action.payload; const editTodoIndex = currentToDel.findIndex(el => el.id === id); currentToDel[editTodoIndex].title = title; - LocalStorage.set(currentToDel); + accessLocalStorage.set(currentToDel); return { ...state, - ...updateAllTodos(), + todos: accessLocalStorage.get(), }; case 'toggleTodo': - const currentToToggle = LocalStorage.get(); + const currentToToggle = accessLocalStorage.get(); const toogleTodoIndex = currentToToggle.findIndex( el => el.id === action.payload, ); @@ -128,35 +106,35 @@ function reducer(state: State, action: Action): State { currentToToggle[toogleTodoIndex].completed = !currentToToggle[toogleTodoIndex].completed; - LocalStorage.set(currentToToggle); + accessLocalStorage.set(currentToToggle); return { ...state, - ...updateAllTodos(), + todos: accessLocalStorage.get(), }; case 'deleteTodo': - const newTodos = LocalStorage.get().filter( - (todo: Todo) => todo.id !== action.payload, - ); + const newTodos = accessLocalStorage + .get() + .filter((todo: Todo) => todo.id !== action.payload); - LocalStorage.set(newTodos); + accessLocalStorage.set(newTodos); return { ...state, - ...updateAllTodos(), + todos: accessLocalStorage.get(), }; case 'deleteAllCompleted': - const allNotCompleted = LocalStorage.get().filter( - todo => !todo.completed, - ); + const allNotCompleted = accessLocalStorage + .get() + .filter(todo => !todo.completed); - LocalStorage.set(allNotCompleted); + accessLocalStorage.set(allNotCompleted); return { ...state, - ...updateAllTodos(), + todos: accessLocalStorage.get(), }; default: @@ -166,7 +144,6 @@ function reducer(state: State, action: Action): State { const initialState: State = { todos: [], - filteredTodos: [], filter: Filters.all, }; @@ -184,8 +161,11 @@ export const GlobalProvider: React.FC = ({ children }) => { const [state, dispatch] = useReducer(reducer, initialState); useEffect(() => { + if (accessLocalStorage.get().length <= 0) { + dispatch({ type: 'setTodos', payload: [] }); + } + dispatch({ type: 'getTodos' }); - dispatch({ type: 'getFilteredTodos' }); }, []); return ( diff --git a/src/Components/TodoFilter/TodoFilter.tsx b/src/Components/TodoFilter/TodoFilter.tsx index f9294ae78..7e0c9e881 100644 --- a/src/Components/TodoFilter/TodoFilter.tsx +++ b/src/Components/TodoFilter/TodoFilter.tsx @@ -1,54 +1,59 @@ -import React, { useContext } from 'react'; +import React, { useContext, useMemo } from 'react'; import classNames from 'classnames'; import { Filters } from '../../types/Filters'; -import { DispatchContext, StateContext } from '../../utils/GlobalContext'; +import { DispatchContext, StateContext } from '../GlobalContext'; +import { getFilteredTodos } from '../../utils/getFilteredTodos'; export const TodoFilter: React.FC = () => { - const { todos, filteredTodos, filter } = useContext(StateContext); + const { todos, filter } = useContext(StateContext); const dispatch = useContext(DispatchContext); const uncmpltTodosCount = todos.filter(todo => !todo.completed).length; + const filteredTodos = useMemo( + () => getFilteredTodos(todos, filter), + [todos, filter], + ); - return ( - <> - {todos.length > 0 && ( -
- - {`${uncmpltTodosCount} ${uncmpltTodosCount === 1 ? 'item left' : 'items left'}`} - + if (todos.length > 0) { + return ( + - )} - - ); + +
+ ); + } else { + return null; + } }; diff --git a/src/Components/TodoHead/TodoHead.tsx b/src/Components/TodoHead/TodoHead.tsx index 5c3133cea..2d119fd4f 100644 --- a/src/Components/TodoHead/TodoHead.tsx +++ b/src/Components/TodoHead/TodoHead.tsx @@ -1,19 +1,25 @@ -import { useContext, useEffect, useRef, useState } from 'react'; +import { useContext, useEffect, useMemo, useRef, useState } from 'react'; import classNames from 'classnames'; -import { DispatchContext, StateContext } from '../../utils/GlobalContext'; +import { DispatchContext, StateContext } from '../GlobalContext'; +import { getFilteredTodos } from '../../utils/getFilteredTodos'; export const TodoHead: React.FC = () => { - const { filteredTodos } = useContext(StateContext); + const { todos, filter } = useContext(StateContext); const dispatch = useContext(DispatchContext); const [title, setTitle] = useState(''); + const filteredTodos = useMemo( + () => getFilteredTodos(todos, filter), + [todos, filter], + ); + const inputElem = useRef(null); useEffect(() => { if (inputElem.current) { inputElem.current.focus(); } - }, []); + }); const handleOnsubmit = (event: React.FormEvent) => { event.preventDefault(); @@ -27,17 +33,18 @@ export const TodoHead: React.FC = () => { return (
-