├── .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 [](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 |
--------------------------------------------------------------------------------