@bagelink/vue 1.4.101 → 1.4.105
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/AccordionItem.vue.d.ts.map +1 -1
- package/dist/components/Icon/Icon.vue.d.ts.map +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.mjs +2 -2
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/AccordionItem.vue +12 -16
- package/src/components/Icon/Icon.vue +90 -29
package/package.json
CHANGED
|
@@ -65,14 +65,12 @@ function toggle() {
|
|
|
65
65
|
<template>
|
|
66
66
|
<div class="accordion-item txt-start" :class="[{ flat }]">
|
|
67
67
|
<button
|
|
68
|
-
:aria-expanded="isOpen ? 'true' : 'false'"
|
|
69
|
-
|
|
70
|
-
:aria-controls="`accordion-body-${id}`"
|
|
71
|
-
@click="toggle()"
|
|
68
|
+
:aria-expanded="isOpen ? 'true' : 'false'" class="accordion-head"
|
|
69
|
+
:aria-controls="`accordion-body-${id}`" @click="toggle()"
|
|
72
70
|
>
|
|
73
71
|
<span
|
|
74
|
-
v-if="iconPosition === 'start'"
|
|
75
|
-
|
|
72
|
+
v-if="iconPosition === 'start'" class="accordion-icon"
|
|
73
|
+
:class="[iconClass, { open: isOpen && iconType === 'expand_more' }]"
|
|
76
74
|
>
|
|
77
75
|
<Icon :icon="computedIcon" />
|
|
78
76
|
</span>
|
|
@@ -84,17 +82,15 @@ function toggle() {
|
|
|
84
82
|
</slot>
|
|
85
83
|
|
|
86
84
|
<span
|
|
87
|
-
v-if="iconPosition === 'end'"
|
|
88
|
-
|
|
85
|
+
v-if="iconPosition === 'end'" class="accordion-icon"
|
|
86
|
+
:class="[iconClass, { open: isOpen && iconType === 'expand_more' }]"
|
|
89
87
|
>
|
|
90
88
|
<Icon :icon="computedIcon" />
|
|
91
89
|
</span>
|
|
92
90
|
</button>
|
|
93
91
|
<Transition name="expand">
|
|
94
92
|
<div
|
|
95
|
-
v-if="isOpen"
|
|
96
|
-
:id="`accordion-body-${id}`"
|
|
97
|
-
class="accordion-body"
|
|
93
|
+
v-if="isOpen" :id="`accordion-body-${id}`" class="accordion-body"
|
|
98
94
|
:aria-hidden="isOpen ? 'false' : 'true'"
|
|
99
95
|
>
|
|
100
96
|
<slot />
|
|
@@ -107,9 +103,9 @@ function toggle() {
|
|
|
107
103
|
.accordion-item {
|
|
108
104
|
border-bottom: 1px solid var(--border-color);
|
|
109
105
|
transition: all 0.2s;
|
|
110
|
-
cursor: pointer;
|
|
111
106
|
overflow: hidden;
|
|
112
107
|
}
|
|
108
|
+
|
|
113
109
|
.accordion-item button {
|
|
114
110
|
cursor: pointer;
|
|
115
111
|
}
|
|
@@ -130,14 +126,14 @@ function toggle() {
|
|
|
130
126
|
display: flex;
|
|
131
127
|
align-items: center;
|
|
132
128
|
flex-shrink: 0;
|
|
133
|
-
width: calc(
|
|
134
|
-
height: calc(
|
|
129
|
+
width: calc(var(--btn-height) / 1.5);
|
|
130
|
+
height: calc(var(--btn-height) / 1.5);
|
|
135
131
|
padding-inline: calc(var(--btn-padding) / 3);
|
|
136
132
|
justify-content: center;
|
|
137
133
|
}
|
|
138
134
|
|
|
139
|
-
[aria-expanded="true"] .accordion-icon
|
|
140
|
-
|
|
135
|
+
[aria-expanded="true"] .accordion-icon {
|
|
136
|
+
transform: rotate(180deg);
|
|
141
137
|
}
|
|
142
138
|
|
|
143
139
|
.accordion-label {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { IconType } from '@bagelink/vue'
|
|
3
|
+
import { useDevice } from '@bagelink/vue'
|
|
4
|
+
import { ref, onMounted, watchEffect } from 'vue'
|
|
3
5
|
import { FONT_AWESOME_ICONS, MATERIAL_ICONS, FONT_AWESOME_BRANDS_ICONS } from './constants'
|
|
4
|
-
import { ref, onMounted, onUnmounted } from 'vue'
|
|
5
6
|
|
|
6
7
|
const props = withDefaults(defineProps<{
|
|
7
8
|
icon?: IconType
|
|
@@ -17,22 +18,9 @@ const props = withDefaults(defineProps<{
|
|
|
17
18
|
size: 1
|
|
18
19
|
})
|
|
19
20
|
|
|
20
|
-
const iconRender = $computed(() => props.icon
|
|
21
|
+
const iconRender = $computed(() => (props.icon ?? props.name) as IconType)
|
|
21
22
|
|
|
22
|
-
const isMobile =
|
|
23
|
-
|
|
24
|
-
const checkMobile = () => {
|
|
25
|
-
isMobile.value = window.innerWidth <= 910
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
onMounted(() => {
|
|
29
|
-
checkMobile()
|
|
30
|
-
window.addEventListener('resize', checkMobile)
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
onUnmounted(() => {
|
|
34
|
-
window.removeEventListener('resize', checkMobile)
|
|
35
|
-
})
|
|
23
|
+
const { isMobile } = useDevice()
|
|
36
24
|
|
|
37
25
|
const computedSize = $computed(() => {
|
|
38
26
|
if (isMobile.value && props.mobileSize !== undefined) {
|
|
@@ -49,36 +37,108 @@ const iconRenderType = $computed(() => {
|
|
|
49
37
|
})
|
|
50
38
|
|
|
51
39
|
const isFaBrand = $computed(() => FONT_AWESOME_BRANDS_ICONS.includes(iconRender))
|
|
40
|
+
|
|
41
|
+
// Hide until required font is ready to avoid ligature text/fallback flash
|
|
42
|
+
const isMaterialReady = ref(true)
|
|
43
|
+
const isFAFreeReady = ref(true)
|
|
44
|
+
const isFABrandsReady = ref(true)
|
|
45
|
+
|
|
46
|
+
function getFontFaceSet(): FontFaceSet | null {
|
|
47
|
+
if (typeof document === 'undefined') return null
|
|
48
|
+
const maybeFonts = (document as unknown as { fonts?: FontFaceSet }).fonts
|
|
49
|
+
return maybeFonts ?? null
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function supportsFontLoading(): boolean {
|
|
53
|
+
const fonts = getFontFaceSet()
|
|
54
|
+
return fonts !== null && typeof fonts.load === 'function'
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function ensureFontLoaded(family: string): Promise<boolean> {
|
|
58
|
+
if (!supportsFontLoading()) return true
|
|
59
|
+
try {
|
|
60
|
+
const fonts = getFontFaceSet()
|
|
61
|
+
if (!fonts) return true
|
|
62
|
+
if (fonts.check(`1em "${family}"`)) return true
|
|
63
|
+
await fonts.load(`1em "${family}"`)
|
|
64
|
+
return fonts.check(`1em "${family}"`)
|
|
65
|
+
} catch {
|
|
66
|
+
return false
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function loadMaterialIfNeeded() {
|
|
71
|
+
const ok = await ensureFontLoaded('Material Symbols Outlined')
|
|
72
|
+
isMaterialReady.value = ok
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function loadFAFreeIfNeeded() {
|
|
76
|
+
const ok = await ensureFontLoaded('Font Awesome 6 Free')
|
|
77
|
+
isFAFreeReady.value = ok
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function loadFABrandsIfNeeded() {
|
|
81
|
+
const ok = await ensureFontLoaded('Font Awesome 6 Brands')
|
|
82
|
+
isFABrandsReady.value = ok
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
onMounted(() => {
|
|
86
|
+
if (!supportsFontLoading()) return
|
|
87
|
+
if (iconRenderType === 'material') {
|
|
88
|
+
const fonts = getFontFaceSet()
|
|
89
|
+
isMaterialReady.value = fonts ? fonts.check('1em "Material Symbols Outlined"') : true
|
|
90
|
+
void loadMaterialIfNeeded()
|
|
91
|
+
} else if (isFaBrand) {
|
|
92
|
+
const fonts = getFontFaceSet()
|
|
93
|
+
isFABrandsReady.value = fonts ? fonts.check('1em "Font Awesome 6 Brands"') : true
|
|
94
|
+
void loadFABrandsIfNeeded()
|
|
95
|
+
} else {
|
|
96
|
+
const fonts = getFontFaceSet()
|
|
97
|
+
isFAFreeReady.value = fonts ? fonts.check('1em "Font Awesome 6 Free"') : true
|
|
98
|
+
void loadFAFreeIfNeeded()
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
watchEffect(() => {
|
|
103
|
+
if (!supportsFontLoading()) return
|
|
104
|
+
if (iconRenderType === 'material') {
|
|
105
|
+
void loadMaterialIfNeeded()
|
|
106
|
+
} else if (isFaBrand) {
|
|
107
|
+
void loadFABrandsIfNeeded()
|
|
108
|
+
} else {
|
|
109
|
+
void loadFAFreeIfNeeded()
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
const isCurrentFontReady = $computed(() => {
|
|
114
|
+
if (iconRenderType === 'material') return isMaterialReady.value
|
|
115
|
+
return isFaBrand ? isFABrandsReady.value : isFAFreeReady.value
|
|
116
|
+
})
|
|
52
117
|
</script>
|
|
53
118
|
|
|
54
119
|
<template>
|
|
55
120
|
<span
|
|
56
|
-
v-if="iconRenderType === 'material'"
|
|
57
|
-
class="
|
|
58
|
-
:class="{ 'round flex aspect-ratio-1 justify-content-center': round }"
|
|
59
|
-
:style="{
|
|
121
|
+
v-if="iconRenderType === 'material'" class="bgl_icon-font notranslate"
|
|
122
|
+
:class="{ 'round flex aspect-ratio-1 justify-content-center': round }" :style="{
|
|
60
123
|
'fontSize': `${computedSize}rem`,
|
|
61
124
|
color,
|
|
62
125
|
'font-variation-settings': `'FILL' ${fill ? 1 : 0}, 'wght' ${weight || 400}`,
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
translate="no"
|
|
126
|
+
'width': round ? `calc(${computedSize}rem * 2)` : 'auto',
|
|
127
|
+
'height': round ? `calc(${computedSize}rem * 2)` : 'auto',
|
|
128
|
+
'visibility': isCurrentFontReady ? 'visible' : 'hidden',
|
|
129
|
+
}" translate="no"
|
|
67
130
|
>
|
|
68
131
|
{{ iconRender }}
|
|
69
132
|
</span>
|
|
70
133
|
<span
|
|
71
|
-
v-else-if="iconRenderType === 'font-awesome'"
|
|
72
|
-
class="fa bgl_icon-font notranslate"
|
|
73
|
-
:class="[
|
|
134
|
+
v-else-if="iconRenderType === 'font-awesome'" class="fa bgl_icon-font notranslate" :class="[
|
|
74
135
|
`fa-${iconRender}`,
|
|
75
136
|
{
|
|
76
137
|
'fa-brands': isFaBrand,
|
|
77
138
|
'fa-solid': fill,
|
|
78
139
|
'far': !fill && !isFaBrand,
|
|
79
140
|
},
|
|
80
|
-
]"
|
|
81
|
-
:style="{ 'fontSize': `${computedSize}rem`, color, 'font-variation-settings': `'wght' ${weight || 400}` }"
|
|
141
|
+
]" :style="{ 'fontSize': `${computedSize}rem`, color, 'font-variation-settings': `'wght' ${weight || 400}`, 'visibility': isCurrentFontReady ? 'visible' : 'hidden' }"
|
|
82
142
|
translate="no"
|
|
83
143
|
/>
|
|
84
144
|
</template>
|
|
@@ -86,6 +146,7 @@ const isFaBrand = $computed(() => FONT_AWESOME_BRANDS_ICONS.includes(iconRender)
|
|
|
86
146
|
<style>
|
|
87
147
|
@import url('https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200');
|
|
88
148
|
@import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css');
|
|
149
|
+
|
|
89
150
|
[dir='rtl'] .data-row .bgl_icon-font,
|
|
90
151
|
[dir='rtl'] .embedded-field .bgl_icon-font,
|
|
91
152
|
[dir='rtl'] .rich-text-editor .toolbar .bgl_icon-font {
|