@xleddyl/443-components-vue 0.10.2 → 0.11.0

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 (53) hide show
  1. package/CATALOG.md +185 -175
  2. package/README.md +47 -21
  3. package/dist/components/data/Tag.vue +2 -0
  4. package/dist/components/data/Tag.vue.d.ts +2 -0
  5. package/dist/components/data/TagList.vue +239 -0
  6. package/dist/components/data/TagList.vue.d.ts +53 -0
  7. package/dist/components/data/index.d.ts +1 -0
  8. package/dist/components/data/index.mjs +1 -0
  9. package/dist/components/data/types.d.ts +15 -0
  10. package/dist/components/forms/Combobox.vue +33 -4
  11. package/dist/components/forms/Combobox.vue.d.ts +3 -1
  12. package/dist/components/forms/NumberInput.vue.d.ts +1 -1
  13. package/dist/components/forms/PinInput.vue.d.ts +2 -2
  14. package/dist/components/forms/types.d.ts +2 -0
  15. package/dist/components/layout/AppShell.vue +14 -2
  16. package/dist/components/layout/AppShell.vue.d.ts +3 -1
  17. package/dist/components/layout/Stepper.vue +148 -0
  18. package/dist/components/layout/Stepper.vue.d.ts +420 -0
  19. package/dist/components/layout/index.d.ts +1 -0
  20. package/dist/components/layout/index.mjs +1 -0
  21. package/dist/components/layout/types.d.ts +12 -0
  22. package/dist/components/overlays/Toaster.vue +10 -0
  23. package/dist/components/overlays/Toaster.vue.d.ts +1 -1
  24. package/dist/components/templates/DashboardLayout.vue +2 -0
  25. package/dist/components/templates/DashboardLayout.vue.d.ts +3 -1
  26. package/dist/components/templates/types.d.ts +2 -1
  27. package/dist/composables/data.d.ts +1 -0
  28. package/dist/composables/data.mjs +1 -0
  29. package/dist/composables/useSortable.d.ts +38 -0
  30. package/dist/composables/useSortable.mjs +215 -0
  31. package/dist/grid/cells/BadgeCell.vue +90 -18
  32. package/dist/grid/cells/BadgeCell.vue.d.ts +14 -14
  33. package/dist/grid/data-grid.css +24 -0
  34. package/dist/grid/spreadsheet.mjs +8 -4
  35. package/dist/grid/types.d.ts +2 -0
  36. package/dist/internal/data/sortable.d.ts +5 -0
  37. package/dist/internal/data/sortable.mjs +23 -0
  38. package/dist/internal/overlays/avoid.d.ts +26 -0
  39. package/dist/internal/overlays/avoid.mjs +134 -0
  40. package/dist/locale/de.mjs +20 -0
  41. package/dist/locale/en.mjs +20 -0
  42. package/dist/locale/it.mjs +20 -0
  43. package/dist/locale/types.d.ts +20 -0
  44. package/dist/nuxt/module.mjs +14 -0
  45. package/dist/nuxt/runtime/layouts/dashboard.vue +8 -1
  46. package/dist/styles/data/tag-list.css +93 -0
  47. package/dist/styles/data.css +1 -0
  48. package/dist/styles/forced-colors.css +10 -0
  49. package/dist/styles/layout/app-shell.css +23 -0
  50. package/dist/styles/layout/stepper.css +244 -0
  51. package/dist/styles/layout.css +1 -0
  52. package/dist/styles/overlays/toast.css +17 -12
  53. package/package.json +1 -1
package/README.md CHANGED
@@ -350,15 +350,15 @@ Several controls in one `FormField`. Only the primary control takes the field id
350
350
  | core | Root, Icon, Button, Spinner, Input, Textarea, FormField, FieldMessage, FieldBoundary, Form, FormAlerts, Kbd, VisuallyHidden |
351
351
  | forms | Calendar, Checkbox, CheckboxGroup, ColorInput, Combobox, DateInput, DatePicker, FileInput, InputAddon, InputGroup, NumberInput, PasswordInput, PinInput, Radio, RadioGroup, SegmentedControl, Select, Slider, Switch, TimeInput |
352
352
  | overlays | CommandPalette, ConfirmDialog, ConfirmHost, ContextMenu, Drawer, DropdownMenu, HoverCard, Modal, OverlayHost, Popover, Toaster, Tooltip |
353
- | data | Avatar, AvatarGroup, Badge, DescriptionList, FilterPill, FilterPills, Pagination, SelectionBar, Stat, Table, Tag, Timeline, Tree |
354
- | layout | Accordion, AppShell, Breadcrumbs, Card, CopyButton, Grid, NavLink, PageHeader, Separator, Sidebar, SidebarNav, SidebarSection, Stack, Tabs, ThemeSwitch, Toolbar |
353
+ | data | Avatar, AvatarGroup, Badge, DescriptionList, FilterPill, FilterPills, Pagination, SelectionBar, Stat, Table, Tag, TagList, Timeline, Tree |
354
+ | layout | Accordion, AppShell, Breadcrumbs, Card, CopyButton, Grid, NavLink, PageHeader, Separator, Sidebar, SidebarNav, SidebarSection, Stack, Stepper, Tabs, ThemeSwitch, Toolbar |
355
355
  | templates | DashboardLayout, AuthLayout, DashboardPage, ListPage, DetailPage, FormPage, SettingsPage, SettingsSection, SettingsModal, SettingsGroup, AppearanceSettings, SaveBar, ErrorPage, NotFoundPage, EmptyPage, Brand, AccountMenu |
356
356
  | feedback | Alert, EmptyState, LoadingOverlay, Meter, Progress, ProgressRing, Skeleton |
357
357
  | grid | DataGrid, GridToolbar, cell renderers (BadgeCell, BooleanCell, DateCell, EmptyCell, ImageCell, LinkCell) |
358
358
  | editor | RichTextEditor |
359
359
  | chart | ChartPanel, ChartLegend, LineChart, BarChart, DoughnutChart |
360
360
 
361
- Composables: `useTheme`, `usePreset`, `useForm`, `useField`, `useFieldControl`, `useToast` (and `toast`), `useConfirm`, `useAlert`, `usePrompt`, `useModal`, `useDrawer`, `useOverlay`, `useModalContext`, `useLoading` (and `withLoading`), `useLoadingState`, `useHotkey`, `useCopy`, `useFileDrop` (with `readDroppedFiles`), `usePasswordStrength`, `useTableSort`, `useAppShell`, `useScrollLock`, `useDirtyGuard`, `useIcons` helpers and `useLinkComponent`, `provideFieldBoundary`, `provideAuxiliaryControls`, `provideForm`, `useLocale`. With the Nuxt module also `useDashboard()` (runtime account, breadcrumbs and badges for the `k-dashboard` layout).
361
+ Composables: `useTheme`, `usePreset`, `useForm`, `useField`, `useFieldControl`, `useToast` (and `toast`), `useConfirm`, `useAlert`, `usePrompt`, `useModal`, `useDrawer`, `useOverlay`, `useModalContext`, `useLoading` (and `withLoading`), `useLoadingState`, `useHotkey`, `useCopy`, `useFileDrop` (with `readDroppedFiles`), `useSortable`, `usePasswordStrength`, `useTableSort`, `useAppShell`, `useScrollLock`, `useDirtyGuard`, `useIcons` helpers and `useLinkComponent`, `provideFieldBoundary`, `provideAuxiliaryControls`, `provideForm`, `useLocale`. With the Nuxt module also `useDashboard()` (runtime account, breadcrumbs and badges for the `k-dashboard` layout).
362
362
 
363
363
  ### Lists, trees and uploads
364
364
 
@@ -368,12 +368,19 @@ Composables: `useTheme`, `usePreset`, `useForm`, `useField`, `useFieldControl`,
368
368
  - `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.
369
369
  - `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.
370
370
  - `CheckboxGroup` takes `columns` (a number): the options are laid out in a grid with that many equal columns. Unset keeps the `orientation` layout. `variant="filled"` (`CheckboxGroupVariant`, `'default' | 'filled'`, default `'default'`) puts the options in a subtle filled panel, styled by `--k-checkbox-group-background` (`--k-subtle`), `--k-checkbox-group-padding` (0.75rem), `--k-checkbox-group-radius` (`--k-radius-md`) and `--k-checkbox-group-border` (none).
371
+ - `Combobox` with `creatable` takes `createTransform` (`ComboboxCreateTransform`, `(input) => string | null | undefined`) and `createValidate` (`ComboboxCreateValidate`, `(input) => string | true | undefined`). The transform runs on the typed text first: the "Create" option shows its result, and an empty result (or `null`) shows no create option. The validator gets the transformed text when the user picks the create option: a returned string is the error, shown inline through the surrounding `FormField` (`useFieldControl().setControlError`, or a `FieldMessage` under the control and an announcement without a field) with `aria-invalid` on the input, and nothing is created; `true` or nothing accepts it. The error clears when the query changes. Both work with `multiple`, where created values become tags: `<KCombobox v-model="tags" multiple creatable :create-transform="(input) => input.toLowerCase()" :create-validate="(input) => input.length >= 3 || 'Too short'" />`.
371
372
  - `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.
372
373
  - `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.
373
374
  - `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.
374
375
  - `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, outOfRange, inMonth }`) replaces the cell content; it is decorative, so announced text still comes from `dayText`. Days before `min` or after `max` get the `is-out-of-range` class (and `is-unavailable`, like days rejected by `isDateDisabled`), so the `--k-calendar-out-of-range-*` tokens style them apart from disabled days.
375
376
  - `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. `align` (`SeparatorAlign`, `'start' | 'center'`, default `'start'`) centers the heading title and description. `--k-separator-heading-font-size` (0.9375rem) sizes the heading, `--k-separator-heading-color` (default `--k-text`) colors it, and `--k-separator-label-font-size` (0.75rem) sizes the small label of `variant="label"`, with or without lines.
376
377
  - `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).
378
+ - `Stepper` shows the steps of a wizard: `steps` (`StepperStep[]`: `value`, `label`, `description`, `icon`, `disabled`, `state` `'complete' | 'error'`) and `v-model` for the current value. It renders a `nav` (named by `label`, default "Progress") around an ordered list; the current step has `aria-current="step"`, every step carries a hidden "Step 2 of 4" (`stepper.position`, `positionLabel` overrides it) and its state ("completed", "has errors"). Clickable steps are buttons with one tab stop and arrow keys, Home and End between them. `linear` makes only completed steps (`state: 'complete'` or before the current one) and the current step clickable; `readonly` renders no buttons. `orientation="vertical"` stacks the steps; `size="sm"` is denser. `compact` is `'auto'` by default: below 36rem of container width the labels are visually hidden (still read) and a "Step 2 of 4 Columns" line shows the current step; `true` forces it, `false` turns it off. `select(step)` fires on a click before the model changes.
379
+
380
+ ```vue
381
+ <KStepper v-model="step" :steps="steps" linear label="Import" />
382
+ ```
383
+
377
384
  - `Tabs` items take `dot: true` for a small indicator without a number (`dotTone`, default `danger`; `dotLabel` is the hidden text, default "Needs attention").
378
385
  - `DropdownMenu` takes `matchTriggerWidth` (the menu is at least as wide as its trigger) and `minWidth` (a CSS length).
379
386
  - `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).
@@ -384,6 +391,23 @@ Composables: `useTheme`, `usePreset`, `useForm`, `useField`, `useFieldControl`,
384
391
  - `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.
385
392
  - `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.
386
393
  - `FileInput` takes `directory` (folder picker; dropped folders are walked, every file keeps its relative path) or `directory="drop"` (the click opens a file picker, dropped folders are still walked), `directoryAction` (an "Upload a folder" button under the dropzone, `directoryActionLabel` renames it) or the `#actions` slot (`{ browse, browseDirectory, disabled }`), `skipHidden` (ignores dot files and dot folders), `list: false` (no file list: picks and drops only emit `add`), `loading` and the `#hint` slot. `add` receives `(files, { entries: [{ file, path }], directories, source, event, dataTransfer })`; `directories` lists every folder path of a folder pick or drop, empty folders included, and `add` also fires for a drop of empty folders. `invalid` fires per file and `reject` once with every rejected file (`(events, detail)`). `acceptMode="hint"` only passes `accept` to the picker and leaves filtering to the host. The component exposes `browse()`, `browseDirectory()`, `addFiles()` and `focus()`.
394
+ - `TagList` renders `v-model` (`TagListItem[]`: `value`, `label`, `tone`, `icon`, `removable`, `disabled`) as `Tag` chips in a labelled list (`label`, default "Items"). `removable` (or `removable` per item) adds the remove button; the chip is dropped from the model, `remove(item, index)` fires, focus moves to the neighbor and "Label removed" is announced. `sortable` reorders by pointer drag (the list updates live while dragging, Escape cancels), and the chips become one tab stop: the arrow keys, Home and End move between chips, Alt with an arrow key (or Alt with Home and End) moves the focused chip, Delete removes it, and every move is announced ("Name moved to position 2 of 4", `sortable.moved`). `moveButtons` adds "Move earlier" and "Move later" buttons to every chip, a single pointer alternative to dragging. A disabled item cannot move or be removed. `move({ item, from, to })` fires after each move. `accept` (a MIME type, a list of them, or `(event) => boolean`) makes the list a drop target for native drags from outside: an insertion marker follows the pointer and `itemDrop({ index, dataTransfer, event })` (`@item-drop`, named apart from the native `drop` event) gives the insertion index, so the host inserts the item (`dropEffect`, default `copy`, must match the `effectAllowed` of the source). The `#item` slot (`{ item, index }`) replaces the chip label.
395
+
396
+ ```vue
397
+ <KTagList
398
+ v-model="columns"
399
+ label="Export columns"
400
+ sortable
401
+ removable
402
+ accept="application/x-column"
403
+ @item-drop="
404
+ ({ index, dataTransfer }) => insert(dataTransfer?.getData('application/x-column'), index)
405
+ "
406
+ />
407
+ ```
408
+
409
+ - `useSortable(container, options)` is the reorder logic of `TagList` for any list. Options: `items` (a ref or getter), `onMove(items, move)` (`SortableMove`: `{ item, from, to, items, source }`, write `items` back to your state), `orientation` (`'vertical'` by default, `'horizontal'` for wrapping rows), `disabled`, `handle` (a selector: only a pointer down on it starts a drag; without it any pointer down outside buttons, links and inputs does), `canMove(item, index)`, `label(item)` and `movedText(move)` for the announcement. Bind `itemProps(index)` on each item element (the attribute marks it, plus pointer and Alt with arrow key handlers) and render `announcement` in a polite live region. `move(from, to, { announce })` moves from your own buttons, `dragging` is the index being dragged (for a lifted style), `canMove(index)` reports locked items. Pointer drags reorder live and follow the pointer, Escape or a cancelled pointer puts the item back, and the click after a drag is swallowed.
410
+
387
411
  - `useFileDrop` returns `{ over, reset }` and takes `ignore(event)`. A drop handled by a nested drop zone is skipped by the outer ones, and the state resets after any drop, even when a child stops propagation. `readDroppedFiles(dataTransfer)` walks dropped folders (call it synchronously in the drop handler) and resolves to `[{ file, path }]`; `readDroppedFiles(dataTransfer, { directories: true })` resolves to `{ files, directories }` with every folder path, empty ones included, and `skipHidden: true` leaves out dot entries.
388
412
  - `Tabs` items take `badgeTone` and `badgeLabel` (the accessible name of the badge); the `#panel="{ item }"` slot renders a single panel for the active tab. `stretch` makes the tabs fill the remaining height of a flex column and the active panel grow with it (`flex: 1; min-height: 0`), and `inset` spaces the tab list from the container edges with the panel flush below it, for inspectors and side panes. `Breadcrumbs` items take `attrs` and `onClick` (an item without a link renders a button, handy for drop targets), `#item` customizes the content, and `ancestors` links every item and ends with a separator (`breadcrumbsAncestors` on `PageHeader`). `Accordion` takes `variant` `card` (default), `outline` (the same card without a shadow), `separated` or `plain`; items take `description`, and `contentVariant="neutral"` drops the content padding and text styling.
389
413
  - `RichTextEditor` labels heading buttons by their real level (`headingLabel="relative"` numbers them from 1) and `toolbar` picks and orders the buttons: `['headings', '|', 'bold', 'italic', 'link']`.
@@ -428,6 +452,7 @@ const { loading: saving, run } = useLoadingState()
428
452
  await run(() => api.save(form))
429
453
  ```
430
454
 
455
+ - Toasts stay clear of an open Drawer: next to a left or right drawer the stack moves beside the panel (by its width) when there is room for it, otherwise it moves above the drawer footer (or below the header for top positions), so the drawer actions stay visible and the toasts stay clickable. The same footer rule applies to a Modal whose footer sits under the toasts (a sheet or a fullscreen modal).
431
456
  - Toasts stack behind the newest one (up to `max`, default 4) and expand on hover or focus, which also pauses their timers. Each tone has its own icon; `description` takes a string or a list of lines. `dismissible: false` hides the close button, `duration: Infinity` keeps a toast open, `progress` on the toaster shows a timer bar.
432
457
  - `useConfirm()` resolves `true` or `false`, `useAlert()` resolves when closed, `usePrompt()` resolves the string or `null`. `useConfirm()` and `useAlert()` also take `body`, a component rendered under the description (for example a list of the pages that use a file). The body can emit `close` to dismiss the dialog as cancelled, for example when one of its links is clicked. Prompt options: `label`, `placeholder`, `value`, `type`, `required`, `requiredText`, `validate(value)` (return an error message to keep the dialog open), `onConfirm(value)`. Without a host they fall back to the native browser dialogs.
433
458
  - `modal.open(Component, props, options)` returns a promise for the result plus `id`, `close(result)` and `update(props)`. Options: `title`, `description`, `size`, `side` (drawers), `sheet`, `flush`, `dismissible`, `hideClose`, `closeLabel`, `as: 'modal' | 'drawer'`. Inside the component, `useModalContext<R>()` gives `close(result?)` and `setLoading(boolean)`. A component can open another modal on top; Escape and the backdrop resolve `undefined`. `modal.close(id?, result?)` and `modal.closeAll()` close from outside.
@@ -480,23 +505,24 @@ Ready defaults to start a dashboard project: an app frame, an auth frame and pag
480
505
  </KDashboardLayout>
481
506
  ```
482
507
 
483
- | Prop | Default | Notes |
484
- | ---------------------------------------------------------------------------------- | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
485
- | `variant` | `'inset'` | `inset` (canvas sidebar, inset content panel), `sidebar` (bordered sidebar, full bleed content), `floating` (raised sidebar card on the canvas). |
486
- | `navigation` | | `NavItem[]` or `NavSection[]` (`{ label?, items }`). Items: `label`, `to`/`href`, `icon`, `badge`, `exact`, `active`, `external`, `disabled`, `children`. |
487
- | `currentPath` | router path | Active state is computed from the router (`$route`) with prefix matching (`/` and `exact` items match exactly). |
488
- | `brandName`, `brandSubtitle`, `brandIcon`, `brandLogo`, `brandShowName`, `brandTo` | `brandTo: '/'` | `brandLogo` takes an image URL, inline SVG markup or a component; the name is hidden next to a logo unless `brandShowName`. Or the `#brand` slot. |
489
- | `account` | | `{ name, email?, avatar?, role?, menu? }`. Shown as a row (avatar, name, email, chevron) at the bottom of the sidebar. |
490
- | `accountAction` | `'settings'` | `settings`: the row opens a `SettingsModal` (`settingsSections`, `settingsPresets`, `v-model:settings-open`, `v-model:settings-section`, `@sign-out`). `menu`: a DropdownMenu with `account.menu`, selections emit `account-select`. Or replace it with the `#account` slot. |
491
- | `breadcrumbs` | | Rendered in the desktop topbar. `#topbar` replaces them, `#actions` adds buttons on the right. |
492
- | `search` | `false` | `true` adds a CommandPalette with every navigation item, an array adds your own command groups, an async function `(query, signal) => CommandGroup<T>[]` searches on your server (item `data` reaches `#search-item` typed) (matching navigation items come first unless `searchNavigation` is `false`). `#search` replaces it. |
493
- | `searchProps` | | Options for the built-in CommandPalette: `minQueryLength`, `debounce`, `placeholder`, `label`, `emptyText` (a string or `(query) => string`), `minQueryText`, `errorText`, `loadingText`, `resultsText`, `notice`, `recentKey`, footer labels. Slots `#search-item`, `#search-empty`, `#search-group-label` and `#search-after-results` reach the palette. |
494
- | `themeSwitch` | `false` | `'sidebar'`, `'topbar'` or `false`. The default keeps the theme choice only in the Appearance section of the settings modal. With `accountAction="menu"` there is no settings modal, so set `'sidebar'` or `'topbar'`. |
495
- | `collapsible`, `v-model:collapsed`, `storageKey` | `true`, `'k-sidebar-collapsed'` | Icon rail on desktop. Persisted in a cookie under Nuxt (rendered on the server, no flash) or `localStorage` elsewhere. `false` disables persistence. |
496
- | `shortcut` | `'mod+b'` | Toggles the rail on desktop and the drawer on mobile. `searchShortcut` defaults to `mod+k`. |
497
- | `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. |
498
- | `fluid`, `viewTransition` | `false`, `true` | Full width content; `view-transition-name` on sidebar and panel so page transitions keep the frame still. |
499
- | `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`. |
508
+ | Prop | Default | Notes |
509
+ | ---------------------------------------------------------------------------------- | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
510
+ | `variant` | `'inset'` | `inset` (canvas sidebar, inset content panel), `sidebar` (bordered sidebar, full bleed content), `floating` (raised sidebar card on the canvas). |
511
+ | `navigation` | | `NavItem[]` or `NavSection[]` (`{ label?, items }`). Items: `label`, `to`/`href`, `icon`, `badge`, `exact`, `active`, `external`, `disabled`, `children`. |
512
+ | `currentPath` | router path | Active state is computed from the router (`$route`) with prefix matching (`/` and `exact` items match exactly). |
513
+ | `brandName`, `brandSubtitle`, `brandIcon`, `brandLogo`, `brandShowName`, `brandTo` | `brandTo: '/'` | `brandLogo` takes an image URL, inline SVG markup or a component; the name is hidden next to a logo unless `brandShowName`. Or the `#brand` slot. |
514
+ | `account` | | `{ name, email?, avatar?, role?, menu? }`. Shown as a row (avatar, name, email, chevron) at the bottom of the sidebar. |
515
+ | `accountAction` | `'settings'` | `settings`: the row opens a `SettingsModal` (`settingsSections`, `settingsPresets`, `v-model:settings-open`, `v-model:settings-section`, `@sign-out`). `menu`: a DropdownMenu with `account.menu`, selections emit `account-select`. Or replace it with the `#account` slot. |
516
+ | `breadcrumbs` | | Rendered in the desktop topbar. `#topbar` replaces them, `#actions` adds buttons on the right. |
517
+ | `search` | `false` | `true` adds a CommandPalette with every navigation item, an array adds your own command groups, an async function `(query, signal) => CommandGroup<T>[]` searches on your server (item `data` reaches `#search-item` typed) (matching navigation items come first unless `searchNavigation` is `false`). `#search` replaces it. |
518
+ | `searchProps` | | Options for the built-in CommandPalette: `minQueryLength`, `debounce`, `placeholder`, `label`, `emptyText` (a string or `(query) => string`), `minQueryText`, `errorText`, `loadingText`, `resultsText`, `notice`, `recentKey`, footer labels. Slots `#search-item`, `#search-empty`, `#search-group-label` and `#search-after-results` reach the palette. |
519
+ | `themeSwitch` | `false` | `'sidebar'`, `'topbar'` or `false`. The default keeps the theme choice only in the Appearance section of the settings modal. With `accountAction="menu"` there is no settings modal, so set `'sidebar'` or `'topbar'`. |
520
+ | `collapsible`, `v-model:collapsed`, `storageKey` | `true`, `'k-sidebar-collapsed'` | Icon rail on desktop. Persisted in a cookie under Nuxt (rendered on the server, no flash) or `localStorage` elsewhere. `false` disables persistence. |
521
+ | `shortcut` | `'mod+b'` | Toggles the rail on desktop and the drawer on mobile. `searchShortcut` defaults to `mod+k`. |
522
+ | `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. |
523
+ | `fluid`, `viewTransition` | `false`, `true` | Full width content; `view-transition-name` on sidebar and panel so page transitions keep the frame still. |
524
+ | `fill` | `false` | Full height, full bleed content (the same as `content="bleed"`): 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`. |
525
+ | `content` | `'page'` | Content layout (`ShellContent`). `page`: max width (`--k-shell-content`, 72rem), padding, the panel scrolls. `fill`: full width and full height with the normal padding and gap; the panel never scrolls and the content is a flex column with `min-height: 0`, so a child with `flex: 1` (a `DataGrid` with `fill`, `Tabs` with `stretch`, a `.k-page` root) takes the rest of the height. `centered`: the same full height, with the width kept at `--k-shell-content` and centered. `bleed`: full width and height without padding or gap. `fill` (boolean) is an alias of `bleed` and `fluid` removes the max width of `page`; `content` wins over both. Also on `AppShell`; in the `k-dashboard` layout per page with `definePageMeta({ content: 'fill' })` (typed) or for every page with `dashboard.content` in the app config. |
500
526
 
501
527
  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`.
502
528
 
@@ -689,7 +715,7 @@ Accessibility related props and defaults:
689
715
  - `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.
690
716
  - `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.
691
717
  - `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.
692
- - `BadgeCell` takes `variant` (`BadgeVariant`, `'soft' | 'solid' | 'outline'`) in `cellRendererParams`: set, the cell renders a `KBadge` with that variant and the resolved tone (`size`, `BadgeSize`, default `'sm'`); unset keeps the compact grid pill. `badgeClass` (`string | ((value, data) => string | undefined)`) adds host classes to the badge, to set the per instance badge tokens (`--k-badge-background`, `--k-badge-color`, `--k-badge-border-color`, which the grid pill also reads) for values outside `Tone`. `BadgeCellParams<T>` types the row of `data`.
718
+ - `BadgeCell` takes `variant` (`BadgeVariant`, `'soft' | 'solid' | 'outline'`) in `cellRendererParams`: set, the cell renders a `KBadge` with that variant and the resolved tone (`size`, `BadgeSize`, default `'sm'`); unset keeps the compact grid pill. `badgeClass` (`string | ((value, data) => string | undefined)`) adds host classes to the badge, to set the per instance badge tokens (`--k-badge-background`, `--k-badge-color`, `--k-badge-border-color`, which the grid pill also reads) for values outside `Tone`. `BadgeCellParams<T>` types the row of `data`. When the value is an array, the cell renders one badge per item (each item resolves its own `tones`, `labels` and `badgeClass`), on one line clipped to the cell, with the full list in the `title` attribute. `max` (a number) shows the first items and a `+N` badge for the rest (announced as "N more", `grid.moreValues`); `wrap: true` lets the badges wrap onto more lines (pair it with `autoHeight` on the column). Give an array column a `valueFormatter` (`(params) => params.value.join(', ')`): AG Grid infers an object type for arrays and uses the formatter for export, copy and the quick filter. Single values render as before.
693
719
  - `DataGrid` `spreadsheet` turns on Excel like editing on AG Grid Community (no Enterprise modules): it registers the editor modules and the AG Grid `ScrollApiModule` (`registerGridSpreadsheetModules()` from `/grid` does it by hand) and leaves the normal grid unchanged when it is off. Set `rowIdKey` or `getRowId`: changes, dirty cells and undo refer to row ids, never to indexes.
694
720
  - Selection: click selects a cell; drag, Shift+click and Shift+arrows extend a rectangular range (Shift+Cmd/Ctrl+arrow to the edge); Cmd/Ctrl+A selects every cell. The range follows the displayed row order (after sort, filter and quick search) and the displayed column order; the row selection checkbox column is not part of it. Escape collapses the range to the active cell.
695
721
  - Keyboard: arrows and Tab move the active cell, Enter, F2 or typing start the AG Grid editor, Escape cancels it. While an editor (or any input) has focus no shortcut fires.
@@ -19,6 +19,7 @@
19
19
  :aria-label="removeText"
20
20
  :title="removeText"
21
21
  :disabled="disabled"
22
+ :tabindex="removeTabindex"
22
23
  @click="onRemove"
23
24
  @keydown="onKeydown"
24
25
  >
@@ -40,6 +41,7 @@ const props = defineProps({
40
41
  icon: { type: null, required: false },
41
42
  removable: { type: Boolean, required: false },
42
43
  removeLabel: { type: String, required: false },
44
+ removeTabindex: { type: Number, required: false },
43
45
  disabled: { type: Boolean, required: false }
44
46
  });
45
47
  const { t, locale } = useLocale();
@@ -12,6 +12,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
12
12
  icon?: IconName;
13
13
  removable?: boolean;
14
14
  removeLabel?: string;
15
+ removeTabindex?: number;
15
16
  disabled?: boolean;
16
17
  key?: PropertyKey | undefined;
17
18
  prefix?: string | undefined | undefined;
@@ -214,6 +215,7 @@ declare const __VLS_base: import("vue").DefineComponent<{
214
215
  icon?: IconName;
215
216
  removable?: boolean;
216
217
  removeLabel?: string;
218
+ removeTabindex?: number;
217
219
  disabled?: boolean;
218
220
  key?: PropertyKey | undefined;
219
221
  prefix?: string | undefined | undefined;
@@ -0,0 +1,239 @@
1
+ <template>
2
+ <div
3
+ ref="root"
4
+ class="k-tag-list"
5
+ :class="{
6
+ 'is-sortable': canSort,
7
+ 'is-dragging': sorter.dragging.value !== null,
8
+ 'is-drop-target': dropIndex !== null,
9
+ 'is-empty': !items.length,
10
+ 'is-disabled': disabled
11
+ }"
12
+ @dragenter="onDragEnter"
13
+ @dragover="onDragOver"
14
+ @dragleave="onDragLeave"
15
+ @drop="onDrop"
16
+ >
17
+ <ul
18
+ v-if="items.length"
19
+ ref="list"
20
+ class="k-tag-list-items"
21
+ :aria-label="labelText"
22
+ :aria-describedby="hintText ? hintId : void 0"
23
+ >
24
+ <li
25
+ v-for="(item, index) in items"
26
+ :key="item.value"
27
+ class="k-tag-list-item"
28
+ :class="{
29
+ 'is-moving': sorter.dragging.value === index,
30
+ 'is-locked': canSort && !sorter.canMove(index)
31
+ }"
32
+ :data-drop="dropIndex === index ? 'before' : void 0"
33
+ :tabindex="canSort ? index === focusIndex ? 0 : -1 : void 0"
34
+ v-bind="canSort ? sorter.itemProps(index) : void 0"
35
+ @focus="focusIndex = index"
36
+ @keydown="onItemKeydown($event, index)"
37
+ >
38
+ <Tag
39
+ :label="item.label"
40
+ :tone="item.tone ?? tone"
41
+ :icon="item.icon"
42
+ :size="size"
43
+ :removable="isRemovable(item)"
44
+ :remove-label="removeLabel"
45
+ :remove-tabindex="canSort ? -1 : void 0"
46
+ :disabled="disabled || item.disabled"
47
+ @remove="remove(index)"
48
+ >
49
+ <slot name="item" :item="item" :index="index">{{ item.label }}</slot>
50
+ </Tag>
51
+ <span v-if="canSort && moveButtons" class="k-tag-list-move">
52
+ <Button
53
+ variant="ghost"
54
+ tone="neutral"
55
+ size="xs"
56
+ icon="chevron-left"
57
+ tabindex="-1"
58
+ :aria-label="t('tagList.moveEarlier', { label: item.label })"
59
+ :title="t('tagList.moveEarlier', { label: item.label })"
60
+ :disabled="index === 0 || !sorter.canMove(index)"
61
+ @click="moveBy(index, -1)"
62
+ />
63
+ <Button
64
+ variant="ghost"
65
+ tone="neutral"
66
+ size="xs"
67
+ icon="chevron-right"
68
+ tabindex="-1"
69
+ :aria-label="t('tagList.moveLater', { label: item.label })"
70
+ :title="t('tagList.moveLater', { label: item.label })"
71
+ :disabled="index === items.length - 1 || !sorter.canMove(index)"
72
+ @click="moveBy(index, 1)"
73
+ />
74
+ </span>
75
+ </li>
76
+ <li
77
+ v-if="dropIndex !== null && dropIndex >= items.length"
78
+ class="k-tag-list-end"
79
+ data-drop="before"
80
+ aria-hidden="true"
81
+ />
82
+ </ul>
83
+ <p v-else class="k-tag-list-empty">
84
+ <slot name="empty">{{ emptyLabel }}</slot>
85
+ </p>
86
+ <span v-if="hintText" :id="hintId" class="k-visually-hidden">{{ hintText }}</span>
87
+ <LiveRegion :message="liveText" />
88
+ </div>
89
+ </template>
90
+
91
+ <script setup>
92
+ import { computed, nextTick, ref, useId, useTemplateRef, watch } from "vue";
93
+ import { useLocale } from "../../composables/useLocale";
94
+ import { useSortable } from "../../composables/useSortable";
95
+ import LiveRegion from "../../internal/core/LiveRegion.vue";
96
+ import { useLiveMessage } from "../../internal/core/live";
97
+ import { focusStable } from "../../internal/core/focus";
98
+ import { rovingIndex } from "../../internal/core/roving";
99
+ import { insertionIndex, isRtl, sortableElements } from "../../internal/data/sortable";
100
+ import Button from "../core/Button.vue";
101
+ import Tag from "./Tag.vue";
102
+ const props = defineProps({
103
+ label: { type: String, required: false },
104
+ sortable: { type: Boolean, required: false },
105
+ removable: { type: Boolean, required: false },
106
+ moveButtons: { type: Boolean, required: false },
107
+ accept: { type: [String, Array, Function], required: false },
108
+ dropEffect: { type: String, required: false, default: "copy" },
109
+ tone: { type: String, required: false, default: "neutral" },
110
+ size: { type: String, required: false, default: "md" },
111
+ disabled: { type: Boolean, required: false },
112
+ emptyText: { type: String, required: false },
113
+ removeLabel: { type: String, required: false }
114
+ });
115
+ const model = defineModel({ type: Array, ...{ default: () => [] } });
116
+ const emit = defineEmits(["remove", "move", "itemDrop"]);
117
+ defineSlots();
118
+ const { t } = useLocale();
119
+ const hintId = `${useId()}-hint`;
120
+ const root = useTemplateRef("root");
121
+ const list = useTemplateRef("list");
122
+ const focusIndex = ref(0);
123
+ const dropIndex = ref(null);
124
+ const removal = useLiveMessage();
125
+ const items = computed(() => model.value);
126
+ const canSort = computed(() => props.sortable && !props.disabled);
127
+ const labelText = computed(() => props.label ?? t("tagList.label"));
128
+ const emptyLabel = computed(() => props.emptyText ?? t("tagList.empty"));
129
+ const hintText = computed(() => {
130
+ const parts = [];
131
+ if (canSort.value) parts.push(t("tagList.sortHint"));
132
+ if (canSort.value && items.value.some(isRemovable)) parts.push(t("tagList.removeHint"));
133
+ return parts.join(". ");
134
+ });
135
+ function isRemovable(item) {
136
+ return !props.disabled && !item.disabled && (item.removable ?? props.removable ?? false);
137
+ }
138
+ const sorter = useSortable(list, {
139
+ items,
140
+ orientation: "horizontal",
141
+ disabled: () => !canSort.value,
142
+ canMove: (item) => !item.disabled,
143
+ label: (item) => item.label,
144
+ onMove: (next, move) => {
145
+ model.value = next;
146
+ focusIndex.value = move.to;
147
+ emit("move", { item: move.item, from: move.from, to: move.to });
148
+ }
149
+ });
150
+ const liveText = computed(() => removal.message.value || sorter.announcement.value);
151
+ watch(
152
+ () => items.value.length,
153
+ (length) => {
154
+ if (focusIndex.value >= length) focusIndex.value = Math.max(0, length - 1);
155
+ }
156
+ );
157
+ function itemElement(index) {
158
+ return list.value?.querySelectorAll(":scope > .k-tag-list-item")[index] ?? null;
159
+ }
160
+ function moveBy(index, delta) {
161
+ sorter.move(index, index + delta, { announce: true, source: "api" });
162
+ }
163
+ function remove(index) {
164
+ const item = items.value[index];
165
+ if (!item || !isRemovable(item)) return;
166
+ const hadFocus = Boolean(root.value?.contains(document.activeElement));
167
+ const next = items.value.filter((_, position) => position !== index);
168
+ model.value = next;
169
+ emit("remove", item, index);
170
+ removal.announce(t("tagList.removed", { label: item.label }));
171
+ if (!hadFocus) return;
172
+ const target = Math.min(index, next.length - 1);
173
+ focusIndex.value = Math.max(0, target);
174
+ nextTick(() => {
175
+ const element = target >= 0 ? itemElement(target) : null;
176
+ const focusable = canSort.value ? element : element?.querySelector(".k-tag-remove:not(:disabled)");
177
+ if (focusable) focusable.focus({ preventScroll: true });
178
+ else if (root.value) focusStable(root.value);
179
+ });
180
+ }
181
+ function onItemKeydown(event, index) {
182
+ if (!canSort.value || event.target !== event.currentTarget) return;
183
+ if (event.altKey) return;
184
+ if (event.key === "Delete" || event.key === "Backspace") {
185
+ event.preventDefault();
186
+ remove(index);
187
+ return;
188
+ }
189
+ const rtl = isRtl(root.value);
190
+ const key = rtl && event.key === "ArrowLeft" ? "ArrowRight" : rtl && event.key === "ArrowRight" ? "ArrowLeft" : event.key;
191
+ const target = rovingIndex(key, index, items.value.length, "both");
192
+ if (target === null) return;
193
+ event.preventDefault();
194
+ focusIndex.value = target;
195
+ itemElement(target)?.focus();
196
+ }
197
+ function accepts(event) {
198
+ const accept = props.accept;
199
+ if (!accept || props.disabled) return false;
200
+ if (typeof accept === "function") return accept(event);
201
+ const types = Array.from(event.dataTransfer?.types ?? []);
202
+ return (Array.isArray(accept) ? accept : [accept]).some((type) => types.includes(type));
203
+ }
204
+ function dropTarget(event) {
205
+ if (!items.value.length) return 0;
206
+ const elements = sortableElements(list.value);
207
+ const targets = elements.length ? elements : Array.from(list.value?.querySelectorAll(":scope > .k-tag-list-item") ?? []);
208
+ return insertionIndex(targets, event.clientX, event.clientY, "horizontal", isRtl(root.value));
209
+ }
210
+ let dragDepth = 0;
211
+ function resetDrop() {
212
+ dragDepth = 0;
213
+ dropIndex.value = null;
214
+ }
215
+ function onDragEnter(event) {
216
+ if (!accepts(event)) return;
217
+ event.preventDefault();
218
+ dragDepth++;
219
+ }
220
+ function onDragOver(event) {
221
+ if (!accepts(event)) return;
222
+ event.preventDefault();
223
+ if (event.dataTransfer) event.dataTransfer.dropEffect = props.dropEffect;
224
+ const index = dropTarget(event);
225
+ if (dropIndex.value !== index) dropIndex.value = index;
226
+ }
227
+ function onDragLeave(event) {
228
+ if (!accepts(event)) return;
229
+ dragDepth = Math.max(0, dragDepth - 1);
230
+ if (!dragDepth) dropIndex.value = null;
231
+ }
232
+ function onDrop(event) {
233
+ if (!accepts(event)) return;
234
+ event.preventDefault();
235
+ const index = dropIndex.value ?? dropTarget(event);
236
+ resetDrop();
237
+ emit("itemDrop", { index, dataTransfer: event.dataTransfer, event });
238
+ }
239
+ </script>
@@ -0,0 +1,53 @@
1
+ import type { Tone } from '../core/types';
2
+ import type { BadgeSize, TagListAccept, TagListDrop, TagListItem } from './types';
3
+ declare const __VLS_export: <T extends TagListItem>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
4
+ props: import("vue").PublicProps & __VLS_PrettifyLocal<({
5
+ label?: string;
6
+ sortable?: boolean;
7
+ removable?: boolean;
8
+ moveButtons?: boolean;
9
+ accept?: TagListAccept;
10
+ dropEffect?: "copy" | "move" | "link";
11
+ tone?: Tone;
12
+ size?: BadgeSize;
13
+ disabled?: boolean;
14
+ emptyText?: string;
15
+ removeLabel?: string;
16
+ } & {
17
+ modelValue?: T[];
18
+ }) & {
19
+ onRemove?: ((item: T, index: number) => any) | undefined;
20
+ onMove?: ((move: {
21
+ item: T;
22
+ from: number;
23
+ to: number;
24
+ }) => any) | undefined;
25
+ "onUpdate:modelValue"?: ((value: T[]) => any) | undefined;
26
+ onItemDrop?: ((drop: TagListDrop) => any) | undefined;
27
+ } & Partial<import("vue").HTMLAttributes & import("vue").ReservedProps>> & (typeof globalThis extends {
28
+ __VLS_PROPS_FALLBACK: infer P;
29
+ } ? P : {});
30
+ expose: (exposed: {}) => void;
31
+ attrs: any;
32
+ slots: {
33
+ item?: (props: {
34
+ item: T;
35
+ index: number;
36
+ }) => unknown;
37
+ empty?: () => unknown;
38
+ };
39
+ emit: (((evt: "remove", item: T, index: number) => void) & ((evt: "move", move: {
40
+ item: T;
41
+ from: number;
42
+ to: number;
43
+ }) => void) & ((evt: "itemDrop", drop: TagListDrop) => void)) & ((event: "update:modelValue", value: T[]) => void);
44
+ }>) => import("vue").VNode & {
45
+ __ctx?: NonNullable<Awaited<typeof __VLS_setup>>;
46
+ };
47
+ declare const _default: typeof __VLS_export;
48
+ export default _default;
49
+ type __VLS_PrettifyLocal<T> = (T extends any ? {
50
+ [K in keyof T]: T[K];
51
+ } : {
52
+ [K in keyof T as K]: T[K];
53
+ }) & {};
@@ -9,6 +9,7 @@ export { default as SelectionBar } from './SelectionBar.vue';
9
9
  export { default as Stat } from './Stat.vue';
10
10
  export { default as Table } from './Table.vue';
11
11
  export { default as Tag } from './Tag.vue';
12
+ export { default as TagList } from './TagList.vue';
12
13
  export { default as Timeline } from './Timeline.vue';
13
14
  export { default as Tree } from './Tree.vue';
14
15
  export type * from './types';
@@ -9,6 +9,7 @@ export { default as SelectionBar } from "./SelectionBar.vue";
9
9
  export { default as Stat } from "./Stat.vue";
10
10
  export { default as Table } from "./Table.vue";
11
11
  export { default as Tag } from "./Tag.vue";
12
+ export { default as TagList } from "./TagList.vue";
12
13
  export { default as Timeline } from "./Timeline.vue";
13
14
  export { default as Tree } from "./Tree.vue";
14
15
  export { applyTreeMove } from "../../internal/data/tree.mjs";
@@ -1,4 +1,5 @@
1
1
  import type { Tone } from '../core/types';
2
+ import type { IconName } from '../../composables/useIcons';
2
3
  import type { MenuItems } from '../overlays/types';
3
4
  export type BadgeVariant = 'soft' | 'solid' | 'outline';
4
5
  export type BadgeSize = 'sm' | 'md';
@@ -94,3 +95,17 @@ export interface FilterPillOption<T extends FilterPillValue = FilterPillValue> {
94
95
  removable?: boolean;
95
96
  }
96
97
  export type SelectionBarPlacement = 'inline' | 'sticky' | 'floating';
98
+ export interface TagListItem {
99
+ value: string | number;
100
+ label: string;
101
+ tone?: Tone;
102
+ icon?: IconName;
103
+ removable?: boolean;
104
+ disabled?: boolean;
105
+ }
106
+ export interface TagListDrop {
107
+ index: number;
108
+ dataTransfer: DataTransfer | null;
109
+ event: DragEvent;
110
+ }
111
+ export type TagListAccept = string | string[] | ((event: DragEvent) => boolean);