@sewadah/selaras 0.7.1 → 0.9.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 (65) hide show
  1. package/README.md +1 -1
  2. package/dist/agent/catalog.json +1 -1
  3. package/dist/module.json +1 -1
  4. package/dist/runtime/components/Autocomplete.vue +6 -0
  5. package/dist/runtime/components/AvatarGroup.vue +3 -2
  6. package/dist/runtime/components/CodeGroup.vue +3 -15
  7. package/dist/runtime/components/ColorModeSwitch.vue +5 -4
  8. package/dist/runtime/components/ColorModeToggle.vue +5 -4
  9. package/dist/runtime/components/Input.d.vue.ts +7 -4
  10. package/dist/runtime/components/Input.vue +22 -5
  11. package/dist/runtime/components/Input.vue.d.ts +7 -4
  12. package/dist/runtime/components/Listbox.d.vue.ts +28 -0
  13. package/dist/runtime/components/Listbox.vue +53 -0
  14. package/dist/runtime/components/Listbox.vue.d.ts +28 -0
  15. package/dist/runtime/components/Select.vue +6 -0
  16. package/dist/runtime/components/Textarea.d.vue.ts +7 -4
  17. package/dist/runtime/components/Textarea.vue +24 -7
  18. package/dist/runtime/components/Textarea.vue.d.ts +7 -4
  19. package/dist/runtime/components/TransferList.d.vue.ts +47 -0
  20. package/dist/runtime/components/TransferList.vue +67 -0
  21. package/dist/runtime/components/TransferList.vue.d.ts +47 -0
  22. package/dist/runtime/internal/ComboboxSelectBase.d.vue.ts +7 -63
  23. package/dist/runtime/internal/ComboboxSelectBase.vue +18 -5
  24. package/dist/runtime/internal/ComboboxSelectBase.vue.d.ts +7 -63
  25. package/dist/runtime/internal/ListSelectionForm.d.vue.ts +15 -0
  26. package/dist/runtime/internal/ListSelectionForm.vue +33 -0
  27. package/dist/runtime/internal/ListSelectionForm.vue.d.ts +15 -0
  28. package/dist/runtime/internal/ListboxBase.d.vue.ts +49 -0
  29. package/dist/runtime/internal/ListboxBase.vue +177 -0
  30. package/dist/runtime/internal/ListboxBase.vue.d.ts +49 -0
  31. package/dist/runtime/internal/ListboxOption.d.vue.ts +31 -0
  32. package/dist/runtime/internal/ListboxOption.vue +37 -0
  33. package/dist/runtime/internal/ListboxOption.vue.d.ts +31 -0
  34. package/dist/runtime/internal/TransferListBase.d.vue.ts +63 -0
  35. package/dist/runtime/internal/TransferListBase.vue +206 -0
  36. package/dist/runtime/internal/TransferListBase.vue.d.ts +63 -0
  37. package/dist/runtime/internal/color-mode-control.d.ts +7 -0
  38. package/dist/runtime/internal/color-mode-control.js +12 -0
  39. package/dist/runtime/internal/list-selection.d.ts +15 -0
  40. package/dist/runtime/internal/list-selection.js +43 -0
  41. package/dist/runtime/theme/input.d.ts +6 -0
  42. package/dist/runtime/theme/input.js +2 -0
  43. package/dist/runtime/theme/listbox.d.ts +131 -0
  44. package/dist/runtime/theme/listbox.js +27 -0
  45. package/dist/runtime/theme/select.d.ts +6 -0
  46. package/dist/runtime/theme/select.js +3 -1
  47. package/dist/runtime/theme/textarea.d.ts +6 -0
  48. package/dist/runtime/theme/textarea.js +2 -0
  49. package/dist/runtime/theme/transfer-list.d.ts +74 -0
  50. package/dist/runtime/theme/transfer-list.js +21 -0
  51. package/dist/runtime/theme-config.d.ts +10 -0
  52. package/dist/runtime/types/index.d.ts +3 -0
  53. package/dist/runtime/utils/field-adornments.d.ts +14 -0
  54. package/dist/runtime/utils/field-adornments.js +36 -0
  55. package/dist/runtime/utils/listbox-contracts.d.ts +31 -0
  56. package/dist/runtime/utils/listbox-contracts.js +0 -0
  57. package/dist/runtime/utils/messages.d.ts +7 -0
  58. package/dist/runtime/utils/messages.js +7 -0
  59. package/dist/runtime/utils/select-contracts.d.ts +3 -0
  60. package/dist/runtime/utils/slot-children.d.ts +2 -0
  61. package/dist/runtime/utils/slot-children.js +15 -0
  62. package/dist/runtime/utils/transfer-list-contracts.d.ts +48 -0
  63. package/dist/runtime/utils/transfer-list-contracts.js +0 -0
  64. package/package.json +5 -4
  65. package/skills/selaras/SKILL.md +7 -7
@@ -0,0 +1,7 @@
1
+ import type { ComputedRef } from 'vue';
2
+ import { useColorMode } from '#imports';
3
+ export declare function useColorModeControl(): {
4
+ colorMode: ReturnType<typeof useColorMode>;
5
+ isDark: ComputedRef<boolean>;
6
+ isDisabled: ComputedRef<boolean>;
7
+ };
@@ -0,0 +1,12 @@
1
+ import { computed, onMounted, ref } from "vue";
2
+ import { useColorMode } from "#imports";
3
+ export function useColorModeControl() {
4
+ const colorMode = useColorMode();
5
+ const mounted = ref(false);
6
+ onMounted(() => mounted.value = true);
7
+ return {
8
+ colorMode,
9
+ isDark: computed(() => mounted.value && colorMode.value === "dark"),
10
+ isDisabled: computed(() => !mounted.value || colorMode.unknown || colorMode.forced)
11
+ };
12
+ }
@@ -0,0 +1,15 @@
1
+ import type { SelectModelValue } from '../utils/select-contracts.js';
2
+ export type ListSelection = SelectModelValue | SelectModelValue[] | undefined;
3
+ export declare function selectionForMode(value: ListSelection, multiple: boolean): ListSelection;
4
+ export declare function assertUniqueSelection(value: ListSelection): void;
5
+ export declare function useListSelection(props: {
6
+ modelValue?: ListSelection;
7
+ defaultValue?: ListSelection;
8
+ multiple?: boolean;
9
+ disabled?: boolean;
10
+ }, emit: (value: ListSelection) => void): {
11
+ selection: import("vue").ComputedRef<string | number | object | undefined>;
12
+ values: import("vue").ComputedRef<SelectModelValue[]>;
13
+ update: (value: ListSelection) => void;
14
+ reset: () => void;
15
+ };
@@ -0,0 +1,43 @@
1
+ import { computed, getCurrentInstance, ref, watch } from "vue";
2
+ export function selectionForMode(value, multiple) {
3
+ return multiple ? value === void 0 ? [] : Array.isArray(value) ? [...value] : [value] : Array.isArray(value) ? value[0] : value;
4
+ }
5
+ export function assertUniqueSelection(value) {
6
+ if (Array.isArray(value) && new Set(value).size !== value.length)
7
+ throw new TypeError("[selaras] List selection values must be unique.");
8
+ }
9
+ export function useListSelection(props, emit) {
10
+ const instance = getCurrentInstance();
11
+ const controlled = () => Object.hasOwn(instance.vnode.props ?? {}, "modelValue") || Object.hasOwn(instance.vnode.props ?? {}, "model-value");
12
+ const initial = Array.isArray(props.defaultValue) ? [...props.defaultValue] : props.defaultValue;
13
+ assertUniqueSelection(initial);
14
+ const local = ref(selectionForMode(initial, !!props.multiple));
15
+ const selection = computed(() => {
16
+ const value = controlled() ? props.modelValue : local.value;
17
+ if (controlled() && value !== void 0 && Array.isArray(value) !== !!props.multiple)
18
+ throw new TypeError("[selaras] List modelValue must be an array in multiple mode and a scalar in single mode.");
19
+ assertUniqueSelection(value);
20
+ return value;
21
+ });
22
+ void selection.value;
23
+ function update(value) {
24
+ if (props.disabled)
25
+ return;
26
+ assertUniqueSelection(value);
27
+ if (!controlled())
28
+ local.value = value;
29
+ emit(value);
30
+ }
31
+ watch(() => props.multiple, (multiple) => {
32
+ if (!controlled())
33
+ update(selectionForMode(local.value, !!multiple));
34
+ });
35
+ function reset() {
36
+ const value = selectionForMode(initial, !!props.multiple);
37
+ if (!controlled())
38
+ local.value = value;
39
+ emit(value);
40
+ }
41
+ const values = computed(() => selection.value === void 0 ? [] : Array.isArray(selection.value) ? selection.value : [selection.value]);
42
+ return { selection, values, update, reset };
43
+ }
@@ -42,6 +42,8 @@ export declare const inputTheme: import("tailwind-variants").TVReturnType<{
42
42
  }, {
43
43
  root: string;
44
44
  base: string;
45
+ leading: string;
46
+ trailing: string;
45
47
  leadingIcon: string;
46
48
  trailingIcon: string;
47
49
  clear: string;
@@ -89,6 +91,8 @@ export declare const inputTheme: import("tailwind-variants").TVReturnType<{
89
91
  }, {
90
92
  root: string;
91
93
  base: string;
94
+ leading: string;
95
+ trailing: string;
92
96
  leadingIcon: string;
93
97
  trailingIcon: string;
94
98
  clear: string;
@@ -136,6 +140,8 @@ export declare const inputTheme: import("tailwind-variants").TVReturnType<{
136
140
  }, {
137
141
  root: string;
138
142
  base: string;
143
+ leading: string;
144
+ trailing: string;
139
145
  leadingIcon: string;
140
146
  trailingIcon: string;
141
147
  clear: string;
@@ -10,6 +10,8 @@ export const inputTheme = tv({
10
10
  // hardcoded here - hover's own color stays neutral regardless of
11
11
  // `color`, matching every other component's own hover state.
12
12
  base: "w-full rounded-[var(--selaras-resolved-radius-md)] bg-[var(--selaras-resolved-surface-default)] text-[var(--selaras-resolved-text-default)] ring-1 ring-inset ring-[var(--selaras-resolved-border-default)] placeholder:text-[var(--selaras-resolved-text-muted)] outline-none transition-[color,background-color,box-shadow] not-focus:hover:ring-[var(--selaras-resolved-border-hover)] hover:bg-[var(--selaras-resolved-surface-elevated)] focus:ring-2 focus:ring-[var(--_selaras-color-focus)] disabled:opacity-50 disabled:pointer-events-none",
13
+ leading: "absolute top-1/2 -translate-y-1/2 start-0 m-0 flex min-w-0 items-center gap-1 border-0 p-0 ps-2.5 text-sm text-[var(--selaras-resolved-text-muted)] disabled:opacity-50",
14
+ trailing: "absolute top-1/2 -translate-y-1/2 end-0 m-0 flex min-w-0 items-center gap-1 border-0 p-0 pe-1 text-sm text-[var(--selaras-resolved-text-muted)] disabled:opacity-50",
13
15
  leadingIcon: "absolute start-2.5 shrink-0 text-[var(--selaras-resolved-text-muted)]",
14
16
  trailingIcon: "absolute end-2.5 shrink-0 text-[var(--selaras-resolved-text-muted)]",
15
17
  // A separate slot from trailingIcon (not reused) - that one is a
@@ -0,0 +1,131 @@
1
+ export declare const listboxTheme: import("tailwind-variants").TVReturnType<{
2
+ size: {
3
+ sm: {
4
+ item: string;
5
+ };
6
+ md: {
7
+ item: string;
8
+ };
9
+ lg: {
10
+ item: string;
11
+ };
12
+ };
13
+ color: {
14
+ primary: "";
15
+ secondary: "";
16
+ neutral: "";
17
+ success: "";
18
+ danger: "";
19
+ info: "";
20
+ warning: "";
21
+ };
22
+ invalid: {
23
+ true: {
24
+ root: string;
25
+ };
26
+ };
27
+ }, {
28
+ root: string;
29
+ header: string;
30
+ footer: string;
31
+ searchWrapper: string;
32
+ searchInput: string;
33
+ content: string;
34
+ group: string;
35
+ groupLabel: string;
36
+ item: string;
37
+ label: string;
38
+ description: string;
39
+ itemContent: string;
40
+ icon: string;
41
+ indicator: string;
42
+ checkbox: string;
43
+ empty: string;
44
+ }, undefined, {
45
+ size: {
46
+ sm: {
47
+ item: string;
48
+ };
49
+ md: {
50
+ item: string;
51
+ };
52
+ lg: {
53
+ item: string;
54
+ };
55
+ };
56
+ color: {
57
+ primary: "";
58
+ secondary: "";
59
+ neutral: "";
60
+ success: "";
61
+ danger: "";
62
+ info: "";
63
+ warning: "";
64
+ };
65
+ invalid: {
66
+ true: {
67
+ root: string;
68
+ };
69
+ };
70
+ }, {
71
+ root: string;
72
+ header: string;
73
+ footer: string;
74
+ searchWrapper: string;
75
+ searchInput: string;
76
+ content: string;
77
+ group: string;
78
+ groupLabel: string;
79
+ item: string;
80
+ label: string;
81
+ description: string;
82
+ itemContent: string;
83
+ icon: string;
84
+ indicator: string;
85
+ checkbox: string;
86
+ empty: string;
87
+ }, import("tailwind-variants").TVReturnTypeLike<{
88
+ size: {
89
+ sm: {
90
+ item: string;
91
+ };
92
+ md: {
93
+ item: string;
94
+ };
95
+ lg: {
96
+ item: string;
97
+ };
98
+ };
99
+ color: {
100
+ primary: "";
101
+ secondary: "";
102
+ neutral: "";
103
+ success: "";
104
+ danger: "";
105
+ info: "";
106
+ warning: "";
107
+ };
108
+ invalid: {
109
+ true: {
110
+ root: string;
111
+ };
112
+ };
113
+ }, {
114
+ root: string;
115
+ header: string;
116
+ footer: string;
117
+ searchWrapper: string;
118
+ searchInput: string;
119
+ content: string;
120
+ group: string;
121
+ groupLabel: string;
122
+ item: string;
123
+ label: string;
124
+ description: string;
125
+ itemContent: string;
126
+ icon: string;
127
+ indicator: string;
128
+ checkbox: string;
129
+ empty: string;
130
+ }>>;
131
+ export type ListboxThemeSlots = keyof (typeof listboxTheme)['slots'];
@@ -0,0 +1,27 @@
1
+ import { tv } from "tailwind-variants";
2
+ export const listboxTheme = tv({
3
+ slots: {
4
+ root: "flex min-h-0 min-w-0 flex-col overflow-hidden rounded-[var(--selaras-resolved-radius-md)] border border-[var(--selaras-resolved-border-default)] bg-[var(--selaras-resolved-surface-default)] text-[var(--selaras-resolved-text-default)]",
5
+ header: "flex items-center justify-between gap-2 border-b border-[var(--selaras-resolved-border-default)] px-3 py-2",
6
+ footer: "border-t border-[var(--selaras-resolved-border-default)] px-3 py-2",
7
+ searchWrapper: "flex items-center gap-2 border-b border-[var(--selaras-resolved-border-default)] px-3",
8
+ searchInput: "h-10 min-w-0 flex-1 bg-transparent text-sm outline-none placeholder:text-[var(--selaras-resolved-text-muted)]",
9
+ content: "max-h-72 min-h-0 overflow-y-auto p-1 outline-none focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-[var(--_selaras-color-focus)]",
10
+ group: "",
11
+ groupLabel: "px-2 py-2 text-xs font-medium text-[var(--selaras-resolved-text-muted)]",
12
+ item: "my-0.5 flex w-full min-w-0 items-center gap-2 rounded-[var(--selaras-resolved-radius-sm)] px-2 py-2 outline-none cursor-pointer select-none transition-colors data-[highlighted]:bg-[var(--selaras-resolved-surface-elevated)] data-[state=checked]:bg-[var(--_selaras-color-subtle)] data-[state=checked]:text-[var(--_selaras-color-on-subtle)] data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
13
+ label: "truncate",
14
+ description: "text-xs text-[var(--selaras-resolved-text-muted)]",
15
+ itemContent: "flex min-w-0 flex-1 flex-col gap-0.5",
16
+ icon: "size-4 shrink-0",
17
+ indicator: "ms-auto flex size-4 shrink-0 items-center justify-center text-[var(--_selaras-color-indicator)]",
18
+ checkbox: "pointer-events-none flex size-4 shrink-0 items-center justify-center rounded-[var(--selaras-resolved-radius-sm)] border border-[var(--selaras-resolved-border-default)] data-[state=checked]:border-[var(--_selaras-color-fill)] data-[state=checked]:bg-[var(--_selaras-color-fill)] data-[state=checked]:text-[var(--_selaras-color-on-fill)]",
19
+ empty: "px-3 py-6 text-center text-sm text-[var(--selaras-resolved-text-muted)]"
20
+ },
21
+ variants: {
22
+ size: { sm: { item: "py-1.5 text-xs" }, md: { item: "text-sm" }, lg: { item: "py-2.5 text-base" } },
23
+ color: { primary: "", secondary: "", neutral: "", success: "", danger: "", info: "", warning: "" },
24
+ invalid: { true: { root: "border-[var(--_selaras-color-border)]" } }
25
+ },
26
+ defaultVariants: { size: "md", color: "primary" }
27
+ });
@@ -29,6 +29,8 @@ export declare const selectTheme: import("tailwind-variants").TVReturnType<{
29
29
  }, {
30
30
  root: string;
31
31
  trigger: string;
32
+ leading: string;
33
+ trailing: string;
32
34
  value: string;
33
35
  chipOverflow: string;
34
36
  icon: string;
@@ -76,6 +78,8 @@ export declare const selectTheme: import("tailwind-variants").TVReturnType<{
76
78
  }, {
77
79
  root: string;
78
80
  trigger: string;
81
+ leading: string;
82
+ trailing: string;
79
83
  value: string;
80
84
  chipOverflow: string;
81
85
  icon: string;
@@ -123,6 +127,8 @@ export declare const selectTheme: import("tailwind-variants").TVReturnType<{
123
127
  }, {
124
128
  root: string;
125
129
  trigger: string;
130
+ leading: string;
131
+ trailing: string;
126
132
  value: string;
127
133
  chipOverflow: string;
128
134
  icon: string;
@@ -8,6 +8,8 @@ export const selectTheme = tv({
8
8
  // let the plain gray hover ring beat the primary one in the cascade,
9
9
  // since they're all equal-specificity pseudo-class/attribute selectors.
10
10
  trigger: "inline-flex w-full min-h-10 flex-wrap items-center gap-1.5 rounded-[var(--selaras-resolved-radius-md)] bg-[var(--selaras-resolved-surface-default)] px-3 py-1.5 text-sm ring-1 ring-inset ring-[var(--selaras-resolved-border-default)] outline-none transition-[color,background-color,box-shadow] not-focus:not-focus-within:not-data-[state=open]:hover:ring-[var(--selaras-resolved-border-hover)] hover:bg-[var(--selaras-resolved-surface-elevated)] focus:ring-2 focus:ring-[var(--_selaras-color-focus)] focus-within:ring-2 focus-within:ring-[var(--_selaras-color-focus)] data-[state=open]:ring-2 data-[state=open]:ring-[var(--_selaras-color-focus)] disabled:opacity-50 disabled:pointer-events-none",
11
+ leading: "absolute start-0 top-1/2 -translate-y-1/2 m-0 flex min-w-0 items-center gap-1 border-0 p-0 ps-2.5 text-sm text-[var(--selaras-resolved-text-muted)] disabled:opacity-50",
12
+ trailing: "absolute end-0 top-1/2 -translate-y-1/2 m-0 flex min-w-0 items-center gap-1 border-0 p-0 pe-1 text-sm text-[var(--selaras-resolved-text-muted)] disabled:opacity-50",
11
13
  value: "flex-1 truncate text-start text-[var(--selaras-resolved-text-default)] data-[placeholder]:text-[var(--selaras-resolved-text-muted)]",
12
14
  // Both chip display branches render a real Chip (see
13
15
  // ComboboxSelectBase.vue) instead of hand-rolled markup, styled/
@@ -40,7 +42,7 @@ export const selectTheme = tv({
40
42
  mobilePanel: "",
41
43
  viewport: "max-h-72 overflow-y-auto p-1",
42
44
  group: "px-2 pt-2 pb-1 text-xs font-medium text-[var(--selaras-resolved-text-muted)]",
43
- item: "relative flex items-center gap-2 rounded-[var(--selaras-resolved-radius-sm)] py-1.5 ps-2 pe-8 text-sm text-[var(--selaras-resolved-text-default)] outline-none cursor-pointer select-none data-[highlighted]:bg-[var(--selaras-resolved-surface-elevated)] data-[disabled]:opacity-50 data-[disabled]:pointer-events-none",
45
+ item: "relative my-0.5 flex w-full min-w-0 items-center gap-2 rounded-[var(--selaras-resolved-radius-sm)] py-1.5 ps-2 pe-8 text-sm text-[var(--selaras-resolved-text-default)] outline-none cursor-pointer select-none transition-colors data-[highlighted]:bg-[var(--selaras-resolved-surface-elevated)] data-[state=checked]:bg-[var(--_selaras-color-subtle)] data-[state=checked]:text-[var(--_selaras-color-on-subtle)] data-[disabled]:opacity-50 data-[disabled]:pointer-events-none",
44
46
  itemIndicator: "absolute end-2 flex items-center text-[var(--_selaras-color-indicator)]",
45
47
  empty: "px-2 py-6 text-center text-sm text-[var(--selaras-resolved-text-muted)]",
46
48
  arrow: "fill-[var(--selaras-resolved-surface-default)] stroke-[var(--selaras-resolved-border-default)] stroke-1"
@@ -51,6 +51,8 @@ export declare const textareaTheme: import("tailwind-variants").TVReturnType<{
51
51
  }, {
52
52
  root: string;
53
53
  base: string;
54
+ leading: string;
55
+ trailing: string;
54
56
  leadingIcon: string;
55
57
  trailingIcon: string;
56
58
  clear: string;
@@ -107,6 +109,8 @@ export declare const textareaTheme: import("tailwind-variants").TVReturnType<{
107
109
  }, {
108
110
  root: string;
109
111
  base: string;
112
+ leading: string;
113
+ trailing: string;
110
114
  leadingIcon: string;
111
115
  trailingIcon: string;
112
116
  clear: string;
@@ -163,6 +167,8 @@ export declare const textareaTheme: import("tailwind-variants").TVReturnType<{
163
167
  }, {
164
168
  root: string;
165
169
  base: string;
170
+ leading: string;
171
+ trailing: string;
166
172
  leadingIcon: string;
167
173
  trailingIcon: string;
168
174
  clear: string;
@@ -9,6 +9,8 @@ export const textareaTheme = tv({
9
9
  // way Input's own icons are - once content wraps past one line,
10
10
  // centering against the whole (variable) box height would drift the
11
11
  // icon away from the first line of text it's meant to sit beside.
12
+ leading: "absolute top-1 start-0 m-0 flex min-w-0 items-center gap-1 border-0 p-0 ps-2.5 text-sm text-[var(--selaras-resolved-text-muted)] disabled:opacity-50",
13
+ trailing: "absolute top-1 end-0 m-0 flex min-w-0 items-center gap-1 border-0 p-0 pe-1 text-sm text-[var(--selaras-resolved-text-muted)] disabled:opacity-50",
12
14
  leadingIcon: "absolute start-3 top-2.5 shrink-0 text-[var(--selaras-resolved-text-muted)]",
13
15
  trailingIcon: "absolute end-3 top-2.5 shrink-0 text-[var(--selaras-resolved-text-muted)]",
14
16
  clear: "absolute end-1 top-1 shrink-0 rounded-full"
@@ -0,0 +1,74 @@
1
+ export declare const transferListTheme: import("tailwind-variants").TVReturnType<{
2
+ layout: {
3
+ auto: {
4
+ root: string;
5
+ actions: string;
6
+ };
7
+ horizontal: {
8
+ root: string;
9
+ actions: string;
10
+ };
11
+ vertical: {
12
+ root: string;
13
+ actions: string;
14
+ };
15
+ };
16
+ }, {
17
+ root: string;
18
+ panel: string;
19
+ header: string;
20
+ count: string;
21
+ listbox: string;
22
+ actions: string;
23
+ action: string;
24
+ announcement: string;
25
+ }, undefined, {
26
+ layout: {
27
+ auto: {
28
+ root: string;
29
+ actions: string;
30
+ };
31
+ horizontal: {
32
+ root: string;
33
+ actions: string;
34
+ };
35
+ vertical: {
36
+ root: string;
37
+ actions: string;
38
+ };
39
+ };
40
+ }, {
41
+ root: string;
42
+ panel: string;
43
+ header: string;
44
+ count: string;
45
+ listbox: string;
46
+ actions: string;
47
+ action: string;
48
+ announcement: string;
49
+ }, import("tailwind-variants").TVReturnTypeLike<{
50
+ layout: {
51
+ auto: {
52
+ root: string;
53
+ actions: string;
54
+ };
55
+ horizontal: {
56
+ root: string;
57
+ actions: string;
58
+ };
59
+ vertical: {
60
+ root: string;
61
+ actions: string;
62
+ };
63
+ };
64
+ }, {
65
+ root: string;
66
+ panel: string;
67
+ header: string;
68
+ count: string;
69
+ listbox: string;
70
+ actions: string;
71
+ action: string;
72
+ announcement: string;
73
+ }>>;
74
+ export type TransferListThemeSlots = keyof (typeof transferListTheme)['slots'];
@@ -0,0 +1,21 @@
1
+ import { tv } from "tailwind-variants";
2
+ export const transferListTheme = tv({
3
+ slots: {
4
+ root: "flex min-w-0 gap-3",
5
+ panel: "flex min-w-0 flex-1 flex-col gap-2",
6
+ header: "flex items-center justify-between gap-2 text-sm font-medium text-[var(--selaras-resolved-text-default)]",
7
+ count: "text-xs font-normal text-[var(--selaras-resolved-text-muted)]",
8
+ listbox: "flex-1",
9
+ actions: "flex shrink-0 items-center justify-center gap-2",
10
+ action: "",
11
+ announcement: "sr-only"
12
+ },
13
+ variants: {
14
+ layout: {
15
+ auto: { root: "flex-col sm:flex-row", actions: "flex-row sm:flex-col" },
16
+ horizontal: { root: "flex-row", actions: "flex-col" },
17
+ vertical: { root: "flex-col", actions: "flex-row" }
18
+ }
19
+ },
20
+ defaultVariants: { layout: "auto" }
21
+ });
@@ -41,6 +41,7 @@ import type { inputTheme, InputThemeSlots } from './theme/input.js';
41
41
  import type { inputGroupTheme, InputGroupThemeSlots } from './theme/input-group.js';
42
42
  import type { inputNumberTheme, InputNumberThemeSlots } from './theme/input-number.js';
43
43
  import type { kbdTheme, KbdThemeSlots } from './theme/kbd.js';
44
+ import type { listboxTheme, ListboxThemeSlots } from './theme/listbox.js';
44
45
  import type { modalTheme, ModalThemeSlots } from './theme/modal.js';
45
46
  import type { navigationMenuTheme, NavigationMenuThemeSlots } from './theme/navigation-menu.js';
46
47
  import type { pageAsideTheme, PageAsideThemeSlots } from './theme/page-aside.js';
@@ -72,6 +73,7 @@ import type { toastTheme, ToastThemeSlots } from './theme/toast.js';
72
73
  import type { toggleTheme, ToggleThemeSlots } from './theme/toggle.js';
73
74
  import type { toggleGroupTheme, ToggleGroupThemeSlots } from './theme/toggle-group.js';
74
75
  import type { tooltipTheme, TooltipThemeSlots } from './theme/tooltip.js';
76
+ import type { transferListTheme, TransferListThemeSlots } from './theme/transfer-list.js';
75
77
  import type { treeTheme, TreeThemeSlots } from './theme/tree.js';
76
78
  import type { ColorRole, RuntimeTokenOverrides } from './utils/color-registry.js';
77
79
  type AccordionVariants = VariantProps<typeof accordionTheme>;
@@ -362,6 +364,14 @@ export interface ThemeComponentRegistry {
362
364
  slots: RadioGroupThemeSlots;
363
365
  conditions: WithRegisteredColor<Pick<RadioGroupVariants, 'color' | 'size' | 'orientation' | 'variant' | 'invalid'>>;
364
366
  };
367
+ listbox: {
368
+ slots: ListboxThemeSlots;
369
+ conditions: WithRegisteredColor<Pick<VariantProps<typeof listboxTheme>, 'color' | 'size' | 'invalid'>>;
370
+ };
371
+ transferList: {
372
+ slots: TransferListThemeSlots;
373
+ conditions: Pick<VariantProps<typeof transferListTheme>, 'layout'>;
374
+ };
365
375
  rating: {
366
376
  slots: RatingThemeSlots;
367
377
  conditions: WithRegisteredColor<Pick<RatingVariants, 'color' | 'size' | 'orientation'>>;
@@ -46,6 +46,7 @@ export type * from '../components/Input.vue';
46
46
  export type * from '../components/InputGroup.vue';
47
47
  export type * from '../components/InputNumber.vue';
48
48
  export type * from '../components/Kbd.vue';
49
+ export type * from '../components/Listbox.vue';
49
50
  export type * from '../components/Modal.vue';
50
51
  export type * from '../components/NavigationMenu.vue';
51
52
  export type * from '../components/PageAside.vue';
@@ -83,6 +84,7 @@ export type * from '../components/Toast.vue';
83
84
  export type * from '../components/Toggle.vue';
84
85
  export type * from '../components/ToggleGroup.vue';
85
86
  export type * from '../components/Tooltip.vue';
87
+ export type * from '../components/TransferList.vue';
86
88
  export type * from '../components/Tree.vue';
87
89
  export type { UseCommandPaletteReturn } from '../composables/use-command-palette.js';
88
90
  export type { UseConfirmOptions, UseConfirmReturn } from '../composables/use-confirm.js';
@@ -95,6 +97,7 @@ export type { ThemeComponentRegistry, ThemeConfiguration, ThemeDefaults, ThemeUi
95
97
  export type { ArrowConfig, RoundedArrowConfig } from '../utils/arrow.js';
96
98
  export type { AutocompleteValue } from '../utils/autocomplete-contracts.js';
97
99
  export type { BuiltinColorName, ColorRecipe, ColorRecipeInput, ColorRole, RuntimeColorOverrides, RuntimeThemeMode, RuntimeTokenOverrides } from '../utils/color-registry.js';
100
+ export type { FieldAdornmentSlotProps } from '../utils/field-adornments.js';
98
101
  export type { FunctionalTokenOverrides, GeometryTokenOverrides, ThemeTokenOverrides } from '../utils/functional-tokens.js';
99
102
  export type { IconRegistry } from '../utils/icons.js';
100
103
  export { defaultIcons } from '../utils/icons.js';
@@ -0,0 +1,14 @@
1
+ import type { CSSProperties } from 'vue';
2
+ export interface FieldAdornmentSlotProps {
3
+ disabled: boolean;
4
+ size: 'sm' | 'md' | 'lg';
5
+ /** Button size that fits inside the field. */
6
+ buttonSize: 'sm' | 'md';
7
+ }
8
+ /** Reserve the rendered width of custom content, including dynamic suffixes. */
9
+ export declare function useFieldAdornments(): {
10
+ leading: import("vue").ShallowRef<HTMLElement | null, HTMLElement | null>;
11
+ trailing: import("vue").ShallowRef<HTMLElement | null, HTMLElement | null>;
12
+ padding: (hasLeading: boolean, hasTrailing: boolean, trailingExtra?: string) => CSSProperties | undefined;
13
+ clearOffset: () => CSSProperties;
14
+ };
@@ -0,0 +1,36 @@
1
+ import { shallowRef, watchEffect } from "vue";
2
+ export function useFieldAdornments() {
3
+ const leading = shallowRef(null);
4
+ const trailing = shallowRef(null);
5
+ const leadingWidth = shallowRef();
6
+ const trailingWidth = shallowRef();
7
+ watchEffect((onCleanup) => {
8
+ const start = leading.value;
9
+ const end = trailing.value;
10
+ function measure() {
11
+ leadingWidth.value = start ? `${start.getBoundingClientRect().width}px` : void 0;
12
+ trailingWidth.value = end ? `${end.getBoundingClientRect().width}px` : void 0;
13
+ }
14
+ measure();
15
+ if (!start && !end || typeof ResizeObserver === "undefined")
16
+ return;
17
+ const observer = new ResizeObserver(measure);
18
+ if (start)
19
+ observer.observe(start);
20
+ if (end)
21
+ observer.observe(end);
22
+ onCleanup(() => observer.disconnect());
23
+ }, { flush: "post" });
24
+ function padding(hasLeading, hasTrailing, trailingExtra = "0rem") {
25
+ if (!hasLeading && !hasTrailing)
26
+ return;
27
+ return {
28
+ paddingInlineStart: hasLeading ? `calc(${leadingWidth.value ?? "2rem"} + 0.5rem)` : void 0,
29
+ paddingInlineEnd: hasTrailing ? `calc(${trailingWidth.value ?? "2rem"} + 0.5rem + ${trailingExtra})` : void 0
30
+ };
31
+ }
32
+ function clearOffset() {
33
+ return { insetInlineEnd: `calc(${trailingWidth.value ?? "2rem"} + 0.25rem)` };
34
+ }
35
+ return { leading, trailing, padding, clearOffset };
36
+ }
@@ -0,0 +1,31 @@
1
+ import type { ListboxThemeSlots } from '../theme/listbox.js';
2
+ import type { SelectEntry, SelectEntryGroup, SelectEntryItem, SelectIdentity, SelectModel, SelectProps } from './select-contracts.js';
3
+ import type { UiProp } from './ui.js';
4
+ export interface ListboxProps<Entry extends SelectEntry = SelectEntry, Key extends string = 'value', Multiple extends boolean = false> extends Pick<SelectProps<Entry, Key, Multiple>, 'items' | 'modelValue' | 'defaultValue' | 'valueKey' | 'labelKey' | 'multiple' | 'searchable' | 'searchTerm' | 'virtualize' | 'disabled' | 'loading' | 'invalid' | 'size' | 'color' | 'id' | 'name' | 'form' | 'required'> {
5
+ label?: string;
6
+ descriptionKey?: keyof SelectEntryItem<Entry> & string;
7
+ selectionBehavior?: 'toggle' | 'replace';
8
+ checkbox?: boolean;
9
+ selectAll?: boolean;
10
+ ui?: UiProp<ListboxThemeSlots>;
11
+ }
12
+ export interface ListboxEmits<Entry extends SelectEntry = SelectEntry, Key extends string = 'value', Multiple extends boolean = false> {
13
+ 'update:modelValue': [value: SelectModel<SelectIdentity<Entry, Key>, Multiple>];
14
+ 'update:searchTerm': [value: string];
15
+ }
16
+ export interface ListboxItemSlot<Entry extends SelectEntry> {
17
+ item: SelectEntryItem<Entry>;
18
+ index: number;
19
+ selected: boolean;
20
+ disabled: boolean;
21
+ }
22
+ export interface ListboxSlots<Entry extends SelectEntry> {
23
+ 'item'?: (props: ListboxItemSlot<Entry>) => any;
24
+ 'group'?: (props: {
25
+ group: SelectEntryGroup<Entry>;
26
+ }) => any;
27
+ 'header'?: () => any;
28
+ 'footer'?: () => any;
29
+ 'empty'?: () => any;
30
+ 'empty-filter'?: () => any;
31
+ }
File without changes
@@ -44,6 +44,13 @@ export interface MessageRegistry {
44
44
  increment: string;
45
45
  decrement: string;
46
46
  noOptions: string;
47
+ selectAll: string;
48
+ transferSource: string;
49
+ transferTarget: string;
50
+ moveToTarget: string;
51
+ moveToSource: string;
52
+ listSelectionCount: (selected: number, total: number) => string;
53
+ transferItems: (count: number, target: string) => string;
47
54
  noResultsFound: string;
48
55
  createOption: (value: string) => string;
49
56
  noData: string;
@@ -44,6 +44,13 @@ export const defaultMessages = {
44
44
  increment: "Increment",
45
45
  decrement: "Decrement",
46
46
  noOptions: "No options",
47
+ selectAll: "Select all",
48
+ transferSource: "Available",
49
+ transferTarget: "Assigned",
50
+ moveToTarget: "Add selected",
51
+ moveToSource: "Remove selected",
52
+ listSelectionCount: (selected, total) => `${selected} of ${total} selected`,
53
+ transferItems: (count, target) => `${count} item${count === 1 ? "" : "s"} moved to ${target}`,
47
54
  noResultsFound: "No results found",
48
55
  createOption: (value) => `Create "${value}"`,
49
56
  noData: "No data",
@@ -2,6 +2,7 @@ import type { VariantProps } from 'tailwind-variants';
2
2
  import type { selectTheme, SelectThemeSlots } from '../theme/select.js';
3
3
  import type { RoundedArrowConfig } from './arrow.js';
4
4
  import type { ColorRole } from './color-registry.js';
5
+ import type { FieldAdornmentSlotProps } from './field-adornments.js';
5
6
  import type { OverlayPortal, OverlayPositioning } from './overlay.js';
6
7
  import type { UiProp } from './ui.js';
7
8
  export type SelectValue = string | number;
@@ -94,6 +95,8 @@ export interface SelectEmits<Entry extends SelectEntry = {
94
95
  'update:searchTerm': [value: string];
95
96
  }
96
97
  export interface SelectSlots<Entry extends SelectEntry, Key extends string = 'value'> {
98
+ 'leading'?: (props: FieldAdornmentSlotProps) => any;
99
+ 'trailing'?: (props: FieldAdornmentSlotProps) => any;
97
100
  'item'?: (props: {
98
101
  item: SelectEntryItem<Entry>;
99
102
  }) => any;
@@ -0,0 +1,2 @@
1
+ import type { VNode } from 'vue';
2
+ export declare function flattenSlotChildren(vnodes: VNode[]): VNode[];