# Vaadin-oriented Kotlin API Keluna's public API should move closer to Vaadin Flow at each development step. The workshop example introduces typed component objects rather than application code built out of generic DOM elements. There is no JVM or Vaadin dependency: these are Kotlin/Native implementations inspired by familiar Flow concepts. ## Components and composition Create components before mounting them. `VerticalLayout`, `HorizontalLayout` and `FormLayout` own their children through `add(...)`. A component can have only one parent and can be mounted once. Use `isVisible`, `isEnabled`, `id`, `addClassName(...)` and `removeClassName(...)` without touching the DOM. Disabling a layout disables its descendants. Invisible, disabled and read-only fields reject client value changes in Kotlin as well as presenting native control states. Read-only controls can still be changed programmatically. ```kotlin import dev.keluna.core.UiTree import dev.keluna.ui.* data class Person(var name: String = "", var accepted: Boolean = false) val name = TextField("Name").apply { maxLength = 100 } val accepted = Checkbox("Teilnahmebedingungen akzeptieren") val binder = Binder() binder.forField(name).asRequired("Name fehlt").bind(Person::name) binder.forField(accepted).asRequired("Zustimmung fehlt").bind(Person::accepted) val draft = Person() binder.readBean(draft) val view = VerticalLayout( FormLayout(name, accepted), Button("Prüfen") { if (binder.writeBeanIfValid(draft)) println(draft.name) }, ) val tree = UiTree().apply { mount(view) } ``` `Container.textField(...)` and `Container.button(...)` also provide typed Kotlin DSL helpers. Existing `Element.column`, `Element.button` etc. remain available for the original Counter and Snake demos. Prefer the typed component layer for new application code. The `element` escape hatch and a field's `inputElement` become available after mounting. ## Typed field values | Component | `value` type | Empty value | |---|---|---| | `TextField`, `EmailField`, `TelephoneField`, `TextArea` | `String` | `""` | | `IntegerField` | `Int?` | `null` | | `DatePicker` | `kotlinx.datetime.LocalDate?` | `null` | | `TimePicker` | `kotlinx.datetime.LocalTime?` | `null` | | `Checkbox` | `Boolean` | `false` | | `Slider` | `Int` | configured minimum | | `Select`, `RadioButtonGroup` | `T?` | `null` | Fields implement `HasValue` and `HasValidation`. `ValueField` is the base for custom fields. `clear()` restores `emptyValue`. Numeric/date/time conversion errors are kept separately from empty values, so invalid input never silently reuses an older valid value. Email validation is a basic address-shape check; it does not verify delivery or implement every RFC address form. ```kotlin val listener = name.addValueChangeListener { event -> println("${event.oldValue} -> ${event.value}, client=${event.isFromClient}") } name.value = "Ada" // event with isFromClient == false listener.remove() ``` Browser edits set `isFromClient` to true. Identical typed values do not generate a value-change event, but parsing/validation state is still updated. Listener registrations can be removed independently and idempotently. Buttons similarly expose `addClickListener` with a typed `ClickEvent`. ## Buffered Binder `Binder` binds explicit getter/setter lambdas or Kotlin mutable property references. It does not use reflection to discover properties by name. - `readBean(bean)` loads field values and clears displayed binding errors. - Editing fields does not mutate the bean; each binding validates on changes. - `asRequired(message)` checks empty/blank values and marks the field as required. - `withValidator(predicate, message)` adds model-value validation. - `withConverter(toModel, toPresentation, errorMessage)` converts both directions; conversion failure produces a field error instead of a model write. - `validate()` validates every binding and returns a Boolean. - `writeBeanIfValid(bean)` checks every binding before invoking any setter. A field/conversion validation failure writes nothing and returns false. Setters should be ordinary in-memory property updates. Rollback of throwing setters, bean-level transactional validators and external side effects are not implemented. Hidden or disabled fields are not silently excluded from validation; use an explicit conditional validator. The workshop catering binding demonstrates this and clears catering in the submitted model for online attendance. `isRequiredIndicatorVisible` is a presentation/native-control hint; use Binder's `asRequired` for a Kotlin-side required-value rule. ## Input synchronization Values and checked state use DOM **properties**, rather than HTML attributes. An `input` or `change` event forwards the node, a monotonically increasing edit revision, the browser's `badInput` flag, and the raw string to Kotlin. The wire format is `value::::`; only the first four colons are delimiters, so Unicode, colons and newlines survive unchanged. Unknown nodes, malformed envelopes and stale/replayed revisions are rejected. Kotlin takes client values into its state without echoing them to the originating control. Programmatic property updates carry the latest processed revision; the browser rejects older updates to an already edited control. Radio-group revisions cover all peers. Equal values are not assigned again, preserving caret and selection. Unfinished invalid number/date text is explicitly cleared on reset, even if the DOM reports an empty value. IME composition is neither forwarded nor overwritten until it commits. Field labels, helper/error text, `aria-invalid` and `aria-describedby` are generated by the component implementation. ## Current limits compared with Vaadin Flow This is an API direction, not source/binary compatibility. Current differences: - The host is Kotlin/Native with GTK/WebKitGTK, not a Java server. - Date/time values use kotlinx-datetime instead of java.time. - Binder is a buffered subset: no `setBean`, bean-level validators or reflection binding. - `forField` currently accepts Keluna `ValueField` subclasses. - Select/radio items are fixed at construction; no lazy data providers or dynamic item replacement yet. - Components support adding children, not removal, reparenting or attach/detach lifecycle events yet. - Typed components expose Element after mounting, not before. - `Slider` is a small Keluna HTML-range component; not a claim of a matching Vaadin core class. References: [Vaadin component model](https://vaadin.com/docs/latest/flow/component-internals/basic), [Vaadin Binder](https://vaadin.com/docs/latest/flow/binding-data/components-binder).