@elvora/core 1.0.0-rc.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Elvora UI Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,24 @@
1
+ # @elvora/core
2
+
3
+ Framework-agnostic primitives, state machines, accessibility logic, and shared types powering [Elvora UI](https://github.com/elvora-ui/elvora)'s React, React Native, Angular, Vue, and Svelte adapters.
4
+
5
+ ## What's inside
6
+
7
+ - **State hooks (React)**: `useControllableState`, `useFocusTrap`, `useDismissable`, `usePortal`, `useId`.
8
+ - **State machines**: `getButtonStyle`, `getNextTabIndex`, focus/keyboard navigation helpers.
9
+ - **Shared types**: `ElvoraTheme`, `ElvoraSize`, `ElvoraVariant`, `ElvoraStatus`, `ElvoraTone`, `Direction`, `Placement`, `Orientation`, etc.
10
+ - **Default props**: `defaultButtonProps`, etc.
11
+
12
+ You probably don't need to install this directly — every framework adapter
13
+ depends on it. Install it only if you want to build your own adapter or
14
+ consume the headless logic in a custom renderer.
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ pnpm add @elvora/core
20
+ ```
21
+
22
+ ## License
23
+
24
+ MIT
@@ -0,0 +1,127 @@
1
+ // src/focus/focusable.ts
2
+ var FOCUSABLE_SELECTORS = [
3
+ "a[href]",
4
+ "area[href]",
5
+ "button:not([disabled])",
6
+ 'input:not([disabled]):not([type="hidden"])',
7
+ "select:not([disabled])",
8
+ "textarea:not([disabled])",
9
+ "iframe",
10
+ "object",
11
+ "embed",
12
+ "audio[controls]",
13
+ "video[controls]",
14
+ '[contenteditable]:not([contenteditable="false"])',
15
+ '[tabindex]:not([tabindex^="-"])'
16
+ ];
17
+ var FOCUSABLE_SELECTOR = FOCUSABLE_SELECTORS.join(", ");
18
+ function isElementVisible(el) {
19
+ if (el.hidden) return false;
20
+ const style = el.ownerDocument.defaultView?.getComputedStyle(el);
21
+ if (!style) return true;
22
+ return style.visibility !== "hidden" && style.display !== "none";
23
+ }
24
+ function isFocusable(el) {
25
+ if (!el.isConnected) return false;
26
+ if (el.disabled) return false;
27
+ if (el.getAttribute("aria-hidden") === "true") return false;
28
+ if (el.hasAttribute("inert")) return false;
29
+ if (!isElementVisible(el)) return false;
30
+ return el.matches(FOCUSABLE_SELECTOR);
31
+ }
32
+ function getFocusableElements(container) {
33
+ const nodes = Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR));
34
+ return nodes.filter(isFocusable);
35
+ }
36
+ function getFirstFocusable(container) {
37
+ return getFocusableElements(container)[0] ?? null;
38
+ }
39
+ function getLastFocusable(container) {
40
+ const list = getFocusableElements(container);
41
+ return list[list.length - 1] ?? null;
42
+ }
43
+
44
+ // src/focus/focusTrap.ts
45
+ function createFocusTrap(container, options = {}) {
46
+ let active = false;
47
+ let previouslyFocused = null;
48
+ function handleKeyDown(event) {
49
+ if (!active) return;
50
+ if (event.key === "Escape" && options.onEscape) {
51
+ options.onEscape(event);
52
+ return;
53
+ }
54
+ if (event.key !== "Tab") return;
55
+ const first = getFirstFocusable(container);
56
+ const last = getLastFocusable(container);
57
+ if (!first || !last) {
58
+ event.preventDefault();
59
+ container.focus();
60
+ return;
61
+ }
62
+ const activeEl = container.ownerDocument.activeElement;
63
+ if (event.shiftKey) {
64
+ if (activeEl === first || !container.contains(activeEl)) {
65
+ event.preventDefault();
66
+ last.focus();
67
+ }
68
+ } else {
69
+ if (activeEl === last) {
70
+ event.preventDefault();
71
+ first.focus();
72
+ }
73
+ }
74
+ }
75
+ function handlePointerDown(event) {
76
+ if (!active) return;
77
+ const target = event.target;
78
+ if (target && !container.contains(target)) {
79
+ options.onOutsideClick?.(event);
80
+ }
81
+ }
82
+ return {
83
+ activate() {
84
+ if (active) return;
85
+ active = true;
86
+ previouslyFocused = container.ownerDocument.activeElement ?? null;
87
+ const target = options.initialFocus ?? getFirstFocusable(container) ?? (isFocusable(container) ? container : null);
88
+ if (target) {
89
+ target.focus({ preventScroll: false });
90
+ } else {
91
+ container.setAttribute("tabindex", "-1");
92
+ container.focus({ preventScroll: false });
93
+ }
94
+ container.ownerDocument.addEventListener("keydown", handleKeyDown, true);
95
+ container.ownerDocument.addEventListener("mousedown", handlePointerDown, true);
96
+ container.ownerDocument.addEventListener("touchstart", handlePointerDown, true);
97
+ },
98
+ deactivate() {
99
+ if (!active) return;
100
+ active = false;
101
+ container.ownerDocument.removeEventListener("keydown", handleKeyDown, true);
102
+ container.ownerDocument.removeEventListener("mousedown", handlePointerDown, true);
103
+ container.ownerDocument.removeEventListener("touchstart", handlePointerDown, true);
104
+ const returnTo = options.returnFocus ?? previouslyFocused;
105
+ if (returnTo && returnTo.isConnected) {
106
+ returnTo.focus({ preventScroll: false });
107
+ }
108
+ },
109
+ isActive() {
110
+ return active;
111
+ }
112
+ };
113
+ }
114
+
115
+ // src/id.ts
116
+ var counter = 0;
117
+ function generateId(prefix = "elvora") {
118
+ if (typeof globalThis !== "undefined" && globalThis.crypto?.randomUUID) {
119
+ return `${prefix}-${globalThis.crypto.randomUUID().slice(0, 8)}`;
120
+ }
121
+ counter = counter + 1 | 0;
122
+ return `${prefix}-${counter.toString(36)}`;
123
+ }
124
+
125
+ export { FOCUSABLE_SELECTOR, createFocusTrap, generateId, getFirstFocusable, getFocusableElements, getLastFocusable, isElementVisible, isFocusable };
126
+ //# sourceMappingURL=chunk-VFHCOPTO.js.map
127
+ //# sourceMappingURL=chunk-VFHCOPTO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/focus/focusable.ts","../src/focus/focusTrap.ts","../src/id.ts"],"names":[],"mappings":"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","file":"chunk-VFHCOPTO.js","sourcesContent":["/**\r\n * Discover and walk focusable elements within a container. Used by focus traps,\r\n * roving-tabindex managers, and keyboard-navigable lists/menus.\r\n *\r\n * Selectors are based on the WAI-ARIA Authoring Practices and the live\r\n * `tabindex`/`disabled`/`inert` semantics of HTML.\r\n */\r\n\r\nconst FOCUSABLE_SELECTORS = [\r\n 'a[href]',\r\n 'area[href]',\r\n 'button:not([disabled])',\r\n 'input:not([disabled]):not([type=\"hidden\"])',\r\n 'select:not([disabled])',\r\n 'textarea:not([disabled])',\r\n 'iframe',\r\n 'object',\r\n 'embed',\r\n 'audio[controls]',\r\n 'video[controls]',\r\n '[contenteditable]:not([contenteditable=\"false\"])',\r\n '[tabindex]:not([tabindex^=\"-\"])',\r\n] as const;\r\n\r\nexport const FOCUSABLE_SELECTOR = FOCUSABLE_SELECTORS.join(', ');\r\n\r\nexport function isElementVisible(el: HTMLElement): boolean {\r\n if (el.hidden) return false;\r\n const style = el.ownerDocument.defaultView?.getComputedStyle(el);\r\n if (!style) return true;\r\n return style.visibility !== 'hidden' && style.display !== 'none';\r\n}\r\n\r\nexport function isFocusable(el: HTMLElement): boolean {\r\n if (!el.isConnected) return false;\r\n if ((el as HTMLInputElement).disabled) return false;\r\n if (el.getAttribute('aria-hidden') === 'true') return false;\r\n if (el.hasAttribute('inert')) return false;\r\n if (!isElementVisible(el)) return false;\r\n return el.matches(FOCUSABLE_SELECTOR);\r\n}\r\n\r\nexport function getFocusableElements(container: HTMLElement): HTMLElement[] {\r\n const nodes = Array.from(container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR));\r\n return nodes.filter(isFocusable);\r\n}\r\n\r\nexport function getFirstFocusable(container: HTMLElement): HTMLElement | null {\r\n return getFocusableElements(container)[0] ?? null;\r\n}\r\n\r\nexport function getLastFocusable(container: HTMLElement): HTMLElement | null {\r\n const list = getFocusableElements(container);\r\n return list[list.length - 1] ?? null;\r\n}\r\n","/**\r\n * Headless focus trap. Pure DOM, no framework dependency.\r\n *\r\n * Adapters call `createFocusTrap(node)` on mount, `trap.activate()` when the\r\n * dialog/menu opens, and `trap.deactivate()` when it closes. The trap is\r\n * compliant with WAI-ARIA APG's modal-dialog pattern.\r\n */\r\nimport { getFirstFocusable, getLastFocusable, isFocusable } from './focusable';\r\n\r\nexport interface FocusTrapOptions {\r\n /** Element to restore focus to on deactivation. Defaults to the previously active element. */\r\n returnFocus?: HTMLElement | null;\r\n /** Initial element to focus. Defaults to first focusable child. */\r\n initialFocus?: HTMLElement | null;\r\n /** When true, pressing Escape calls `onEscape`. */\r\n onEscape?: (event: KeyboardEvent) => void;\r\n /** Outside clicks call this. Trap does not auto-close on outside click. */\r\n onOutsideClick?: (event: MouseEvent | TouchEvent) => void;\r\n}\r\n\r\nexport interface FocusTrap {\r\n activate(): void;\r\n deactivate(): void;\r\n isActive(): boolean;\r\n}\r\n\r\nexport function createFocusTrap(container: HTMLElement, options: FocusTrapOptions = {}): FocusTrap {\r\n let active = false;\r\n let previouslyFocused: HTMLElement | null = null;\r\n\r\n function handleKeyDown(event: KeyboardEvent): void {\r\n if (!active) return;\r\n\r\n if (event.key === 'Escape' && options.onEscape) {\r\n options.onEscape(event);\r\n return;\r\n }\r\n\r\n if (event.key !== 'Tab') return;\r\n\r\n const first = getFirstFocusable(container);\r\n const last = getLastFocusable(container);\r\n if (!first || !last) {\r\n event.preventDefault();\r\n container.focus();\r\n return;\r\n }\r\n\r\n const activeEl = container.ownerDocument.activeElement as HTMLElement | null;\r\n\r\n if (event.shiftKey) {\r\n if (activeEl === first || !container.contains(activeEl)) {\r\n event.preventDefault();\r\n last.focus();\r\n }\r\n } else {\r\n if (activeEl === last) {\r\n event.preventDefault();\r\n first.focus();\r\n }\r\n }\r\n }\r\n\r\n function handlePointerDown(event: MouseEvent | TouchEvent): void {\r\n if (!active) return;\r\n const target = event.target as Node | null;\r\n if (target && !container.contains(target)) {\r\n options.onOutsideClick?.(event);\r\n }\r\n }\r\n\r\n return {\r\n activate() {\r\n if (active) return;\r\n active = true;\r\n previouslyFocused = (container.ownerDocument.activeElement as HTMLElement) ?? null;\r\n\r\n const target =\r\n options.initialFocus ??\r\n getFirstFocusable(container) ??\r\n (isFocusable(container) ? container : null);\r\n\r\n if (target) {\r\n target.focus({ preventScroll: false });\r\n } else {\r\n container.setAttribute('tabindex', '-1');\r\n container.focus({ preventScroll: false });\r\n }\r\n\r\n container.ownerDocument.addEventListener('keydown', handleKeyDown, true);\r\n container.ownerDocument.addEventListener('mousedown', handlePointerDown, true);\r\n container.ownerDocument.addEventListener('touchstart', handlePointerDown, true);\r\n },\r\n deactivate() {\r\n if (!active) return;\r\n active = false;\r\n container.ownerDocument.removeEventListener('keydown', handleKeyDown, true);\r\n container.ownerDocument.removeEventListener('mousedown', handlePointerDown, true);\r\n container.ownerDocument.removeEventListener('touchstart', handlePointerDown, true);\r\n\r\n const returnTo = options.returnFocus ?? previouslyFocused;\r\n if (returnTo && returnTo.isConnected) {\r\n returnTo.focus({ preventScroll: false });\r\n }\r\n },\r\n isActive() {\r\n return active;\r\n },\r\n };\r\n}\r\n","/**\r\n * Tiny, SSR-safe id generator. Prefer crypto.randomUUID when available, fall\r\n * back to a monotonic counter otherwise so generated ids are stable across\r\n * server/client when adapters seed them once.\r\n */\r\n\r\nlet counter = 0;\r\n\r\nexport function generateId(prefix = 'elvora'): string {\r\n if (typeof globalThis !== 'undefined' && globalThis.crypto?.randomUUID) {\r\n return `${prefix}-${globalThis.crypto.randomUUID().slice(0, 8)}`;\r\n }\r\n counter = (counter + 1) | 0;\r\n return `${prefix}-${counter.toString(36)}`;\r\n}\r\n"]}
@@ -0,0 +1,18 @@
1
+ interface FocusTrapOptions {
2
+ /** Element to restore focus to on deactivation. Defaults to the previously active element. */
3
+ returnFocus?: HTMLElement | null;
4
+ /** Initial element to focus. Defaults to first focusable child. */
5
+ initialFocus?: HTMLElement | null;
6
+ /** When true, pressing Escape calls `onEscape`. */
7
+ onEscape?: (event: KeyboardEvent) => void;
8
+ /** Outside clicks call this. Trap does not auto-close on outside click. */
9
+ onOutsideClick?: (event: MouseEvent | TouchEvent) => void;
10
+ }
11
+ interface FocusTrap {
12
+ activate(): void;
13
+ deactivate(): void;
14
+ isActive(): boolean;
15
+ }
16
+ declare function createFocusTrap(container: HTMLElement, options?: FocusTrapOptions): FocusTrap;
17
+
18
+ export { type FocusTrap as F, type FocusTrapOptions as a, createFocusTrap as c };
@@ -0,0 +1,18 @@
1
+ interface FocusTrapOptions {
2
+ /** Element to restore focus to on deactivation. Defaults to the previously active element. */
3
+ returnFocus?: HTMLElement | null;
4
+ /** Initial element to focus. Defaults to first focusable child. */
5
+ initialFocus?: HTMLElement | null;
6
+ /** When true, pressing Escape calls `onEscape`. */
7
+ onEscape?: (event: KeyboardEvent) => void;
8
+ /** Outside clicks call this. Trap does not auto-close on outside click. */
9
+ onOutsideClick?: (event: MouseEvent | TouchEvent) => void;
10
+ }
11
+ interface FocusTrap {
12
+ activate(): void;
13
+ deactivate(): void;
14
+ isActive(): boolean;
15
+ }
16
+ declare function createFocusTrap(container: HTMLElement, options?: FocusTrapOptions): FocusTrap;
17
+
18
+ export { type FocusTrap as F, type FocusTrapOptions as a, createFocusTrap as c };