@xleddyl/443-components-vue 0.8.2 → 0.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +41 -22
  2. package/dist/components/core/FormField.vue +5 -0
  3. package/dist/components/core/FormField.vue.d.ts +4 -0
  4. package/dist/components/core/Input.vue +59 -5
  5. package/dist/components/core/Input.vue.d.ts +8 -4
  6. package/dist/components/core/Textarea.vue +28 -3
  7. package/dist/components/core/Textarea.vue.d.ts +6 -4
  8. package/dist/components/data/DescriptionList.vue.d.ts +1 -1
  9. package/dist/components/data/Pagination.vue +8 -2
  10. package/dist/components/data/Pagination.vue.d.ts +2 -0
  11. package/dist/components/forms/CheckboxGroup.vue +7 -1
  12. package/dist/components/forms/CheckboxGroup.vue.d.ts +1 -0
  13. package/dist/components/forms/ColorInput.vue.d.ts +4 -4
  14. package/dist/components/forms/Combobox.vue +4 -3
  15. package/dist/components/forms/Combobox.vue.d.ts +1 -0
  16. package/dist/components/forms/DateInput.vue.d.ts +8 -4
  17. package/dist/components/forms/NumberInput.vue.d.ts +4 -4
  18. package/dist/components/forms/PasswordInput.vue.d.ts +8 -4
  19. package/dist/components/forms/PinInput.vue.d.ts +4 -4
  20. package/dist/components/forms/Select.vue +21 -2
  21. package/dist/components/forms/Select.vue.d.ts +3 -3
  22. package/dist/components/forms/Slider.vue.d.ts +4 -4
  23. package/dist/components/forms/TimeInput.vue.d.ts +8 -4
  24. package/dist/components/layout/Card.vue.d.ts +1 -1
  25. package/dist/components/layout/Separator.vue +22 -7
  26. package/dist/components/layout/Separator.vue.d.ts +14 -4
  27. package/dist/components/layout/types.d.ts +1 -0
  28. package/dist/components/overlays/ConfirmDialog.vue.d.ts +4 -4
  29. package/dist/components/overlays/ConfirmHost.vue.d.ts +4 -4
  30. package/dist/components/overlays/ContextMenu.vue.d.ts +2 -2
  31. package/dist/components/overlays/DropdownMenu.vue.d.ts +2 -2
  32. package/dist/components/overlays/types.d.ts +2 -2
  33. package/dist/components/templates/SettingsModal.vue.d.ts +4 -4
  34. package/dist/composables/useForm.d.ts +9 -2
  35. package/dist/composables/useForm.mjs +75 -13
  36. package/dist/composables/useOverlay.d.ts +1 -1
  37. package/dist/editor/RichTextEditor.vue +95 -9
  38. package/dist/editor/RichTextEditor.vue.d.ts +4 -1
  39. package/dist/editor/rich-text-editor.css +21 -0
  40. package/dist/editor/types.d.ts +1 -0
  41. package/dist/grid/DataGrid.vue +48 -18
  42. package/dist/grid/DataGrid.vue.d.ts +6 -1
  43. package/dist/grid/cells/DateCell.vue +7 -2
  44. package/dist/grid/cells/ImageCell.vue +6 -1
  45. package/dist/grid/data-grid.css +16 -2
  46. package/dist/grid/index.d.ts +2 -1
  47. package/dist/grid/index.mjs +6 -1
  48. package/dist/grid/modules.d.ts +1 -0
  49. package/dist/grid/modules.mjs +23 -0
  50. package/dist/grid/theme.mjs +1 -1
  51. package/dist/grid/types.d.ts +4 -0
  52. package/dist/internal/forms/counter.d.ts +18 -0
  53. package/dist/internal/forms/counter.mjs +31 -0
  54. package/dist/internal/overlays/OverlayView.vue.d.ts +4 -4
  55. package/dist/locale/de.mjs +11 -1
  56. package/dist/locale/en.mjs +11 -1
  57. package/dist/locale/it.mjs +11 -1
  58. package/dist/locale/types.d.ts +10 -0
  59. package/dist/styles/core/button.css +12 -6
  60. package/dist/styles/core/form-field.css +6 -0
  61. package/dist/styles/core/input.css +29 -0
  62. package/dist/styles/core/textarea.css +10 -0
  63. package/dist/styles/forms/calendar.css +8 -8
  64. package/dist/styles/forms/choice.css +6 -0
  65. package/dist/styles/layout/separator.css +49 -0
  66. package/dist/styles/shared.css +2 -1
  67. package/package.json +1 -1
package/README.md CHANGED
@@ -110,20 +110,22 @@ Input tokens, declared only on the outermost root so nested roots inherit them:
110
110
 
111
111
  A few component tokens are unset by default and only exist to be overridden on a root (or any ancestor). Unset, every one keeps the library look. The full list with defaults is in [CONVENTIONS.md](./CONVENTIONS.md#tokens):
112
112
 
113
- | Area | Tokens |
114
- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
115
- | Type | `--k-font-weight` (400), `--k-font-weight-medium` (500), `--k-font-weight-strong` (600), `--k-font-weight-display` (650) |
116
- | Buttons | `--k-btn-highlight`, `--k-btn-shadow` (`0 0 #0000` for a flat button), `--k-btn-border`, `--k-btn-padding-x`, `--k-btn-font-size`, `--k-btn-icon-size`, `--k-press-scale` (0.97) |
117
- | Fields | `--k-field-padding-x` |
118
- | Overlays | `--k-modal-radius`, `--k-modal-shadow`, `--k-drawer-radius`, `--k-drawer-shadow`, `--k-popover-radius`, `--k-popover-shadow`, `--k-toast-radius`, `--k-toast-shadow`, `--k-scrim-blur` (3px) |
119
- | Tooltip | `--k-tooltip-background`, `--k-tooltip-text`, `--k-tooltip-border`, `--k-tooltip-radius`, `--k-tooltip-shadow`, `--k-tooltip-padding` |
120
- | Shell | `--k-sidebar-width` (15rem), `--k-sidebar-rail-width`, `--k-topbar-height` (3rem), `--k-shell-inset`, `--k-shell-padding`, `--k-shell-content` |
121
- | Switch | `--k-switch-track-width`, `--k-switch-track-height`, `--k-switch-thumb-size` (md size) |
122
- | Badge | `--k-badge-radius`, `--k-badge-font`, `--k-badge-font-size`, `--k-badge-font-weight`, `--k-badge-text-transform`, `--k-badge-letter-spacing` |
123
- | DataGrid | `--k-grid-header-background`, `--k-grid-header-text`, `--k-grid-header-font`, `--k-grid-header-font-weight`, `--k-grid-font-size` (13px), `--k-grid-radius`, `--k-grid-selected-background` |
124
- | Charts | `--k-chart-1` to `--k-chart-8` (series palette) |
125
-
126
- Some tokens are meant for one element: set `--k-btn-tone`, `--k-btn-on-tone` and `--k-btn-tone-text` on a button for a brand tone outside `Tone` (a lime fill with green text), and `--k-badge-background`, `--k-badge-color` and `--k-badge-border-color` on a badge for a custom pill color. `variant="subtle"` is a borderless tinted fill (`--k-fill` for the neutral tone).
113
+ | Area | Tokens |
114
+ | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
115
+ | Type | `--k-font-weight` (400), `--k-font-weight-medium` (500), `--k-font-weight-strong` (600), `--k-font-weight-display` (650) |
116
+ | Buttons | `--k-btn-highlight`, `--k-btn-shadow` (`0 0 #0000` for a flat button), `--k-btn-border`, `--k-btn-hover-background`, `--k-btn-active-background`, `--k-btn-padding-x`, `--k-btn-font-size`, `--k-btn-icon-size`, `--k-press-scale` (0.97) |
117
+ | Fields | `--k-field-padding-x` |
118
+ | Overlays | `--k-modal-radius`, `--k-modal-shadow`, `--k-drawer-radius`, `--k-drawer-shadow`, `--k-popover-radius`, `--k-popover-shadow`, `--k-toast-radius`, `--k-toast-shadow`, `--k-scrim-blur` (3px) |
119
+ | Tooltip | `--k-tooltip-background`, `--k-tooltip-text`, `--k-tooltip-border`, `--k-tooltip-radius`, `--k-tooltip-shadow`, `--k-tooltip-padding` |
120
+ | Shell | `--k-sidebar-width` (15rem), `--k-sidebar-rail-width`, `--k-topbar-height` (3rem), `--k-shell-inset`, `--k-shell-padding`, `--k-shell-content` |
121
+ | Switch | `--k-switch-track-width`, `--k-switch-track-height`, `--k-switch-thumb-size` (md size) |
122
+ | Separator | `--k-separator-heading-font-size` (0.9375rem) |
123
+ | Calendar | `--k-calendar-day-radius`, `--k-calendar-unavailable-color`, `--k-calendar-unavailable-decoration` (line-through), `--k-calendar-unavailable-opacity` (0.6) |
124
+ | Badge | `--k-badge-radius`, `--k-badge-font`, `--k-badge-font-size`, `--k-badge-font-weight`, `--k-badge-text-transform`, `--k-badge-letter-spacing` |
125
+ | DataGrid | `--k-grid-header-background`, `--k-grid-header-text`, `--k-grid-header-font`, `--k-grid-header-font-weight`, `--k-grid-header-font-size` (12px), `--k-grid-font-size` (13px), `--k-grid-radius`, `--k-grid-selected-background` |
126
+ | Charts | `--k-chart-1` to `--k-chart-8` (series palette) |
127
+
128
+ Some tokens are meant for one element: set `--k-btn-tone`, `--k-btn-on-tone` and `--k-btn-tone-text` on a button for a brand tone outside `Tone` (a lime fill with green text), and `--k-badge-background`, `--k-badge-color` and `--k-badge-border-color` on a badge for a custom pill color. `variant="subtle"` is a borderless tinted fill (`--k-fill` for the neutral tone). For solid buttons without hover darkening, declare the fill tokens on the button so `var(--k-btn-tone)` resolves per tone: `.k-btn-solid { --k-btn-hover-background: var(--k-btn-tone); --k-btn-active-background: var(--k-btn-tone) }` (the neutral solid button rests on `--k-text`).
127
129
 
128
130
  Contrast is part of the tokens: `--k-text-muted` is the lightest text color (4.5:1), `--k-text-dimmed` is for icons and decoration (3:1), `--k-ring` is an opaque focus color (3:1 on every surface), and `--k-danger-text`, `--k-success-text`, `--k-warning-text`, `--k-info-text` color status text (4.5:1, also on soft tints). Every preset sets them for light and dark; keep the same ratios when you override them.
129
131
 
@@ -233,7 +235,7 @@ k: { locale: 'it', messages: { it: { common: { cancel: 'Indietro' } } } }
233
235
 
234
236
  ## Forms
235
237
 
236
- `useForm({ schema })` takes field definitions (`initial`, `required`, `min`, `max`, `minLength`, `maxLength`, `pattern`, `validate`). Any [Standard Schema](https://standardschema.dev) library (Zod 4, Valibot, ArkType) also works, per field (`schema` in the definition) or for the whole form (`validator`, issues are matched to fields by their first path segment):
238
+ `useForm({ schema })` takes field definitions (`initial`, `required`, `min`, `max`, `minLength`, `maxLength`, `pattern`, `validate`, `isEmpty`). Any [Standard Schema](https://standardschema.dev) library (Zod 4, Valibot, ArkType) also works, per field (`schema` in the definition) or for the whole form (`validator`, issues are matched to fields by their first path segment):
237
239
 
238
240
  ```ts
239
241
  const form = useForm({
@@ -247,7 +249,13 @@ Issues are keyed by their first path segment and by their full dotted path (`blo
247
249
 
248
250
  Built-in messages follow the active language. `messages` overrides them for one form.
249
251
 
250
- Rules that return message keys instead of text (an existing validator library, server side codes) go through `translateMessage(message, { name, label, kind })`: it receives every string returned by a `validate` function, a field `schema`, the form `validator` and `warnings` (`kind` is `'error'` or `'warning'`), and returns the text to show. Built-in messages are not passed to it. Set `translateMessage` and `messages` once for the whole app with `app.use(createFormConfig({ translateMessage: (key) => i18n.t(key), messages: { required: () => i18n.t('form.required') } }))`, or `provideFormConfig()` in a component; the options of one `useForm` still win.
252
+ `schema` also accepts a ref or getter (`MaybeRefOrGetter<FormSchema>`), for one form component reused with different fields, labels or rules per open, mode or language: `schema: () => (mode.value === 'edit' ? editSchema : createSchema)`. Field definitions, labels, `required` and rules follow the source (after the first submit the form validates again when they change). A field that appears starts from its `initial` and is clean; a field that disappears loses its value, error and touched state. With `values` the host owns the values, so only rules and labels change. `form.schema` always returns the current definitions.
253
+
254
+ `required` treats `null`, `undefined`, blank strings, empty arrays, `NaN` and plain objects whose values are all empty (a per-language map like `{ it: '', en: '' }`) as empty. A field with another idea of empty passes `isEmpty: (value) => boolean`, which replaces the check for that field. `isEmptyValue(value)` is the default check, exported for reuse.
255
+
256
+ A `validate` function returns a message, `false` (the built-in invalid message), nothing (valid), or `{ message, params }` when the message needs values: `validate: (value) => value.length > 80 ? { message: 'errors.maxLength', params: { max: 80 } } : undefined`. The params reach `translateMessage` as `context.params`; without a translator they fill the `{name}` placeholders of the message.
257
+
258
+ Rules that return message keys instead of text (an existing validator library, server side codes) go through `translateMessage(message, { name, label, kind, params })`: it receives every string returned by a `validate` function, a field `schema`, the form `validator` and `warnings` (`kind` is `'error'` or `'warning'`), and returns the text to show. Built-in messages are not passed to it. Set `translateMessage` and `messages` once for the whole app with `app.use(createFormConfig({ translateMessage: (key) => i18n.t(key), messages: { required: () => i18n.t('form.required') } }))`, or `provideFormConfig()` in a component; the options of one `useForm` still win.
251
259
 
252
260
  Forms can also drive values they do not own. Pass `values` (a reactive object, ref or getter) and the form reads, validates, tracks dirty state and submits it. The schema becomes optional and only adds rules, labels and `required`; its `initial` values are not used. `reset()` restores the clean snapshot, `reset(next)` replaces the values and makes them the new clean state (`form.reset(record.value)` after a save). `validator` accepts a ref or getter, so it can change with the record type:
253
261
 
@@ -304,7 +312,7 @@ Several controls in one `FormField`. Only the primary control takes the field id
304
312
  | layout | Accordion, AppShell, Breadcrumbs, Card, CopyButton, Grid, NavLink, PageHeader, Separator, Sidebar, SidebarNav, SidebarSection, Stack, Tabs, ThemeSwitch, Toolbar |
305
313
  | templates | DashboardLayout, AuthLayout, DashboardPage, ListPage, DetailPage, FormPage, SettingsPage, SettingsSection, SettingsModal, SettingsGroup, AppearanceSettings, SaveBar, ErrorPage, NotFoundPage, EmptyPage, Brand, AccountMenu |
306
314
  | feedback | Alert, EmptyState, LoadingOverlay, Meter, Progress, ProgressRing, Skeleton |
307
- | grid | DataGrid, GridToolbar, cell renderers (BadgeCell, BooleanCell, DateCell, ImageCell, LinkCell) |
315
+ | grid | DataGrid, GridToolbar, cell renderers (BadgeCell, BooleanCell, DateCell, EmptyCell, ImageCell, LinkCell) |
308
316
  | editor | RichTextEditor |
309
317
  | chart | ChartPanel, ChartLegend, LineChart, BarChart, DoughnutChart |
310
318
 
@@ -313,16 +321,22 @@ Composables: `useTheme`, `usePreset`, `useForm`, `useField`, `useFieldControl`,
313
321
  ### Lists, trees and uploads
314
322
 
315
323
  - `Tree` takes `size="sm"` for dense rows, `checkmark`, `descriptionVariant` (`mono`, the default for paths and slugs, or `text`; a node can override it with its own `descriptionVariant`), `emptyText` (or `#empty`) and works inside a `FormField` (labelled and described by the field). With `reorderable` rows get a drag handle and Alt with the up and down arrows moves them; `nestable` also lets rows change level (drop in the middle of a row, Alt with left and right). The `move` event carries `{ node, target, position, from: { parent, index }, to: { parent, index } }`, `to.index` being the final index, and `applyTreeMove(nodes, move)` returns the updated tree. `canDrop(move)` vetoes a move. Moves are announced to screen readers.
316
- - `Select` takes string, number or boolean option values (`{ value: true, label: 'Yes' }`); the model gets the option value back with its type.
317
- - `Pagination` takes `showEdges` (first and last page buttons, `firstLabel`, `lastLabel`) and `showJump` (a typed page number, `jumpLabel`). Both are off by default.
324
+ - `Select` takes string, number or boolean option values (`{ value: true, label: 'Yes' }`); the model gets the option value back with its type and is typed `T | null | undefined`, so `v-model` works on a narrow ref (`Ref<'a' | 'b' | null>`). An option whose value is `''` is a real option: it is selected and shown with its own label, distinct from the placeholder, which still maps to `null`.
325
+ - `Input` and `Textarea` take `counter`: with a `maxlength` attribute they show `length / maxlength` inside the field, describe the control with "12 of 40 characters" (`form.characterCount`) and announce "Character limit reached" when it is hit.
326
+ - `Input` takes `transform: (value: string) => string`, applied to the typed text before it reaches the model (the caret keeps its place; IME composition is transformed when it ends). `:transform="(value) => value.toUpperCase()"` enforces uppercase codes. Values set from outside are not transformed.
327
+ - `FormField` takes `optional`, a muted localized "Optional" marker in the label row (`form.optional`, `optionalText` overrides it), the counterpart of the required asterisk. It is hidden when the field is required.
328
+ - `CheckboxGroup` takes `columns` (a number): the options are laid out in a grid with that many equal columns. Unset keeps the `orientation` layout.
329
+ - `Combobox` with `multiple` takes `hideTags`: the selected values are not rendered as tags inside the control (the host shows them elsewhere), the placeholder stays visible and Backspace no longer removes the last value. Selection is still announced through the options.
330
+ - `Pagination` takes `showEdges` (first and last page buttons, `firstLabel`, `lastLabel`) and `showJump` (a typed page number, `jumpLabel`). Both are off by default. `page` is clamped to the page count only when `total` is known: while `total` is `0` or `loading` is set (it also sets `aria-busy`), the page is kept, so a server reload does not reset it.
318
331
  - `FilterPills` can list active filters: options with `removable: true` render as active pills with a remove button (`removeLabel`, a `'Remove {label}'` template). Clicking the pill emits `select(value)`, the button emits `remove(value)` (and drops the value from the model when it is selected); focus moves to the next pill when the removed one disappears.
319
332
  - `Calendar` takes `dayText: (iso) => string | undefined`, a short line under each day number that is also added to the day's accessible name (availability, prices). The `#day` slot (`{ date, number, text, selected, unavailable, inMonth }`) replaces the cell content; it is decorative, so announced text still comes from `dayText`.
320
- - `Separator` takes `description` (or `#description`) under its label, and an `#actions` slot rendered next to them (a small add or edit button).
333
+ - `Separator` takes `description` (or `#description`) under its label, and an `#actions` slot rendered next to them (a small add or edit button). `variant="heading"` (`SeparatorVariant`, `'label' | 'heading'`, default `'label'`) shows the label as a section title on its own (an `h3`, `level` changes it) with the description below and the actions at the end, under one line above instead of the small muted label between two lines. `:lines="false"` (default `true`) hides the lines of either variant; `--k-separator-heading-font-size` (0.9375rem) sizes the heading.
321
334
  - `Modal` takes `width` (any CSS length, overrides `size`) and `minHeight` (keeps the height steady while tabs or steps change). The size widths are tokens: `--k-modal-width-sm` (26rem), `--k-modal-width-md` (32rem), `--k-modal-width-lg` (50rem), `--k-modal-width-xl` (64rem).
322
335
  - `Tabs` items take `dot: true` for a small indicator without a number (`dotTone`, default `danger`; `dotLabel` is the hidden text, default "Needs attention").
323
336
  - `DropdownMenu` takes `matchTriggerWidth` (the menu is at least as wide as its trigger) and `minWidth` (a CSS length).
324
337
  - `PageHeader` takes `align` (`'end'` by default, `'start'` or `'center'`) for the title block and the actions, and `truncate` to keep the title on one line with an ellipsis (the full title is in its `title` attribute).
325
338
  - `RichTextEditor` accepts `'underline'` in `toolbar` (not in the default toolbar).
339
+ - `RichTextEditor` takes `linkTypes` (`EditorLinkType[]`, `'url' | 'email' | 'phone'`, default `['url']`). With more than one type the link form shows a type select; email links get `mailto:` and phone links `tel:` (spaces and separators removed). When no text is selected the form asks for the link text too (`editor.linkText`) and inserts it as a link, falling back to the address.
326
340
  - `BarChart` takes `colorByPoint` (every bar of a series takes the next palette color, or one from `colors: string[]`; the legend lists and toggles the categories). The palette comes from `--k-chart-1` to `--k-chart-8`.
327
341
  - `SelectionBar` shows a plural aware selected count (announced in a live region), select all (`total` plus `@select-all`) and clear (`@clear`) actions, an `#actions` slot and `placement` `inline`, `sticky` or `floating`. With `count` 0 it renders no box (only its live region), so it can sit in a flex toolbar without adding a gap; the first count after mount is announced too. `ListPage` renders it for `#bulk`.
328
342
  - `FilterPills` is generic: the value type comes from the options, `multiple` makes the model an array and `required` drops `null` from it (like `SegmentedControl`). Options take `menu` items (a dropdown trigger showing `valueLabel`) or `panel: true`, which opens a popover with the `#panel-<value>` slot (`{ option, active, toggle, close }`, `panelLabel` names it). The `#pill` slot renders any trigger and its scope has `defaultPill`, a component that renders the built-in pill for that option, so one custom pill does not mean rewriting the others: `<component :is="defaultPill" v-else />`. `FilterPill` is exported so custom triggers match the other pills.
@@ -625,8 +639,13 @@ Accessibility related props and defaults:
625
639
  - `DataGrid` sort is a model: `v-model:sort` (`{ column, direction } | null`) sets the sorted column from outside (server side sort, a sort restored from the URL) and follows header clicks. Leave it unset for an uncontrolled grid. `sortChange` still fires with `serverSort`.
626
640
  - `DataGrid` takes `rowHeight` and `headerHeight` (pixels) for an exact row size on top of `density`, and `headerText: (field, column) => string | undefined` to name every column that has a `field` and no `headerName` (from your i18n).
627
641
  - `DataGrid` `filterable` registers the AG Grid text, number and date filters (`registerGridFilterModules()` does it without the prop) and turns `filter` on for every column. `filterChange(model, event)` fires with `api.getFilterModel()` whenever a column filter changes, also when the filters come from `gridOptions` or your own registered modules.
628
- - `DateCell` takes `relative: true` (or `Intl.RelativeTimeFormatOptions`) in `cellRendererParams` for "3 days ago"; the absolute date stays in the `title`.
629
- - `DataGrid` takes `gridOptions` for any AG Grid option it does not wrap (`rowHeight`, `onCellEditingStopped`, `onFilterChanged`, ...). They are applied before the props of `DataGrid`, so its own props win, and handlers from both run.
642
+ - `DateCell` takes `relative: true` (or `Intl.RelativeTimeFormatOptions`) in `cellRendererParams` for "3 days ago"; the absolute date stays in the `title`. `relativeWithin` (milliseconds, default unset: always relative) shows the absolute date instead once the value is further than that from now, in the past or the future (`relativeWithin: 7 * 86_400_000` keeps "3 days ago" and prints older dates in full).
643
+ - `DataGrid` takes `gridOptions` for any AG Grid option it does not wrap (`rowHeight`, `onCellEditingStopped`, `onFilterChanged`, ...). A prop of `DataGrid` wins only when it is set: `getRowId`, `rowSelection` (when `selectable` is off), `autoSizeStrategy` (also `undefined` to turn it off), `tooltipShowDelay`, `overlayNoRowsTemplate` and `overlayLoadingTemplate` from `gridOptions` are used otherwise; `defaultColDef` and `localeText` are merged over the component defaults, and `context` is merged under the cell context. Handlers from both run.
644
+ - `DataGrid` row ids: `getRowId: (params) => string` (wins), `rowIdKey` (a field of the row), or `gridOptions.getRowId`.
645
+ - `DataGrid` `fill` makes the grid, with its toolbar and footer, fill its parent (a flex or grid item with `flex: 1; min-height: 0`, or `height: 100%` in a block with a height) instead of the fixed `height`. Ignored with `autoHeight`.
646
+ - `DataGrid` `showEdges` and `showJump` reach the footer pager (first and last page buttons, page number input). `loading` also keeps the pager from resetting the page while a server total is unknown.
647
+ - `DataGrid` registers the AG Grid cell editors (text, large text, number, date, checkbox, select, custom and undo/redo) when `editable` is set or when a column or `gridOptions.defaultColDef` has `editable` at mount. `registerGridEditorModules()` from `/grid` does it by hand, before the first grid mounts, for columns that become editable later.
648
+ - `ImageCell` takes `fit` (`'cover'` default, or `'contain'` for logos) and `size` (any CSS length, default `1.75rem`) in `cellRendererParams`. `EmptyCell` (the hidden "Empty" dash) is exported from `/grid` for custom renderers.
630
649
  - `DataGrid` translates the AG Grid ARIA strings through the catalog (`grid.*`; your own `localeText` still wins) and announces row count changes.
631
650
 
632
651
  ## Browser support
@@ -25,6 +25,9 @@
25
25
  <span v-if="isRequired && showRequiredHint" class="k-visually-hidden">{{
26
26
  t("form.requiredHint")
27
27
  }}</span>
28
+ <span v-if="optional && !isRequired" class="k-form-field-optional">{{
29
+ optionalText ?? t("form.optional")
30
+ }}</span>
28
31
  </label>
29
32
  <span v-if="hint || $slots.hint" :id="hintId" class="k-form-field-hint">
30
33
  <slot name="hint">{{ hint }}</slot>
@@ -76,6 +79,8 @@ const props = defineProps({
76
79
  error: { type: [String, Boolean], required: false, default: void 0 },
77
80
  warning: { type: [String, Boolean], required: false, default: void 0 },
78
81
  required: { type: Boolean, required: false },
82
+ optional: { type: Boolean, required: false },
83
+ optionalText: { type: String, required: false },
79
84
  disabled: { type: Boolean, required: false },
80
85
  icon: { type: null, required: false },
81
86
  id: { type: String, required: false }
@@ -23,6 +23,8 @@ declare const __VLS_base: import("vue").DefineComponent<{
23
23
  error?: string | false;
24
24
  warning?: string | false;
25
25
  required?: boolean;
26
+ optional?: boolean;
27
+ optionalText?: string;
26
28
  disabled?: boolean;
27
29
  icon?: IconName;
28
30
  id?: string | undefined;
@@ -225,6 +227,8 @@ declare const __VLS_base: import("vue").DefineComponent<{
225
227
  error?: string | false;
226
228
  warning?: string | false;
227
229
  required?: boolean;
230
+ optional?: boolean;
231
+ optionalText?: string;
228
232
  disabled?: boolean;
229
233
  icon?: IconName;
230
234
  id?: string | undefined;
@@ -7,10 +7,14 @@
7
7
  'has-lead': hasLead,
8
8
  'has-trail': hasTrail,
9
9
  'is-numeric': numeric,
10
- 'is-disabled': control.disabled.value
10
+ 'is-disabled': control.disabled.value,
11
+ 'has-counter': counterState().visible
11
12
  }
12
13
  ]"
13
- :style="props.style"
14
+ :style="[
15
+ props.style,
16
+ counterState().visible ? { '--k-input-counter-chars': String(counterState().width) } : {}
17
+ ]"
14
18
  >
15
19
  <span v-if="hasLead" class="k-input-lead">
16
20
  <slot name="leading"><Icon v-if="icon" :name="icon" /></slot>
@@ -29,13 +33,20 @@
29
33
  :disabled="control.disabled.value || void 0"
30
34
  :required="control.required.value || void 0"
31
35
  :aria-invalid="control.invalid.value ? 'true' : void 0"
32
- :aria-describedby="joinIds($attrs['aria-describedby'], control.describedBy.value)"
36
+ :aria-describedby="
37
+ joinIds(
38
+ $attrs['aria-describedby'],
39
+ control.describedBy.value,
40
+ counterState().visible && counterState().id
41
+ )
42
+ "
33
43
  :type="type"
34
44
  :value="model ?? ''"
35
45
  :readonly="readonly"
36
46
  :class="['k-field', `k-field-${size}`, { 'is-invalid': control.invalid.value }]"
37
47
  @input="onInput"
38
48
  @change="onChange"
49
+ @compositionend="onInput"
39
50
  />
40
51
  <span v-if="hasTrail" class="k-input-trail">
41
52
  <button
@@ -50,15 +61,28 @@
50
61
  </button>
51
62
  <slot name="trailing" />
52
63
  </span>
64
+ <span
65
+ v-if="counterState().visible"
66
+ class="k-field-counter k-tabular"
67
+ :class="{ 'is-over': counterState().atLimit }"
68
+ aria-hidden="true"
69
+ >{{ counterState().text }}</span
70
+ >
71
+ <span v-if="counterState().visible" :id="counterState().id" class="k-visually-hidden">{{
72
+ counterState().label
73
+ }}</span>
74
+ <LiveRegion v-if="counter" :message="counterState().limitMessage" />
53
75
  </div>
54
76
  </template>
55
77
 
56
78
  <script setup>
57
- import { computed, useSlots, useTemplateRef } from "vue";
79
+ import { computed, useAttrs, useSlots, useTemplateRef } from "vue";
58
80
  import { useFieldControl } from "../../composables/useField";
59
81
  import { useLocale } from "../../composables/useLocale";
60
82
  import Icon from "./Icon.vue";
61
83
  import { joinIds } from "../../internal/forms/control";
84
+ import { useCharacterCounter } from "../../internal/forms/counter";
85
+ import LiveRegion from "../../internal/core/LiveRegion.vue";
62
86
  defineOptions({ inheritAttrs: false });
63
87
  const props = defineProps({
64
88
  type: { type: String, required: false, default: "text" },
@@ -72,6 +96,8 @@ const props = defineProps({
72
96
  readonly: { type: Boolean, required: false },
73
97
  required: { type: Boolean, required: false },
74
98
  noAutofill: { type: Boolean, required: false },
99
+ counter: { type: Boolean, required: false },
100
+ transform: { type: Function, required: false },
75
101
  class: { type: null, required: false },
76
102
  style: { type: null, required: false }
77
103
  });
@@ -81,6 +107,7 @@ const { t } = useLocale();
81
107
  const clearText = computed(() => props.clearLabel ?? t("common.clear"));
82
108
  const slots = useSlots();
83
109
  const inputRef = useTemplateRef("inputRef");
110
+ const attrs = useAttrs();
84
111
  const control = useFieldControl({
85
112
  invalid: () => props.invalid,
86
113
  disabled: () => props.disabled,
@@ -95,6 +122,11 @@ const showClear = computed(
95
122
  );
96
123
  const hasLead = computed(() => Boolean(props.icon || slots.leading));
97
124
  const hasTrail = computed(() => Boolean(props.clearable || slots.trailing));
125
+ const counterState = useCharacterCounter({
126
+ enabled: () => props.counter,
127
+ value: () => model.value,
128
+ attrs
129
+ });
98
130
  function parse(raw) {
99
131
  if (modifiers.trim) raw = raw.trim();
100
132
  if (!parsesNumber.value) return raw;
@@ -102,8 +134,30 @@ function parse(raw) {
102
134
  const parsed = Number(raw.replace(",", "."));
103
135
  return Number.isNaN(parsed) ? raw : parsed;
104
136
  }
137
+ function applyTransform(target) {
138
+ const raw = target.value;
139
+ if (!props.transform) return raw;
140
+ const next = props.transform(raw);
141
+ if (next === raw) return raw;
142
+ const caret = target.selectionEnd;
143
+ target.value = next;
144
+ if (caret !== null) {
145
+ const position = Math.max(0, next.length - (raw.length - caret));
146
+ try {
147
+ target.setSelectionRange(position, position);
148
+ } catch {
149
+ return next;
150
+ }
151
+ }
152
+ return next;
153
+ }
105
154
  function onInput(event) {
106
- model.value = parse(event.target.value);
155
+ const target = event.target;
156
+ if (event.isComposing) {
157
+ model.value = parse(target.value);
158
+ return;
159
+ }
160
+ model.value = parse(applyTransform(target));
107
161
  }
108
162
  function onChange(event) {
109
163
  if (!modifiers.trim) return;
@@ -18,6 +18,8 @@ declare const __VLS_base: import("vue").DefineComponent<{
18
18
  readonly?: (boolean | "true" | "false") | undefined;
19
19
  required?: (boolean | "true" | "false") | undefined;
20
20
  noAutofill?: boolean;
21
+ counter?: boolean;
22
+ transform?: (value: string) => string;
21
23
  class?: unknown;
22
24
  style?: unknown;
23
25
  modelValue?: string | number | null | undefined;
@@ -233,13 +235,13 @@ declare const __VLS_base: import("vue").DefineComponent<{
233
235
  popovertarget?: string | undefined;
234
236
  popovertargetaction?: import("../../index.ts").PopoverTargetAction | undefined;
235
237
  autocomplete?: import("vue").InputAutoCompleteAttribute | undefined;
238
+ maxlength?: (string | number) | undefined;
239
+ multiple?: (boolean | "true" | "false") | undefined;
236
240
  accept?: string | undefined | undefined;
237
241
  alt?: string | undefined | undefined;
238
242
  capture?: boolean | "user" | "environment" | undefined | undefined;
239
243
  crossorigin?: string | undefined | undefined;
240
- maxlength?: (string | number) | undefined;
241
244
  minlength?: (string | number) | undefined;
242
- multiple?: (boolean | "true" | "false") | undefined;
243
245
  src?: string | undefined | undefined;
244
246
  onCancel?: ((payload: Event) => void) | undefined;
245
247
  }, {
@@ -261,6 +263,8 @@ declare const __VLS_base: import("vue").DefineComponent<{
261
263
  readonly?: (boolean | "true" | "false") | undefined;
262
264
  required?: (boolean | "true" | "false") | undefined;
263
265
  noAutofill?: boolean;
266
+ counter?: boolean;
267
+ transform?: (value: string) => string;
264
268
  class?: unknown;
265
269
  style?: unknown;
266
270
  modelValue?: string | number | null | undefined;
@@ -476,13 +480,13 @@ declare const __VLS_base: import("vue").DefineComponent<{
476
480
  popovertarget?: string | undefined;
477
481
  popovertargetaction?: import("../../index.ts").PopoverTargetAction | undefined;
478
482
  autocomplete?: import("vue").InputAutoCompleteAttribute | undefined;
483
+ maxlength?: (string | number) | undefined;
484
+ multiple?: (boolean | "true" | "false") | undefined;
479
485
  accept?: string | undefined | undefined;
480
486
  alt?: string | undefined | undefined;
481
487
  capture?: boolean | "user" | "environment" | undefined | undefined;
482
488
  crossorigin?: string | undefined | undefined;
483
- maxlength?: (string | number) | undefined;
484
489
  minlength?: (string | number) | undefined;
485
- multiple?: (boolean | "true" | "false") | undefined;
486
490
  src?: string | undefined | undefined;
487
491
  onCancel?: ((payload: Event) => void) | undefined;
488
492
  }> & Readonly<{
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div
3
3
  class="k-textarea-box"
4
- :class="[props.class, { 'has-trail': hasTrail }]"
4
+ :class="[props.class, { 'has-trail': hasTrail, 'has-counter': counterState().visible }]"
5
5
  :style="props.style"
6
6
  >
7
7
  <textarea
@@ -17,7 +17,13 @@
17
17
  :disabled="control.disabled.value || void 0"
18
18
  :required="control.required.value || void 0"
19
19
  :aria-invalid="control.invalid.value ? 'true' : void 0"
20
- :aria-describedby="joinIds($attrs['aria-describedby'], control.describedBy.value)"
20
+ :aria-describedby="
21
+ joinIds(
22
+ $attrs['aria-describedby'],
23
+ control.describedBy.value,
24
+ counterState().visible && counterState().id
25
+ )
26
+ "
21
27
  :rows="rows"
22
28
  :value="model ?? ''"
23
29
  :readonly="readonly"
@@ -41,13 +47,26 @@
41
47
  <span v-if="hasTrail" class="k-textarea-trail">
42
48
  <slot name="trailing" />
43
49
  </span>
50
+ <span
51
+ v-if="counterState().visible"
52
+ class="k-field-counter k-tabular"
53
+ :class="{ 'is-over': counterState().atLimit }"
54
+ aria-hidden="true"
55
+ >{{ counterState().text }}</span
56
+ >
57
+ <span v-if="counterState().visible" :id="counterState().id" class="k-visually-hidden">{{
58
+ counterState().label
59
+ }}</span>
60
+ <LiveRegion v-if="counter" :message="counterState().limitMessage" />
44
61
  </div>
45
62
  </template>
46
63
 
47
64
  <script setup>
48
- import { computed, nextTick, onMounted, useSlots, useTemplateRef, watch } from "vue";
65
+ import { computed, nextTick, onMounted, useAttrs, useSlots, useTemplateRef, watch } from "vue";
49
66
  import { useFieldControl } from "../../composables/useField";
50
67
  import { joinIds } from "../../internal/forms/control";
68
+ import { useCharacterCounter } from "../../internal/forms/counter";
69
+ import LiveRegion from "../../internal/core/LiveRegion.vue";
51
70
  defineOptions({ inheritAttrs: false });
52
71
  const props = defineProps({
53
72
  size: { type: String, required: false, default: "md" },
@@ -60,12 +79,18 @@ const props = defineProps({
60
79
  readonly: { type: Boolean, required: false },
61
80
  required: { type: Boolean, required: false },
62
81
  noAutofill: { type: Boolean, required: false },
82
+ counter: { type: Boolean, required: false },
63
83
  class: { type: null, required: false },
64
84
  style: { type: null, required: false }
65
85
  });
66
86
  const model = defineModel({ type: null });
67
87
  const slots = useSlots();
68
88
  const hasTrail = computed(() => Boolean(slots.trailing));
89
+ const counterState = useCharacterCounter({
90
+ enabled: () => props.counter,
91
+ value: () => model.value,
92
+ attrs: useAttrs()
93
+ });
69
94
  const textareaRef = useTemplateRef("textareaRef");
70
95
  const control = useFieldControl({
71
96
  invalid: () => props.invalid,
@@ -14,6 +14,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
14
14
  readonly?: (boolean | "true" | "false") | undefined;
15
15
  required?: (boolean | "true" | "false") | undefined;
16
16
  noAutofill?: boolean;
17
+ counter?: boolean;
17
18
  class?: unknown;
18
19
  style?: unknown;
19
20
  modelValue?: string | null | undefined;
@@ -209,14 +210,14 @@ declare const __VLS_base: import("vue").DefineComponent<{
209
210
  is?: string | undefined | undefined;
210
211
  exportparts?: string | undefined;
211
212
  part?: string | undefined;
212
- popover?: (import("../..").PopoverMode | "" | boolean) | undefined;
213
+ popover?: (import("../../index.ts").PopoverMode | "" | boolean) | undefined;
213
214
  autofocus?: (boolean | "true" | "false") | undefined;
214
215
  autocomplete?: string | undefined | undefined;
215
216
  maxlength?: (string | number) | undefined;
217
+ wrap?: string | undefined | undefined;
216
218
  minlength?: (string | number) | undefined;
217
219
  cols?: (string | number) | undefined;
218
220
  dirname?: string | undefined | undefined;
219
- wrap?: string | undefined | undefined;
220
221
  }, {
221
222
  focus: () => void | undefined;
222
223
  el: Readonly<import("vue").ShallowRef<HTMLTextAreaElement | null>>;
@@ -233,6 +234,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
233
234
  readonly?: (boolean | "true" | "false") | undefined;
234
235
  required?: (boolean | "true" | "false") | undefined;
235
236
  noAutofill?: boolean;
237
+ counter?: boolean;
236
238
  class?: unknown;
237
239
  style?: unknown;
238
240
  modelValue?: string | null | undefined;
@@ -428,14 +430,14 @@ declare const __VLS_base: import("vue").DefineComponent<{
428
430
  is?: string | undefined | undefined;
429
431
  exportparts?: string | undefined;
430
432
  part?: string | undefined;
431
- popover?: (import("../..").PopoverMode | "" | boolean) | undefined;
433
+ popover?: (import("../../index.ts").PopoverMode | "" | boolean) | undefined;
432
434
  autofocus?: (boolean | "true" | "false") | undefined;
433
435
  autocomplete?: string | undefined | undefined;
434
436
  maxlength?: (string | number) | undefined;
437
+ wrap?: string | undefined | undefined;
435
438
  minlength?: (string | number) | undefined;
436
439
  cols?: (string | number) | undefined;
437
440
  dirname?: string | undefined | undefined;
438
- wrap?: string | undefined | undefined;
439
441
  }> & Readonly<{
440
442
  "onUpdate:modelValue"?: ((value: string | null | undefined) => any) | undefined;
441
443
  }>, {
@@ -410,8 +410,8 @@ declare const __VLS_base: import("vue").DefineComponent<{
410
410
  ref_key?: string | undefined | undefined;
411
411
  }> & Readonly<{}>, {
412
412
  columns: number;
413
- items: DescriptionItem[];
414
413
  layout: DescriptionLayout;
414
+ items: DescriptionItem[];
415
415
  divided: boolean;
416
416
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
417
417
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
@@ -1,5 +1,9 @@
1
1
  <template>
2
- <div class="k-pagination" :class="`k-pagination-${size}`">
2
+ <div
3
+ class="k-pagination"
4
+ :class="[`k-pagination-${size}`, { 'is-loading': loading }]"
5
+ :aria-busy="loading ? 'true' : void 0"
6
+ >
3
7
  <p v-if="showSummary" class="k-pagination-summary" aria-live="polite">
4
8
  <slot name="summary" :start="range.start" :end="range.end" :total="total">
5
9
  <template v-if="total > 0">
@@ -130,6 +134,7 @@ const props = defineProps({
130
134
  size: { type: String, required: false, default: "md" },
131
135
  compact: { type: Boolean, required: false },
132
136
  disabled: { type: Boolean, required: false },
137
+ loading: { type: Boolean, required: false },
133
138
  showSummary: { type: Boolean, required: false, default: true },
134
139
  showEdges: { type: Boolean, required: false },
135
140
  showJump: { type: Boolean, required: false },
@@ -168,7 +173,8 @@ const range = computed(() => ({
168
173
  start: props.total === 0 ? 0 : (page.value - 1) * pageSize.value + 1,
169
174
  end: Math.min(page.value * pageSize.value, props.total)
170
175
  }));
171
- watch(pageCount, (count) => {
176
+ watch([pageCount, () => props.total, () => props.loading], ([count, total, loading]) => {
177
+ if (loading || total <= 0) return;
172
178
  if (page.value > count) page.value = count;
173
179
  });
174
180
  function go(target) {
@@ -13,6 +13,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
13
13
  size?: "sm" | "md";
14
14
  compact?: boolean;
15
15
  disabled?: boolean;
16
+ loading?: boolean;
16
17
  showSummary?: boolean;
17
18
  showEdges?: boolean;
18
19
  showJump?: boolean;
@@ -230,6 +231,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
230
231
  size?: "sm" | "md";
231
232
  compact?: boolean;
232
233
  disabled?: boolean;
234
+ loading?: boolean;
233
235
  showSummary?: boolean;
234
236
  showEdges?: boolean;
235
237
  showJump?: boolean;
@@ -9,8 +9,13 @@
9
9
  class="k-choice-group k-checkbox-group"
10
10
  :class="[
11
11
  `k-choice-group-${orientation}`,
12
- { 'is-invalid': control.invalid.value, 'is-disabled': control.disabled.value }
12
+ {
13
+ 'is-invalid': control.invalid.value,
14
+ 'is-disabled': control.disabled.value,
15
+ 'has-columns': Boolean(columns)
16
+ }
13
17
  ]"
18
+ :style="columns ? { '--k-choice-columns': String(columns) } : void 0"
14
19
  >
15
20
  <legend v-if="hasLegend" class="k-choice-group-legend">
16
21
  <slot name="legend">{{ legendText }}</slot>
@@ -43,6 +48,7 @@ const props = defineProps({
43
48
  label: { type: String, required: false },
44
49
  legend: { type: String, required: false },
45
50
  orientation: { type: String, required: false, default: "vertical" },
51
+ columns: { type: Number, required: false },
46
52
  size: { type: String, required: false, default: "md" },
47
53
  name: { type: String, required: false },
48
54
  invalid: { type: Boolean, required: false, default: void 0 },
@@ -6,6 +6,7 @@ declare const __VLS_export: <T extends ChoiceValue>(__VLS_props: NonNullable<Awa
6
6
  label?: string;
7
7
  legend?: string;
8
8
  orientation?: ChoiceOrientation;
9
+ columns?: number;
9
10
  size?: FieldSize;
10
11
  name?: string;
11
12
  invalid?: boolean;
@@ -225,13 +225,13 @@ declare const __VLS_export: import("vue").DefineComponent<{
225
225
  popovertarget?: string | undefined;
226
226
  popovertargetaction?: import("../../index.ts").PopoverTargetAction | undefined;
227
227
  autocomplete?: import("vue").InputAutoCompleteAttribute | undefined;
228
+ maxlength?: (string | number) | undefined;
229
+ multiple?: (boolean | "true" | "false") | undefined;
228
230
  accept?: string | undefined | undefined;
229
231
  alt?: string | undefined | undefined;
230
232
  capture?: boolean | "user" | "environment" | undefined | undefined;
231
233
  crossorigin?: string | undefined | undefined;
232
- maxlength?: (string | number) | undefined;
233
234
  minlength?: (string | number) | undefined;
234
- multiple?: (boolean | "true" | "false") | undefined;
235
235
  src?: string | undefined | undefined;
236
236
  onCancel?: ((payload: Event) => void) | undefined;
237
237
  }, {
@@ -464,13 +464,13 @@ declare const __VLS_export: import("vue").DefineComponent<{
464
464
  popovertarget?: string | undefined;
465
465
  popovertargetaction?: import("../../index.ts").PopoverTargetAction | undefined;
466
466
  autocomplete?: import("vue").InputAutoCompleteAttribute | undefined;
467
+ maxlength?: (string | number) | undefined;
468
+ multiple?: (boolean | "true" | "false") | undefined;
467
469
  accept?: string | undefined | undefined;
468
470
  alt?: string | undefined | undefined;
469
471
  capture?: boolean | "user" | "environment" | undefined | undefined;
470
472
  crossorigin?: string | undefined | undefined;
471
- maxlength?: (string | number) | undefined;
472
473
  minlength?: (string | number) | undefined;
473
- multiple?: (boolean | "true" | "false") | undefined;
474
474
  src?: string | undefined | undefined;
475
475
  onCancel?: ((payload: Event) => void) | undefined;
476
476
  }> & Readonly<{
@@ -28,7 +28,7 @@
28
28
  @mousedown="onControlMousedown"
29
29
  >
30
30
  <span v-if="icon" class="k-combobox-lead" aria-hidden="true"><Icon :name="icon" /></span>
31
- <template v-if="multiple">
31
+ <template v-if="multiple && !hideTags">
32
32
  <span v-for="value in selectedValues" :key="keyOf(value)" class="k-combobox-tag">
33
33
  <span class="k-combobox-tag-label">{{ labelFor(value) }}</span>
34
34
  <button
@@ -211,6 +211,7 @@ const props = defineProps({
211
211
  search: { type: Function, required: false },
212
212
  filter: { type: Function, required: false },
213
213
  multiple: { type: Boolean, required: false },
214
+ hideTags: { type: Boolean, required: false },
214
215
  creatable: { type: [Boolean, Function], required: false },
215
216
  debounce: { type: Number, required: false, default: 250 },
216
217
  minQueryLength: { type: Number, required: false, default: 0 },
@@ -316,7 +317,7 @@ const inputValue = computed(() => {
316
317
  return selectedLabel.value;
317
318
  });
318
319
  const inputPlaceholder = computed(
319
- () => props.multiple && selectedValues.value.length ? void 0 : props.placeholder
320
+ () => props.multiple && !props.hideTags && selectedValues.value.length ? void 0 : props.placeholder
320
321
  );
321
322
  const showClear = computed(
322
323
  () => props.clearable && interactive.value && selectedValues.value.length > 0
@@ -517,7 +518,7 @@ function onKeydown(event) {
517
518
  }
518
519
  break;
519
520
  case "Backspace":
520
- if (props.multiple && !query.value && selectedValues.value.length) {
521
+ if (props.multiple && !props.hideTags && !query.value && selectedValues.value.length) {
521
522
  event.preventDefault();
522
523
  remove(selectedValues.value[selectedValues.value.length - 1]);
523
524
  }