Browse Source
* web: update change and beforeInput event listeners * web: update Clipboard events: copy, cut, paste * web: update Keyboard events: keyup, keydown * web: update select event (only for input and textarea) * web: update FocusEvents: focus, blur, focusin, focusout * web: update Touch Events * web: remove search event listener as it's not supported in Firefox * web: update Animation Events * web: update scroll event * web: rename WrappedEventListener to SyntheticEventListener * refactor internal event listeners to avoid redundant lambdas * remove WrappedEvent.kt * move SyntheticInputEvent.kt to `events` package * Add tests for Select element events * remove import from another changelist * web: add Form events: submit and reset * fix PR discussions * web: rename `attrsBuilder` to `attrs` Co-authored-by: Oleksandr Karpovich <oleksandr.karpovich@jetbrains.com>pull/905/head
Oleksandr Karpovich
3 years ago
committed by
GitHub
32 changed files with 1278 additions and 500 deletions
@ -1,33 +0,0 @@
|
||||
/* |
||||
* Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. |
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||
*/ |
||||
|
||||
package androidx.compose.web.attributes |
||||
|
||||
import androidx.compose.web.events.SyntheticEvent |
||||
import org.jetbrains.compose.web.attributes.AttrsBuilder |
||||
import org.jetbrains.compose.web.attributes.InputType |
||||
import org.jetbrains.compose.web.attributes.Options |
||||
import org.w3c.dom.HTMLInputElement |
||||
import org.w3c.dom.events.Event |
||||
import org.w3c.dom.events.EventTarget |
||||
|
||||
class SyntheticInputEvent<ValueType, Element : EventTarget>( |
||||
val value: ValueType, |
||||
nativeEvent: Event |
||||
) : SyntheticEvent<Element>( |
||||
nativeEvent = nativeEvent |
||||
) |
||||
|
||||
class InputAttrsBuilder<T>( |
||||
val inputType: InputType<T> |
||||
) : AttrsBuilder<HTMLInputElement>() { |
||||
|
||||
fun onInput(options: Options = Options.DEFAULT, listener: (SyntheticInputEvent<T, HTMLInputElement>) -> Unit) { |
||||
addEventListener(INPUT, options) { |
||||
val value = inputType.inputValue(it.nativeEvent) |
||||
listener(SyntheticInputEvent(value, it.nativeEvent)) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,156 @@
|
||||
package org.jetbrains.compose.web.attributes |
||||
|
||||
import org.jetbrains.compose.web.events.SyntheticInputEvent |
||||
import androidx.compose.web.events.SyntheticDragEvent |
||||
import androidx.compose.web.events.SyntheticEvent |
||||
import androidx.compose.web.events.SyntheticMouseEvent |
||||
import androidx.compose.web.events.SyntheticWheelEvent |
||||
import org.jetbrains.compose.web.attributes.EventsListenerBuilder.Companion.CHANGE |
||||
import org.jetbrains.compose.web.attributes.EventsListenerBuilder.Companion.INPUT |
||||
import org.jetbrains.compose.web.attributes.EventsListenerBuilder.Companion.SELECT |
||||
import org.jetbrains.compose.web.events.* |
||||
import org.w3c.dom.DragEvent |
||||
import org.w3c.dom.TouchEvent |
||||
import org.w3c.dom.clipboard.ClipboardEvent |
||||
import org.w3c.dom.events.* |
||||
|
||||
open class SyntheticEventListener<T : SyntheticEvent<*>> internal constructor( |
||||
val event: String, |
||||
val options: Options, |
||||
val listener: (T) -> Unit |
||||
) : EventListener { |
||||
|
||||
@Suppress("UNCHECKED_CAST") |
||||
override fun handleEvent(event: Event) { |
||||
listener(SyntheticEvent<EventTarget>(event).unsafeCast<T>()) |
||||
} |
||||
} |
||||
|
||||
class Options { |
||||
// TODO: add options for addEventListener |
||||
|
||||
companion object { |
||||
val DEFAULT = Options() |
||||
} |
||||
} |
||||
|
||||
internal class AnimationEventListener( |
||||
event: String, |
||||
options: Options, |
||||
listener: (SyntheticAnimationEvent) -> Unit |
||||
) : SyntheticEventListener<SyntheticAnimationEvent>( |
||||
event, options, listener |
||||
) { |
||||
override fun handleEvent(event: Event) { |
||||
listener(SyntheticAnimationEvent(event, event.unsafeCast<AnimationEventDetails>())) |
||||
} |
||||
} |
||||
|
||||
internal class MouseEventListener( |
||||
event: String, |
||||
options: Options, |
||||
listener: (SyntheticMouseEvent) -> Unit |
||||
) : SyntheticEventListener<SyntheticMouseEvent>(event, options, listener) { |
||||
override fun handleEvent(event: Event) { |
||||
listener(SyntheticMouseEvent(event.unsafeCast<MouseEvent>())) |
||||
} |
||||
} |
||||
|
||||
internal class MouseWheelEventListener( |
||||
event: String, |
||||
options: Options, |
||||
listener: (SyntheticWheelEvent) -> Unit |
||||
) : SyntheticEventListener<SyntheticWheelEvent>(event, options, listener) { |
||||
override fun handleEvent(event: Event) { |
||||
listener(SyntheticWheelEvent(event.unsafeCast<WheelEvent>())) |
||||
} |
||||
} |
||||
|
||||
internal class KeyboardEventListener( |
||||
event: String, |
||||
options: Options, |
||||
listener: (SyntheticKeyboardEvent) -> Unit |
||||
) : SyntheticEventListener<SyntheticKeyboardEvent>(event, options, listener) { |
||||
override fun handleEvent(event: Event) { |
||||
listener(SyntheticKeyboardEvent(event.unsafeCast<KeyboardEvent>())) |
||||
} |
||||
} |
||||
|
||||
internal class FocusEventListener( |
||||
event: String, |
||||
options: Options, |
||||
listener: (SyntheticFocusEvent) -> Unit |
||||
) : SyntheticEventListener<SyntheticFocusEvent>(event, options, listener) { |
||||
override fun handleEvent(event: Event) { |
||||
listener(SyntheticFocusEvent(event.unsafeCast<FocusEvent>())) |
||||
} |
||||
} |
||||
|
||||
internal class TouchEventListener( |
||||
event: String, |
||||
options: Options, |
||||
listener: (SyntheticTouchEvent) -> Unit |
||||
) : SyntheticEventListener<SyntheticTouchEvent>(event, options, listener) { |
||||
override fun handleEvent(event: Event) { |
||||
listener(SyntheticTouchEvent(event.unsafeCast<TouchEvent>())) |
||||
} |
||||
} |
||||
|
||||
internal class DragEventListener( |
||||
event: String, |
||||
options: Options, |
||||
listener: (SyntheticDragEvent) -> Unit |
||||
) : SyntheticEventListener<SyntheticDragEvent>(event, options, listener) { |
||||
override fun handleEvent(event: Event) { |
||||
listener(SyntheticDragEvent(event.unsafeCast<DragEvent>())) |
||||
} |
||||
} |
||||
|
||||
internal class ClipboardEventListener( |
||||
event: String, |
||||
options: Options, |
||||
listener: (SyntheticClipboardEvent) -> Unit |
||||
) : SyntheticEventListener<SyntheticClipboardEvent>(event, options, listener) { |
||||
override fun handleEvent(event: Event) { |
||||
listener(SyntheticClipboardEvent(event.unsafeCast<ClipboardEvent>())) |
||||
} |
||||
} |
||||
|
||||
internal class InputEventListener<InputValueType, Target: EventTarget>( |
||||
eventName: String = INPUT, |
||||
options: Options, |
||||
val inputType: InputType<InputValueType>, |
||||
listener: (SyntheticInputEvent<InputValueType, Target>) -> Unit |
||||
) : SyntheticEventListener<SyntheticInputEvent<InputValueType, Target>>( |
||||
eventName, options, listener |
||||
) { |
||||
override fun handleEvent(event: Event) { |
||||
val value = inputType.inputValue(event) |
||||
listener(SyntheticInputEvent(value, event)) |
||||
} |
||||
} |
||||
|
||||
internal class ChangeEventListener<InputValueType, Target: EventTarget>( |
||||
options: Options, |
||||
val inputType: InputType<InputValueType>, |
||||
listener: (SyntheticChangeEvent<InputValueType, Target>) -> Unit |
||||
) : SyntheticEventListener<SyntheticChangeEvent<InputValueType, Target>>( |
||||
CHANGE, options, listener |
||||
) { |
||||
override fun handleEvent(event: Event) { |
||||
val value = inputType.inputValue(event) |
||||
listener(SyntheticChangeEvent(value, event)) |
||||
} |
||||
} |
||||
|
||||
internal class SelectEventListener<Target: EventTarget>( |
||||
options: Options, |
||||
listener: (SyntheticSelectEvent<Target>) -> Unit |
||||
) : SyntheticEventListener<SyntheticSelectEvent<Target>>( |
||||
SELECT, options, listener |
||||
) { |
||||
override fun handleEvent(event: Event) { |
||||
listener(SyntheticSelectEvent(event, event.target.unsafeCast<SelectionInfoDetails>())) |
||||
} |
||||
} |
||||
|
@ -1,23 +0,0 @@
|
||||
/* |
||||
* Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. |
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||
*/ |
||||
|
||||
package androidx.compose.web.attributes |
||||
|
||||
import org.jetbrains.compose.web.attributes.AttrsBuilder |
||||
import org.jetbrains.compose.web.attributes.Options |
||||
import org.w3c.dom.HTMLTextAreaElement |
||||
|
||||
class TextAreaAttrsBuilder : AttrsBuilder<HTMLTextAreaElement>() { |
||||
|
||||
fun onInput( |
||||
options: Options = Options.DEFAULT, |
||||
listener: (SyntheticInputEvent<String, HTMLTextAreaElement>) -> Unit |
||||
) { |
||||
addEventListener(INPUT, options) { |
||||
val text = it.nativeEvent.target.asDynamic().value.unsafeCast<String>() |
||||
listener(SyntheticInputEvent(text, it.nativeEvent)) |
||||
} |
||||
} |
||||
} |
@ -1,168 +0,0 @@
|
||||
package org.jetbrains.compose.web.attributes |
||||
|
||||
import org.jetbrains.compose.web.events.GenericWrappedEvent |
||||
import org.jetbrains.compose.web.events.WrappedCheckBoxInputEvent |
||||
import org.jetbrains.compose.web.events.WrappedClipboardEvent |
||||
import org.jetbrains.compose.web.events.WrappedDragEvent |
||||
import org.jetbrains.compose.web.events.WrappedEventImpl |
||||
import org.jetbrains.compose.web.events.WrappedFocusEvent |
||||
import org.jetbrains.compose.web.events.WrappedInputEvent |
||||
import org.jetbrains.compose.web.events.WrappedKeyboardEvent |
||||
import org.jetbrains.compose.web.events.WrappedMouseEvent |
||||
import org.jetbrains.compose.web.events.WrappedPointerEvent |
||||
import org.jetbrains.compose.web.events.WrappedRadioInputEvent |
||||
import org.jetbrains.compose.web.events.WrappedTextInputEvent |
||||
import org.jetbrains.compose.web.events.WrappedTouchEvent |
||||
import org.jetbrains.compose.web.events.WrappedWheelEvent |
||||
import org.w3c.dom.DragEvent |
||||
import org.w3c.dom.TouchEvent |
||||
import org.w3c.dom.clipboard.ClipboardEvent |
||||
import org.w3c.dom.events.Event |
||||
import org.w3c.dom.events.FocusEvent |
||||
import org.w3c.dom.events.InputEvent |
||||
import org.w3c.dom.events.KeyboardEvent |
||||
import org.w3c.dom.events.MouseEvent |
||||
import org.w3c.dom.events.WheelEvent |
||||
import org.w3c.dom.pointerevents.PointerEvent |
||||
|
||||
open class WrappedEventListener<T : GenericWrappedEvent<*>>( |
||||
val event: String, |
||||
val options: Options, |
||||
val listener: (T) -> Unit |
||||
) : org.w3c.dom.events.EventListener { |
||||
|
||||
@Suppress("UNCHECKED_CAST") |
||||
override fun handleEvent(event: Event) { |
||||
listener(WrappedEventImpl(event) as T) |
||||
} |
||||
} |
||||
|
||||
class Options { |
||||
// TODO: add options for addEventListener |
||||
|
||||
companion object { |
||||
val DEFAULT = Options() |
||||
} |
||||
} |
||||
|
||||
internal class MouseEventListener( |
||||
event: String, |
||||
options: Options, |
||||
listener: (WrappedMouseEvent) -> Unit |
||||
) : WrappedEventListener<WrappedMouseEvent>(event, options, listener) { |
||||
override fun handleEvent(event: Event) { |
||||
listener(WrappedMouseEvent(event as MouseEvent)) |
||||
} |
||||
} |
||||
|
||||
internal class MouseWheelEventListener( |
||||
event: String, |
||||
options: Options, |
||||
listener: (WrappedWheelEvent) -> Unit |
||||
) : WrappedEventListener<WrappedWheelEvent>(event, options, listener) { |
||||
override fun handleEvent(event: Event) { |
||||
listener(WrappedWheelEvent(event as WheelEvent)) |
||||
} |
||||
} |
||||
|
||||
internal class KeyboardEventListener( |
||||
event: String, |
||||
options: Options, |
||||
listener: (WrappedKeyboardEvent) -> Unit |
||||
) : WrappedEventListener<WrappedKeyboardEvent>(event, options, listener) { |
||||
override fun handleEvent(event: Event) { |
||||
listener(WrappedKeyboardEvent(event as KeyboardEvent)) |
||||
} |
||||
} |
||||
|
||||
internal class FocusEventListener( |
||||
event: String, |
||||
options: Options, |
||||
listener: (WrappedFocusEvent) -> Unit |
||||
) : WrappedEventListener<WrappedFocusEvent>(event, options, listener) { |
||||
override fun handleEvent(event: Event) { |
||||
listener(WrappedFocusEvent(event as FocusEvent)) |
||||
} |
||||
} |
||||
|
||||
internal class TouchEventListener( |
||||
event: String, |
||||
options: Options, |
||||
listener: (WrappedTouchEvent) -> Unit |
||||
) : WrappedEventListener<WrappedTouchEvent>(event, options, listener) { |
||||
override fun handleEvent(event: Event) { |
||||
listener(WrappedTouchEvent(event as TouchEvent)) |
||||
} |
||||
} |
||||
|
||||
internal class DragEventListener( |
||||
event: String, |
||||
options: Options, |
||||
listener: (WrappedDragEvent) -> Unit |
||||
) : WrappedEventListener<WrappedDragEvent>(event, options, listener) { |
||||
override fun handleEvent(event: Event) { |
||||
listener(WrappedDragEvent(event as DragEvent)) |
||||
} |
||||
} |
||||
|
||||
internal class PointerEventListener( |
||||
event: String, |
||||
options: Options, |
||||
listener: (WrappedPointerEvent) -> Unit |
||||
) : WrappedEventListener<WrappedPointerEvent>(event, options, listener) { |
||||
override fun handleEvent(event: Event) { |
||||
listener(WrappedPointerEvent(event as PointerEvent)) |
||||
} |
||||
} |
||||
|
||||
internal class ClipboardEventListener( |
||||
event: String, |
||||
options: Options, |
||||
listener: (WrappedClipboardEvent) -> Unit |
||||
) : WrappedEventListener<WrappedClipboardEvent>(event, options, listener) { |
||||
override fun handleEvent(event: Event) { |
||||
listener(WrappedClipboardEvent(event as ClipboardEvent)) |
||||
} |
||||
} |
||||
|
||||
internal class InputEventListener( |
||||
event: String, |
||||
options: Options, |
||||
listener: (WrappedInputEvent) -> Unit |
||||
) : WrappedEventListener<WrappedInputEvent>(event, options, listener) { |
||||
override fun handleEvent(event: Event) { |
||||
listener(WrappedInputEvent(event as InputEvent)) |
||||
} |
||||
} |
||||
|
||||
internal class RadioInputEventListener( |
||||
options: Options, |
||||
listener: (WrappedRadioInputEvent) -> Unit |
||||
) : WrappedEventListener<WrappedRadioInputEvent>(EventsListenerBuilder.INPUT, options, listener) { |
||||
override fun handleEvent(event: Event) { |
||||
val checked = event.target.asDynamic().checked as Boolean |
||||
listener(WrappedRadioInputEvent(event, checked)) |
||||
} |
||||
} |
||||
|
||||
internal class CheckBoxInputEventListener( |
||||
options: Options, |
||||
listener: (WrappedCheckBoxInputEvent) -> Unit |
||||
) : WrappedEventListener<WrappedCheckBoxInputEvent>( |
||||
EventsListenerBuilder.INPUT, options, listener |
||||
) { |
||||
override fun handleEvent(event: Event) { |
||||
val checked = event.target.asDynamic().checked as Boolean |
||||
listener(WrappedCheckBoxInputEvent(event, checked)) |
||||
} |
||||
} |
||||
|
||||
internal class TextInputEventListener( |
||||
options: Options, |
||||
listener: (WrappedTextInputEvent) -> Unit |
||||
) : WrappedEventListener<WrappedTextInputEvent>(EventsListenerBuilder.INPUT, options, listener) { |
||||
override fun handleEvent(event: Event) { |
||||
val text = event.target.asDynamic().value as String |
||||
listener(WrappedTextInputEvent(event as InputEvent, text)) |
||||
} |
||||
} |
@ -0,0 +1,53 @@
|
||||
/* |
||||
* Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. |
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||
*/ |
||||
|
||||
package org.jetbrains.compose.web.attributes.builders |
||||
|
||||
import androidx.compose.web.events.SyntheticEvent |
||||
import org.jetbrains.compose.web.attributes.* |
||||
import org.jetbrains.compose.web.events.SyntheticChangeEvent |
||||
import org.jetbrains.compose.web.events.SyntheticInputEvent |
||||
import org.jetbrains.compose.web.events.SyntheticSelectEvent |
||||
import org.w3c.dom.HTMLInputElement |
||||
|
||||
class InputAttrsBuilder<ValueType>( |
||||
val inputType: InputType<ValueType> |
||||
) : AttrsBuilder<HTMLInputElement>() { |
||||
|
||||
fun onInvalid( |
||||
options: Options = Options.DEFAULT, |
||||
listener: (SyntheticEvent<HTMLInputElement>) -> Unit |
||||
) { |
||||
addEventListener(INVALID, options, listener) |
||||
} |
||||
|
||||
fun onInput( |
||||
options: Options = Options.DEFAULT, |
||||
listener: (SyntheticInputEvent<ValueType, HTMLInputElement>) -> Unit |
||||
) { |
||||
listeners.add(InputEventListener(eventName = INPUT, options, inputType, listener)) |
||||
} |
||||
|
||||
fun onChange( |
||||
options: Options = Options.DEFAULT, |
||||
listener: (SyntheticChangeEvent<ValueType, HTMLInputElement>) -> Unit |
||||
) { |
||||
listeners.add(ChangeEventListener(options, inputType, listener)) |
||||
} |
||||
|
||||
fun onBeforeInput( |
||||
options: Options = Options.DEFAULT, |
||||
listener: (SyntheticInputEvent<ValueType, HTMLInputElement>) -> Unit |
||||
) { |
||||
listeners.add(InputEventListener(eventName = BEFOREINPUT, options, inputType, listener)) |
||||
} |
||||
|
||||
fun onSelect( |
||||
options: Options = Options.DEFAULT, |
||||
listener: (SyntheticSelectEvent<HTMLInputElement>) -> Unit |
||||
) { |
||||
listeners.add(SelectEventListener(options, listener)) |
||||
} |
||||
} |
@ -0,0 +1,58 @@
|
||||
/* |
||||
* Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. |
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||
*/ |
||||
|
||||
package androidx.compose.web.attributes |
||||
|
||||
import org.jetbrains.compose.web.attributes.AttrsBuilder |
||||
import org.jetbrains.compose.web.attributes.EventsListenerBuilder.Companion.CHANGE |
||||
import org.jetbrains.compose.web.attributes.EventsListenerBuilder.Companion.INPUT |
||||
import org.jetbrains.compose.web.attributes.Options |
||||
import org.jetbrains.compose.web.attributes.SyntheticEventListener |
||||
import org.jetbrains.compose.web.events.SyntheticChangeEvent |
||||
import org.jetbrains.compose.web.events.SyntheticInputEvent |
||||
import org.w3c.dom.HTMLSelectElement |
||||
import org.w3c.dom.events.Event |
||||
|
||||
class SelectAttrsBuilder : AttrsBuilder<HTMLSelectElement>() { |
||||
|
||||
fun onInput( |
||||
options: Options = Options.DEFAULT, |
||||
listener: (SyntheticInputEvent<String?, HTMLSelectElement>) -> Unit |
||||
) { |
||||
listeners.add(SelectInputEventListener(INPUT, options, listener)) |
||||
} |
||||
|
||||
fun onChange( |
||||
options: Options = Options.DEFAULT, |
||||
listener: (SyntheticChangeEvent<String?, HTMLSelectElement>) -> Unit |
||||
) { |
||||
listeners.add(SelectChangeEventListener(options, listener)) |
||||
} |
||||
} |
||||
|
||||
private class SelectInputEventListener( |
||||
eventName: String = INPUT, |
||||
options: Options = Options.DEFAULT, |
||||
listener: (SyntheticInputEvent<String?, HTMLSelectElement>) -> Unit |
||||
) : SyntheticEventListener<SyntheticInputEvent<String?, HTMLSelectElement>>( |
||||
eventName, options, listener |
||||
) { |
||||
override fun handleEvent(event: Event) { |
||||
val value = event.target?.asDynamic().value?.toString() |
||||
listener(SyntheticInputEvent(value, event)) |
||||
} |
||||
} |
||||
|
||||
private class SelectChangeEventListener( |
||||
options: Options = Options.DEFAULT, |
||||
listener: (SyntheticChangeEvent<String?, HTMLSelectElement>) -> Unit |
||||
): SyntheticEventListener<SyntheticChangeEvent<String?, HTMLSelectElement>>( |
||||
CHANGE, options, listener |
||||
) { |
||||
override fun handleEvent(event: Event) { |
||||
val value = event.target?.asDynamic().value?.toString() |
||||
listener(SyntheticChangeEvent(value, event)) |
||||
} |
||||
} |
@ -0,0 +1,43 @@
|
||||
/* |
||||
* Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers. |
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file. |
||||
*/ |
||||
|
||||
package org.jetbrains.compose.web.attributes.builders |
||||
|
||||
import org.jetbrains.compose.web.attributes.* |
||||
import org.jetbrains.compose.web.events.SyntheticChangeEvent |
||||
import org.jetbrains.compose.web.events.SyntheticSelectEvent |
||||
import org.jetbrains.compose.web.events.SyntheticInputEvent |
||||
import org.w3c.dom.HTMLTextAreaElement |
||||
|
||||
class TextAreaAttrsBuilder : AttrsBuilder<HTMLTextAreaElement>() { |
||||
|
||||
fun onInput( |
||||
options: Options = Options.DEFAULT, |
||||
listener: (SyntheticInputEvent<String, HTMLTextAreaElement>) -> Unit |
||||
) { |
||||
listeners.add(InputEventListener(INPUT, options, InputType.Text, listener)) |
||||
} |
||||
|
||||
fun onChange( |
||||
options: Options = Options.DEFAULT, |
||||
listener: (SyntheticChangeEvent<String, HTMLTextAreaElement>) -> Unit |
||||
) { |
||||
listeners.add(ChangeEventListener(options, InputType.Text, listener)) |
||||
} |
||||
|
||||
fun onBeforeInput( |
||||
options: Options = Options.DEFAULT, |
||||
listener: (SyntheticInputEvent<String, HTMLTextAreaElement>) -> Unit |
||||
) { |
||||
listeners.add(InputEventListener(BEFOREINPUT, options, InputType.Text, listener)) |
||||
} |
||||
|
||||
fun onSelect( |
||||
options: Options = Options.DEFAULT, |
||||
listener: (SyntheticSelectEvent<HTMLTextAreaElement>) -> Unit |
||||
) { |
||||
listeners.add(SelectEventListener(options, listener)) |
||||
} |
||||
} |
@ -0,0 +1,21 @@
|
||||
package org.jetbrains.compose.web.events |
||||
|
||||
import androidx.compose.web.events.SyntheticEvent |
||||
import org.w3c.dom.events.Event |
||||
import org.w3c.dom.events.EventTarget |
||||
|
||||
class SyntheticAnimationEvent internal constructor( |
||||
nativeEvent: Event, |
||||
animationEventDetails: AnimationEventDetails |
||||
) : SyntheticEvent<EventTarget>(nativeEvent) { |
||||
|
||||
val animationName: String = animationEventDetails.animationName |
||||
val elapsedTime: Number = animationEventDetails.elapsedTime |
||||
val pseudoElement: String = animationEventDetails.pseudoElement |
||||
} |
||||
|
||||
internal external interface AnimationEventDetails { |
||||
val animationName: String |
||||
val elapsedTime: Number |
||||
val pseudoElement: String |
||||
} |
@ -0,0 +1,11 @@
|
||||
package org.jetbrains.compose.web.events |
||||
|
||||
import androidx.compose.web.events.SyntheticEvent |
||||
import org.w3c.dom.HTMLElement |
||||
import org.w3c.dom.events.Event |
||||
import org.w3c.dom.events.EventTarget |
||||
|
||||
class SyntheticChangeEvent<Value, Element : EventTarget> internal constructor( |
||||
val value: Value, |
||||
nativeEvent: Event, |
||||
) : SyntheticEvent<Element>(nativeEvent) |
@ -0,0 +1,21 @@
|
||||
package org.jetbrains.compose.web.events |
||||
|
||||
import androidx.compose.web.events.SyntheticEvent |
||||
import org.w3c.dom.DataTransfer |
||||
import org.w3c.dom.clipboard.ClipboardEvent |
||||
import org.w3c.dom.events.EventTarget |
||||
|
||||
class SyntheticClipboardEvent internal constructor( |
||||
nativeEvent: ClipboardEvent |
||||
) : SyntheticEvent<EventTarget>(nativeEvent) { |
||||
|
||||
val clipboardData: DataTransfer? = nativeEvent.clipboardData |
||||
|
||||
fun getData(format: String): String? { |
||||
return clipboardData?.getData(format) |
||||
} |
||||
|
||||
fun setData(format: String, data: String) { |
||||
clipboardData?.setData(format, data) |
||||
} |
||||
} |
@ -0,0 +1,12 @@
|
||||
package org.jetbrains.compose.web.events |
||||
|
||||
import androidx.compose.web.events.SyntheticEvent |
||||
import org.w3c.dom.events.EventTarget |
||||
import org.w3c.dom.events.FocusEvent |
||||
|
||||
class SyntheticFocusEvent internal constructor( |
||||
nativeEvent: FocusEvent, |
||||
) : SyntheticEvent<EventTarget>(nativeEvent) { |
||||
|
||||
val relatedTarget: EventTarget? = nativeEvent.relatedTarget |
||||
} |
@ -0,0 +1,20 @@
|
||||
package org.jetbrains.compose.web.events |
||||
|
||||
import androidx.compose.web.events.SyntheticEvent |
||||
import org.w3c.dom.DataTransfer |
||||
import org.w3c.dom.events.Event |
||||
import org.w3c.dom.events.EventTarget |
||||
|
||||
// @param nativeEvent: Event - we don't use [org.w3c.dom.events.InputEvent] here, |
||||
// since for cases it can be just [org.w3c.dom.events.Event] |
||||
class SyntheticInputEvent<ValueType, Element : EventTarget> internal constructor( |
||||
val value: ValueType, |
||||
nativeEvent: Event |
||||
) : SyntheticEvent<Element>( |
||||
nativeEvent = nativeEvent |
||||
) { |
||||
val data: String? = nativeEvent.asDynamic().data?.unsafeCast<String>() |
||||
val dataTransfer: DataTransfer? = nativeEvent.asDynamic().dataTransfer?.unsafeCast<DataTransfer>() |
||||
val inputType: String? = nativeEvent.asDynamic().inputType?.unsafeCast<String>() |
||||
val isComposing: Boolean = nativeEvent.asDynamic().isComposing?.unsafeCast<Boolean>() ?: false |
||||
} |
@ -0,0 +1,44 @@
|
||||
package org.jetbrains.compose.web.events |
||||
|
||||
import androidx.compose.web.events.SyntheticEvent |
||||
import org.w3c.dom.events.EventTarget |
||||
import org.w3c.dom.events.KeyboardEvent |
||||
|
||||
class SyntheticKeyboardEvent internal constructor( |
||||
nativeEvent: KeyboardEvent |
||||
) : SyntheticEvent<EventTarget>(nativeEvent) { |
||||
|
||||
private val keyboardEvent = nativeEvent |
||||
|
||||
val altKey: Boolean = nativeEvent.altKey |
||||
val code: String = nativeEvent.code |
||||
val ctrlKey: Boolean = nativeEvent.ctrlKey |
||||
val isComposing: Boolean = nativeEvent.isComposing |
||||
val key: String = nativeEvent.key |
||||
val locale: String = nativeEvent.asDynamic().locale.toString() |
||||
val location: Int = nativeEvent.location |
||||
val metaKey: Boolean = nativeEvent.metaKey |
||||
val repeat: Boolean = nativeEvent.repeat |
||||
val shiftKey: Boolean = nativeEvent.shiftKey |
||||
|
||||
fun getModifierState(keyArg: String): Boolean = keyboardEvent.getModifierState(keyArg) |
||||
|
||||
fun getNormalizedKey(): String = key.let { |
||||
normalizedKeys[it] ?: it |
||||
} |
||||
} |
||||
|
||||
private val normalizedKeys = mapOf( |
||||
"Esc" to "Escape", |
||||
"Spacebar" to " ", |
||||
"Left" to "ArrowLeft", |
||||
"Up" to "ArrowUp", |
||||
"Right" to "ArrowRight", |
||||
"Down" to "ArrowDown", |
||||
"Del" to "Delete", |
||||
"Apps" to "ContextMenu", |
||||
"Menu" to "ContextMenu", |
||||
"Scroll" to "ScrollLock", |
||||
"MozPrintableKey" to "Unidentified", |
||||
) |
||||
// Firefox bug for Windows key https://bugzilla.mozilla.org/show_bug.cgi?id=1232918 |
@ -0,0 +1,26 @@
|
||||
package org.jetbrains.compose.web.events |
||||
|
||||
import androidx.compose.web.events.SyntheticEvent |
||||
import org.w3c.dom.events.Event |
||||
import org.w3c.dom.events.EventTarget |
||||
|
||||
class SyntheticSelectEvent<Element : EventTarget> internal constructor( |
||||
nativeEvent: Event, |
||||
selectionInfoDetails: SelectionInfoDetails |
||||
) : SyntheticEvent<Element>(nativeEvent) { |
||||
|
||||
val selectionStart: Int = selectionInfoDetails.selectionStart |
||||
val selectionEnd: Int = selectionInfoDetails.selectionEnd |
||||
|
||||
|
||||
fun selection(): String { |
||||
return nativeEvent.target.asDynamic().value.unsafeCast<String?>()?.substring( |
||||
selectionStart, selectionEnd |
||||
) ?: "" |
||||
} |
||||
} |
||||
|
||||
internal external interface SelectionInfoDetails { |
||||
val selectionStart: Int |
||||
val selectionEnd: Int |
||||
} |
@ -0,0 +1,9 @@
|
||||
package org.jetbrains.compose.web.events |
||||
|
||||
import androidx.compose.web.events.SyntheticEvent |
||||
import org.w3c.dom.events.Event |
||||
import org.w3c.dom.events.EventTarget |
||||
|
||||
class SyntheticSubmitEvent internal constructor( |
||||
nativeEvent: Event |
||||
) : SyntheticEvent<EventTarget>(nativeEvent) |
@ -0,0 +1,18 @@
|
||||
package org.jetbrains.compose.web.events |
||||
|
||||
import androidx.compose.web.events.SyntheticEvent |
||||
import org.w3c.dom.TouchEvent |
||||
import org.w3c.dom.TouchList |
||||
import org.w3c.dom.events.EventTarget |
||||
|
||||
class SyntheticTouchEvent( |
||||
nativeEvent: TouchEvent, |
||||
) : SyntheticEvent<EventTarget>(nativeEvent) { |
||||
|
||||
val altKey: Boolean = nativeEvent.altKey |
||||
val changedTouches: TouchList = nativeEvent.changedTouches |
||||
val ctrlKey: Boolean = nativeEvent.ctrlKey |
||||
val metaKey: Boolean = nativeEvent.metaKey |
||||
val shiftKey: Boolean = nativeEvent.shiftKey |
||||
val touches: TouchList = nativeEvent.touches |
||||
} |
@ -1,107 +0,0 @@
|
||||
package org.jetbrains.compose.web.events |
||||
|
||||
import org.w3c.dom.DragEvent |
||||
import org.w3c.dom.TouchEvent |
||||
import org.w3c.dom.clipboard.ClipboardEvent |
||||
import org.w3c.dom.events.CompositionEvent |
||||
import org.w3c.dom.events.Event |
||||
import org.w3c.dom.events.FocusEvent |
||||
import org.w3c.dom.events.InputEvent |
||||
import org.w3c.dom.events.KeyboardEvent |
||||
import org.w3c.dom.events.MouseEvent |
||||
import org.w3c.dom.events.WheelEvent |
||||
import org.w3c.dom.pointerevents.PointerEvent |
||||
|
||||
interface GenericWrappedEvent<T : Event> { |
||||
val nativeEvent: T |
||||
} |
||||
|
||||
interface WrappedEvent : GenericWrappedEvent<Event> |
||||
|
||||
internal open class WrappedMouseEvent( |
||||
override val nativeEvent: MouseEvent |
||||
) : GenericWrappedEvent<MouseEvent> { |
||||
|
||||
// MouseEvent doesn't support movementX and movementY on IE6-11, and it's OK for now. |
||||
val movementX: Double |
||||
get() = nativeEvent.asDynamic().movementX as Double |
||||
val movementY: Double |
||||
get() = nativeEvent.asDynamic().movementY as Double |
||||
} |
||||
|
||||
internal open class WrappedWheelEvent( |
||||
override val nativeEvent: WheelEvent |
||||
) : GenericWrappedEvent<WheelEvent> |
||||
|
||||
open class WrappedInputEvent( |
||||
override val nativeEvent: Event |
||||
) : GenericWrappedEvent<Event> |
||||
|
||||
open class WrappedKeyboardEvent( |
||||
override val nativeEvent: KeyboardEvent |
||||
) : GenericWrappedEvent<KeyboardEvent> { |
||||
|
||||
fun getNormalizedKey(): String = nativeEvent.key.let { |
||||
normalizedKeys[it] ?: it |
||||
} |
||||
|
||||
companion object { |
||||
private val normalizedKeys = mapOf( |
||||
"Esc" to "Escape", |
||||
"Spacebar" to " ", |
||||
"Left" to "ArrowLeft", |
||||
"Up" to "ArrowUp", |
||||
"Right" to "ArrowRight", |
||||
"Down" to "ArrowDown", |
||||
"Del" to "Delete", |
||||
"Apps" to "ContextMenu", |
||||
"Menu" to "ContextMenu", |
||||
"Scroll" to "ScrollLock", |
||||
"MozPrintableKey" to "Unidentified", |
||||
) |
||||
// Firefox bug for Windows key https://bugzilla.mozilla.org/show_bug.cgi?id=1232918 |
||||
} |
||||
} |
||||
|
||||
open class WrappedFocusEvent( |
||||
override val nativeEvent: FocusEvent |
||||
) : GenericWrappedEvent<FocusEvent> |
||||
|
||||
open class WrappedTouchEvent( |
||||
override val nativeEvent: TouchEvent |
||||
) : GenericWrappedEvent<TouchEvent> |
||||
|
||||
open class WrappedCompositionEvent( |
||||
override val nativeEvent: CompositionEvent |
||||
) : GenericWrappedEvent<CompositionEvent> |
||||
|
||||
open class WrappedDragEvent( |
||||
override val nativeEvent: DragEvent |
||||
) : GenericWrappedEvent<DragEvent> |
||||
|
||||
open class WrappedPointerEvent( |
||||
override val nativeEvent: PointerEvent |
||||
) : GenericWrappedEvent<PointerEvent> |
||||
|
||||
open class WrappedClipboardEvent( |
||||
override val nativeEvent: ClipboardEvent |
||||
) : GenericWrappedEvent<ClipboardEvent> |
||||
|
||||
class WrappedTextInputEvent( |
||||
nativeEvent: Event, |
||||
val inputValue: String |
||||
) : WrappedInputEvent(nativeEvent) |
||||
|
||||
class WrappedCheckBoxInputEvent( |
||||
override val nativeEvent: Event, |
||||
val checked: Boolean |
||||
) : GenericWrappedEvent<Event> |
||||
|
||||
class WrappedRadioInputEvent( |
||||
override val nativeEvent: Event, |
||||
val checked: Boolean |
||||
) : GenericWrappedEvent<Event> |
||||
|
||||
class WrappedEventImpl( |
||||
override val nativeEvent: Event |
||||
) : WrappedEvent |
@ -0,0 +1,75 @@
|
||||
package androidx.compose.web.sample.tests |
||||
|
||||
import androidx.compose.runtime.Composable |
||||
import androidx.compose.runtime.mutableStateOf |
||||
import androidx.compose.runtime.remember |
||||
import androidx.compose.runtime.getValue |
||||
import androidx.compose.runtime.setValue |
||||
import org.jetbrains.compose.web.attributes.name |
||||
import org.jetbrains.compose.web.dom.* |
||||
import org.jetbrains.compose.web.events.SyntheticChangeEvent |
||||
import org.jetbrains.compose.web.events.SyntheticInputEvent |
||||
import org.jetbrains.compose.web.sample.tests.TestText |
||||
import org.jetbrains.compose.web.sample.tests.testCase |
||||
import org.w3c.dom.HTMLSelectElement |
||||
import org.w3c.dom.asList |
||||
|
||||
class SelectElementTests { |
||||
|
||||
@Composable |
||||
private fun ChooseAPetSelect( |
||||
canSelectMultiple: Boolean = false, |
||||
onInput: (SyntheticInputEvent<String?, HTMLSelectElement>) -> Unit, |
||||
onChange: (SyntheticChangeEvent<String?, HTMLSelectElement>) -> Unit |
||||
) { |
||||
Label(forId = "pet-select") { |
||||
Text("Choose a pet:") |
||||
} |
||||
|
||||
Select(multiple = canSelectMultiple, attrs = { |
||||
name("pets") |
||||
id("pet-select") |
||||
|
||||
onInput { onInput(it) } |
||||
onChange { onChange(it) } |
||||
|
||||
}) { |
||||
Option(value = "") { Text("--Please choose an option--") } |
||||
Option(value = "dog") { Text("Dog") } |
||||
Option(value = "cat") { Text("Cat") } |
||||
Option(value = "hamster") { Text("Hamster") } |
||||
Option(value = "parrot") { Text("Parrot") } |
||||
Option(value = "spider") { Text("Spider") } |
||||
Option(value = "goldfish") { Text("Goldfish") } |
||||
} |
||||
} |
||||
|
||||
val selectDispatchesInputAndChangeAndBeforeInputEvents by testCase { |
||||
var onInputState by remember { mutableStateOf("None") } |
||||
var onChangeState by remember { mutableStateOf("None") } |
||||
|
||||
P { TestText(value = onInputState, id = "txt_oninput") } |
||||
P { TestText(value = onChangeState, id = "txt_onchange") } |
||||
|
||||
ChooseAPetSelect( |
||||
onInput = { onInputState = it.value ?: "" }, |
||||
onChange = { onChangeState = it.value ?: "" } |
||||
) |
||||
} |
||||
|
||||
val selectMultipleItems by testCase { |
||||
var selectedItemsText by remember { mutableStateOf("None") } |
||||
|
||||
P { TestText(value = selectedItemsText) } |
||||
|
||||
ChooseAPetSelect( |
||||
canSelectMultiple = true, |
||||
onInput = {}, |
||||
onChange = { |
||||
selectedItemsText = it.target.selectedOptions.asList().joinToString(separator = ", ") { |
||||
it.asDynamic().value |
||||
} |
||||
} |
||||
) |
||||
} |
||||
} |
@ -0,0 +1,49 @@
|
||||
package org.jetbrains.compose.web.tests.integration |
||||
|
||||
import org.jetbrains.compose.web.tests.integration.common.BaseIntegrationTests |
||||
import org.jetbrains.compose.web.tests.integration.common.ResolveDrivers |
||||
import org.jetbrains.compose.web.tests.integration.common.openTestPage |
||||
import org.jetbrains.compose.web.tests.integration.common.waitTextToBe |
||||
import org.openqa.selenium.By |
||||
import org.openqa.selenium.WebDriver |
||||
import org.openqa.selenium.support.ui.Select |
||||
|
||||
class SelectElementTests : BaseIntegrationTests() { |
||||
|
||||
@ResolveDrivers |
||||
fun selectDispatchesInputAndChangeAndBeforeInputEvents(driver: WebDriver) { |
||||
driver.openTestPage("selectDispatchesInputAndChangeAndBeforeInputEvents") |
||||
driver.waitTextToBe(textId = "txt_oninput", value = "None") |
||||
driver.waitTextToBe(textId = "txt_onchange", value = "None") |
||||
|
||||
// Commented code does force onChange, but doesn't force onInput for some reason |
||||
// val select = Select(driver.findElement(By.name("pets"))) |
||||
// select.selectByIndex(0) |
||||
|
||||
// Code below works properly: forces both onInput and onChange |
||||
driver.findElement(By.name("pets")).sendKeys("Dog") |
||||
|
||||
driver.waitTextToBe(textId = "txt_onchange", value = "dog") |
||||
driver.waitTextToBe(textId = "txt_oninput", value = "dog") |
||||
} |
||||
|
||||
@ResolveDrivers |
||||
fun selectMultipleItems(driver: WebDriver) { |
||||
driver.openTestPage("selectMultipleItems") |
||||
driver.waitTextToBe(value = "None") |
||||
|
||||
val select = Select(driver.findElement(By.name("pets"))) |
||||
select.selectByIndex(1) |
||||
|
||||
driver.waitTextToBe(value = "dog") |
||||
|
||||
select.selectByIndex(2) |
||||
driver.waitTextToBe(value = "dog, cat") |
||||
|
||||
select.selectByIndex(3) |
||||
driver.waitTextToBe(value = "dog, cat, hamster") |
||||
|
||||
select.deselectByIndex(2) |
||||
driver.waitTextToBe(value = "dog, hamster") |
||||
} |
||||
} |
Loading…
Reference in new issue