Skip to content

Commit

Permalink
fix #886 Select component improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
vegegoku committed Nov 30, 2023
1 parent c92a2b3 commit e9540d6
Show file tree
Hide file tree
Showing 16 changed files with 444 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,20 @@ public InputElement setName(String name) {
element.element().name = name;
return this;
}

/** @return The String value of the input element */
public String getValue() {
return element.element().value;
}

/**
* Set the value for this input element.
*
* @param value String value
* @return Same InputElement instance
*/
public InputElement setValue(String value) {
element.element().value = value;
return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ public interface FormsStyles {
/** CSS class for a hidden input element within a form field. */
CssClass dui_hidden_input = () -> "dui-field-input-hidden";

CssClass dui_auto_type_input = () -> "dui-auto-type-input";

/** CSS class for a form switch component. */
CssClass dui_switch = () -> "dui-form-switch";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,18 @@
import static org.dominokit.domino.ui.utils.Domino.*;

import elemental2.dom.DomGlobal;
import elemental2.dom.Event;
import elemental2.dom.HTMLElement;
import elemental2.dom.HTMLInputElement;
import elemental2.dom.KeyboardEvent;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import jsinterop.base.Js;
import org.dominokit.domino.ui.IsElement;
import org.dominokit.domino.ui.elements.DivElement;
import org.dominokit.domino.ui.elements.InputElement;
import org.dominokit.domino.ui.elements.SpanElement;
import org.dominokit.domino.ui.forms.AbstractFormElement;
import org.dominokit.domino.ui.forms.AutoValidator;
Expand Down Expand Up @@ -75,7 +78,8 @@ public abstract class AbstractSelect<
protected Menu<T> optionsMenu;
protected DivElement fieldInput;
private SpanElement placeHolderElement;
private DominoElement<HTMLInputElement> inputElement;
private InputElement inputElement;
private InputElement typingElement;

/**
* Default constructor which initializes the underlying structures, sets up event listeners, and
Expand All @@ -85,12 +89,58 @@ public AbstractSelect() {
placeHolderElement = span();
addCss(dui_form_select);
wrapperElement
.addCss(dui_relative)
.appendChild(
fieldInput =
div()
.addCss(dui_field_input)
.appendChild(placeHolderElement.addCss(dui_field_placeholder)))
.appendChild(inputElement = input(getType()).addCss(dui_hidden_input).toDominoElement());
.appendChild(inputElement = input(getType()).addCss(dui_hidden_input))
.appendChild(
typingElement =
input("text")
.addCss(dui_auto_type_input, dui_hidden)
.setTabIndex(-1)
.onKeyPress(keyEvents -> keyEvents.alphanumeric(Event::stopPropagation)));

DelayedTextInput.create(typingElement, 1000)
.setDelayedAction(
() -> {
optionsMenu
.findOptionStarsWith(typingElement.getValue())
.flatMap(OptionMeta::get)
.ifPresent(
meta -> onOptionSelected((O) meta.getOption(), isChangeListenersPaused()));
optionsMenu.focusFirstMatch(typingElement.getValue());
typingElement.setValue(null).addCss(dui_hidden);
focus();
})
.setOnEnterAction(
() -> {
openOptionMenu(false);
String token = typingElement.getValue();
typingElement.setValue(null).addCss(dui_hidden);
DomGlobal.setTimeout(p0 -> optionsMenu.focusFirstMatch(token), 0);
});

onKeyPress(
keyEvents -> {
keyEvents.alphanumeric(
evt -> {
KeyboardEvent keyboardEvent = Js.uncheckedCast(evt);
keyboardEvent.stopPropagation();
keyboardEvent.preventDefault();
String key = keyboardEvent.key;
if (nonNull(key)
&& !optionsMenu.isOpened()
&& (isNull(typingElement.getValue()) || typingElement.getValue().isEmpty())) {
typingElement.removeCss(dui_hidden);
typingElement.element().value = key;
typingElement.element().focus();
}
});
});

labelForId(inputElement.getDominoId());

optionsMenu =
Expand Down Expand Up @@ -123,7 +173,8 @@ public AbstractSelect() {
getInputElement()
.onKeyDown(
keyEvents ->
keyEvents.onEnter(evt -> openOptionMenu()).onSpace(evt -> openOptionMenu()));
keyEvents.onEnter(evt -> openOptionMenu()).onSpace(evt -> openOptionMenu()))
.onKeyUp(keyEvents -> keyEvents.onArrowDown(evt -> openOptionMenu()));

appendChild(
PrimaryAddOn.of(
Expand Down Expand Up @@ -163,13 +214,23 @@ public AbstractSelect() {
* disabled.
*/
private void openOptionMenu() {
openOptionMenu(true);
}

/**
* Opens the options menu allowing user to select an option, unless the select is read-only or
* disabled.
*
* @param focus a flag to decide if the menu should be focused on first element or not.
*/
private void openOptionMenu(boolean focus) {
if (isReadOnly() || isDisabled()) {
return;
}
if (optionsMenu.isOpened() && !optionsMenu.isContextMenu()) {
optionsMenu.close();
} else {
optionsMenu.open(true);
optionsMenu.open(focus);
}
}

Expand Down Expand Up @@ -439,7 +500,7 @@ public C setPlaceholder(String placeholder) {
*/
@Override
public DominoElement<HTMLInputElement> getInputElement() {
return inputElement;
return inputElement.toDominoElement();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
*/
package org.dominokit.domino.ui.keyboard;

import static org.dominokit.domino.ui.utils.Domino.*;

import elemental2.dom.EventListener;
import elemental2.dom.KeyboardEvent;
import java.util.function.Predicate;

/**
* The {@code AcceptKeyEvents} interface defines methods for handling keyboard events.
Expand Down Expand Up @@ -246,14 +246,44 @@ public interface AcceptKeyEvents {
AcceptKeyEvents on(String key, EventListener handler);

/**
* Registers an event listener be called when ctrl + any key is pressed with options.
* Registers an event listener be called when any key is pressed with options.
*
* @param options The {@link org.dominokit.domino.ui.keyboard.KeyboardEventOptions}.
* @param handler The {@link elemental2.dom.EventListener} to call when the event occurs.
* @return The same instance of {@code AcceptKeyEvents}.
*/
AcceptKeyEvents any(KeyboardEventOptions options, EventListener handler);

/**
* Registers an event listener be called when any alphanumeric key is pressed with options.
*
* @param options The {@link org.dominokit.domino.ui.keyboard.KeyboardEventOptions}.
* @param handler The {@link elemental2.dom.EventListener} to call when the event occurs.
* @return The same instance of {@code AcceptKeyEvents}.
*/
AcceptKeyEvents alphanumeric(KeyboardEventOptions options, EventListener handler);

/**
* Registers an event listener be called when any alphanumeric key is pressed with default
* options.
*
* @param handler The {@link elemental2.dom.EventListener} to call when the event occurs.
* @return The same instance of {@code AcceptKeyEvents}.
*/
AcceptKeyEvents alphanumeric(EventListener handler);

/**
* Registers an event listener be called when any key is pressed with options if the predicate
* condition is matched.
*
* @param options The {@link org.dominokit.domino.ui.keyboard.KeyboardEventOptions}.
* @param handler The {@link elemental2.dom.EventListener} to call when the event occurs.
* @param predicate A predicate to be executed to decide if the handler will be triggered or not.
* @return The same instance of {@code AcceptKeyEvents}.
*/
AcceptKeyEvents any(
KeyboardEventOptions options, EventListener handler, Predicate<KeyboardEvent> predicate);

/**
* Registers an event listener be called when ctrl + any key is pressed.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
*/
package org.dominokit.domino.ui.keyboard;

import static org.dominokit.domino.ui.utils.Domino.*;

import elemental2.dom.EventListener;
import elemental2.dom.KeyboardEvent;
import java.util.function.Predicate;
import java.util.function.Supplier;

/**
Expand All @@ -37,6 +37,8 @@ class KeyEventHandlerContext {
/** The supplier for {@link KeyboardEventOptions} associated with this context. */
final Supplier<KeyboardEventOptions> options;

final Predicate<KeyboardEvent> predicate;

/**
* Constructs a new {@code KeyEventHandlerContext} with the specified event listener handler and
* options supplier.
Expand All @@ -47,5 +49,22 @@ class KeyEventHandlerContext {
public KeyEventHandlerContext(EventListener handler, Supplier<KeyboardEventOptions> options) {
this.handler = handler;
this.options = options;
this.predicate = keyboardEvent -> true;
}

/**
* Constructs a new {@code KeyEventHandlerContext} with the specified event listener handler and
* options supplier.
*
* @param handler The event listener handler.
* @param options The supplier for {@link KeyboardEventOptions}.
*/
public KeyEventHandlerContext(
EventListener handler,
Supplier<KeyboardEventOptions> options,
Predicate<KeyboardEvent> predicate) {
this.handler = handler;
this.options = options;
this.predicate = predicate;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,17 @@
*/
package org.dominokit.domino.ui.keyboard;

import static org.dominokit.domino.ui.utils.Domino.*;
import static java.util.Objects.nonNull;

import elemental2.core.JsRegExp;
import elemental2.dom.Event;
import elemental2.dom.EventListener;
import elemental2.dom.KeyboardEvent;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.function.Supplier;
import jsinterop.base.Js;
import org.dominokit.domino.ui.events.HasDefaultEventOptions;
Expand Down Expand Up @@ -106,7 +108,8 @@ private void callHandlers(List<KeyEventHandlerContext> keyEventHandlerContexts,
keyEventHandlerContexts.stream()
.filter(
context ->
context.options.get().withCtrlKey == keyboardEvent.ctrlKey
context.predicate.test(keyboardEvent)
&& context.options.get().withCtrlKey == keyboardEvent.ctrlKey
&& context.options.get().withAltKey == keyboardEvent.altKey
&& context.options.get().withShiftKey == keyboardEvent.shiftKey
&& context.options.get().withMetaKey == keyboardEvent.metaKey
Expand Down Expand Up @@ -273,6 +276,39 @@ public AcceptKeyEvents any(KeyboardEventOptions options, EventListener handler)
return addGlobalHandler(contextOf(handler, () -> options));
}

/** {@inheritDoc} */
@Override
public AcceptKeyEvents alphanumeric(KeyboardEventOptions options, EventListener handler) {
return any(
options,
handler,
keyboardEvent ->
nonNull(keyboardEvent.key)
&& new JsRegExp(
"^[a-zA-Z0-9\\u0600-\\u06FF\\u0660-\\u0669\\u06F0-\\u06F9 _.-]{1}$", "g")
.test(keyboardEvent.key));
}

/** {@inheritDoc} */
@Override
public AcceptKeyEvents alphanumeric(EventListener handler) {
return any(
hasDefaultEventOptions.getOptions(),
handler,
keyboardEvent ->
nonNull(keyboardEvent.key)
&& new JsRegExp(
"^[a-zA-Z0-9\\u0600-\\u06FF\\u0660-\\u0669\\u06F0-\\u06F9 _.-]{1}$", "g")
.test(keyboardEvent.key));
}

/** {@inheritDoc} */
@Override
public AcceptKeyEvents any(
KeyboardEventOptions options, EventListener handler, Predicate<KeyboardEvent> predicate) {
return addGlobalHandler(contextOf(handler, () -> options, predicate));
}

/** {@inheritDoc} */
@Override
public AcceptKeyEvents any(EventListener handler) {
Expand All @@ -297,6 +333,13 @@ private KeyEventHandlerContext contextOf(
return new KeyEventHandlerContext(handler, options);
}

private KeyEventHandlerContext contextOf(
EventListener handler,
Supplier<KeyboardEventOptions> options,
Predicate<KeyboardEvent> predicate) {
return new KeyEventHandlerContext(handler, options, predicate);
}

/** {@inheritDoc} */
@Override
public AcceptKeyEvents clearAll() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ public class AbstractMenuItem<V> extends BaseDominoElement<HTMLLIElement, Abstra
protected boolean searchable = true;
protected boolean selectable = true;

protected MenuSearchFilter searchFilter = (token, caseSensitive) -> false;

/** Default constructor to create a menu item. */
public AbstractMenuItem() {
root = li().addCss(dui_menu_item);
Expand Down Expand Up @@ -580,6 +582,30 @@ public <T extends AbstractMenuItem<V>> T appendChild(PrefixAddOn<?> prefixAddOn)
return (T) this;
}

/**
* Retrieves the current {@link MenuSearchFilter} used for search operations.
*
* @return the current {@link MenuSearchFilter}
*/
public MenuSearchFilter getSearchFilter() {
return searchFilter;
}

/**
* Sets the {@link MenuSearchFilter} to be used during search operations.
*
* @param searchFilter the search filter to set
* @return this Menu item instance for chaining
*/
public <T extends AbstractMenuItem<V>> T setSearchFilter(MenuSearchFilter searchFilter) {
this.searchFilter = searchFilter;
return (T) this;
}

public boolean startsWith(String character) {
return false;
}

/**
* Returns the underlying DOM element.
*
Expand Down
Loading

0 comments on commit e9540d6

Please sign in to comment.