@xen-orchestra/web-core 0.18.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.
Files changed (50) hide show
  1. package/lib/components/backdrop/VtsBackdrop.vue +1 -1
  2. package/lib/components/column/VtsColumn.vue +21 -0
  3. package/lib/components/columns/VtsColumns.vue +38 -0
  4. package/lib/components/copy-button/VtsCopyButton.vue +29 -0
  5. package/lib/components/enabled-state/VtsEnabledState.vue +23 -0
  6. package/lib/components/icon/VtsIcon.vue +9 -1
  7. package/lib/components/input-wrapper/VtsInputWrapper.vue +1 -0
  8. package/lib/components/layout/VtsLayoutSidebar.vue +1 -1
  9. package/lib/components/quick-info-column/VtsQuickInfoColumn.vue +1 -1
  10. package/lib/components/quick-info-row/VtsQuickInfoRow.vue +26 -7
  11. package/lib/components/relative-time/VtsRelativeTime.vue +18 -0
  12. package/lib/components/select/VtsOption.vue +24 -0
  13. package/lib/components/select/VtsSelect.vue +96 -0
  14. package/lib/components/state-hero/VtsLoadingHero.vue +45 -4
  15. package/lib/components/tree/VtsTreeItem.vue +11 -1
  16. package/lib/components/ui/alert/UiAlert.vue +105 -0
  17. package/lib/components/ui/circle-progress-bar/UiCircleProgressBar.vue +212 -0
  18. package/lib/components/ui/dropdown/UiDropdownList.vue +10 -2
  19. package/lib/components/ui/head-bar/UiHeadBar.vue +2 -2
  20. package/lib/components/ui/info/UiInfo.vue +5 -3
  21. package/lib/components/ui/input/UiInput.vue +126 -109
  22. package/lib/composables/chart-theme.composable.ts +3 -3
  23. package/lib/composables/relative-time.composable.ts +1 -1
  24. package/lib/i18n.ts +4 -0
  25. package/lib/locales/cs.json +65 -18
  26. package/lib/locales/en.json +65 -1
  27. package/lib/locales/es.json +60 -13
  28. package/lib/locales/fa.json +59 -12
  29. package/lib/locales/fr.json +67 -3
  30. package/lib/locales/it.json +145 -7
  31. package/lib/locales/nl.json +502 -0
  32. package/lib/locales/ru.json +91 -1
  33. package/lib/locales/sv.json +75 -19
  34. package/lib/packages/collection/README.md +172 -0
  35. package/lib/packages/collection/create-collection.ts +74 -0
  36. package/lib/packages/collection/create-item.ts +39 -0
  37. package/lib/packages/collection/guess-item-id.ts +26 -0
  38. package/lib/packages/collection/index.ts +2 -0
  39. package/lib/packages/collection/types.ts +57 -0
  40. package/lib/packages/collection/use-collection.ts +47 -0
  41. package/lib/packages/collection/use-flag-registry.ts +64 -0
  42. package/lib/packages/form-select/README.md +96 -0
  43. package/lib/packages/form-select/index.ts +2 -0
  44. package/lib/packages/form-select/types.ts +75 -0
  45. package/lib/packages/form-select/use-form-option-controller.ts +50 -0
  46. package/lib/packages/form-select/use-form-select-controller.ts +205 -0
  47. package/lib/packages/form-select/use-form-select-keyboard-navigation.ts +157 -0
  48. package/lib/packages/form-select/use-form-select.ts +193 -0
  49. package/lib/stores/sidebar.store.ts +14 -1
  50. package/package.json +1 -1
@@ -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,7 +1,7 @@
1
1
  {
2
2
  "name": "@xen-orchestra/web-core",
3
3
  "type": "module",
4
- "version": "0.18.0",
4
+ "version": "0.20.0",
5
5
  "private": false,
6
6
  "exports": {
7
7
  "./*": {