@sewadah/selaras 0.7.1 → 0.8.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 (44) hide show
  1. package/dist/agent/catalog.json +1 -1
  2. package/dist/module.json +1 -1
  3. package/dist/runtime/components/AvatarGroup.vue +3 -2
  4. package/dist/runtime/components/CodeGroup.vue +3 -15
  5. package/dist/runtime/components/ColorModeSwitch.vue +5 -4
  6. package/dist/runtime/components/ColorModeToggle.vue +5 -4
  7. package/dist/runtime/components/Listbox.d.vue.ts +28 -0
  8. package/dist/runtime/components/Listbox.vue +53 -0
  9. package/dist/runtime/components/Listbox.vue.d.ts +28 -0
  10. package/dist/runtime/components/TransferList.d.vue.ts +47 -0
  11. package/dist/runtime/components/TransferList.vue +67 -0
  12. package/dist/runtime/components/TransferList.vue.d.ts +47 -0
  13. package/dist/runtime/internal/ListSelectionForm.d.vue.ts +15 -0
  14. package/dist/runtime/internal/ListSelectionForm.vue +33 -0
  15. package/dist/runtime/internal/ListSelectionForm.vue.d.ts +15 -0
  16. package/dist/runtime/internal/ListboxBase.d.vue.ts +49 -0
  17. package/dist/runtime/internal/ListboxBase.vue +177 -0
  18. package/dist/runtime/internal/ListboxBase.vue.d.ts +49 -0
  19. package/dist/runtime/internal/ListboxOption.d.vue.ts +31 -0
  20. package/dist/runtime/internal/ListboxOption.vue +37 -0
  21. package/dist/runtime/internal/ListboxOption.vue.d.ts +31 -0
  22. package/dist/runtime/internal/TransferListBase.d.vue.ts +63 -0
  23. package/dist/runtime/internal/TransferListBase.vue +206 -0
  24. package/dist/runtime/internal/TransferListBase.vue.d.ts +63 -0
  25. package/dist/runtime/internal/color-mode-control.d.ts +7 -0
  26. package/dist/runtime/internal/color-mode-control.js +12 -0
  27. package/dist/runtime/internal/list-selection.d.ts +15 -0
  28. package/dist/runtime/internal/list-selection.js +43 -0
  29. package/dist/runtime/theme/listbox.d.ts +131 -0
  30. package/dist/runtime/theme/listbox.js +27 -0
  31. package/dist/runtime/theme/select.js +1 -1
  32. package/dist/runtime/theme/transfer-list.d.ts +74 -0
  33. package/dist/runtime/theme/transfer-list.js +21 -0
  34. package/dist/runtime/theme-config.d.ts +10 -0
  35. package/dist/runtime/types/index.d.ts +2 -0
  36. package/dist/runtime/utils/listbox-contracts.d.ts +31 -0
  37. package/dist/runtime/utils/listbox-contracts.js +0 -0
  38. package/dist/runtime/utils/messages.d.ts +7 -0
  39. package/dist/runtime/utils/messages.js +7 -0
  40. package/dist/runtime/utils/slot-children.d.ts +2 -0
  41. package/dist/runtime/utils/slot-children.js +15 -0
  42. package/dist/runtime/utils/transfer-list-contracts.d.ts +48 -0
  43. package/dist/runtime/utils/transfer-list-contracts.js +0 -0
  44. package/package.json +1 -1
@@ -0,0 +1,177 @@
1
+ <script setup>
2
+ import { ListboxContent, ListboxFilter, ListboxGroup, ListboxGroupLabel, ListboxRoot, ListboxVirtualizer, useFilter } from "reka-ui";
3
+ import { computed, ref, toRaw, useId } from "vue";
4
+ import Checkbox from "../components/Checkbox.vue";
5
+ import Icon from "../components/Icon.vue";
6
+ import { useFormField } from "../composables/use-form-field";
7
+ import { useIcons } from "../composables/use-icons";
8
+ import { useMessages } from "../composables/use-messages";
9
+ import { listboxTheme } from "../theme/listbox";
10
+ import { resolveRegisteredColorRole } from "../utils/registered-colors";
11
+ import { resolveSlot, useComponentTheme, useFallthroughAttrs, useRootProps } from "../utils/ui";
12
+ import { isOptionGroup, useComboboxSelect } from "./combobox-select";
13
+ import { useListSelection } from "./list-selection";
14
+ import ListboxOption from "./ListboxOption.vue";
15
+ import ListSelectionForm from "./ListSelectionForm.vue";
16
+ defineOptions({ inheritAttrs: false });
17
+ const props = defineProps({
18
+ items: { type: null, required: true },
19
+ valueKey: { type: String, required: false },
20
+ labelKey: { type: String, required: false },
21
+ descriptionKey: { type: String, required: false },
22
+ multiple: { type: Boolean, required: false },
23
+ modelValue: { type: [String, Number, Object, Array], required: false },
24
+ defaultValue: { type: [String, Number, Object, Array], required: false },
25
+ formField: { type: Boolean, required: false, default: true },
26
+ label: { type: String, required: false },
27
+ selectionBehavior: { type: String, required: false },
28
+ checkbox: { type: Boolean, required: false },
29
+ selectAll: { type: Boolean, required: false },
30
+ ui: { type: Object, required: false },
31
+ searchable: { type: Boolean, required: false },
32
+ searchTerm: { type: String, required: false },
33
+ virtualize: { type: [Boolean, Object], required: false },
34
+ disabled: { type: Boolean, required: false },
35
+ loading: { type: Boolean, required: false },
36
+ invalid: { type: Boolean, required: false },
37
+ size: { type: null, required: false },
38
+ color: { type: null, required: false },
39
+ id: { type: String, required: false },
40
+ name: { type: String, required: false },
41
+ form: { type: String, required: false },
42
+ required: { type: Boolean, required: false }
43
+ });
44
+ const emit = defineEmits(["update:modelValue", "update:searchTerm"]);
45
+ const slots = defineSlots();
46
+ const parentField = useFormField();
47
+ const field = props.formField === false ? void 0 : parentField;
48
+ const icons = useIcons();
49
+ const messages = useMessages();
50
+ const { selection, values, update, reset } = useListSelection(props, (value) => emit("update:modelValue", value));
51
+ const { flatOptions } = useComboboxSelect({
52
+ get items() {
53
+ return props.items;
54
+ },
55
+ get valueKey() {
56
+ return props.valueKey;
57
+ },
58
+ get labelKey() {
59
+ return props.labelKey;
60
+ },
61
+ get modelValue() {
62
+ return selection.value;
63
+ }
64
+ }, (_, value) => update(value));
65
+ const localSearch = ref(props.searchTerm ?? "");
66
+ const search = computed({
67
+ get: () => props.searchTerm ?? localSearch.value,
68
+ set: (value) => {
69
+ localSearch.value = value;
70
+ emit("update:searchTerm", value);
71
+ }
72
+ });
73
+ const { contains } = useFilter({ sensitivity: "base" });
74
+ function matches(option) {
75
+ return !props.searchable || contains(option.label, search.value);
76
+ }
77
+ const visible = computed(() => flatOptions.value.filter(matches));
78
+ const visibleValues = computed(() => visible.value.map((option) => option.value));
79
+ const optionsByValue = computed(() => new Map(flatOptions.value.map((option) => [option.value, option])));
80
+ const indices = computed(() => new Map(flatOptions.value.map((option, index) => [option, index])));
81
+ const groups = computed(() => {
82
+ let offset = 0;
83
+ return props.items.map((entry) => {
84
+ const group = isOptionGroup(entry) ? entry : void 0;
85
+ const count = group ? group.items.length : 1;
86
+ const options = flatOptions.value.slice(offset, offset + count).map((option, index) => ({ option, index: offset + index })).filter(({ option }) => matches(option));
87
+ offset += count;
88
+ return { group, options };
89
+ }).filter((group) => group.options.length);
90
+ });
91
+ const eligible = computed(() => visible.value.filter((option) => !option.disabled).map((option) => option.value));
92
+ const allChecked = computed(() => eligible.value.length > 0 && eligible.value.every((value) => values.value.includes(value)));
93
+ const someChecked = computed(() => eligible.value.some((value) => values.value.includes(value)));
94
+ function selectVisible(checked) {
95
+ update(checked === true ? [...values.value, ...eligible.value.filter((value) => !values.value.includes(value))] : values.value.filter((value) => !eligible.value.includes(value)));
96
+ }
97
+ function onListKeydown(event) {
98
+ if (props.multiple && (event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "a") {
99
+ event.preventDefault();
100
+ event.stopPropagation();
101
+ selectVisible(true);
102
+ }
103
+ }
104
+ function updateSelection(value) {
105
+ update(Array.isArray(value) ? value.filter((item) => !optionsByValue.value.get(item)?.disabled || values.value.includes(item)) : value);
106
+ }
107
+ const generatedId = useId();
108
+ const id = computed(() => props.id ?? field?.id ?? `selaras-listbox-${generatedId}`);
109
+ const content = ref();
110
+ defineExpose({ focus: () => content.value?.$el.focus() });
111
+ const size = computed(() => props.size ?? field?.size ?? "md");
112
+ const invalid = computed(() => props.invalid || field?.invalid.value || false);
113
+ const color = computed(() => resolveRegisteredColorRole(invalid.value ? "danger" : props.color ?? "primary", "primary"));
114
+ const theme = useComponentTheme("listbox", listboxTheme);
115
+ const ui = computed(() => theme.value({ size: size.value, color: color.value, invalid: invalid.value }));
116
+ const rootProps = useRootProps(() => ui.value.root, () => props.ui?.root, { exclude: (key) => key.startsWith("aria-") });
117
+ const a11yAttrs = useFallthroughAttrs((key) => key.startsWith("aria-"));
118
+ const parts = computed(() => Object.fromEntries(Object.keys(listboxTheme.slots).map((key) => [key, resolveSlot(ui.value[key], props.ui?.[key])])));
119
+ const virtual = computed(() => typeof props.virtualize === "object" ? { estimateSize: 40, overscan: 8, ...props.virtualize } : { estimateSize: 40, overscan: 8 });
120
+ const rowProps = computed(() => ({ parts: parts.value, disabled: props.disabled, checkbox: !!props.multiple && props.checkbox, descriptionKey: props.descriptionKey ?? "description", color: color.value, size: size.value }));
121
+ function sameValue(a, b) {
122
+ return toRaw(a) === toRaw(b);
123
+ }
124
+ function resetForm() {
125
+ reset();
126
+ search.value = "";
127
+ }
128
+ </script>
129
+
130
+ <template>
131
+ <ListboxRoot :model-value="selection ?? (multiple ? [] : null)" :multiple="multiple" :disabled="disabled" :selection-behavior="selectionBehavior ?? 'toggle'" :by="sameValue" highlight-on-hover :data-selaras-color="color" v-bind="rootProps" @update:model-value="updateSelection($event)">
132
+ <div v-if="slots.header || multiple && selectAll || loading" v-bind="parts.header">
133
+ <slot name="header" />
134
+ <Checkbox v-if="multiple && selectAll" :id="`${generatedId}-select-all`" name="" :model-value="allChecked ? true : someChecked ? 'indeterminate' : false" :disabled="disabled || !eligible.length" :color="color" :size="size" :label="messages.selectAll" @update:model-value="selectVisible" />
135
+ <Icon v-if="loading" :name="icons.loading" :aria-label="messages.loading" class="animate-spin" v-bind="parts.icon" />
136
+ </div>
137
+ <div v-if="searchable" v-bind="parts.searchWrapper">
138
+ <Icon :name="icons.search" v-bind="parts.icon" />
139
+ <ListboxFilter v-model="search" :disabled="disabled" :placeholder="messages.search" :aria-label="messages.search" role="combobox" aria-autocomplete="list" :aria-expanded="true" :aria-controls="id" v-bind="parts.searchInput" />
140
+ </div>
141
+ <ListboxContent :id="id" ref="content" :aria-label="label ?? (field?.labelId ? void 0 : messages.select)" :aria-labelledby="field?.labelId" :aria-describedby="field?.describedBy.value" :aria-invalid="invalid || void 0" :aria-busy="loading || void 0" v-bind="{ ...parts.content, ...a11yAttrs }" @keydown.capture="onListKeydown">
142
+ <div v-if="!visible.length" v-bind="parts.empty">
143
+ <slot v-if="!flatOptions.length" name="empty">
144
+ {{ messages.noOptions }}
145
+ </slot>
146
+ <slot v-else name="empty-filter">
147
+ {{ messages.noResultsFound }}
148
+ </slot>
149
+ </div>
150
+ <ListboxVirtualizer v-else-if="virtualize" v-slot="{ option: value, virtualItem }" :options="visibleValues" :text-content="(value2) => optionsByValue.get(value2).label" v-bind="virtual">
151
+ <ListboxOption :option="optionsByValue.get(value)" :index="indices.get(optionsByValue.get(value)) ?? virtualItem.index" :selected="values.includes(value)" v-bind="rowProps">
152
+ <template v-if="slots.item" #default="data">
153
+ <slot name="item" v-bind="data" />
154
+ </template>
155
+ </ListboxOption>
156
+ </ListboxVirtualizer>
157
+ <template v-else>
158
+ <ListboxGroup v-for="(group, groupIndex) in groups" :key="groupIndex" v-bind="parts.group">
159
+ <ListboxGroupLabel v-if="group.group" v-bind="parts.groupLabel">
160
+ <slot name="group" :group="group.group">
161
+ {{ group.group.label }}
162
+ </slot>
163
+ </ListboxGroupLabel>
164
+ <ListboxOption v-for="{ option, index } in group.options" :key="option.key" :option="option" :index="index" :selected="values.includes(option.value)" v-bind="rowProps">
165
+ <template v-if="slots.item" #default="data">
166
+ <slot name="item" v-bind="data" />
167
+ </template>
168
+ </ListboxOption>
169
+ </ListboxGroup>
170
+ </template>
171
+ </ListboxContent>
172
+ <div v-if="slots.footer" v-bind="parts.footer">
173
+ <slot name="footer" />
174
+ </div>
175
+ <ListSelectionForm v-if="formField !== false" :values="values" :name="name ?? field?.name" :form="form" :required="required" :disabled="disabled" @reset="resetForm" />
176
+ </ListboxRoot>
177
+ </template>
@@ -0,0 +1,49 @@
1
+ import type { ListboxProps } from '../utils/listbox-contracts.js';
2
+ import type { SelectModelValue } from '../utils/select-contracts.js';
3
+ import type { SelectItems } from './combobox-select.js';
4
+ export interface ListboxBaseProps extends Omit<ListboxProps, 'items' | 'valueKey' | 'labelKey' | 'descriptionKey' | 'multiple' | 'modelValue' | 'defaultValue'> {
5
+ items: SelectItems;
6
+ valueKey?: string;
7
+ labelKey?: string;
8
+ descriptionKey?: string;
9
+ multiple?: boolean;
10
+ modelValue?: SelectModelValue | SelectModelValue[];
11
+ defaultValue?: SelectModelValue | SelectModelValue[];
12
+ /** Composite panels must not inherit or submit their parent's form value. */
13
+ formField?: boolean;
14
+ }
15
+ type __VLS_Slots = {
16
+ 'item'?: (props: {
17
+ item: SelectModelValue;
18
+ index: number;
19
+ selected: boolean;
20
+ disabled: boolean;
21
+ }) => any;
22
+ 'group'?: (props: {
23
+ group: {
24
+ label: string;
25
+ items: readonly SelectModelValue[];
26
+ };
27
+ }) => any;
28
+ 'header'?: () => any;
29
+ 'footer'?: () => any;
30
+ 'empty'?: () => any;
31
+ 'empty-filter'?: () => any;
32
+ };
33
+ declare const __VLS_base: import("vue").DefineComponent<ListboxBaseProps, {
34
+ focus: () => void | undefined;
35
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
36
+ "update:modelValue": (value: SelectModelValue | SelectModelValue[] | undefined) => any;
37
+ "update:searchTerm": (value: string) => any;
38
+ }, string, import("vue").PublicProps, Readonly<ListboxBaseProps> & Readonly<{
39
+ "onUpdate:modelValue"?: ((value: SelectModelValue | SelectModelValue[] | undefined) => any) | undefined;
40
+ "onUpdate:searchTerm"?: ((value: string) => any) | undefined;
41
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
42
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
43
+ declare const _default: typeof __VLS_export;
44
+ export default _default;
45
+ type __VLS_WithSlots<T, S> = T & {
46
+ new (): {
47
+ $slots: S;
48
+ };
49
+ };
@@ -0,0 +1,31 @@
1
+ import type { ListboxThemeSlots } from '../theme/listbox.js';
2
+ import type { ResolvedItemOption } from './combobox-select.js';
3
+ type __VLS_Props = {
4
+ option: ResolvedItemOption;
5
+ index: number;
6
+ selected: boolean;
7
+ disabled?: boolean;
8
+ checkbox?: boolean;
9
+ descriptionKey: string;
10
+ color: string;
11
+ size?: 'sm' | 'md' | 'lg';
12
+ parts: Record<ListboxThemeSlots, Record<string, unknown>>;
13
+ };
14
+ declare var __VLS_13: {
15
+ item: import("../utils/select-contracts.js").SelectModelValue | import("./combobox-select.js").SelectOption<import("../utils/select-contracts.js").SelectModelValue>;
16
+ index: number;
17
+ selected: boolean;
18
+ disabled: boolean;
19
+ };
20
+ type __VLS_Slots = {} & {
21
+ default?: (props: typeof __VLS_13) => any;
22
+ };
23
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
24
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
25
+ declare const _default: typeof __VLS_export;
26
+ export default _default;
27
+ type __VLS_WithSlots<T, S> = T & {
28
+ new (): {
29
+ $slots: S;
30
+ };
31
+ };
@@ -0,0 +1,37 @@
1
+ <script setup>
2
+ import { ListboxItem, ListboxItemIndicator } from "reka-ui";
3
+ import { computed } from "vue";
4
+ import Icon from "../components/Icon.vue";
5
+ import { useIcons } from "../composables/use-icons";
6
+ const props = defineProps({
7
+ option: { type: Object, required: true },
8
+ index: { type: Number, required: true },
9
+ selected: { type: Boolean, required: true },
10
+ disabled: { type: Boolean, required: false },
11
+ checkbox: { type: Boolean, required: false },
12
+ descriptionKey: { type: String, required: true },
13
+ color: { type: String, required: true },
14
+ size: { type: String, required: false },
15
+ parts: { type: Object, required: true }
16
+ });
17
+ const icons = useIcons();
18
+ const record = computed(() => typeof props.option.raw === "object" ? props.option.raw : void 0);
19
+ const description = computed(() => record.value?.[props.descriptionKey]);
20
+ const icon = computed(() => typeof record.value?.icon === "string" ? record.value.icon : void 0);
21
+ </script>
22
+
23
+ <template>
24
+ <ListboxItem :value="option.value" :text-value="option.label" :disabled="disabled || option.disabled" v-bind="parts.item">
25
+ <span v-if="checkbox" aria-hidden="true" :data-state="selected ? 'checked' : 'unchecked'" v-bind="parts.checkbox"><Icon v-if="selected" :name="icons.check" class="size-3" /></span>
26
+ <slot :item="option.raw" :index="index" :selected="selected" :disabled="!!(disabled || option.disabled)">
27
+ <Icon v-if="icon" :name="icon" v-bind="parts.icon" />
28
+ <span v-bind="parts.itemContent">
29
+ <span v-bind="parts.label">{{ option.label }}</span>
30
+ <span v-if="description" v-bind="parts.description">{{ description }}</span>
31
+ </span>
32
+ </slot>
33
+ <ListboxItemIndicator v-if="!checkbox" v-bind="parts.indicator">
34
+ <Icon :name="icons.check" v-bind="parts.icon" />
35
+ </ListboxItemIndicator>
36
+ </ListboxItem>
37
+ </template>
@@ -0,0 +1,31 @@
1
+ import type { ListboxThemeSlots } from '../theme/listbox.js';
2
+ import type { ResolvedItemOption } from './combobox-select.js';
3
+ type __VLS_Props = {
4
+ option: ResolvedItemOption;
5
+ index: number;
6
+ selected: boolean;
7
+ disabled?: boolean;
8
+ checkbox?: boolean;
9
+ descriptionKey: string;
10
+ color: string;
11
+ size?: 'sm' | 'md' | 'lg';
12
+ parts: Record<ListboxThemeSlots, Record<string, unknown>>;
13
+ };
14
+ declare var __VLS_13: {
15
+ item: import("../utils/select-contracts.js").SelectModelValue | import("./combobox-select.js").SelectOption<import("../utils/select-contracts.js").SelectModelValue>;
16
+ index: number;
17
+ selected: boolean;
18
+ disabled: boolean;
19
+ };
20
+ type __VLS_Slots = {} & {
21
+ default?: (props: typeof __VLS_13) => any;
22
+ };
23
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
24
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
25
+ declare const _default: typeof __VLS_export;
26
+ export default _default;
27
+ type __VLS_WithSlots<T, S> = T & {
28
+ new (): {
29
+ $slots: S;
30
+ };
31
+ };
@@ -0,0 +1,63 @@
1
+ import type { SelectModelValue } from '../utils/select-contracts.js';
2
+ import type { TransferListProps, TransferSide } from '../utils/transfer-list-contracts.js';
3
+ import type { SelectItems } from './combobox-select.js';
4
+ export interface TransferListBaseProps extends Omit<TransferListProps, 'items' | 'valueKey' | 'labelKey' | 'descriptionKey'> {
5
+ items: SelectItems;
6
+ valueKey?: string;
7
+ labelKey?: string;
8
+ descriptionKey?: string;
9
+ }
10
+ declare var __VLS_2: "source-header" | "target-header", __VLS_3: {
11
+ count: number;
12
+ checkedCount: number;
13
+ }, __VLS_15: {
14
+ side: TransferSide;
15
+ item: SelectModelValue;
16
+ index: number;
17
+ selected: boolean;
18
+ disabled: boolean;
19
+ }, __VLS_18: {
20
+ side: TransferSide;
21
+ }, __VLS_21: {
22
+ side: TransferSide;
23
+ }, __VLS_25: "source-footer" | "target-footer", __VLS_26: {};
24
+ type __VLS_Slots = {} & {
25
+ [K in NonNullable<typeof __VLS_2>]?: (props: typeof __VLS_3) => any;
26
+ } & {
27
+ [K in NonNullable<typeof __VLS_25>]?: (props: typeof __VLS_26) => any;
28
+ } & {
29
+ item?: (props: typeof __VLS_15) => any;
30
+ } & {
31
+ empty?: (props: typeof __VLS_18) => any;
32
+ } & {
33
+ 'empty-filter'?: (props: typeof __VLS_21) => any;
34
+ };
35
+ declare const __VLS_base: import("vue").DefineComponent<TransferListBaseProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
36
+ "update:modelValue": (value: SelectModelValue[]) => any;
37
+ "update:checked": (value: SelectModelValue[]) => any;
38
+ "update:sourceSearchTerm": (value: string) => any;
39
+ "update:targetSearchTerm": (value: string) => any;
40
+ transfer: (payload: {
41
+ direction: "to-target" | "to-source";
42
+ items: SelectModelValue[];
43
+ values: SelectModelValue[];
44
+ }) => any;
45
+ }, string, import("vue").PublicProps, Readonly<TransferListBaseProps> & Readonly<{
46
+ "onUpdate:modelValue"?: ((value: SelectModelValue[]) => any) | undefined;
47
+ "onUpdate:checked"?: ((value: SelectModelValue[]) => any) | undefined;
48
+ "onUpdate:sourceSearchTerm"?: ((value: string) => any) | undefined;
49
+ "onUpdate:targetSearchTerm"?: ((value: string) => any) | undefined;
50
+ onTransfer?: ((payload: {
51
+ direction: "to-target" | "to-source";
52
+ items: SelectModelValue[];
53
+ values: SelectModelValue[];
54
+ }) => any) | undefined;
55
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
56
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
57
+ declare const _default: typeof __VLS_export;
58
+ export default _default;
59
+ type __VLS_WithSlots<T, S> = T & {
60
+ new (): {
61
+ $slots: S;
62
+ };
63
+ };
@@ -0,0 +1,206 @@
1
+ <script setup>
2
+ import { computed, getCurrentInstance, nextTick, ref, useSlots } from "vue";
3
+ import Button from "../components/Button.vue";
4
+ import { useFormField } from "../composables/use-form-field";
5
+ import { useIcons } from "../composables/use-icons";
6
+ import { useMessages } from "../composables/use-messages";
7
+ import { transferListTheme } from "../theme/transfer-list";
8
+ import { resolveSlot, useComponentTheme, useRootProps } from "../utils/ui";
9
+ import { isOptionGroup, useComboboxSelect } from "./combobox-select";
10
+ import { assertUniqueSelection, useListSelection } from "./list-selection";
11
+ import ListboxBase from "./ListboxBase.vue";
12
+ import ListSelectionForm from "./ListSelectionForm.vue";
13
+ defineOptions({ inheritAttrs: false });
14
+ const props = defineProps({
15
+ items: { type: null, required: true },
16
+ valueKey: { type: String, required: false },
17
+ labelKey: { type: String, required: false },
18
+ descriptionKey: { type: String, required: false },
19
+ checked: { type: null, required: false },
20
+ sourceLabel: { type: String, required: false },
21
+ targetLabel: { type: String, required: false },
22
+ sourceSearchTerm: { type: String, required: false },
23
+ targetSearchTerm: { type: String, required: false },
24
+ showSelectAll: { type: Boolean, required: false },
25
+ layout: { type: String, required: false },
26
+ ui: { type: Object, required: false },
27
+ modelValue: { type: null, required: false },
28
+ defaultValue: { type: null, required: false },
29
+ searchable: { type: Boolean, required: false },
30
+ virtualize: { type: [Boolean, Object], required: false },
31
+ disabled: { type: Boolean, required: false },
32
+ loading: { type: Boolean, required: false },
33
+ invalid: { type: Boolean, required: false },
34
+ size: { type: null, required: false },
35
+ color: { type: null, required: false },
36
+ id: { type: String, required: false },
37
+ name: { type: String, required: false },
38
+ form: { type: String, required: false },
39
+ required: { type: Boolean, required: false }
40
+ });
41
+ const emit = defineEmits(["update:modelValue", "update:checked", "update:sourceSearchTerm", "update:targetSearchTerm", "transfer"]);
42
+ const slots = useSlots();
43
+ const field = useFormField();
44
+ const messages = useMessages();
45
+ const icons = useIcons();
46
+ const { values, update, reset } = useListSelection({
47
+ get modelValue() {
48
+ return props.modelValue;
49
+ },
50
+ get defaultValue() {
51
+ return props.defaultValue;
52
+ },
53
+ get disabled() {
54
+ return props.disabled;
55
+ },
56
+ multiple: true
57
+ }, (value) => emit("update:modelValue", value));
58
+ const { flatOptions, toOption } = useComboboxSelect(props, (_, value) => update(value));
59
+ const instance = getCurrentInstance();
60
+ const localChecked = ref([]);
61
+ const checked = computed(() => {
62
+ const value = Object.hasOwn(instance.vnode.props ?? {}, "checked") ? props.checked ?? [] : localChecked.value;
63
+ if (!Array.isArray(value))
64
+ throw new TypeError("[selaras] TransferList checked must be an array.");
65
+ assertUniqueSelection(value);
66
+ return value;
67
+ });
68
+ function setChecked(value) {
69
+ if (props.disabled)
70
+ return;
71
+ if (!Object.hasOwn(instance.vnode.props ?? {}, "checked"))
72
+ localChecked.value = value;
73
+ emit("update:checked", value);
74
+ }
75
+ const sides = ["source", "target"];
76
+ const panelRefs = {};
77
+ const localSearch = ref({ source: "", target: "" });
78
+ function query(side) {
79
+ return (side === "source" ? props.sourceSearchTerm : props.targetSearchTerm) ?? localSearch.value[side];
80
+ }
81
+ function setQuery(side, value) {
82
+ localSearch.value[side] = value;
83
+ if (side === "source")
84
+ emit("update:sourceSearchTerm", value);
85
+ else
86
+ emit("update:targetSearchTerm", value);
87
+ }
88
+ function onSide(value, side) {
89
+ return values.value.includes(value) === (side === "target");
90
+ }
91
+ function panelItems(side) {
92
+ return props.items.flatMap((entry) => {
93
+ if (isOptionGroup(entry)) {
94
+ const items = entry.items.filter((item) => onSide(toOption(item).value, side));
95
+ return items.length ? [{ ...entry, items }] : [];
96
+ }
97
+ return onSide(toOption(entry).value, side) ? [entry] : [];
98
+ });
99
+ }
100
+ const panels = computed(() => Object.fromEntries(sides.map((side) => [side, {
101
+ items: panelItems(side),
102
+ options: flatOptions.value.filter((option) => onSide(option.value, side))
103
+ }])));
104
+ function panelChecked(side) {
105
+ return checked.value.filter((value) => onSide(value, side));
106
+ }
107
+ function updatePanelChecked(side, next) {
108
+ setChecked([...checked.value.filter((value) => !onSide(value, side)), ...Array.isArray(next) ? next : []]);
109
+ }
110
+ function movable(side) {
111
+ return panels.value[side].options.filter((option) => !option.disabled && checked.value.includes(option.value));
112
+ }
113
+ const announcement = ref("");
114
+ const sourceLabel = computed(() => props.sourceLabel ?? messages.value.transferSource);
115
+ const targetLabel = computed(() => props.targetLabel ?? messages.value.transferTarget);
116
+ function transfer(side) {
117
+ if (props.disabled)
118
+ return;
119
+ const options = movable(side);
120
+ if (!options.length)
121
+ return;
122
+ const moved = options.map((option) => option.value);
123
+ update(side === "source" ? [...values.value, ...moved] : values.value.filter((value) => !moved.includes(value)));
124
+ setChecked(checked.value.filter((value) => !moved.includes(value)));
125
+ emit("transfer", { direction: side === "source" ? "to-target" : "to-source", items: options.map((option) => option.raw), values: moved });
126
+ announcement.value = "";
127
+ void nextTick(() => {
128
+ const destination = side === "source" ? "target" : "source";
129
+ if (moved.every((value) => onSide(value, destination))) {
130
+ announcement.value = messages.value.transferItems(moved.length, destination === "target" ? targetLabel.value : sourceLabel.value);
131
+ panelRefs[destination]?.focus();
132
+ }
133
+ });
134
+ }
135
+ const theme = useComponentTheme("transferList", transferListTheme);
136
+ const ui = computed(() => theme.value({ layout: props.layout }));
137
+ const rootProps = useRootProps(() => ui.value.root, () => props.ui?.root);
138
+ const parts = computed(() => Object.fromEntries(Object.keys(transferListTheme.slots).map((key) => [key, resolveSlot(ui.value[key], props.ui?.[key])])));
139
+ function resetForm() {
140
+ reset();
141
+ localChecked.value = [];
142
+ emit("update:checked", []);
143
+ setQuery("source", "");
144
+ setQuery("target", "");
145
+ announcement.value = "";
146
+ }
147
+ </script>
148
+
149
+ <template>
150
+ <div :id="id ?? field?.id" :aria-invalid="invalid || field?.invalid.value || void 0" :aria-describedby="field?.describedBy.value" v-bind="rootProps">
151
+ <template v-for="side in sides" :key="side">
152
+ <div v-bind="parts.panel">
153
+ <div v-bind="parts.header">
154
+ <slot :name="`${side}-header`" :count="panels[side].options.length" :checked-count="panelChecked(side).length">
155
+ <span>{{ side === "source" ? sourceLabel : targetLabel }}</span>
156
+ <span v-bind="parts.count">{{ messages.listSelectionCount(panelChecked(side).length, panels[side].options.length) }}</span>
157
+ </slot>
158
+ </div>
159
+ <ListboxBase
160
+ :ref="(element) => {
161
+ panelRefs[side] = element;
162
+ }"
163
+ :items="panels[side].items"
164
+ :model-value="panelChecked(side)"
165
+ :value-key="valueKey"
166
+ :label-key="labelKey"
167
+ :description-key="descriptionKey"
168
+ :label="side === 'source' ? sourceLabel : targetLabel"
169
+ :search-term="query(side)"
170
+ :select-all="showSelectAll ?? true"
171
+ :searchable="searchable"
172
+ :virtualize="virtualize"
173
+ :disabled="disabled"
174
+ :loading="loading"
175
+ :invalid="invalid || field?.invalid.value"
176
+ :size="size ?? field?.size"
177
+ :color="color"
178
+ :form-field="false"
179
+ multiple checkbox
180
+ v-bind="parts.listbox"
181
+ @update:model-value="updatePanelChecked(side, $event)"
182
+ @update:search-term="setQuery(side, $event)"
183
+ >
184
+ <template v-if="slots.item" #item="data">
185
+ <slot name="item" v-bind="data" :side="side" />
186
+ </template>
187
+ <template v-if="slots.empty" #empty>
188
+ <slot name="empty" :side="side" />
189
+ </template>
190
+ <template v-if="slots['empty-filter']" #empty-filter>
191
+ <slot name="empty-filter" :side="side" />
192
+ </template>
193
+ <template v-if="slots[`${side}-footer`]" #footer>
194
+ <slot :name="`${side}-footer`" />
195
+ </template>
196
+ </ListboxBase>
197
+ </div>
198
+ <div v-if="side === 'source'" v-bind="parts.actions">
199
+ <Button :icon="icons.chevronRight" :aria-label="messages.moveToTarget" :title="messages.moveToTarget" :disabled="disabled || !movable('source').length" :size="size ?? field?.size" :color="color" variant="outline" square v-bind="parts.action" @click="transfer('source')" />
200
+ <Button :icon="icons.chevronLeft" :aria-label="messages.moveToSource" :title="messages.moveToSource" :disabled="disabled || !movable('target').length" :size="size ?? field?.size" :color="color" variant="outline" square v-bind="parts.action" @click="transfer('target')" />
201
+ </div>
202
+ </template>
203
+ <span role="status" aria-live="polite" aria-atomic="true" v-bind="parts.announcement">{{ announcement }}</span>
204
+ <ListSelectionForm :values="values" :name="name ?? field?.name" :form="form" :required="required" :disabled="disabled" @reset="resetForm" />
205
+ </div>
206
+ </template>
@@ -0,0 +1,63 @@
1
+ import type { SelectModelValue } from '../utils/select-contracts.js';
2
+ import type { TransferListProps, TransferSide } from '../utils/transfer-list-contracts.js';
3
+ import type { SelectItems } from './combobox-select.js';
4
+ export interface TransferListBaseProps extends Omit<TransferListProps, 'items' | 'valueKey' | 'labelKey' | 'descriptionKey'> {
5
+ items: SelectItems;
6
+ valueKey?: string;
7
+ labelKey?: string;
8
+ descriptionKey?: string;
9
+ }
10
+ declare var __VLS_2: "source-header" | "target-header", __VLS_3: {
11
+ count: number;
12
+ checkedCount: number;
13
+ }, __VLS_15: {
14
+ side: TransferSide;
15
+ item: SelectModelValue;
16
+ index: number;
17
+ selected: boolean;
18
+ disabled: boolean;
19
+ }, __VLS_18: {
20
+ side: TransferSide;
21
+ }, __VLS_21: {
22
+ side: TransferSide;
23
+ }, __VLS_25: "source-footer" | "target-footer", __VLS_26: {};
24
+ type __VLS_Slots = {} & {
25
+ [K in NonNullable<typeof __VLS_2>]?: (props: typeof __VLS_3) => any;
26
+ } & {
27
+ [K in NonNullable<typeof __VLS_25>]?: (props: typeof __VLS_26) => any;
28
+ } & {
29
+ item?: (props: typeof __VLS_15) => any;
30
+ } & {
31
+ empty?: (props: typeof __VLS_18) => any;
32
+ } & {
33
+ 'empty-filter'?: (props: typeof __VLS_21) => any;
34
+ };
35
+ declare const __VLS_base: import("vue").DefineComponent<TransferListBaseProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
36
+ "update:modelValue": (value: SelectModelValue[]) => any;
37
+ "update:checked": (value: SelectModelValue[]) => any;
38
+ "update:sourceSearchTerm": (value: string) => any;
39
+ "update:targetSearchTerm": (value: string) => any;
40
+ transfer: (payload: {
41
+ direction: "to-target" | "to-source";
42
+ items: SelectModelValue[];
43
+ values: SelectModelValue[];
44
+ }) => any;
45
+ }, string, import("vue").PublicProps, Readonly<TransferListBaseProps> & Readonly<{
46
+ "onUpdate:modelValue"?: ((value: SelectModelValue[]) => any) | undefined;
47
+ "onUpdate:checked"?: ((value: SelectModelValue[]) => any) | undefined;
48
+ "onUpdate:sourceSearchTerm"?: ((value: string) => any) | undefined;
49
+ "onUpdate:targetSearchTerm"?: ((value: string) => any) | undefined;
50
+ onTransfer?: ((payload: {
51
+ direction: "to-target" | "to-source";
52
+ items: SelectModelValue[];
53
+ values: SelectModelValue[];
54
+ }) => any) | undefined;
55
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
56
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
57
+ declare const _default: typeof __VLS_export;
58
+ export default _default;
59
+ type __VLS_WithSlots<T, S> = T & {
60
+ new (): {
61
+ $slots: S;
62
+ };
63
+ };