@xleddyl/443-components-vue 0.8.1 → 0.8.3

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 (164) hide show
  1. package/README.md +52 -4
  2. package/dist/chart/BarChart.vue +33 -22
  3. package/dist/chart/BarChart.vue.d.ts +4 -0
  4. package/dist/chart/chart.css +2 -2
  5. package/dist/chart/useChartColors.mjs +8 -8
  6. package/dist/components/core/Button.vue.d.ts +1 -1
  7. package/dist/components/core/FormField.vue +5 -0
  8. package/dist/components/core/FormField.vue.d.ts +4 -0
  9. package/dist/components/core/Input.vue +59 -5
  10. package/dist/components/core/Input.vue.d.ts +9 -5
  11. package/dist/components/core/Textarea.vue +28 -3
  12. package/dist/components/core/Textarea.vue.d.ts +7 -5
  13. package/dist/components/core/types.d.ts +1 -1
  14. package/dist/components/data/Badge.vue.d.ts +1 -1
  15. package/dist/components/data/DescriptionList.vue.d.ts +1 -1
  16. package/dist/components/data/FilterPills.vue +43 -4
  17. package/dist/components/data/FilterPills.vue.d.ts +4 -1
  18. package/dist/components/data/Pagination.vue +63 -2
  19. package/dist/components/data/Pagination.vue.d.ts +12 -0
  20. package/dist/components/data/Tag.vue.d.ts +1 -1
  21. package/dist/components/data/Tree.vue.d.ts +1 -1
  22. package/dist/components/data/types.d.ts +1 -0
  23. package/dist/components/feedback/Meter.vue.d.ts +1 -1
  24. package/dist/components/feedback/Progress.vue.d.ts +1 -1
  25. package/dist/components/feedback/ProgressRing.vue.d.ts +1 -1
  26. package/dist/components/forms/Calendar.vue +31 -7
  27. package/dist/components/forms/Calendar.vue.d.ts +13 -2
  28. package/dist/components/forms/CheckboxGroup.vue +7 -1
  29. package/dist/components/forms/CheckboxGroup.vue.d.ts +1 -0
  30. package/dist/components/forms/ColorInput.vue.d.ts +5 -5
  31. package/dist/components/forms/Combobox.vue +4 -3
  32. package/dist/components/forms/Combobox.vue.d.ts +1 -0
  33. package/dist/components/forms/DateInput.vue.d.ts +10 -6
  34. package/dist/components/forms/DatePicker.vue.d.ts +1 -1
  35. package/dist/components/forms/FileInput.vue.d.ts +1 -1
  36. package/dist/components/forms/NumberInput.vue.d.ts +5 -5
  37. package/dist/components/forms/PasswordInput.vue.d.ts +9 -5
  38. package/dist/components/forms/PinInput.vue.d.ts +5 -5
  39. package/dist/components/forms/Select.vue +21 -2
  40. package/dist/components/forms/Select.vue.d.ts +4 -4
  41. package/dist/components/forms/Slider.vue.d.ts +5 -5
  42. package/dist/components/forms/Switch.vue.d.ts +1 -1
  43. package/dist/components/forms/TimeInput.vue.d.ts +9 -5
  44. package/dist/components/layout/AppShell.vue +4 -2
  45. package/dist/components/layout/AppShell.vue.d.ts +2 -0
  46. package/dist/components/layout/Card.vue.d.ts +1 -1
  47. package/dist/components/layout/CopyButton.vue.d.ts +1 -1
  48. package/dist/components/layout/PageHeader.vue +15 -3
  49. package/dist/components/layout/PageHeader.vue.d.ts +5 -0
  50. package/dist/components/layout/Separator.vue +17 -4
  51. package/dist/components/layout/Separator.vue.d.ts +7 -1
  52. package/dist/components/layout/Tabs.vue +10 -0
  53. package/dist/components/layout/types.d.ts +3 -0
  54. package/dist/components/overlays/ConfirmDialog.vue.d.ts +8 -4
  55. package/dist/components/overlays/ConfirmHost.vue.d.ts +8 -4
  56. package/dist/components/overlays/ContextMenu.vue.d.ts +4 -2
  57. package/dist/components/overlays/DropdownMenu.vue +5 -1
  58. package/dist/components/overlays/DropdownMenu.vue.d.ts +8 -2
  59. package/dist/components/overlays/Modal.vue +9 -2
  60. package/dist/components/overlays/Modal.vue.d.ts +4 -0
  61. package/dist/components/overlays/types.d.ts +2 -2
  62. package/dist/components/templates/DashboardLayout.vue +2 -0
  63. package/dist/components/templates/DashboardLayout.vue.d.ts +2 -0
  64. package/dist/components/templates/SettingsModal.vue.d.ts +8 -4
  65. package/dist/components/templates/types.d.ts +1 -0
  66. package/dist/composables/useForm.d.ts +24 -2
  67. package/dist/composables/useForm.mjs +51 -8
  68. package/dist/composables/useOverlay.d.ts +1 -1
  69. package/dist/editor/RichTextEditor.vue +108 -10
  70. package/dist/editor/RichTextEditor.vue.d.ts +5 -2
  71. package/dist/editor/rich-text-editor.css +31 -4
  72. package/dist/editor/types.d.ts +2 -1
  73. package/dist/grid/DataGrid.vue +106 -24
  74. package/dist/grid/DataGrid.vue.d.ts +14 -2
  75. package/dist/grid/cells/DateCell.vue +18 -3
  76. package/dist/grid/cells/ImageCell.vue +6 -1
  77. package/dist/grid/data-grid.css +17 -3
  78. package/dist/grid/index.d.ts +2 -1
  79. package/dist/grid/index.mjs +6 -1
  80. package/dist/grid/modules.d.ts +2 -0
  81. package/dist/grid/modules.mjs +32 -0
  82. package/dist/grid/theme.mjs +5 -5
  83. package/dist/grid/types.d.ts +4 -0
  84. package/dist/icons/builtin.d.ts +3 -0
  85. package/dist/icons/builtin.mjs +3 -0
  86. package/dist/index.d.ts +1 -1
  87. package/dist/index.mjs +1 -0
  88. package/dist/internal/core/intl.d.ts +2 -0
  89. package/dist/internal/core/intl.mjs +17 -0
  90. package/dist/internal/forms/counter.d.ts +18 -0
  91. package/dist/internal/forms/counter.mjs +31 -0
  92. package/dist/internal/keys.d.ts +2 -1
  93. package/dist/internal/keys.mjs +1 -0
  94. package/dist/internal/overlays/MenuList.vue +4 -2
  95. package/dist/internal/overlays/MenuList.vue.d.ts +2 -0
  96. package/dist/internal/overlays/OverlayView.vue.d.ts +8 -4
  97. package/dist/locale/de.mjs +21 -3
  98. package/dist/locale/en.mjs +21 -3
  99. package/dist/locale/it.mjs +21 -3
  100. package/dist/locale/types.d.ts +18 -0
  101. package/dist/nuxt/runtime/layouts/dashboard.vue +1 -0
  102. package/dist/styles/base.css +2 -1
  103. package/dist/styles/core/button.css +42 -16
  104. package/dist/styles/core/form-field.css +7 -1
  105. package/dist/styles/core/input.css +31 -2
  106. package/dist/styles/core/kbd.css +1 -1
  107. package/dist/styles/core/textarea.css +10 -0
  108. package/dist/styles/data/avatar.css +1 -1
  109. package/dist/styles/data/badge.css +39 -23
  110. package/dist/styles/data/description-list.css +2 -2
  111. package/dist/styles/data/filter-pills.css +24 -3
  112. package/dist/styles/data/pagination.css +26 -3
  113. package/dist/styles/data/selection-bar.css +1 -1
  114. package/dist/styles/data/stat.css +4 -4
  115. package/dist/styles/data/table.css +4 -4
  116. package/dist/styles/data/tag.css +1 -1
  117. package/dist/styles/data/timeline.css +1 -1
  118. package/dist/styles/data/tree.css +2 -2
  119. package/dist/styles/feedback/empty-state.css +1 -1
  120. package/dist/styles/feedback/loading-host.css +1 -1
  121. package/dist/styles/feedback/loading-overlay.css +1 -1
  122. package/dist/styles/feedback/meter.css +1 -1
  123. package/dist/styles/feedback/progress.css +2 -2
  124. package/dist/styles/forced-colors.css +6 -1
  125. package/dist/styles/forms/calendar.css +30 -3
  126. package/dist/styles/forms/checkbox.css +1 -1
  127. package/dist/styles/forms/choice.css +7 -1
  128. package/dist/styles/forms/combobox.css +2 -2
  129. package/dist/styles/forms/file-input.css +1 -1
  130. package/dist/styles/forms/password-input.css +1 -1
  131. package/dist/styles/forms/pin-input.css +1 -1
  132. package/dist/styles/forms/radio.css +1 -1
  133. package/dist/styles/forms/segmented.css +2 -2
  134. package/dist/styles/forms/select.css +2 -2
  135. package/dist/styles/forms/slider.css +1 -1
  136. package/dist/styles/forms/switch.css +4 -4
  137. package/dist/styles/layout/accordion.css +2 -2
  138. package/dist/styles/layout/app-shell.css +18 -6
  139. package/dist/styles/layout/breadcrumbs.css +3 -3
  140. package/dist/styles/layout/card.css +1 -1
  141. package/dist/styles/layout/nav-link.css +1 -1
  142. package/dist/styles/layout/page-header.css +21 -1
  143. package/dist/styles/layout/separator.css +21 -1
  144. package/dist/styles/layout/sidebar.css +1 -1
  145. package/dist/styles/layout/tabs.css +30 -1
  146. package/dist/styles/layout/theme-switch.css +1 -1
  147. package/dist/styles/overlays/command.css +3 -3
  148. package/dist/styles/overlays/drawer.css +4 -3
  149. package/dist/styles/overlays/floating.css +2 -2
  150. package/dist/styles/overlays/menu.css +5 -1
  151. package/dist/styles/overlays/modal.css +12 -9
  152. package/dist/styles/overlays/toast.css +3 -3
  153. package/dist/styles/overlays/tooltip.css +26 -8
  154. package/dist/styles/shared.css +2 -2
  155. package/dist/styles/templates/account-menu.css +1 -1
  156. package/dist/styles/templates/auth-layout.css +4 -4
  157. package/dist/styles/templates/brand.css +2 -2
  158. package/dist/styles/templates/dashboard-page.css +1 -1
  159. package/dist/styles/templates/empty-page.css +3 -3
  160. package/dist/styles/templates/error-page.css +1 -1
  161. package/dist/styles/templates/save-bar.css +1 -1
  162. package/dist/styles/templates/settings-modal.css +6 -6
  163. package/dist/styles/utilities.css +3 -3
  164. package/package.json +1 -1
package/README.md CHANGED
@@ -108,7 +108,22 @@ Tokens live on `.k-root`, never on `:root`. Override them on a root element: Roo
108
108
 
109
109
  Input tokens, declared only on the outermost root so nested roots inherit them: `--k-accent`, `--k-accent-contrast`, `--k-font-sans`, `--k-font-mono`, `--k-font-size`, `--k-font-features`, `--k-line-height`, `--k-radius-scale`. Every other token (surfaces, text, lines, accent shades, status colors, radii, shadows, motion) is derived per root, so a nested root with its own `theme` or `accent` re-themes a region. The full list is in [CONVENTIONS.md](./CONVENTIONS.md#tokens).
110
110
 
111
- A few component tokens are unset by default and only exist to be overridden on a root: `--k-scrim-blur` (backdrop blur of Modal and Drawer, `3px`), `--k-badge-radius` (Badge corners, `--k-radius-full`), `--k-grid-header-background`, `--k-grid-header-text` and `--k-grid-header-font` (DataGrid header, `--k-subtle`, `--k-text-muted` and `--k-font-sans`).
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
+
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-header-font-size` (12px), `--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).
112
127
 
113
128
  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.
114
129
 
@@ -218,7 +233,7 @@ k: { locale: 'it', messages: { it: { common: { cancel: 'Indietro' } } } }
218
233
 
219
234
  ## Forms
220
235
 
221
- `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):
236
+ `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):
222
237
 
223
238
  ```ts
224
239
  const form = useForm({
@@ -232,6 +247,12 @@ Issues are keyed by their first path segment and by their full dotted path (`blo
232
247
 
233
248
  Built-in messages follow the active language. `messages` overrides them for one form.
234
249
 
250
+ `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.
251
+
252
+ 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.
253
+
254
+ 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.
255
+
235
256
  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:
236
257
 
237
258
  ```ts
@@ -287,7 +308,7 @@ Several controls in one `FormField`. Only the primary control takes the field id
287
308
  | layout | Accordion, AppShell, Breadcrumbs, Card, CopyButton, Grid, NavLink, PageHeader, Separator, Sidebar, SidebarNav, SidebarSection, Stack, Tabs, ThemeSwitch, Toolbar |
288
309
  | templates | DashboardLayout, AuthLayout, DashboardPage, ListPage, DetailPage, FormPage, SettingsPage, SettingsSection, SettingsModal, SettingsGroup, AppearanceSettings, SaveBar, ErrorPage, NotFoundPage, EmptyPage, Brand, AccountMenu |
289
310
  | feedback | Alert, EmptyState, LoadingOverlay, Meter, Progress, ProgressRing, Skeleton |
290
- | grid | DataGrid, GridToolbar, cell renderers (BadgeCell, BooleanCell, DateCell, ImageCell, LinkCell) |
311
+ | grid | DataGrid, GridToolbar, cell renderers (BadgeCell, BooleanCell, DateCell, EmptyCell, ImageCell, LinkCell) |
291
312
  | editor | RichTextEditor |
292
313
  | chart | ChartPanel, ChartLegend, LineChart, BarChart, DoughnutChart |
293
314
 
@@ -296,6 +317,23 @@ Composables: `useTheme`, `usePreset`, `useForm`, `useField`, `useFieldControl`,
296
317
  ### Lists, trees and uploads
297
318
 
298
319
  - `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.
320
+ - `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`.
321
+ - `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.
322
+ - `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.
323
+ - `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.
324
+ - `CheckboxGroup` takes `columns` (a number): the options are laid out in a grid with that many equal columns. Unset keeps the `orientation` layout.
325
+ - `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.
326
+ - `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.
327
+ - `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.
328
+ - `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`.
329
+ - `Separator` takes `description` (or `#description`) under its label, and an `#actions` slot rendered next to them (a small add or edit button).
330
+ - `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).
331
+ - `Tabs` items take `dot: true` for a small indicator without a number (`dotTone`, default `danger`; `dotLabel` is the hidden text, default "Needs attention").
332
+ - `DropdownMenu` takes `matchTriggerWidth` (the menu is at least as wide as its trigger) and `minWidth` (a CSS length).
333
+ - `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).
334
+ - `RichTextEditor` accepts `'underline'` in `toolbar` (not in the default toolbar).
335
+ - `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.
336
+ - `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`.
299
337
  - `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`.
300
338
  - `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.
301
339
  - `SegmentedControl` with `required` narrows `v-model` to a non null value. Options take `title` (tooltip and description) and `muted`, and the `#option` slot replaces the content of each option.
@@ -412,6 +450,7 @@ Ready defaults to start a dashboard project: an app frame, an auth frame and pag
412
450
  | `shortcut` | `'mod+b'` | Toggles the rail on desktop and the drawer on mobile. `searchShortcut` defaults to `mod+k`. |
413
451
  | `v-model:open` | `false` | Mobile drawer (below 768px of shell width): scrim, focus moves into the drawer, the content is `inert`, Escape and the scrim close it and focus returns to the burger. |
414
452
  | `fluid`, `viewTransition` | `false`, `true` | Full width content; `view-transition-name` on sidebar and panel so page transitions keep the frame still. |
453
+ | `fill` | `false` | Full height, full bleed content: no padding, gap or max width, and the content fills the panel (a page that is one DataGrid with `height="100%"`). Also on `AppShell`; in the `k-dashboard` layout per page with `definePageMeta({ fill: true })` or for every page with `dashboard.fill`. |
415
454
 
416
455
  Slots: `brand`, `search`, `sidebar` (replaces the navigation), `sidebar-footer`, `account`, `topbar-brand`, `topbar`, `actions`, default, plus `settings-section-<id>`, `settings-actions-<id>` and `settings-account-fields`, forwarded to the SettingsModal. The exposed `openSettings(id?)` opens it on a section. Landmarks: skip link, `aside` and `nav` with labels, `main` with an id, `aria-current="page"` on the active link. `SidebarNav` (layout group) renders the same navigation data inside your own `Sidebar`.
417
456
 
@@ -593,7 +632,16 @@ Accessibility related props and defaults:
593
632
  - Copy props that contain a value are templates: `removeLabel` (`'Remove {label}'`) on `Tag`, `Combobox` and `FileInput`, `moreLabel` (`'{count} more'`) on `AvatarGroup`, `pageLabel` (`'Page {page}'`) on `Pagination`. `ListPage` takes `resultsText: (count) => string` for the announced result count, `ColorInput` takes `invalidText`.
594
633
  - `ColorInput` `swatches` accept `{ value, label }` objects (a radio group named by `label` or the hex). `PasswordInput` picks `autocomplete="new-password"` with `generate` or `strength`, `current-password` otherwise.
595
634
  - `Tree` with `reorderable` shows small move buttons (up, down, indent, outdent) on the hovered or focused row, as a single pointer alternative to dragging. Set `patterns` on `LineChart` and `BarChart` to tell series apart without color (dash and point style per line, a fill pattern per bar series, mirrored in the legend); it is off by default.
596
- - `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.
635
+ - `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`.
636
+ - `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).
637
+ - `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.
638
+ - `DateCell` takes `relative: true` (or `Intl.RelativeTimeFormatOptions`) in `cellRendererParams` for "3 days ago"; the absolute date stays in the `title`.
639
+ - `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.
640
+ - `DataGrid` row ids: `getRowId: (params) => string` (wins), `rowIdKey` (a field of the row), or `gridOptions.getRowId`.
641
+ - `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`.
642
+ - `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.
643
+ - `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.
644
+ - `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.
597
645
  - `DataGrid` translates the AG Grid ARIA strings through the catalog (`grid.*`; your own `localeText` still wins) and announces row count changes.
598
646
 
599
647
  ## Browser support
@@ -35,40 +35,51 @@ const props = defineProps({
35
35
  horizontal: { type: Boolean, required: false },
36
36
  patterns: { type: Boolean, required: false },
37
37
  formatValue: { type: Function, required: false },
38
- options: { type: null, required: false }
38
+ options: { type: null, required: false },
39
+ colorByPoint: { type: Boolean, required: false },
40
+ colors: { type: Array, required: false }
39
41
  });
40
42
  const { colors, reducedMotion, ready, colorAt, isHidden, toggle } = useChartSurface(
41
43
  useTemplateRef("root"),
42
44
  {
43
- color: (index) => props.series[index]?.color,
44
- key: (index) => props.series[index]?.label
45
+ color: (index) => props.colorByPoint ? props.colors?.[index] : props.series[index]?.color,
46
+ key: (index) => props.colorByPoint ? props.labels[index] : props.series[index]?.label
45
47
  }
46
48
  );
47
49
  const legendItems = computed(
48
- () => props.series.map((entry, index) => ({
49
- label: entry.label,
50
- color: colorAt(index),
51
- hidden: isHidden(index),
52
- pattern: props.patterns ? seriesPattern(index) : void 0
53
- }))
50
+ () => (props.colorByPoint ? props.labels : props.series.map((entry) => entry.label)).map(
51
+ (label, index) => ({
52
+ label,
53
+ color: colorAt(index),
54
+ hidden: isHidden(index),
55
+ pattern: props.patterns ? seriesPattern(index) : void 0
56
+ })
57
+ )
58
+ );
59
+ const visibleIndexes = computed(
60
+ () => props.labels.map((_, index) => index).filter((index) => !props.colorByPoint || !isHidden(index))
54
61
  );
55
62
  function fillAt(index) {
56
63
  const color = colorAt(index);
57
64
  return props.patterns ? barFill(color, seriesPattern(index), colors.value.surface) : color;
58
65
  }
59
- const chartData = computed(() => ({
60
- labels: props.labels,
61
- datasets: props.series.map((entry, index) => ({
62
- label: entry.label,
63
- data: entry.data,
64
- hidden: isHidden(index),
65
- backgroundColor: fillAt(index),
66
- hoverBackgroundColor: fillAt(index),
67
- borderRadius: 4,
68
- borderSkipped: false,
69
- maxBarThickness: 36
70
- }))
71
- }));
66
+ const chartData = computed(() => {
67
+ const indexes = visibleIndexes.value;
68
+ const pointFills = props.colorByPoint ? indexes.map(fillAt) : void 0;
69
+ return {
70
+ labels: props.colorByPoint ? indexes.map((index) => props.labels[index] ?? "") : props.labels,
71
+ datasets: props.series.map((entry, index) => ({
72
+ label: entry.label,
73
+ data: props.colorByPoint ? indexes.map((point) => entry.data[point] ?? null) : entry.data,
74
+ hidden: props.colorByPoint ? false : isHidden(index),
75
+ backgroundColor: pointFills ?? fillAt(index),
76
+ hoverBackgroundColor: pointFills ?? fillAt(index),
77
+ borderRadius: 4,
78
+ borderSkipped: false,
79
+ maxBarThickness: 36
80
+ }))
81
+ };
82
+ });
72
83
  const chartOptions = computed(
73
84
  () => buildCartesianOptions(
74
85
  {
@@ -12,6 +12,8 @@ declare const __VLS_export: import("vue").DefineComponent<{
12
12
  patterns?: boolean;
13
13
  formatValue?: ChartValueFormatter;
14
14
  options?: ChartOptions<"bar">;
15
+ colorByPoint?: boolean;
16
+ colors?: string[];
15
17
  innerHTML?: string | undefined | undefined;
16
18
  class?: import("vue").ClassValue;
17
19
  style?: import("vue").StyleValue;
@@ -216,6 +218,8 @@ declare const __VLS_export: import("vue").DefineComponent<{
216
218
  patterns?: boolean;
217
219
  formatValue?: ChartValueFormatter;
218
220
  options?: ChartOptions<"bar">;
221
+ colorByPoint?: boolean;
222
+ colors?: string[];
219
223
  innerHTML?: string | undefined | undefined;
220
224
  class?: import("vue").ClassValue;
221
225
  style?: import("vue").StyleValue;
@@ -24,7 +24,7 @@
24
24
  .k-chart-panel-title {
25
25
  margin: 0;
26
26
  font-size: 0.9375rem;
27
- font-weight: 600;
27
+ font-weight: var(--k-font-weight-strong, 600);
28
28
  letter-spacing: -0.015em;
29
29
  color: var(--k-text);
30
30
  }
@@ -156,7 +156,7 @@
156
156
  }
157
157
 
158
158
  .k-chart-legend-item:active {
159
- scale: 0.97;
159
+ scale: var(--k-press-scale, 0.97);
160
160
  }
161
161
 
162
162
  .k-chart-legend-item.is-hidden {
@@ -23,14 +23,14 @@ const fallbackColors = {
23
23
  fontFamily: "ui-sans-serif, system-ui, sans-serif"
24
24
  };
25
25
  const seriesExpressions = [
26
- "var(--k-accent)",
27
- "var(--k-info)",
28
- "var(--k-success)",
29
- "var(--k-warning)",
30
- "var(--k-danger)",
31
- "color-mix(in oklab, var(--k-accent) 55%, var(--k-danger))",
32
- "color-mix(in oklab, var(--k-info) 55%, var(--k-success))",
33
- "color-mix(in oklab, var(--k-warning) 50%, var(--k-success))"
26
+ "var(--k-chart-1, var(--k-accent))",
27
+ "var(--k-chart-2, var(--k-info))",
28
+ "var(--k-chart-3, var(--k-success))",
29
+ "var(--k-chart-4, var(--k-warning))",
30
+ "var(--k-chart-5, var(--k-danger))",
31
+ "var(--k-chart-6, color-mix(in oklab, var(--k-accent) 55%, var(--k-danger)))",
32
+ "var(--k-chart-7, color-mix(in oklab, var(--k-info) 55%, var(--k-success)))",
33
+ "var(--k-chart-8, color-mix(in oklab, var(--k-warning) 50%, var(--k-success)))"
34
34
  ];
35
35
  let normalizeContext;
36
36
  function normalize(color) {
@@ -459,8 +459,8 @@ declare const __VLS_base: import("vue").DefineComponent<{
459
459
  popovertarget?: string | undefined;
460
460
  popovertargetaction?: import("../../index.ts").PopoverTargetAction | undefined;
461
461
  }> & Readonly<{}>, {
462
- tone: Tone;
463
462
  size: ButtonSize;
463
+ tone: Tone;
464
464
  type: "submit" | "button" | "reset";
465
465
  variant: ButtonVariant;
466
466
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -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,21 +480,21 @@ 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<{
489
493
  onClear?: (() => any) | undefined;
490
494
  "onUpdate:modelValue"?: ((value: string | number | null | undefined) => any) | undefined;
491
495
  }>, {
492
- invalid: boolean;
493
496
  size: FieldSize;
497
+ invalid: boolean;
494
498
  type: string;
495
499
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
496
500
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
@@ -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,19 +430,19 @@ 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
  }>, {
442
- invalid: boolean;
443
444
  size: FieldSize;
445
+ invalid: boolean;
444
446
  rows: number;
445
447
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
446
448
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
@@ -1,5 +1,5 @@
1
1
  export type ButtonSize = 'xs' | 'sm' | 'md' | 'lg';
2
2
  export type Tone = 'neutral' | 'accent' | 'info' | 'success' | 'warning' | 'danger';
3
- export type ButtonVariant = 'solid' | 'soft' | 'outline' | 'ghost' | 'link';
3
+ export type ButtonVariant = 'solid' | 'soft' | 'subtle' | 'outline' | 'ghost' | 'link';
4
4
  export type FieldSize = 'sm' | 'md' | 'lg';
5
5
  export type LinkTarget = string | Record<string, unknown>;