   [{:name :model            :required true                   :type "string | atom"    :validate-fn string-or-atom?    :description "text of the input (can be atom or value)"}
    {:name :on-change        :required true                   :type "string -> nil"    :validate-fn fn?                :description [:span [:code ":change-on-blur?"] " controls when it is called. Passed the current input string"] }
    {:name :data-source      :required true                   :type "async fn"         :validate-fn fn?                :description [:span [:code ":data-source"] " populates the suggestions. Should be a 2 argument function taking a search query and a callback, and should call the callback with the search query and a vector of suggestions."]}
+   {:name :render-suggestion :required false                 :type "component"        :validate-fn fn?                :description "override the rendering of the suggestion items by passing a component fn."}
+   {:name :rigid?           :required false :default true    :type "boolean | atom"                                   :description "Set to `false` will allow the user to choose arbitrary text input rather than a suggestion from `data-source`."}
    {:name :status           :required false                  :type "keyword"          :validate-fn input-status-type? :description [:span "validation status. " [:code "nil/omitted"] " for normal status or one of: " input-status-types-list]}
    {:name :status-icon?     :required false :default false   :type "boolean"                                          :description [:span "when true, display an icon to match " [:code ":status"] " (no icon for nil)"]}
    {:name :status-tooltip   :required false                  :type "string"           :validate-fn string?            :description "displayed in status icon's tooltip"}


(defn- typeahead
  "Returns markup for a typeahead text input"
  [& {:keys [data-source on-change] :as args}]
  {:pre [(validate-args-macro typeahead-args-desc args "typeahead")]}
  (let [{:as state :keys [ c-search c-sugg c-input c-keypress ]} (make-typeahead-state on-change)
        state-atom (reagent/atom state)
        input-text-model (reagent/cursor state-atom [:input-text])]
    (handle-search-results state-atom c-sugg)

(defn- handle-search-results
  [state-atom c-sugg]
  (go-loop []
    (let [[search suggestions] (<! c-sugg)
          {:keys [input-text]} @state-atom]
      (when (= search input-text)
        (swap! state-atom #(-> %
                               (assoc :suggestions suggestions :waiting? false)
                               select-reset))))
    (recur)))

(defn- make-typeahead-state
  [{:as args :keys [on-change rigid?]}]
  (let [c-input (chan)]
    (assoc typeahead-state-initial
           :on-change  on-change
           :rigid?     rigid?
           :c-input    c-input
           :c-search   (debounce c-input 250) ;; FIXME hard-coded
           :c-keypress (chan)
           :c-sugg     (chan))))

(def ^:private keys-by-code
  {40 :down
