@xen-orchestra/web-core 0.52.0 → 0.54.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 (45) hide show
  1. package/lib/components/console/VtsRemoteConsole.vue +7 -3
  2. package/lib/components/form/VtsForm.vue +15 -0
  3. package/lib/components/input-wrapper/VtsInputWrapper.vue +2 -1
  4. package/lib/components/status/VtsStatus.vue +1 -7
  5. package/lib/components/table/cells/VtsDoubleLinkCell.vue +1 -1
  6. package/lib/components/table/cells/VtsTagCell.vue +2 -4
  7. package/lib/components/tag/VtsTag.vue +30 -0
  8. package/lib/components/ui/chip/UiChip.vue +52 -29
  9. package/lib/components/ui/info/UiInfo.vue +17 -6
  10. package/lib/components/ui/input/UiInput.vue +7 -3
  11. package/lib/components/ui/link/UiLink.vue +6 -2
  12. package/lib/components/ui/tag/UiTag.vue +10 -17
  13. package/lib/components/ui/tag/UiTertiaryTag.vue +39 -0
  14. package/lib/icons/action-icons.ts +1 -1
  15. package/lib/icons/status-icons.ts +1 -1
  16. package/lib/locales/cs.json +88 -1
  17. package/lib/locales/de.json +150 -23
  18. package/lib/locales/en.json +74 -0
  19. package/lib/locales/es.json +30 -2
  20. package/lib/locales/fr.json +74 -0
  21. package/lib/locales/ko.json +136 -0
  22. package/lib/locales/nl.json +120 -10
  23. package/lib/locales/sk.json +87 -0
  24. package/lib/locales/sv.json +179 -1
  25. package/lib/locales/zh-Hans.json +717 -635
  26. package/lib/packages/form-validation/README.md +273 -0
  27. package/lib/packages/form-validation/custom-rules/ipv4-or-cidr.rule.ts +17 -0
  28. package/lib/packages/form-validation/custom-rules/out-of-range.rule.ts +15 -0
  29. package/lib/packages/form-validation/index.ts +9 -0
  30. package/lib/packages/form-validation/merge-validation-configs.ts +46 -0
  31. package/lib/packages/form-validation/types.ts +104 -0
  32. package/lib/packages/form-validation/use-form-validation.ts +196 -0
  33. package/lib/packages/modal/types.ts +1 -2
  34. package/lib/packages/remote-resource/define-remote-resource.ts +2 -1
  35. package/lib/packages/remote-resource/types.ts +1 -3
  36. package/lib/packages/request/define-request.ts +1 -2
  37. package/lib/packages/validated-form/README.md +389 -0
  38. package/lib/packages/validated-form/index.ts +2 -0
  39. package/lib/packages/validated-form/use-multi-step-validated-form.ts +203 -0
  40. package/lib/packages/validated-form/use-validated-form.ts +180 -0
  41. package/lib/utils/parse-tag.util.ts +22 -0
  42. package/package.json +11 -8
  43. package/tsconfig.json +2 -3
  44. package/lib/components/ui/chip/ChipIcon.vue +0 -21
  45. package/lib/components/ui/chip/ChipRemoveIcon.vue +0 -16
@@ -8,7 +8,8 @@
8
8
  <script lang="ts" setup>
9
9
  import VtsStateHero from '@core/components/state-hero/VtsStateHero.vue'
10
10
  import { useUiStore } from '@core/stores/ui.store'
11
- import VncClient from '@novnc/novnc/lib/rfb'
11
+ import type NoVncClient from '@novnc/novnc/lib/rfb'
12
+ import _RFB from '@novnc/novnc/lib/rfb'
12
13
  import { whenever } from '@vueuse/core'
13
14
  import { promiseTimeout } from '@vueuse/shared'
14
15
  import { fibonacci } from 'iterable-backoff'
@@ -30,7 +31,10 @@ const FIBONACCI_MS_ARRAY: number[] = Array.from(fibonacci().toMs().take(N_TOTAL_
30
31
  const consoleContainer = useTemplateRef<HTMLDivElement | null>('console-container')
31
32
  const isReady = ref(false)
32
33
 
33
- let vncClient: VncClient | undefined
34
+ // See https://rolldown.rs/in-depth/bundling-cjs#ambiguous-default-import-from-cjs-modules
35
+ const VncClient = typeof _RFB === 'object' && _RFB !== null && (_RFB as any).__esModule ? (_RFB as any).default : _RFB
36
+
37
+ let vncClient: NoVncClient | undefined
34
38
  let nConnectionAttempts = 0
35
39
 
36
40
  function handleDisconnectionEvent() {
@@ -80,7 +84,7 @@ async function createVncConnection() {
80
84
 
81
85
  vncClient = new VncClient(consoleContainer.value!, url.toString(), {
82
86
  wsProtocols: ['binary'],
83
- })
87
+ }) as NoVncClient
84
88
  vncClient.scaleViewport = true
85
89
  vncClient.background = 'transparent'
86
90
 
@@ -0,0 +1,15 @@
1
+ <template>
2
+ <form novalidate @submit.prevent="emit('submit')">
3
+ <slot />
4
+ </form>
5
+ </template>
6
+
7
+ <script lang="ts" setup>
8
+ const emit = defineEmits<{
9
+ submit: []
10
+ }>()
11
+
12
+ defineSlots<{
13
+ default(): any
14
+ }>()
15
+ </script>
@@ -4,7 +4,7 @@
4
4
  <slot name="label">{{ label }}</slot>
5
5
  </UiLabel>
6
6
  <slot />
7
- <UiInfo v-for="{ content, accent } of messages" :key="content" :accent>
7
+ <UiInfo v-for="{ content, accent } of messages" :key="content" :accent :wrap="wrapMessage">
8
8
  {{ content }}
9
9
  </UiInfo>
10
10
  </div>
@@ -33,6 +33,7 @@ const { message: _message } = defineProps<{
33
33
  label?: string
34
34
  learnMoreUrl?: string
35
35
  message?: InputWrapperMessage
36
+ wrapMessage?: boolean
36
37
  }>()
37
38
 
38
39
  defineSlots<{
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <UiInfo v-tooltip="iconOnly ? currentStatus.text : false" class="vts-status" :accent="currentStatus.accent">
2
+ <UiInfo v-tooltip="iconOnly ? currentStatus.text : false" :accent="currentStatus.accent">
3
3
  <template v-if="!iconOnly">{{ currentStatus.text }}</template>
4
4
  </UiInfo>
5
5
  </template>
@@ -62,9 +62,3 @@ const currentStatus = useMapper<Status, { text: string; accent: InfoAccent }>(
62
62
  false
63
63
  )
64
64
  </script>
65
-
66
- <style lang="postcss" scoped>
67
- .vts-status {
68
- align-items: center;
69
- }
70
- </style>
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <UiTableCell>
3
3
  <div class="vts-double-link-cell">
4
- <UiLink size="medium" :icon :to :href :target class="link">
4
+ <UiLink size="medium" :icon :to :href :target :disabled class="link">
5
5
  <slot />
6
6
  </UiLink>
7
7
  <UiLink
@@ -1,16 +1,14 @@
1
1
  <template>
2
2
  <UiTableCell>
3
3
  <UiTagsList>
4
- <UiTag v-for="tagItem of tags" :key="tagItem" accent="info" variant="secondary">
5
- {{ tagItem }}
6
- </UiTag>
4
+ <VtsTag v-for="tagItem of tags" :key="tagItem" :value="tagItem" />
7
5
  </UiTagsList>
8
6
  </UiTableCell>
9
7
  </template>
10
8
 
11
9
  <script setup lang="ts">
10
+ import VtsTag from '@core/components/tag/VtsTag.vue'
12
11
  import UiTableCell from '@core/components/ui/table-cell/UiTableCell.vue'
13
- import UiTag from '@core/components/ui/tag/UiTag.vue'
14
12
  import UiTagsList from '@core/components/ui/tag/UiTagsList.vue'
15
13
  import type { MaybeArray } from '@core/types/utility.type'
16
14
  import { toArray } from '@core/utils/to-array.utils'
@@ -0,0 +1,30 @@
1
+ <template>
2
+ <UiTertiaryTag v-if="parsedTag" :accent>
3
+ <template #term>{{ parsedTag.term }}</template>
4
+ {{ parsedTag.label }}
5
+ </UiTertiaryTag>
6
+ <UiTag v-else :accent :variant>
7
+ {{ value }}
8
+ </UiTag>
9
+ </template>
10
+
11
+ <script lang="ts" setup>
12
+ import UiTag, { type TagAccent } from '@core/components/ui/tag/UiTag.vue'
13
+ import UiTertiaryTag from '@core/components/ui/tag/UiTertiaryTag.vue'
14
+ import { parseTag } from '@core/utils/parse-tag.util'
15
+ import { computed } from 'vue'
16
+
17
+ type TagVariant = 'primary' | 'secondary'
18
+
19
+ const {
20
+ value,
21
+ accent = 'info',
22
+ variant = 'secondary',
23
+ } = defineProps<{
24
+ value: string
25
+ accent?: TagAccent
26
+ variant?: TagVariant
27
+ }>()
28
+
29
+ const parsedTag = computed(() => parseTag(value))
30
+ </script>
@@ -1,31 +1,28 @@
1
- <!-- v4 -->
1
+ <!-- v8 -->
2
2
  <template>
3
- <span :class="classNames" class="ui-chip typo-body-regular-small" @click="emit('edit')">
4
- <ChipIcon :disabled :icon />
5
- <span class="content text-ellipsis">
3
+ <span :class="classNames" class="ui-chip typo-body-regular-small">
4
+ <span class="text-ellipsis">
6
5
  <slot />
7
6
  </span>
8
- <ChipRemoveIcon v-if="!disabled" :accent @click.stop="emit('remove')" />
7
+ <button v-if="!disabled" class="icon" type="button" @click.stop="emit('remove')">
8
+ <VtsIcon name="action:close-cancel-clear" size="medium" />
9
+ </button>
9
10
  </span>
10
11
  </template>
11
12
 
12
13
  <script lang="ts" setup>
13
- import ChipIcon from '@core/components/ui/chip/ChipIcon.vue'
14
- import ChipRemoveIcon from '@core/components/ui/chip/ChipRemoveIcon.vue'
15
- import type { IconName } from '@core/icons'
14
+ import VtsIcon from '@core/components/icon/VtsIcon.vue'
16
15
  import { toVariants } from '@core/utils/to-variants.util'
17
16
  import { computed } from 'vue'
18
17
 
19
18
  export type ChipAccent = 'info' | 'success' | 'warning' | 'danger'
20
19
 
21
- const props = defineProps<{
20
+ const { accent, disabled } = defineProps<{
22
21
  accent: ChipAccent
23
- icon?: IconName
24
22
  disabled?: boolean
25
23
  }>()
26
24
 
27
25
  const emit = defineEmits<{
28
- edit: []
29
26
  remove: []
30
27
  }>()
31
28
 
@@ -36,8 +33,8 @@ defineSlots<{
36
33
  const classNames = computed(() => {
37
34
  return [
38
35
  toVariants({
39
- accent: props.accent,
40
- muted: props.disabled,
36
+ accent,
37
+ muted: disabled,
41
38
  }),
42
39
  ]
43
40
  })
@@ -45,37 +42,51 @@ const classNames = computed(() => {
45
42
 
46
43
  <style lang="postcss" scoped>
47
44
  .ui-chip {
48
- display: flex;
45
+ display: inline-flex;
49
46
  align-items: center;
50
- gap: 0.8rem;
51
- padding: 0.4rem 0.8rem;
47
+ gap: 0.4rem;
48
+ padding: 0.4rem 1.2rem;
52
49
  border-radius: 10rem;
53
50
  color: var(--color-neutral-txt-primary);
54
- cursor: pointer;
55
51
  min-height: 2.4rem;
56
52
  vertical-align: middle;
57
53
  white-space: nowrap;
58
- min-width: 0;
59
54
 
60
55
  &.muted {
61
56
  color: var(--color-neutral-txt-secondary);
62
57
  pointer-events: none;
63
58
  }
64
59
 
65
- .content {
66
- line-height: 1.6rem;
60
+ .icon {
61
+ border-radius: 0 10rem 10rem 0;
62
+ padding: 0.4rem;
63
+ margin: -0.4rem -1.2rem -0.4rem 0;
64
+ align-self: stretch;
65
+ display: flex;
66
+ align-items: center;
67
+ justify-content: center;
68
+ cursor: pointer;
69
+ background-color: transparent;
70
+ border: none;
67
71
  }
68
72
 
73
+ .icon:focus-visible {
74
+ outline: 0.2rem solid var(--color-brand-txt-base);
75
+ outline-offset: 0.2rem;
76
+ }
69
77
  /* COLOR VARIANTS */
70
-
71
78
  &.accent--info {
72
79
  background-color: var(--color-info-background-selected);
73
80
 
74
- &:is(:hover, :focus-visible) {
81
+ .icon {
82
+ color: var(--color-info-txt-hover);
83
+ }
84
+
85
+ .icon:hover {
75
86
  background-color: var(--color-info-background-hover);
76
87
  }
77
88
 
78
- &:active {
89
+ .icon:active {
79
90
  background-color: var(--color-info-background-active);
80
91
  }
81
92
 
@@ -87,11 +98,15 @@ const classNames = computed(() => {
87
98
  &.accent--success {
88
99
  background-color: var(--color-success-background-selected);
89
100
 
90
- &:is(:hover, :focus-visible) {
101
+ .icon {
102
+ color: var(--color-success-txt-hover);
103
+ }
104
+
105
+ .icon:hover {
91
106
  background-color: var(--color-success-background-hover);
92
107
  }
93
108
 
94
- &:active {
109
+ .icon:active {
95
110
  background-color: var(--color-success-background-active);
96
111
  }
97
112
 
@@ -103,11 +118,15 @@ const classNames = computed(() => {
103
118
  &.accent--warning {
104
119
  background-color: var(--color-warning-background-selected);
105
120
 
106
- &:is(:hover, :focus-visible) {
121
+ .icon {
122
+ color: var(--color-warning-txt-hover);
123
+ }
124
+
125
+ .icon:hover {
107
126
  background-color: var(--color-warning-background-hover);
108
127
  }
109
128
 
110
- &:active {
129
+ .icon:active {
111
130
  background-color: var(--color-warning-background-active);
112
131
  }
113
132
 
@@ -119,11 +138,15 @@ const classNames = computed(() => {
119
138
  &.accent--danger {
120
139
  background-color: var(--color-danger-background-selected);
121
140
 
122
- &:is(:hover, :focus-visible) {
141
+ .icon {
142
+ color: var(--color-danger-txt-hover);
143
+ }
144
+
145
+ .icon:hover {
123
146
  background-color: var(--color-danger-background-hover);
124
147
  }
125
148
 
126
- &:active {
149
+ .icon:active {
127
150
  background-color: var(--color-danger-background-active);
128
151
  }
129
152
 
@@ -1,8 +1,8 @@
1
- <!-- v4 -->
1
+ <!-- v6 -->
2
2
  <template>
3
3
  <div class="ui-info">
4
- <VtsIcon class="icon" :name="icon" size="medium" />
5
- <p v-tooltip="!wrap" class="typo-body-regular-small label" :class="{ 'text-ellipsis': !wrap }">
4
+ <VtsIcon class="icon" :name="icon" :size />
5
+ <p v-tooltip="!wrap" class="label" :class="[textSize, { 'text-ellipsis': !wrap }]">
6
6
  <slot />
7
7
  </p>
8
8
  </div>
@@ -15,10 +15,12 @@ import type { IconName } from '@core/icons'
15
15
  import { useMapper } from '@core/packages/mapper'
16
16
 
17
17
  export type InfoAccent = 'info' | 'success' | 'warning' | 'danger' | 'muted'
18
+ type Size = 'small' | 'medium'
18
19
 
19
- const { accent } = defineProps<{
20
+ const { accent, size = 'medium' } = defineProps<{
20
21
  accent: InfoAccent
21
22
  wrap?: boolean
23
+ size?: Size
22
24
  }>()
23
25
 
24
26
  defineSlots<{
@@ -36,16 +38,25 @@ const icon = useMapper<InfoAccent, IconName>(
36
38
  },
37
39
  'muted'
38
40
  )
41
+
42
+ const textSize = useMapper<Size, any>(
43
+ () => size,
44
+ {
45
+ small: 'typo-body-regular-small',
46
+ medium: 'typo-body-regular',
47
+ },
48
+ 'medium'
49
+ )
39
50
  </script>
40
51
 
41
52
  <style lang="postcss" scoped>
42
53
  .ui-info {
43
- align-items: start;
54
+ align-items: baseline;
44
55
  display: flex;
45
56
  gap: 0.8rem;
46
57
 
47
58
  .icon {
48
- font-size: 1.6rem;
59
+ transform: translateY(0.2ex);
49
60
  }
50
61
 
51
62
  .label:empty {
@@ -133,6 +133,10 @@ defineExpose({ focus })
133
133
  color: var(--color-brand-txt-base);
134
134
  }
135
135
 
136
+ &.disabled .right-icon {
137
+ color: var(--color-neutral-txt-secondary);
138
+ }
139
+
136
140
  .readonly-input {
137
141
  border: none;
138
142
  width: 0;
@@ -171,7 +175,7 @@ defineExpose({ focus })
171
175
  border-color: var(--color-brand-item-active);
172
176
  }
173
177
 
174
- &:has(input:disabled) {
178
+ &.disabled {
175
179
  border-color: var(--color-neutral-border);
176
180
  background-color: var(--color-neutral-background-disabled);
177
181
  }
@@ -188,7 +192,7 @@ defineExpose({ focus })
188
192
  border-color: var(--color-warning-item-active);
189
193
  }
190
194
 
191
- &:has(input:disabled) {
195
+ &.disabled {
192
196
  border-color: var(--color-neutral-border);
193
197
  background-color: var(--color-neutral-background-disabled);
194
198
  }
@@ -205,7 +209,7 @@ defineExpose({ focus })
205
209
  border-color: var(--color-danger-item-active);
206
210
  }
207
211
 
208
- &:has(input:disabled) {
212
+ &.disabled {
209
213
  border-color: var(--color-neutral-border);
210
214
  background-color: var(--color-neutral-background-disabled);
211
215
  }
@@ -1,15 +1,17 @@
1
- <!-- v3 -->
1
+ <!-- v6 -->
2
2
  <template>
3
3
  <component :is="component" :class="classes" class="ui-link" v-bind="attributes">
4
4
  <VtsIcon :name="icon" size="medium" />
5
5
  <slot />
6
- <VtsIcon v-if="attributes.target === '_blank'" name="fa:up-right-from-square" size="medium" class="external-icon" />
6
+ <VtsIcon v-if="attributes.target === '_blank'" name="action:open-in-new-tab" size="medium" class="external-icon" />
7
+ <VtsIcon v-if="isPrimary" v-tooltip="primaryTooltip" name="status:primary-circle" size="medium" />
7
8
  </component>
8
9
  </template>
9
10
 
10
11
  <script lang="ts" setup>
11
12
  import VtsIcon from '@core/components/icon/VtsIcon.vue'
12
13
  import { type LinkOptions, useLinkComponent } from '@core/composables/link-component.composable'
14
+ import { vTooltip } from '@core/directives/tooltip.directive'
13
15
  import type { IconName } from '@core/icons'
14
16
  import { computed } from 'vue'
15
17
 
@@ -17,6 +19,8 @@ const props = defineProps<
17
19
  LinkOptions & {
18
20
  size: 'small' | 'medium'
19
21
  icon?: IconName
22
+ isPrimary?: boolean
23
+ primaryTooltip?: string
20
24
  }
21
25
  >()
22
26
 
@@ -1,34 +1,27 @@
1
- <!-- v3 -->
2
- <!-- TODO: implement tertiary variant to bump to v4 -->
1
+ <!-- v5 -->
3
2
  <template>
4
- <span :class="toVariants({ accent, variant })" class="ui-tag typo-body-regular-small">
5
- <slot name="icon">
6
- <VtsIcon :name="icon" size="medium" />
7
- </slot>
8
- <span>
9
- <slot />
10
- </span>
3
+ <span :class="className" class="ui-tag typo-body-regular-small">
4
+ <slot />
11
5
  </span>
12
6
  </template>
13
7
 
14
8
  <script lang="ts" setup>
15
- import VtsIcon from '@core/components/icon/VtsIcon.vue'
16
- import type { IconName } from '@core/icons'
17
9
  import { toVariants } from '@core/utils/to-variants.util'
10
+ import { computed } from 'vue'
18
11
 
19
- type TagAccent = 'info' | 'neutral' | 'success' | 'warning' | 'danger' | 'muted'
12
+ export type TagAccent = 'info' | 'neutral' | 'success' | 'warning' | 'danger' | 'muted'
20
13
  type TagVariant = 'primary' | 'secondary'
21
14
 
22
- defineProps<{
15
+ const { accent, variant } = defineProps<{
23
16
  accent: TagAccent
24
17
  variant: TagVariant
25
- icon?: IconName
26
18
  }>()
27
19
 
28
20
  defineSlots<{
29
21
  default(): any
30
- icon?(): any
31
22
  }>()
23
+
24
+ const className = computed(() => toVariants({ accent, variant }))
32
25
  </script>
33
26
 
34
27
  <style lang="postcss" scoped>
@@ -39,9 +32,9 @@ defineSlots<{
39
32
  gap: 0.8rem;
40
33
  white-space: normal;
41
34
  word-break: break-word;
42
- padding: 0.2rem 0.8rem;
43
- border-radius: 0.4rem;
35
+ padding: 0.35rem 0.8rem;
44
36
  vertical-align: middle;
37
+ border-radius: 0.4rem;
45
38
 
46
39
  /* COLOR VARIANTS */
47
40
 
@@ -0,0 +1,39 @@
1
+ <!-- v1 -->
2
+ <template>
3
+ <span class="ui-tertiary-tag">
4
+ <UiTag :accent variant="primary" class="radius-left">
5
+ <slot name="term" />
6
+ </UiTag>
7
+ <UiTag :accent variant="secondary" class="radius-right">
8
+ <slot />
9
+ </UiTag>
10
+ </span>
11
+ </template>
12
+
13
+ <script lang="ts" setup>
14
+ import UiTag, { type TagAccent } from '@core/components/ui/tag/UiTag.vue'
15
+
16
+ defineProps<{
17
+ accent: TagAccent
18
+ }>()
19
+
20
+ defineSlots<{
21
+ default(): any
22
+ term(): any
23
+ }>()
24
+ </script>
25
+
26
+ <style lang="postcss" scoped>
27
+ .ui-tertiary-tag {
28
+ display: inline-flex;
29
+ flex-direction: row;
30
+
31
+ :deep(.radius-left) {
32
+ border-radius: 0.4rem 0 0 0.4rem;
33
+ }
34
+
35
+ :deep(.radius-right) {
36
+ border-radius: 0 0.4rem 0.4rem 0;
37
+ }
38
+ }
39
+ </style>
@@ -39,7 +39,7 @@ import {
39
39
  function constructIcon(icon: IconDefinition): IconSingleConfig {
40
40
  return {
41
41
  icon,
42
- color: 'var(--color-neutral-txt-primary)',
42
+ color: 'currentColor',
43
43
  }
44
44
  }
45
45
 
@@ -29,7 +29,7 @@ import {
29
29
  function constructIcon(icon: IconDefinition): IconSingleConfig {
30
30
  return {
31
31
  icon,
32
- color: 'var(--color-neutral-txt-primary)',
32
+ color: 'currentColor',
33
33
  }
34
34
  }
35
35