# vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2016, Kovid Goyal from __python__ import hash_literals, bound_methods from dom import ensure_id, unique_id, clear from elementmaker import E from session import local_storage from popups import CompletionPopup from utils import uniq class EditWithComplete: def __init__(self, name, placeholder=None, tooltip=None, parent=None, input_type='text', onenterkey=None): inpt = E.input(type=input_type, name=name, title=tooltip or '', placeholder=placeholder or '', autocomplete='off') self.input_id = ensure_id(inpt) self.onenterkey = onenterkey self.completion_popup = CompletionPopup(parent=parent, onselect=self.apply_completion) inpt.addEventListener('keydown', self.onkeydown) inpt.addEventListener('input', self.oninput) self.completion_popup.add_associated_widget(inpt) if parent: parent.appendChild(inpt) @property def text_input(self): return document.getElementById(self.input_id) def apply_completion(self, text): if text: ti = self.text_input ti.value = text ti.focus() return True def onkeydown(self, event): if self.completion_popup.is_visible and self.completion_popup.handle_keydown(event): event.preventDefault(), event.stopPropagation() return if event.key is 'Enter': if self.completion_popup.is_visible: if self.apply_completion(self.completion_popup.current_text): self.completion_popup.hide() event.preventDefault(), event.stopPropagation() return if self.onenterkey: event.preventDefault(), event.stopPropagation() self.onenterkey() elif event.key is 'Tab': if self.completion_popup.is_visible: if self.apply_completion(self.completion_popup.current_text): self.completion_popup.hide() else: self.completion_popup.move_highlight() event.preventDefault(), event.stopPropagation() elif event.key is 'ArrowDown': ti = self.text_input if not ti.value: self.completion_popup.set_query('') self.completion_popup.popup(ti) event.preventDefault(), event.stopPropagation() def oninput(self, event): ti = self.text_input self.completion_popup.set_query(ti.value or '') self.completion_popup.popup(ti) def hide_completion_popup(self): self.completion_popup.hide() def add_associated_widget(self, widget_or_id): self.completion_popup.add_associated_widget(widget_or_id) def set_all_items(self, items): self.completion_popup.set_all_items(items) def create_search_bar(action, name, tooltip=None, placeholder=None, button=None, history_size=100, associated_widgets=None): parent = E.div(style="display:flex") ewc = EditWithComplete(name, parent=parent, tooltip=tooltip, placeholder=placeholder, input_type='search') parent.lastChild.style.width = '100%' history_name = 'search-bar-history-' + name def update_completion_items(): items = local_storage().get(history_name) if items?.length: ewc.set_all_items(items) update_completion_items() def trigger(): text = ewc.text_input.value ewc.hide_completion_popup() action(text) if text and text.strip(): items = local_storage().get(history_name) or v'[]' idx = items.indexOf(text) if idx > -1: items.splice(idx, 1) items.unshift(text) local_storage().set(history_name, uniq(items[:history_size])) update_completion_items() ewc.onenterkey = trigger parent.querySelector('input').addEventListener('search', trigger) if button: ewc.add_associated_widget(button) button.addEventListener('click', trigger) if associated_widgets is not None: for w in associated_widgets: ewc.add_associated_widget(w) return parent def create_simple_input_with_history_native(name, tooltip=None, placeholder=None, history_size=100, input_type='text'): # cannot use in standalone viewer because of: https://bugreports.qt.io/browse/QTBUG-54433 # fixed in Qt 6.4.0 dl_id = unique_id() dl = document.createElement('datalist') dl.id = dl_id history_name = f'simple_input_history_{name}' def update_completion_items(x): items = local_storage().get(history_name) dl = x or document.getElementById(dl_id) if dl: clear(dl) if items?.length: for item in items: dl.appendChild(E.option(value=item)) def save_completion_items(): text = document.getElementById(dl_id).nextSibling.value if text and text.strip(): items = local_storage().get(history_name) or v'[]' idx = items.indexOf(text) if idx > -1: items.splice(idx, 1) items.unshift(text) local_storage().set(history_name, uniq(items[:history_size])) update_completion_items() update_completion_items(dl) ans = E.span( data_calibre_history_input='1', dl, E.input(type=input_type, name=name, list=dl_id, title=tooltip or '', placeholder=placeholder or ''), ) ans.addEventListener('save_history', save_completion_items) return ans def create_simple_input_with_history(name, tooltip=None, placeholder=None, history_size=100, input_type='text'): # to use simply add the returned element to the DOM and when you want to # save a new completion item, use dispatchEvent to send a 'save_history' # event to it. parent = E.span(data_calibre_history_input='1') ewc = EditWithComplete(name, parent=parent, tooltip=tooltip, placeholder=placeholder, input_type=input_type) history_name = f'simple_input_history_{name}' def update_completion_items(): items = local_storage().get(history_name) if items?.length: ewc.set_all_items(items) update_completion_items() def save_completion_items(): text = ewc.text_input.value if text and text.strip(): items = local_storage().get(history_name) or v'[]' idx = items.indexOf(text) if idx > -1: items.splice(idx, 1) items.unshift(text) local_storage().set(history_name, uniq(items[:history_size])) update_completion_items() parent.addEventListener('save_history', save_completion_items) return parent # }}} def main(container): container.appendChild(create_search_bar(print, 'test-search-bar', placeholder='Testing search bar')) container.firstChild.lastChild.focus() # ewc = EditWithComplete(parent=container, placeholder='Testing edit with complete') # ewc.set_all_items('a a1 a11 a12 a13 b b1 b2 b3'.split(' ')) # ewc.text_input.focus()