@xen-orchestra/web-core 0.20.0 → 0.21.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 (118) hide show
  1. package/lib/assets/all-done.svg +62 -0
  2. package/lib/assets/all-good.svg +113 -0
  3. package/lib/assets/error.svg +57 -372
  4. package/lib/assets/no-data.svg +190 -65
  5. package/lib/assets/not-found.svg +446 -126
  6. package/lib/assets/offline.svg +118 -0
  7. package/lib/assets/under-construction.svg +245 -193
  8. package/lib/assets/zoom.svg +85 -0
  9. package/lib/components/backup-state/VtsBackupState.vue +20 -17
  10. package/lib/components/cell-object/VtsCellObject.vue +4 -1
  11. package/lib/components/console/VtsActionsConsole.vue +7 -4
  12. package/lib/components/console/VtsClipboardConsole.vue +9 -6
  13. package/lib/components/copy-button/VtsCopyButton.vue +7 -14
  14. package/lib/components/dropdown/DropdownTitle.vue +5 -2
  15. package/lib/components/icon/NewVtsIcon.vue +49 -0
  16. package/lib/components/input-group/VtsInputGroup.vue +41 -0
  17. package/lib/components/input-wrapper/VtsInputWrapper.vue +2 -2
  18. package/lib/components/layout/VtsLayoutSidebar.vue +6 -3
  19. package/lib/components/linear-chart/VtsLinearChart.vue +4 -0
  20. package/lib/components/object-icon/VtsObjectIcon.vue +22 -0
  21. package/lib/components/quick-info-card/VtsQuickInfoCard.vue +4 -1
  22. package/lib/components/select/VtsOption.vue +10 -6
  23. package/lib/components/select/VtsSelect.vue +74 -50
  24. package/lib/components/state-hero/VtsAllDoneHero.vue +16 -0
  25. package/lib/components/state-hero/VtsAllGoodHero.vue +16 -0
  26. package/lib/components/state-hero/VtsComingSoonHero.vue +4 -1
  27. package/lib/components/state-hero/VtsErrorNoDataHero.vue +4 -1
  28. package/lib/components/state-hero/VtsLoadingHero.vue +4 -1
  29. package/lib/components/state-hero/VtsNoDataHero.vue +4 -1
  30. package/lib/components/state-hero/VtsNoSelectionHero.vue +4 -1
  31. package/lib/components/state-hero/VtsObjectNotFoundHero.vue +4 -1
  32. package/lib/components/state-hero/VtsOfflineHero.vue +16 -0
  33. package/lib/components/state-hero/VtsPageNotFoundHero.vue +4 -1
  34. package/lib/components/state-hero/VtsStateHero.vue +10 -1
  35. package/lib/components/table/ColumnTitle.vue +2 -2
  36. package/lib/components/task/VtsQuickTaskButton.vue +4 -1
  37. package/lib/components/task/VtsQuickTaskList.vue +5 -2
  38. package/lib/components/task/VtsQuickTaskTabBar.vue +8 -5
  39. package/lib/components/ui/card-numbers/UiCardNumbers.vue +15 -33
  40. package/lib/components/ui/character-limit/UiCharacterLimit.vue +4 -1
  41. package/lib/components/ui/input/UiInput.vue +2 -2
  42. package/lib/components/ui/label/UiLabel.vue +4 -1
  43. package/lib/components/ui/progress-bar/UiProgressBar.vue +5 -2
  44. package/lib/components/ui/query-search-bar/UiQuerySearchBar.vue +9 -6
  45. package/lib/components/ui/quick-task-item/UiQuickTaskItem.vue +6 -3
  46. package/lib/components/ui/stacked-bar/StackedBarSegment.vue +4 -1
  47. package/lib/components/ui/table-pagination/UiTablePagination.vue +6 -3
  48. package/lib/components/ui/text-area/UiTextarea.vue +4 -1
  49. package/lib/components/ui/top-bottom-table/UiTopBottomTable.vue +6 -3
  50. package/lib/components/ui/tree-item-label/UiTreeItemLabel.vue +4 -1
  51. package/lib/composables/local-time-ago.composable.ts +53 -0
  52. package/lib/composables/locale-time-ago.composable.ts +53 -0
  53. package/lib/icons/fa-icons.ts +164 -0
  54. package/lib/icons/index.ts +15 -0
  55. package/lib/icons/legacy-icons.ts +80 -0
  56. package/lib/icons/object-icons.ts +187 -0
  57. package/lib/layouts/CoreLayout.vue +7 -3
  58. package/lib/locales/cs.json +73 -7
  59. package/lib/locales/de.json +5 -1
  60. package/lib/locales/en.json +33 -3
  61. package/lib/locales/es.json +9 -5
  62. package/lib/locales/fr.json +32 -2
  63. package/lib/locales/it.json +1 -1
  64. package/lib/locales/nl.json +51 -9
  65. package/lib/locales/ru.json +28 -1
  66. package/lib/locales/sv.json +77 -13
  67. package/lib/packages/collection/README.md +23 -18
  68. package/lib/packages/collection/create-collection.ts +22 -21
  69. package/lib/packages/collection/create-item.ts +21 -20
  70. package/lib/packages/collection/create-use-subset.ts +23 -0
  71. package/lib/packages/collection/guess-item-id.ts +26 -16
  72. package/lib/packages/collection/index.ts +4 -0
  73. package/lib/packages/collection/types.ts +65 -37
  74. package/lib/packages/collection/use-collection.ts +68 -18
  75. package/lib/packages/collection/use-flag-registry.ts +38 -17
  76. package/lib/packages/form-select/guess-label.ts +45 -0
  77. package/lib/packages/form-select/guess-value.ts +23 -0
  78. package/lib/packages/form-select/index.ts +6 -0
  79. package/lib/packages/form-select/normalize-search-term.ts +11 -0
  80. package/lib/packages/form-select/types.ts +90 -42
  81. package/lib/packages/form-select/use-form-option-controller.ts +7 -3
  82. package/lib/packages/form-select/use-form-select-controller.ts +38 -27
  83. package/lib/packages/form-select/use-form-select-keyboard-navigation.ts +1 -1
  84. package/lib/packages/form-select/use-form-select.ts +308 -130
  85. package/lib/packages/icon/DisplayIcon.vue +25 -0
  86. package/lib/packages/icon/DisplayIconAny.vue +16 -0
  87. package/lib/packages/icon/DisplayIconSingle.vue +35 -0
  88. package/lib/packages/icon/DisplayIconStack.vue +34 -0
  89. package/lib/packages/icon/README.md +286 -0
  90. package/lib/packages/icon/create-icon-bindings.ts +27 -0
  91. package/lib/packages/icon/define-icon-pack.ts +23 -0
  92. package/lib/packages/icon/define-icon-single.ts +17 -0
  93. package/lib/packages/icon/define-icon-stack.ts +20 -0
  94. package/lib/packages/icon/define-icon.ts +40 -0
  95. package/lib/packages/icon/generate-icon-variants.ts +17 -0
  96. package/lib/packages/icon/index.ts +8 -0
  97. package/lib/packages/icon/is-icon-stack.ts +5 -0
  98. package/lib/packages/icon/merge-icons.ts +25 -0
  99. package/lib/packages/icon/merge-transforms.ts +12 -0
  100. package/lib/packages/icon/normalize-icon.ts +25 -0
  101. package/lib/packages/icon/to-tuple.ts +7 -0
  102. package/lib/packages/icon/types.ts +72 -0
  103. package/lib/packages/job/README.md +2 -2
  104. package/lib/packages/mapper/README.md +166 -0
  105. package/lib/packages/mapper/convert-to-map.ts +5 -0
  106. package/lib/packages/mapper/create-mapper.ts +30 -0
  107. package/lib/packages/mapper/index.ts +4 -0
  108. package/lib/packages/mapper/types.ts +1 -0
  109. package/lib/packages/mapper/use-mapper.ts +31 -0
  110. package/lib/stores/sidebar.store.ts +1 -1
  111. package/lib/types/chart.ts +2 -2
  112. package/lib/types/utility.type.ts +9 -0
  113. package/lib/utils/object.util.ts +16 -0
  114. package/lib/utils/size.util.ts +4 -2
  115. package/package.json +2 -1
  116. package/lib/components/backup-item/VtsBackupItem.vue +0 -47
  117. package/lib/composables/mapper.composable.md +0 -74
  118. package/lib/composables/mapper.composable.ts +0 -18
@@ -1,193 +1,371 @@
1
- import { useCollection } from '@core/packages/collection'
1
+ import {
2
+ type CollectionItemId,
3
+ type CollectionItemProperties,
4
+ type GetItemId,
5
+ guessItemId,
6
+ useCollection,
7
+ } from '@core/packages/collection'
8
+ import type { EmptyObject, MaybeArray } from '@core/types/utility.type.ts'
9
+ import { toArray } from '@core/utils/to-array.utils.ts'
2
10
  import type {
3
- FormOptionProperties,
4
- FormOptionValue,
5
- FormSelectBaseConfig,
6
- FormSelectBaseProperties,
11
+ ExtractValue,
12
+ FormOptionCollectionItemProperties,
13
+ FormSelect,
14
+ FormSelectId,
15
+ GetOptionLabel,
16
+ GetOptionValue,
7
17
  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'
18
+ } from './types.ts'
19
+ import { computed, type ComputedRef, type MaybeRefOrGetter, provide, ref, type Ref, toValue, watch } from 'vue'
20
+ import { guessLabel } from './guess-label.ts'
21
+ import { guessValue } from './guess-value.ts'
22
+ import { normalizeSearchTerm } from './normalize-search-term.ts'
12
23
 
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>
24
+ // Overload #1: Source is CollectionItemId
29
25
 
30
26
  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
- },
27
+ TBaseSource extends CollectionItemId,
28
+ TAllowEmpty extends boolean = false,
29
+ TCustomProperties extends CollectionItemProperties = EmptyObject,
30
+ TGetValue extends GetOptionValue<$TSource, TCustomProperties> = undefined,
31
+ TMultiple extends boolean = false,
32
+ $TSource = TAllowEmpty extends true ? TBaseSource | undefined : TBaseSource,
33
+ $TValue = ExtractValue<$TSource, TGetValue>,
40
34
  >(
41
- sources: MaybeRefOrGetter<TSource[]>,
42
- config?: FormSelectBaseConfig & {
43
- properties?: (source: TSource) => TProperties
35
+ sources: MaybeRefOrGetter<TBaseSource[]>,
36
+ config?: {
37
+ allowEmpty?: MaybeRefOrGetter<TAllowEmpty>
38
+ multiple?: MaybeRefOrGetter<TMultiple>
39
+ model?: Ref<unknown>
40
+ disabled?: MaybeRefOrGetter<boolean>
41
+ selectedLabel?: (count: number, labels: string[]) => string | undefined
42
+ placeholder?: MaybeRefOrGetter<string>
43
+ searchPlaceholder?: MaybeRefOrGetter<string>
44
+ loading?: MaybeRefOrGetter<boolean>
45
+ required?: MaybeRefOrGetter<boolean>
46
+ searchable?: MaybeRefOrGetter<boolean>
47
+ option?: {
48
+ id?: GetItemId<TBaseSource>
49
+ value?: TGetValue | ((source: $TSource, properties: TCustomProperties) => $TValue)
50
+ properties?: (source: $TSource) => TCustomProperties
51
+ label?: GetOptionLabel<$TSource, TCustomProperties>
52
+ selectedLabel?: (source: $TSource, properties: TCustomProperties) => string
53
+ disabled?: (source: $TSource, properties: TCustomProperties) => boolean
54
+ searchableTerm?: (source: $TSource, properties: TCustomProperties) => MaybeArray<string>
55
+ }
44
56
  }
45
- ): UseFormSelectReturn<TSource, TValue, TProperties>
57
+ ): UseFormSelectReturn<TCustomProperties, $TSource, $TValue, TMultiple>
58
+
59
+ // Overload #2: Source is an object with id and label
46
60
 
47
61
  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
- },
62
+ TBaseSource extends { id: CollectionItemId; label: string },
63
+ TAllowEmpty extends boolean = false,
64
+ TCustomProperties extends CollectionItemProperties = EmptyObject,
65
+ TGetValue extends GetOptionValue<$TSource, TCustomProperties> = undefined,
66
+ TMultiple extends boolean = false,
67
+ $TSource = TAllowEmpty extends true ? TBaseSource | undefined : TBaseSource,
68
+ $TValue = ExtractValue<$TSource, TGetValue>,
57
69
  >(
58
- sources: MaybeRefOrGetter<TSource[]>,
59
- config: FormSelectBaseConfig & {
60
- properties: (source: TSource) => TProperties
70
+ sources: MaybeRefOrGetter<TBaseSource[]>,
71
+ config?: {
72
+ allowEmpty?: MaybeRefOrGetter<TAllowEmpty>
73
+ multiple?: MaybeRefOrGetter<TMultiple>
74
+ model?: Ref<unknown>
75
+ disabled?: MaybeRefOrGetter<boolean>
76
+ selectedLabel?: (count: number, labels: string[]) => string | undefined
77
+ placeholder?: MaybeRefOrGetter<string>
78
+ searchPlaceholder?: MaybeRefOrGetter<string>
79
+ loading?: MaybeRefOrGetter<boolean>
80
+ required?: MaybeRefOrGetter<boolean>
81
+ searchable?: MaybeRefOrGetter<boolean>
82
+ option?: {
83
+ id?: GetItemId<TBaseSource>
84
+ value?: TGetValue | ((source: $TSource, properties: TCustomProperties) => $TValue)
85
+ properties?: (source: $TSource) => TCustomProperties
86
+ label?: GetOptionLabel<$TSource, TCustomProperties>
87
+ selectedLabel?: (source: $TSource, properties: TCustomProperties) => string
88
+ disabled?: (source: $TSource, properties: TCustomProperties) => boolean
89
+ searchableTerm?: (source: $TSource, properties: TCustomProperties) => MaybeArray<string>
90
+ }
61
91
  }
62
- ): UseFormSelectReturn<TSource, TValue, TProperties>
92
+ ): UseFormSelectReturn<TCustomProperties, $TSource, $TValue, TMultiple>
93
+
94
+ // Overload #3: Source is an object with id only
63
95
 
64
96
  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
- },
97
+ TBaseSource extends { id: CollectionItemId },
98
+ TAllowEmpty extends boolean = false,
99
+ TCustomProperties extends CollectionItemProperties = EmptyObject,
100
+ TGetValue extends GetOptionValue<$TSource, TCustomProperties> = undefined,
101
+ TMultiple extends boolean = false,
102
+ $TSource = TAllowEmpty extends true ? TBaseSource | undefined : TBaseSource,
103
+ $TValue = ExtractValue<$TSource, TGetValue>,
74
104
  >(
75
- sources: MaybeRefOrGetter<TSource[]>,
76
- config: FormSelectBaseConfig & {
77
- properties: (source: TSource) => TProperties
105
+ sources: MaybeRefOrGetter<TBaseSource[]>,
106
+ config: {
107
+ allowEmpty?: MaybeRefOrGetter<TAllowEmpty>
108
+ multiple?: MaybeRefOrGetter<TMultiple>
109
+ model?: Ref<unknown>
110
+ disabled?: MaybeRefOrGetter<boolean>
111
+ selectedLabel?: (count: number, labels: string[]) => string | undefined
112
+ placeholder?: MaybeRefOrGetter<string>
113
+ searchPlaceholder?: MaybeRefOrGetter<string>
114
+ loading?: MaybeRefOrGetter<boolean>
115
+ required?: MaybeRefOrGetter<boolean>
116
+ searchable?: MaybeRefOrGetter<boolean>
117
+ option: {
118
+ id?: GetItemId<TBaseSource>
119
+ value?: TGetValue | ((source: $TSource, properties: TCustomProperties) => $TValue)
120
+ properties?: (source: $TSource) => TCustomProperties
121
+ label: GetOptionLabel<$TSource, TCustomProperties>
122
+ selectedLabel?: (source: $TSource, properties: TCustomProperties) => string
123
+ disabled?: (source: $TSource, properties: TCustomProperties) => boolean
124
+ searchableTerm?: (source: $TSource, properties: TCustomProperties) => MaybeArray<string>
125
+ }
78
126
  }
79
- ): UseFormSelectReturn<TSource, TValue, TProperties>
127
+ ): UseFormSelectReturn<TCustomProperties, $TSource, $TValue, TMultiple>
128
+
129
+ // Overload #4: Source is an object with label only
80
130
 
81
131
  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
- },
132
+ TBaseSource extends { label: string },
133
+ TAllowEmpty extends boolean = false,
134
+ TCustomProperties extends CollectionItemProperties = EmptyObject,
135
+ TGetValue extends GetOptionValue<$TSource, TCustomProperties> = undefined,
136
+ TMultiple extends boolean = false,
137
+ $TSource = TAllowEmpty extends true ? TBaseSource | undefined : TBaseSource,
138
+ $TValue = ExtractValue<$TSource, TGetValue>,
91
139
  >(
92
- sources: MaybeRefOrGetter<TSource[]>,
93
- config: FormSelectBaseConfig & {
94
- properties: (source: TSource) => TProperties
140
+ sources: MaybeRefOrGetter<TBaseSource[]>,
141
+ config: {
142
+ allowEmpty?: MaybeRefOrGetter<TAllowEmpty>
143
+ multiple?: MaybeRefOrGetter<TMultiple>
144
+ model?: Ref<unknown>
145
+ disabled?: MaybeRefOrGetter<boolean>
146
+ selectedLabel?: (count: number, labels: string[]) => string | undefined
147
+ placeholder?: MaybeRefOrGetter<string>
148
+ searchPlaceholder?: MaybeRefOrGetter<string>
149
+ loading?: MaybeRefOrGetter<boolean>
150
+ required?: MaybeRefOrGetter<boolean>
151
+ searchable?: MaybeRefOrGetter<boolean>
152
+ option: {
153
+ id: GetItemId<TBaseSource>
154
+ value?: TGetValue | ((source: $TSource, properties: TCustomProperties) => $TValue)
155
+ properties?: (source: $TSource) => TCustomProperties
156
+ label?: GetOptionLabel<$TSource, TCustomProperties>
157
+ selectedLabel?: (source: $TSource, properties: TCustomProperties) => string
158
+ disabled?: (source: $TSource, properties: TCustomProperties) => boolean
159
+ searchableTerm?: (source: $TSource, properties: TCustomProperties) => MaybeArray<string>
160
+ }
95
161
  }
96
- ): UseFormSelectReturn<TSource, TValue, TProperties>
162
+ ): UseFormSelectReturn<TCustomProperties, $TSource, $TValue, TMultiple>
163
+
164
+ // Overload #5: Any other case
97
165
 
98
166
  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
- },
167
+ TBaseSource,
168
+ TAllowEmpty extends boolean = false,
169
+ TCustomProperties extends CollectionItemProperties = EmptyObject,
170
+ TGetValue extends GetOptionValue<$TSource, TCustomProperties> = undefined,
171
+ TMultiple extends boolean = false,
172
+ $TSource = TAllowEmpty extends true ? TBaseSource | undefined : TBaseSource,
173
+ $TValue = ExtractValue<$TSource, TGetValue>,
108
174
  >(
109
- sources: MaybeRefOrGetter<TSource[]>,
110
- config: FormSelectBaseConfig & {
111
- properties: (source: TSource) => TProperties
175
+ sources: MaybeRefOrGetter<TBaseSource[]>,
176
+ config: {
177
+ allowEmpty?: MaybeRefOrGetter<TAllowEmpty>
178
+ multiple?: MaybeRefOrGetter<TMultiple>
179
+ model?: Ref<unknown>
180
+ disabled?: MaybeRefOrGetter<boolean>
181
+ selectedLabel?: (count: number, labels: string[]) => string | undefined
182
+ placeholder?: MaybeRefOrGetter<string>
183
+ searchPlaceholder?: MaybeRefOrGetter<string>
184
+ loading?: MaybeRefOrGetter<boolean>
185
+ required?: MaybeRefOrGetter<boolean>
186
+ searchable?: MaybeRefOrGetter<boolean>
187
+ option: {
188
+ id: GetItemId<TBaseSource>
189
+ value?: TGetValue | ((source: $TSource, properties: TCustomProperties) => $TValue)
190
+ properties?: (source: $TSource) => TCustomProperties
191
+ label: GetOptionLabel<$TSource, TCustomProperties>
192
+ selectedLabel?: (source: $TSource, properties: TCustomProperties) => string
193
+ disabled?: (source: $TSource, properties: TCustomProperties) => boolean
194
+ searchableTerm?: (source: $TSource, properties: TCustomProperties) => MaybeArray<string>
195
+ }
112
196
  }
113
- ): UseFormSelectReturn<TSource, TValue, TProperties>
197
+ ): UseFormSelectReturn<TCustomProperties, $TSource, $TValue, TMultiple>
198
+
199
+ // Implementation
114
200
 
115
201
  export function useFormSelect<
116
- TSource,
117
- TProperties extends FormSelectBaseProperties & {
118
- value?: FormOptionValue
119
- label?: string
120
- },
202
+ TBaseSource,
203
+ TAllowEmpty extends boolean = false,
204
+ TCustomProperties extends CollectionItemProperties = EmptyObject,
205
+ TGetValue extends GetOptionValue<$TSource, TCustomProperties> = undefined,
206
+ TMultiple extends boolean = false,
207
+ $TSource = TAllowEmpty extends true ? TBaseSource | undefined : TBaseSource,
208
+ $TValue = ExtractValue<$TSource, TGetValue>,
121
209
  >(
122
- sources: MaybeRefOrGetter<TSource[]>,
123
- config?: FormSelectBaseConfig & {
124
- properties?: (source: TSource) => TProperties
210
+ baseSources: MaybeRefOrGetter<TBaseSource[]>,
211
+ config?: {
212
+ allowEmpty?: MaybeRefOrGetter<boolean>
213
+ multiple?: MaybeRefOrGetter<boolean>
214
+ model?: Ref<unknown>
215
+ disabled?: MaybeRefOrGetter<boolean>
216
+ selectedLabel?: (count: number, labels: string[]) => string | undefined
217
+ placeholder?: MaybeRefOrGetter<string>
218
+ searchPlaceholder?: MaybeRefOrGetter<string>
219
+ loading?: MaybeRefOrGetter<boolean>
220
+ required?: MaybeRefOrGetter<boolean>
221
+ searchable?: MaybeRefOrGetter<boolean>
222
+ option?: {
223
+ id?: GetItemId<TBaseSource>
224
+ value?: GetOptionValue<$TSource, TCustomProperties>
225
+ properties?: (source: $TSource) => TCustomProperties
226
+ label?: GetOptionLabel<$TSource, TCustomProperties>
227
+ selectedLabel?: (source: $TSource, properties: TCustomProperties) => string
228
+ disabled?: (source: $TSource, properties: TCustomProperties) => boolean
229
+ searchableTerm?: (source: $TSource, properties: TCustomProperties) => MaybeArray<string>
230
+ }
125
231
  }
126
- ) {
127
- const _searchTerm = ref('')
232
+ ): UseFormSelectReturn<TCustomProperties, $TSource, $TValue, TMultiple> {
233
+ const searchTerm = ref('')
128
234
 
129
- const searchTerm = computed({
130
- get: () => _searchTerm.value,
131
- set: (value: string) => {
132
- _searchTerm.value = value.toLowerCase().trim()
133
- },
134
- })
235
+ const normalizedSearchTerm = computed(() => normalizeSearchTerm(searchTerm))
236
+
237
+ const isMultiple = computed(() => toValue(config?.multiple) ?? false) as ComputedRef<TMultiple>
238
+
239
+ const isDisabled = computed(() => toValue(config?.disabled) ?? false)
240
+
241
+ const isLoading = computed(() => toValue(config?.loading) ?? false)
242
+
243
+ const isRequired = computed(() => toValue(config?.required) ?? false)
244
+
245
+ const placeholder = computed(() => toValue(config?.placeholder) ?? '')
246
+
247
+ const searchPlaceholder = computed(() => toValue(config?.searchPlaceholder) ?? '')
248
+
249
+ const isSearchable = computed(() => toValue(config?.searchable) ?? false)
250
+
251
+ const sources = computed(() =>
252
+ config?.allowEmpty ? [undefined, ...toValue(baseSources)] : toValue(baseSources)
253
+ ) as ComputedRef<$TSource[]>
135
254
 
136
255
  const {
137
256
  items: allOptions,
138
- useFlag,
139
257
  useSubset,
258
+ useFlag,
140
259
  } = useCollection(sources, {
260
+ itemId: source =>
261
+ source === undefined ? '__EMPTY_OPTION__' : guessItemId(source as TBaseSource, config?.option?.id),
141
262
  flags: {
142
263
  active: { multiple: false },
143
- selected: { multiple: config?.multiple ?? false },
264
+ selected: { multiple: isMultiple },
144
265
  },
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) ?? {}
266
+ properties: (source): FormOptionCollectionItemProperties<TCustomProperties, $TValue> => {
267
+ const customProperties = config?.option?.properties?.(source) ?? ({} as TCustomProperties)
268
+ const label = computed(() => guessLabel(source, customProperties, config?.option?.label))
269
+ const value = computed(() => guessValue(source, customProperties, config?.option?.value) as $TValue)
270
+ const disabled = computed(() => isDisabled.value || config?.option?.disabled?.(source, customProperties) === true)
271
+ const searchableTerm = computed(() => config?.option?.searchableTerm?.(source, customProperties))
272
+ const selectedLabel = computed(() => config?.option?.selectedLabel?.(source, customProperties))
273
+
274
+ const searchableTerms = computed(() =>
275
+ toArray(toValue(searchableTerm) ?? toValue(label)).map(term => normalizeSearchTerm(term))
276
+ )
153
277
 
154
278
  const matching = computed(() => {
155
- if (!searchTerm.value) {
279
+ if (normalizedSearchTerm.value === '') {
156
280
  return true
157
281
  }
158
282
 
159
- const searchableTerms = toArray(searchableTerm ?? label)
160
-
161
- return searchableTerms.some(term => term.toLowerCase().includes(searchTerm.value))
283
+ return searchableTerms.value.some(term => term.includes(normalizedSearchTerm.value))
162
284
  })
163
285
 
164
- return reactive({
165
- id: value,
286
+ return {
287
+ value,
166
288
  label,
167
- multiple: config?.multiple ?? false,
289
+ selectedLabel,
168
290
  disabled,
169
291
  matching,
170
- ...extraProperties,
171
- }) satisfies FormOptionProperties<FormOptionValue>
292
+ ...customProperties,
293
+ }
172
294
  },
173
295
  })
174
296
 
175
297
  const { items: options } = useSubset(option => option.properties.matching)
176
298
 
177
- const { items: selectedOptions, ids: selectedValues } = useFlag('selected')
299
+ const { items: selectedOptions } = useFlag('selected')
178
300
 
179
- const selectedLabels = computed(() => selectedOptions.value.map(option => option.properties.label))
301
+ const selectedOption = computed(() => selectedOptions.value[0])
302
+
303
+ const selectedValues = computed(() => selectedOptions.value.map(option => option.properties.value))
304
+
305
+ const selectedValue = computed(() => selectedValues.value[0])
306
+
307
+ const selectedLabels = computed(() =>
308
+ selectedOptions.value.map(option => option.properties.selectedLabel ?? option.properties.label)
309
+ )
180
310
 
181
311
  const selectedLabel = computed(
182
312
  () => config?.selectedLabel?.(selectedLabels.value.length, selectedLabels.value) ?? selectedLabels.value.join(', ')
183
313
  )
184
314
 
185
- return {
315
+ const { model } = config ?? {}
316
+
317
+ if (model) {
318
+ watch(
319
+ model,
320
+ modelValue => {
321
+ if (isMultiple.value) {
322
+ allOptions.value.forEach(option => {
323
+ option.toggleFlag('selected', (modelValue as $TValue[]).includes(option.properties.value as $TValue))
324
+ })
325
+ } else {
326
+ allOptions.value.find(option => option.properties.value === modelValue)?.toggleFlag('selected', true)
327
+ }
328
+ },
329
+ { immediate: true }
330
+ )
331
+
332
+ watch(
333
+ selectedValues,
334
+ newValues => {
335
+ if (isMultiple.value) {
336
+ model.value = newValues as TMultiple extends true ? $TValue[] : $TValue
337
+ } else {
338
+ model.value = newValues[0] as TMultiple extends true ? $TValue[] : $TValue
339
+ }
340
+ },
341
+ { deep: 1 }
342
+ )
343
+ }
344
+
345
+ const select = {
346
+ isMultiple,
347
+ isDisabled,
348
+ isRequired,
349
+ isSearchable,
350
+ isLoading,
351
+ placeholder,
352
+ searchPlaceholder,
186
353
  searchTerm,
187
354
  allOptions,
188
355
  options,
189
- selectedOptions,
356
+ selectedValue,
190
357
  selectedValues,
358
+ selectedOption,
359
+ selectedOptions,
191
360
  selectedLabel,
192
- }
361
+ } satisfies FormSelect<TCustomProperties, any> as FormSelect<TCustomProperties, $TSource, $TValue, TMultiple>
362
+
363
+ const id = Symbol('useFormSelect ID') as FormSelectId<TCustomProperties, $TSource, $TValue, TMultiple>
364
+
365
+ provide(id, select)
366
+
367
+ return {
368
+ id,
369
+ ...select,
370
+ } satisfies UseFormSelectReturn<TCustomProperties, $TSource, $TValue, TMultiple>
193
371
  }
@@ -0,0 +1,25 @@
1
+ <template>
2
+ <span class="display-icon-root">
3
+ <DisplayIconAny :icon />
4
+ </span>
5
+ </template>
6
+
7
+ <script lang="ts" setup>
8
+ import DisplayIconAny from './DisplayIconAny.vue'
9
+ import { type Icon } from './types.ts'
10
+
11
+ defineProps<{
12
+ icon: Icon
13
+ }>()
14
+ </script>
15
+
16
+ <style lang="postcss" scoped>
17
+ .display-icon-root {
18
+ display: inline-grid;
19
+ height: 1em;
20
+ width: 1em;
21
+ transform-origin: 50% 50%;
22
+ grid-template-rows: 1fr;
23
+ grid-template-columns: 1fr;
24
+ }
25
+ </style>
@@ -0,0 +1,16 @@
1
+ <template>
2
+ <DisplayIconStack v-if="isIconStack(icon)" :stack="icon" :stroke />
3
+ <DisplayIconSingle v-else :icon :stroke />
4
+ </template>
5
+
6
+ <script lang="ts" setup>
7
+ import DisplayIconSingle from './DisplayIconSingle.vue'
8
+ import DisplayIconStack from './DisplayIconStack.vue'
9
+ import { isIconStack } from './is-icon-stack.ts'
10
+ import { type Icon } from './types.ts'
11
+
12
+ defineProps<{
13
+ icon: Icon
14
+ stroke?: string
15
+ }>()
16
+ </script>
@@ -0,0 +1,35 @@
1
+ <template>
2
+ <svg v-if="icon.paths.length > 0" :viewBox="icon.viewBox" class="display-icon-single" v-bind="icon.bindings">
3
+ <path
4
+ v-for="(path, index) of icon.paths"
5
+ :key="index"
6
+ :d="path"
7
+ class="icon-path"
8
+ :stroke="stroke ?? icon.config.borderColor"
9
+ :stroke-width="stroke ? 64 : 32"
10
+ />
11
+ </svg>
12
+ </template>
13
+
14
+ <script lang="ts" setup>
15
+ import type { IconSingle } from './types.ts'
16
+
17
+ defineProps<{
18
+ icon: IconSingle
19
+ stroke?: string
20
+ }>()
21
+ </script>
22
+
23
+ <style lang="postcss" scoped>
24
+ .display-icon-single {
25
+ height: 1em;
26
+ width: 1em;
27
+ overflow: visible;
28
+ grid-row: 1;
29
+ grid-column: 1;
30
+
31
+ .icon-path {
32
+ fill: currentColor;
33
+ }
34
+ }
35
+ </style>
@@ -0,0 +1,34 @@
1
+ <template>
2
+ <span v-bind="stack.bindings" class="display-icon-stack">
3
+ <DisplayIconAny
4
+ v-for="(icon, index) of stack.icons"
5
+ :key="index"
6
+ :icon
7
+ :stroke="stroke ?? stack.config.borderColor"
8
+ />
9
+ <DisplayIconAny v-for="(icon, index) of stack.icons" :key="index" :icon />
10
+ </span>
11
+ </template>
12
+
13
+ <script lang="ts" setup>
14
+ import DisplayIconAny from '@core/packages/icon/DisplayIconAny.vue'
15
+ import type { IconStack } from './types.ts'
16
+
17
+ defineProps<{
18
+ stack: IconStack
19
+ stroke?: string
20
+ }>()
21
+ </script>
22
+
23
+ <style lang="postcss" scoped>
24
+ .display-icon-stack {
25
+ height: 1em;
26
+ width: 1em;
27
+ overflow: visible;
28
+ grid-row: 1;
29
+ grid-column: 1;
30
+ display: inline-grid;
31
+ grid-template-rows: 1fr;
32
+ grid-template-columns: 1fr;
33
+ }
34
+ </style>