@oxcide-ui/core 0.0.3 → 0.0.4
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/index.d.ts +136 -0
- package/dist/index.js +217 -0
- package/package.json +9 -5
- package/CHANGELOG.md +0 -13
- package/src/composables/index.ts +0 -2
- package/src/composables/useFocusTrap.ts +0 -70
- package/src/composables/useKeydown.ts +0 -41
- package/src/composables/useLocale.ts +0 -28
- package/src/composables/useOxcideUI.ts +0 -14
- package/src/composables/useZIndex.ts +0 -18
- package/src/config.ts +0 -58
- package/src/index.ts +0 -9
- package/src/types.ts +0 -45
- package/src/utils/dom.ts +0 -22
- package/src/utils/zindex.ts +0 -29
- package/tsconfig.json +0 -11
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import * as vue from 'vue';
|
|
2
|
+
import { App, Ref } from 'vue';
|
|
3
|
+
|
|
4
|
+
interface LocaleConfig {
|
|
5
|
+
/** UI text translations */
|
|
6
|
+
accept?: string;
|
|
7
|
+
reject?: string;
|
|
8
|
+
clear?: string;
|
|
9
|
+
apply?: string;
|
|
10
|
+
/** Calendar */
|
|
11
|
+
dayNames?: string[];
|
|
12
|
+
dayNamesShort?: string[];
|
|
13
|
+
dayNamesMin?: string[];
|
|
14
|
+
monthNames?: string[];
|
|
15
|
+
monthNamesShort?: string[];
|
|
16
|
+
/** ARIA accessibility labels */
|
|
17
|
+
aria?: {
|
|
18
|
+
close?: string;
|
|
19
|
+
previous?: string;
|
|
20
|
+
next?: string;
|
|
21
|
+
navigation?: string;
|
|
22
|
+
selectAll?: string;
|
|
23
|
+
[key: string]: string | undefined;
|
|
24
|
+
};
|
|
25
|
+
[key: string]: unknown;
|
|
26
|
+
}
|
|
27
|
+
interface OxcideUIConfig {
|
|
28
|
+
/** Locale configuration for i18n */
|
|
29
|
+
locale?: LocaleConfig;
|
|
30
|
+
/** Theme name */
|
|
31
|
+
theme?: string;
|
|
32
|
+
/** Whether to enable ripple effect */
|
|
33
|
+
ripple?: boolean;
|
|
34
|
+
/** z-index layering config */
|
|
35
|
+
zIndex?: {
|
|
36
|
+
modal?: number;
|
|
37
|
+
overlay?: number;
|
|
38
|
+
menu?: number;
|
|
39
|
+
tooltip?: number;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
declare function createOxcideUI(config?: OxcideUIConfig): {
|
|
44
|
+
install(app: App): void;
|
|
45
|
+
config: {
|
|
46
|
+
locale?: {
|
|
47
|
+
[x: string]: unknown;
|
|
48
|
+
accept?: string | undefined;
|
|
49
|
+
reject?: string | undefined;
|
|
50
|
+
clear?: string | undefined;
|
|
51
|
+
apply?: string | undefined;
|
|
52
|
+
dayNames?: string[] | undefined;
|
|
53
|
+
dayNamesShort?: string[] | undefined;
|
|
54
|
+
dayNamesMin?: string[] | undefined;
|
|
55
|
+
monthNames?: string[] | undefined;
|
|
56
|
+
monthNamesShort?: string[] | undefined;
|
|
57
|
+
aria?: {
|
|
58
|
+
[x: string]: string | undefined;
|
|
59
|
+
close?: string | undefined;
|
|
60
|
+
previous?: string | undefined;
|
|
61
|
+
next?: string | undefined;
|
|
62
|
+
navigation?: string | undefined;
|
|
63
|
+
selectAll?: string | undefined;
|
|
64
|
+
} | undefined;
|
|
65
|
+
} | undefined;
|
|
66
|
+
theme?: string | undefined;
|
|
67
|
+
ripple?: boolean | undefined;
|
|
68
|
+
zIndex?: {
|
|
69
|
+
modal?: number | undefined;
|
|
70
|
+
overlay?: number | undefined;
|
|
71
|
+
menu?: number | undefined;
|
|
72
|
+
tooltip?: number | undefined;
|
|
73
|
+
} | undefined;
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Access the global OxcideUI configuration instance.
|
|
79
|
+
*/
|
|
80
|
+
declare function useOxcideUI(): OxcideUIConfig;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Access locale translations from OxcideUI configuration.
|
|
84
|
+
* Provides reactive locale values that update when config changes.
|
|
85
|
+
*/
|
|
86
|
+
declare function useLocale(): {
|
|
87
|
+
locale: vue.ComputedRef<LocaleConfig>;
|
|
88
|
+
t: (key: string) => string;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Traps focus within the specified element.
|
|
93
|
+
* Useful for modal dialogs, drawers, etc.
|
|
94
|
+
*
|
|
95
|
+
* @param target The element to trap focus in
|
|
96
|
+
* @param active Whether the trap is currently active
|
|
97
|
+
*/
|
|
98
|
+
declare function useFocusTrap(target: Ref<HTMLElement | null | undefined>, active: Ref<boolean>): void;
|
|
99
|
+
|
|
100
|
+
type KeyEventHandler = (event: KeyboardEvent) => void;
|
|
101
|
+
/**
|
|
102
|
+
* Register a keyboard event handler for a specific key.
|
|
103
|
+
*
|
|
104
|
+
* @param key The keyboard key to listen for (e.g., 'Escape')
|
|
105
|
+
* @param handler The handler function
|
|
106
|
+
* @param active Whether the listener is active
|
|
107
|
+
*/
|
|
108
|
+
declare function useKeydown(key: string, handler: KeyEventHandler, active?: boolean | Ref<boolean>): void;
|
|
109
|
+
|
|
110
|
+
type ZIndexType = 'modal' | 'overlay' | 'menu' | 'tooltip';
|
|
111
|
+
/**
|
|
112
|
+
* Get the next z-index for a specific layer type and increment it.
|
|
113
|
+
*/
|
|
114
|
+
declare function getNextZIndex(key: ZIndexType): number;
|
|
115
|
+
/**
|
|
116
|
+
* Set the base z-index for a specific layer type.
|
|
117
|
+
*/
|
|
118
|
+
declare function setBaseZIndex(key: ZIndexType, value: number): void;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Composable to manage z-index for dynamic overlays.
|
|
122
|
+
* Automatically increments the z-index on mount if autoZIndex is enabled.
|
|
123
|
+
*/
|
|
124
|
+
declare function useZIndex(key?: ZIndexType, autoZIndex?: boolean, baseZIndex?: number): {
|
|
125
|
+
zIndex: vue.Ref<number, number>;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
declare function isClient(): boolean;
|
|
129
|
+
/**
|
|
130
|
+
* Ensures that a teleport target exist in the DOM.
|
|
131
|
+
* Useful for Nuxt SSR environments where the teleport target might missing in the initial HTML.
|
|
132
|
+
* @param to The selector for the teleport target (e.g., '#teleports')
|
|
133
|
+
*/
|
|
134
|
+
declare function ensureTeleportTarget(to: string): void;
|
|
135
|
+
|
|
136
|
+
export { type LocaleConfig, type OxcideUIConfig, createOxcideUI, ensureTeleportTarget, getNextZIndex, isClient, setBaseZIndex, useFocusTrap, useKeydown, useLocale, useOxcideUI, useZIndex };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
// src/config.ts
|
|
2
|
+
import { reactive } from "vue";
|
|
3
|
+
var defaultLocale = {
|
|
4
|
+
accept: "Yes",
|
|
5
|
+
reject: "No",
|
|
6
|
+
clear: "Clear",
|
|
7
|
+
apply: "Apply",
|
|
8
|
+
aria: {
|
|
9
|
+
close: "Close",
|
|
10
|
+
previous: "Previous",
|
|
11
|
+
next: "Next",
|
|
12
|
+
navigation: "Navigation",
|
|
13
|
+
selectAll: "Select All"
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
var defaultConfig = {
|
|
17
|
+
locale: defaultLocale,
|
|
18
|
+
theme: "aura",
|
|
19
|
+
ripple: false,
|
|
20
|
+
zIndex: {
|
|
21
|
+
modal: 1100,
|
|
22
|
+
overlay: 1e3,
|
|
23
|
+
menu: 1e3,
|
|
24
|
+
tooltip: 1100
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
var OxcideUISymbol = /* @__PURE__ */ Symbol("OxcideUI");
|
|
28
|
+
function createOxcideUI(config = {}) {
|
|
29
|
+
const resolvedConfig = reactive({
|
|
30
|
+
...defaultConfig,
|
|
31
|
+
...config,
|
|
32
|
+
locale: {
|
|
33
|
+
...defaultLocale,
|
|
34
|
+
...config.locale,
|
|
35
|
+
aria: {
|
|
36
|
+
...defaultLocale.aria,
|
|
37
|
+
...config.locale?.aria
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
zIndex: {
|
|
41
|
+
...defaultConfig.zIndex,
|
|
42
|
+
...config.zIndex
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
return {
|
|
46
|
+
install(app) {
|
|
47
|
+
app.provide(OxcideUISymbol, resolvedConfig);
|
|
48
|
+
app.config.globalProperties.$oxcide = resolvedConfig;
|
|
49
|
+
},
|
|
50
|
+
config: resolvedConfig
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/composables/useOxcideUI.ts
|
|
55
|
+
import { inject } from "vue";
|
|
56
|
+
function useOxcideUI() {
|
|
57
|
+
const config = inject(OxcideUISymbol);
|
|
58
|
+
if (!config) {
|
|
59
|
+
throw new Error("[OxcideUI] Plugin not installed. Call app.use(createOxcideUI()) first.");
|
|
60
|
+
}
|
|
61
|
+
return config;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/composables/useLocale.ts
|
|
65
|
+
import { computed } from "vue";
|
|
66
|
+
function useLocale() {
|
|
67
|
+
const config = useOxcideUI();
|
|
68
|
+
const locale = computed(() => config.locale ?? {});
|
|
69
|
+
function t(key) {
|
|
70
|
+
const value = locale.value[key];
|
|
71
|
+
if (typeof value === "string") return value;
|
|
72
|
+
if (key.startsWith("aria.")) {
|
|
73
|
+
const ariaKey = key.slice(5);
|
|
74
|
+
return locale.value.aria?.[ariaKey] ?? key;
|
|
75
|
+
}
|
|
76
|
+
return key;
|
|
77
|
+
}
|
|
78
|
+
return { locale, t };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/composables/useFocusTrap.ts
|
|
82
|
+
import { onUnmounted, watch } from "vue";
|
|
83
|
+
var FOCUSABLE_SELECTOR = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
|
|
84
|
+
function useFocusTrap(target, active) {
|
|
85
|
+
let firstFocusableEl = null;
|
|
86
|
+
let lastFocusableEl = null;
|
|
87
|
+
const getFocusableElements = () => {
|
|
88
|
+
if (!target.value) return [];
|
|
89
|
+
return Array.from(target.value.querySelectorAll(FOCUSABLE_SELECTOR)).filter(
|
|
90
|
+
(el) => !el.hasAttribute("disabled") && el.getAttribute("aria-hidden") !== "true"
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
const handleKeyDown = (e) => {
|
|
94
|
+
if (!active.value || e.key !== "Tab") return;
|
|
95
|
+
const focusableElements = getFocusableElements();
|
|
96
|
+
if (focusableElements.length === 0) {
|
|
97
|
+
e.preventDefault();
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
firstFocusableEl = focusableElements[0];
|
|
101
|
+
lastFocusableEl = focusableElements[focusableElements.length - 1];
|
|
102
|
+
if (e.shiftKey) {
|
|
103
|
+
if (document.activeElement === firstFocusableEl) {
|
|
104
|
+
lastFocusableEl.focus();
|
|
105
|
+
e.preventDefault();
|
|
106
|
+
}
|
|
107
|
+
} else {
|
|
108
|
+
if (document.activeElement === lastFocusableEl) {
|
|
109
|
+
firstFocusableEl.focus();
|
|
110
|
+
e.preventDefault();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
watch(
|
|
115
|
+
active,
|
|
116
|
+
(isActive) => {
|
|
117
|
+
if (isActive) {
|
|
118
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
119
|
+
const focusable = getFocusableElements();
|
|
120
|
+
if (focusable.length > 0) {
|
|
121
|
+
focusable[0].focus();
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
{ immediate: true }
|
|
128
|
+
);
|
|
129
|
+
onUnmounted(() => {
|
|
130
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// src/composables/useKeydown.ts
|
|
135
|
+
import { onUnmounted as onUnmounted2, watch as watch2 } from "vue";
|
|
136
|
+
function useKeydown(key, handler, active = true) {
|
|
137
|
+
const handleKeyDown = (e) => {
|
|
138
|
+
if (e.key === key) {
|
|
139
|
+
handler(e);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
const activate = () => {
|
|
143
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
144
|
+
};
|
|
145
|
+
const deactivate = () => {
|
|
146
|
+
window.removeEventListener("keydown", handleKeyDown);
|
|
147
|
+
};
|
|
148
|
+
if (active === true || typeof active !== "boolean" && active.value) {
|
|
149
|
+
activate();
|
|
150
|
+
}
|
|
151
|
+
if (typeof active !== "boolean") {
|
|
152
|
+
watch2(active, (isActive) => {
|
|
153
|
+
if (isActive) activate();
|
|
154
|
+
else deactivate();
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
onUnmounted2(() => {
|
|
158
|
+
deactivate();
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// src/composables/useZIndex.ts
|
|
163
|
+
import { ref, onMounted } from "vue";
|
|
164
|
+
|
|
165
|
+
// src/utils/zindex.ts
|
|
166
|
+
var zIndex = {
|
|
167
|
+
modal: 1100,
|
|
168
|
+
overlay: 1e3,
|
|
169
|
+
menu: 1e3,
|
|
170
|
+
tooltip: 1100
|
|
171
|
+
};
|
|
172
|
+
function getNextZIndex(key) {
|
|
173
|
+
return ++zIndex[key];
|
|
174
|
+
}
|
|
175
|
+
function setBaseZIndex(key, value) {
|
|
176
|
+
zIndex[key] = value;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/composables/useZIndex.ts
|
|
180
|
+
function useZIndex(key = "overlay", autoZIndex = true, baseZIndex = 0) {
|
|
181
|
+
const z = ref(baseZIndex || 0);
|
|
182
|
+
onMounted(() => {
|
|
183
|
+
if (autoZIndex) {
|
|
184
|
+
z.value = getNextZIndex(key);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
return { zIndex: z };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// src/utils/dom.ts
|
|
191
|
+
function isClient() {
|
|
192
|
+
return typeof window !== "undefined" && typeof document !== "undefined";
|
|
193
|
+
}
|
|
194
|
+
function ensureTeleportTarget(to) {
|
|
195
|
+
if (!isClient()) return;
|
|
196
|
+
if (typeof to === "string" && to.startsWith("#")) {
|
|
197
|
+
const id = to.slice(1);
|
|
198
|
+
const target = document.getElementById(id);
|
|
199
|
+
if (!target) {
|
|
200
|
+
const el = document.createElement("div");
|
|
201
|
+
el.id = id;
|
|
202
|
+
document.body.appendChild(el);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
export {
|
|
207
|
+
createOxcideUI,
|
|
208
|
+
ensureTeleportTarget,
|
|
209
|
+
getNextZIndex,
|
|
210
|
+
isClient,
|
|
211
|
+
setBaseZIndex,
|
|
212
|
+
useFocusTrap,
|
|
213
|
+
useKeydown,
|
|
214
|
+
useLocale,
|
|
215
|
+
useOxcideUI,
|
|
216
|
+
useZIndex
|
|
217
|
+
};
|
package/package.json
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxcide-ui/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
7
|
-
"types": "./
|
|
8
|
-
"import": "./
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"import": "./dist/index.js"
|
|
9
9
|
}
|
|
10
10
|
},
|
|
11
|
-
"main": "./
|
|
12
|
-
"types": "./
|
|
11
|
+
"main": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
13
16
|
"dependencies": {
|
|
14
17
|
"zod": "3.25.76"
|
|
15
18
|
},
|
|
@@ -25,6 +28,7 @@
|
|
|
25
28
|
"access": "public"
|
|
26
29
|
},
|
|
27
30
|
"scripts": {
|
|
31
|
+
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
28
32
|
"type-check": "vue-tsc --noEmit",
|
|
29
33
|
"lint": "biome check .",
|
|
30
34
|
"lint:fix": "biome check --write ."
|
package/CHANGELOG.md
DELETED
package/src/composables/index.ts
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { type Ref, onUnmounted, watch } from 'vue'
|
|
2
|
-
|
|
3
|
-
const FOCUSABLE_SELECTOR = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Traps focus within the specified element.
|
|
7
|
-
* Useful for modal dialogs, drawers, etc.
|
|
8
|
-
*
|
|
9
|
-
* @param target The element to trap focus in
|
|
10
|
-
* @param active Whether the trap is currently active
|
|
11
|
-
*/
|
|
12
|
-
export function useFocusTrap(target: Ref<HTMLElement | null | undefined>, active: Ref<boolean>) {
|
|
13
|
-
let firstFocusableEl: HTMLElement | null = null
|
|
14
|
-
let lastFocusableEl: HTMLElement | null = null
|
|
15
|
-
|
|
16
|
-
const getFocusableElements = () => {
|
|
17
|
-
if (!target.value) return []
|
|
18
|
-
return Array.from(target.value.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)).filter(
|
|
19
|
-
(el) => !el.hasAttribute('disabled') && el.getAttribute('aria-hidden') !== 'true'
|
|
20
|
-
)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const handleKeyDown = (e: KeyboardEvent) => {
|
|
24
|
-
if (!active.value || e.key !== 'Tab') return
|
|
25
|
-
|
|
26
|
-
const focusableElements = getFocusableElements()
|
|
27
|
-
if (focusableElements.length === 0) {
|
|
28
|
-
e.preventDefault()
|
|
29
|
-
return
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
firstFocusableEl = focusableElements[0]
|
|
33
|
-
lastFocusableEl = focusableElements[focusableElements.length - 1]
|
|
34
|
-
|
|
35
|
-
if (e.shiftKey) {
|
|
36
|
-
// Shift + Tab (backwards)
|
|
37
|
-
if (document.activeElement === firstFocusableEl) {
|
|
38
|
-
lastFocusableEl.focus()
|
|
39
|
-
e.preventDefault()
|
|
40
|
-
}
|
|
41
|
-
} else {
|
|
42
|
-
// Tab (forwards)
|
|
43
|
-
if (document.activeElement === lastFocusableEl) {
|
|
44
|
-
firstFocusableEl.focus()
|
|
45
|
-
e.preventDefault()
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
watch(
|
|
51
|
-
active,
|
|
52
|
-
(isActive) => {
|
|
53
|
-
if (isActive) {
|
|
54
|
-
document.addEventListener('keydown', handleKeyDown)
|
|
55
|
-
// Optional: Auto focus the first element
|
|
56
|
-
const focusable = getFocusableElements()
|
|
57
|
-
if (focusable.length > 0) {
|
|
58
|
-
focusable[0].focus()
|
|
59
|
-
}
|
|
60
|
-
} else {
|
|
61
|
-
document.removeEventListener('keydown', handleKeyDown)
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
{ immediate: true }
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
onUnmounted(() => {
|
|
68
|
-
document.removeEventListener('keydown', handleKeyDown)
|
|
69
|
-
})
|
|
70
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { onUnmounted, watch, type Ref } from 'vue'
|
|
2
|
-
|
|
3
|
-
type KeyEventHandler = (event: KeyboardEvent) => void
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Register a keyboard event handler for a specific key.
|
|
7
|
-
*
|
|
8
|
-
* @param key The keyboard key to listen for (e.g., 'Escape')
|
|
9
|
-
* @param handler The handler function
|
|
10
|
-
* @param active Whether the listener is active
|
|
11
|
-
*/
|
|
12
|
-
export function useKeydown(key: string, handler: KeyEventHandler, active: boolean | Ref<boolean> = true) {
|
|
13
|
-
const handleKeyDown = (e: KeyboardEvent) => {
|
|
14
|
-
if (e.key === key) {
|
|
15
|
-
handler(e)
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const activate = () => {
|
|
20
|
-
window.addEventListener('keydown', handleKeyDown)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const deactivate = () => {
|
|
24
|
-
window.removeEventListener('keydown', handleKeyDown)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (active === true || (typeof active !== 'boolean' && active.value)) {
|
|
28
|
-
activate()
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (typeof active !== 'boolean') {
|
|
32
|
-
watch(active, (isActive) => {
|
|
33
|
-
if (isActive) activate()
|
|
34
|
-
else deactivate()
|
|
35
|
-
})
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
onUnmounted(() => {
|
|
39
|
-
deactivate()
|
|
40
|
-
})
|
|
41
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { computed } from 'vue'
|
|
2
|
-
import { useOxcideUI } from './useOxcideUI'
|
|
3
|
-
import type { LocaleConfig } from '../types'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Access locale translations from OxcideUI configuration.
|
|
7
|
-
* Provides reactive locale values that update when config changes.
|
|
8
|
-
*/
|
|
9
|
-
export function useLocale() {
|
|
10
|
-
const config = useOxcideUI()
|
|
11
|
-
|
|
12
|
-
const locale = computed<LocaleConfig>(() => config.locale ?? {})
|
|
13
|
-
|
|
14
|
-
function t(key: string): string {
|
|
15
|
-
const value = locale.value[key]
|
|
16
|
-
if (typeof value === 'string') return value
|
|
17
|
-
|
|
18
|
-
// Check nested aria keys
|
|
19
|
-
if (key.startsWith('aria.')) {
|
|
20
|
-
const ariaKey = key.slice(5)
|
|
21
|
-
return locale.value.aria?.[ariaKey] ?? key
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return key
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return { locale, t }
|
|
28
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { inject } from 'vue'
|
|
2
|
-
import { OxcideUISymbol } from '../config'
|
|
3
|
-
import type { OxcideUIConfig } from '../types'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Access the global OxcideUI configuration instance.
|
|
7
|
-
*/
|
|
8
|
-
export function useOxcideUI(): OxcideUIConfig {
|
|
9
|
-
const config = inject(OxcideUISymbol)
|
|
10
|
-
if (!config) {
|
|
11
|
-
throw new Error('[OxcideUI] Plugin not installed. Call app.use(createOxcideUI()) first.')
|
|
12
|
-
}
|
|
13
|
-
return config
|
|
14
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { ref, onMounted } from 'vue'
|
|
2
|
-
import { getNextZIndex, type ZIndexType } from '../utils/zindex'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Composable to manage z-index for dynamic overlays.
|
|
6
|
-
* Automatically increments the z-index on mount if autoZIndex is enabled.
|
|
7
|
-
*/
|
|
8
|
-
export function useZIndex(key: ZIndexType = 'overlay', autoZIndex = true, baseZIndex = 0) {
|
|
9
|
-
const z = ref(baseZIndex || 0)
|
|
10
|
-
|
|
11
|
-
onMounted(() => {
|
|
12
|
-
if (autoZIndex) {
|
|
13
|
-
z.value = getNextZIndex(key)
|
|
14
|
-
}
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
return { zIndex: z }
|
|
18
|
-
}
|
package/src/config.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import type { App, InjectionKey } from 'vue'
|
|
2
|
-
import { reactive } from 'vue'
|
|
3
|
-
import type { OxcideUIConfig } from './types'
|
|
4
|
-
|
|
5
|
-
const defaultLocale = {
|
|
6
|
-
accept: 'Yes',
|
|
7
|
-
reject: 'No',
|
|
8
|
-
clear: 'Clear',
|
|
9
|
-
apply: 'Apply',
|
|
10
|
-
aria: {
|
|
11
|
-
close: 'Close',
|
|
12
|
-
previous: 'Previous',
|
|
13
|
-
next: 'Next',
|
|
14
|
-
navigation: 'Navigation',
|
|
15
|
-
selectAll: 'Select All'
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const defaultConfig: OxcideUIConfig = {
|
|
20
|
-
locale: defaultLocale,
|
|
21
|
-
theme: 'aura',
|
|
22
|
-
ripple: false,
|
|
23
|
-
zIndex: {
|
|
24
|
-
modal: 1100,
|
|
25
|
-
overlay: 1000,
|
|
26
|
-
menu: 1000,
|
|
27
|
-
tooltip: 1100
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export const OxcideUISymbol: InjectionKey<OxcideUIConfig> = Symbol('OxcideUI')
|
|
32
|
-
|
|
33
|
-
export function createOxcideUI(config: OxcideUIConfig = {}) {
|
|
34
|
-
const resolvedConfig = reactive<OxcideUIConfig>({
|
|
35
|
-
...defaultConfig,
|
|
36
|
-
...config,
|
|
37
|
-
locale: {
|
|
38
|
-
...defaultLocale,
|
|
39
|
-
...config.locale,
|
|
40
|
-
aria: {
|
|
41
|
-
...defaultLocale.aria,
|
|
42
|
-
...config.locale?.aria
|
|
43
|
-
}
|
|
44
|
-
},
|
|
45
|
-
zIndex: {
|
|
46
|
-
...defaultConfig.zIndex,
|
|
47
|
-
...config.zIndex
|
|
48
|
-
}
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
return {
|
|
52
|
-
install(app: App) {
|
|
53
|
-
app.provide(OxcideUISymbol, resolvedConfig)
|
|
54
|
-
app.config.globalProperties.$oxcide = resolvedConfig
|
|
55
|
-
},
|
|
56
|
-
config: resolvedConfig
|
|
57
|
-
}
|
|
58
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export { createOxcideUI } from './config'
|
|
2
|
-
export { useOxcideUI } from './composables/useOxcideUI'
|
|
3
|
-
export { useLocale } from './composables/useLocale'
|
|
4
|
-
export { useFocusTrap } from './composables/useFocusTrap'
|
|
5
|
-
export { useKeydown } from './composables/useKeydown'
|
|
6
|
-
export { useZIndex } from './composables/useZIndex'
|
|
7
|
-
export { getNextZIndex, setBaseZIndex } from './utils/zindex'
|
|
8
|
-
export { ensureTeleportTarget, isClient } from './utils/dom'
|
|
9
|
-
export type { OxcideUIConfig, LocaleConfig } from './types'
|
package/src/types.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
export interface LocaleConfig {
|
|
2
|
-
/** UI text translations */
|
|
3
|
-
accept?: string
|
|
4
|
-
reject?: string
|
|
5
|
-
clear?: string
|
|
6
|
-
apply?: string
|
|
7
|
-
|
|
8
|
-
/** Calendar */
|
|
9
|
-
dayNames?: string[]
|
|
10
|
-
dayNamesShort?: string[]
|
|
11
|
-
dayNamesMin?: string[]
|
|
12
|
-
monthNames?: string[]
|
|
13
|
-
monthNamesShort?: string[]
|
|
14
|
-
|
|
15
|
-
/** ARIA accessibility labels */
|
|
16
|
-
aria?: {
|
|
17
|
-
close?: string
|
|
18
|
-
previous?: string
|
|
19
|
-
next?: string
|
|
20
|
-
navigation?: string
|
|
21
|
-
selectAll?: string
|
|
22
|
-
[key: string]: string | undefined
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
[key: string]: unknown
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface OxcideUIConfig {
|
|
29
|
-
/** Locale configuration for i18n */
|
|
30
|
-
locale?: LocaleConfig
|
|
31
|
-
|
|
32
|
-
/** Theme name */
|
|
33
|
-
theme?: string
|
|
34
|
-
|
|
35
|
-
/** Whether to enable ripple effect */
|
|
36
|
-
ripple?: boolean
|
|
37
|
-
|
|
38
|
-
/** z-index layering config */
|
|
39
|
-
zIndex?: {
|
|
40
|
-
modal?: number
|
|
41
|
-
overlay?: number
|
|
42
|
-
menu?: number
|
|
43
|
-
tooltip?: number
|
|
44
|
-
}
|
|
45
|
-
}
|
package/src/utils/dom.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
export function isClient() {
|
|
2
|
-
return typeof window !== 'undefined' && typeof document !== 'undefined'
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Ensures that a teleport target exist in the DOM.
|
|
7
|
-
* Useful for Nuxt SSR environments where the teleport target might missing in the initial HTML.
|
|
8
|
-
* @param to The selector for the teleport target (e.g., '#teleports')
|
|
9
|
-
*/
|
|
10
|
-
export function ensureTeleportTarget(to: string) {
|
|
11
|
-
if (!isClient()) return
|
|
12
|
-
|
|
13
|
-
if (typeof to === 'string' && to.startsWith('#')) {
|
|
14
|
-
const id = to.slice(1)
|
|
15
|
-
const target = document.getElementById(id)
|
|
16
|
-
if (!target) {
|
|
17
|
-
const el = document.createElement('div')
|
|
18
|
-
el.id = id
|
|
19
|
-
document.body.appendChild(el)
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
package/src/utils/zindex.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
export type ZIndexType = 'modal' | 'overlay' | 'menu' | 'tooltip'
|
|
2
|
-
|
|
3
|
-
const zIndex: Record<ZIndexType, number> = {
|
|
4
|
-
modal: 1100,
|
|
5
|
-
overlay: 1000,
|
|
6
|
-
menu: 1000,
|
|
7
|
-
tooltip: 1100
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Get the next z-index for a specific layer type and increment it.
|
|
12
|
-
*/
|
|
13
|
-
export function getNextZIndex(key: ZIndexType) {
|
|
14
|
-
return ++zIndex[key]
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Get the current z-index for a specific layer type.
|
|
19
|
-
*/
|
|
20
|
-
export function getCurrentZIndex(key: ZIndexType) {
|
|
21
|
-
return zIndex[key]
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Set the base z-index for a specific layer type.
|
|
26
|
-
*/
|
|
27
|
-
export function setBaseZIndex(key: ZIndexType, value: number) {
|
|
28
|
-
zIndex[key] = value
|
|
29
|
-
}
|
package/tsconfig.json
DELETED