miyamoen / select-list / SelectList

Yet another SelectList implementation

A SelectList is a non-empty list which always has exactly one element selected. It is an example of a list zipper.

Inspired by these modules

selectedMap is the feature function in this package. Use selectedMap in view.

view : SelectList String -> Html Msg
view selectList =
    ul [] <|
        SelectList.selectedMap
            (\position item ->
                li [ onClick (Set item) ]
                    [ text <| toString <| SelectList.index item
                    , toString <| SelectList.selected item
                    ]
            )
            selectList

Type


type alias SelectList a =
Types.SelectList a

A nonempty list which always has exactly one element selected.

Constructor

fromLists : List a -> a -> List a -> SelectList a

Create a SelectList.

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> selected
    == 4

fromList : List a -> Maybe (SelectList a)

Create a SelectList if list has elements.

If empty, Nothing.

fromList [] == Nothing

fromList [ 2, 3, 4 ] == Just (fromLists [] 2 [ 3, 4 ])

singleton : a -> SelectList a

Create a SelectList containing exactly one element.

singleton 3 == fromLists [] 3 []

Destructor

toTuple : SelectList a -> ( List a, a, List a )

Destruct SelectList.

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> toTuple
    == ( 1, 2, 3 ) 4 ( 5, 6 )

selected : SelectList a -> a

Return the selected element.

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> selected
    == 4

listAfter : SelectList a -> List a

Return the elements after the selected element.

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> listAfter
    == [ 5, 6 ]

listBefore : SelectList a -> List a

Return the elements before the selected element.

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> listBefore
    == [ 1, 2, 3 ]

toList : SelectList a -> List a

Destruct SelectList.

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> toList
    == [ 1, 2, 3, 4, 5, 6 ]

Query

isHead : SelectList a -> Basics.Bool

Check if the selected element is first element.

fromLists [] 4 [ 5, 6 ]
    |> isHead
    == True

isLast : SelectList a -> Basics.Bool

Check if the selected element is last element.

fromLists [ 1, 2, 3 ] 4 []
    |> isLast
    == True

isSingle : SelectList a -> Basics.Bool

Check if the selected element is only element in select list.

fromLists [] 4 []
    |> isSingle
    == True

length : SelectList a -> Basics.Int

Length of SelectList.

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> length
    == 6

beforeLength : SelectList a -> Basics.Int

Length of the elements before the selected element

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> beforeLength
    == 3

afterLength : SelectList a -> Basics.Int

Length of the elements after the selected element

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> afterLength
    == 2

index : SelectList a -> Basics.Int

Index of the selected element.

This is alias of beforeLength.

index =
    beforeLength

Operation

reverse : SelectList a -> SelectList a

Reverse a select list. Pivot is selected element.

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> reverse
    == fromLists [ 6, 5 ] 4 [ 3, 2, 1 ]

attempt : (SelectList a -> Maybe (SelectList a)) -> SelectList a -> SelectList a

Attempt to perform action over selectList and return original SelectList in cases where this action returns Nothing.

attempt f selectList =
    f selectList
        |> Maybe.withDefault selectList

delete : SelectList a -> Maybe (SelectList a)

Delete the selected element, then select an element after the selected.

If a list after selected is empty, then select an element before the selected.

Returns Nothing if SelectList has only single value.

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> delete
    == Just (fromLists [ 1, 2, 3 ] 5 [ 6 ])

fromLists [ 1, 2, 3 ] 4 []
    |> delete
    == Just (fromLists [ 1, 2 ] 3 [])

fromLists [] 4 []
    |> delete
    == Nothing

insertBefore : a -> SelectList a -> SelectList a

Insert new selected element, then move the old before it.

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> insertBefore 8
    == fromLists [ 1, 2, 3, 4 ] 8 [ 5, 6 ]

insertAfter : a -> SelectList a -> SelectList a

Insert new selected element, then move the old after it.

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> insertAfter 8
    == fromLists [ 1, 2, 3 ] 8 [ 4, 5, 6 ]

Transform

map : (a -> b) -> SelectList a -> SelectList b

Apply a function to every element of a SelectList.

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> map (\num -> num * 2)
    == fromLists [ 2, 4, 6 ] 8 [ 10, 12 ]

mapBefore : (a -> a) -> SelectList a -> SelectList a

Apply a function to elements before the selected element.

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> mapBefore (\selected -> 2 * selected)
    == fromLists [ 2, 4, 6 ] 4 [ 5, 6 ]

mapAfter : (a -> a) -> SelectList a -> SelectList a

Apply a function to elements after the selected element.

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> mapAfter (\selected -> 2 * selected)
    == fromLists [ 1, 2, 3 ] 4 [ 10, 12 ]

Update

updateSelected : (a -> a) -> SelectList a -> SelectList a

Update the selected element using given function.

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> updateSelected (\selected -> 2 * selected)
    == fromLists [ 1, 2, 3 ] 8 [ 5, 6 ]

updateBefore : (List a -> List a) -> SelectList a -> SelectList a

Update elements before the selected element using given function.

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> updateBefore (\before -> List.map ((*) 2) before)
    == fromLists [ 2, 4, 6 ] 4 [ 5, 6 ]

updateAfter : (List a -> List a) -> SelectList a -> SelectList a

Update elements after the selected element using given function.

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> updateBefore (\after -> List.map ((*) 2) after)
    == fromLists [ 1, 2, 3 ] 4 [ 10, 12 ]

Replace

Alias of update function.

replaceX x =
    updateX (always x)

replaceSelected : a -> SelectList a -> SelectList a

Replace the selected element with new one.

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> replaceSelected 10
    == fromLists [ 1, 2, 3 ] 10 [ 5, 6 ]

replaceBefore : List a -> SelectList a -> SelectList a

Replace elements before the selected element with new elements.

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> replaceBefore [ 7, 8 ]
    == fromLists [ 7, 8 ] 4 [ 5, 6 ]

replaceAfter : List a -> SelectList a -> SelectList a

Replace elements after the selected element with new elements.

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> replaceAfter [ 9, 10, 11 ]
    == fromLists [ 1, 2, 3 ] 4 [ 9, 10, 11 ]

Feature Functions


type Position
    = BeforeSelected
    | Selected
    | AfterSelected

Position is used with selectedMap.

Position is Selected if the selected element, BeforeSelected if an element before the selected element, and AfterSelected if an element after the selected element.

selectedMap : (Position -> SelectList a -> b) -> SelectList a -> List b

Apply a function to every element of a SelectList.

The transform function receives a Position and SelectList which selects a focused element.

Use in view.

view : SelectList String -> Html Msg
view selectList =
    ul [] <|
        SelectList.selectedMap
            (\position item ->
                li [ onClick (Set item) ]
                    [ text <| toString <| SelectList.index item
                    , toString <| SelectList.selected item
                    ]
            )
            selectList

Get a focused item and index from select list. Position describes whether it is selected, or not.

Compared with List.indexedMap.

selectedMap : (Position -> SelectList a -> b) -> SelectList a -> List b

indexedMap : (Int -> a -> b) -> List a -> List b

Unlike indexedMap, we can get full access to all elements in the list. And set new list to Model.

If you don't use non-empty list, use selectedMapForList that receives List instead of SelectList.

selectedMapForList : (SelectList a -> b) -> List a -> List b

Apply a function to every element of a List.

The transform function receives a SelectList which selects a focused element.

Use in view.

view : SelectList String -> Html Msg
view selectList =
    ul [] <|
        SelectList.selectedMapForList
            (\item ->
                li [ onClick (Set <| SelectList.toList <| SelectList.update updateFunction item) ]
                    [ text <| toString <| SelectList.index item
                    , toString <| SelectList.selected item
                    ]
            )
            selectList

Use this instead of indexedMap.

indexedMap : (Basics.Int -> a -> b) -> SelectList a -> List b

Apply a function to every element of a SelectList.

The transform function receives an index and an element.

A problem with selectedMap is to produce many SelectLists. indexedMap solves it.

The index is relative. We can create new list with original list and relative index.

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> indexedMap (\index elm -> ( index, elm * 2 ))
    == [ ( -3, 2 )
       , ( -2, 4 )
       , ( -1, 6 )
       , ( 0, 8 )
       , ( 1, 10 )
       , ( 2, 12 )
       ]

indexedMap_ : (Basics.Bool -> Basics.Int -> a -> b) -> SelectList a -> List b

Absolute index version.

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> indexedMap_ (\selected index elm -> ( selected, index, 2 * elm ))
    == [ ( False, 0, 2 )
       , ( False, 1, 4 )
       , ( False, 2, 6 )
       , ( True, 3, 8 )
       , ( False, 4, 10 )
       , ( False, 5, 12 )
       ]

Move

Move selected element.

moveBy : Basics.Int -> SelectList a -> SelectList a

Move a selected element by n steps. Pass an index over the length, then move to head/last.

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> moveBy -2
    == fromLists [ 1 ] 4 [ 2, 3, 5, 6 ]

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> moveBy 1
    == fromLists [ 1, 2, 3, 5 ] 4 [ 6 ]

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> moveBy 3
    == fromLists [ 1, 2, 3, 5, 6 ] 4 []

moveWhileLoopBy : Basics.Int -> SelectList a -> SelectList a

Move a selected element by n steps while loop. Pass an index over the length, then loop.

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> moveWhileLoopBy 4
    == fromLists [ 1 ] 4 [ 2, 3, 5, 6 ]

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> moveWhileLoopBy -4
    == fromLists [ 1 2, 3, 5, 6 ] 4 []

moveToHead : SelectList a -> SelectList a

Move a selected element to head.

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> moveToHead
    == fromLists [] 4 [ 1, 2, 3, 5, 6 ]

moveToLast : SelectList a -> SelectList a

Move a selected element to last.

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> moveToLast
    == fromLists [ 1, 2, 3, 5, 6 ] 4 []

Select

Select new element, otherwise move focus.

Predicate

selectBeforeIf : (a -> Basics.Bool) -> SelectList a -> Maybe (SelectList a)

Select the first element, before the old selected, that satisfies a predicate. If none match, return Nothing.

isOdd num =
    modBy 2 num /= 0

fromLists [ 1, 2 ] 3 [ 4, 5, 6 ]
    |> selectBeforeIf isOdd
    == Just (fromLists [ 0 ] 1 [ 2, 3, 4, 5, 6 ])

selectAfterIf : (a -> Basics.Bool) -> SelectList a -> Maybe (SelectList a)

Select the first element, after the old selected, that satisfies a predicate. If none match, return Nothing.

isOdd num =
    modBy 2 num /= 0

fromLists [ 1, 2 ] 3 [ 4, 5, 6 ]
    |> selectAfterIf isOdd
    == Just (fromLists [ 1, 2, 3, 4 ] 5 [ 6 ])

Index

selectBy : Basics.Int -> SelectList a -> Maybe (SelectList a)

Select an element by n steps. Pass an index over the length, then returns Nothing.

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> selectBy -1
    == Just (fromLists [ 1, 2 ] 3 [ 4, 5, 6 ])

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> selectBy 2
    == Just (fromLists [ 1, 2, 3, 4, 5 ] 6 [])

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> selectBy 3
    == Nothing

selectWhileLoopBy : Basics.Int -> SelectList a -> SelectList a

Select an element by n steps while loop. Pass an index over the length, then loop.

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> selectWhileLoopBy 3
    == fromLists [] 1 [ 2, 3, 4, 5, 6 ]

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> selectWhileLoopBy -5
    == fromLists [ 1, 2, 3, 4 ] 5 [ 6 ]

selectHead : SelectList a -> SelectList a

Select a head element.

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> selectHead
    == fromLists [] 1 [ 2, 3, 4, 5, 6 ]

selectLast : SelectList a -> SelectList a

Select a last element.

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    |> selectLast
    == fromLists [ 1, 2, 3, 4, 5 ] 6 []

Multi

selectAll : SelectList a -> List (SelectList a)

List of all SelectList.

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    == [ fromLists [] 1 [ 2, 3, 4, 5, 6 ]
       , fromLists [ 1 ] 2 [ 3, 4, 5, 6 ]
       , fromLists [ 1, 2 ] 3 [ 4, 5, 6 ]
       , fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
       , fromLists [ 1, 2, 3, 4 ] 5 [ 6 ]
       , fromLists [ 1, 2, 3, 4, 5 ] 6 []
       ]

selectAllBefore : SelectList a -> List (SelectList a)

List of all SelectList before the selected.

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    == [ fromLists [] 1 [ 2, 3, 4, 5, 6 ]
       , fromLists [ 1 ] 2 [ 3, 4, 5, 6 ]
       , fromLists [ 1, 2 ] 3 [ 4, 5, 6 ]
       , fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
       ]

selectAllAfter : SelectList a -> List (SelectList a)

List of all SelectList after the selected.

fromLists [ 1, 2, 3 ] 4 [ 5, 6 ]
    == [ fromLists [ 1, 2, 3, 4 ] 5 [ 6 ]
       , fromLists [ 1, 2, 3, 4, 5 ] 6 []
       ]