├── .gitignore ├── benchmarks ├── .gitignore ├── elm-package.json └── Benchmarks.elm ├── tests ├── .gitignore ├── TestRunner.elm ├── elm-package.json ├── Tests.elm └── ArchitectureTests.elm ├── demo ├── .gitignore ├── Makefile ├── static │ ├── index.html │ └── selectize.scss ├── elm.json └── src │ └── Demo.elm ├── elm.json ├── README.md ├── .travis.yml ├── LICENSE └── src ├── Selectize.elm └── Selectize └── Selectize.elm /.gitignore: -------------------------------------------------------------------------------- 1 | elm-stuff/ 2 | .* 3 | -------------------------------------------------------------------------------- /benchmarks/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | index.html 2 | -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | gh-pages/ 2 | elm-stuff/ 3 | -------------------------------------------------------------------------------- /tests/TestRunner.elm: -------------------------------------------------------------------------------- 1 | module TestRunner exposing (main) 2 | 3 | import Test exposing (concat) 4 | import Test.Runner.Html 5 | import Tests 6 | import ArchitectureTests 7 | 8 | 9 | main : Test.Runner.Html.TestProgram 10 | main = 11 | [ Tests.suite 12 | , ArchitectureTests.suite 13 | ] 14 | |> concat 15 | |> Test.Runner.Html.run 16 | -------------------------------------------------------------------------------- /demo/Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | mkdir -p ./gh-pages 3 | sass static/selectize.scss > gh-pages/selectize.css 4 | cp static/index.html gh-pages/index.html 5 | elm make --output=gh-pages/elm.js --optimize src/Demo.elm 6 | uglifyjs gh-pages/elm.js \ 7 | --compress 'pure_funcs="F2,F3,F4,F5,F6,F7,F8,F9,A2,A3,A4,A5,A6,A7,A8,A9",pure_getters,keep_fargs=false,unsafe_comps,unsafe' \ 8 | | uglifyjs --mangle --output=gh-pages/elm.min.js 9 | mv gh-pages/elm.min.js gh-pages/elm.js 10 | -------------------------------------------------------------------------------- /demo/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | elm-dropdown demo 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "package", 3 | "name": "kirchner/elm-selectize", 4 | "summary": "Selectize-like dropdown menu with autocompletion", 5 | "license": "Apache-2.0", 6 | "version": "2.0.7", 7 | "exposed-modules": [ 8 | "Selectize" 9 | ], 10 | "elm-version": "0.19.0 <= v < 0.20.0", 11 | "dependencies": { 12 | "elm/browser": "1.0.0 <= v < 2.0.0", 13 | "elm/core": "1.0.0 <= v < 2.0.0", 14 | "elm/html": "1.0.0 <= v < 2.0.0", 15 | "elm/json": "1.0.0 <= v < 2.0.0" 16 | }, 17 | "test-dependencies": {} 18 | } -------------------------------------------------------------------------------- /demo/elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "source-directories": [ 4 | "src", 5 | "../src" 6 | ], 7 | "elm-version": "0.19.0", 8 | "dependencies": { 9 | "direct": { 10 | "elm/browser": "1.0.0", 11 | "elm/core": "1.0.0", 12 | "elm/html": "1.0.0", 13 | "elm/json": "1.0.0" 14 | }, 15 | "indirect": { 16 | "elm/time": "1.0.0", 17 | "elm/url": "1.0.0", 18 | "elm/virtual-dom": "1.0.0" 19 | } 20 | }, 21 | "test-dependencies": { 22 | "direct": {}, 23 | "indirect": {} 24 | } 25 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Filterable Dropdown Menu [![Build Status](https://travis-ci.org/kirchner/elm-selectize.svg?branch=master)](https://travis-ci.org/kirchner/elm-selectize) 2 | 3 | This is a customizable dropdown menu with the following features: 4 | 5 | * filtering the displayed entries with a textfield 6 | * keyboard selection using up/down arrow keys and enter/escape 7 | * auto scrolling if the menu is styled with a maximum height and `overflow-y: 8 | scroll` 9 | * you can insert non-selectable dividers between entries 10 | * the styling and rendering of the entries can be fully customized 11 | 12 | If you like you can check out the 13 | [demo](https://kirchner.github.io/elm-selectize). 14 | -------------------------------------------------------------------------------- /benchmarks/elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "summary": "helpful summary of your project, less than 80 characters", 4 | "repository": "https://github.com/user/project.git", 5 | "license": "BSD3", 6 | "source-directories": [ 7 | ".", 8 | "../src" 9 | ], 10 | "exposed-modules": [], 11 | "dependencies": { 12 | "BrianHicks/elm-benchmark": "1.0.2 <= v < 2.0.0", 13 | "NoRedInk/elm-decode-pipeline": "3.0.0 <= v < 4.0.0", 14 | "debois/elm-dom": "1.2.3 <= v < 2.0.0", 15 | "elm-community/list-extra": "6.1.0 <= v < 7.0.0", 16 | "elm-lang/core": "5.1.1 <= v < 6.0.0", 17 | "elm-lang/dom": "1.1.1 <= v < 2.0.0", 18 | "elm-lang/html": "2.0.0 <= v < 3.0.0", 19 | "elm-lang/keyboard": "1.0.1 <= v < 2.0.0", 20 | "ohanhi/keyboard-extra": "3.0.4 <= v < 4.0.0" 21 | }, 22 | "elm-version": "0.18.0 <= v < 0.19.0" 23 | } 24 | -------------------------------------------------------------------------------- /tests/elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "summary": "Test Suites", 4 | "repository": "https://github.com/user/project.git", 5 | "license": "BSD3", 6 | "source-directories": [ 7 | "../src", 8 | "../demo", 9 | "." 10 | ], 11 | "exposed-modules": [], 12 | "dependencies": { 13 | "elm-community/html-test-runner": "1.0.6 <= v < 2.0.0", 14 | "NoRedInk/elm-decode-pipeline": "3.0.0 <= v < 4.0.0", 15 | "debois/elm-dom": "1.2.3 <= v < 2.0.0", 16 | "eeue56/elm-html-test": "4.1.0 <= v < 5.0.0", 17 | "elm-community/elm-test": "4.0.0 <= v < 5.0.0", 18 | "elm-community/list-extra": "6.1.0 <= v < 7.0.0", 19 | "elm-lang/core": "5.1.1 <= v < 6.0.0", 20 | "elm-lang/dom": "1.1.1 <= v < 2.0.0", 21 | "elm-lang/html": "2.0.0 <= v < 3.0.0", 22 | "elm-lang/keyboard": "1.0.1 <= v < 2.0.0", 23 | "ohanhi/keyboard-extra": "3.0.4 <= v < 4.0.0", 24 | "Janiczek/elm-architecture-test": "1.0.9 <= v < 2.0.0" 25 | }, 26 | "elm-version": "0.18.0 <= v < 0.19.0" 27 | } 28 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | node_js: node 5 | 6 | cache: 7 | directories: 8 | - elm-stuff/build-artifacts 9 | - elm-stuff/packages 10 | - sysconfcpus 11 | os: 12 | - linux 13 | 14 | env: ELM_VERSION=0.18.0 15 | 16 | before_install: 17 | - echo -e "Host github.com\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config 18 | 19 | install: 20 | - node --version 21 | - npm --version 22 | - npm install -g elm@$ELM_VERSION elm-test 23 | - git clone https://github.com/NoRedInk/elm-ops-tooling 24 | - elm-ops-tooling/with_retry.rb elm package install --yes 25 | # Faster compile on Travis. 26 | - | 27 | if [ ! -d sysconfcpus/bin ]; 28 | then 29 | git clone https://github.com/obmarg/libsysconfcpus.git; 30 | cd libsysconfcpus; 31 | ./configure --prefix=$TRAVIS_BUILD_DIR/sysconfcpus; 32 | make && make install; 33 | cd ..; 34 | fi 35 | 36 | before_script: 37 | - cd tests && $TRAVIS_BUILD_DIR/sysconfcpus/bin/sysconfcpus -n 2 elm-make --yes Tests.elm && cd .. 38 | 39 | script: 40 | - $TRAVIS_BUILD_DIR/sysconfcpus/bin/sysconfcpus -n 2 elm-test 41 | -------------------------------------------------------------------------------- /demo/static/selectize.scss: -------------------------------------------------------------------------------- 1 | h3 { 2 | font-family: "Source Sans Pro", "Helvetica Neue", Arial, sans-serif; 3 | font-weight: normal; 4 | } 5 | 6 | .container { 7 | display: flex; 8 | padding: 0.5rem; 9 | } 10 | 11 | .caption { 12 | padding: 6px 8px; 13 | font-family: "Source Sans Pro", "Helvetica Neue", Arial, sans-serif; 14 | font-weight: normal; 15 | } 16 | 17 | .selectize { 18 | &__container { 19 | position: relative; 20 | height: 36px; 21 | } 22 | 23 | &__button { 24 | border: 1px solid #d0d0d0; 25 | padding: 6px 8px; 26 | padding-right: 62px; 27 | width: 100%; 28 | height: 35px; 29 | z-index: 1; 30 | cursor: pointer; 31 | white-space: nowrap; 32 | overflow-x: hidden; 33 | text-overflow: ellipsis; 34 | 35 | color: #303030; 36 | background-color: #ffffff; 37 | 38 | text-align: left; 39 | 40 | font-family: "Source Sans Pro","Helvetica Neue",Arial,sans-serif; 41 | font-size: 15px; 42 | line-height: 21px; 43 | -webkit-font-smoothing: inherit; 44 | 45 | -webkit-box-sizing: border-box; 46 | -moz-box-sizing: border-box; 47 | box-sizing: border-box; 48 | 49 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.1); 50 | box-shadow: inset 0 1px 1px rgba(0,0,0,0.1); 51 | 52 | -webkit-border-radius: 3px; 53 | -moz-border-radius: 3px; 54 | border-radius: 3px; 55 | 56 | &:focus { 57 | outline: none !important; 58 | border-color: #66afe9; 59 | } 60 | 61 | &--light { 62 | color: #808080; 63 | cursor: auto; 64 | } 65 | } 66 | 67 | &__textfield { 68 | border: 1px solid #d0d0d0; 69 | padding: 6px 8px; 70 | padding-right: 62px; 71 | width: 100%; 72 | height: 35px; 73 | z-index: 1; 74 | cursor: pointer; 75 | text-overflow: ellipsis; 76 | overflow-x: hidden; 77 | white-space: nowrap; 78 | 79 | color: #303030; 80 | 81 | font-family: "Source Sans Pro","Helvetica Neue",Arial,sans-serif; 82 | font-size: 15px; 83 | line-height: 21px; 84 | -webkit-font-smoothing: inherit; 85 | 86 | -webkit-box-sizing: border-box; 87 | -moz-box-sizing: border-box; 88 | box-sizing: border-box; 89 | 90 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.1); 91 | box-shadow: inset 0 1px 1px rgba(0,0,0,0.1); 92 | 93 | -webkit-border-radius: 3px; 94 | -moz-border-radius: 3px; 95 | border-radius: 3px; 96 | 97 | &:focus { 98 | outline: none !important; 99 | border-color: #66afe9; 100 | } 101 | 102 | &--no-selection { 103 | &::-webkit-input-placeholder { 104 | color: #808080; 105 | } 106 | 107 | &::-moz-placeholder { 108 | color: #808080; 109 | } 110 | 111 | &:-ms-input-placeholder { 112 | color: #808080; 113 | } 114 | 115 | &:-moz-placeholder { 116 | color: #808080; 117 | } 118 | } 119 | 120 | &--selection { 121 | &::-webkit-input-placeholder { 122 | color: #303030; 123 | } 124 | 125 | &::-moz-placeholder { 126 | color: #303030; 127 | } 128 | 129 | &:-ms-input-placeholder { 130 | color: #303030; 131 | } 132 | 133 | &:-moz-placeholder { 134 | color: #303030; 135 | } 136 | } 137 | 138 | &--menu-open { 139 | cursor: auto; 140 | } 141 | } 142 | 143 | &__menu-toggle { 144 | z-index: 1000; 145 | color: #808080; 146 | display: flex; 147 | flex-flow: column; 148 | justify-content: center; 149 | height: 35px; 150 | width: 1.8rem; 151 | cursor: pointer; 152 | 153 | -webkit-touch-callout: none; 154 | -webkit-user-select: none; 155 | -moz-user-select: none; 156 | -ms-user-select: none; 157 | user-select: none; 158 | 159 | &:hover { 160 | color: #555555; 161 | } 162 | } 163 | 164 | &__menu { 165 | z-index: 2000; 166 | width: 100%; 167 | margin-top: 5px; 168 | background: white; 169 | color: black; 170 | max-height: 20rem; 171 | overflow: scroll; 172 | padding: 0; 173 | margin: 2px 0 0 0; 174 | color: #000; 175 | background: #fff; 176 | 177 | border: 1px solid #ccc; 178 | border: 1px solid rgba(0, 0, 0, 0.15); 179 | -webkit-border-radius: 2px; 180 | -moz-border-radius: 2px; 181 | border-radius: 2px; 182 | 183 | -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); 184 | box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); 185 | 186 | -webkit-box-sizing: border-box; 187 | -moz-box-sizing: border-box; 188 | box-sizing: border-box; 189 | } 190 | 191 | 192 | &__list { 193 | list-style: none; 194 | padding: 0; 195 | margin: auto; 196 | overflow-y: auto; 197 | } 198 | 199 | 200 | &__item { 201 | display: block; 202 | padding: 9px 8px; 203 | cursor: pointer; 204 | font-size: 16px; 205 | line-height: 24px; 206 | color: #555555; 207 | font-family: "Source Sans Pro", "Helvetica Neue", Arial, sans-serif; 208 | font-weight: normal; 209 | 210 | &--mouse-selected { 211 | background-color: #fafafa; 212 | color: #495c68; 213 | } 214 | 215 | &--key-selected { 216 | background-color: #f5fafd; 217 | color: #495c68; 218 | } 219 | } 220 | 221 | &__divider { 222 | display: block; 223 | padding-top: 8px; 224 | padding-bottom: 3px; 225 | padding-left: 10px; 226 | padding-right: 8px; 227 | border-bottom: 1px solid #ccc; 228 | font-size: 12px; 229 | line-height: 1; 230 | color: #555555; 231 | font-family: "Source Sans Pro", "Helvetica Neue", Arial, sans-serif; 232 | font-weight: normal; 233 | white-space: nowrap; 234 | overflow-x: hidden; 235 | text-overflow: ellipsis; 236 | } 237 | 238 | &__icon { 239 | color: #808080; 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /tests/Tests.elm: -------------------------------------------------------------------------------- 1 | module Tests exposing (..) 2 | 3 | import Expect 4 | import Fuzz exposing (Fuzzer) 5 | import Random 6 | import Selectize.Selectize exposing (..) 7 | import Test exposing (..) 8 | 9 | 10 | suite : Test 11 | suite = 12 | describe "unit tests" 13 | [ testFirst 14 | , testNext 15 | , testPrevious 16 | , testTopAndHeight 17 | ] 18 | 19 | 20 | testFirst : Test 21 | testFirst = 22 | describe "first" 23 | [ fuzz (Fuzz.intRange 0 42) "first entries are dividers" <| 24 | \count -> 25 | let 26 | entries = 27 | List.repeat count (LDivider "divider") 28 | ++ (trees1 |> List.map entry) 29 | 30 | entry tree = 31 | LEntry tree tree 32 | 33 | heights = 34 | List.repeat (List.length entries) 30 35 | in 36 | fromList entries heights 37 | |> Maybe.map currentEntry 38 | |> Expect.equal (List.head trees1) 39 | ] 40 | 41 | 42 | testNext : Test 43 | testNext = 44 | describe "next" 45 | [ fuzz (Fuzz.intRange 0 42) "select the next non divider" <| 46 | \count -> 47 | let 48 | entries = 49 | (trees1 |> List.map entry) 50 | ++ [ entry current ] 51 | ++ List.repeat count (LDivider "divider") 52 | ++ [ entry next ] 53 | ++ (trees2 |> List.map entry) 54 | 55 | entry tree = 56 | LEntry tree tree 57 | 58 | heights = 59 | List.repeat (List.length entries) 30 60 | 61 | current = 62 | treeA 63 | 64 | next = 65 | treeB 66 | in 67 | fromList entries heights 68 | |> Maybe.map (moveForwardTo current) 69 | |> Maybe.map zipNext 70 | |> Maybe.map currentEntry 71 | |> Expect.equal (Just next) 72 | ] 73 | 74 | 75 | testPrevious : Test 76 | testPrevious = 77 | describe "previous" 78 | [ fuzz (Fuzz.intRange 0 42) "select the previous non divider" <| 79 | \count -> 80 | let 81 | entries = 82 | (trees1 |> List.map entry) 83 | ++ [ entry previous ] 84 | ++ List.repeat count (LDivider "divider") 85 | ++ [ entry current ] 86 | ++ (trees2 |> List.map entry) 87 | 88 | entry tree = 89 | LEntry tree tree 90 | 91 | heights = 92 | List.repeat (List.length entries) 30 93 | 94 | current = 95 | treeA 96 | 97 | previous = 98 | treeB 99 | in 100 | fromList entries heights 101 | |> Maybe.map (moveForwardTo current) 102 | |> Maybe.map zipPrevious 103 | |> Maybe.map currentEntry 104 | |> Expect.equal (Just previous) 105 | ] 106 | 107 | 108 | testTopAndHeight : Test 109 | testTopAndHeight = 110 | describe "topAndHeight" 111 | [ fuzz (listCount (Fuzz.intRange 0 42) (List.length trees1)) 112 | "satisfies a priori bounds" 113 | <| 114 | \entryHeights -> 115 | let 116 | entries = 117 | trees1 |> List.map (\tree -> LEntry tree tree) 118 | 119 | sum = 120 | List.foldl (+) 0 entryHeights 121 | 122 | maximum = 123 | List.maximum entryHeights 124 | |> Maybe.withDefault 0 125 | 126 | minimum = 127 | List.minimum entryHeights 128 | |> Maybe.withDefault 0 129 | in 130 | case fromList entries entryHeights of 131 | Just zipList -> 132 | zipList 133 | |> Expect.all 134 | [ .currentTop 135 | >> Expect.all 136 | [ Expect.atLeast 0 137 | , Expect.atMost sum 138 | ] 139 | , zipCurrentHeight 140 | >> Expect.all 141 | [ Expect.atLeast 0 142 | , Expect.atMost maximum 143 | , Expect.atLeast minimum 144 | ] 145 | ] 146 | 147 | Nothing -> 148 | Expect.fail "could not create zipList" 149 | ] 150 | 151 | 152 | 153 | {- fuzzer -} 154 | 155 | 156 | listCount : Fuzzer a -> Int -> Fuzzer (List a) 157 | listCount fuzzer count = 158 | if count > 1 then 159 | Fuzz.map2 (\a rest -> a :: rest) 160 | fuzzer 161 | (listCount fuzzer (count - 1)) 162 | else 163 | fuzzer |> Fuzz.map (\a -> [ a ]) 164 | 165 | 166 | 167 | {- constants -} 168 | 169 | 170 | treeA : String 171 | treeA = 172 | "not really a tree" 173 | 174 | 175 | treeB : String 176 | treeB = 177 | "not really a tree, either" 178 | 179 | 180 | trees1 : List String 181 | trees1 = 182 | [ "Abelia x grandiflora" 183 | , "Abienus festuschristus" 184 | , "Abies alba" 185 | , "Acacia dealbata" 186 | , "Acacia retinodes" 187 | , "Acer buergerianum" 188 | ] 189 | 190 | 191 | trees2 : List String 192 | trees2 = 193 | [ "Carya cordiformis" 194 | , "Cascabela thevetia" 195 | , "Cassia siberiana" 196 | , "Castanea sativa" 197 | , "Casuarina stricta" 198 | , "Catalpa x erubescens" 199 | , "Ceanothus x delilianus" 200 | , "Cedrus libani" 201 | , "Ceiba speciosa" 202 | , "Celtis occidentalis" 203 | , "Cephalanthus occidentalis" 204 | ] 205 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/Selectize.elm: -------------------------------------------------------------------------------- 1 | module Selectize 2 | exposing 3 | ( Entry 4 | , HtmlDetails 5 | , Input 6 | , Msg 7 | , State 8 | , ViewConfig 9 | , autocomplete 10 | , closed 11 | , divider 12 | , entry 13 | , simple 14 | , update 15 | , view 16 | , viewConfig 17 | ) 18 | 19 | {-| This is a dropdown menu whose entries can be filtered. You can 20 | select entries using the mouse or with the keyboard (arrow up/down and 21 | enter). 22 | 23 | The dropdown menu manages the keyboard and mouse focus, as well as the 24 | open/closed state itself. The (unfiltered) list of possible entries and 25 | the eventually selected entry have to live in the model of the 26 | actual application. 27 | 28 | If you want to use it, your model should look something like this 29 | 30 | type alias Model = 31 | { selection : Maybe Tree 32 | , menu : Selectize.State Tree 33 | } 34 | 35 | type alias Tree = 36 | { name : String 37 | , latinName : String 38 | } 39 | 40 | The state of the dropdown menu is instanciated via 41 | 42 | menu = 43 | Selectize.closed "unique-menu-id" 44 | (\tree -> tree.name ++ " - " ++ tree.latinName) 45 | (trees |> List.map Selectize.entry) 46 | 47 | with 48 | 49 | trees : List Tree 50 | 51 | And you have to hook it up in your update function like so 52 | 53 | type Msg 54 | = MenuMsg (Selectize.Msg Tree) 55 | | SelectTree (Maybe Tree) 56 | 57 | update : Msg -> Model -> ( Model, Cmd Msg ) 58 | update msg model = 59 | case msg of 60 | MenuMsg selectizeMsg -> 61 | let 62 | ( newMenu, menuCmd, maybeMsg ) = 63 | Selectize.update SelectTree 64 | model.selection 65 | model.menu 66 | selectizeMsg 67 | 68 | newModel = 69 | { model | menu = newMenu } 70 | 71 | cmd = 72 | menuCmd |> Cmd.map MenuMsg 73 | in 74 | case maybeMsg of 75 | Just nextMsg -> 76 | update nextMsg newModel 77 | |> andDo cmd 78 | 79 | Nothing -> 80 | ( newModel, cmd ) 81 | 82 | SelectTree newSelection -> 83 | ( { model | selection = newSelection }, Cmd.none ) 84 | 85 | andDo : Cmd msg -> ( model, Cmd msg ) -> ( model, Cmd msg ) 86 | andDo cmd ( model, cmds ) = 87 | ( model 88 | , Cmd.batch [ cmd, cmds ] 89 | ) 90 | 91 | Finally, the menu can be rendered like this 92 | 93 | view : Model -> Html Msg 94 | view model = 95 | Html.div [] 96 | [ Selectize.view viewConfig 97 | model.selection 98 | model.menu 99 | |> Html.map MenuMsg 100 | ] 101 | 102 | with the view configuration given by 103 | 104 | viewConfig : Selectize.ViewConfig Tree 105 | viewConfig = 106 | Selectize.viewConfig 107 | { container = 108 | [ Attributes.class "selectize__container" ] 109 | , menu = 110 | [ Attributes.class "selectize__menu" ] 111 | , ul = 112 | [ Attributes.class "selectize__list" ] 113 | , entry = 114 | \tree mouseFocused keyboardFocused -> 115 | { attributes = 116 | [ Attributes.class "selectize__item" 117 | , Attributes.classList 118 | [ ( "selectize__item--mouse-selected" 119 | , mouseFocused 120 | ) 121 | , ( "selectize__item--key-selected" 122 | , keyboardFocused 123 | ) 124 | ] 125 | ] 126 | , children = 127 | [ Html.text 128 | (tree.name ++ " - " ++ tree.latinName) 129 | ] 130 | } 131 | , divider = 132 | \title -> 133 | { attributes = 134 | [ Attributes.class "selectize__divider" ] 135 | , children = 136 | [ Html.text title ] 137 | } 138 | , input = styledInput 139 | } 140 | 141 | and an input given by, for example, 142 | 143 | styledInput : Selectize.Input Tree 144 | styledInput = 145 | Selectize.autocomplete <| 146 | { attrs = 147 | \sthSelected open -> 148 | [ Attributes.class "selectize__textfield" 149 | , Attributes.classList 150 | [ ( "selectize__textfield--selection", sthSelected ) 151 | , ( "selectize__textfield--no-selection", not sthSelected ) 152 | , ( "selectize__textfield--menu-open", open ) 153 | ] 154 | ] 155 | , toggleButton = 156 | Just <| 157 | \open -> 158 | Html.i 159 | [ Attributes.class "material-icons" 160 | , Attributes.class "selectize__icon" 161 | ] 162 | [ if open then 163 | Html.text "arrow_drop_up" 164 | else 165 | Html.text "arrow_drop_down" 166 | ] 167 | , clearButton = Nothing 168 | , placeholder = "Select a Tree" 169 | } 170 | 171 | 172 | # Types 173 | 174 | @docs State, closed, Entry, entry, divider 175 | 176 | 177 | # Update 178 | 179 | @docs Msg, update 180 | 181 | 182 | # View 183 | 184 | @docs view, ViewConfig, viewConfig, HtmlDetails, Input, simple, autocomplete 185 | 186 | -} 187 | 188 | {- 189 | 190 | Copyright 2018 Fabian Kirchner 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | 204 | -} 205 | 206 | import Html exposing (Html) 207 | import Html.Lazy as Lazy 208 | import Selectize.Selectize as Internal 209 | 210 | 211 | {- model -} 212 | 213 | 214 | {-| The internal state of the dropdown menu. This lives in your model. 215 | -} 216 | type alias State a = 217 | Internal.State a 218 | 219 | 220 | {-| Use this function to initialize your dropdown menu, for example by 221 | 222 | menu = 223 | Selectize.closed "unique-menu-id" 224 | entryToLabel 225 | entries 226 | 227 | It will have the provided entries and be closed. The provided id should 228 | be unique. If for some reason the entries change, just reinstantiate 229 | your dropdown state with this function. 230 | 231 | -} 232 | closed : 233 | String 234 | -> (a -> String) 235 | -> List (Entry a) 236 | -> State a 237 | closed id toLabel entries = 238 | Internal.closed id toLabel entries 239 | 240 | 241 | {-| Each entry of the menu has to be wrapped in this type. We need this, 242 | as an entry can either be selectable (and therefore also focusable) or 243 | not. You can construct these using `entry` and `divider`. 244 | -} 245 | type alias Entry a = 246 | Internal.Entry a 247 | 248 | 249 | {-| Create a selectable `Entry a`. 250 | -} 251 | entry : a -> Entry a 252 | entry a = 253 | Internal.entry a 254 | 255 | 256 | {-| Create a divider, which can neither be selected nor focused. It 257 | is therefore skipped while traversing the list via up/down keys. 258 | -} 259 | divider : String -> Entry a 260 | divider title = 261 | Internal.divider title 262 | 263 | 264 | 265 | {- configuration -} 266 | 267 | 268 | {-| The configuration for `Selectize.view`. 269 | -} 270 | type alias ViewConfig a = 271 | Internal.ViewConfig a 272 | 273 | 274 | {-| Create the view configuration, for example 275 | 276 | viewConfig : Selectize.ViewConfig String 277 | viewConfig = 278 | Selectize.viewConfig 279 | { container = [ ... ] 280 | , menu = [ ... ] 281 | , ul = [ ... ] 282 | , entry = 283 | \entry mouseFocused keyboardFocused -> 284 | { attributes = ... 285 | , children = ... 286 | } 287 | , divider = 288 | \title -> 289 | { attributes = ... 290 | , children = ... 291 | } 292 | , input = someInput 293 | } 294 | 295 | - `container`, `menu`, `ul`, `entry` and `divider` can be 296 | used to style the different parts of the dropdown view, c.f. the 297 | modul documentation for an example. 298 | - with `input` you can choose if you want autocompletion or just 299 | a simple dropdown menu, you can choose for example 300 | `Selectize.simple` or `Selectize.autocomplete`. 301 | 302 | -} 303 | viewConfig : 304 | { container : List (Html.Attribute Never) 305 | , menu : List (Html.Attribute Never) 306 | , ul : List (Html.Attribute Never) 307 | , entry : a -> Bool -> Bool -> HtmlDetails Never 308 | , divider : String -> HtmlDetails Never 309 | , input : Input a 310 | } 311 | -> ViewConfig a 312 | viewConfig config = 313 | { container = config.container 314 | , menu = config.menu 315 | , ul = config.ul 316 | , entry = config.entry 317 | , divider = config.divider 318 | , input = config.input 319 | } 320 | 321 | 322 | {-| `entry` and `divider` should return this. 323 | -} 324 | type alias HtmlDetails msg = 325 | { attributes : List (Html.Attribute msg) 326 | , children : List (Html msg) 327 | } 328 | 329 | 330 | 331 | {- update -} 332 | 333 | 334 | {-| The dropdown menu produces these messages. 335 | -} 336 | type alias Msg a = 337 | Internal.Msg a 338 | 339 | 340 | {-| The dropdown's update function. Take a look at the beginning of this 341 | module documentation to see what boilerplate is needed in your main 342 | update. 343 | -} 344 | update : 345 | (Maybe a -> msg) 346 | -> Maybe a 347 | -> State a 348 | -> Msg a 349 | -> ( State a, Cmd (Msg a), Maybe msg ) 350 | update select selection state msg = 351 | Internal.update select selection state msg 352 | 353 | 354 | 355 | {- view -} 356 | 357 | 358 | {-| The dropdown's view function. You have to provide the current 359 | selection (along with the configuration and the its actual state). 360 | -} 361 | view : ViewConfig a -> Maybe a -> State a -> Html (Msg a) 362 | view = 363 | Lazy.lazy3 Internal.view 364 | 365 | 366 | {-| You have to choose an `Input` in your view configuration. This 367 | decides if you have a simple dropdown or an autocompletion version. 368 | -} 369 | type alias Input a = 370 | Internal.Input a 371 | 372 | 373 | {-| An input for displaying a simple dropdown. 374 | 375 | - `attrs = \sthSelected open -> [ ... ]` is used to style the actual 376 | button 377 | - you can style optional buttons via `toggleButton` and `clearButton`, 378 | for example `toggleButton = Just (\open -> Html.div [ ... ] [ ... ])` 379 | - tell us the `placeholder` if the selection is empty 380 | 381 | -} 382 | simple : 383 | { attrs : Bool -> Bool -> List (Html.Attribute Never) 384 | , toggleButton : Maybe (Bool -> Html Never) 385 | , clearButton : Maybe (Html Never) 386 | , placeholder : String 387 | } 388 | -> Input a 389 | simple config = 390 | Internal.simple config 391 | 392 | 393 | {-| An input for an autocompletion dropdown. 394 | 395 | - `attrs = \sthSelected open -> [ ... ]` is used to style the actual 396 | textfield (You probably need to overwrite the placeholder styling, 397 | see the source code of the demo for an example stylesheet.) 398 | - you can style optional buttons via `toggleButton` and `clearButton`, 399 | for example `toggleButton = Just (\open -> Html.div [ ... ] [ ... ])` 400 | - tell us the `placeholder` if the selection is empty 401 | 402 | -} 403 | autocomplete : 404 | { attrs : Bool -> Bool -> List (Html.Attribute Never) 405 | , toggleButton : Maybe (Bool -> Html Never) 406 | , clearButton : Maybe (Html Never) 407 | , placeholder : String 408 | } 409 | -> Input a 410 | autocomplete config = 411 | Internal.autocomplete config 412 | -------------------------------------------------------------------------------- /demo/src/Demo.elm: -------------------------------------------------------------------------------- 1 | module Demo 2 | exposing 3 | ( Model 4 | , Msg(..) 5 | , init 6 | , main 7 | , update 8 | ) 9 | 10 | import Browser exposing (Document) 11 | import Html exposing (Html) 12 | import Html.Attributes as Attributes 13 | import Selectize 14 | 15 | 16 | main : Program {} Model Msg 17 | main = 18 | Browser.document 19 | { init = init 20 | , view = view 21 | , update = update 22 | , subscriptions = subscriptions 23 | } 24 | 25 | 26 | 27 | ---- MODEL 28 | 29 | 30 | type alias Model = 31 | { textfieldSelection : Maybe String 32 | , textfieldMenu : Selectize.State String 33 | , buttonSelection : Maybe String 34 | , buttonMenu : Selectize.State String 35 | } 36 | 37 | 38 | init : {} -> ( Model, Cmd Msg ) 39 | init _ = 40 | ( { textfieldSelection = Nothing 41 | , textfieldMenu = 42 | Selectize.closed 43 | "textfield-menu" 44 | identity 45 | licenses 46 | , buttonSelection = Nothing 47 | , buttonMenu = 48 | Selectize.closed 49 | "button-menu" 50 | identity 51 | licenses 52 | } 53 | , Cmd.none 54 | ) 55 | 56 | 57 | 58 | ---- UPDATE 59 | 60 | 61 | type Msg 62 | = TextfieldMenuMsg (Selectize.Msg String) 63 | | ButtonMenuMsg (Selectize.Msg String) 64 | | SelectTextfieldLicense (Maybe String) 65 | | SelectButtonLicense (Maybe String) 66 | 67 | 68 | update : Msg -> Model -> ( Model, Cmd Msg ) 69 | update msg model = 70 | case msg of 71 | TextfieldMenuMsg selectizeMsg -> 72 | let 73 | ( newMenu, menuCmd, maybeMsg ) = 74 | Selectize.update SelectTextfieldLicense 75 | model.textfieldSelection 76 | model.textfieldMenu 77 | selectizeMsg 78 | 79 | newModel = 80 | { model | textfieldMenu = newMenu } 81 | 82 | cmd = 83 | menuCmd |> Cmd.map TextfieldMenuMsg 84 | in 85 | case maybeMsg of 86 | Just nextMsg -> 87 | update nextMsg newModel 88 | |> andDo cmd 89 | 90 | Nothing -> 91 | ( newModel, cmd ) 92 | 93 | ButtonMenuMsg selectizeMsg -> 94 | let 95 | ( newMenu, menuCmd, maybeMsg ) = 96 | Selectize.update SelectButtonLicense 97 | model.buttonSelection 98 | model.buttonMenu 99 | selectizeMsg 100 | 101 | newModel = 102 | { model | buttonMenu = newMenu } 103 | 104 | cmd = 105 | menuCmd |> Cmd.map ButtonMenuMsg 106 | in 107 | case maybeMsg of 108 | Just nextMsg -> 109 | update nextMsg newModel 110 | |> andDo cmd 111 | 112 | Nothing -> 113 | ( newModel, cmd ) 114 | 115 | SelectTextfieldLicense newSelection -> 116 | ( { model | textfieldSelection = newSelection }, Cmd.none ) 117 | 118 | SelectButtonLicense newSelection -> 119 | ( { model | buttonSelection = newSelection }, Cmd.none ) 120 | 121 | 122 | andDo : Cmd msg -> ( model, Cmd msg ) -> ( model, Cmd msg ) 123 | andDo cmd ( model, cmds ) = 124 | ( model 125 | , Cmd.batch [ cmd, cmds ] 126 | ) 127 | 128 | 129 | 130 | ---- SUBSCRIPTIONS 131 | 132 | 133 | subscriptions : Model -> Sub Msg 134 | subscriptions model = 135 | Sub.none 136 | 137 | 138 | 139 | ---- VIEW 140 | 141 | 142 | view : Model -> Document Msg 143 | view model = 144 | { title = "elm-selectize demo" 145 | , body = 146 | [ Html.h3 [] 147 | [ Html.text "Dropdown Menus" ] 148 | , Html.div 149 | [ Attributes.style "display" "flex" 150 | , Attributes.style "flex-flow" "column" 151 | ] 152 | [ Html.div 153 | [ Attributes.class "container" ] 154 | [ Html.div 155 | [ Attributes.class "caption" ] 156 | [ Html.text "with autocompletion: " ] 157 | , Html.div 158 | [ Attributes.style "width" "30rem" ] 159 | [ Selectize.view 160 | viewConfigTextfield 161 | model.textfieldSelection 162 | model.textfieldMenu 163 | |> Html.map TextfieldMenuMsg 164 | ] 165 | ] 166 | , Html.div 167 | [ Attributes.class "container" ] 168 | [ Html.div 169 | [ Attributes.class "caption" ] 170 | [ Html.text "without autocompletion: " ] 171 | , Html.div 172 | [ Attributes.style "width" "30rem" ] 173 | [ Selectize.view 174 | viewConfigButton 175 | model.buttonSelection 176 | model.buttonMenu 177 | |> Html.map ButtonMenuMsg 178 | ] 179 | ] 180 | ] 181 | ] 182 | } 183 | 184 | 185 | 186 | ---- CONFIGURATION 187 | 188 | 189 | viewConfigTextfield : Selectize.ViewConfig String 190 | viewConfigTextfield = 191 | viewConfig textfieldSelector 192 | 193 | 194 | viewConfigButton : Selectize.ViewConfig String 195 | viewConfigButton = 196 | viewConfig buttonSelector 197 | 198 | 199 | viewConfig : Selectize.Input String -> Selectize.ViewConfig String 200 | viewConfig selector = 201 | Selectize.viewConfig 202 | { container = [] 203 | , menu = 204 | [ Attributes.class "selectize__menu" ] 205 | , ul = 206 | [ Attributes.class "selectize__list" ] 207 | , entry = 208 | \tree mouseFocused keyboardFocused -> 209 | { attributes = 210 | [ Attributes.class "selectize__item" 211 | , Attributes.classList 212 | [ ( "selectize__item--mouse-selected" 213 | , mouseFocused 214 | ) 215 | , ( "selectize__item--key-selected" 216 | , keyboardFocused 217 | ) 218 | ] 219 | ] 220 | , children = 221 | [ Html.text tree ] 222 | } 223 | , divider = 224 | \title -> 225 | { attributes = 226 | [ Attributes.class "selectize__divider" ] 227 | , children = 228 | [ Html.text title ] 229 | } 230 | , input = selector 231 | } 232 | 233 | 234 | textfieldSelector : Selectize.Input String 235 | textfieldSelector = 236 | Selectize.autocomplete <| 237 | { attrs = 238 | \sthSelected open -> 239 | [ Attributes.class "selectize__textfield" 240 | , Attributes.classList 241 | [ ( "selectize__textfield--selection", sthSelected ) 242 | , ( "selectize__textfield--no-selection", not sthSelected ) 243 | , ( "selectize__textfield--menu-open", open ) 244 | ] 245 | ] 246 | , toggleButton = toggleButton 247 | , clearButton = clearButton 248 | , placeholder = "Select a License" 249 | } 250 | 251 | 252 | buttonSelector : Selectize.Input String 253 | buttonSelector = 254 | Selectize.simple 255 | { attrs = 256 | \sthSelected open -> 257 | [ Attributes.class "selectize__button" 258 | , Attributes.classList 259 | [ ( "selectize__button--light", open && not sthSelected ) ] 260 | ] 261 | , toggleButton = toggleButton 262 | , clearButton = clearButton 263 | , placeholder = "Select a License" 264 | } 265 | 266 | 267 | toggleButton : Maybe (Bool -> Html Never) 268 | toggleButton = 269 | Just <| 270 | \open -> 271 | Html.div 272 | [ Attributes.class "selectize__menu-toggle" 273 | , Attributes.classList 274 | [ ( "selectize__menu-toggle--menu-open", open ) ] 275 | ] 276 | [ Html.i 277 | [ Attributes.class "material-icons" 278 | , Attributes.class "selectize__icon" 279 | ] 280 | [ if open then 281 | Html.text "arrow_drop_up" 282 | else 283 | Html.text "arrow_drop_down" 284 | ] 285 | ] 286 | 287 | 288 | clearButton : Maybe (Html Never) 289 | clearButton = 290 | Just <| 291 | Html.div 292 | [ Attributes.class "selectize__menu-toggle" ] 293 | [ Html.i 294 | [ Attributes.class "material-icons" 295 | , Attributes.class "selectize__icon" 296 | ] 297 | [ Html.text "clear" ] 298 | ] 299 | 300 | 301 | 302 | ---- DATA 303 | 304 | 305 | toLabel : String -> String 306 | toLabel license = 307 | license 308 | 309 | 310 | licenses : List (Selectize.Entry String) 311 | licenses = 312 | List.concat 313 | [ [ Selectize.divider "GPL-Compatible Free Software Licenses" ] 314 | , gplCompatible |> List.map Selectize.entry 315 | , [ Selectize.divider "GPL-Incompatible Free Software Licenses" ] 316 | , gplIncompatible |> List.map Selectize.entry 317 | , [ Selectize.divider "Nonfree Software Licenses" ] 318 | , nonfree |> List.map Selectize.entry 319 | ] 320 | 321 | 322 | gplCompatible : List String 323 | gplCompatible = 324 | [ "GNU General Public License (GPL) version 3 (#GNUGPL) (#GNUGPLv3)" 325 | , "GNU General Public License (GPL) version 2 (#GPLv2)" 326 | , "GNU Lesser General Public License (LGPL) version 3 (#LGPL) (#LGPLv3)" 327 | , "GNU Lesser General Public License (LGPL) version 2.1 (#LGPLv2.1)" 328 | , "GNU Affero General Public License (AGPL) version 3 (#AGPL) (#AGPLv3.0)" 329 | , "GNU All-Permissive License (#GNUAllPermissive)" 330 | , "Apache License, Version 2.0 (#apache2)" 331 | , "Artistic License 2.0 (#ArtisticLicense2)" 332 | , "Clarified Artistic License" 333 | , "Berkeley Database License (a.k.a. the Sleepycat Software Product License) (#BerkeleyDB)" 334 | , "Boost Software License (#boost)" 335 | , "Modified BSD license (#ModifiedBSD)" 336 | , "CC0 (#CC0)" 337 | , "CeCILL version 2 (#CeCILL)" 338 | , "The Clear BSD License (#clearbsd)" 339 | , "Cryptix General License (#CryptixGeneralLicense)" 340 | , "eCos license version 2.0 (#eCos2.0)" 341 | , "Educational Community License 2.0 (#ECL2.0)" 342 | , "Eiffel Forum License, version 2 (#Eiffel)" 343 | , "EU DataGrid Software License (#EUDataGrid)" 344 | , "Expat License (#Expat)" 345 | , "FreeBSD license (#FreeBSD)" 346 | , "Freetype Project License (#freetype)" 347 | , "Historical Permission Notice and Disclaimer (#HPND)" 348 | , "License of the iMatix Standard Function Library (#iMatix)" 349 | , "License of imlib2 (#imlib)" 350 | , "Independent JPEG Group License (#ijg)" 351 | , "Informal license (#informal)" 352 | , "Intel Open Source License (#intel)" 353 | , "ISC License (#ISC)" 354 | , "Mozilla Public License (MPL) version 2.0 (#MPL-2.0)" 355 | , "NCSA/University of Illinois Open Source License (#NCSA)" 356 | , "License of Netscape JavaScript (#NetscapeJavaScript)" 357 | , "OpenLDAP License, Version 2.7 (#newOpenLDAP)" 358 | , "License of Perl 5 and below (#PerlLicense)" 359 | , "Public Domain (#PublicDomain)" 360 | , "License of Python 2.0.1, 2.1.1, and newer versions (#Python)" 361 | , "License of Python 1.6a2 and earlier versions (#Python1.6a2)" 362 | , "License of Ruby (#Ruby)" 363 | , "SGI Free Software License B, version 2.0 (#SGIFreeB)" 364 | , "Standard ML of New Jersey Copyright License (#StandardMLofNJ)" 365 | , "Unicode, Inc. License Agreement for Data Files and Software (#Unicode)" 366 | , "Universal Permissive License (UPL) (#UPL)" 367 | , "The Unlicense (#Unlicense)" 368 | , "License of Vim, Version 6.1 or later (#Vim)" 369 | , "W3C Software Notice and License (#W3C)" 370 | , "License of WebM (#WebM)" 371 | , "WTFPL, Version 2 (#WTFPL)" 372 | , "WxWidgets License (#Wx)" 373 | , "X11 License (#X11License)" 374 | , "XFree86 1.1 License (#XFree861.1License)" 375 | , "License of ZLib (#ZLib)" 376 | , "Zope Public License, versions 2.0 and 2.1 (#Zope2.0)" 377 | ] 378 | 379 | 380 | gplIncompatible : List String 381 | gplIncompatible = 382 | [ "Affero General Public License version 1 (#AGPLv1.0)" 383 | , "Academic Free License, all versions through 3.0 (#AcademicFreeLicense)" 384 | , "Apache License, Version 1.1 (#apache1.1)" 385 | , "Apache License, Version 1.0 (#apache1)" 386 | , "Apple Public Source License (APSL), version 2 (#apsl2)" 387 | , "BitTorrent Open Source License (#bittorrent)" 388 | , "Original BSD license (#OriginalBSD)" 389 | , "Common Development and Distribution License (CDDL), version 1.0 (#CDDL)" 390 | , "Common Public Attribution License 1.0 (CPAL) (#CPAL)" 391 | , "Common Public License Version 1.0 (#CommonPublicLicense10)" 392 | , "Condor Public License (#Condor)" 393 | , "Eclipse Public License Version 1.0 (#EPL)" 394 | , "European Union Public License (EUPL) version 1.1 (#EUPL)" 395 | , "Gnuplot license (#gnuplot)" 396 | , "IBM Public License, Version 1.0 (#IBMPL)" 397 | , "Jabber Open Source License, Version 1.0 (#josl)" 398 | , "LaTeX Project Public License 1.3a (#LPPL-1.3a)" 399 | , "LaTeX Project Public License 1.2 (#LPPL-1.2)" 400 | , "Lucent Public License Version 1.02 (Plan 9 license) (#lucent102)" 401 | , "Microsoft Public License (Ms-PL) (#ms-pl)" 402 | , "Microsoft Reciprocal License (Ms-RL) (#ms-rl)" 403 | , "Mozilla Public License (MPL) version 1.1 (#MPL)" 404 | , "Netizen Open Source License (NOSL), Version 1.0 (#NOSL)" 405 | , "Netscape Public License (NPL), versions 1.0 and 1.1 (#NPL)" 406 | , "Nokia Open Source License (#Nokia)" 407 | , "Old OpenLDAP License, Version 2.3 (#oldOpenLDAP)" 408 | , "Open Software License, all versions through 3.0 (#OSL)" 409 | , "OpenSSL license (#OpenSSL)" 410 | , "Phorum License, Version 2.0 (#Phorum)" 411 | , "PHP License, Version 3.01 (#PHP-3.01)" 412 | , "License of Python 1.6b1 through 2.0 and 2.1 (#PythonOld)" 413 | , "Q Public License (QPL), Version 1.0 (#QPL)" 414 | , "RealNetworks Public Source License (RPSL), Version 1.0 (#RPSL)" 415 | , "Sun Industry Standards Source License 1.0 (#SISSL)" 416 | , "Sun Public License (#SPL)" 417 | , "License of xinetd (#xinetd)" 418 | , "Yahoo! Public License 1.1 (#Yahoo)" 419 | , "Zend License, Version 2.0 (#Zend)" 420 | , "Zimbra Public License 1.3 (#Zimbra)" 421 | , "Zope Public License version 1 (#Zope)" 422 | ] 423 | 424 | 425 | nonfree : List String 426 | nonfree = 427 | [ "No license (#NoLicense)" 428 | , "Aladdin Free Public License (#Aladdin)" 429 | , "Apple Public Source License (APSL), version 1.x (#apsl1)" 430 | , "Artistic License 1.0 (#ArtisticLicense)" 431 | , "AT&T Public License (#ATTPublicLicense)" 432 | , "eCos Public License, version 1.1 (#eCos11)" 433 | , "CNRI Digital Object Repository License Agreement (#DOR)" 434 | , "GPL for Computer Programs of the Public Administration (#GPL-PA)" 435 | , "Jahia Community Source License (#Jahia)" 436 | , "The JSON License (#JSON)" 437 | , "Old license of ksh93 (#ksh93)" 438 | , "License of Lha (#Lha)" 439 | , "Microsoft's Shared Source CLI, C#, and Jscript License (#Ms-SS)" 440 | , "NASA Open Source Agreement (#NASA)" 441 | , "Oculus Rift SDK License (#OculusRiftSDK)" 442 | , "Peer-Production License (#PPL)" 443 | , "License of PINE (#PINE)" 444 | , "Old Plan 9 license (#Plan9)" 445 | , "Reciprocal Public License (#RPL)" 446 | , "Scilab license (#Scilab)" 447 | , "Scratch 1.4 license (#Scratch)" 448 | , "Simple Machines License (#SML)" 449 | , "Sun Community Source License (#SunCommunitySourceLicense)" 450 | , "Sun Solaris Source Code (Foundation Release) License, Version 1.1 (#SunSolarisSourceCode)" 451 | , "Sybase Open Watcom Public License version 1.0 (#Watcom)" 452 | , "SystemC “Open Source” License, Version 3.0 (#SystemC-3.0)" 453 | , "Truecrypt license 3.0 (#Truecrypt-3.0)" 454 | , "University of Utah Public License (#UtahPublicLicense)" 455 | , "YaST License (#YaST)" 456 | ] 457 | -------------------------------------------------------------------------------- /benchmarks/Benchmarks.elm: -------------------------------------------------------------------------------- 1 | module Benchmarks exposing (main) 2 | 3 | import Benchmark exposing (..) 4 | import Benchmark.Runner exposing (BenchmarkProgram, program) 5 | import Selectize.Selectize as Selectize 6 | 7 | 8 | main : BenchmarkProgram 9 | main = 10 | program suite 11 | 12 | 13 | suite : Benchmark 14 | suite = 15 | let 16 | firstOfPart1 = 17 | "Abelia x grandiflora" 18 | 19 | firstOfPart2 = 20 | "Eriobotrya japonica" 21 | 22 | firstOfPart3 = 23 | "Pieris floribunda" 24 | 25 | last = 26 | "Ziziphus jujuba" 27 | 28 | entryHeights = 29 | List.repeat (List.length trees) 50 30 | in 31 | describe "helper functions" 32 | [ benchmark1 "first" Selectize.first trees 33 | , describe "next" 34 | [ benchmark2 "at the beginning" 35 | Selectize.next 36 | trees 37 | firstOfPart1 38 | , benchmark2 "after the first part" 39 | Selectize.next 40 | trees 41 | firstOfPart2 42 | , benchmark2 "after the second part" 43 | Selectize.next 44 | trees 45 | firstOfPart3 46 | , benchmark2 "at the last entry" 47 | Selectize.next 48 | trees 49 | last 50 | ] 51 | , describe "previous" 52 | [ benchmark2 "at the beginning" 53 | Selectize.previous 54 | trees 55 | firstOfPart1 56 | , benchmark2 "after the first part" 57 | Selectize.previous 58 | trees 59 | firstOfPart2 60 | , benchmark2 "after the second part" 61 | Selectize.previous 62 | trees 63 | firstOfPart3 64 | , benchmark2 "at the last entry" 65 | Selectize.previous 66 | trees 67 | last 68 | ] 69 | , describe "topAndHeight" 70 | [ benchmark3 "at the beginning" 71 | Selectize.topAndHeight 72 | entryHeights 73 | trees 74 | (Just firstOfPart1) 75 | , benchmark3 "after the first part" 76 | Selectize.topAndHeight 77 | entryHeights 78 | trees 79 | (Just firstOfPart2) 80 | , benchmark3 "after the second part" 81 | Selectize.topAndHeight 82 | entryHeights 83 | trees 84 | (Just firstOfPart3) 85 | , benchmark3 "at the last entry" 86 | Selectize.topAndHeight 87 | entryHeights 88 | trees 89 | (Just last) 90 | ] 91 | ] 92 | 93 | 94 | 95 | {- data -} 96 | 97 | 98 | trees : List (Selectize.Entry String) 99 | trees = 100 | List.concat 101 | [ [ Selectize.divider "First Part" ] 102 | , treesPart1 |> List.map Selectize.entry 103 | , [ Selectize.divider "Second Part" ] 104 | , treesPart2 |> List.map Selectize.entry 105 | , [ Selectize.divider "Third Part" ] 106 | , treesPart3 |> List.map Selectize.entry 107 | ] 108 | 109 | 110 | treesPart1 : List String 111 | treesPart1 = 112 | [ "Abelia x grandiflora" 113 | , "Abienus festuschristus" 114 | , "Abies alba" 115 | , "Abies balsamea" 116 | , "Abies cephalonica" 117 | , "Abies concolor" 118 | , "Abies equi-trojani" 119 | , "Abies grandis" 120 | , "Abies holophylla" 121 | , "Abies homolepis" 122 | , "Abies koreana" 123 | , "Abies lasiocarpa" 124 | , "Abies nordmanniana" 125 | , "Abies pinsapo" 126 | , "Abies procera" 127 | , "Abies procera 'Glauca'" 128 | , "Abies veitchii" 129 | , "Acacia dealbata" 130 | , "Acacia karroo" 131 | , "Acacia retinodes" 132 | , "Acer buergerianum" 133 | , "Acer campestre" 134 | , "Acer cappadocicum" 135 | , "Acer carpinifolium" 136 | , "Acer caudatum subsp. caudatum" 137 | , "Acer circinatum" 138 | , "Acer cissifolium" 139 | , "Acer coriaceifolium" 140 | , "Acer davidii" 141 | , "Acer davidii subsp. grosserii" 142 | , "Acer griseum" 143 | , "Acer japonicum 'Aconitifolium'" 144 | , "Acer macrophyllum" 145 | , "Acer mandshuricum" 146 | , "Acer monspessulanum" 147 | , "Acer negundo" 148 | , "Acer opalus" 149 | , "Acer opalus ssp. obtusatum" 150 | , "Acer palmatum" 151 | , "Acer palmatum 'Dissectum Viridis'" 152 | , "Acer pensylvanicum" 153 | , "Acer platanoides" 154 | , "Acer platanoides 'Crimson King'" 155 | , "Acer platanoides f. drummondii" 156 | , "Acer pseudoplatanus" 157 | , "Acer pseudoplatanus 'Leopoldii'" 158 | , "Acer pseudoplatanus f. atropurpureum" 159 | , "Acer rubrum" 160 | , "Acer rufinerve" 161 | , "Acer saccharinum" 162 | , "Acer saccharum" 163 | , "Acer sempervirens" 164 | , "Acer tataricum" 165 | , "Acer tataricum subsp. ginnala" 166 | , "Acer tegmentosum" 167 | , "Acer triflorum" 168 | , "Acer x zoeschense" 169 | , "Actinidia deliciosa" 170 | , "Actinidia kolomikta" 171 | , "Adonidia merrillii" 172 | , "Aesculus flava" 173 | , "Aesculus glabra" 174 | , "Aesculus hippocastanum" 175 | , "Aesculus indica" 176 | , "Aesculus parviflora" 177 | , "Aesculus pavia" 178 | , "Aesculus turbinata" 179 | , "Aesculus x carnea" 180 | , "Aesculus x mutabilis 'Penduliflora'" 181 | , "Aesculus x neglecta 'Erythroblastos'" 182 | , "Afzelia africana" 183 | , "Ailanthus altissima" 184 | , "Akebia quinata" 185 | , "Alangium platanifolium" 186 | , "Albizia julibrissin" 187 | , "Allocasuarina luehmannii" 188 | , "Alnus cordata" 189 | , "Alnus glutinosa" 190 | , "Alnus incana" 191 | , "Alnus sinuata" 192 | , "Alnus viridis" 193 | , "Amelanchier asiatica" 194 | , "Amelanchier laevis" 195 | , "Amelanchier lamarckii" 196 | , "Amelanchier ovalis" 197 | , "Amelanchier spicata" 198 | , "Amorpha fructicosa" 199 | , "Anacardium occidentale" 200 | , "Anagyris foetida" 201 | , "Andromeda polifolia" 202 | , "Annona cherimola" 203 | , "Aralia elata" 204 | , "Araucaria araucana" 205 | , "Araucaria bidwillii" 206 | , "Araucaria columnaris" 207 | , "Araucaria cunninghamii" 208 | , "Araucaria heterophylla" 209 | , "Arbutus menziesii" 210 | , "Arbutus unedo" 211 | , "Argania spinosa" 212 | , "Argyrocytisus battandieri" 213 | , "Aronia arbutifolia" 214 | , "Aronia melanocarpa" 215 | , "Aronia x prunifolia" 216 | , "Asimina triloba" 217 | , "Asparagus acutifolius" 218 | , "Aucuba japonica" 219 | , "Averrhoa carambola" 220 | , "Barringtonia asiatica" 221 | , "Bauhinia kockiana" 222 | , "Bauhinia x blakeana" 223 | , "Berberis julianae" 224 | , "Berberis koreana" 225 | , "Berberis thunbergii" 226 | , "Berberis vulgaris" 227 | , "Betula alleghaniensis" 228 | , "Betula alnoides subsp. alnoides" 229 | , "Betula alnoides subsp. luminifera" 230 | , "Betula costata" 231 | , "Betula dahurica" 232 | , "Betula ermanii" 233 | , "Betula insignis" 234 | , "Betula lenta" 235 | , "Betula lenta f. uber" 236 | , "Betula maximowicziana" 237 | , "Betula nigra" 238 | , "Betula papyrifera" 239 | , "Betula pendula" 240 | , "Betula pendula f. dalecarlica" 241 | , "Betula populifolia" 242 | , "Betula pubescens" 243 | , "Betula utilis var. jacquemontii 'Doorenbos'" 244 | , "Bougainvillea glabra" 245 | , "Bougainvillea spectabilis" 246 | , "Brachychiton acerifolius" 247 | , "Brachychiton discolor" 248 | , "Brachychiton populneus" 249 | , "Brachychiton rupestris" 250 | , "Broussonetia papyrifera" 251 | , "Buddleja alternifolia" 252 | , "Buddleja davidii" 253 | , "Buddleja globosa" 254 | , "Buddleja x weyeriana" 255 | , "Butyrospermum parkii" 256 | , "Buxus sempervirens" 257 | , "Caesalpinia gilliesii" 258 | , "Calicotome spinosa" 259 | , "Callicarpa dichotoma" 260 | , "Callistemon citrinus" 261 | , "Calocedrus decurrens" 262 | , "Calocedrus decurrens 'Aureovariegata'" 263 | , "Calycanthus fertilis" 264 | , "Calycanthus floridus" 265 | , "Camellia japonica" 266 | , "Campsis radicans" 267 | , "Campsis x tagliabuana" 268 | , "Capparis spinosa" 269 | , "Caragana arborescens" 270 | , "Carpenteria californica" 271 | , "Carpinus betulus" 272 | , "Carpinus betulus 'Quercifolia'" 273 | , "Carpinus caroliniana" 274 | , "Carpinus japonica" 275 | , "Carya cordiformis" 276 | , "Carya illinoinensis" 277 | , "Carya laciniosa" 278 | , "Carya ovata" 279 | , "Carya tomentosa" 280 | , "Cascabela thevetia" 281 | , "Cassia siberiana" 282 | , "Castanea crenata" 283 | , "Castanea sativa" 284 | , "Casuarina cunninghamiana" 285 | , "Casuarina stricta" 286 | , "Catalpa bignonioides" 287 | , "Catalpa bungei" 288 | , "Catalpa ovata" 289 | , "Catalpa speciosa" 290 | , "Catalpa x erubescens" 291 | , "Ceanothus x delilianus" 292 | , "Cedrus atlantica" 293 | , "Cedrus atlantica 'Glauca'" 294 | , "Cedrus brevifolia" 295 | , "Cedrus deodara" 296 | , "Cedrus deodara 'Paktia'" 297 | , "Cedrus libani" 298 | , "Ceiba speciosa" 299 | , "Celtis australis" 300 | , "Celtis occidentalis" 301 | , "Cephalanthus occidentalis" 302 | , "Cephalotaxus harringtonia" 303 | , "Cephalotaxus sinensis" 304 | , "Ceratonia siliqua" 305 | , "Cercidiphyllum japonicum" 306 | , "Cercis canadensis" 307 | , "Cercis chinensis" 308 | , "Cercis siliquastrum" 309 | , "Chaenomeles japonica" 310 | , "Chaenomeles speciosa" 311 | , "Chamaecyparis lawsoniana" 312 | , "Chamaecyparis pisifera" 313 | , "Chamaerops humilis" 314 | , "Chimonanthus praecox" 315 | , "Chionanthus retusus" 316 | , "Chionanthus virginicus" 317 | , "Chitalpa tashkentensis" 318 | , "Choisya ternata" 319 | , "Cinnamomum camphora" 320 | , "Cistus albidus" 321 | , "Cistus crispus" 322 | , "Cistus incanus" 323 | , "Cistus incanus ssp. creticus" 324 | , "Cistus ladanifer" 325 | , "Cistus laurifolius" 326 | , "Cistus monspeliensis" 327 | , "Cistus populifolius" 328 | , "Cistus salviifolius" 329 | , "Cistus symphytifolius" 330 | , "Citharexylum spinosum" 331 | , "Citrus x aurantium" 332 | , "Citrus x limon" 333 | , "Cladrastis kentukea" 334 | , "Clematis flammula" 335 | , "Clematis montana" 336 | , "Clematis vitalba" 337 | , "Clematis viticella" 338 | , "Clerodendrum trichotomum" 339 | , "Clethra alnifolia" 340 | , "Cneorum tricoccon" 341 | , "Coccoloba uvifera" 342 | , "Cocos nucifera" 343 | , "Colutea arborescens" 344 | , "Cornus alba" 345 | , "Cornus controversa" 346 | , "Cornus florida" 347 | , "Cornus kousa" 348 | , "Cornus mas" 349 | , "Cornus nuttallii" 350 | , "Cornus officinalis" 351 | , "Cornus racemosa" 352 | , "Cornus sanguinea" 353 | , "Cornus sericea" 354 | , "Corylopsis pauciflora" 355 | , "Corylopsis spicata" 356 | , "Corylopsis veitchiana" 357 | , "Corylus avellana" 358 | , "Corylus avellana 'Contorta'" 359 | , "Corylus colurna" 360 | , "Corylus maxima" 361 | , "Corymbia dallachiana" 362 | , "Cotinus coggygria" 363 | , "Cotoneaster dielsianus" 364 | , "Cotoneaster floccosus" 365 | , "Cotoneaster frigidus" 366 | , "Cotoneaster horizontalis" 367 | , "Cotoneaster integerrimus" 368 | , "Cotoneaster multiflorus" 369 | , "Crataegus laevigata" 370 | , "Crataegus laevigata 'Paul's Scarlet'" 371 | , "Crataegus monogyna" 372 | , "Crataegus nigra" 373 | , "Crataegus pedicellata" 374 | , "Crataegus pinnatifida" 375 | , "Crataegus succulenta var. macrantha" 376 | , "Crataegus x lavallei 'Carrierei'" 377 | , "Crataemespilus grandiflora" 378 | , "Cryptomeria japonica" 379 | , "Cryptomeria japonica f. cristata" 380 | , "Cunninghamia lanceolata" 381 | , "Cupressus arizonica" 382 | , "Cupressus glabra" 383 | , "Cupressus sempervirens" 384 | , "Cycas revoluta" 385 | , "Cydonia oblonga" 386 | , "Cytisus scoparius" 387 | , "Danae racemosa" 388 | , "Daphne gnidium" 389 | , "Daphne mezereum" 390 | , "Dasiphora fruticosa" 391 | , "Davidia involucrata" 392 | , "Decaisnea fargesii" 393 | , "Delonix regia" 394 | , "Deutzia longifolia" 395 | , "Deutzia scabra" 396 | , "Deutzia x hybrida" 397 | , "Diospyros kaki" 398 | , "Diospyros lotus" 399 | , "Dipelta floribunda" 400 | , "Distylium racemosum" 401 | , "Dracaena draco" 402 | , "Duranta erecta" 403 | , "Edgeworthia chrysantha" 404 | , "Ehretia dicksonii" 405 | , "Elaeagnus angustifolia" 406 | , "Elaeagnus pungens" 407 | , "Elaeagnus umbellata" 408 | , "Elaeagnus x ebbingei" 409 | , "Eleutherococcus sieboldianus" 410 | , "Erica arborea" 411 | ] 412 | 413 | 414 | treesPart2 : List String 415 | treesPart2 = 416 | [ "Eriobotrya japonica" 417 | , "Erythrina crista-galli" 418 | , "Eucalyptus globulus" 419 | , "Eucommia ulmoides" 420 | , "Euonymus alatus" 421 | , "Euonymus europaeus" 422 | , "Euonymus fortunei" 423 | , "Euonymus macropterus" 424 | , "Euonymus planipes" 425 | , "Exochorda giraldii" 426 | , "Fagus grandifolia" 427 | , "Fagus orientalis" 428 | , "Fagus sylvatica" 429 | , "Fagus sylvatica 'Asplenifolia'" 430 | , "Fagus sylvatica 'Felderbach'" 431 | , "Fagus sylvatica 'Pendula'" 432 | , "Fagus sylvatica 'Purpurea'" 433 | , "Fagus sylvatica 'Tortuosa'" 434 | , "Fagus sylvatica f. purpurea tricolor" 435 | , "Fatsia japonica" 436 | , "Feijoa sellowiana" 437 | , "Ficus benjamina" 438 | , "Ficus carica" 439 | , "Ficus lyrata" 440 | , "Ficus macrophylla" 441 | , "Ficus sycomorus" 442 | , "Firmiana simplex" 443 | , "Forsythia x intermedia" 444 | , "Fortunella" 445 | , "Fothergilla major" 446 | , "Frangula alnus" 447 | , "Fraxinus angustifolia" 448 | , "Fraxinus excelsior" 449 | , "Fraxinus excelsior f. diversifolia" 450 | , "Fraxinus latifolia" 451 | , "Fraxinus ornus" 452 | , "Fraxinus paxiana" 453 | , "Ginkgo biloba L." 454 | , "Gleditsia triacanthos" 455 | , "Grevillea robusta" 456 | , "Gymnocladus dioicus" 457 | , "Halesia carolina" 458 | , "Halesia monticola" 459 | , "Halimodendron halodendron" 460 | , "Hamamelis virginiana" 461 | , "Hamamelis x intermedia" 462 | , "Hedera helix" 463 | , "Heptacodium miconioides" 464 | , "Hibiscus rosa-sinensis" 465 | , "Hibiscus syriacus" 466 | , "Hippophae rhamnoides" 467 | , "Holodiscus discolor" 468 | , "Humulus lupulus" 469 | , "Hura crepitans" 470 | , "Hydrangea arborescens" 471 | , "Hydrangea aspera ssp. aspera" 472 | , "Hydrangea aspera ssp. sargentiana" 473 | , "Hydrangea macrophylla" 474 | , "Hydrangea paniculata" 475 | , "Hydrangea petiolaris" 476 | , "Hydrangea quercifolia" 477 | , "Hypericum androsaemum" 478 | , "Hypericum balearicum" 479 | , "Ilex aquifolium" 480 | , "Ilex crenata" 481 | , "Ilex pernyi" 482 | , "Jacaranda mimosifolia" 483 | , "Jasminum nudiflorum" 484 | , "Jasminum officinale" 485 | , "Juglans ailantifolia" 486 | , "Juglans cinerea" 487 | , "Juglans nigra" 488 | , "Juglans regia" 489 | , "Juniperus communis" 490 | , "Juniperus oxycedrus" 491 | , "Juniperus sabina" 492 | , "Juniperus scopulorum" 493 | , "Kalmia latifolia" 494 | , "Kerria japonica 'Pleniflora'" 495 | , "Khaya senegalensis" 496 | , "Koelreuteria paniculata" 497 | , "Kolkwitzia amabilis" 498 | , "Laburnum alpinum" 499 | , "Laburnum anagyroides" 500 | , "Lagerstroemia indica" 501 | , "Lagerstroemia speciosa" 502 | , "Lagunaria patersonia" 503 | , "Lantana camara" 504 | , "Larix decidua" 505 | , "Larix kaempferi" 506 | , "Larix x marschlinsii" 507 | , "Laurus nobilis" 508 | , "Lavandula angustifolia" 509 | , "Lavandula stoechas" 510 | , "Leucaena leucocephala" 511 | , "Leycesteria formosa" 512 | , "Ligustrum japonicum" 513 | , "Ligustrum lucidum" 514 | , "Ligustrum ovalifolium" 515 | , "Ligustrum vulgare" 516 | , "Liquidambar formosana" 517 | , "Liquidambar orientalis" 518 | , "Liquidambar styraciflua" 519 | , "Liriodendron chinense" 520 | , "Liriodendron tulipifera" 521 | , "Lonicera caprifolium" 522 | , "Lonicera etrusca" 523 | , "Lonicera henryi" 524 | , "Lonicera implexa" 525 | , "Lonicera kamtschatica" 526 | , "Lonicera maackii" 527 | , "Lonicera nigra" 528 | , "Lonicera periclymenum" 529 | , "Lonicera pileata" 530 | , "Lonicera tatarica" 531 | , "Lonicera x heckrottii" 532 | , "Lonicera x purpusii 'Winter Beauty'" 533 | , "Lonicera xylosteum" 534 | , "Lycium barbarum" 535 | , "Maclura pomifera" 536 | , "Magnolia acuminata" 537 | , "Magnolia denudata" 538 | , "Magnolia grandiflora" 539 | , "Magnolia kobus" 540 | , "Magnolia liliiflora" 541 | , "Magnolia obovata" 542 | , "Magnolia sieboldii" 543 | , "Magnolia stellata" 544 | , "Magnolia tripetala" 545 | , "Magnolia x soulangeana" 546 | , "Magnolia x wiesneri" 547 | , "Mahonia aquifolium" 548 | , "Mahonia x media" 549 | , "Malus domestica" 550 | , "Malus floribunda" 551 | , "Malus sargentii" 552 | , "Malus sylvestris" 553 | , "Malus toringoides" 554 | , "Malus x purpurea" 555 | , "Melia azedarach" 556 | , "Mespilus germanica" 557 | , "Metasequoia glyptostroboides" 558 | , "Morus alba" 559 | , "Morus alba f. macrophylla" 560 | , "Morus nigra" 561 | , "Myoporum serratum" 562 | , "Myrtus communis" 563 | , "Nandina domestica" 564 | , "Nerium oleander" 565 | , "Nicotiana glauca" 566 | , "Nothofagus antarctica" 567 | , "Nyssa sylvatica" 568 | , "Olea europaea" 569 | , "Osmanthus x burkwoodii" 570 | , "Osmanthus x fortunei" 571 | , "Ostrya carpinifolia" 572 | , "Ostrya virginiana" 573 | , "Osyris alba" 574 | , "Oxydendrum arboreum" 575 | , "Pachypodium lamerei" 576 | , "Paeonia x suffruticosa" 577 | , "Paliurus spina-christi" 578 | , "Parkinsonia aculeata" 579 | , "Parrotia persica" 580 | , "Parrotiopsis jaquemontiana" 581 | , "Parthenocissus inserta" 582 | , "Parthenocissus quinquefolia" 583 | , "Parthenocissus tricuspidata" 584 | , "Passiflora caerulea" 585 | , "Paulownia tomentosa" 586 | , "Peltophorum pterocarpum" 587 | , "Pereskia bleo" 588 | , "Persea americana" 589 | , "Petteria ramentacea" 590 | , "Phellodendron amurense" 591 | , "Philadelphus coronarius" 592 | , "Philadelphus x virginalis" 593 | , "Phillyrea angustifolia" 594 | , "Phillyrea latifolia" 595 | , "Phoenix canariensis" 596 | , "Phoenix dactylifera" 597 | , "Photinia davidiana" 598 | , "Photinia x fraseri" 599 | , "Physocarpus opulifolius" 600 | , "Phytolacca dioica" 601 | , "Picea abies" 602 | , "Picea abies 'Inversa'" 603 | , "Picea asperata" 604 | , "Picea breweriana" 605 | , "Picea engelmanii" 606 | , "Picea glauca" 607 | , "Picea glauca 'Conica'" 608 | , "Picea mariana" 609 | , "Picea omorika" 610 | , "Picea orientalis" 611 | , "Picea polita" 612 | , "Picea pungens 'Glauca'" 613 | , "Picea sitchensis" 614 | , "Picea wilsonii" 615 | ] 616 | 617 | 618 | treesPart3 : List String 619 | treesPart3 = 620 | [ "Pieris floribunda" 621 | , "Pieris japonica" 622 | , "Pinus aristata" 623 | , "Pinus armandii" 624 | , "Pinus attenuata" 625 | , "Pinus banksiana" 626 | , "Pinus bungeana" 627 | , "Pinus canariensis" 628 | , "Pinus cembra" 629 | , "Pinus contorta" 630 | , "Pinus coulteri" 631 | , "Pinus halepensis" 632 | , "Pinus heldreichii" 633 | , "Pinus jeffreyi" 634 | , "Pinus koraiensis" 635 | , "Pinus leucodermis" 636 | , "Pinus monticola" 637 | , "Pinus mugo" 638 | , "Pinus nigra" 639 | , "Pinus nigra var. laricio" 640 | , "Pinus nigra var. salzmanii" 641 | , "Pinus parviflora" 642 | , "Pinus peuce" 643 | , "Pinus pinaster" 644 | , "Pinus pinea" 645 | , "Pinus ponderosa" 646 | , "Pinus strobus" 647 | , "Pinus sylvestris" 648 | , "Pinus thunbergii" 649 | , "Pinus wallichiana" 650 | , "Pistacia lentiscus" 651 | , "Pistacia terebinthus" 652 | , "Pistacia vera" 653 | , "Pittosporum tobira" 654 | , "Platanus orientalis" 655 | , "Platanus x hispanica" 656 | , "Platycarya strobilacea" 657 | , "Platycladus orientalis" 658 | , "Plumbago auriculata" 659 | , "Plumeria rubra" 660 | , "Podranea ricasoliana" 661 | , "Polygala myrtifolia" 662 | , "Poncirus trifoliata" 663 | , "Populus alba" 664 | , "Populus balsamifera" 665 | , "Populus nigra" 666 | , "Populus nigra 'Italica'" 667 | , "Populus simonii" 668 | , "Populus tremula" 669 | , "Populus x canadensis" 670 | , "Populus x canescens" 671 | , "Prosopis juliflora" 672 | , "Prunus 'Accolade'" 673 | , "Prunus 'Kursar'" 674 | , "Prunus armeniaca" 675 | , "Prunus avium" 676 | , "Prunus avium 'Plena'" 677 | , "Prunus cerasifera" 678 | , "Prunus cerasifera 'Nigra'" 679 | , "Prunus cerasifera 'Rosea'" 680 | , "Prunus cerasus" 681 | , "Prunus cerasus 'Rhexii'" 682 | , "Prunus davidiana" 683 | , "Prunus domestica" 684 | , "Prunus domestica ssp. insititia" 685 | , "Prunus domestica ssp. syriaca" 686 | , "Prunus domestica subsp. domestica" 687 | , "Prunus domestica subsp. italica" 688 | , "Prunus dulcis" 689 | , "Prunus fenziliana" 690 | , "Prunus incana" 691 | , "Prunus incisa" 692 | , "Prunus laurocerasus" 693 | , "Prunus lusitanica" 694 | , "Prunus maackii" 695 | , "Prunus mahaleb" 696 | , "Prunus mume" 697 | , "Prunus nipponica var. kurilensis" 698 | , "Prunus padus" 699 | , "Prunus persica" 700 | , "Prunus sargentii" 701 | , "Prunus serotina" 702 | , "Prunus serrula" 703 | , "Prunus serrulata 'Kanzan'" 704 | , "Prunus sibirica" 705 | , "Prunus spinosa" 706 | , "Prunus subhirtella" 707 | , "Prunus subhirtella f. autumnalis" 708 | , "Prunus tomentosa" 709 | , "Prunus triloba" 710 | , "Pseudocydonia sinensis" 711 | , "Pseudolarix amabilis" 712 | , "Pseudotsuga menziesii" 713 | , "Ptelea trifoliata" 714 | , "Pterocarya fraxinifolia" 715 | , "Pterocarya stenoptera" 716 | , "Pterostyrax corymbosus" 717 | , "Pterostyrax hispidus" 718 | , "Punica granatum" 719 | , "Pyracantha coccinea" 720 | , "Pyrus betulifolia" 721 | , "Pyrus calleryana 'Chanticleer'" 722 | , "Pyrus communis" 723 | , "Pyrus pyraster" 724 | , "Pyrus pyrifolia var. culta" 725 | , "Pyrus salicifolia" 726 | , "Quercus acutissima" 727 | , "Quercus alba" 728 | , "Quercus canariensis" 729 | , "Quercus castaneifolia" 730 | , "Quercus cerris" 731 | , "Quercus coccifera" 732 | , "Quercus coccinea" 733 | , "Quercus dentata" 734 | , "Quercus faginea" 735 | , "Quercus frainetto" 736 | , "Quercus ilex" 737 | , "Quercus ilicifolia" 738 | , "Quercus imbricaria" 739 | , "Quercus libani" 740 | , "Quercus macranthera" 741 | , "Quercus macrocarpa" 742 | , "Quercus marilandica" 743 | , "Quercus palustris" 744 | , "Quercus petraea" 745 | , "Quercus petraea 'Laciniata Crispa'" 746 | , "Quercus phellos" 747 | , "Quercus pontica" 748 | , "Quercus pubescens" 749 | , "Quercus pyrenaica" 750 | , "Quercus robur" 751 | , "Quercus robur 'Pectinata'" 752 | , "Quercus robur f. fastigiata" 753 | , "Quercus rubra" 754 | , "Quercus shumardii" 755 | , "Quercus suber" 756 | , "Quercus velutina" 757 | , "Quercus x hispanica 'Lucombeana'" 758 | , "Quercus x turneri" 759 | , "Rhamnus alaternus" 760 | , "Rhamnus cathartica" 761 | , "Rhamnus imeretina" 762 | , "Rhamnus lycioides" 763 | , "Rhamnus pumila" 764 | , "Rhamnus saxatilis" 765 | , "Rhododendron catawbiense" 766 | , "Rhododendron tomentosum" 767 | , "Rhodotypos scandens" 768 | , "Rhus typhina" 769 | , "Ribes aureum" 770 | , "Ribes rubrum" 771 | , "Ribes sanguineum" 772 | , "Ribes uva-crispa var. sativum" 773 | , "Ricinus communis" 774 | , "Robinia hispida" 775 | , "Robinia luxurians" 776 | , "Robinia pseudoacacia" 777 | , "Robinia x margaretta 'Casque Rouge'" 778 | , "Rosa canina" 779 | , "Rosa rugosa" 780 | , "Rosa spinosissima" 781 | , "Rosa tomentosa" 782 | , "Rubia peregrina" 783 | , "Rubus fruticosus" 784 | , "Rubus idaeus" 785 | , "Rubus odoratus" 786 | , "Rubus spectabilis" 787 | , "Ruscus aculeatus" 788 | , "Salix alba" 789 | , "Salix alba 'Tristis'" 790 | , "Salix aurita" 791 | , "Salix babylonica" 792 | , "Salix caprea" 793 | , "Salix caprea 'Kilmarnock'" 794 | , "Salix cinerea" 795 | , "Salix fragilis" 796 | , "Salix integra 'Hakuro Nishiki'" 797 | , "Salix irrorata" 798 | , "Salix matsudana 'Tortuosa'" 799 | , "Salix purpurea" 800 | , "Salix udensis f. sekka" 801 | , "Salix viminalis" 802 | , "Samanea saman" 803 | , "Sambucus nigra" 804 | , "Sambucus nigra ssp. caerulea" 805 | , "Sambucus racemosa" 806 | , "Sarcococca hookeriana var. humilis" 807 | , "Sassafras albidum" 808 | , "Schefflera actinophylla" 809 | , "Schinus molle" 810 | , "Sciadopitys verticillata" 811 | , "Senna didymobotrya" 812 | , "Senna x floribunda" 813 | , "Sequoia sempervirens" 814 | , "Sequoiadendron giganteum" 815 | , "Shepherdia argentea" 816 | , "Sinocalycanthus chinensis" 817 | , "Skimmia japonica" 818 | , "Smilax aspera" 819 | , "Solanum dulcamara" 820 | , "Solanum jasminoides" 821 | , "Sorbaria sorbifolia" 822 | , "Sorbus alnifolia" 823 | , "Sorbus americana" 824 | , "Sorbus aria" 825 | , "Sorbus aucuparia" 826 | , "Sorbus domestica" 827 | , "Sorbus intermedia" 828 | , "Sorbus torminalis" 829 | , "Spartium junceum" 830 | , "Spathodea campanulata" 831 | , "Spiraea japonica" 832 | , "Spiraea thunbergii" 833 | , "Spiraea x billardii" 834 | , "Spiraea x vanhouttei" 835 | , "Stachyurus praecox" 836 | , "Staphylea colchica" 837 | , "Staphylea holocarpa" 838 | , "Staphylea pinnata" 839 | , "Stewartia pseudocamellia" 840 | , "Styphnolobium japonicum" 841 | , "Styrax japonicus" 842 | , "Styrax obassia" 843 | , "Symphoricarpos albus" 844 | , "Symphoricarpos x chenaultii" 845 | , "Syringa reflexa" 846 | , "Syringa vulgaris" 847 | , "Taiwania cryptomerioides" 848 | , "Tamarix parviflora" 849 | , "Taxodium distichum" 850 | , "Taxus baccata" 851 | , "Tecoma capensis" 852 | , "Tecoma stans" 853 | , "Terminalia catappa" 854 | , "Tetradium daniellii" 855 | , "Thuja occidentalis" 856 | , "Thuja plicata" 857 | , "Thujopsis dolabrata" 858 | , "Thymelaea hirsuta" 859 | , "Tilia americana" 860 | , "Tilia cordata" 861 | , "Tilia dasystyla" 862 | , "Tilia henryana" 863 | , "Tilia mongolica" 864 | , "Tilia platyphyllos" 865 | , "Tilia tomentosa" 866 | , "Tilia tomentosa f. petiolaris" 867 | , "Tilia x euchlora" 868 | , "Tilia x europaea" 869 | , "Tipuana tipu" 870 | , "Toona sinensis" 871 | , "Trachycarpus fortunei" 872 | , "Tsuga canadensis" 873 | , "Tsuga diversifolia" 874 | , "Tsuga heterophylla" 875 | , "Ulex europaeus" 876 | , "Ulmus glabra" 877 | , "Ulmus laevis" 878 | , "Ulmus minor" 879 | , "Ulmus minor 'Jacqueline Hillier'" 880 | , "Ulmus minor 'Wredei'" 881 | , "Ulmus parvifolia" 882 | , "Ulmus pumila" 883 | , "Vaccinium myrtillus" 884 | , "Vaccinium uliginosum" 885 | , "Vaccinium vitis-idaea" 886 | , "Vachellia xanthophloea" 887 | , "Viburnum davidii" 888 | , "Viburnum farreri" 889 | , "Viburnum lantana" 890 | , "Viburnum lantanoides" 891 | , "Viburnum lentago" 892 | , "Viburnum opulus" 893 | , "Viburnum opulus f. roseum" 894 | , "Viburnum plicatum" 895 | , "Viburnum plicatum f. sterile" 896 | , "Viburnum rhytidophyllum" 897 | , "Viburnum tinus" 898 | , "Viburnum x bodnantense" 899 | , "Viburnum x burkwoodii" 900 | , "Viburnum x carlcephalum" 901 | , "Viscum album" 902 | , "Vitex agnus-castus" 903 | , "Vitis coignetiae" 904 | , "Vitis vinifera" 905 | , "Washingtonia filifera" 906 | , "Washingtonia robusta" 907 | , "Weigela florida" 908 | , "Wisteria sinensis" 909 | , "Wollemia nobilis" 910 | , "Xanthocyparis nootkatensis" 911 | , "Zanthoxylum simulans" 912 | , "Zelkova carpinifolia" 913 | , "Zelkova serrata" 914 | , "Ziziphus jujuba" 915 | ] 916 | -------------------------------------------------------------------------------- /src/Selectize/Selectize.elm: -------------------------------------------------------------------------------- 1 | module Selectize.Selectize 2 | exposing 3 | ( Entry 4 | , Heights 5 | , Input 6 | , LEntry(..) 7 | , Movement(..) 8 | , Msg(..) 9 | , State 10 | , ViewConfig 11 | , ZipList 12 | , autocomplete 13 | , closed 14 | , contains 15 | , currentEntry 16 | , divider 17 | , entry 18 | , fromList 19 | , moveForwardTo 20 | , simple 21 | , update 22 | , view 23 | , viewConfig 24 | , zipCurrentHeight 25 | , zipNext 26 | , zipPrevious 27 | ) 28 | 29 | {- 30 | 31 | Copyright 2018 Fabian Kirchner 32 | 33 | Licensed under the Apache License, Version 2.0 (the "License"); 34 | you may not use this file except in compliance with the License. 35 | You may obtain a copy of the License at 36 | 37 | http://www.apache.org/licenses/LICENSE-2.0 38 | 39 | Unless required by applicable law or agreed to in writing, software 40 | distributed under the License is distributed on an "AS IS" BASIS, 41 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 42 | See the License for the specific language governing permissions and 43 | limitations under the License. 44 | 45 | -} 46 | 47 | import Browser.Dom as Dom 48 | import Html exposing (Html) 49 | import Html.Attributes as Attributes 50 | import Html.Events as Events 51 | import Html.Lazy 52 | import Json.Decode as Decode exposing (Decoder) 53 | import Task 54 | 55 | 56 | ---- MODEL 57 | 58 | 59 | type alias State a = 60 | { id : String 61 | , entries : List (LEntry a) 62 | , query : String 63 | , zipList : Maybe (ZipList a) 64 | , open : Bool 65 | , mouseFocus : Maybe a 66 | , preventBlur : Bool 67 | 68 | -- dom measurements 69 | , entryHeights : List Float 70 | , menuHeight : Float 71 | , scrollTop : Float 72 | } 73 | 74 | 75 | type alias Heights = 76 | { entries : List Float 77 | , menu : Float 78 | } 79 | 80 | 81 | type LEntry a 82 | = LEntry a String 83 | | LDivider String 84 | 85 | 86 | removeLabel : LEntry a -> Entry a 87 | removeLabel labeledEntry = 88 | case labeledEntry of 89 | LEntry a _ -> 90 | Entry a 91 | 92 | LDivider text -> 93 | Divider text 94 | 95 | 96 | selectFirst : List (LEntry a) -> a -> Maybe ( a, String ) 97 | selectFirst entries a = 98 | case entries of 99 | [] -> 100 | Nothing 101 | 102 | first :: rest -> 103 | case first of 104 | LEntry value label -> 105 | if a == value then 106 | Just ( a, label ) 107 | else 108 | selectFirst rest a 109 | 110 | _ -> 111 | selectFirst rest a 112 | 113 | 114 | closed : String -> (a -> String) -> List (Entry a) -> State a 115 | closed id toLabel entries = 116 | let 117 | addLabel e = 118 | case e of 119 | Entry a -> 120 | LEntry a (toLabel a) 121 | 122 | Divider text -> 123 | LDivider text 124 | 125 | labeledEntries = 126 | entries |> List.map addLabel 127 | in 128 | { id = id 129 | , entries = labeledEntries 130 | , query = "" 131 | , zipList = Nothing 132 | , open = False 133 | , mouseFocus = Nothing 134 | , preventBlur = False 135 | , entryHeights = [] 136 | , menuHeight = 0 137 | , scrollTop = 0 138 | } 139 | 140 | 141 | 142 | ---- CONFIGURATION 143 | 144 | 145 | type alias ViewConfig a = 146 | { container : List (Html.Attribute Never) 147 | , menu : List (Html.Attribute Never) 148 | , ul : List (Html.Attribute Never) 149 | , entry : a -> Bool -> Bool -> HtmlDetails Never 150 | , divider : String -> HtmlDetails Never 151 | , input : Input a 152 | } 153 | 154 | 155 | type alias HtmlDetails msg = 156 | { attributes : List (Html.Attribute msg) 157 | , children : List (Html msg) 158 | } 159 | 160 | 161 | type Entry a 162 | = Entry a 163 | | Divider String 164 | 165 | 166 | entry : a -> Entry a 167 | entry a = 168 | Entry a 169 | 170 | 171 | divider : String -> Entry a 172 | divider title = 173 | Divider title 174 | 175 | 176 | viewConfig : 177 | { container : List (Html.Attribute Never) 178 | , menu : List (Html.Attribute Never) 179 | , ul : List (Html.Attribute Never) 180 | , entry : a -> Bool -> Bool -> HtmlDetails Never 181 | , divider : String -> HtmlDetails Never 182 | , input : Input a 183 | } 184 | -> ViewConfig a 185 | viewConfig config = 186 | { container = config.container 187 | , menu = config.menu 188 | , ul = config.ul 189 | , entry = config.entry 190 | , divider = config.divider 191 | , input = config.input 192 | } 193 | 194 | 195 | 196 | ---- UPDATE 197 | 198 | 199 | type Msg a 200 | = NoOp 201 | -- open/close menu 202 | | OpenMenu Heights Float 203 | | CloseMenu 204 | | FocusTextfield 205 | | BlurTextfield 206 | | PreventClosing Bool 207 | -- query 208 | | SetQuery String 209 | -- handle focus and selection 210 | | SetMouseFocus (Maybe a) 211 | | Select a 212 | | SetKeyboardFocus Movement Float 213 | | SelectKeyboardFocusAndBlur 214 | | ClearSelection 215 | 216 | 217 | type Movement 218 | = Up 219 | | Down 220 | | PageUp 221 | | PageDown 222 | 223 | 224 | update : 225 | (Maybe a -> msg) 226 | -> Maybe a 227 | -> State a 228 | -> Msg a 229 | -> ( State a, Cmd (Msg a), Maybe msg ) 230 | update select maybeSelection state msg = 231 | case msg of 232 | NoOp -> 233 | ( state, Cmd.none, Nothing ) 234 | 235 | OpenMenu heights scrollTop -> 236 | let 237 | newZipList = 238 | fromList state.entries heights.entries 239 | |> Maybe.map 240 | (case maybeSelection of 241 | Just a -> 242 | moveForwardTo a 243 | 244 | Nothing -> 245 | identity 246 | ) 247 | 248 | top = 249 | newZipList 250 | |> Maybe.map .currentTop 251 | |> Maybe.withDefault 0 252 | 253 | height = 254 | newZipList 255 | |> Maybe.map zipCurrentHeight 256 | |> Maybe.withDefault 0 257 | in 258 | ( { state 259 | | zipList = newZipList 260 | , open = True 261 | , mouseFocus = Nothing 262 | , query = "" 263 | , entryHeights = heights.entries 264 | , menuHeight = heights.menu 265 | , scrollTop = scrollTop 266 | } 267 | , scroll state.id (top - (heights.menu - height) / 2) 268 | , Nothing 269 | ) 270 | 271 | CloseMenu -> 272 | if state.preventBlur then 273 | ( state, Cmd.none, Nothing ) 274 | else 275 | ( state |> reset 276 | , Cmd.none 277 | , Nothing 278 | ) 279 | 280 | FocusTextfield -> 281 | ( state 282 | , focus state.id 283 | , Nothing 284 | ) 285 | 286 | BlurTextfield -> 287 | ( state 288 | , blur state.id 289 | , Nothing 290 | ) 291 | 292 | PreventClosing preventBlur -> 293 | ( { state | preventBlur = preventBlur } 294 | , Cmd.none 295 | , Nothing 296 | ) 297 | 298 | SetQuery newQuery -> 299 | let 300 | newZipList = 301 | fromListWithFilter newQuery state.entries state.entryHeights 302 | in 303 | ( { state 304 | | query = newQuery 305 | , zipList = newZipList 306 | , mouseFocus = Nothing 307 | } 308 | , scroll state.id 0 309 | , Just (select Nothing) 310 | ) 311 | 312 | SetMouseFocus newFocus -> 313 | ( { state | mouseFocus = newFocus } 314 | , Cmd.none 315 | , Nothing 316 | ) 317 | 318 | Select a -> 319 | let 320 | selection = 321 | a |> selectFirst state.entries 322 | in 323 | ( state |> reset 324 | , Cmd.none 325 | , Just (select (Just a)) 326 | ) 327 | 328 | SetKeyboardFocus movement scrollTop -> 329 | state 330 | |> updateKeyboardFocus select movement 331 | |> scrollToKeyboardFocus state.id scrollTop 332 | 333 | SelectKeyboardFocusAndBlur -> 334 | let 335 | selection = 336 | state.zipList 337 | |> Maybe.andThen currentEntry 338 | |> Maybe.andThen (selectFirst state.entries) 339 | in 340 | ( state |> reset 341 | , blur state.id 342 | , Just (select (state.zipList |> Maybe.andThen currentEntry)) 343 | ) 344 | 345 | ClearSelection -> 346 | ( state 347 | , Cmd.none 348 | , Just (select Nothing) 349 | ) 350 | 351 | 352 | type alias WithKeyboardFocus a r = 353 | { r | keyboardFocus : Maybe a } 354 | 355 | 356 | reset : State a -> State a 357 | reset state = 358 | { state 359 | | query = "" 360 | , zipList = Nothing 361 | , open = False 362 | , mouseFocus = Nothing 363 | } 364 | 365 | 366 | updateKeyboardFocus : 367 | (Maybe a -> msg) 368 | -> Movement 369 | -> State a 370 | -> ( State a, Cmd (Msg a), Maybe msg ) 371 | updateKeyboardFocus select movement state = 372 | let 373 | newZipList = 374 | case movement of 375 | Up -> 376 | state.zipList 377 | |> Maybe.map zipPrevious 378 | 379 | Down -> 380 | state.zipList 381 | |> Maybe.map zipNext 382 | 383 | _ -> 384 | state.zipList 385 | in 386 | ( { state 387 | | zipList = newZipList 388 | } 389 | , Cmd.none 390 | , Just (select Nothing) 391 | ) 392 | 393 | 394 | scrollToKeyboardFocus : 395 | String 396 | -> Float 397 | -> ( State a, Cmd (Msg a), Maybe msg ) 398 | -> ( State a, Cmd (Msg a), Maybe msg ) 399 | scrollToKeyboardFocus id scrollTop ( state, cmd, maybeMsg ) = 400 | case state.zipList of 401 | Just zipList -> 402 | let 403 | top = 404 | zipList.currentTop 405 | 406 | height = 407 | zipCurrentHeight zipList 408 | 409 | y = 410 | if top < scrollTop then 411 | top 412 | else if 413 | (top + height) 414 | > (scrollTop + state.menuHeight) 415 | then 416 | top + height - state.menuHeight 417 | else 418 | scrollTop 419 | in 420 | ( state 421 | , Cmd.batch [ scroll id y, cmd ] 422 | , maybeMsg 423 | ) 424 | 425 | Nothing -> 426 | ( state 427 | , cmd 428 | , maybeMsg 429 | ) 430 | 431 | 432 | 433 | ---- VIEW 434 | 435 | 436 | view : 437 | ViewConfig a 438 | -> Maybe a 439 | -> State a 440 | -> Html (Msg a) 441 | view config selection state = 442 | let 443 | selectionText = 444 | selection 445 | |> Maybe.andThen (selectFirst state.entries) 446 | |> Maybe.map Tuple.second 447 | 448 | menuAttrs = 449 | [ Attributes.id (menuId state.id) 450 | , Events.onMouseDown (PreventClosing True) 451 | , Events.onMouseUp (PreventClosing False) 452 | , Attributes.style "position" "absolute" 453 | ] 454 | ++ noOp config.menu 455 | in 456 | case state.zipList of 457 | Nothing -> 458 | Html.div 459 | ((config.container |> noOp) 460 | ++ [ Attributes.style "overflow" "hidden" 461 | , Attributes.style "position" "relative" 462 | ] 463 | ) 464 | [ config.input 465 | state.id 466 | selectionText 467 | state.query 468 | state.open 469 | , Html.div menuAttrs 470 | [ state.entries 471 | |> List.map 472 | (removeLabel >> viewUnfocusedEntry config Nothing) 473 | |> Html.ul (noOp config.ul) 474 | ] 475 | ] 476 | 477 | Just zipList -> 478 | Html.div 479 | [ Attributes.style "position" "relative" 480 | ] 481 | [ config.input 482 | state.id 483 | selectionText 484 | state.query 485 | state.open 486 | , Html.div menuAttrs 487 | [ [ zipList.front 488 | |> viewEntries config state 489 | |> List.reverse 490 | , [ zipList.current 491 | |> viewCurrentEntry config state 492 | ] 493 | , zipList.back 494 | |> viewEntries config state 495 | ] 496 | |> List.concat 497 | |> Html.ul (noOp config.ul) 498 | ] 499 | ] 500 | 501 | 502 | viewEntries : 503 | ViewConfig a 504 | -> State a 505 | -> List (EntryWithHeight a) 506 | -> List (Html (Msg a)) 507 | viewEntries config state front = 508 | List.map 509 | (Tuple.first 510 | >> Html.Lazy.lazy3 viewUnfocusedEntry 511 | config 512 | state.mouseFocus 513 | ) 514 | front 515 | 516 | 517 | viewCurrentEntry : 518 | ViewConfig a 519 | -> State a 520 | -> EntryWithHeight a 521 | -> Html (Msg a) 522 | viewCurrentEntry config state current = 523 | current 524 | |> Tuple.first 525 | |> viewFocusedEntry config state.mouseFocus 526 | 527 | 528 | viewUnfocusedEntry : 529 | { r 530 | | entry : a -> Bool -> Bool -> HtmlDetails Never 531 | , divider : String -> HtmlDetails Never 532 | } 533 | -> Maybe a 534 | -> Entry a 535 | -> Html (Msg a) 536 | viewUnfocusedEntry config = 537 | viewEntry config False 538 | 539 | 540 | viewFocusedEntry : 541 | { r 542 | | entry : a -> Bool -> Bool -> HtmlDetails Never 543 | , divider : String -> HtmlDetails Never 544 | } 545 | -> Maybe a 546 | -> Entry a 547 | -> Html (Msg a) 548 | viewFocusedEntry config = 549 | viewEntry config True 550 | 551 | 552 | viewEntry : 553 | { r 554 | | entry : a -> Bool -> Bool -> HtmlDetails Never 555 | , divider : String -> HtmlDetails Never 556 | } 557 | -> Bool 558 | -> Maybe a 559 | -> Entry a 560 | -> Html (Msg a) 561 | viewEntry config keyboardFocused mouseFocus e = 562 | let 563 | { attributes, children } = 564 | case e of 565 | Entry actualEntry -> 566 | config.entry actualEntry 567 | (mouseFocus == Just actualEntry) 568 | keyboardFocused 569 | 570 | Divider title -> 571 | config.divider title 572 | 573 | liAttrs attrs = 574 | attrs ++ noOp attributes 575 | in 576 | Html.li 577 | (liAttrs <| 578 | case e of 579 | Entry actualEntry -> 580 | [ Events.onClick (Select actualEntry) 581 | , Events.onMouseEnter (SetMouseFocus (Just actualEntry)) 582 | , Events.onMouseLeave (SetMouseFocus Nothing) 583 | ] 584 | 585 | _ -> 586 | [] 587 | ) 588 | (children |> List.map mapToNoOp) 589 | 590 | 591 | type alias Input a = 592 | String 593 | -> Maybe String 594 | -> String 595 | -> Bool 596 | -> Html (Msg a) 597 | 598 | 599 | simple : 600 | { attrs : Bool -> Bool -> List (Html.Attribute Never) 601 | , toggleButton : Maybe (Bool -> Html Never) 602 | , clearButton : Maybe (Html Never) 603 | , placeholder : String 604 | } 605 | -> String 606 | -> Maybe String 607 | -> String 608 | -> Bool 609 | -> Html (Msg a) 610 | simple config id selection _ open = 611 | let 612 | buttonAttrs = 613 | [ [ Attributes.id (textfieldId id) 614 | , Attributes.tabindex 0 615 | , Attributes.style "-webkit-touch-callout" "none" 616 | , Attributes.style "-webkit-user-select" "none" 617 | , Attributes.style "-moz-user-select" "none" 618 | , Attributes.style "-ms-user-select" "none" 619 | , Attributes.style "user-select" "none" 620 | ] 621 | , if open then 622 | [ Events.onBlur CloseMenu 623 | , Events.on "keyup" keyupDecoder 624 | , Events.preventDefaultOn "keydown" keydownDecoder 625 | ] 626 | else 627 | [ Events.on "focus" focusDecoder ] 628 | , noOp (config.attrs (selection /= Nothing) open) 629 | ] 630 | |> List.concat 631 | 632 | actualText = 633 | selection 634 | |> Maybe.withDefault config.placeholder 635 | in 636 | Html.div [] 637 | [ Html.div buttonAttrs 638 | [ Html.text actualText ] 639 | , buttons 640 | config.clearButton 641 | config.toggleButton 642 | (selection /= Nothing) 643 | open 644 | ] 645 | 646 | 647 | autocomplete : 648 | { attrs : Bool -> Bool -> List (Html.Attribute Never) 649 | , toggleButton : Maybe (Bool -> Html Never) 650 | , clearButton : Maybe (Html Never) 651 | , placeholder : String 652 | } 653 | -> String 654 | -> Maybe String 655 | -> String 656 | -> Bool 657 | -> Html (Msg a) 658 | autocomplete config id selection query open = 659 | let 660 | inputAttrs = 661 | [ [ Attributes.value query 662 | , Attributes.id (textfieldId id) 663 | , Events.on "focus" focusDecoder 664 | ] 665 | , if selection == Nothing then 666 | if open then 667 | [ Attributes.placeholder config.placeholder ] 668 | else 669 | [ Attributes.value config.placeholder ] 670 | else 671 | [ Attributes.style "color" "transparent" ] 672 | , if open then 673 | [ Events.onBlur CloseMenu 674 | , Events.on "keyup" keyupDecoder 675 | , Events.preventDefaultOn "keydown" keydownDecoder 676 | , Events.onInput SetQuery 677 | ] 678 | else 679 | [] 680 | , noOp (config.attrs (selection /= Nothing) open) 681 | ] 682 | |> List.concat 683 | in 684 | Html.div [] 685 | [ Html.input inputAttrs [] 686 | , Html.div 687 | ([ Attributes.style "position" "absolute" 688 | , Attributes.style "width" "100%" 689 | , Attributes.style "height" "100%" 690 | , Attributes.style "left" "0" 691 | , Attributes.style "top" "0" 692 | , Attributes.style "pointer-events" "none" 693 | , Attributes.style "border-color" "transparent" 694 | , Attributes.style "background-color" "transparent" 695 | , Attributes.style "box-shadow" "none" 696 | ] 697 | ++ noOp (config.attrs (selection /= Nothing) open) 698 | ) 699 | [ Html.text (selection |> Maybe.withDefault "") ] 700 | , buttons 701 | config.clearButton 702 | config.toggleButton 703 | (selection /= Nothing) 704 | open 705 | ] 706 | 707 | 708 | buttons : 709 | Maybe (Html Never) 710 | -> Maybe (Bool -> Html Never) 711 | -> Bool 712 | -> Bool 713 | -> Html (Msg a) 714 | buttons clearButton toggleButton sthSelected open = 715 | Html.div 716 | [ Attributes.style "position" "absolute" 717 | , Attributes.style "right" "0" 718 | , Attributes.style "top" "0" 719 | , Attributes.style "display" "flex" 720 | ] 721 | [ case ( clearButton, sthSelected ) of 722 | ( Just clear, True ) -> 723 | Html.div 724 | [ Events.onClick ClearSelection ] 725 | [ clear |> mapToNoOp ] 726 | 727 | _ -> 728 | Html.text "" 729 | , case toggleButton of 730 | Just toggle -> 731 | Html.div 732 | [ case open of 733 | True -> 734 | Events.preventDefaultOn "click" 735 | (Decode.succeed ( BlurTextfield, True )) 736 | 737 | False -> 738 | Events.preventDefaultOn "click" 739 | (Decode.succeed ( FocusTextfield, True )) 740 | ] 741 | [ toggle open |> mapToNoOp ] 742 | 743 | Nothing -> 744 | Html.div [] [] 745 | ] 746 | 747 | 748 | focusDecoder : Decoder (Msg a) 749 | focusDecoder = 750 | Decode.map3 751 | (\entryHeights menuHeight scrollTop -> 752 | OpenMenu { entries = entryHeights, menu = menuHeight } scrollTop 753 | ) 754 | entryHeightsDecoder 755 | menuHeightDecoder 756 | scrollTopDecoder 757 | 758 | 759 | keydownDecoder : Decoder ( Msg a, Bool ) 760 | keydownDecoder = 761 | Decode.map2 762 | (\code scrollTop -> 763 | case code of 764 | 38 -> 765 | Ok ( SetKeyboardFocus Up scrollTop, True ) 766 | 767 | 40 -> 768 | Ok ( SetKeyboardFocus Down scrollTop, True ) 769 | 770 | 13 -> 771 | Ok ( SelectKeyboardFocusAndBlur, True ) 772 | 773 | 27 -> 774 | Ok ( BlurTextfield, True ) 775 | 776 | _ -> 777 | Err "not handling that key here" 778 | ) 779 | Events.keyCode 780 | scrollTopDecoder 781 | |> Decode.andThen fromResult 782 | 783 | 784 | keyupDecoder : Decoder (Msg a) 785 | keyupDecoder = 786 | Events.keyCode 787 | |> Decode.map 788 | (\code -> 789 | case code of 790 | 8 -> 791 | Ok ClearSelection 792 | 793 | 46 -> 794 | Ok ClearSelection 795 | 796 | _ -> 797 | Err "not handling that key here" 798 | ) 799 | |> Decode.andThen fromResult 800 | 801 | 802 | 803 | ---- HELPER 804 | 805 | 806 | contains : String -> String -> Bool 807 | contains query label = 808 | label 809 | |> String.toLower 810 | |> String.contains (String.toLower query) 811 | 812 | 813 | 814 | ---- VIEW HELPER 815 | 816 | 817 | menuId : String -> String 818 | menuId id = 819 | id ++ "__menu" 820 | 821 | 822 | textfieldId : String -> String 823 | textfieldId id = 824 | id ++ "__textfield" 825 | 826 | 827 | noOp : List (Html.Attribute Never) -> List (Html.Attribute (Msg a)) 828 | noOp attrs = 829 | List.map (Attributes.map (\_ -> NoOp)) attrs 830 | 831 | 832 | mapToNoOp : Html Never -> Html (Msg a) 833 | mapToNoOp = 834 | Html.map (\_ -> NoOp) 835 | 836 | 837 | 838 | ---- CMDS 839 | 840 | 841 | scroll : String -> Float -> Cmd (Msg a) 842 | scroll id y = 843 | Task.attempt (\_ -> NoOp) <| 844 | (Dom.getViewportOf (menuId id) 845 | |> Task.andThen 846 | (\{ viewport } -> 847 | Dom.setViewportOf (menuId id) viewport.x y 848 | ) 849 | ) 850 | 851 | 852 | focus : String -> Cmd (Msg a) 853 | focus id = 854 | Task.attempt (\_ -> NoOp) <| 855 | Dom.focus (textfieldId id) 856 | 857 | 858 | blur : String -> Cmd (Msg a) 859 | blur id = 860 | Task.attempt (\_ -> NoOp) <| 861 | Dom.blur (textfieldId id) 862 | 863 | 864 | 865 | ---- DECODER 866 | 867 | 868 | entryHeightsDecoder : Decoder (List Float) 869 | entryHeightsDecoder = 870 | let 871 | loop idx xs = 872 | Decode.maybe 873 | (Decode.at 874 | [ String.fromInt idx 875 | , "offsetHeight" 876 | ] 877 | Decode.float 878 | ) 879 | |> Decode.andThen 880 | (Maybe.map (\x -> loop (idx + 1) (x :: xs)) 881 | >> Maybe.withDefault (Decode.succeed xs) 882 | ) 883 | in 884 | Decode.map List.reverse <| 885 | Decode.at 886 | [ "target" 887 | , "parentElement" 888 | , "parentElement" 889 | , "childNodes" 890 | , "1" 891 | , "childNodes" 892 | , "0" 893 | , "childNodes" 894 | ] 895 | (loop 0 []) 896 | 897 | 898 | menuHeightDecoder : Decoder Float 899 | menuHeightDecoder = 900 | Decode.at 901 | [ "target" 902 | , "parentElement" 903 | , "parentElement" 904 | , "childNodes" 905 | , "1" 906 | , "clientHeight" 907 | ] 908 | Decode.float 909 | 910 | 911 | scrollTopDecoder : Decoder Float 912 | scrollTopDecoder = 913 | Decode.at 914 | [ "target" 915 | , "parentElement" 916 | , "parentElement" 917 | , "childNodes" 918 | , "1" 919 | , "scrollTop" 920 | ] 921 | Decode.float 922 | 923 | 924 | fromResult : Result String a -> Decoder a 925 | fromResult result = 926 | case result of 927 | Ok val -> 928 | Decode.succeed val 929 | 930 | Err reason -> 931 | Decode.fail reason 932 | 933 | 934 | 935 | ---- ZIPLIST 936 | 937 | 938 | type alias ZipList a = 939 | { front : List (EntryWithHeight a) 940 | , current : EntryWithHeight a 941 | , back : List (EntryWithHeight a) 942 | , currentTop : Float 943 | } 944 | 945 | 946 | type alias EntryWithHeight a = 947 | ( Entry a, Float ) 948 | 949 | 950 | currentEntry : { r | current : EntryWithHeight a } -> Maybe a 951 | currentEntry { current } = 952 | case current of 953 | ( Entry a, _ ) -> 954 | Just a 955 | 956 | _ -> 957 | Nothing 958 | 959 | 960 | zipCurrentHeight : { r | current : EntryWithHeight a } -> Float 961 | zipCurrentHeight { current } = 962 | current |> Tuple.second 963 | 964 | 965 | fromList : List (LEntry a) -> List Float -> Maybe (ZipList a) 966 | fromList entries entryHeights = 967 | case ( entries |> List.map removeLabel, entryHeights ) of 968 | ( firstEntry :: restEntries, firstHeight :: restHeights ) -> 969 | { front = [] 970 | , current = ( firstEntry, firstHeight ) 971 | , back = zip restEntries restHeights 972 | , currentTop = 0 973 | } 974 | |> zipFirst 975 | 976 | _ -> 977 | Nothing 978 | 979 | 980 | fromListWithFilter : 981 | String 982 | -> List (LEntry a) 983 | -> List Float 984 | -> Maybe (ZipList a) 985 | fromListWithFilter query entries entryHeights = 986 | let 987 | filtered = 988 | zip entries entryHeights 989 | |> List.filterMap 990 | (\( e, height ) -> 991 | case e of 992 | LEntry a label -> 993 | if label |> contains query then 994 | Just ( Entry a, height ) 995 | else 996 | Nothing 997 | 998 | LDivider text -> 999 | Just ( Divider text, height ) 1000 | ) 1001 | in 1002 | case filtered of 1003 | first :: rest -> 1004 | { front = [] 1005 | , current = first 1006 | , back = rest 1007 | , currentTop = 0 1008 | } 1009 | |> zipFirst 1010 | 1011 | _ -> 1012 | Nothing 1013 | 1014 | 1015 | zipFirst : ZipList a -> Maybe (ZipList a) 1016 | zipFirst ({ front, current, back, currentTop } as zipList) = 1017 | case current of 1018 | ( Divider _, _ ) -> 1019 | case back of 1020 | [] -> 1021 | Nothing 1022 | 1023 | next :: rest -> 1024 | { front = current :: front 1025 | , current = next 1026 | , back = rest 1027 | , currentTop = currentTop + Tuple.second current 1028 | } 1029 | |> zipFirst 1030 | 1031 | _ -> 1032 | Just zipList 1033 | 1034 | 1035 | zipReverseFirst : ZipList a -> Maybe (ZipList a) 1036 | zipReverseFirst ({ front, current, back, currentTop } as zipList) = 1037 | case current of 1038 | ( Divider _, _ ) -> 1039 | case front of 1040 | [] -> 1041 | Nothing 1042 | 1043 | previous :: rest -> 1044 | { front = rest 1045 | , current = previous 1046 | , back = current :: back 1047 | , currentTop = currentTop - Tuple.second previous 1048 | } 1049 | |> zipReverseFirst 1050 | 1051 | _ -> 1052 | Just zipList 1053 | 1054 | 1055 | zipNext : ZipList a -> ZipList a 1056 | zipNext ({ front, current, back, currentTop } as zipList) = 1057 | case back of 1058 | [] -> 1059 | zipList 1060 | 1061 | next :: rest -> 1062 | { front = current :: front 1063 | , current = next 1064 | , back = rest 1065 | , currentTop = currentTop + Tuple.second current 1066 | } 1067 | |> zipFirst 1068 | |> Maybe.withDefault zipList 1069 | 1070 | 1071 | zipPrevious : ZipList a -> ZipList a 1072 | zipPrevious ({ front, current, back, currentTop } as zipList) = 1073 | case front of 1074 | [] -> 1075 | zipList 1076 | 1077 | previous :: rest -> 1078 | { front = rest 1079 | , current = previous 1080 | , back = current :: back 1081 | , currentTop = currentTop - Tuple.second previous 1082 | } 1083 | |> zipReverseFirst 1084 | |> Maybe.withDefault zipList 1085 | 1086 | 1087 | moveForwardTo : a -> ZipList a -> ZipList a 1088 | moveForwardTo a zipList = 1089 | moveForwardToHelper a zipList 1090 | |> Maybe.withDefault zipList 1091 | 1092 | 1093 | moveForwardToHelper : 1094 | a 1095 | -> ZipList a 1096 | -> Maybe (ZipList a) 1097 | moveForwardToHelper a zipList = 1098 | if (zipList.current |> Tuple.first) == Entry a then 1099 | Just zipList 1100 | else 1101 | case zipList.back of 1102 | [] -> 1103 | Nothing 1104 | 1105 | _ -> 1106 | zipList 1107 | |> zipNext 1108 | |> moveForwardToHelper a 1109 | 1110 | 1111 | zip : List a -> List b -> List ( a, b ) 1112 | zip listA listB = 1113 | zipHelper listA listB [] |> List.reverse 1114 | 1115 | 1116 | zipHelper : List a -> List b -> List ( a, b ) -> List ( a, b ) 1117 | zipHelper listA listB sum = 1118 | case ( listA, listB ) of 1119 | ( a :: restA, b :: restB ) -> 1120 | zipHelper restA restB (( a, b ) :: sum) 1121 | 1122 | _ -> 1123 | sum 1124 | -------------------------------------------------------------------------------- /tests/ArchitectureTests.elm: -------------------------------------------------------------------------------- 1 | module ArchitectureTests exposing (..) 2 | 3 | import ArchitectureTest exposing (..) 4 | import ArchitectureTest.Types exposing (..) 5 | import Expect exposing (Expectation) 6 | import Fuzz exposing (Fuzzer) 7 | import Random 8 | import Selectize.Selectize as S 9 | import Test exposing (..) 10 | 11 | 12 | suite : Test 13 | suite = 14 | describe "architecture tests" 15 | [ testUpdate ] 16 | 17 | 18 | 19 | {- actual tests -} 20 | 21 | 22 | testUpdate : Test 23 | testUpdate = 24 | concat 25 | [ msgTestWithPrecondition "reset model if textfield blured and preventBlur is False" 26 | app 27 | textfieldBlured 28 | (\model -> not model.menu.preventBlur) 29 | <| 30 | \_ _ _ _ finalModel -> 31 | finalModel.menu 32 | |> expectReset 33 | , msgTestWithPrecondition "do not change model if textfield blured and preventBlur is True" 34 | app 35 | textfieldBlured 36 | (\model -> model.menu.preventBlur) 37 | <| 38 | \_ _ beforeMsgModel _ finalModel -> 39 | beforeMsgModel 40 | |> Expect.equal finalModel 41 | , msgTest "model is reseted after sth is selected" 42 | app 43 | (Fuzz.oneOf 44 | [ select 45 | , selectKeyboardFocusAndBlur 46 | ] 47 | ) 48 | <| 49 | \_ _ _ _ finalModel -> 50 | finalModel.menu 51 | |> expectReset 52 | , invariantTest "mouseFocus and keyboardFocus are always from the list" 53 | app 54 | <| 55 | \_ _ finalModel -> 56 | finalModel.menu 57 | |> Expect.all 58 | [ .mouseFocus >> expectMember 59 | , .zipList >> Maybe.map S.currentEntry >> expectMember 60 | ] 61 | , msgTest "First possible entry is keyboardFocused after query change" 62 | app 63 | setQuery 64 | <| 65 | \_ _ _ _ finalModel -> 66 | let 67 | filter entry = 68 | case entry of 69 | S.LEntry _ label -> 70 | if label |> S.contains finalModel.menu.query then 71 | Just entry 72 | else 73 | Nothing 74 | 75 | _ -> 76 | Just entry 77 | in 78 | if finalModel.menu.open then 79 | finalModel.menu.zipList 80 | |> Maybe.map S.currentEntry 81 | |> Maybe.map entry 82 | |> Expect.equal 83 | (treesWithoutDivider 84 | |> List.filterMap filter 85 | |> List.head 86 | ) 87 | else 88 | Expect.pass 89 | , msgTest "model is unchanged after clear selection" 90 | app 91 | clearSelection 92 | <| 93 | \_ _ beforeMsgModel _ finalModel -> 94 | beforeMsgModel 95 | |> Expect.equal finalModel 96 | ] 97 | 98 | 99 | 100 | {- expectations -} 101 | 102 | 103 | expectReset : S.State String -> Expectation 104 | expectReset menu = 105 | menu 106 | |> Expect.all 107 | [ .query >> Expect.equal "" 108 | , .zipList >> Expect.equal Nothing 109 | , .mouseFocus >> Expect.equal Nothing 110 | , .open >> Expect.false "Expected the menu to be closed" 111 | ] 112 | 113 | 114 | expectMember : Maybe String -> Expectation 115 | expectMember maybeFocus = 116 | case maybeFocus of 117 | Just focus -> 118 | List.member (S.entry focus) trees 119 | |> Expect.true "Expected the focus to be in the list of possible entries" 120 | 121 | Nothing -> 122 | Expect.pass 123 | 124 | 125 | 126 | {- setup -} 127 | 128 | 129 | app : TestedApp Model (S.Msg String) 130 | app = 131 | { model = ConstantModel init 132 | , update = BeginnerUpdate update 133 | , msgFuzzer = msg 134 | } 135 | 136 | 137 | type alias Model = 138 | { menu : S.State String 139 | , selection : Maybe String 140 | } 141 | 142 | 143 | init : Model 144 | init = 145 | { menu = S.closed "menu" identity trees 146 | , selection = Nothing 147 | } 148 | 149 | 150 | update : S.Msg String -> Model -> Model 151 | update msg model = 152 | let 153 | ( newMenu, _, _ ) = 154 | S.update (\_ -> ()) model.selection model.menu msg 155 | in 156 | { model | menu = newMenu } 157 | 158 | 159 | 160 | {- msg fuzzer -} 161 | 162 | 163 | msg : Fuzzer (S.Msg String) 164 | msg = 165 | Fuzz.oneOf 166 | -- TODO: add setPreventBlur 167 | [ noOp 168 | , textfieldFocused 169 | , textfieldBlured 170 | , blurTextfield 171 | , setQuery 172 | , setMouseFocus 173 | , select 174 | , setKeyboardFocus 175 | , selectKeyboardFocusAndBlur 176 | , clearSelection 177 | ] 178 | 179 | 180 | noOp : Fuzzer (S.Msg String) 181 | noOp = 182 | Fuzz.constant S.NoOp 183 | 184 | 185 | textfieldFocused : Fuzzer (S.Msg String) 186 | textfieldFocused = 187 | Fuzz.map2 S.OpenMenu 188 | (Fuzz.map2 189 | S.Heights 190 | (listCount (Fuzz.intRange 0 42 |> Fuzz.map toFloat) (List.length trees)) 191 | (Fuzz.intRange 0 100 |> Fuzz.map toFloat) 192 | ) 193 | (Fuzz.intRange 0 1000 |> Fuzz.map toFloat) 194 | 195 | 196 | textfieldBlured : Fuzzer (S.Msg String) 197 | textfieldBlured = 198 | Fuzz.constant S.CloseMenu 199 | 200 | 201 | blurTextfield : Fuzzer (S.Msg String) 202 | blurTextfield = 203 | Fuzz.constant S.BlurTextfield 204 | 205 | 206 | setQuery : Fuzzer (S.Msg String) 207 | setQuery = 208 | Fuzz.string |> Fuzz.map S.SetQuery 209 | 210 | 211 | setMouseFocus : Fuzzer (S.Msg String) 212 | setMouseFocus = 213 | (treesPart1 ++ treesPart2) 214 | |> List.map Fuzz.constant 215 | |> Fuzz.oneOf 216 | |> Fuzz.maybe 217 | |> Fuzz.map S.SetMouseFocus 218 | 219 | 220 | select : Fuzzer (S.Msg String) 221 | select = 222 | (treesPart1 ++ treesPart2) 223 | |> List.map Fuzz.constant 224 | |> Fuzz.oneOf 225 | |> Fuzz.map S.Select 226 | 227 | 228 | setKeyboardFocus : Fuzzer (S.Msg String) 229 | setKeyboardFocus = 230 | Fuzz.map2 S.SetKeyboardFocus 231 | movement 232 | (Fuzz.intRange 0 1000 |> Fuzz.map toFloat) 233 | 234 | 235 | selectKeyboardFocusAndBlur : Fuzzer (S.Msg String) 236 | selectKeyboardFocusAndBlur = 237 | Fuzz.constant S.SelectKeyboardFocusAndBlur 238 | 239 | 240 | movement : Fuzzer S.Movement 241 | movement = 242 | Fuzz.oneOf 243 | [ Fuzz.constant S.Up 244 | , Fuzz.constant S.Down 245 | ] 246 | 247 | 248 | clearSelection : Fuzzer (S.Msg String) 249 | clearSelection = 250 | Fuzz.constant S.ClearSelection 251 | 252 | 253 | 254 | {- fuzzer -} 255 | 256 | 257 | listCount : Fuzzer a -> Int -> Fuzzer (List a) 258 | listCount fuzzer count = 259 | if count > 1 then 260 | Fuzz.map2 (\a rest -> a :: rest) 261 | fuzzer 262 | (listCount fuzzer (count - 1)) 263 | else 264 | fuzzer |> Fuzz.map (\a -> [ a ]) 265 | 266 | 267 | 268 | {- data -} 269 | 270 | 271 | entry tree = 272 | S.LEntry tree tree 273 | 274 | 275 | trees : List (S.Entry String) 276 | trees = 277 | List.concat 278 | [ [ S.divider "First Part" ] 279 | , treesPart1 |> List.map S.entry 280 | , [ S.divider "Second Part" ] 281 | , treesPart2 |> List.map S.entry 282 | ] 283 | 284 | 285 | treesWithoutDivider : List (S.LEntry String) 286 | treesWithoutDivider = 287 | List.concat 288 | [ treesPart1 |> List.map entry 289 | , treesPart2 |> List.map entry 290 | ] 291 | 292 | 293 | treesPart1 : List String 294 | treesPart1 = 295 | [ "Abelia x grandiflora" 296 | , "Abienus festuschristus" 297 | , "Abies alba" 298 | , "Abies balsamea" 299 | , "Abies cephalonica" 300 | , "Abies concolor" 301 | , "Abies equi-trojani" 302 | , "Abies grandis" 303 | , "Abies holophylla" 304 | , "Abies homolepis" 305 | , "Abies koreana" 306 | , "Abies lasiocarpa" 307 | , "Abies nordmanniana" 308 | , "Abies pinsapo" 309 | , "Abies procera" 310 | , "Abies procera 'Glauca'" 311 | , "Abies veitchii" 312 | , "Acacia dealbata" 313 | , "Acacia karroo" 314 | , "Acacia retinodes" 315 | , "Acer buergerianum" 316 | , "Acer campestre" 317 | , "Acer cappadocicum" 318 | , "Acer carpinifolium" 319 | , "Acer caudatum subsp. caudatum" 320 | , "Acer circinatum" 321 | , "Acer cissifolium" 322 | , "Acer coriaceifolium" 323 | , "Acer davidii" 324 | , "Acer davidii subsp. grosserii" 325 | , "Acer griseum" 326 | , "Acer japonicum 'Aconitifolium'" 327 | , "Acer macrophyllum" 328 | , "Acer mandshuricum" 329 | , "Acer monspessulanum" 330 | , "Acer negundo" 331 | , "Acer opalus" 332 | , "Acer opalus ssp. obtusatum" 333 | , "Acer palmatum" 334 | , "Acer palmatum 'Dissectum Viridis'" 335 | , "Acer pensylvanicum" 336 | , "Acer platanoides" 337 | , "Acer platanoides 'Crimson King'" 338 | , "Acer platanoides f. drummondii" 339 | , "Acer pseudoplatanus" 340 | , "Acer pseudoplatanus 'Leopoldii'" 341 | , "Acer pseudoplatanus f. atropurpureum" 342 | , "Acer rubrum" 343 | , "Acer rufinerve" 344 | , "Acer saccharinum" 345 | , "Acer saccharum" 346 | , "Acer sempervirens" 347 | , "Acer tataricum" 348 | , "Acer tataricum subsp. ginnala" 349 | , "Acer tegmentosum" 350 | , "Acer triflorum" 351 | , "Acer x zoeschense" 352 | , "Actinidia deliciosa" 353 | , "Actinidia kolomikta" 354 | , "Adonidia merrillii" 355 | , "Aesculus flava" 356 | , "Aesculus glabra" 357 | , "Aesculus hippocastanum" 358 | , "Aesculus indica" 359 | , "Aesculus parviflora" 360 | , "Aesculus pavia" 361 | , "Aesculus turbinata" 362 | , "Aesculus x carnea" 363 | , "Aesculus x mutabilis 'Penduliflora'" 364 | , "Aesculus x neglecta 'Erythroblastos'" 365 | , "Afzelia africana" 366 | , "Ailanthus altissima" 367 | , "Akebia quinata" 368 | , "Alangium platanifolium" 369 | , "Albizia julibrissin" 370 | , "Allocasuarina luehmannii" 371 | , "Alnus cordata" 372 | , "Alnus glutinosa" 373 | , "Alnus incana" 374 | , "Alnus sinuata" 375 | , "Alnus viridis" 376 | , "Amelanchier asiatica" 377 | , "Amelanchier laevis" 378 | , "Amelanchier lamarckii" 379 | , "Amelanchier ovalis" 380 | , "Amelanchier spicata" 381 | , "Amorpha fructicosa" 382 | , "Anacardium occidentale" 383 | , "Anagyris foetida" 384 | , "Andromeda polifolia" 385 | , "Annona cherimola" 386 | , "Aralia elata" 387 | , "Araucaria araucana" 388 | , "Araucaria bidwillii" 389 | , "Araucaria columnaris" 390 | , "Araucaria cunninghamii" 391 | , "Araucaria heterophylla" 392 | , "Arbutus menziesii" 393 | , "Arbutus unedo" 394 | , "Argania spinosa" 395 | , "Argyrocytisus battandieri" 396 | , "Aronia arbutifolia" 397 | , "Aronia melanocarpa" 398 | , "Aronia x prunifolia" 399 | , "Asimina triloba" 400 | , "Asparagus acutifolius" 401 | , "Aucuba japonica" 402 | , "Averrhoa carambola" 403 | , "Barringtonia asiatica" 404 | , "Bauhinia kockiana" 405 | , "Bauhinia x blakeana" 406 | , "Berberis julianae" 407 | , "Berberis koreana" 408 | , "Berberis thunbergii" 409 | , "Berberis vulgaris" 410 | , "Betula alleghaniensis" 411 | , "Betula alnoides subsp. alnoides" 412 | , "Betula alnoides subsp. luminifera" 413 | , "Betula costata" 414 | , "Betula dahurica" 415 | , "Betula ermanii" 416 | , "Betula insignis" 417 | , "Betula lenta" 418 | , "Betula lenta f. uber" 419 | , "Betula maximowicziana" 420 | , "Betula nigra" 421 | , "Betula papyrifera" 422 | , "Betula pendula" 423 | , "Betula pendula f. dalecarlica" 424 | , "Betula populifolia" 425 | , "Betula pubescens" 426 | , "Betula utilis var. jacquemontii 'Doorenbos'" 427 | , "Bougainvillea glabra" 428 | , "Bougainvillea spectabilis" 429 | , "Brachychiton acerifolius" 430 | , "Brachychiton discolor" 431 | , "Brachychiton populneus" 432 | , "Brachychiton rupestris" 433 | , "Broussonetia papyrifera" 434 | , "Buddleja alternifolia" 435 | , "Buddleja davidii" 436 | , "Buddleja globosa" 437 | , "Buddleja x weyeriana" 438 | , "Butyrospermum parkii" 439 | , "Buxus sempervirens" 440 | , "Caesalpinia gilliesii" 441 | , "Calicotome spinosa" 442 | , "Callicarpa dichotoma" 443 | , "Callistemon citrinus" 444 | , "Calocedrus decurrens" 445 | , "Calocedrus decurrens 'Aureovariegata'" 446 | , "Calycanthus fertilis" 447 | , "Calycanthus floridus" 448 | , "Camellia japonica" 449 | , "Campsis radicans" 450 | , "Campsis x tagliabuana" 451 | , "Capparis spinosa" 452 | , "Caragana arborescens" 453 | , "Carpenteria californica" 454 | , "Carpinus betulus" 455 | , "Carpinus betulus 'Quercifolia'" 456 | , "Carpinus caroliniana" 457 | , "Carpinus japonica" 458 | , "Carya cordiformis" 459 | , "Carya illinoinensis" 460 | , "Carya laciniosa" 461 | , "Carya ovata" 462 | , "Carya tomentosa" 463 | , "Cascabela thevetia" 464 | , "Cassia siberiana" 465 | , "Castanea crenata" 466 | , "Castanea sativa" 467 | , "Casuarina cunninghamiana" 468 | , "Casuarina stricta" 469 | , "Catalpa bignonioides" 470 | , "Catalpa bungei" 471 | , "Catalpa ovata" 472 | , "Catalpa speciosa" 473 | , "Catalpa x erubescens" 474 | , "Ceanothus x delilianus" 475 | , "Cedrus atlantica" 476 | , "Cedrus atlantica 'Glauca'" 477 | , "Cedrus brevifolia" 478 | , "Cedrus deodara" 479 | , "Cedrus deodara 'Paktia'" 480 | , "Cedrus libani" 481 | , "Ceiba speciosa" 482 | , "Celtis australis" 483 | , "Celtis occidentalis" 484 | , "Cephalanthus occidentalis" 485 | , "Cephalotaxus harringtonia" 486 | , "Cephalotaxus sinensis" 487 | , "Ceratonia siliqua" 488 | , "Cercidiphyllum japonicum" 489 | , "Cercis canadensis" 490 | , "Cercis chinensis" 491 | , "Cercis siliquastrum" 492 | , "Chaenomeles japonica" 493 | , "Chaenomeles speciosa" 494 | , "Chamaecyparis lawsoniana" 495 | , "Chamaecyparis pisifera" 496 | , "Chamaerops humilis" 497 | , "Chimonanthus praecox" 498 | , "Chionanthus retusus" 499 | , "Chionanthus virginicus" 500 | , "Chitalpa tashkentensis" 501 | , "Choisya ternata" 502 | , "Cinnamomum camphora" 503 | , "Cistus albidus" 504 | , "Cistus crispus" 505 | , "Cistus incanus" 506 | , "Cistus incanus ssp. creticus" 507 | , "Cistus ladanifer" 508 | , "Cistus laurifolius" 509 | , "Cistus monspeliensis" 510 | , "Cistus populifolius" 511 | , "Cistus salviifolius" 512 | , "Cistus symphytifolius" 513 | , "Citharexylum spinosum" 514 | , "Citrus x aurantium" 515 | , "Citrus x limon" 516 | , "Cladrastis kentukea" 517 | , "Clematis flammula" 518 | , "Clematis montana" 519 | , "Clematis vitalba" 520 | , "Clematis viticella" 521 | , "Clerodendrum trichotomum" 522 | , "Clethra alnifolia" 523 | , "Cneorum tricoccon" 524 | , "Coccoloba uvifera" 525 | , "Cocos nucifera" 526 | , "Colutea arborescens" 527 | , "Cornus alba" 528 | , "Cornus controversa" 529 | , "Cornus florida" 530 | , "Cornus kousa" 531 | , "Cornus mas" 532 | , "Cornus nuttallii" 533 | , "Cornus officinalis" 534 | , "Cornus racemosa" 535 | , "Cornus sanguinea" 536 | , "Cornus sericea" 537 | , "Corylopsis pauciflora" 538 | , "Corylopsis spicata" 539 | , "Corylopsis veitchiana" 540 | , "Corylus avellana" 541 | , "Corylus avellana 'Contorta'" 542 | , "Corylus colurna" 543 | , "Corylus maxima" 544 | , "Corymbia dallachiana" 545 | , "Cotinus coggygria" 546 | , "Cotoneaster dielsianus" 547 | , "Cotoneaster floccosus" 548 | , "Cotoneaster frigidus" 549 | , "Cotoneaster horizontalis" 550 | , "Cotoneaster integerrimus" 551 | , "Cotoneaster multiflorus" 552 | , "Crataegus laevigata" 553 | , "Crataegus laevigata 'Paul's Scarlet'" 554 | , "Crataegus monogyna" 555 | , "Crataegus nigra" 556 | , "Crataegus pedicellata" 557 | , "Crataegus pinnatifida" 558 | , "Crataegus succulenta var. macrantha" 559 | , "Crataegus x lavallei 'Carrierei'" 560 | , "Crataemespilus grandiflora" 561 | , "Cryptomeria japonica" 562 | , "Cryptomeria japonica f. cristata" 563 | , "Cunninghamia lanceolata" 564 | , "Cupressus arizonica" 565 | , "Cupressus glabra" 566 | , "Cupressus sempervirens" 567 | , "Cycas revoluta" 568 | , "Cydonia oblonga" 569 | , "Cytisus scoparius" 570 | , "Danae racemosa" 571 | , "Daphne gnidium" 572 | , "Daphne mezereum" 573 | , "Dasiphora fruticosa" 574 | , "Davidia involucrata" 575 | , "Decaisnea fargesii" 576 | , "Delonix regia" 577 | , "Deutzia longifolia" 578 | , "Deutzia scabra" 579 | , "Deutzia x hybrida" 580 | , "Diospyros kaki" 581 | , "Diospyros lotus" 582 | , "Dipelta floribunda" 583 | , "Distylium racemosum" 584 | , "Dracaena draco" 585 | , "Duranta erecta" 586 | , "Edgeworthia chrysantha" 587 | , "Ehretia dicksonii" 588 | , "Elaeagnus angustifolia" 589 | , "Elaeagnus pungens" 590 | , "Elaeagnus umbellata" 591 | , "Elaeagnus x ebbingei" 592 | , "Eleutherococcus sieboldianus" 593 | , "Erica arborea" 594 | ] 595 | 596 | 597 | treesPart2 : List String 598 | treesPart2 = 599 | [ "Eriobotrya japonica" 600 | , "Erythrina crista-galli" 601 | , "Eucalyptus globulus" 602 | , "Eucommia ulmoides" 603 | , "Euonymus alatus" 604 | , "Euonymus europaeus" 605 | , "Euonymus fortunei" 606 | , "Euonymus macropterus" 607 | , "Euonymus planipes" 608 | , "Exochorda giraldii" 609 | , "Fagus grandifolia" 610 | , "Fagus orientalis" 611 | , "Fagus sylvatica" 612 | , "Fagus sylvatica 'Asplenifolia'" 613 | , "Fagus sylvatica 'Felderbach'" 614 | , "Fagus sylvatica 'Pendula'" 615 | , "Fagus sylvatica 'Purpurea'" 616 | , "Fagus sylvatica 'Tortuosa'" 617 | , "Fagus sylvatica f. purpurea tricolor" 618 | , "Fatsia japonica" 619 | , "Feijoa sellowiana" 620 | , "Ficus benjamina" 621 | , "Ficus carica" 622 | , "Ficus lyrata" 623 | , "Ficus macrophylla" 624 | , "Ficus sycomorus" 625 | , "Firmiana simplex" 626 | , "Forsythia x intermedia" 627 | , "Fortunella" 628 | , "Fothergilla major" 629 | , "Frangula alnus" 630 | , "Fraxinus angustifolia" 631 | , "Fraxinus excelsior" 632 | , "Fraxinus excelsior f. diversifolia" 633 | , "Fraxinus latifolia" 634 | , "Fraxinus ornus" 635 | , "Fraxinus paxiana" 636 | , "Ginkgo biloba L." 637 | , "Gleditsia triacanthos" 638 | , "Grevillea robusta" 639 | , "Gymnocladus dioicus" 640 | , "Halesia carolina" 641 | , "Halesia monticola" 642 | , "Halimodendron halodendron" 643 | , "Hamamelis virginiana" 644 | , "Hamamelis x intermedia" 645 | , "Hedera helix" 646 | , "Heptacodium miconioides" 647 | , "Hibiscus rosa-sinensis" 648 | , "Hibiscus syriacus" 649 | , "Hippophae rhamnoides" 650 | , "Holodiscus discolor" 651 | , "Humulus lupulus" 652 | , "Hura crepitans" 653 | , "Hydrangea arborescens" 654 | , "Hydrangea aspera ssp. aspera" 655 | , "Hydrangea aspera ssp. sargentiana" 656 | , "Hydrangea macrophylla" 657 | , "Hydrangea paniculata" 658 | , "Hydrangea petiolaris" 659 | , "Hydrangea quercifolia" 660 | , "Hypericum androsaemum" 661 | , "Hypericum balearicum" 662 | , "Ilex aquifolium" 663 | , "Ilex crenata" 664 | , "Ilex pernyi" 665 | , "Jacaranda mimosifolia" 666 | , "Jasminum nudiflorum" 667 | , "Jasminum officinale" 668 | , "Juglans ailantifolia" 669 | , "Juglans cinerea" 670 | , "Juglans nigra" 671 | , "Juglans regia" 672 | , "Juniperus communis" 673 | , "Juniperus oxycedrus" 674 | , "Juniperus sabina" 675 | , "Juniperus scopulorum" 676 | , "Kalmia latifolia" 677 | , "Kerria japonica 'Pleniflora'" 678 | , "Khaya senegalensis" 679 | , "Koelreuteria paniculata" 680 | , "Kolkwitzia amabilis" 681 | , "Laburnum alpinum" 682 | , "Laburnum anagyroides" 683 | , "Lagerstroemia indica" 684 | , "Lagerstroemia speciosa" 685 | , "Lagunaria patersonia" 686 | , "Lantana camara" 687 | , "Larix decidua" 688 | , "Larix kaempferi" 689 | , "Larix x marschlinsii" 690 | , "Laurus nobilis" 691 | , "Lavandula angustifolia" 692 | , "Lavandula stoechas" 693 | , "Leucaena leucocephala" 694 | , "Leycesteria formosa" 695 | , "Ligustrum japonicum" 696 | , "Ligustrum lucidum" 697 | , "Ligustrum ovalifolium" 698 | , "Ligustrum vulgare" 699 | , "Liquidambar formosana" 700 | , "Liquidambar orientalis" 701 | , "Liquidambar styraciflua" 702 | , "Liriodendron chinense" 703 | , "Liriodendron tulipifera" 704 | , "Lonicera caprifolium" 705 | , "Lonicera etrusca" 706 | , "Lonicera henryi" 707 | , "Lonicera implexa" 708 | , "Lonicera kamtschatica" 709 | , "Lonicera maackii" 710 | , "Lonicera nigra" 711 | , "Lonicera periclymenum" 712 | , "Lonicera pileata" 713 | , "Lonicera tatarica" 714 | , "Lonicera x heckrottii" 715 | , "Lonicera x purpusii 'Winter Beauty'" 716 | , "Lonicera xylosteum" 717 | , "Lycium barbarum" 718 | , "Maclura pomifera" 719 | , "Magnolia acuminata" 720 | , "Magnolia denudata" 721 | , "Magnolia grandiflora" 722 | , "Magnolia kobus" 723 | , "Magnolia liliiflora" 724 | , "Magnolia obovata" 725 | , "Magnolia sieboldii" 726 | , "Magnolia stellata" 727 | , "Magnolia tripetala" 728 | , "Magnolia x soulangeana" 729 | , "Magnolia x wiesneri" 730 | , "Mahonia aquifolium" 731 | , "Mahonia x media" 732 | , "Malus domestica" 733 | , "Malus floribunda" 734 | , "Malus sargentii" 735 | , "Malus sylvestris" 736 | , "Malus toringoides" 737 | , "Malus x purpurea" 738 | , "Melia azedarach" 739 | , "Mespilus germanica" 740 | , "Metasequoia glyptostroboides" 741 | , "Morus alba" 742 | , "Morus alba f. macrophylla" 743 | , "Morus nigra" 744 | , "Myoporum serratum" 745 | , "Myrtus communis" 746 | , "Nandina domestica" 747 | , "Nerium oleander" 748 | , "Nicotiana glauca" 749 | , "Nothofagus antarctica" 750 | , "Nyssa sylvatica" 751 | , "Olea europaea" 752 | , "Osmanthus x burkwoodii" 753 | , "Osmanthus x fortunei" 754 | , "Ostrya carpinifolia" 755 | , "Ostrya virginiana" 756 | , "Osyris alba" 757 | , "Oxydendrum arboreum" 758 | , "Pachypodium lamerei" 759 | , "Paeonia x suffruticosa" 760 | , "Paliurus spina-christi" 761 | , "Parkinsonia aculeata" 762 | , "Parrotia persica" 763 | , "Parrotiopsis jaquemontiana" 764 | , "Parthenocissus inserta" 765 | , "Parthenocissus quinquefolia" 766 | , "Parthenocissus tricuspidata" 767 | , "Passiflora caerulea" 768 | , "Paulownia tomentosa" 769 | , "Peltophorum pterocarpum" 770 | , "Pereskia bleo" 771 | , "Persea americana" 772 | , "Petteria ramentacea" 773 | , "Phellodendron amurense" 774 | , "Philadelphus coronarius" 775 | , "Philadelphus x virginalis" 776 | , "Phillyrea angustifolia" 777 | , "Phillyrea latifolia" 778 | , "Phoenix canariensis" 779 | , "Phoenix dactylifera" 780 | , "Photinia davidiana" 781 | , "Photinia x fraseri" 782 | , "Physocarpus opulifolius" 783 | , "Phytolacca dioica" 784 | , "Picea abies" 785 | , "Picea abies 'Inversa'" 786 | , "Picea asperata" 787 | , "Picea breweriana" 788 | , "Picea engelmanii" 789 | , "Picea glauca" 790 | , "Picea glauca 'Conica'" 791 | , "Picea mariana" 792 | , "Picea omorika" 793 | , "Picea orientalis" 794 | , "Picea polita" 795 | , "Picea pungens 'Glauca'" 796 | , "Picea sitchensis" 797 | , "Picea wilsonii" 798 | ] 799 | 800 | 801 | treesPart3 : List String 802 | treesPart3 = 803 | [ "Pieris floribunda" 804 | , "Pieris japonica" 805 | , "Pinus aristata" 806 | , "Pinus armandii" 807 | , "Pinus attenuata" 808 | , "Pinus banksiana" 809 | , "Pinus bungeana" 810 | , "Pinus canariensis" 811 | , "Pinus cembra" 812 | , "Pinus contorta" 813 | , "Pinus coulteri" 814 | , "Pinus halepensis" 815 | , "Pinus heldreichii" 816 | , "Pinus jeffreyi" 817 | , "Pinus koraiensis" 818 | , "Pinus leucodermis" 819 | , "Pinus monticola" 820 | , "Pinus mugo" 821 | , "Pinus nigra" 822 | , "Pinus nigra var. laricio" 823 | , "Pinus nigra var. salzmanii" 824 | , "Pinus parviflora" 825 | , "Pinus peuce" 826 | , "Pinus pinaster" 827 | , "Pinus pinea" 828 | , "Pinus ponderosa" 829 | , "Pinus strobus" 830 | , "Pinus sylvestris" 831 | , "Pinus thunbergii" 832 | , "Pinus wallichiana" 833 | , "Pistacia lentiscus" 834 | , "Pistacia terebinthus" 835 | , "Pistacia vera" 836 | , "Pittosporum tobira" 837 | , "Platanus orientalis" 838 | , "Platanus x hispanica" 839 | , "Platycarya strobilacea" 840 | , "Platycladus orientalis" 841 | , "Plumbago auriculata" 842 | , "Plumeria rubra" 843 | , "Podranea ricasoliana" 844 | , "Polygala myrtifolia" 845 | , "Poncirus trifoliata" 846 | , "Populus alba" 847 | , "Populus balsamifera" 848 | , "Populus nigra" 849 | , "Populus nigra 'Italica'" 850 | , "Populus simonii" 851 | , "Populus tremula" 852 | , "Populus x canadensis" 853 | , "Populus x canescens" 854 | , "Prosopis juliflora" 855 | , "Prunus 'Accolade'" 856 | , "Prunus 'Kursar'" 857 | , "Prunus armeniaca" 858 | , "Prunus avium" 859 | , "Prunus avium 'Plena'" 860 | , "Prunus cerasifera" 861 | , "Prunus cerasifera 'Nigra'" 862 | , "Prunus cerasifera 'Rosea'" 863 | , "Prunus cerasus" 864 | , "Prunus cerasus 'Rhexii'" 865 | , "Prunus davidiana" 866 | , "Prunus domestica" 867 | , "Prunus domestica ssp. insititia" 868 | , "Prunus domestica ssp. syriaca" 869 | , "Prunus domestica subsp. domestica" 870 | , "Prunus domestica subsp. italica" 871 | , "Prunus dulcis" 872 | , "Prunus fenziliana" 873 | , "Prunus incana" 874 | , "Prunus incisa" 875 | , "Prunus laurocerasus" 876 | , "Prunus lusitanica" 877 | , "Prunus maackii" 878 | , "Prunus mahaleb" 879 | , "Prunus mume" 880 | , "Prunus nipponica var. kurilensis" 881 | , "Prunus padus" 882 | , "Prunus persica" 883 | , "Prunus sargentii" 884 | , "Prunus serotina" 885 | , "Prunus serrula" 886 | , "Prunus serrulata 'Kanzan'" 887 | , "Prunus sibirica" 888 | , "Prunus spinosa" 889 | , "Prunus subhirtella" 890 | , "Prunus subhirtella f. autumnalis" 891 | , "Prunus tomentosa" 892 | , "Prunus triloba" 893 | , "Pseudocydonia sinensis" 894 | , "Pseudolarix amabilis" 895 | , "Pseudotsuga menziesii" 896 | , "Ptelea trifoliata" 897 | , "Pterocarya fraxinifolia" 898 | , "Pterocarya stenoptera" 899 | , "Pterostyrax corymbosus" 900 | , "Pterostyrax hispidus" 901 | , "Punica granatum" 902 | , "Pyracantha coccinea" 903 | , "Pyrus betulifolia" 904 | , "Pyrus calleryana 'Chanticleer'" 905 | , "Pyrus communis" 906 | , "Pyrus pyraster" 907 | , "Pyrus pyrifolia var. culta" 908 | , "Pyrus salicifolia" 909 | , "Quercus acutissima" 910 | , "Quercus alba" 911 | , "Quercus canariensis" 912 | , "Quercus castaneifolia" 913 | , "Quercus cerris" 914 | , "Quercus coccifera" 915 | , "Quercus coccinea" 916 | , "Quercus dentata" 917 | , "Quercus faginea" 918 | , "Quercus frainetto" 919 | , "Quercus ilex" 920 | , "Quercus ilicifolia" 921 | , "Quercus imbricaria" 922 | , "Quercus libani" 923 | , "Quercus macranthera" 924 | , "Quercus macrocarpa" 925 | , "Quercus marilandica" 926 | , "Quercus palustris" 927 | , "Quercus petraea" 928 | , "Quercus petraea 'Laciniata Crispa'" 929 | , "Quercus phellos" 930 | , "Quercus pontica" 931 | , "Quercus pubescens" 932 | , "Quercus pyrenaica" 933 | , "Quercus robur" 934 | , "Quercus robur 'Pectinata'" 935 | , "Quercus robur f. fastigiata" 936 | , "Quercus rubra" 937 | , "Quercus shumardii" 938 | , "Quercus suber" 939 | , "Quercus velutina" 940 | , "Quercus x hispanica 'Lucombeana'" 941 | , "Quercus x turneri" 942 | , "Rhamnus alaternus" 943 | , "Rhamnus cathartica" 944 | , "Rhamnus imeretina" 945 | , "Rhamnus lycioides" 946 | , "Rhamnus pumila" 947 | , "Rhamnus saxatilis" 948 | , "Rhododendron catawbiense" 949 | , "Rhododendron tomentosum" 950 | , "Rhodotypos scandens" 951 | , "Rhus typhina" 952 | , "Ribes aureum" 953 | , "Ribes rubrum" 954 | , "Ribes sanguineum" 955 | , "Ribes uva-crispa var. sativum" 956 | , "Ricinus communis" 957 | , "Robinia hispida" 958 | , "Robinia luxurians" 959 | , "Robinia pseudoacacia" 960 | , "Robinia x margaretta 'Casque Rouge'" 961 | , "Rosa canina" 962 | , "Rosa rugosa" 963 | , "Rosa spinosissima" 964 | , "Rosa tomentosa" 965 | , "Rubia peregrina" 966 | , "Rubus fruticosus" 967 | , "Rubus idaeus" 968 | , "Rubus odoratus" 969 | , "Rubus spectabilis" 970 | , "Ruscus aculeatus" 971 | , "Salix alba" 972 | , "Salix alba 'Tristis'" 973 | , "Salix aurita" 974 | , "Salix babylonica" 975 | , "Salix caprea" 976 | , "Salix caprea 'Kilmarnock'" 977 | , "Salix cinerea" 978 | , "Salix fragilis" 979 | , "Salix integra 'Hakuro Nishiki'" 980 | , "Salix irrorata" 981 | , "Salix matsudana 'Tortuosa'" 982 | , "Salix purpurea" 983 | , "Salix udensis f. sekka" 984 | , "Salix viminalis" 985 | , "Samanea saman" 986 | , "Sambucus nigra" 987 | , "Sambucus nigra ssp. caerulea" 988 | , "Sambucus racemosa" 989 | , "Sarcococca hookeriana var. humilis" 990 | , "Sassafras albidum" 991 | , "Schefflera actinophylla" 992 | , "Schinus molle" 993 | , "Sciadopitys verticillata" 994 | , "Senna didymobotrya" 995 | , "Senna x floribunda" 996 | , "Sequoia sempervirens" 997 | , "Sequoiadendron giganteum" 998 | , "Shepherdia argentea" 999 | , "Sinocalycanthus chinensis" 1000 | , "Skimmia japonica" 1001 | , "Smilax aspera" 1002 | , "Solanum dulcamara" 1003 | , "Solanum jasminoides" 1004 | , "Sorbaria sorbifolia" 1005 | , "Sorbus alnifolia" 1006 | , "Sorbus americana" 1007 | , "Sorbus aria" 1008 | , "Sorbus aucuparia" 1009 | , "Sorbus domestica" 1010 | , "Sorbus intermedia" 1011 | , "Sorbus torminalis" 1012 | , "Spartium junceum" 1013 | , "Spathodea campanulata" 1014 | , "Spiraea japonica" 1015 | , "Spiraea thunbergii" 1016 | , "Spiraea x billardii" 1017 | , "Spiraea x vanhouttei" 1018 | , "Stachyurus praecox" 1019 | , "Staphylea colchica" 1020 | , "Staphylea holocarpa" 1021 | , "Staphylea pinnata" 1022 | , "Stewartia pseudocamellia" 1023 | , "Styphnolobium japonicum" 1024 | , "Styrax japonicus" 1025 | , "Styrax obassia" 1026 | , "Symphoricarpos albus" 1027 | , "Symphoricarpos x chenaultii" 1028 | , "Syringa reflexa" 1029 | , "Syringa vulgaris" 1030 | , "Taiwania cryptomerioides" 1031 | , "Tamarix parviflora" 1032 | , "Taxodium distichum" 1033 | , "Taxus baccata" 1034 | , "Tecoma capensis" 1035 | , "Tecoma stans" 1036 | , "Terminalia catappa" 1037 | , "Tetradium daniellii" 1038 | , "Thuja occidentalis" 1039 | , "Thuja plicata" 1040 | , "Thujopsis dolabrata" 1041 | , "Thymelaea hirsuta" 1042 | , "Tilia americana" 1043 | , "Tilia cordata" 1044 | , "Tilia dasystyla" 1045 | , "Tilia henryana" 1046 | , "Tilia mongolica" 1047 | , "Tilia platyphyllos" 1048 | , "Tilia tomentosa" 1049 | , "Tilia tomentosa f. petiolaris" 1050 | , "Tilia x euchlora" 1051 | , "Tilia x europaea" 1052 | , "Tipuana tipu" 1053 | , "Toona sinensis" 1054 | , "Trachycarpus fortunei" 1055 | , "Tsuga canadensis" 1056 | , "Tsuga diversifolia" 1057 | , "Tsuga heterophylla" 1058 | , "Ulex europaeus" 1059 | , "Ulmus glabra" 1060 | , "Ulmus laevis" 1061 | , "Ulmus minor" 1062 | , "Ulmus minor 'Jacqueline Hillier'" 1063 | , "Ulmus minor 'Wredei'" 1064 | , "Ulmus parvifolia" 1065 | , "Ulmus pumila" 1066 | , "Vaccinium myrtillus" 1067 | , "Vaccinium uliginosum" 1068 | , "Vaccinium vitis-idaea" 1069 | , "Vachellia xanthophloea" 1070 | , "Viburnum davidii" 1071 | , "Viburnum farreri" 1072 | , "Viburnum lantana" 1073 | , "Viburnum lantanoides" 1074 | , "Viburnum lentago" 1075 | , "Viburnum opulus" 1076 | , "Viburnum opulus f. roseum" 1077 | , "Viburnum plicatum" 1078 | , "Viburnum plicatum f. sterile" 1079 | , "Viburnum rhytidophyllum" 1080 | , "Viburnum tinus" 1081 | , "Viburnum x bodnantense" 1082 | , "Viburnum x burkwoodii" 1083 | , "Viburnum x carlcephalum" 1084 | , "Viscum album" 1085 | , "Vitex agnus-castus" 1086 | , "Vitis coignetiae" 1087 | , "Vitis vinifera" 1088 | , "Washingtonia filifera" 1089 | , "Washingtonia robusta" 1090 | , "Weigela florida" 1091 | , "Wisteria sinensis" 1092 | , "Wollemia nobilis" 1093 | , "Xanthocyparis nootkatensis" 1094 | , "Zanthoxylum simulans" 1095 | , "Zelkova carpinifolia" 1096 | , "Zelkova serrata" 1097 | , "Ziziphus jujuba" 1098 | ] 1099 | --------------------------------------------------------------------------------