@xen-orchestra/web-core 0.20.1 → 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.
- package/lib/components/backup-state/VtsBackupState.vue +20 -17
- package/lib/components/cell-object/VtsCellObject.vue +4 -1
- package/lib/components/console/VtsActionsConsole.vue +7 -4
- package/lib/components/console/VtsClipboardConsole.vue +9 -6
- package/lib/components/copy-button/VtsCopyButton.vue +7 -14
- package/lib/components/dropdown/DropdownTitle.vue +5 -2
- package/lib/components/icon/NewVtsIcon.vue +49 -0
- package/lib/components/input-group/VtsInputGroup.vue +41 -0
- package/lib/components/input-wrapper/VtsInputWrapper.vue +2 -2
- package/lib/components/layout/VtsLayoutSidebar.vue +6 -3
- package/lib/components/linear-chart/VtsLinearChart.vue +4 -0
- package/lib/components/object-icon/VtsObjectIcon.vue +22 -0
- package/lib/components/quick-info-card/VtsQuickInfoCard.vue +4 -1
- package/lib/components/select/VtsOption.vue +10 -6
- package/lib/components/select/VtsSelect.vue +74 -50
- package/lib/components/state-hero/VtsAllDoneHero.vue +4 -1
- package/lib/components/state-hero/VtsAllGoodHero.vue +4 -1
- package/lib/components/state-hero/VtsComingSoonHero.vue +4 -1
- package/lib/components/state-hero/VtsErrorNoDataHero.vue +4 -1
- package/lib/components/state-hero/VtsLoadingHero.vue +4 -1
- package/lib/components/state-hero/VtsNoDataHero.vue +4 -1
- package/lib/components/state-hero/VtsNoSelectionHero.vue +4 -1
- package/lib/components/state-hero/VtsObjectNotFoundHero.vue +4 -1
- package/lib/components/state-hero/VtsOfflineHero.vue +4 -1
- package/lib/components/state-hero/VtsPageNotFoundHero.vue +4 -1
- package/lib/components/table/ColumnTitle.vue +2 -2
- package/lib/components/task/VtsQuickTaskButton.vue +4 -1
- package/lib/components/task/VtsQuickTaskList.vue +5 -2
- package/lib/components/task/VtsQuickTaskTabBar.vue +8 -5
- package/lib/components/ui/card-numbers/UiCardNumbers.vue +4 -1
- package/lib/components/ui/character-limit/UiCharacterLimit.vue +4 -1
- package/lib/components/ui/input/UiInput.vue +2 -2
- package/lib/components/ui/label/UiLabel.vue +4 -1
- package/lib/components/ui/progress-bar/UiProgressBar.vue +5 -2
- package/lib/components/ui/query-search-bar/UiQuerySearchBar.vue +9 -6
- package/lib/components/ui/quick-task-item/UiQuickTaskItem.vue +6 -3
- package/lib/components/ui/stacked-bar/StackedBarSegment.vue +4 -1
- package/lib/components/ui/table-pagination/UiTablePagination.vue +6 -3
- package/lib/components/ui/text-area/UiTextarea.vue +4 -1
- package/lib/components/ui/top-bottom-table/UiTopBottomTable.vue +6 -3
- package/lib/components/ui/tree-item-label/UiTreeItemLabel.vue +4 -1
- package/lib/composables/local-time-ago.composable.ts +53 -0
- package/lib/composables/locale-time-ago.composable.ts +53 -0
- package/lib/icons/fa-icons.ts +164 -0
- package/lib/icons/index.ts +15 -0
- package/lib/icons/legacy-icons.ts +80 -0
- package/lib/icons/object-icons.ts +187 -0
- package/lib/layouts/CoreLayout.vue +7 -3
- package/lib/locales/cs.json +0 -1
- package/lib/locales/de.json +1 -1
- package/lib/locales/en.json +32 -4
- package/lib/locales/es.json +1 -1
- package/lib/locales/fr.json +31 -3
- package/lib/locales/it.json +1 -1
- package/lib/locales/nl.json +1 -1
- package/lib/locales/ru.json +1 -1
- package/lib/locales/sv.json +1 -2
- package/lib/packages/collection/README.md +23 -18
- package/lib/packages/collection/create-collection.ts +22 -21
- package/lib/packages/collection/create-item.ts +21 -20
- package/lib/packages/collection/create-use-subset.ts +23 -0
- package/lib/packages/collection/guess-item-id.ts +26 -16
- package/lib/packages/collection/index.ts +4 -0
- package/lib/packages/collection/types.ts +65 -37
- package/lib/packages/collection/use-collection.ts +68 -18
- package/lib/packages/collection/use-flag-registry.ts +38 -17
- package/lib/packages/form-select/guess-label.ts +45 -0
- package/lib/packages/form-select/guess-value.ts +23 -0
- package/lib/packages/form-select/index.ts +6 -0
- package/lib/packages/form-select/normalize-search-term.ts +11 -0
- package/lib/packages/form-select/types.ts +90 -42
- package/lib/packages/form-select/use-form-option-controller.ts +7 -3
- package/lib/packages/form-select/use-form-select-controller.ts +38 -27
- package/lib/packages/form-select/use-form-select-keyboard-navigation.ts +1 -1
- package/lib/packages/form-select/use-form-select.ts +308 -130
- package/lib/packages/icon/DisplayIcon.vue +25 -0
- package/lib/packages/icon/DisplayIconAny.vue +16 -0
- package/lib/packages/icon/DisplayIconSingle.vue +35 -0
- package/lib/packages/icon/DisplayIconStack.vue +34 -0
- package/lib/packages/icon/README.md +286 -0
- package/lib/packages/icon/create-icon-bindings.ts +27 -0
- package/lib/packages/icon/define-icon-pack.ts +23 -0
- package/lib/packages/icon/define-icon-single.ts +17 -0
- package/lib/packages/icon/define-icon-stack.ts +20 -0
- package/lib/packages/icon/define-icon.ts +40 -0
- package/lib/packages/icon/generate-icon-variants.ts +17 -0
- package/lib/packages/icon/index.ts +8 -0
- package/lib/packages/icon/is-icon-stack.ts +5 -0
- package/lib/packages/icon/merge-icons.ts +25 -0
- package/lib/packages/icon/merge-transforms.ts +12 -0
- package/lib/packages/icon/normalize-icon.ts +25 -0
- package/lib/packages/icon/to-tuple.ts +7 -0
- package/lib/packages/icon/types.ts +72 -0
- package/lib/packages/job/README.md +2 -2
- package/lib/packages/mapper/README.md +166 -0
- package/lib/packages/mapper/convert-to-map.ts +5 -0
- package/lib/packages/mapper/create-mapper.ts +30 -0
- package/lib/packages/mapper/index.ts +4 -0
- package/lib/packages/mapper/types.ts +1 -0
- package/lib/packages/mapper/use-mapper.ts +31 -0
- package/lib/stores/sidebar.store.ts +1 -1
- package/lib/types/chart.ts +2 -2
- package/lib/types/utility.type.ts +9 -0
- package/lib/utils/object.util.ts +16 -0
- package/lib/utils/size.util.ts +4 -2
- package/package.json +2 -1
- package/lib/components/backup-item/VtsBackupItem.vue +0 -47
- package/lib/composables/mapper.composable.md +0 -74
- package/lib/composables/mapper.composable.ts +0 -18
|
@@ -1,39 +1,40 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { CollectionConfigProperties, CollectionItem, FlagRegistry } from '@core/packages/collection/types.ts'
|
|
1
|
+
import type { CollectionItem, CollectionItemId, CollectionItemProperties, FlagRegistry } from './types.ts'
|
|
3
2
|
import { reactive } from 'vue'
|
|
4
3
|
|
|
5
|
-
export function createItem<
|
|
4
|
+
export function createItem<
|
|
5
|
+
TSource,
|
|
6
|
+
TFlag extends string,
|
|
7
|
+
TProperties extends CollectionItemProperties,
|
|
8
|
+
TId extends CollectionItemId,
|
|
9
|
+
>(
|
|
10
|
+
id: TId,
|
|
6
11
|
source: TSource,
|
|
7
|
-
|
|
8
|
-
flagRegistry: FlagRegistry<TFlag>
|
|
9
|
-
): CollectionItem<TSource, TFlag, TProperties> {
|
|
10
|
-
const properties = reactive(getProperties?.(source) ?? ({} as TProperties))
|
|
11
|
-
|
|
12
|
-
const id = guessItemId(source, properties)
|
|
13
|
-
|
|
12
|
+
properties: TProperties,
|
|
13
|
+
flagRegistry: FlagRegistry<TId, TFlag>
|
|
14
|
+
): CollectionItem<TSource, TFlag, TProperties, TId> {
|
|
14
15
|
return {
|
|
15
16
|
id,
|
|
16
17
|
source,
|
|
17
|
-
toggleFlag(flag: TFlag,
|
|
18
|
-
flagRegistry.toggleFlag(id, flag,
|
|
18
|
+
toggleFlag(flag: TFlag, shouldBeFlagged?: boolean) {
|
|
19
|
+
flagRegistry.toggleFlag(id, flag, shouldBeFlagged)
|
|
19
20
|
},
|
|
20
21
|
flags: new Proxy({} as Record<TFlag, boolean>, {
|
|
21
|
-
has(_, flag
|
|
22
|
-
return flagRegistry.isFlagDefined(flag)
|
|
22
|
+
has(_, flag) {
|
|
23
|
+
return flagRegistry.isFlagDefined(flag as TFlag)
|
|
23
24
|
},
|
|
24
|
-
get(_, flag
|
|
25
|
-
if (
|
|
25
|
+
get(_, flag) {
|
|
26
|
+
if (typeof flag === 'symbol' || flag.startsWith('__v_')) {
|
|
26
27
|
return undefined
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
return flagRegistry.isFlagged(id, flag)
|
|
30
|
+
return flagRegistry.isFlagged(id, flag as TFlag)
|
|
30
31
|
},
|
|
31
|
-
set(_, flag
|
|
32
|
-
flagRegistry.toggleFlag(id, flag, value)
|
|
32
|
+
set(_, flag, value: boolean) {
|
|
33
|
+
flagRegistry.toggleFlag(id, flag as TFlag, value)
|
|
33
34
|
|
|
34
35
|
return true
|
|
35
36
|
},
|
|
36
37
|
}),
|
|
37
|
-
properties,
|
|
38
|
+
properties: reactive(properties),
|
|
38
39
|
}
|
|
39
40
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createCollection } from '@core/packages/collection/create-collection.ts'
|
|
2
|
+
import type {
|
|
3
|
+
Collection,
|
|
4
|
+
CollectionItem,
|
|
5
|
+
CollectionItemId,
|
|
6
|
+
CollectionItemProperties,
|
|
7
|
+
FlagRegistry,
|
|
8
|
+
} from '@core/packages/collection/types.ts'
|
|
9
|
+
import type { ArrayFilterPredicate } from '@core/types/utility.type.ts'
|
|
10
|
+
import { useArrayFilter } from '@vueuse/core'
|
|
11
|
+
import type { ComputedRef } from 'vue'
|
|
12
|
+
|
|
13
|
+
export function createUseSubset<
|
|
14
|
+
TSource,
|
|
15
|
+
TFlag extends string,
|
|
16
|
+
TProperties extends CollectionItemProperties,
|
|
17
|
+
TId extends CollectionItemId,
|
|
18
|
+
$TItem extends CollectionItem<TSource, TFlag, TProperties, TId> = CollectionItem<TSource, TFlag, TProperties, TId>,
|
|
19
|
+
>(items: ComputedRef<$TItem[]>, flagRegistry: FlagRegistry<TId, TFlag>) {
|
|
20
|
+
return function useSubset(filter: ArrayFilterPredicate<$TItem>): Collection<TSource, TFlag, TProperties, TId> {
|
|
21
|
+
return createCollection(useArrayFilter(items, filter), flagRegistry)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -1,26 +1,36 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { hasObjectProperty } from '@core/utils/object.util.ts'
|
|
2
|
+
import type { CollectionItemId, GetItemId } from './types.ts'
|
|
2
3
|
|
|
3
|
-
function
|
|
4
|
-
const
|
|
4
|
+
export function guessItemId<TSource>(source: TSource, getter: GetItemId<TSource>): CollectionItemId {
|
|
5
|
+
const id = extractItemId(source, getter)
|
|
5
6
|
|
|
6
|
-
if (
|
|
7
|
-
|
|
7
|
+
if (isCollectionId(id)) {
|
|
8
|
+
return id
|
|
8
9
|
}
|
|
10
|
+
|
|
11
|
+
throw new Error(`Unable to guess id from source: ${JSON.stringify(source)}`)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function isCollectionId(value: unknown): value is CollectionItemId {
|
|
15
|
+
return typeof value === 'string' || typeof value === 'number'
|
|
9
16
|
}
|
|
10
17
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
function extractItemId<TSource>(source: TSource, getter: GetItemId<TSource>) {
|
|
19
|
+
if (getter === undefined) {
|
|
20
|
+
if (hasObjectProperty(source, 'id')) {
|
|
21
|
+
return source.id
|
|
22
|
+
}
|
|
16
23
|
|
|
17
|
-
|
|
18
|
-
id = properties.id
|
|
19
|
-
} else if (typeof source === 'object' && source !== null && 'id' in source) {
|
|
20
|
-
id = source.id
|
|
24
|
+
return source
|
|
21
25
|
}
|
|
22
26
|
|
|
23
|
-
|
|
27
|
+
if (typeof getter === 'function') {
|
|
28
|
+
return getter(source)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (hasObjectProperty(source, getter)) {
|
|
32
|
+
return source[getter]
|
|
33
|
+
}
|
|
24
34
|
|
|
25
|
-
|
|
35
|
+
throw new Error(`Property "${String(getter)}" not found in source: ${JSON.stringify(source)}`)
|
|
26
36
|
}
|
|
@@ -1,57 +1,85 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
export type CollectionConfigProperties = Record<string, unknown> & { id?: unknown }
|
|
4
|
-
|
|
5
|
-
export type CollectionConfigFlags<TFlag extends string> = TFlag[] | Record<TFlag, { multiple?: boolean }>
|
|
1
|
+
import type { ArrayFilterPredicate, KeyOfByValue } from '@core/types/utility.type.ts'
|
|
2
|
+
import type { ComputedRef, MaybeRefOrGetter, Reactive } from 'vue'
|
|
6
3
|
|
|
7
4
|
export type CollectionItem<
|
|
8
|
-
TSource,
|
|
9
|
-
TFlag extends string =
|
|
10
|
-
TProperties extends
|
|
5
|
+
TSource = unknown,
|
|
6
|
+
TFlag extends string = string,
|
|
7
|
+
TProperties extends CollectionItemProperties = CollectionItemProperties,
|
|
8
|
+
TId extends CollectionItemId = PickSourceId<TSource, 'id'>,
|
|
11
9
|
> = {
|
|
12
|
-
id:
|
|
10
|
+
id: TId
|
|
13
11
|
source: TSource
|
|
14
12
|
flags: Record<TFlag, boolean>
|
|
15
13
|
properties: Reactive<TProperties>
|
|
16
|
-
toggleFlag: (flag: TFlag,
|
|
14
|
+
toggleFlag: (flag: TFlag, shouldBeFlagged?: boolean) => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type Collection<
|
|
18
|
+
TSource = unknown,
|
|
19
|
+
TFlag extends string = string,
|
|
20
|
+
TProperties extends CollectionItemProperties = CollectionItemProperties,
|
|
21
|
+
TId extends CollectionItemId = PickSourceId<TSource, 'id'>,
|
|
22
|
+
$TItem extends CollectionItem<TSource, TFlag, TProperties, TId> = CollectionItem<TSource, TFlag, TProperties, TId>,
|
|
23
|
+
> = {
|
|
24
|
+
items: ComputedRef<$TItem[]>
|
|
25
|
+
count: ComputedRef<number>
|
|
26
|
+
useFlag: (flag: TFlag) => UseFlagReturn<TSource, TFlag, TProperties, TId>
|
|
27
|
+
toggleFlag: (id: TId, flag: TFlag, shouldBeFlagged?: boolean) => void
|
|
28
|
+
useSubset: (filter: ArrayFilterPredicate<$TItem>) => Collection<TSource, TFlag, TProperties, TId>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type CollectionItemProperties = Record<PropertyKey, unknown>
|
|
32
|
+
|
|
33
|
+
export type CollectionItemId = string | number
|
|
34
|
+
|
|
35
|
+
export type PickSourceId<TSource, TKey> = TKey extends keyof TSource
|
|
36
|
+
? TSource[TKey] extends CollectionItemId
|
|
37
|
+
? TSource[TKey]
|
|
38
|
+
: never
|
|
39
|
+
: never
|
|
40
|
+
|
|
41
|
+
export type ExtractSourceId<TSource, TGetId extends GetItemId<TSource>> = TGetId extends keyof TSource
|
|
42
|
+
? PickSourceId<TSource, TGetId>
|
|
43
|
+
: TGetId extends (source: TSource) => infer R
|
|
44
|
+
? R
|
|
45
|
+
: TSource extends CollectionItemId
|
|
46
|
+
? TSource
|
|
47
|
+
: PickSourceId<TSource, 'id'>
|
|
48
|
+
|
|
49
|
+
export type FlagConfig = {
|
|
50
|
+
multiple?: MaybeRefOrGetter<boolean>
|
|
17
51
|
}
|
|
18
52
|
|
|
19
|
-
export type
|
|
20
|
-
|
|
53
|
+
export type CollectionConfigFlags<TFlag extends string> = TFlag[] | Record<TFlag, FlagConfig>
|
|
54
|
+
|
|
55
|
+
export type FlagRegistry<TId extends CollectionItemId, TFlag extends string> = {
|
|
56
|
+
isFlagged: (id: TId, flag: TFlag) => boolean
|
|
21
57
|
isFlagDefined: (flag: TFlag) => boolean
|
|
22
|
-
toggleFlag: (id:
|
|
58
|
+
toggleFlag: (id: TId, flag: TFlag, shouldBeFlagged?: boolean) => void
|
|
23
59
|
clearFlag: (flag: TFlag) => void
|
|
24
60
|
isMultipleAllowed: (flag: TFlag) => boolean
|
|
25
61
|
assertFlag: (flag: TFlag) => void
|
|
26
62
|
}
|
|
27
63
|
|
|
28
|
-
export type UseFlagReturn<
|
|
29
|
-
|
|
30
|
-
|
|
64
|
+
export type UseFlagReturn<
|
|
65
|
+
TSource,
|
|
66
|
+
TFlag extends string,
|
|
67
|
+
TProperties extends CollectionItemProperties,
|
|
68
|
+
TId extends CollectionItemId,
|
|
69
|
+
$TItem extends CollectionItem<TSource, TFlag, TProperties, TId> = CollectionItem<TSource, TFlag, TProperties, TId>,
|
|
70
|
+
> = {
|
|
71
|
+
items: ComputedRef<$TItem[]>
|
|
72
|
+
ids: ComputedRef<TId[]>
|
|
31
73
|
count: ComputedRef<number>
|
|
32
74
|
areAllOn: ComputedRef<boolean>
|
|
33
75
|
areSomeOn: ComputedRef<boolean>
|
|
34
76
|
areNoneOn: ComputedRef<boolean>
|
|
35
|
-
toggle: (id:
|
|
36
|
-
toggleAll: (
|
|
37
|
-
useSubset: (
|
|
38
|
-
filter: (item: CollectionItem<TSource, TFlag, TProperties>) => boolean
|
|
39
|
-
) => Collection<TSource, TFlag, TProperties>
|
|
77
|
+
toggle: (id: TId, shouldBeFlagged?: boolean) => void
|
|
78
|
+
toggleAll: (shouldBeFlagged?: boolean) => void
|
|
79
|
+
useSubset: (filter: ArrayFilterPredicate<$TItem>) => Collection<TSource, TFlag, TProperties, TId>
|
|
40
80
|
}
|
|
41
81
|
|
|
42
|
-
export type
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
useSubset: (
|
|
47
|
-
filter: (item: CollectionItem<TSource, TFlag, TProperties>) => boolean
|
|
48
|
-
) => Collection<TSource, TFlag, TProperties>
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
type AssertId<TId> = TId extends PropertyKey ? TId : never
|
|
52
|
-
|
|
53
|
-
export type GuessItemId<TSource, TProperties> = TProperties extends { id: infer TId }
|
|
54
|
-
? AssertId<TId>
|
|
55
|
-
: TSource extends { id: infer TId }
|
|
56
|
-
? AssertId<TId>
|
|
57
|
-
: never
|
|
82
|
+
export type GetItemId<TSource> =
|
|
83
|
+
| undefined
|
|
84
|
+
| KeyOfByValue<TSource, CollectionItemId>
|
|
85
|
+
| ((source: TSource) => CollectionItemId)
|
|
@@ -1,47 +1,97 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import type {
|
|
4
|
-
|
|
1
|
+
import { guessItemId } from '@core/packages/collection/guess-item-id.ts'
|
|
2
|
+
import type { EmptyObject } from '@core/types/utility.type.ts'
|
|
3
|
+
import type {
|
|
4
|
+
Collection,
|
|
5
|
+
CollectionConfigFlags,
|
|
6
|
+
CollectionItemId,
|
|
7
|
+
CollectionItemProperties,
|
|
8
|
+
ExtractSourceId,
|
|
9
|
+
GetItemId,
|
|
10
|
+
} from './types.ts'
|
|
5
11
|
import { computed, type MaybeRefOrGetter, toValue } from 'vue'
|
|
12
|
+
import { createCollection } from './create-collection.ts'
|
|
13
|
+
import { createItem } from './create-item.ts'
|
|
14
|
+
import { useFlagRegistry } from './use-flag-registry.ts'
|
|
15
|
+
|
|
16
|
+
// Overload #1: Source is CollectionItemId
|
|
17
|
+
|
|
18
|
+
export function useCollection<
|
|
19
|
+
TSource extends CollectionItemId,
|
|
20
|
+
TFlag extends string = never,
|
|
21
|
+
TProperties extends CollectionItemProperties = EmptyObject,
|
|
22
|
+
TGetId extends ((source: TSource) => CollectionItemId) | undefined = undefined,
|
|
23
|
+
$TId extends CollectionItemId = ExtractSourceId<TSource, TGetId>,
|
|
24
|
+
>(
|
|
25
|
+
source: MaybeRefOrGetter<TSource[]>,
|
|
26
|
+
config?: {
|
|
27
|
+
itemId?: TGetId | ((source: TSource) => CollectionItemId)
|
|
28
|
+
flags?: CollectionConfigFlags<TFlag>
|
|
29
|
+
properties?: (source: TSource) => TProperties
|
|
30
|
+
}
|
|
31
|
+
): Collection<TSource, TFlag, TProperties, $TId>
|
|
32
|
+
|
|
33
|
+
// Overload #2: Source is an object with id
|
|
6
34
|
|
|
7
35
|
export function useCollection<
|
|
8
|
-
TSource extends { id:
|
|
36
|
+
TSource extends { id: CollectionItemId },
|
|
9
37
|
TFlag extends string = never,
|
|
10
|
-
TProperties extends
|
|
38
|
+
TProperties extends CollectionItemProperties = EmptyObject,
|
|
39
|
+
TGetId extends GetItemId<TSource> = undefined,
|
|
40
|
+
$TId extends CollectionItemId = ExtractSourceId<TSource, TGetId>,
|
|
11
41
|
>(
|
|
12
|
-
|
|
42
|
+
source: MaybeRefOrGetter<TSource[]>,
|
|
13
43
|
config?: {
|
|
44
|
+
itemId?: TGetId | ((source: TSource) => CollectionItemId)
|
|
14
45
|
flags?: CollectionConfigFlags<TFlag>
|
|
15
46
|
properties?: (source: TSource) => TProperties
|
|
16
47
|
}
|
|
17
|
-
): Collection<TSource, TFlag, TProperties>
|
|
48
|
+
): Collection<TSource, TFlag, TProperties, $TId>
|
|
49
|
+
|
|
50
|
+
// Overload #3: Any other case
|
|
18
51
|
|
|
19
52
|
export function useCollection<
|
|
20
53
|
TSource,
|
|
21
54
|
TFlag extends string = never,
|
|
22
|
-
TProperties extends
|
|
55
|
+
TProperties extends CollectionItemProperties = EmptyObject,
|
|
56
|
+
TGetId extends GetItemId<TSource> = never,
|
|
57
|
+
$TId extends CollectionItemId = ExtractSourceId<TSource, TGetId>,
|
|
23
58
|
>(
|
|
24
|
-
|
|
59
|
+
source: MaybeRefOrGetter<TSource[]>,
|
|
25
60
|
config: {
|
|
61
|
+
itemId: TGetId | ((source: TSource) => CollectionItemId)
|
|
26
62
|
flags?: CollectionConfigFlags<TFlag>
|
|
27
|
-
properties
|
|
63
|
+
properties?: (source: TSource) => TProperties
|
|
28
64
|
}
|
|
29
|
-
): Collection<TSource, TFlag, TProperties>
|
|
65
|
+
): Collection<TSource, TFlag, TProperties, $TId>
|
|
66
|
+
|
|
67
|
+
// Implementation
|
|
30
68
|
|
|
31
69
|
export function useCollection<
|
|
32
70
|
TSource,
|
|
33
|
-
TFlag extends string
|
|
34
|
-
TProperties extends
|
|
71
|
+
TFlag extends string,
|
|
72
|
+
TProperties extends CollectionItemProperties,
|
|
73
|
+
TGetId extends GetItemId<TSource>,
|
|
74
|
+
$TId extends CollectionItemId,
|
|
35
75
|
>(
|
|
36
|
-
|
|
76
|
+
_sources: MaybeRefOrGetter<TSource[]>,
|
|
37
77
|
config?: {
|
|
78
|
+
itemId?: TGetId
|
|
38
79
|
flags?: CollectionConfigFlags<TFlag>
|
|
39
80
|
properties?: (source: TSource) => TProperties
|
|
40
81
|
}
|
|
41
|
-
): Collection<TSource, TFlag, TProperties> {
|
|
42
|
-
const flagRegistry = useFlagRegistry(config?.flags)
|
|
82
|
+
): Collection<TSource, TFlag, TProperties, $TId> {
|
|
83
|
+
const flagRegistry = useFlagRegistry<TFlag, $TId>(config?.flags)
|
|
84
|
+
|
|
85
|
+
const sources = computed(() => toValue(_sources))
|
|
86
|
+
|
|
87
|
+
const items = computed(() =>
|
|
88
|
+
sources.value.map(source => {
|
|
89
|
+
const id = guessItemId(source, config?.itemId) as $TId
|
|
90
|
+
const properties = config?.properties?.(source) ?? ({} as TProperties)
|
|
43
91
|
|
|
44
|
-
|
|
92
|
+
return createItem<TSource, TFlag, TProperties, $TId>(id, source, properties, flagRegistry)
|
|
93
|
+
})
|
|
94
|
+
)
|
|
45
95
|
|
|
46
96
|
return createCollection(items, flagRegistry)
|
|
47
97
|
}
|
|
@@ -1,12 +1,21 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { objectFromEntries, objectEntries } from '@core/utils/object.util.ts'
|
|
2
|
+
import type { CollectionConfigFlags, CollectionItemId, FlagConfig, FlagRegistry } from './types.ts'
|
|
3
|
+
import { isRef, reactive, toValue, watch } from 'vue'
|
|
3
4
|
|
|
4
|
-
export function useFlagRegistry<TFlag extends string>(
|
|
5
|
+
export function useFlagRegistry<TFlag extends string, TId extends CollectionItemId>(
|
|
5
6
|
config: CollectionConfigFlags<TFlag> = [] as TFlag[]
|
|
6
|
-
): FlagRegistry<TFlag> {
|
|
7
|
-
const registry = reactive(new Map<TFlag, Set<
|
|
7
|
+
): FlagRegistry<TId, TFlag> {
|
|
8
|
+
const registry = reactive(new Map()) as Map<TFlag, Set<TId>>
|
|
8
9
|
|
|
9
|
-
const flags = Array.isArray(config)
|
|
10
|
+
const flags = Array.isArray(config)
|
|
11
|
+
? objectFromEntries(config.map(flag => [flag, { multiple: true } as FlagConfig]))
|
|
12
|
+
: config
|
|
13
|
+
|
|
14
|
+
for (const [flag, { multiple }] of objectEntries(flags)) {
|
|
15
|
+
if (isRef(multiple)) {
|
|
16
|
+
watch(multiple, () => clearFlag(flag))
|
|
17
|
+
}
|
|
18
|
+
}
|
|
10
19
|
|
|
11
20
|
function isFlagDefined(flag: TFlag) {
|
|
12
21
|
return Object.prototype.hasOwnProperty.call(flags, flag)
|
|
@@ -18,39 +27,51 @@ export function useFlagRegistry<TFlag extends string>(
|
|
|
18
27
|
}
|
|
19
28
|
}
|
|
20
29
|
|
|
21
|
-
function isFlagged(id:
|
|
30
|
+
function isFlagged(id: TId, flag: TFlag) {
|
|
22
31
|
assertFlag(flag)
|
|
23
32
|
|
|
24
33
|
return registry.get(flag)?.has(id) ?? false
|
|
25
34
|
}
|
|
26
35
|
|
|
27
|
-
function toggleFlag(id:
|
|
36
|
+
function toggleFlag(id: TId, flag: TFlag, shouldBeFlagged = !isFlagged(id, flag)) {
|
|
28
37
|
assertFlag(flag)
|
|
29
38
|
|
|
30
39
|
if (!registry.has(flag)) {
|
|
31
40
|
registry.set(flag, new Set())
|
|
32
41
|
}
|
|
33
42
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
const flagSet = registry.get(flag)!
|
|
44
|
+
|
|
45
|
+
if (shouldBeFlagged === flagSet.has(id)) {
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!shouldBeFlagged) {
|
|
50
|
+
flagSet.delete(id)
|
|
51
|
+
return
|
|
41
52
|
}
|
|
53
|
+
|
|
54
|
+
if (!isMultipleAllowed(flag) && flagSet.size > 0) {
|
|
55
|
+
clearFlag(flag)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
flagSet.add(id)
|
|
42
59
|
}
|
|
43
60
|
|
|
44
61
|
function clearFlag(flag: TFlag) {
|
|
45
62
|
assertFlag(flag)
|
|
46
63
|
|
|
47
|
-
registry.
|
|
64
|
+
if (!registry.has(flag) || registry.get(flag)!.size === 0) {
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
registry.get(flag)!.clear()
|
|
48
69
|
}
|
|
49
70
|
|
|
50
71
|
function isMultipleAllowed(flag: TFlag) {
|
|
51
72
|
assertFlag(flag)
|
|
52
73
|
|
|
53
|
-
return flags[flag]?.multiple ??
|
|
74
|
+
return toValue(flags[flag]?.multiple) ?? true
|
|
54
75
|
}
|
|
55
76
|
|
|
56
77
|
return {
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { CollectionItemProperties } from '@core/packages/collection'
|
|
2
|
+
import { hasObjectProperty } from '@core/utils/object.util.ts'
|
|
3
|
+
import type { GetOptionLabel } from './types.ts'
|
|
4
|
+
|
|
5
|
+
export function guessLabel<TSource, TCustomProperties extends CollectionItemProperties>(
|
|
6
|
+
source: TSource,
|
|
7
|
+
extraProperties: TCustomProperties,
|
|
8
|
+
getter: GetOptionLabel<TSource, TCustomProperties>
|
|
9
|
+
): string {
|
|
10
|
+
const label = extractLabel(source, extraProperties, getter)
|
|
11
|
+
|
|
12
|
+
if (typeof label === 'string' || typeof label === 'number') {
|
|
13
|
+
return label.toString()
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
throw new Error(`Unable to guess label from source: ${JSON.stringify(source)}`)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function extractLabel<TSource, TCustomProperties extends CollectionItemProperties>(
|
|
20
|
+
source: TSource,
|
|
21
|
+
extraProperties: TCustomProperties,
|
|
22
|
+
getter: undefined | keyof TSource | ((source: TSource, properties: TCustomProperties) => string)
|
|
23
|
+
): unknown {
|
|
24
|
+
if (getter === undefined) {
|
|
25
|
+
if (hasObjectProperty(source, 'label')) {
|
|
26
|
+
return source.label
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return source
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (typeof getter === 'function') {
|
|
33
|
+
return getter(source, extraProperties)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (hasObjectProperty(source, getter)) {
|
|
37
|
+
return source[getter]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (source === undefined) {
|
|
41
|
+
return ''
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
throw new Error(`Property "${String(getter)}" not found in source: ${JSON.stringify(source)}`)
|
|
45
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { CollectionItemProperties } from '@core/packages/collection'
|
|
2
|
+
import { hasObjectProperty } from '@core/utils/object.util.ts'
|
|
3
|
+
import type { GetOptionValue } from './types.ts'
|
|
4
|
+
|
|
5
|
+
export function guessValue<TSource, TValue, TCustomProperties extends CollectionItemProperties>(
|
|
6
|
+
source: TSource,
|
|
7
|
+
customProperties: TCustomProperties,
|
|
8
|
+
getter: GetOptionValue<TSource, TCustomProperties>
|
|
9
|
+
): TValue {
|
|
10
|
+
if (getter === undefined) {
|
|
11
|
+
return source as unknown as TValue
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (typeof getter === 'function') {
|
|
15
|
+
return getter(source, customProperties) as TValue
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (hasObjectProperty(source, getter)) {
|
|
19
|
+
return source[getter] as TValue
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
throw new Error(`Property "${String(getter)}" not found in source: ${JSON.stringify(source)}`)
|
|
23
|
+
}
|
|
@@ -1,2 +1,8 @@
|
|
|
1
|
+
export * from './guess-label.ts'
|
|
2
|
+
export * from './guess-value.ts'
|
|
3
|
+
export * from './normalize-search-term.ts'
|
|
1
4
|
export * from './types.ts'
|
|
5
|
+
export * from './use-form-option-controller.ts'
|
|
2
6
|
export * from './use-form-select.ts'
|
|
7
|
+
export * from './use-form-select-controller.ts'
|
|
8
|
+
export * from './use-form-select-keyboard-navigation.ts'
|