@xen-orchestra/web-core 0.19.0 → 0.20.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.
- package/lib/components/backdrop/VtsBackdrop.vue +1 -1
- package/lib/components/column/VtsColumn.vue +21 -0
- package/lib/components/columns/VtsColumns.vue +38 -0
- package/lib/components/copy-button/VtsCopyButton.vue +29 -0
- package/lib/components/enabled-state/VtsEnabledState.vue +23 -0
- package/lib/components/layout/VtsLayoutSidebar.vue +1 -1
- package/lib/components/quick-info-column/VtsQuickInfoColumn.vue +1 -1
- package/lib/components/quick-info-row/VtsQuickInfoRow.vue +26 -7
- package/lib/components/relative-time/VtsRelativeTime.vue +18 -0
- package/lib/components/select/VtsOption.vue +24 -0
- package/lib/components/select/VtsSelect.vue +96 -0
- package/lib/components/state-hero/VtsLoadingHero.vue +45 -4
- package/lib/components/tree/VtsTreeItem.vue +11 -1
- package/lib/components/ui/dropdown/UiDropdownList.vue +10 -2
- package/lib/components/ui/head-bar/UiHeadBar.vue +2 -2
- package/lib/composables/relative-time.composable.ts +1 -1
- package/lib/locales/cs.json +0 -1
- package/lib/locales/en.json +65 -1
- package/lib/locales/es.json +0 -1
- package/lib/locales/fa.json +0 -1
- package/lib/locales/fr.json +67 -3
- package/lib/locales/nl.json +0 -1
- package/lib/locales/sv.json +0 -1
- package/lib/packages/collection/README.md +38 -33
- package/lib/packages/collection/create-collection.ts +27 -13
- package/lib/packages/collection/create-item.ts +39 -0
- package/lib/packages/collection/guess-item-id.ts +26 -0
- package/lib/packages/collection/index.ts +0 -3
- package/lib/packages/collection/types.ts +46 -18
- package/lib/packages/collection/use-collection.ts +39 -7
- package/lib/packages/collection/use-flag-registry.ts +22 -5
- package/lib/packages/form-select/README.md +96 -0
- package/lib/packages/form-select/index.ts +2 -0
- package/lib/packages/form-select/types.ts +75 -0
- package/lib/packages/form-select/use-form-option-controller.ts +50 -0
- package/lib/packages/form-select/use-form-select-controller.ts +205 -0
- package/lib/packages/form-select/use-form-select-keyboard-navigation.ts +157 -0
- package/lib/packages/form-select/use-form-select.ts +193 -0
- package/lib/stores/sidebar.store.ts +14 -1
- package/package.json +1 -1
- package/lib/packages/collection/build-item.ts +0 -45
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type FormOption, IK_FORM_SELECT_CONTROLLER } from '@core/packages/form-select/types.ts'
|
|
2
|
+
import { unrefElement, useEventListener, whenever } from '@vueuse/core'
|
|
3
|
+
import { computed, inject, type MaybeRefOrGetter, ref, toValue } from 'vue'
|
|
4
|
+
|
|
5
|
+
export function useFormOptionController<TOption extends FormOption>(_option: MaybeRefOrGetter<TOption>) {
|
|
6
|
+
const controller = inject(IK_FORM_SELECT_CONTROLLER)
|
|
7
|
+
|
|
8
|
+
if (!controller) {
|
|
9
|
+
throw new Error('useFormOption needs a FormSelectController to be injected')
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const option = computed(() => toValue(_option))
|
|
13
|
+
|
|
14
|
+
const elementRef = ref<HTMLDivElement>()
|
|
15
|
+
|
|
16
|
+
whenever(
|
|
17
|
+
() => option.value.flags.active,
|
|
18
|
+
() => {
|
|
19
|
+
unrefElement(elementRef)?.scrollIntoView({ block: 'nearest' })
|
|
20
|
+
}
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
useEventListener(elementRef, 'click', event => {
|
|
24
|
+
event.preventDefault()
|
|
25
|
+
|
|
26
|
+
if (option.value.properties.disabled) {
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (option.value.properties.multiple) {
|
|
31
|
+
option.value.toggleFlag('selected')
|
|
32
|
+
controller.focusSearchOrTrigger()
|
|
33
|
+
} else {
|
|
34
|
+
option.value.toggleFlag('selected', true)
|
|
35
|
+
controller.closeDropdown(true)
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
useEventListener(elementRef, 'mouseenter', () => {
|
|
40
|
+
if (option.value.properties.disabled || controller.isNavigatingWithKeyboard) {
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
option.value.flags.active = true
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
elementRef,
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { type FormOption, type FormOptionIndex, IK_FORM_SELECT_CONTROLLER } from '@core/packages/form-select/types.ts'
|
|
2
|
+
import { useFormSelectKeyboardNavigation } from '@core/packages/form-select/use-form-select-keyboard-navigation.ts'
|
|
3
|
+
import { ifElse } from '@core/utils/if-else.utils'
|
|
4
|
+
import { type MaybeElement, useFloating } from '@floating-ui/vue'
|
|
5
|
+
import { clamp, onClickOutside, useEventListener, useFocusWithin, whenever } from '@vueuse/core'
|
|
6
|
+
import { logicOr } from '@vueuse/math'
|
|
7
|
+
import { computed, type MaybeRefOrGetter, provide, reactive, type Ref, ref, toValue, watch } from 'vue'
|
|
8
|
+
|
|
9
|
+
export function useFormSelectController<TOption extends FormOption>(config: {
|
|
10
|
+
options: MaybeRefOrGetter<TOption[]>
|
|
11
|
+
searchTerm: Ref<string | undefined>
|
|
12
|
+
}) {
|
|
13
|
+
const options = computed(() => toValue(config.options))
|
|
14
|
+
|
|
15
|
+
const isMultiple = computed(() => options.value[0]?.properties.multiple ?? false)
|
|
16
|
+
|
|
17
|
+
const activeOption = computed(() => options.value.find(option => option.flags.active))
|
|
18
|
+
|
|
19
|
+
const isOpen = ref(false)
|
|
20
|
+
|
|
21
|
+
/* TRIGGER */
|
|
22
|
+
|
|
23
|
+
const triggerRef = ref<HTMLElement>()
|
|
24
|
+
|
|
25
|
+
const { focused: isTriggerFocused } = useFocusWithin(triggerRef)
|
|
26
|
+
|
|
27
|
+
const isActive = logicOr(isTriggerFocused, isOpen)
|
|
28
|
+
|
|
29
|
+
useEventListener(triggerRef, 'click', () => openDropdown())
|
|
30
|
+
|
|
31
|
+
function focusTrigger() {
|
|
32
|
+
triggerRef.value?.focus?.()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* KEYBOARD NAVIGATION */
|
|
36
|
+
|
|
37
|
+
const { isNavigatingWithKeyboard, stopKeyboardNavigation } = useFormSelectKeyboardNavigation({
|
|
38
|
+
isActive,
|
|
39
|
+
isMultiple,
|
|
40
|
+
isOpen,
|
|
41
|
+
onSelect: () => activeOption.value?.toggleFlag('selected', true),
|
|
42
|
+
onToggle: () => activeOption.value?.toggleFlag('selected'),
|
|
43
|
+
onOpen: openDropdown,
|
|
44
|
+
onClose: closeDropdown,
|
|
45
|
+
onMove: index => moveToOptionIndex(index),
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
/* DROPDOWN */
|
|
49
|
+
|
|
50
|
+
const dropdownRef = ref<HTMLElement>()
|
|
51
|
+
|
|
52
|
+
onClickOutside(dropdownRef, () => closeDropdown(true), { ignore: [triggerRef] })
|
|
53
|
+
|
|
54
|
+
useEventListener(dropdownRef, 'mousemove', () => stopKeyboardNavigation())
|
|
55
|
+
|
|
56
|
+
/* DROPDOWN PLACEMENT */
|
|
57
|
+
|
|
58
|
+
const { floatingStyles } = useFloating(triggerRef, dropdownRef, {
|
|
59
|
+
placement: 'bottom-start',
|
|
60
|
+
open: isOpen,
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
/* SEARCH */
|
|
64
|
+
|
|
65
|
+
watch(config.searchTerm, () => stopKeyboardNavigation())
|
|
66
|
+
|
|
67
|
+
const searchRef = ref<MaybeElement<HTMLElement> & { focus?: () => void }>()
|
|
68
|
+
|
|
69
|
+
function focusSearch() {
|
|
70
|
+
if (!searchRef.value?.focus) {
|
|
71
|
+
return false
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
searchRef.value.focus()
|
|
75
|
+
|
|
76
|
+
return true
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
whenever(isOpen, () => focusSearch(), { flush: 'post' })
|
|
80
|
+
|
|
81
|
+
const currentIndex = computed(() => options.value.findIndex(option => option.flags.active))
|
|
82
|
+
|
|
83
|
+
whenever(options, () => moveToOptionIndex('first'), { flush: 'post' })
|
|
84
|
+
|
|
85
|
+
ifElse(isOpen, () => moveToOptionIndex('selected'), clear, { flush: 'post' })
|
|
86
|
+
|
|
87
|
+
function openDropdown() {
|
|
88
|
+
if (!isOpen.value) {
|
|
89
|
+
isOpen.value = true
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function closeDropdown(keepFocus: boolean) {
|
|
94
|
+
if (!isOpen.value) {
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
isOpen.value = false
|
|
99
|
+
|
|
100
|
+
if (keepFocus) {
|
|
101
|
+
focusTrigger()
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function clear() {
|
|
106
|
+
if (config.searchTerm.value !== undefined) {
|
|
107
|
+
config.searchTerm.value = ''
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const boundaryIndexes = computed(() => {
|
|
112
|
+
let firstIndex: number | undefined
|
|
113
|
+
let lastIndex: number | undefined
|
|
114
|
+
|
|
115
|
+
options.value.forEach((option, index) => {
|
|
116
|
+
if (option.properties.disabled) {
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (firstIndex === undefined) {
|
|
121
|
+
firstIndex = index
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
lastIndex = index
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
if (firstIndex === undefined || lastIndex === undefined) {
|
|
128
|
+
return undefined
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
first: firstIndex,
|
|
133
|
+
last: lastIndex,
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
function parseIndex(index: FormOptionIndex): number {
|
|
138
|
+
switch (index) {
|
|
139
|
+
case 'previous':
|
|
140
|
+
return currentIndex.value - 1
|
|
141
|
+
case 'next':
|
|
142
|
+
return currentIndex.value + 1
|
|
143
|
+
case 'previous-page':
|
|
144
|
+
return currentIndex.value - 7 // TODO: Better handle page size.
|
|
145
|
+
case 'next-page':
|
|
146
|
+
return currentIndex.value + 7 // TODO: Better handle page size.
|
|
147
|
+
case 'first':
|
|
148
|
+
return 0
|
|
149
|
+
case 'last':
|
|
150
|
+
return options.value.length - 1
|
|
151
|
+
case 'selected':
|
|
152
|
+
return options.value.findIndex(option => option.flags.selected)
|
|
153
|
+
default:
|
|
154
|
+
return index
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function moveToOptionIndex(_index: FormOptionIndex) {
|
|
159
|
+
if (boundaryIndexes.value === undefined) {
|
|
160
|
+
activeOption.value?.toggleFlag('active', false)
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const index = clamp(parseIndex(_index), boundaryIndexes.value.first, boundaryIndexes.value.last)
|
|
165
|
+
|
|
166
|
+
options.value[getClosestEnabledIndex(index)]?.toggleFlag('active', true)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function getClosestEnabledIndex(expectedIndex: number) {
|
|
170
|
+
let index = expectedIndex
|
|
171
|
+
|
|
172
|
+
const direction = expectedIndex < currentIndex.value ? -1 : 1
|
|
173
|
+
|
|
174
|
+
while (options.value[index]?.properties.disabled) {
|
|
175
|
+
index += direction
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return index
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function focusSearchOrTrigger() {
|
|
182
|
+
if (!focusSearch()) {
|
|
183
|
+
focusTrigger()
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
provide(
|
|
188
|
+
IK_FORM_SELECT_CONTROLLER,
|
|
189
|
+
reactive({
|
|
190
|
+
isNavigatingWithKeyboard,
|
|
191
|
+
focusSearchOrTrigger,
|
|
192
|
+
closeDropdown,
|
|
193
|
+
})
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
triggerRef,
|
|
198
|
+
dropdownRef,
|
|
199
|
+
searchRef,
|
|
200
|
+
openDropdown,
|
|
201
|
+
closeDropdown,
|
|
202
|
+
isOpen,
|
|
203
|
+
floatingStyles,
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { FORM_SELECT_HANDLED_KEY, type FormOptionIndex } from '@core/packages/form-select/types.ts'
|
|
2
|
+
import { ifElse } from '@core/utils/if-else.utils.ts'
|
|
3
|
+
import { onKeyStroke } from '@vueuse/core'
|
|
4
|
+
import { type EffectScope, effectScope, ref, type Ref } from 'vue'
|
|
5
|
+
|
|
6
|
+
export function useFormSelectKeyboardNavigation({
|
|
7
|
+
isActive,
|
|
8
|
+
isOpen,
|
|
9
|
+
isMultiple,
|
|
10
|
+
onOpen,
|
|
11
|
+
onClose,
|
|
12
|
+
onMove,
|
|
13
|
+
onToggle,
|
|
14
|
+
onSelect,
|
|
15
|
+
}: {
|
|
16
|
+
isActive: Ref<boolean>
|
|
17
|
+
isOpen: Ref<boolean>
|
|
18
|
+
isMultiple: Ref<boolean>
|
|
19
|
+
onOpen: () => void
|
|
20
|
+
onClose: (keepFocus: boolean) => void
|
|
21
|
+
onMove: (index: FormOptionIndex) => void
|
|
22
|
+
onToggle: () => void
|
|
23
|
+
onSelect: () => void
|
|
24
|
+
}) {
|
|
25
|
+
const isNavigatingWithKeyboard = ref(false)
|
|
26
|
+
|
|
27
|
+
function stopKeyboardNavigation() {
|
|
28
|
+
if (isNavigatingWithKeyboard.value) {
|
|
29
|
+
isNavigatingWithKeyboard.value = false
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function handleArrowUpDownKeys(event: KeyboardEvent, key: FORM_SELECT_HANDLED_KEY.UP | FORM_SELECT_HANDLED_KEY.DOWN) {
|
|
34
|
+
event.preventDefault()
|
|
35
|
+
|
|
36
|
+
isNavigatingWithKeyboard.value = true
|
|
37
|
+
|
|
38
|
+
if (!isOpen.value) {
|
|
39
|
+
return onOpen()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
onMove(key === FORM_SELECT_HANDLED_KEY.UP ? 'previous' : 'next')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function handlePageUpDownKeys(
|
|
46
|
+
event: KeyboardEvent,
|
|
47
|
+
key: FORM_SELECT_HANDLED_KEY.PAGE_UP | FORM_SELECT_HANDLED_KEY.PAGE_DOWN
|
|
48
|
+
) {
|
|
49
|
+
event.preventDefault()
|
|
50
|
+
|
|
51
|
+
isNavigatingWithKeyboard.value = true
|
|
52
|
+
|
|
53
|
+
if (!isOpen.value) {
|
|
54
|
+
return onOpen()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
onMove(key === FORM_SELECT_HANDLED_KEY.PAGE_UP ? 'previous-page' : 'next-page')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function handleHomeEndKeys(event: KeyboardEvent, key: FORM_SELECT_HANDLED_KEY.HOME | FORM_SELECT_HANDLED_KEY.END) {
|
|
61
|
+
if (!isOpen.value) {
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
event.preventDefault()
|
|
66
|
+
|
|
67
|
+
isNavigatingWithKeyboard.value = true
|
|
68
|
+
|
|
69
|
+
onMove(key === FORM_SELECT_HANDLED_KEY.HOME ? 'first' : 'last')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function handleLeftRightKeys() {
|
|
73
|
+
isNavigatingWithKeyboard.value = false
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function handleEnterSpaceKeys(
|
|
77
|
+
event: KeyboardEvent,
|
|
78
|
+
key: FORM_SELECT_HANDLED_KEY.ENTER | FORM_SELECT_HANDLED_KEY.SPACE
|
|
79
|
+
) {
|
|
80
|
+
if (key === FORM_SELECT_HANDLED_KEY.SPACE && !isNavigatingWithKeyboard.value) {
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
event.preventDefault()
|
|
85
|
+
|
|
86
|
+
if (!isOpen.value) {
|
|
87
|
+
return onOpen()
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (isMultiple.value) {
|
|
91
|
+
onToggle()
|
|
92
|
+
} else {
|
|
93
|
+
onSelect()
|
|
94
|
+
onClose(true)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function handleTabEscapeKeys(
|
|
99
|
+
event: KeyboardEvent,
|
|
100
|
+
key: FORM_SELECT_HANDLED_KEY.TAB | FORM_SELECT_HANDLED_KEY.ESCAPE
|
|
101
|
+
) {
|
|
102
|
+
if (key === FORM_SELECT_HANDLED_KEY.ESCAPE) {
|
|
103
|
+
event.preventDefault()
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
onClose(key === FORM_SELECT_HANDLED_KEY.ESCAPE)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const handledKeys = Object.values(FORM_SELECT_HANDLED_KEY)
|
|
110
|
+
|
|
111
|
+
let scope: EffectScope | undefined
|
|
112
|
+
|
|
113
|
+
function registerEvents() {
|
|
114
|
+
scope = effectScope()
|
|
115
|
+
|
|
116
|
+
scope.run(() =>
|
|
117
|
+
onKeyStroke(handledKeys, (event): void => {
|
|
118
|
+
const key = event.key as FORM_SELECT_HANDLED_KEY
|
|
119
|
+
|
|
120
|
+
switch (key) {
|
|
121
|
+
case FORM_SELECT_HANDLED_KEY.DOWN:
|
|
122
|
+
case FORM_SELECT_HANDLED_KEY.UP:
|
|
123
|
+
return handleArrowUpDownKeys(event, key)
|
|
124
|
+
case FORM_SELECT_HANDLED_KEY.PAGE_DOWN:
|
|
125
|
+
case FORM_SELECT_HANDLED_KEY.PAGE_UP:
|
|
126
|
+
return handlePageUpDownKeys(event, key)
|
|
127
|
+
case FORM_SELECT_HANDLED_KEY.HOME:
|
|
128
|
+
case FORM_SELECT_HANDLED_KEY.END:
|
|
129
|
+
return handleHomeEndKeys(event, key)
|
|
130
|
+
case FORM_SELECT_HANDLED_KEY.ENTER:
|
|
131
|
+
case FORM_SELECT_HANDLED_KEY.SPACE:
|
|
132
|
+
return handleEnterSpaceKeys(event, key)
|
|
133
|
+
case FORM_SELECT_HANDLED_KEY.LEFT:
|
|
134
|
+
case FORM_SELECT_HANDLED_KEY.RIGHT:
|
|
135
|
+
return handleLeftRightKeys()
|
|
136
|
+
case FORM_SELECT_HANDLED_KEY.TAB:
|
|
137
|
+
case FORM_SELECT_HANDLED_KEY.ESCAPE:
|
|
138
|
+
return handleTabEscapeKeys(event, key)
|
|
139
|
+
default:
|
|
140
|
+
return key
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function unregisterEvents() {
|
|
147
|
+
scope?.stop()
|
|
148
|
+
scope = undefined
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
ifElse(isActive, registerEvents, unregisterEvents)
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
isNavigatingWithKeyboard,
|
|
155
|
+
stopKeyboardNavigation,
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { useCollection } from '@core/packages/collection'
|
|
2
|
+
import type {
|
|
3
|
+
FormOptionProperties,
|
|
4
|
+
FormOptionValue,
|
|
5
|
+
FormSelectBaseConfig,
|
|
6
|
+
FormSelectBaseProperties,
|
|
7
|
+
UseFormSelectReturn,
|
|
8
|
+
} from '@core/packages/form-select/types.ts'
|
|
9
|
+
import { toArray } from '@core/utils/to-array.utils.ts'
|
|
10
|
+
import type { MaybeRefOrGetter } from '@vueuse/shared'
|
|
11
|
+
import { computed, reactive, ref } from 'vue'
|
|
12
|
+
|
|
13
|
+
export function useFormSelect<
|
|
14
|
+
TSource extends { value: FormOptionValue; label: string },
|
|
15
|
+
TValue extends FormOptionValue = TSource['value'],
|
|
16
|
+
TProperties extends FormSelectBaseProperties & {
|
|
17
|
+
value?: TValue
|
|
18
|
+
label?: string
|
|
19
|
+
} = FormSelectBaseProperties & {
|
|
20
|
+
value?: TValue
|
|
21
|
+
label?: string
|
|
22
|
+
},
|
|
23
|
+
>(
|
|
24
|
+
sources: MaybeRefOrGetter<TSource[]>,
|
|
25
|
+
config?: FormSelectBaseConfig & {
|
|
26
|
+
properties?: (source: TSource) => TProperties
|
|
27
|
+
}
|
|
28
|
+
): UseFormSelectReturn<TSource, TValue, TProperties>
|
|
29
|
+
|
|
30
|
+
export function useFormSelect<
|
|
31
|
+
TSource extends { id: FormOptionValue; label: string },
|
|
32
|
+
TValue extends FormOptionValue = TSource['id'],
|
|
33
|
+
TProperties extends FormSelectBaseProperties & {
|
|
34
|
+
value?: TValue
|
|
35
|
+
label?: string
|
|
36
|
+
} = FormSelectBaseProperties & {
|
|
37
|
+
value?: TValue
|
|
38
|
+
label?: string
|
|
39
|
+
},
|
|
40
|
+
>(
|
|
41
|
+
sources: MaybeRefOrGetter<TSource[]>,
|
|
42
|
+
config?: FormSelectBaseConfig & {
|
|
43
|
+
properties?: (source: TSource) => TProperties
|
|
44
|
+
}
|
|
45
|
+
): UseFormSelectReturn<TSource, TValue, TProperties>
|
|
46
|
+
|
|
47
|
+
export function useFormSelect<
|
|
48
|
+
TSource extends { value: FormOptionValue },
|
|
49
|
+
TValue extends FormOptionValue = TSource['value'],
|
|
50
|
+
TProperties extends FormSelectBaseProperties & {
|
|
51
|
+
value?: TValue
|
|
52
|
+
label: string
|
|
53
|
+
} = FormSelectBaseProperties & {
|
|
54
|
+
value?: TValue
|
|
55
|
+
label: string
|
|
56
|
+
},
|
|
57
|
+
>(
|
|
58
|
+
sources: MaybeRefOrGetter<TSource[]>,
|
|
59
|
+
config: FormSelectBaseConfig & {
|
|
60
|
+
properties: (source: TSource) => TProperties
|
|
61
|
+
}
|
|
62
|
+
): UseFormSelectReturn<TSource, TValue, TProperties>
|
|
63
|
+
|
|
64
|
+
export function useFormSelect<
|
|
65
|
+
TSource extends { id: FormOptionValue },
|
|
66
|
+
TValue extends FormOptionValue = TSource['id'],
|
|
67
|
+
TProperties extends FormSelectBaseProperties & {
|
|
68
|
+
value?: TValue
|
|
69
|
+
label: string
|
|
70
|
+
} = FormSelectBaseProperties & {
|
|
71
|
+
value?: TValue
|
|
72
|
+
label: string
|
|
73
|
+
},
|
|
74
|
+
>(
|
|
75
|
+
sources: MaybeRefOrGetter<TSource[]>,
|
|
76
|
+
config: FormSelectBaseConfig & {
|
|
77
|
+
properties: (source: TSource) => TProperties
|
|
78
|
+
}
|
|
79
|
+
): UseFormSelectReturn<TSource, TValue, TProperties>
|
|
80
|
+
|
|
81
|
+
export function useFormSelect<
|
|
82
|
+
TSource extends { label: string },
|
|
83
|
+
TValue extends FormOptionValue,
|
|
84
|
+
TProperties extends FormSelectBaseProperties & {
|
|
85
|
+
value: TValue
|
|
86
|
+
label?: string
|
|
87
|
+
} = FormSelectBaseProperties & {
|
|
88
|
+
value: TValue
|
|
89
|
+
label?: string
|
|
90
|
+
},
|
|
91
|
+
>(
|
|
92
|
+
sources: MaybeRefOrGetter<TSource[]>,
|
|
93
|
+
config: FormSelectBaseConfig & {
|
|
94
|
+
properties: (source: TSource) => TProperties
|
|
95
|
+
}
|
|
96
|
+
): UseFormSelectReturn<TSource, TValue, TProperties>
|
|
97
|
+
|
|
98
|
+
export function useFormSelect<
|
|
99
|
+
TSource,
|
|
100
|
+
TValue extends FormOptionValue,
|
|
101
|
+
TProperties extends FormSelectBaseProperties & {
|
|
102
|
+
value: TValue
|
|
103
|
+
label: string
|
|
104
|
+
} = FormSelectBaseProperties & {
|
|
105
|
+
value: TValue
|
|
106
|
+
label: string
|
|
107
|
+
},
|
|
108
|
+
>(
|
|
109
|
+
sources: MaybeRefOrGetter<TSource[]>,
|
|
110
|
+
config: FormSelectBaseConfig & {
|
|
111
|
+
properties: (source: TSource) => TProperties
|
|
112
|
+
}
|
|
113
|
+
): UseFormSelectReturn<TSource, TValue, TProperties>
|
|
114
|
+
|
|
115
|
+
export function useFormSelect<
|
|
116
|
+
TSource,
|
|
117
|
+
TProperties extends FormSelectBaseProperties & {
|
|
118
|
+
value?: FormOptionValue
|
|
119
|
+
label?: string
|
|
120
|
+
},
|
|
121
|
+
>(
|
|
122
|
+
sources: MaybeRefOrGetter<TSource[]>,
|
|
123
|
+
config?: FormSelectBaseConfig & {
|
|
124
|
+
properties?: (source: TSource) => TProperties
|
|
125
|
+
}
|
|
126
|
+
) {
|
|
127
|
+
const _searchTerm = ref('')
|
|
128
|
+
|
|
129
|
+
const searchTerm = computed({
|
|
130
|
+
get: () => _searchTerm.value,
|
|
131
|
+
set: (value: string) => {
|
|
132
|
+
_searchTerm.value = value.toLowerCase().trim()
|
|
133
|
+
},
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
const {
|
|
137
|
+
items: allOptions,
|
|
138
|
+
useFlag,
|
|
139
|
+
useSubset,
|
|
140
|
+
} = useCollection(sources, {
|
|
141
|
+
flags: {
|
|
142
|
+
active: { multiple: false },
|
|
143
|
+
selected: { multiple: config?.multiple ?? false },
|
|
144
|
+
},
|
|
145
|
+
properties: (source: TSource) => {
|
|
146
|
+
const {
|
|
147
|
+
value = (source as { value: FormOptionValue }).value ?? (source as { id: FormOptionValue }).id,
|
|
148
|
+
label = (source as { label: string }).label,
|
|
149
|
+
disabled = false,
|
|
150
|
+
searchableTerm,
|
|
151
|
+
...extraProperties
|
|
152
|
+
} = config?.properties?.(source) ?? {}
|
|
153
|
+
|
|
154
|
+
const matching = computed(() => {
|
|
155
|
+
if (!searchTerm.value) {
|
|
156
|
+
return true
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const searchableTerms = toArray(searchableTerm ?? label)
|
|
160
|
+
|
|
161
|
+
return searchableTerms.some(term => term.toLowerCase().includes(searchTerm.value))
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
return reactive({
|
|
165
|
+
id: value,
|
|
166
|
+
label,
|
|
167
|
+
multiple: config?.multiple ?? false,
|
|
168
|
+
disabled,
|
|
169
|
+
matching,
|
|
170
|
+
...extraProperties,
|
|
171
|
+
}) satisfies FormOptionProperties<FormOptionValue>
|
|
172
|
+
},
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
const { items: options } = useSubset(option => option.properties.matching)
|
|
176
|
+
|
|
177
|
+
const { items: selectedOptions, ids: selectedValues } = useFlag('selected')
|
|
178
|
+
|
|
179
|
+
const selectedLabels = computed(() => selectedOptions.value.map(option => option.properties.label))
|
|
180
|
+
|
|
181
|
+
const selectedLabel = computed(
|
|
182
|
+
() => config?.selectedLabel?.(selectedLabels.value.length, selectedLabels.value) ?? selectedLabels.value.join(', ')
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
searchTerm,
|
|
187
|
+
allOptions,
|
|
188
|
+
options,
|
|
189
|
+
selectedOptions,
|
|
190
|
+
selectedValues,
|
|
191
|
+
selectedLabel,
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -2,7 +2,7 @@ import { useUiStore } from '@core/stores/ui.store'
|
|
|
2
2
|
import { ifElse } from '@core/utils/if-else.utils'
|
|
3
3
|
import { useLocalStorage, useRafFn, useStyleTag, useToggle } from '@vueuse/core'
|
|
4
4
|
import { defineStore } from 'pinia'
|
|
5
|
-
import { computed, ref } from 'vue'
|
|
5
|
+
import { computed, ref, watch } from 'vue'
|
|
6
6
|
|
|
7
7
|
export const useSidebarStore = defineStore('layout', () => {
|
|
8
8
|
const uiStore = useUiStore()
|
|
@@ -12,6 +12,7 @@ export const useSidebarStore = defineStore('layout', () => {
|
|
|
12
12
|
const toggleExpand = useToggle(isExpanded)
|
|
13
13
|
const toggleLock = useToggle(isLocked)
|
|
14
14
|
const isResizing = ref(false)
|
|
15
|
+
let desktopState = false
|
|
15
16
|
|
|
16
17
|
let initialX: number
|
|
17
18
|
let initialWidth: number
|
|
@@ -49,6 +50,18 @@ export const useSidebarStore = defineStore('layout', () => {
|
|
|
49
50
|
|
|
50
51
|
ifElse(isResizing, [load, resume], [pause, unload])
|
|
51
52
|
|
|
53
|
+
watch(
|
|
54
|
+
() => uiStore.isMobile,
|
|
55
|
+
isMobile => {
|
|
56
|
+
// keep the state of desktop expention
|
|
57
|
+
if (isMobile) {
|
|
58
|
+
desktopState = isExpanded.value
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
isExpanded.value = desktopState && !isMobile
|
|
62
|
+
}
|
|
63
|
+
)
|
|
64
|
+
|
|
52
65
|
return {
|
|
53
66
|
isExpanded,
|
|
54
67
|
isLocked,
|
package/package.json
CHANGED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import type { CollectionItem, CollectionOptions, FlagRegistry } from '@core/packages/collection'
|
|
2
|
-
import { unref, type UnwrapRef } from 'vue'
|
|
3
|
-
|
|
4
|
-
export function buildItem<
|
|
5
|
-
TSource,
|
|
6
|
-
TId extends PropertyKey,
|
|
7
|
-
TFlag extends string,
|
|
8
|
-
TProperties extends Record<string, unknown>,
|
|
9
|
-
>(
|
|
10
|
-
source: TSource,
|
|
11
|
-
options: CollectionOptions<TSource, TId, TFlag, TProperties>,
|
|
12
|
-
flagRegistry: FlagRegistry<TFlag>
|
|
13
|
-
): CollectionItem<TSource, TId, TFlag, UnwrapRef<TProperties>> {
|
|
14
|
-
const id = options.identifier(source)
|
|
15
|
-
const properties = options.properties?.(source)
|
|
16
|
-
|
|
17
|
-
return {
|
|
18
|
-
id,
|
|
19
|
-
source,
|
|
20
|
-
toggleFlag(flag: TFlag, forcedValue?: boolean) {
|
|
21
|
-
flagRegistry.toggleFlag(id, flag, forcedValue)
|
|
22
|
-
},
|
|
23
|
-
flags: new Proxy({} as Record<TFlag, boolean>, {
|
|
24
|
-
has(target, flag: TFlag) {
|
|
25
|
-
return flagRegistry.isFlagDefined(flag)
|
|
26
|
-
},
|
|
27
|
-
get(target, flag: TFlag) {
|
|
28
|
-
return flagRegistry.isFlagged(id, flag)
|
|
29
|
-
},
|
|
30
|
-
set(target, flag: TFlag, value) {
|
|
31
|
-
flagRegistry.toggleFlag(id, flag, value)
|
|
32
|
-
|
|
33
|
-
return true
|
|
34
|
-
},
|
|
35
|
-
}),
|
|
36
|
-
properties: new Proxy({} as UnwrapRef<TProperties>, {
|
|
37
|
-
has(target, prop: Extract<keyof TProperties, string>) {
|
|
38
|
-
return properties !== undefined && prop in properties
|
|
39
|
-
},
|
|
40
|
-
get(target, prop: Extract<keyof TProperties, string>) {
|
|
41
|
-
return unref(properties?.[prop])
|
|
42
|
-
},
|
|
43
|
-
}),
|
|
44
|
-
}
|
|
45
|
-
}
|