Skip to content

Repository files navigation

Vue wrappers for Vaadin components, PoC

Phase B of the Vue types proof of concept for vaadin/web-components, branch proto/vue-types. The question: once every package ships a generated vue.d.ts, do thin Vue wrappers still add enough value?

The page shows the same components in two ways:

  • src/components/ – thin wrappers VTextField, VComboBox, VButton built on the generated *Props types.
  • src/App.vue and src/Overlays.vue – the bare custom elements, typed only through the vue.d.ts files in types/.

Run

npm install
npm run sync-types   # needs ../../vaadin/wc on branch proto/vue-types
npm run typecheck    # vue-tsc with strictTemplates, plus a lib check of types/
npm run dev

sync-types runs the generator from the web-components checkout against the installed @vaadin/* packages and copies the result into types/. Once the files ship with the packages, tsconfig.json can include node_modules/@vaadin/*/vue.d.ts directly.

The sample uses @vaadin/*@next (25.3.0-beta1). The 25.2 manifests do not list the theme attribute, so theme="primary" fails to type-check there.

Wrapper pattern

<script setup lang="ts">
import "@vaadin/text-field";
import type { TextFieldValueChangedEvent } from "@vaadin/text-field";
import type { TextFieldProps } from "../../types/vaadin-text-field";

type Props = Omit<TextFieldProps, "value" | "onValueChanged">;
const props = defineProps<Props>();
const model = defineModel<string>({ default: "" });
</script>

<template>
  <vaadin-text-field
    v-bind="forwarded"
    :value="model"
    @value-changed="onValueChanged"
  />
</template>
  • defineProps<Omit<TextFieldProps, …>>() works with the imported type. Vue reads the literal boolean types in vue.d.ts as runtime Boolean props, which is what makes <VTextField required> work.
  • forwarded drops undefined props, so the wrapper does not reset element state on re-render.
  • VComboBox turns the renderer property into an item scoped slot.
  • VButton maps named slots to light DOM slot attributes and re-emits click.

Text field, combo box, button

Checked with Playwright against vite preview:

Case Wrapper Bare element with types
Typing updates the model v-model :value + @value-changed
Selecting a combo-box item v-model same handler pattern
Custom item rendering #item slot, re-renders renderer function, no templates
required boolean attribute required === true required === "", so not required
theme, label, error-message, @click pass through work directly
Wrong prop or handler type error error

The required row matters. Vue sets a DOM property when key in el, and it turns an empty attribute value into true only when the property already holds a boolean. Vaadin boolean properties default to undefined, so the bare element gets required = "". Users must write :required="true", or the wrapper declares a Boolean prop. Defaulting boolean properties to false in the components would fix this for every Vue user.

Select, context menu, dialog

src/Overlays.vue uses the bare elements with the slotted APIs from 25.3: <vaadin-select-list-box slot="overlay">, <vaadin-context-menu-list-box slot="overlay">, and dialog content with header-title and the footer slot. Checked the same way:

Case Result
Select options from v-for selection updates the model, label shows
Option added later appears and is selectable
Menu item with @click handler runs, menu closes
Menu item under v-if toggles between openings
Dialog body with v-model, footer close closes, closed fires, draft state stays
Esc on the dialog @opened-changed sets opened back to false

The sub-elements (vaadin-select-item, vaadin-context-menu-item, both list-boxes) are in GlobalComponents, so the nested markup type-checks. None of the three needs a wrapper. v-model on the select is the only gap, and it is the same defineModel recipe as VTextField.

Two notes:

  • Vue's HTMLAttributes has no slot, so slot="footer" fails under strictTemplates. The generated types now add it.
  • id on a wrapper is rejected under strictTemplates, because wrappers declare only the element's props. It still works at runtime.

Conclusion

  • Types alone cover bindings, events, hover docs, and template errors.
  • Wrappers add v-model, Vue slots for renderers, and correct boolean attributes.
  • Both type-check under strictTemplates and share the generated *Props.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages