@dative-gpi/foundation-shared-components 1.0.180-utils-composable → 1.0.181-fix-sandbox-connectivity
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 +1 -1
- package/composables/useAccessibilityPreferences.ts +27 -2
- package/composables/useAnimationFrame.ts +41 -0
- package/composables/useCountUp.ts +50 -77
- package/composables/useElementVisibility.ts +38 -36
- 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
|
@@ -5,7 +5,7 @@ export * from "./useBreakpoints";
|
|
|
5
5
|
export * from "./useColors";
|
|
6
6
|
export * from "./useCountUp";
|
|
7
7
|
export * from "./useDebounce";
|
|
8
|
-
export * from "./useElementVisibility"
|
|
8
|
+
export * from "./useElementVisibility";
|
|
9
9
|
export * from "./useMapLayers";
|
|
10
10
|
export * from "./useRules";
|
|
11
11
|
export * from "./useSlots";
|
|
@@ -1,7 +1,32 @@
|
|
|
1
|
+
import { ref, onMounted, onBeforeUnmount } from 'vue';
|
|
2
|
+
|
|
3
|
+
const PREFERS_REDUCED_MOTION_QUERY = "(prefers-reduced-motion: reduce)";
|
|
4
|
+
|
|
1
5
|
export function useAccessibilityPreferences() {
|
|
2
|
-
const prefersReducedMotion =
|
|
6
|
+
const prefersReducedMotion = ref(false);
|
|
7
|
+
let mediaQuery: MediaQueryList | null = null;
|
|
8
|
+
|
|
9
|
+
const handleChange = (e: MediaQueryListEvent) => {
|
|
10
|
+
prefersReducedMotion.value = e.matches;
|
|
11
|
+
};
|
|
3
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
|
+
|
|
4
29
|
return {
|
|
5
30
|
prefersReducedMotion
|
|
6
31
|
};
|
|
7
|
-
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { onBeforeUnmount, computed } from 'vue';
|
|
2
|
+
|
|
3
|
+
export const useAnimationFrame = (callback: () => void) => {
|
|
4
|
+
let animationId: number | null = null;
|
|
5
|
+
let isActive = false;
|
|
6
|
+
|
|
7
|
+
const start = () => {
|
|
8
|
+
if (isActive) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
isActive = true;
|
|
12
|
+
|
|
13
|
+
const animate = () => {
|
|
14
|
+
if (!isActive) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
callback();
|
|
19
|
+
animationId = requestAnimationFrame(animate);
|
|
20
|
+
};
|
|
21
|
+
animationId = requestAnimationFrame(animate);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const stop = () => {
|
|
25
|
+
isActive = 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: computed(() => isActive)
|
|
40
|
+
};
|
|
41
|
+
};
|
|
@@ -1,101 +1,74 @@
|
|
|
1
|
-
import { ref, computed,
|
|
1
|
+
import { ref, computed, watch, type Ref, type ComponentPublicInstance } from 'vue';
|
|
2
2
|
import { useAccessibilityPreferences } from './useAccessibilityPreferences';
|
|
3
|
+
import { useAnimationFrame } from './useAnimationFrame';
|
|
4
|
+
import { useElementVisibility } from './useElementVisibility';
|
|
3
5
|
|
|
4
|
-
export function useCountUp(
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const {
|
|
13
|
-
value,
|
|
14
|
-
duration = 800,
|
|
15
|
-
countUp = true,
|
|
16
|
-
pad = 2,
|
|
17
|
-
startOnVisible = true,
|
|
18
|
-
easing = easeOutCubic
|
|
19
|
-
} = options;
|
|
20
|
-
|
|
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
|
+
) {
|
|
21
14
|
const { prefersReducedMotion } = useAccessibilityPreferences();
|
|
22
|
-
const current = ref(0);
|
|
23
|
-
const rafId = ref(0);
|
|
24
|
-
const hasAnimated = ref(false);
|
|
25
|
-
|
|
26
|
-
// Easing function
|
|
27
|
-
function easeOutCubic(t: number) {
|
|
28
|
-
return 1 - Math.pow(1 - t, 3);
|
|
29
|
-
}
|
|
30
15
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return Number.isFinite(n) ? Math.trunc(n) : 0;
|
|
35
|
-
});
|
|
16
|
+
const { onVisible } = useElementVisibility(
|
|
17
|
+
elementRef
|
|
18
|
+
);
|
|
36
19
|
|
|
37
|
-
|
|
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
|
+
|
|
38
26
|
const displayText = computed(() => {
|
|
39
|
-
const
|
|
40
|
-
return pad > 0 ?
|
|
27
|
+
const text = String(current.value);
|
|
28
|
+
return pad.value > 0 ? text.padStart(pad.value, "0") : text;
|
|
41
29
|
});
|
|
42
30
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const start = performance.now();
|
|
47
|
-
|
|
48
|
-
const step = (now: number) => {
|
|
49
|
-
const t = Math.min(1, (now - start) / animDuration);
|
|
50
|
-
const v = from + (to - from) * easing(t);
|
|
51
|
-
current.value = Math.round(v);
|
|
52
|
-
if (t < 1) {
|
|
53
|
-
rafId.value = requestAnimationFrame(step);
|
|
54
|
-
}
|
|
55
|
-
};
|
|
31
|
+
const { start: afStart, stop: afStop } = useAnimationFrame(() => {
|
|
32
|
+
const elapsed = performance.now() - startTime.value;
|
|
33
|
+
const progress = Math.min(1, elapsed / duration.value);
|
|
56
34
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
function start() {
|
|
62
|
-
if (!countUp) {
|
|
63
|
-
current.value = target.value;
|
|
35
|
+
if (progress >= 1) {
|
|
36
|
+
current.value = to.value;
|
|
37
|
+
animating.value = false;
|
|
38
|
+
afStop();
|
|
64
39
|
return;
|
|
65
40
|
}
|
|
66
41
|
|
|
67
|
-
|
|
68
|
-
|
|
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;
|
|
69
50
|
return;
|
|
70
51
|
}
|
|
71
52
|
|
|
72
|
-
|
|
73
|
-
|
|
53
|
+
from.value = current.value;
|
|
54
|
+
to.value = targetValue.value;
|
|
55
|
+
startTime.value = performance.now();
|
|
56
|
+
animating.value = true;
|
|
57
|
+
afStart();
|
|
74
58
|
}
|
|
75
59
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (hasAnimated.value || !startOnVisible) {
|
|
60
|
+
watch(targetValue, () => {
|
|
61
|
+
start();
|
|
62
|
+
}, { immediate: true });
|
|
63
|
+
|
|
64
|
+
onVisible.value = () => {
|
|
65
|
+
if (startOnVisible.value && !animating.value) {
|
|
66
|
+
current.value = from.value;
|
|
84
67
|
start();
|
|
85
68
|
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Monitor changes in value
|
|
89
|
-
watch(() => value, () => {
|
|
90
|
-
restart();
|
|
91
|
-
});
|
|
69
|
+
};
|
|
92
70
|
|
|
93
71
|
return {
|
|
94
|
-
current,
|
|
95
|
-
target,
|
|
96
72
|
displayText,
|
|
97
|
-
start,
|
|
98
|
-
restart,
|
|
99
|
-
hasAnimated
|
|
100
73
|
};
|
|
101
74
|
}
|
|
@@ -1,39 +1,41 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
} = options;
|
|
11
|
-
|
|
12
|
-
const isVisible = ref(false);
|
|
13
|
-
const observer = ref<IntersectionObserver | null>(null);
|
|
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;
|
|
14
10
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (entry.isIntersecting && onVisible) {
|
|
24
|
-
onVisible();
|
|
25
|
-
}
|
|
26
|
-
});
|
|
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
|
+
}
|
|
27
19
|
}, { threshold });
|
|
28
|
-
|
|
29
|
-
observer.
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
observer
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return {
|
|
37
|
-
isVisible
|
|
20
|
+
|
|
21
|
+
observer.observe(el);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const stopObserver = () => {
|
|
25
|
+
observer?.disconnect();
|
|
26
|
+
observer = null;
|
|
38
27
|
};
|
|
39
|
-
|
|
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.181-fix-sandbox-connectivity",
|
|
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.181-fix-sandbox-connectivity",
|
|
14
|
+
"@dative-gpi/foundation-shared-services": "1.0.181-fix-sandbox-connectivity"
|
|
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": "953ddcfdd3728cda9d1da39d665c3a2944ec56d5"
|
|
39
39
|
}
|