@citizenplane/pimp 18.24.0 → 18.25.1
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/dist/components/CpDynamicMenu.vue.d.ts +51 -0
- package/dist/components/CpDynamicMenu.vue.d.ts.map +1 -0
- package/dist/components/CpLanguageSwitcher.vue.d.ts +35 -0
- package/dist/components/CpLanguageSwitcher.vue.d.ts.map +1 -0
- package/dist/components/CpMenu.vue.d.ts.map +1 -1
- package/dist/components/index.d.ts +4 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/pimp.es.js +1204 -1005
- package/dist/pimp.umd.js +36 -36
- package/dist/style.css +1 -1
- package/package.json +2 -1
- package/src/components/CpDynamicMenu.vue +226 -0
- package/src/components/CpLanguageSwitcher.vue +151 -0
- package/src/components/CpMenu.vue +36 -8
- package/src/components/index.ts +7 -0
- package/src/stories/CpDynamicMenu.stories.ts +279 -0
- package/src/stories/CpLanguageSwitcher.stories.ts +196 -0
- package/src/stories/CpMenu.stories.ts +37 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@citizenplane/pimp",
|
|
3
|
-
"version": "18.
|
|
3
|
+
"version": "18.25.1",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "storybook dev -p 8081",
|
|
6
6
|
"build-storybook": "storybook build --output-dir ./docs",
|
|
@@ -75,6 +75,7 @@
|
|
|
75
75
|
"@vitest/coverage-v8": "4.1.9",
|
|
76
76
|
"@vue/test-utils": "2.4.11",
|
|
77
77
|
"commitlint-config-gitmoji": "2.3.1",
|
|
78
|
+
"flag-icons": "7.5.0",
|
|
78
79
|
"happy-dom": "20.14.5",
|
|
79
80
|
"husky": "9.1.7",
|
|
80
81
|
"lint-staged": "17.2.0",
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="cpDynamicMenu">
|
|
3
|
+
<cp-menu v-if="!isMobile" ref="menuRef" v-bind="menuProps" @hide="onMenuHide" @show="onMenuShow">
|
|
4
|
+
<template #trigger>
|
|
5
|
+
<slot name="trigger" />
|
|
6
|
+
</template>
|
|
7
|
+
<slot />
|
|
8
|
+
</cp-menu>
|
|
9
|
+
<template v-else>
|
|
10
|
+
<div
|
|
11
|
+
ref="triggerRef"
|
|
12
|
+
:aria-controls="menuId"
|
|
13
|
+
:aria-expanded="isSheetVisible"
|
|
14
|
+
:aria-haspopup="popupType"
|
|
15
|
+
class="cpDynamicMenu__trigger"
|
|
16
|
+
@click="toggleSheet"
|
|
17
|
+
>
|
|
18
|
+
<slot name="trigger" />
|
|
19
|
+
</div>
|
|
20
|
+
<cp-bottom-sheet v-model="isSheetVisible" v-bind="bottomSheetProps">
|
|
21
|
+
<template v-if="hasSheetTitle" #header>
|
|
22
|
+
<slot name="sheet-title">
|
|
23
|
+
<cp-heading class="cpDynamicMenu__sheetTitle" heading-level="h3">{{ sheetTitle }}</cp-heading>
|
|
24
|
+
</slot>
|
|
25
|
+
</template>
|
|
26
|
+
<cp-menu-list v-if="hasItems" :id="menuId" :items="items" @async-complete="onItemClick" @click="onItemClick" />
|
|
27
|
+
<slot v-else />
|
|
28
|
+
<template #footer>
|
|
29
|
+
<slot name="footer" />
|
|
30
|
+
</template>
|
|
31
|
+
</cp-bottom-sheet>
|
|
32
|
+
</template>
|
|
33
|
+
</div>
|
|
34
|
+
</template>
|
|
35
|
+
|
|
36
|
+
<script setup lang="ts">
|
|
37
|
+
import type { MenuItem } from 'primevue/menuitem'
|
|
38
|
+
import { computed, ref, useId, useSlots, useTemplateRef, watch } from 'vue'
|
|
39
|
+
|
|
40
|
+
import type { CpMenuPlacement } from '@/components/CpMenu.vue'
|
|
41
|
+
|
|
42
|
+
import { useIsBreakpoint } from '@/composables/useIsBreakpoint'
|
|
43
|
+
|
|
44
|
+
import CpBottomSheet from '@/components/CpBottomSheet.vue'
|
|
45
|
+
import CpHeading from '@/components/CpHeading.vue'
|
|
46
|
+
import CpMenu from '@/components/CpMenu.vue'
|
|
47
|
+
import CpMenuList from '@/components/CpMenuList.vue'
|
|
48
|
+
|
|
49
|
+
import { Breakpoints } from '@/constants/layout'
|
|
50
|
+
import { getKeyboardFocusableElements } from '@/helpers/dom'
|
|
51
|
+
|
|
52
|
+
interface Props {
|
|
53
|
+
animationDuration?: number
|
|
54
|
+
breakpoint?: number
|
|
55
|
+
class?: string
|
|
56
|
+
contentClass?: string
|
|
57
|
+
expandOnContentDrag?: boolean
|
|
58
|
+
halfAndExpand?: boolean
|
|
59
|
+
items?: MenuItem[]
|
|
60
|
+
keepOpenOnClick?: boolean
|
|
61
|
+
placement?: CpMenuPlacement
|
|
62
|
+
preventClose?: boolean
|
|
63
|
+
sheetTitle?: string
|
|
64
|
+
snapPoints?: Array<number | `${number}%`>
|
|
65
|
+
swipeCloseThreshold?: string
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const props = defineProps<Props>()
|
|
69
|
+
|
|
70
|
+
const emits = defineEmits<{
|
|
71
|
+
hide: []
|
|
72
|
+
show: []
|
|
73
|
+
}>()
|
|
74
|
+
|
|
75
|
+
const slots = useSlots()
|
|
76
|
+
|
|
77
|
+
const isMobile = useIsBreakpoint(props.breakpoint ?? Breakpoints.MOBILE)
|
|
78
|
+
|
|
79
|
+
const menuRef = useTemplateRef('menuRef')
|
|
80
|
+
const triggerRef = useTemplateRef<HTMLElement>('triggerRef')
|
|
81
|
+
|
|
82
|
+
const isMenuOpen = ref(false)
|
|
83
|
+
const isSheetVisible = ref(false)
|
|
84
|
+
const menuId = useId()
|
|
85
|
+
|
|
86
|
+
const hasItems = computed(() => Boolean(props.items?.length))
|
|
87
|
+
const hasSheetTitle = computed(() => Boolean(props.sheetTitle || slots['sheet-title']))
|
|
88
|
+
|
|
89
|
+
const popupType = computed(() => {
|
|
90
|
+
if (hasItems.value) return 'menu'
|
|
91
|
+
return 'dialog'
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
const menuProps = computed(() => ({
|
|
95
|
+
class: props.class,
|
|
96
|
+
forcePopover: true,
|
|
97
|
+
items: props.items,
|
|
98
|
+
keepOpenOnClick: props.keepOpenOnClick,
|
|
99
|
+
placement: props.placement,
|
|
100
|
+
}))
|
|
101
|
+
|
|
102
|
+
const sheetContentClass = computed(() => {
|
|
103
|
+
if (!hasItems.value) return props.contentClass
|
|
104
|
+
if (!props.contentClass) return 'cpDynamicMenu__menuListContent'
|
|
105
|
+
return `cpDynamicMenu__menuListContent ${props.contentClass}`
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
const sheetBaseClass = computed(() => {
|
|
109
|
+
if (hasSheetTitle.value) return 'cpDynamicMenu__sheet'
|
|
110
|
+
return 'cpDynamicMenu__sheet cpDynamicMenu__sheet--hasNoTitle'
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
const sheetClass = computed(() => {
|
|
114
|
+
if (!props.class) return sheetBaseClass.value
|
|
115
|
+
return `${sheetBaseClass.value} ${props.class}`
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
const bottomSheetProps = computed(() => ({
|
|
119
|
+
animationDuration: props.animationDuration,
|
|
120
|
+
canBackdropClose: !props.preventClose,
|
|
121
|
+
canSwipeClose: !props.preventClose,
|
|
122
|
+
class: sheetClass.value,
|
|
123
|
+
contentClass: sheetContentClass.value,
|
|
124
|
+
expandOnContentDrag: props.expandOnContentDrag,
|
|
125
|
+
halfAndExpand: props.halfAndExpand,
|
|
126
|
+
preventClose: props.preventClose,
|
|
127
|
+
snapPoints: props.snapPoints,
|
|
128
|
+
swipeCloseThreshold: props.swipeCloseThreshold,
|
|
129
|
+
}))
|
|
130
|
+
|
|
131
|
+
const focusTrigger = () => {
|
|
132
|
+
if (!triggerRef.value) return
|
|
133
|
+
const [firstFocusableElement] = getKeyboardFocusableElements(triggerRef.value) as HTMLElement[]
|
|
134
|
+
firstFocusableElement?.focus()
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// CpMenu emits show/hide twice when driven imperatively, so only real state changes are relayed.
|
|
138
|
+
const onMenuShow = () => {
|
|
139
|
+
if (isMenuOpen.value) return
|
|
140
|
+
isMenuOpen.value = true
|
|
141
|
+
emits('show')
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const onMenuHide = () => {
|
|
145
|
+
if (!isMenuOpen.value) return
|
|
146
|
+
isMenuOpen.value = false
|
|
147
|
+
emits('hide')
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const toggleSheet = () => {
|
|
151
|
+
isSheetVisible.value = !isSheetVisible.value
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const onItemClick = () => {
|
|
155
|
+
if (props.keepOpenOnClick) return
|
|
156
|
+
isSheetVisible.value = false
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const show = (event: Event) => {
|
|
160
|
+
if (isMobile.value) {
|
|
161
|
+
isSheetVisible.value = true
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
menuRef.value?.show(event)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const hide = () => {
|
|
168
|
+
if (isMobile.value) {
|
|
169
|
+
isSheetVisible.value = false
|
|
170
|
+
return
|
|
171
|
+
}
|
|
172
|
+
menuRef.value?.hide()
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const toggle = (event: Event) => {
|
|
176
|
+
if (isMobile.value) {
|
|
177
|
+
toggleSheet()
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
menuRef.value?.toggle(event)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
watch(isSheetVisible, (isVisible) => {
|
|
184
|
+
if (isVisible) {
|
|
185
|
+
emits('show')
|
|
186
|
+
return
|
|
187
|
+
}
|
|
188
|
+
emits('hide')
|
|
189
|
+
focusTrigger()
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
watch(isMobile, (isMobileViewport) => {
|
|
193
|
+
if (!isMobileViewport) {
|
|
194
|
+
isSheetVisible.value = false
|
|
195
|
+
return
|
|
196
|
+
}
|
|
197
|
+
if (isMenuOpen.value) menuRef.value?.hide()
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
defineExpose({ show, hide, toggle })
|
|
201
|
+
</script>
|
|
202
|
+
|
|
203
|
+
<style lang="scss">
|
|
204
|
+
.cpDynamicMenu {
|
|
205
|
+
display: contents;
|
|
206
|
+
|
|
207
|
+
&__trigger {
|
|
208
|
+
display: inline-flex;
|
|
209
|
+
cursor: pointer;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
&__sheetTitle {
|
|
213
|
+
font-size: var(--cp-text-size-2xl);
|
|
214
|
+
font-weight: 700;
|
|
215
|
+
line-height: var(--cp-line-height-2xl);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
&__menuListContent.cpBottomSheet__content {
|
|
219
|
+
padding: var(--cp-spacing-sm) 0;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
&__sheet--hasNoTitle .cpBottomSheet__header {
|
|
223
|
+
padding-bottom: 0;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
</style>
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<cp-dynamic-menu
|
|
3
|
+
ref="dynamicMenuRef"
|
|
4
|
+
:breakpoint="breakpoint"
|
|
5
|
+
:class="props.class"
|
|
6
|
+
content-class="cpLanguageSwitcher__content"
|
|
7
|
+
:placement="placement"
|
|
8
|
+
>
|
|
9
|
+
<template #trigger>
|
|
10
|
+
<slot name="trigger">
|
|
11
|
+
<cp-button appearance="tertiary" color="accent" size="sm">
|
|
12
|
+
<template #leading-icon>
|
|
13
|
+
<cp-icon type="globe" />
|
|
14
|
+
</template>
|
|
15
|
+
{{ uppercasedCurrentCode }}
|
|
16
|
+
</cp-button>
|
|
17
|
+
</slot>
|
|
18
|
+
</template>
|
|
19
|
+
<ul class="cpLanguageSwitcher__list">
|
|
20
|
+
<li v-for="lang in langs" :key="lang.code">
|
|
21
|
+
<button
|
|
22
|
+
:aria-current="getAriaCurrent(lang.code)"
|
|
23
|
+
class="cpLanguageSwitcher__lang"
|
|
24
|
+
:class="getLangClass(lang.code)"
|
|
25
|
+
type="button"
|
|
26
|
+
@click="selectLanguage(lang.code)"
|
|
27
|
+
>
|
|
28
|
+
<span class="cpLanguageSwitcher__left">
|
|
29
|
+
<span v-if="lang.flag" class="cpLanguageSwitcher__flag" :class="getFlagClass(lang.flag)" />
|
|
30
|
+
<span class="cpLanguageSwitcher__name">{{ lang.name }}</span>
|
|
31
|
+
</span>
|
|
32
|
+
<cp-icon v-if="isCurrentLanguage(lang.code)" class="cpLanguageSwitcher__check" size="20" type="check" />
|
|
33
|
+
</button>
|
|
34
|
+
</li>
|
|
35
|
+
</ul>
|
|
36
|
+
</cp-dynamic-menu>
|
|
37
|
+
</template>
|
|
38
|
+
|
|
39
|
+
<script setup lang="ts">
|
|
40
|
+
import { computed, useTemplateRef } from 'vue'
|
|
41
|
+
|
|
42
|
+
import type { CpMenuPlacement } from '@/components/CpMenu.vue'
|
|
43
|
+
|
|
44
|
+
import CpButton from '@/components/CpButton.vue'
|
|
45
|
+
import CpDynamicMenu from '@/components/CpDynamicMenu.vue'
|
|
46
|
+
import CpIcon from '@/components/CpIcon.vue'
|
|
47
|
+
|
|
48
|
+
export interface LanguageOption {
|
|
49
|
+
code: string
|
|
50
|
+
flag?: string
|
|
51
|
+
name: string
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface Props {
|
|
55
|
+
breakpoint?: number
|
|
56
|
+
class?: string
|
|
57
|
+
langs: LanguageOption[]
|
|
58
|
+
placement?: CpMenuPlacement
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const props = defineProps<Props>()
|
|
62
|
+
|
|
63
|
+
const currentCode = defineModel<string>()
|
|
64
|
+
|
|
65
|
+
const dynamicMenuRef = useTemplateRef('dynamicMenuRef')
|
|
66
|
+
|
|
67
|
+
const uppercasedCurrentCode = computed(() => {
|
|
68
|
+
if (!currentCode.value) return ''
|
|
69
|
+
return currentCode.value.toUpperCase()
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
const isCurrentLanguage = (code: string) => code === currentCode.value
|
|
73
|
+
|
|
74
|
+
const getLangClass = (code: string) => ({ 'cpLanguageSwitcher__lang--isSelected': isCurrentLanguage(code) })
|
|
75
|
+
|
|
76
|
+
// flag-icons is a devDependency only: the consumer app loads flag-icons.min.css itself.
|
|
77
|
+
const getFlagClass = (flag: string) => `fi fi-${flag.toLowerCase()}`
|
|
78
|
+
|
|
79
|
+
const getAriaCurrent = (code: string) => {
|
|
80
|
+
if (!isCurrentLanguage(code)) return undefined
|
|
81
|
+
return 'true'
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const selectLanguage = (code: string) => {
|
|
85
|
+
if (!isCurrentLanguage(code)) currentCode.value = code
|
|
86
|
+
dynamicMenuRef.value?.hide()
|
|
87
|
+
}
|
|
88
|
+
</script>
|
|
89
|
+
|
|
90
|
+
<style lang="scss">
|
|
91
|
+
.cpLanguageSwitcher {
|
|
92
|
+
&__content.cpBottomSheet__content {
|
|
93
|
+
padding: var(--cp-spacing-sm) 0;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
&__list {
|
|
97
|
+
display: flex;
|
|
98
|
+
flex-direction: column;
|
|
99
|
+
padding: 0 var(--cp-spacing-sm);
|
|
100
|
+
margin: 0;
|
|
101
|
+
gap: var(--cp-spacing-sm);
|
|
102
|
+
list-style: none;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
&__lang {
|
|
106
|
+
@extend %u-focus-outline;
|
|
107
|
+
|
|
108
|
+
display: flex;
|
|
109
|
+
width: 100%;
|
|
110
|
+
align-items: center;
|
|
111
|
+
justify-content: space-between;
|
|
112
|
+
padding: var(--cp-spacing-lg) var(--cp-spacing-xl) var(--cp-spacing-lg) var(--cp-spacing-lg);
|
|
113
|
+
border: none;
|
|
114
|
+
border-radius: var(--cp-radius-md);
|
|
115
|
+
background-color: transparent;
|
|
116
|
+
color: var(--cp-text-primary);
|
|
117
|
+
cursor: pointer;
|
|
118
|
+
gap: var(--cp-spacing-md);
|
|
119
|
+
|
|
120
|
+
&:hover {
|
|
121
|
+
background-color: var(--cp-background-primary-hover);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
&--isSelected {
|
|
125
|
+
background-color: var(--cp-background-accent-primary);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
&__left {
|
|
130
|
+
display: flex;
|
|
131
|
+
align-items: center;
|
|
132
|
+
gap: var(--cp-spacing-lg);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
&__flag {
|
|
136
|
+
flex: none;
|
|
137
|
+
border-radius: var(--cp-radius-sm);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
&__name {
|
|
141
|
+
font-size: var(--cp-text-size-md);
|
|
142
|
+
font-weight: 500;
|
|
143
|
+
line-height: var(--cp-line-height-md);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
&__check {
|
|
147
|
+
flex: none;
|
|
148
|
+
color: var(--cp-text-accent-primary);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
</style>
|
|
@@ -63,10 +63,11 @@
|
|
|
63
63
|
</template>
|
|
64
64
|
|
|
65
65
|
<script setup lang="ts">
|
|
66
|
+
import { useResizeObserver } from '@vueuse/core'
|
|
66
67
|
import PrimevueDrawer from 'primevue/drawer'
|
|
67
68
|
import type { MenuItem } from 'primevue/menuitem'
|
|
68
69
|
import PrimevuePopover from 'primevue/popover'
|
|
69
|
-
import { computed, nextTick, ref, useId } from 'vue'
|
|
70
|
+
import { computed, nextTick, ref, shallowRef, useId } from 'vue'
|
|
70
71
|
|
|
71
72
|
import { useIsBreakpoint } from '@/composables/useIsBreakpoint'
|
|
72
73
|
|
|
@@ -107,6 +108,7 @@ const trigger = ref<HTMLElement | null>(null)
|
|
|
107
108
|
const popover = ref<InstanceType<typeof PrimevuePopover>>()
|
|
108
109
|
const closeButton = ref<InstanceType<typeof CpButton> | null>(null)
|
|
109
110
|
const isOpen = ref(false)
|
|
111
|
+
const observedPopoverElement = shallowRef<HTMLElement | null>(null)
|
|
110
112
|
const menuId = useId()
|
|
111
113
|
|
|
112
114
|
const isMobileViewport = useIsBreakpoint(Breakpoints.TABLET)
|
|
@@ -186,30 +188,56 @@ const focusCloseButton = () => {
|
|
|
186
188
|
|
|
187
189
|
const onHide = () => {
|
|
188
190
|
isOpen.value = false
|
|
191
|
+
observedPopoverElement.value = null
|
|
189
192
|
focusTrigger()
|
|
190
193
|
emits('hide')
|
|
191
194
|
}
|
|
192
195
|
|
|
193
|
-
const
|
|
194
|
-
if (!popover.value) return
|
|
196
|
+
const getPopoverElements = () => {
|
|
197
|
+
if (!popover.value) return null
|
|
195
198
|
|
|
196
199
|
const { container: popoverElement, target: triggerElement } = popover.value as unknown as {
|
|
197
200
|
container: HTMLElement | null
|
|
198
201
|
target: HTMLElement | null
|
|
199
202
|
}
|
|
200
203
|
|
|
201
|
-
if (!popoverElement || !triggerElement) return
|
|
204
|
+
if (!popoverElement || !triggerElement) return null
|
|
205
|
+
|
|
206
|
+
return { popoverElement, triggerElement }
|
|
207
|
+
}
|
|
202
208
|
|
|
203
|
-
|
|
204
|
-
const
|
|
209
|
+
const getPopoverRightEdge = (popoverElement: HTMLElement) => {
|
|
210
|
+
const offsetParent = popoverElement.offsetParent as HTMLElement | null
|
|
211
|
+
const offsetParentLeft = offsetParent?.getBoundingClientRect().left ?? 0
|
|
205
212
|
|
|
206
|
-
|
|
213
|
+
return offsetParentLeft + popoverElement.offsetLeft + popoverElement.offsetWidth
|
|
207
214
|
}
|
|
208
215
|
|
|
216
|
+
const alignPopoverEnd = () => {
|
|
217
|
+
const elements = getPopoverElements()
|
|
218
|
+
if (!elements) return
|
|
219
|
+
|
|
220
|
+
const { popoverElement, triggerElement } = elements
|
|
221
|
+
popoverElement.style.marginLeft = ''
|
|
222
|
+
|
|
223
|
+
const gapAfterTriggerEnd = getPopoverRightEdge(popoverElement) - triggerElement.getBoundingClientRect().right
|
|
224
|
+
if (!gapAfterTriggerEnd) return
|
|
225
|
+
|
|
226
|
+
popoverElement.style.marginLeft = `${gapAfterTriggerEnd * -1}px`
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
useResizeObserver(observedPopoverElement, alignPopoverEnd)
|
|
230
|
+
|
|
209
231
|
const onShow = async () => {
|
|
210
232
|
isOpen.value = true
|
|
211
|
-
if (isPopoverPlacementEnd.value) alignPopoverEnd()
|
|
212
233
|
emits('show')
|
|
234
|
+
|
|
235
|
+
if (!isPopoverPlacementEnd.value) return
|
|
236
|
+
|
|
237
|
+
await nextTick()
|
|
238
|
+
|
|
239
|
+
alignPopoverEnd()
|
|
240
|
+
observedPopoverElement.value = getPopoverElements()?.popoverElement ?? null
|
|
213
241
|
}
|
|
214
242
|
|
|
215
243
|
defineExpose({ show, hide, toggle })
|
package/src/components/index.ts
CHANGED
|
@@ -36,12 +36,14 @@ import CpDate from './CpDate.vue'
|
|
|
36
36
|
import CpDatepicker from './CpDatepicker.vue'
|
|
37
37
|
import CpDialog from './CpDialog.vue'
|
|
38
38
|
import CpDynamicDialog from './CpDynamicDialog.vue'
|
|
39
|
+
import CpDynamicMenu from './CpDynamicMenu.vue'
|
|
39
40
|
import CpFlightTabs from './CpFlightTabs.vue'
|
|
40
41
|
import CpHeading from './CpHeading.vue'
|
|
41
42
|
import CpIcon from './CpIcon.vue'
|
|
42
43
|
import CpIllustration from './CpIllustration.vue'
|
|
43
44
|
import CpInput from './CpInput.vue'
|
|
44
45
|
import CpItemActions from './CpItemActions.vue'
|
|
46
|
+
import CpLanguageSwitcher from './CpLanguageSwitcher.vue'
|
|
45
47
|
import CpLoader from './CpLoader.vue'
|
|
46
48
|
import CpMenu from './CpMenu.vue'
|
|
47
49
|
import CpMenuItem from './CpMenuItem.vue'
|
|
@@ -112,11 +114,13 @@ const Components = {
|
|
|
112
114
|
CpButtonGroup,
|
|
113
115
|
CpDialog,
|
|
114
116
|
CpDynamicDialog,
|
|
117
|
+
CpDynamicMenu,
|
|
115
118
|
CpDate,
|
|
116
119
|
CpContextualMenu,
|
|
117
120
|
CpMenu,
|
|
118
121
|
CpMenuItem,
|
|
119
122
|
CpMenuList,
|
|
123
|
+
CpLanguageSwitcher,
|
|
120
124
|
CpItemActions,
|
|
121
125
|
CpCoreDatepicker,
|
|
122
126
|
CpDatepicker,
|
|
@@ -203,6 +207,7 @@ const Pimp: Plugin = {
|
|
|
203
207
|
},
|
|
204
208
|
}
|
|
205
209
|
|
|
210
|
+
export type { LanguageOption } from '@/components/CpLanguageSwitcher.vue'
|
|
206
211
|
export type { CpAncillaryFlightSelectionFlight } from '@/constants/CpAncillaryFlightSelectionTypes'
|
|
207
212
|
export type { CpBasketCategory, CpBasketGroup, CpBasketRow } from '@/constants/CpBasketTypes'
|
|
208
213
|
export type { CpFlightTabsFlight } from '@/constants/CpFlightTabsTypes'
|
|
@@ -266,12 +271,14 @@ export {
|
|
|
266
271
|
CpDatepicker,
|
|
267
272
|
CpDialog,
|
|
268
273
|
CpDynamicDialog,
|
|
274
|
+
CpDynamicMenu,
|
|
269
275
|
CpFlightTabs,
|
|
270
276
|
CpHeading,
|
|
271
277
|
CpIcon,
|
|
272
278
|
CpIllustration,
|
|
273
279
|
CpInput,
|
|
274
280
|
CpItemActions,
|
|
281
|
+
CpLanguageSwitcher,
|
|
275
282
|
CpLoader,
|
|
276
283
|
CpMenu,
|
|
277
284
|
CpMenuItem,
|