@dative-gpi/foundation-shared-components 1.0.181 → 1.0.182
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/components/FSWidgetStandardOptions.vue +60 -0
- package/components/FSWidgetTemplateCardUI.vue +52 -0
- package/composables/index.ts +3 -0
- package/composables/useAccessibilityPreferences.ts +32 -0
- package/composables/useAnimationFrame.ts +41 -0
- package/composables/useCountUp.ts +74 -0
- package/composables/useElementVisibility.ts +41 -0
- package/package.json +4 -4
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<FSRow
|
|
3
|
+
align="bottom-left"
|
|
4
|
+
:wrap="false"
|
|
5
|
+
gap="16px"
|
|
6
|
+
>
|
|
7
|
+
<FSRow
|
|
8
|
+
align="bottom-center"
|
|
9
|
+
>
|
|
10
|
+
<FSNumberField
|
|
11
|
+
:label="$tr('entity.widget.width', 'Width')"
|
|
12
|
+
:modelValue="widgetWidth"
|
|
13
|
+
@update:modelValue="$emit('update:widgetWidth', $event)"
|
|
14
|
+
/>
|
|
15
|
+
<FSNumberField
|
|
16
|
+
:label="$tr('entity.widget.height', 'Height')"
|
|
17
|
+
:modelValue="widgetHeight"
|
|
18
|
+
@update:modelValue="$emit('update:widgetHeight', $event)"
|
|
19
|
+
/>
|
|
20
|
+
<FSSwitch
|
|
21
|
+
class="dialog-configure-widget-hide-borders"
|
|
22
|
+
:label="$tr('entity.widget.hide-borders', 'Hide borders')"
|
|
23
|
+
:modelValue="hideBorders"
|
|
24
|
+
@update:modelValue="$emit('update:hideBorders', $event)"
|
|
25
|
+
/>
|
|
26
|
+
</FSRow>
|
|
27
|
+
</FSRow>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<script lang="ts">
|
|
31
|
+
import { defineComponent } from "vue";
|
|
32
|
+
|
|
33
|
+
import FSRow from "./FSRow.vue";
|
|
34
|
+
import FSNumberField from "./fields/FSNumberField.vue";
|
|
35
|
+
import FSSwitch from "./FSSwitch.vue";
|
|
36
|
+
|
|
37
|
+
export default defineComponent({
|
|
38
|
+
name: "WidgetStandardOptions",
|
|
39
|
+
components: {
|
|
40
|
+
FSRow,
|
|
41
|
+
FSNumberField,
|
|
42
|
+
FSSwitch
|
|
43
|
+
},
|
|
44
|
+
props: {
|
|
45
|
+
widgetWidth: {
|
|
46
|
+
type: Number,
|
|
47
|
+
required: true
|
|
48
|
+
},
|
|
49
|
+
widgetHeight: {
|
|
50
|
+
type: Number,
|
|
51
|
+
required: true
|
|
52
|
+
},
|
|
53
|
+
hideBorders: {
|
|
54
|
+
type: Boolean,
|
|
55
|
+
required: true
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
emits: ["update:widgetWidth", "update:widgetHeight", "update:hideBorders"]
|
|
59
|
+
});
|
|
60
|
+
</script>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<FSCard
|
|
3
|
+
height="100px"
|
|
4
|
+
width="100%"
|
|
5
|
+
>
|
|
6
|
+
<FSCol
|
|
7
|
+
align="center-center"
|
|
8
|
+
>
|
|
9
|
+
<FSIcon
|
|
10
|
+
v-if="icon"
|
|
11
|
+
size="32px"
|
|
12
|
+
>
|
|
13
|
+
{{ $props.icon }}
|
|
14
|
+
</FSIcon>
|
|
15
|
+
<FSText
|
|
16
|
+
font="text-overline"
|
|
17
|
+
>
|
|
18
|
+
{{ $props.label }}
|
|
19
|
+
</FSText>
|
|
20
|
+
</FSCol>
|
|
21
|
+
</FSCard>
|
|
22
|
+
</template>
|
|
23
|
+
|
|
24
|
+
<script lang="ts">
|
|
25
|
+
import { defineComponent } from "vue";
|
|
26
|
+
|
|
27
|
+
import FSCard from "./FSCard.vue";
|
|
28
|
+
import FSCol from "./FSCol.vue";
|
|
29
|
+
import FSIcon from "./FSIcon.vue";
|
|
30
|
+
import FSText from "./FSText.vue";
|
|
31
|
+
|
|
32
|
+
export default defineComponent({
|
|
33
|
+
name: "FSWidgetTemplateCardUI",
|
|
34
|
+
components: {
|
|
35
|
+
FSCard,
|
|
36
|
+
FSCol,
|
|
37
|
+
FSIcon,
|
|
38
|
+
FSText
|
|
39
|
+
},
|
|
40
|
+
props: {
|
|
41
|
+
icon: {
|
|
42
|
+
type: String,
|
|
43
|
+
required: false,
|
|
44
|
+
default: "mdi-chart-bar"
|
|
45
|
+
},
|
|
46
|
+
label: {
|
|
47
|
+
type: String,
|
|
48
|
+
required: true
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
</script>
|
package/composables/index.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
export * from "./useAccessibilityPreferences";
|
|
1
2
|
export * from "./useAddress";
|
|
2
3
|
export * from "./useAutocomplete";
|
|
3
4
|
export * from "./useBreakpoints";
|
|
4
5
|
export * from "./useColors";
|
|
6
|
+
export * from "./useCountUp";
|
|
5
7
|
export * from "./useDebounce";
|
|
8
|
+
export * from "./useElementVisibility";
|
|
6
9
|
export * from "./useMapLayers";
|
|
7
10
|
export * from "./useRules";
|
|
8
11
|
export * from "./useSlots";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ref, onMounted, onBeforeUnmount } from 'vue';
|
|
2
|
+
|
|
3
|
+
const PREFERS_REDUCED_MOTION_QUERY = "(prefers-reduced-motion: reduce)";
|
|
4
|
+
|
|
5
|
+
export function useAccessibilityPreferences() {
|
|
6
|
+
const prefersReducedMotion = ref(false);
|
|
7
|
+
let mediaQuery: MediaQueryList | null = null;
|
|
8
|
+
|
|
9
|
+
const handleChange = (e: MediaQueryListEvent) => {
|
|
10
|
+
prefersReducedMotion.value = e.matches;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
onMounted(() => {
|
|
14
|
+
|
|
15
|
+
mediaQuery = window.matchMedia?.(PREFERS_REDUCED_MOTION_QUERY) || null;
|
|
16
|
+
prefersReducedMotion.value = mediaQuery?.matches || false;
|
|
17
|
+
|
|
18
|
+
if (mediaQuery?.addEventListener) {
|
|
19
|
+
mediaQuery.addEventListener('change', handleChange);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
onBeforeUnmount(() => {
|
|
24
|
+
if (mediaQuery?.removeEventListener) {
|
|
25
|
+
mediaQuery.removeEventListener('change', handleChange);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
prefersReducedMotion
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { onBeforeUnmount, ref, readonly } from 'vue';
|
|
2
|
+
|
|
3
|
+
export const useAnimationFrame = (callback: () => void) => {
|
|
4
|
+
let animationId: number | null = null;
|
|
5
|
+
const isActive = ref(false);
|
|
6
|
+
|
|
7
|
+
const start = () => {
|
|
8
|
+
if (isActive.value) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
isActive.value = true;
|
|
12
|
+
|
|
13
|
+
const animate = () => {
|
|
14
|
+
if (!isActive.value) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
callback();
|
|
19
|
+
animationId = requestAnimationFrame(animate);
|
|
20
|
+
};
|
|
21
|
+
animationId = requestAnimationFrame(animate);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const stop = () => {
|
|
25
|
+
isActive.value = false;
|
|
26
|
+
if (animationId !== null) {
|
|
27
|
+
cancelAnimationFrame(animationId);
|
|
28
|
+
animationId = null;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
onBeforeUnmount(() => {
|
|
33
|
+
stop();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
start,
|
|
38
|
+
stop,
|
|
39
|
+
isActive: readonly(isActive)
|
|
40
|
+
};
|
|
41
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { ref, computed, watch, type Ref, type ComponentPublicInstance } from 'vue';
|
|
2
|
+
import { useAccessibilityPreferences } from './useAccessibilityPreferences';
|
|
3
|
+
import { useAnimationFrame } from './useAnimationFrame';
|
|
4
|
+
import { useElementVisibility } from './useElementVisibility';
|
|
5
|
+
|
|
6
|
+
export function useCountUp(
|
|
7
|
+
targetValue: Ref<number>,
|
|
8
|
+
duration = ref<number>(800),
|
|
9
|
+
pad = ref<number>(2),
|
|
10
|
+
startOnVisible = ref<boolean>(false),
|
|
11
|
+
elementRef: Ref<ComponentPublicInstance | null> = ref(null),
|
|
12
|
+
easing = (t: number) => 1 - Math.pow(1 - t, 3)
|
|
13
|
+
) {
|
|
14
|
+
const { prefersReducedMotion } = useAccessibilityPreferences();
|
|
15
|
+
|
|
16
|
+
const { onVisible } = useElementVisibility(
|
|
17
|
+
elementRef
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const current = ref<number>(0);
|
|
21
|
+
const animating = ref<boolean>(false);
|
|
22
|
+
const startTime = ref<number>(0);
|
|
23
|
+
const from = ref<number>(0);
|
|
24
|
+
const to = ref<number>(0);
|
|
25
|
+
|
|
26
|
+
const displayText = computed(() => {
|
|
27
|
+
const text = String(current.value);
|
|
28
|
+
return pad.value > 0 ? text.padStart(pad.value, "0") : text;
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const { start: afStart, stop: afStop } = useAnimationFrame(() => {
|
|
32
|
+
const elapsed = performance.now() - startTime.value;
|
|
33
|
+
const progress = Math.min(1, elapsed / duration.value);
|
|
34
|
+
|
|
35
|
+
if (progress >= 1) {
|
|
36
|
+
current.value = to.value;
|
|
37
|
+
animating.value = false;
|
|
38
|
+
afStop();
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const easedProgress = easing(progress);
|
|
43
|
+
current.value = Math.round(from.value + (to.value - from.value) * easedProgress);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const start = () => {
|
|
47
|
+
if (prefersReducedMotion.value) {
|
|
48
|
+
current.value = targetValue.value;
|
|
49
|
+
animating.value = false;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
from.value = current.value;
|
|
54
|
+
to.value = targetValue.value;
|
|
55
|
+
startTime.value = performance.now();
|
|
56
|
+
animating.value = true;
|
|
57
|
+
afStart();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
watch(targetValue, () => {
|
|
61
|
+
start();
|
|
62
|
+
}, { immediate: true });
|
|
63
|
+
|
|
64
|
+
onVisible.value = () => {
|
|
65
|
+
if (startOnVisible.value && !animating.value) {
|
|
66
|
+
current.value = from.value;
|
|
67
|
+
start();
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
displayText,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type Ref, ref, onBeforeUnmount, watch, type ComponentPublicInstance } from 'vue';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export function useElementVisibility(
|
|
5
|
+
target: Ref<ComponentPublicInstance | null>,
|
|
6
|
+
threshold = 0.3
|
|
7
|
+
) {
|
|
8
|
+
const onVisible: Ref<(() => void) | null> = ref(null);
|
|
9
|
+
let observer: IntersectionObserver | null = null;
|
|
10
|
+
|
|
11
|
+
const startObserver = (el: Element) => {
|
|
12
|
+
stopObserver();
|
|
13
|
+
|
|
14
|
+
observer = new IntersectionObserver(([entry]) => {
|
|
15
|
+
const visible = Boolean(entry?.isIntersecting);
|
|
16
|
+
if (visible && onVisible.value) {
|
|
17
|
+
onVisible.value();
|
|
18
|
+
}
|
|
19
|
+
}, { threshold });
|
|
20
|
+
|
|
21
|
+
observer.observe(el);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const stopObserver = () => {
|
|
25
|
+
observer?.disconnect();
|
|
26
|
+
observer = null;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
watch(target , (newVal) => {
|
|
30
|
+
console.log('Target changed:', newVal);
|
|
31
|
+
if(newVal && newVal.$el){
|
|
32
|
+
startObserver(newVal.$el);
|
|
33
|
+
}
|
|
34
|
+
}, { immediate: true });
|
|
35
|
+
|
|
36
|
+
onBeforeUnmount(stopObserver);
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
onVisible
|
|
40
|
+
};
|
|
41
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dative-gpi/foundation-shared-components",
|
|
3
3
|
"sideEffects": false,
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.182",
|
|
5
5
|
"description": "",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
"author": "",
|
|
11
11
|
"license": "ISC",
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@dative-gpi/foundation-shared-domain": "1.0.
|
|
14
|
-
"@dative-gpi/foundation-shared-services": "1.0.
|
|
13
|
+
"@dative-gpi/foundation-shared-domain": "1.0.182",
|
|
14
|
+
"@dative-gpi/foundation-shared-services": "1.0.182"
|
|
15
15
|
},
|
|
16
16
|
"peerDependencies": {
|
|
17
17
|
"@dative-gpi/bones-ui": "^1.0.0",
|
|
@@ -35,5 +35,5 @@
|
|
|
35
35
|
"sass": "1.71.1",
|
|
36
36
|
"sass-loader": "13.3.2"
|
|
37
37
|
},
|
|
38
|
-
"gitHead": "
|
|
38
|
+
"gitHead": "1e64f82da4dcedf3908383bb0d7c6a33f85ac0f2"
|
|
39
39
|
}
|