v-uixy 1.9.1 → 1.9.3
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/package.json +1 -1
- package/templates/components/Chart/Chart.component.vue +895 -222
- package/templates/components/Chart/Chart.types.ts +7 -0
- package/templates/components/Modal/Modal.component.vue +8 -1
- package/templates/components/Modal/Modal.styles.ts +1 -1
- package/templates/components/PieChart/PieChart.component.vue +515 -152
- package/templates/components/PieChart/PieChart.types.ts +7 -1
- package/templates/components/Select/Select.component.vue +58 -32
- package/templates/components/Select/Select.styles.ts +37 -12
- package/templates/components/Select/Select.types.ts +4 -0
- package/templates/components/Select/SelectTreeItem.vue +11 -3
- package/templates/components/Select/composables/useSelect.composable.ts +244 -122
- package/templates/components/Select/index.ts +2 -0
- package/templates/components/Sheet/Sheet.component.vue +4 -2
- package/templates/components/index.ts +8 -1
- package/templates/components.json +4 -4
- package/templates/composables/useGpuAcceleration.ts +79 -0
- package/templates/composables/useOverlayLayer.ts +86 -31
- package/templates/composables/usePosition.ts +1 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { onMounted } from "vue";
|
|
2
|
+
|
|
3
|
+
const GPU_ATTRIBUTE = "data-uixy-gpu";
|
|
4
|
+
const IDLE_TIMEOUT_MS = 2000;
|
|
5
|
+
const FALLBACK_DELAY_MS = 200;
|
|
6
|
+
|
|
7
|
+
const SOFTWARE_RENDERER_PATTERN =
|
|
8
|
+
/swiftshader|llvmpipe|softpipe|software|basic render/i;
|
|
9
|
+
|
|
10
|
+
let cachedResult: boolean | null = null;
|
|
11
|
+
let isDetectionScheduled = false;
|
|
12
|
+
|
|
13
|
+
const createWebGlContext = (): WebGLRenderingContext | null => {
|
|
14
|
+
const canvas = document.createElement("canvas");
|
|
15
|
+
const options = { failIfMajorPerformanceCaveat: true };
|
|
16
|
+
|
|
17
|
+
return (canvas.getContext("webgl2", options) ??
|
|
18
|
+
canvas.getContext("webgl", options)) as WebGLRenderingContext | null;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const getRendererName = (gl: WebGLRenderingContext): string => {
|
|
22
|
+
const info = gl.getExtension("WEBGL_debug_renderer_info");
|
|
23
|
+
|
|
24
|
+
return info ? String(gl.getParameter(info.UNMASKED_RENDERER_WEBGL)) : "";
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const checkGpuAcceleration = (): boolean => {
|
|
28
|
+
try {
|
|
29
|
+
const gl = createWebGlContext();
|
|
30
|
+
|
|
31
|
+
if (!gl) return false;
|
|
32
|
+
|
|
33
|
+
const isHardwareRenderer = !SOFTWARE_RENDERER_PATTERN.test(
|
|
34
|
+
getRendererName(gl),
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
gl.getExtension("WEBGL_lose_context")?.loseContext();
|
|
38
|
+
|
|
39
|
+
return isHardwareRenderer;
|
|
40
|
+
} catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const detectGpuAcceleration = (): boolean => {
|
|
46
|
+
if (typeof document === "undefined") return false;
|
|
47
|
+
|
|
48
|
+
cachedResult ??= checkGpuAcceleration();
|
|
49
|
+
|
|
50
|
+
return cachedResult;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const applyGpuAttribute = () => {
|
|
54
|
+
const value = detectGpuAcceleration() ? "on" : "off";
|
|
55
|
+
|
|
56
|
+
document.documentElement.setAttribute(GPU_ATTRIBUTE, value);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const runWhenIdle = (callback: () => void) => {
|
|
60
|
+
if ("requestIdleCallback" in window) {
|
|
61
|
+
window.requestIdleCallback(callback, { timeout: IDLE_TIMEOUT_MS });
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
setTimeout(callback, FALLBACK_DELAY_MS);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export function useGpuAcceleration() {
|
|
69
|
+
onMounted(() => {
|
|
70
|
+
if (
|
|
71
|
+
isDetectionScheduled ||
|
|
72
|
+
document.documentElement.hasAttribute(GPU_ATTRIBUTE)
|
|
73
|
+
)
|
|
74
|
+
return;
|
|
75
|
+
|
|
76
|
+
isDetectionScheduled = true;
|
|
77
|
+
runWhenIdle(applyGpuAttribute);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
@@ -1,73 +1,123 @@
|
|
|
1
1
|
import { computed, nextTick, onScopeDispose, ref, type ComputedRef } from "vue";
|
|
2
2
|
|
|
3
|
+
const PAGE_ANCHORED_Z = 30;
|
|
3
4
|
const BASE_Z = 60;
|
|
4
5
|
const STEP_Z = 10;
|
|
5
6
|
|
|
7
|
+
const MANAGED_ATTR = "data-uixy-inert-managed";
|
|
8
|
+
const OVERLAY_ATTR = "data-uixy-overlay";
|
|
9
|
+
export const MODAL_ATTR = "data-uixy-modal";
|
|
10
|
+
export const MODAL_ID_ATTR = "data-uixy-modal-id";
|
|
11
|
+
|
|
6
12
|
let seq = 0;
|
|
7
13
|
|
|
8
14
|
const layers = ref<number[]>([]);
|
|
9
15
|
|
|
16
|
+
const modalStack = ref<number[]>([]);
|
|
17
|
+
|
|
18
|
+
const zOf = (el: Element): number => {
|
|
19
|
+
const raw = window.getComputedStyle(el).zIndex;
|
|
20
|
+
const parsed = Number.parseInt(raw ?? "", 10);
|
|
21
|
+
|
|
22
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const hasPinnedAncestor = (el: HTMLElement | null): boolean => {
|
|
26
|
+
while (el) {
|
|
27
|
+
const { position } = window.getComputedStyle(el);
|
|
28
|
+
|
|
29
|
+
if (position === "fixed" || position === "sticky") return true;
|
|
30
|
+
|
|
31
|
+
el = el.parentElement;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return false;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const isAnchoredToPage = (anchor: HTMLElement): boolean => {
|
|
38
|
+
const overlay = anchor.closest(`[${OVERLAY_ATTR}]`);
|
|
39
|
+
|
|
40
|
+
if (overlay && zOf(overlay) > PAGE_ANCHORED_Z) return false;
|
|
41
|
+
|
|
42
|
+
return !hasPinnedAncestor(anchor);
|
|
43
|
+
};
|
|
44
|
+
|
|
10
45
|
export interface UseOverlayLayerReturn {
|
|
11
46
|
id: number;
|
|
12
47
|
zIndex: ComputedRef<number>;
|
|
13
48
|
isTop: ComputedRef<boolean>;
|
|
14
49
|
isActive: ComputedRef<boolean>;
|
|
15
|
-
acquire: () => void;
|
|
50
|
+
acquire: (anchor?: HTMLElement | null) => void;
|
|
16
51
|
release: () => void;
|
|
17
52
|
}
|
|
18
53
|
|
|
19
54
|
export function useOverlayLayer(): UseOverlayLayerReturn {
|
|
20
55
|
const id = ++seq;
|
|
21
56
|
|
|
57
|
+
const pageAnchored = ref(false);
|
|
58
|
+
|
|
22
59
|
const index = computed(() => layers.value.indexOf(id));
|
|
23
60
|
const isActive = computed(() => index.value !== -1);
|
|
24
61
|
|
|
25
|
-
const zIndex = computed(() =>
|
|
26
|
-
|
|
27
|
-
|
|
62
|
+
const zIndex = computed(() => {
|
|
63
|
+
if (pageAnchored.value) return PAGE_ANCHORED_Z;
|
|
64
|
+
|
|
65
|
+
const position = isActive.value ? index.value + 1 : 1;
|
|
66
|
+
|
|
67
|
+
return BASE_Z + position * STEP_Z;
|
|
68
|
+
});
|
|
28
69
|
|
|
29
70
|
const isTop = computed(
|
|
30
71
|
() => isActive.value && index.value === layers.value.length - 1,
|
|
31
72
|
);
|
|
32
73
|
|
|
33
|
-
const acquire = () => {
|
|
34
|
-
if (!layers.value.includes(id)) layers.value = [...layers.value, id];
|
|
35
|
-
};
|
|
36
|
-
|
|
37
74
|
const release = () => {
|
|
38
|
-
|
|
75
|
+
pageAnchored.value = false;
|
|
76
|
+
|
|
77
|
+
if (layers.value.includes(id)) {
|
|
39
78
|
layers.value = layers.value.filter((x) => x !== id);
|
|
79
|
+
}
|
|
40
80
|
};
|
|
41
81
|
|
|
42
|
-
|
|
82
|
+
const acquire = (anchor?: HTMLElement | null) => {
|
|
83
|
+
if (anchor && isAnchoredToPage(anchor)) {
|
|
84
|
+
release();
|
|
43
85
|
|
|
44
|
-
|
|
45
|
-
}
|
|
86
|
+
pageAnchored.value = true;
|
|
46
87
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
export const MODAL_ATTR = "data-uixy-modal";
|
|
50
|
-
export const MODAL_ID_ATTR = "data-uixy-modal-id";
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
51
90
|
|
|
52
|
-
|
|
91
|
+
pageAnchored.value = false;
|
|
53
92
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
93
|
+
if (!layers.value.includes(id)) {
|
|
94
|
+
layers.value = [...layers.value, id];
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
onScopeDispose(release);
|
|
99
|
+
|
|
100
|
+
return { id, zIndex, isTop, isActive, acquire, release };
|
|
101
|
+
}
|
|
59
102
|
|
|
60
103
|
export const getTopModalEl = (): HTMLElement | null => {
|
|
61
104
|
if (typeof document === "undefined") return null;
|
|
62
|
-
|
|
105
|
+
|
|
106
|
+
const modals = Array.from(
|
|
63
107
|
document.querySelectorAll<HTMLElement>(`[${MODAL_ATTR}]`),
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const openModals = modals.filter((el) => {
|
|
111
|
+
const rawId = el.getAttribute(MODAL_ID_ATTR);
|
|
112
|
+
|
|
113
|
+
if (rawId === null) return true;
|
|
114
|
+
|
|
115
|
+
return modalStack.value.includes(Number(rawId));
|
|
68
116
|
});
|
|
69
|
-
|
|
70
|
-
|
|
117
|
+
|
|
118
|
+
if (openModals.length === 0) return null;
|
|
119
|
+
|
|
120
|
+
return openModals.reduce((a, b) => (zOf(b) >= zOf(a) ? b : a));
|
|
71
121
|
};
|
|
72
122
|
|
|
73
123
|
function reconcileInert() {
|
|
@@ -80,6 +130,7 @@ function reconcileInert() {
|
|
|
80
130
|
});
|
|
81
131
|
|
|
82
132
|
const topEl = getTopModalEl();
|
|
133
|
+
|
|
83
134
|
if (!topEl) return;
|
|
84
135
|
|
|
85
136
|
const topZ = zOf(topEl);
|
|
@@ -87,7 +138,6 @@ function reconcileInert() {
|
|
|
87
138
|
Array.from(document.body.children).forEach((el) => {
|
|
88
139
|
if (!(el instanceof HTMLElement)) return;
|
|
89
140
|
if (el === topEl || el.contains(topEl)) return;
|
|
90
|
-
|
|
91
141
|
if (el.hasAttribute(OVERLAY_ATTR) && zOf(el) > topZ) return;
|
|
92
142
|
|
|
93
143
|
el.setAttribute("inert", "");
|
|
@@ -113,14 +163,19 @@ export function useModalLayer(): UseModalLayerReturn {
|
|
|
113
163
|
|
|
114
164
|
const open = () => {
|
|
115
165
|
layer.acquire();
|
|
116
|
-
|
|
166
|
+
|
|
167
|
+
if (!modalStack.value.includes(layer.id)) {
|
|
117
168
|
modalStack.value = [...modalStack.value, layer.id];
|
|
169
|
+
}
|
|
170
|
+
|
|
118
171
|
nextTick(reconcileInert);
|
|
119
172
|
};
|
|
120
173
|
|
|
121
174
|
const close = () => {
|
|
122
175
|
layer.release();
|
|
176
|
+
|
|
123
177
|
modalStack.value = modalStack.value.filter((x) => x !== layer.id);
|
|
178
|
+
|
|
124
179
|
reconcileInert();
|
|
125
180
|
nextTick(reconcileInert);
|
|
126
181
|
};
|
|
@@ -118,7 +118,7 @@ export function usePosition({
|
|
|
118
118
|
[active, () => direction, () => align],
|
|
119
119
|
([isActive]) => {
|
|
120
120
|
if (isActive) {
|
|
121
|
-
acquire();
|
|
121
|
+
acquire(target.value);
|
|
122
122
|
updatePosition();
|
|
123
123
|
observeResize();
|
|
124
124
|
window.addEventListener("resize", updatePosition);
|