keystra 0.0.0-stage → 0.1.0

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.
Files changed (52) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/LICENSE +21 -0
  3. package/README.md +177 -2
  4. package/dist/chunk-2BLQUBG7.js +816 -0
  5. package/dist/chunk-77GGU3AX.js +135 -0
  6. package/dist/chunk-KDOJARJD.js +29 -0
  7. package/dist/chunk-LYYUOTBP.js +125 -0
  8. package/dist/chunk-S5YVXIMA.js +61 -0
  9. package/dist/chunk-SSIKGDD4.js +255 -0
  10. package/dist/chunk-U7YK6PPT.js +1729 -0
  11. package/dist/chunk-XFNYIPOW.js +253 -0
  12. package/dist/chunk-ZOJRXXQN.js +308 -0
  13. package/dist/core.cjs +2861 -0
  14. package/dist/core.d.cts +41 -0
  15. package/dist/core.d.ts +41 -0
  16. package/dist/core.js +7 -0
  17. package/dist/engine-COPfXXTp.d.ts +229 -0
  18. package/dist/engine-DcbkGRgz.d.cts +229 -0
  19. package/dist/focus.cjs +374 -0
  20. package/dist/focus.d.cts +74 -0
  21. package/dist/focus.d.ts +74 -0
  22. package/dist/focus.js +2 -0
  23. package/dist/index-IZkcWAXP.d.cts +395 -0
  24. package/dist/index-IZkcWAXP.d.ts +395 -0
  25. package/dist/index.cjs +3745 -0
  26. package/dist/index.d.cts +9 -0
  27. package/dist/index.d.ts +9 -0
  28. package/dist/index.global.js +1 -0
  29. package/dist/index.js +9 -0
  30. package/dist/layout-C2-n8aUI.d.ts +17 -0
  31. package/dist/layout-CBzD-87D.d.cts +17 -0
  32. package/dist/navigation.cjs +293 -0
  33. package/dist/navigation.d.cts +33 -0
  34. package/dist/navigation.d.ts +33 -0
  35. package/dist/navigation.js +2 -0
  36. package/dist/patterns.cjs +3505 -0
  37. package/dist/patterns.d.cts +12 -0
  38. package/dist/patterns.d.ts +12 -0
  39. package/dist/patterns.js +7 -0
  40. package/dist/roving-focus-CT7Dm6di.d.ts +63 -0
  41. package/dist/roving-focus-ZQ0v7hLe.d.cts +63 -0
  42. package/dist/selection.cjs +266 -0
  43. package/dist/selection.d.cts +43 -0
  44. package/dist/selection.d.ts +43 -0
  45. package/dist/selection.js +2 -0
  46. package/dist/utils-CkveVeIJ.d.cts +18 -0
  47. package/dist/utils-JmPZTlys.d.ts +18 -0
  48. package/dist/utils.cjs +195 -0
  49. package/dist/utils.d.cts +3 -0
  50. package/dist/utils.d.ts +3 -0
  51. package/dist/utils.js +2 -0
  52. package/package.json +147 -4
package/dist/utils.cjs ADDED
@@ -0,0 +1,195 @@
1
+ 'use strict';
2
+
3
+ // src/utils/dom.ts
4
+ function hasDom() {
5
+ return typeof globalThis !== "undefined" && typeof globalThis.document !== "undefined";
6
+ }
7
+ function resolveElement(target, root = null) {
8
+ if (target == null) return null;
9
+ if (typeof target === "function") {
10
+ return resolveElement(target(), root);
11
+ }
12
+ if (typeof target === "string") {
13
+ const scope = root ?? (hasDom() ? document : null);
14
+ if (!scope) return null;
15
+ try {
16
+ return scope.querySelector(target);
17
+ } catch {
18
+ return null;
19
+ }
20
+ }
21
+ return target;
22
+ }
23
+ function queryItems(container, selector) {
24
+ if (!container) return [];
25
+ if (typeof selector === "function") {
26
+ const result = selector(container);
27
+ return Array.from(result);
28
+ }
29
+ if (!selector) return [];
30
+ try {
31
+ return Array.from(container.querySelectorAll(selector));
32
+ } catch {
33
+ return [];
34
+ }
35
+ }
36
+ var DISABLED_SELECTOR_DEFAULT = "[disabled],[aria-disabled='true']";
37
+ function isDisabled(element, selector = DISABLED_SELECTOR_DEFAULT) {
38
+ if (!selector) return false;
39
+ const html = element;
40
+ if (html.disabled === true) return true;
41
+ if (element.getAttribute("aria-disabled") === "true") return true;
42
+ if (selector === DISABLED_SELECTOR_DEFAULT) return element.hasAttribute("disabled");
43
+ try {
44
+ return element.matches(selector);
45
+ } catch {
46
+ return false;
47
+ }
48
+ }
49
+ function isHidden(element) {
50
+ const html = element;
51
+ const probe = html;
52
+ if (typeof probe.checkVisibility === "function") {
53
+ try {
54
+ return !probe.checkVisibility.call(html, { checkOpacity: false, checkVisibilityCSS: true });
55
+ } catch {
56
+ }
57
+ }
58
+ let node = html;
59
+ while (node && node.nodeType === 1) {
60
+ if (node.hidden === true) return true;
61
+ if (node.style && node.style.display === "none") return true;
62
+ node = node.parentElement;
63
+ }
64
+ return false;
65
+ }
66
+ function isFocusable(element) {
67
+ const html = element;
68
+ if (typeof html.focus !== "function") return false;
69
+ if (isDisabled(element)) return false;
70
+ return !isHidden(element);
71
+ }
72
+ var EDITABLE_SELECTOR_DEFAULT = "input:not([type='hidden']):not([readonly]),textarea:not([readonly]),select,[contenteditable=''],[contenteditable='true'],[iscontenteditable]";
73
+ function isEditableTarget(target, extraSelector) {
74
+ if (!target || typeof target.matches !== "function") return false;
75
+ const element = target;
76
+ const selector = extraSelector ? `${EDITABLE_SELECTOR_DEFAULT},${extraSelector}` : EDITABLE_SELECTOR_DEFAULT;
77
+ try {
78
+ if (element.matches(selector)) return true;
79
+ } catch {
80
+ }
81
+ const html = element;
82
+ return html.isContentEditable === true;
83
+ }
84
+ function getText(element) {
85
+ const label = element.getAttribute("aria-label") ?? element.textContent ?? "";
86
+ return label.replace(/\s+/g, " ").trim();
87
+ }
88
+ function setAttribute(element, name, value) {
89
+ if (value === null) element.removeAttribute(name);
90
+ else if (element.getAttribute(name) !== value) element.setAttribute(name, value);
91
+ }
92
+ function toggleClass(element, name, on) {
93
+ if (!name) return;
94
+ const html = element;
95
+ if (typeof html.classList?.toggle === "function") html.classList.toggle(name, on);
96
+ }
97
+ function focusElement(element, preventScroll = true) {
98
+ if (typeof element.focus !== "function") return false;
99
+ try {
100
+ element.focus({ preventScroll });
101
+ } catch {
102
+ try {
103
+ element.focus();
104
+ } catch {
105
+ return false;
106
+ }
107
+ }
108
+ return true;
109
+ }
110
+ function contains(parent, child) {
111
+ if (!parent || !child) return false;
112
+ if (parent === child) return true;
113
+ if (typeof parent.contains === "function") return parent.contains(child);
114
+ let node = child;
115
+ while (node) {
116
+ if (node === parent) return true;
117
+ node = node.parentNode;
118
+ }
119
+ return false;
120
+ }
121
+
122
+ // src/utils/emitter.ts
123
+ var Emitter = class {
124
+ constructor() {
125
+ this.handlers = null;
126
+ }
127
+ on(event, handler) {
128
+ if (!this.handlers) this.handlers = /* @__PURE__ */ new Map();
129
+ let set = this.handlers.get(event);
130
+ if (!set) {
131
+ set = /* @__PURE__ */ new Set();
132
+ this.handlers.set(event, set);
133
+ }
134
+ set.add(handler);
135
+ let disposed = false;
136
+ return () => {
137
+ if (disposed) return;
138
+ disposed = true;
139
+ this.off(event, handler);
140
+ };
141
+ }
142
+ off(event, handler) {
143
+ const set = this.handlers?.get(event);
144
+ if (!set) return;
145
+ if (handler) set.delete(handler);
146
+ else set.clear();
147
+ if (set.size === 0) this.handlers?.delete(event);
148
+ }
149
+ emit(event, payload) {
150
+ const set = this.handlers?.get(event);
151
+ if (!set || set.size === 0) return;
152
+ for (const handler of Array.from(set)) {
153
+ handler(payload);
154
+ }
155
+ }
156
+ clear() {
157
+ this.handlers?.clear();
158
+ this.handlers = null;
159
+ }
160
+ get size() {
161
+ if (!this.handlers) return 0;
162
+ let total = 0;
163
+ for (const set of this.handlers.values()) total += set.size;
164
+ return total;
165
+ }
166
+ };
167
+
168
+ // src/utils/layout.ts
169
+ var LayoutCache = class {
170
+ constructor() {
171
+ this.rects = /* @__PURE__ */ new WeakMap();
172
+ }
173
+ rect(element, version) {
174
+ const cached = this.rects.get(element);
175
+ if (cached && cached.version === version) return cached.rect;
176
+ const rect = element.getBoundingClientRect();
177
+ this.rects.set(element, { version, rect });
178
+ return rect;
179
+ }
180
+ };
181
+
182
+ exports.Emitter = Emitter;
183
+ exports.LayoutCache = LayoutCache;
184
+ exports.contains = contains;
185
+ exports.focusElement = focusElement;
186
+ exports.getText = getText;
187
+ exports.hasDom = hasDom;
188
+ exports.isDisabled = isDisabled;
189
+ exports.isEditableTarget = isEditableTarget;
190
+ exports.isFocusable = isFocusable;
191
+ exports.isHidden = isHidden;
192
+ exports.queryItems = queryItems;
193
+ exports.resolveElement = resolveElement;
194
+ exports.setAttribute = setAttribute;
195
+ exports.toggleClass = toggleClass;
@@ -0,0 +1,3 @@
1
+ export { c as contains, f as focusElement, g as getText, h as hasDom, i as isDisabled, a as isEditableTarget, b as isFocusable, d as isHidden, q as queryItems, r as resolveElement, s as setAttribute, t as toggleClass } from './utils-CkveVeIJ.cjs';
2
+ export { E as Emitter, L as LayoutCache } from './layout-CBzD-87D.cjs';
3
+ import './index-IZkcWAXP.cjs';
@@ -0,0 +1,3 @@
1
+ export { c as contains, f as focusElement, g as getText, h as hasDom, i as isDisabled, a as isEditableTarget, b as isFocusable, d as isHidden, q as queryItems, r as resolveElement, s as setAttribute, t as toggleClass } from './utils-JmPZTlys.js';
2
+ export { E as Emitter, L as LayoutCache } from './layout-C2-n8aUI.js';
3
+ import './index-IZkcWAXP.js';
package/dist/utils.js ADDED
@@ -0,0 +1,2 @@
1
+ export { Emitter, LayoutCache } from './chunk-S5YVXIMA.js';
2
+ export { contains, focusElement, getText, hasDom, isDisabled, isEditableTarget, isFocusable, isHidden, queryItems, resolveElement, setAttribute, toggleClass } from './chunk-77GGU3AX.js';
package/package.json CHANGED
@@ -1,6 +1,149 @@
1
1
  {
2
2
  "name": "keystra",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "A tiny, framework-independent keyboard interaction engine: navigation, roving focus and selection for any DOM UI.",
5
+ "keywords": [
6
+ "keyboard",
7
+ "navigation",
8
+ "focus",
9
+ "focus-management",
10
+ "roving-tabindex",
11
+ "accessibility",
12
+ "a11y",
13
+ "aria",
14
+ "typeahead",
15
+ "menu",
16
+ "tabs",
17
+ "grid",
18
+ "tree",
19
+ "command-palette",
20
+ "vanilla-js",
21
+ "typescript"
22
+ ],
23
+ "license": "MIT",
24
+ "author": "Zahid Hasan",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/zahidhasann88/keystra.git"
28
+ },
29
+ "homepage": "https://github.com/zahidhasann88/keystra#readme",
30
+ "bugs": "https://github.com/zahidhasann88/keystra/issues",
31
+ "type": "module",
32
+ "main": "./dist/index.cjs",
33
+ "module": "./dist/index.js",
34
+ "types": "./dist/index.d.ts",
35
+ "unpkg": "./dist/index.global.js",
36
+ "jsdelivr": "./dist/index.global.js",
37
+ "exports": {
38
+ ".": {
39
+ "import": {
40
+ "types": "./dist/index.d.ts",
41
+ "default": "./dist/index.js"
42
+ },
43
+ "require": {
44
+ "types": "./dist/index.d.cts",
45
+ "default": "./dist/index.cjs"
46
+ }
47
+ },
48
+ "./core": {
49
+ "import": {
50
+ "types": "./dist/core.d.ts",
51
+ "default": "./dist/core.js"
52
+ },
53
+ "require": {
54
+ "types": "./dist/core.d.cts",
55
+ "default": "./dist/core.cjs"
56
+ }
57
+ },
58
+ "./navigation": {
59
+ "import": {
60
+ "types": "./dist/navigation.d.ts",
61
+ "default": "./dist/navigation.js"
62
+ },
63
+ "require": {
64
+ "types": "./dist/navigation.d.cts",
65
+ "default": "./dist/navigation.cjs"
66
+ }
67
+ },
68
+ "./focus": {
69
+ "import": {
70
+ "types": "./dist/focus.d.ts",
71
+ "default": "./dist/focus.js"
72
+ },
73
+ "require": {
74
+ "types": "./dist/focus.d.cts",
75
+ "default": "./dist/focus.cjs"
76
+ }
77
+ },
78
+ "./selection": {
79
+ "import": {
80
+ "types": "./dist/selection.d.ts",
81
+ "default": "./dist/selection.js"
82
+ },
83
+ "require": {
84
+ "types": "./dist/selection.d.cts",
85
+ "default": "./dist/selection.cjs"
86
+ }
87
+ },
88
+ "./patterns": {
89
+ "import": {
90
+ "types": "./dist/patterns.d.ts",
91
+ "default": "./dist/patterns.js"
92
+ },
93
+ "require": {
94
+ "types": "./dist/patterns.d.cts",
95
+ "default": "./dist/patterns.cjs"
96
+ }
97
+ },
98
+ "./utils": {
99
+ "import": {
100
+ "types": "./dist/utils.d.ts",
101
+ "default": "./dist/utils.js"
102
+ },
103
+ "require": {
104
+ "types": "./dist/utils.d.cts",
105
+ "default": "./dist/utils.cjs"
106
+ }
107
+ },
108
+ "./package.json": "./package.json"
109
+ },
110
+ "sideEffects": false,
111
+ "files": [
112
+ "dist",
113
+ "README.md",
114
+ "CHANGELOG.md",
115
+ "LICENSE"
116
+ ],
117
+ "engines": {
118
+ "node": ">=18"
119
+ },
120
+ "publishConfig": {
121
+ "access": "public"
122
+ },
123
+ "scripts": {
124
+ "build": "tsup",
125
+ "dev": "tsup --watch",
126
+ "typecheck": "tsc -p tsconfig.json --noEmit",
127
+ "test": "vitest run",
128
+ "test:watch": "vitest",
129
+ "test:coverage": "vitest run --coverage",
130
+ "test:browser": "npm run build && node tests/browser/run.mjs",
131
+ "bench": "npm run build && node benchmarks/run.mjs",
132
+ "size": "npm run build && node scripts/size.mjs",
133
+ "verify": "npm run typecheck && npm run test && npm run build && node scripts/verify-package.mjs",
134
+ "prepublishOnly": "npm run verify"
135
+ },
136
+ "devDependencies": {
137
+ "@types/node": "^22.20.5",
138
+ "@vitest/coverage-v8": "^5.0.5",
139
+ "esbuild": "^0.28.2",
140
+ "happy-dom": "^21.0.4",
141
+ "hotkeys-js": "^4.0.10",
142
+ "playwright-core": "^1.64.1",
143
+ "roving-ux": "^1.0.5",
144
+ "tinykeys": "^4.0.1",
145
+ "tsup": "^8.5.1",
146
+ "typescript": "^5.9.3",
147
+ "vitest": "^5.0.5"
148
+ }
149
+ }