@luna_ui/luna 0.7.3 → 0.17.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 (83) hide show
  1. package/dist/api-DAWeanTX.js +1 -0
  2. package/dist/api-qXll116-.d.ts +80 -0
  3. package/dist/cli.mjs +27 -22
  4. package/dist/css/index.js +1 -0
  5. package/dist/event-utils.d.ts +1 -1
  6. package/dist/event-utils.js +1 -1
  7. package/dist/{index-vO066aMd.d.ts → index-VY8G32hr.d.ts} +16 -76
  8. package/dist/index.d.ts +4 -3
  9. package/dist/index.js +1 -1
  10. package/dist/jsx-dev-runtime.js +1 -1
  11. package/dist/jsx-runtime.d.ts +1 -1
  12. package/dist/jsx-runtime.js +1 -1
  13. package/dist/raw.d.ts +2 -0
  14. package/dist/raw.js +1 -0
  15. package/dist/resource.d.ts +41 -0
  16. package/dist/resource.js +1 -0
  17. package/dist/router-lite.d.ts +44 -0
  18. package/dist/router-lite.js +1 -0
  19. package/dist/signals-shared.d.ts +12 -0
  20. package/dist/signals-shared.js +1 -0
  21. package/dist/signals.d.ts +50 -0
  22. package/dist/signals.js +1 -0
  23. package/dist/vite-plugin.d.ts +7708 -2
  24. package/dist/vite-plugin.js +7 -6
  25. package/package.json +34 -11
  26. package/dist/event-utils-C_M2XBNj.js +0 -1
  27. package/dist/src-BbjOW18q.js +0 -1
  28. package/src/css/extract.ts +0 -798
  29. package/src/css/index.ts +0 -10
  30. package/src/css/inject.ts +0 -205
  31. package/src/css/inline.ts +0 -182
  32. package/src/css/minify.ts +0 -70
  33. package/src/css/optimizer.ts +0 -6
  34. package/src/css/runtime.ts +0 -344
  35. package/src/css-optimizer/README.md +0 -353
  36. package/src/css-optimizer/cooccurrence.ts +0 -100
  37. package/src/css-optimizer/core.ts +0 -263
  38. package/src/css-optimizer/extractors.ts +0 -243
  39. package/src/css-optimizer/hash.ts +0 -54
  40. package/src/css-optimizer/index.ts +0 -129
  41. package/src/css-optimizer/merge.ts +0 -109
  42. package/src/css-optimizer/moonbit-analyzer.ts +0 -210
  43. package/src/css-optimizer/parser.ts +0 -120
  44. package/src/css-optimizer/pattern.ts +0 -171
  45. package/src/css-optimizer/transformers.ts +0 -301
  46. package/src/css-optimizer/types.ts +0 -128
  47. package/src/event-utils.ts +0 -227
  48. package/src/hydration/createHydrator.ts +0 -62
  49. package/src/hydration/delegate.ts +0 -62
  50. package/src/hydration/drag.ts +0 -214
  51. package/src/hydration/index.ts +0 -12
  52. package/src/hydration/keyboard.ts +0 -64
  53. package/src/hydration/toggle.ts +0 -101
  54. package/src/index.ts +0 -908
  55. package/src/jsx-dev-runtime.ts +0 -2
  56. package/src/jsx-runtime.ts +0 -398
  57. package/src/vite-plugin.ts +0 -718
  58. package/tests/__screenshots__/apg.test.ts/APG-Components---Accessibility-Tests-Button-Pattern-disabled-button-has-aria-disabled-1.png +0 -0
  59. package/tests/apg.test.ts +0 -466
  60. package/tests/context.test.ts +0 -118
  61. package/tests/css-optimizer-extractors.test.ts +0 -264
  62. package/tests/css-optimizer-integration.test.ts +0 -566
  63. package/tests/css-optimizer-transformers.test.ts +0 -301
  64. package/tests/css-optimizer.test.ts +0 -646
  65. package/tests/css-runtime.bench.ts +0 -442
  66. package/tests/css-runtime.test.ts +0 -342
  67. package/tests/debounced.test.ts +0 -165
  68. package/tests/dom.test.ts +0 -873
  69. package/tests/integration.test.ts +0 -405
  70. package/tests/issue-11-show-null-to-truthy.test.ts +0 -176
  71. package/tests/issue-5-for-infinite-loop.test.ts +0 -516
  72. package/tests/jsx-runtime.test.tsx +0 -393
  73. package/tests/lifecycle.test.ts +0 -833
  74. package/tests/move-before.bench.ts +0 -304
  75. package/tests/preact-signals-comparison.test.ts +0 -1608
  76. package/tests/resource.test.ts +0 -160
  77. package/tests/router.test.ts +0 -117
  78. package/tests/show-initial-mount-leak.test.tsx +0 -182
  79. package/tests/solidjs-api.test.ts +0 -660
  80. package/tests/static-perf.bench.ts +0 -64
  81. package/tests/store.test.ts +0 -263
  82. package/tests/tsx-syntax.test.tsx +0 -404
  83. /package/dist/{event-utils-Cd5f3Njd.d.ts → event-utils-BvAf0NwN.d.ts} +0 -0
@@ -1,227 +0,0 @@
1
- /**
2
- * Event utilities for common event handling patterns
3
- *
4
- * These utilities are tree-shakeable - only import what you use.
5
- *
6
- * @example
7
- * import { getTargetValue, getDataId, stopEvent } from "@luna_ui/luna/event-utils";
8
- *
9
- * on=events().input(fn(e) {
10
- * const value = getTargetValue(e);
11
- * setValue(value);
12
- * })
13
- */
14
-
15
- /**
16
- * Get value from input/textarea/select element
17
- */
18
- export function getTargetValue(e: Event): string {
19
- return (e.target as HTMLInputElement)?.value ?? "";
20
- }
21
-
22
- /**
23
- * Get checked state from checkbox/radio input
24
- */
25
- export function getTargetChecked(e: Event): boolean {
26
- return (e.target as HTMLInputElement)?.checked ?? false;
27
- }
28
-
29
- /**
30
- * Get selected index from select element
31
- */
32
- export function getSelectedIndex(e: Event): number {
33
- return (e.target as HTMLSelectElement)?.selectedIndex ?? -1;
34
- }
35
-
36
- /**
37
- * Get client position (relative to viewport) from mouse/pointer/touch event
38
- */
39
- export function getClientPos(e: MouseEvent | PointerEvent | TouchEvent): { x: number; y: number } {
40
- if ("touches" in e && e.touches.length > 0) {
41
- return { x: e.touches[0].clientX, y: e.touches[0].clientY };
42
- }
43
- const me = e as MouseEvent;
44
- return { x: me.clientX, y: me.clientY };
45
- }
46
-
47
- /**
48
- * Get offset position (relative to target element) from mouse/pointer event
49
- */
50
- export function getOffsetPos(e: MouseEvent | PointerEvent): { x: number; y: number } {
51
- return { x: e.offsetX, y: e.offsetY };
52
- }
53
-
54
- /**
55
- * Get page position (relative to document) from mouse/pointer/touch event
56
- */
57
- export function getPagePos(e: MouseEvent | PointerEvent | TouchEvent): { x: number; y: number } {
58
- if ("touches" in e && e.touches.length > 0) {
59
- return { x: e.touches[0].pageX, y: e.touches[0].pageY };
60
- }
61
- const me = e as MouseEvent;
62
- return { x: me.pageX, y: me.pageY };
63
- }
64
-
65
- /**
66
- * Get movement delta from mouse/pointer event
67
- */
68
- export function getMovement(e: MouseEvent | PointerEvent): { dx: number; dy: number } {
69
- return { dx: e.movementX, dy: e.movementY };
70
- }
71
-
72
- /**
73
- * Get data attribute from event target or closest ancestor
74
- */
75
- export function getDataAttr(e: Event, key: string): string | null {
76
- const target = e.target as HTMLElement;
77
- return target?.closest(`[data-${key}]`)?.getAttribute(`data-${key}`) ?? null;
78
- }
79
-
80
- /**
81
- * Get data-id from closest ancestor (common pattern for list items)
82
- */
83
- export function getDataId(e: Event): string | null {
84
- return getDataAttr(e, "id");
85
- }
86
-
87
- /**
88
- * Get all data attributes from event target as object
89
- */
90
- export function getDataset(e: Event): DOMStringMap {
91
- return (e.target as HTMLElement)?.dataset ?? {};
92
- }
93
-
94
- /**
95
- * Check if event target matches a CSS selector
96
- */
97
- export function matchesSelector(e: Event, selector: string): boolean {
98
- const target = e.target as HTMLElement;
99
- return target?.matches?.(selector) ?? false;
100
- }
101
-
102
- /**
103
- * Get closest ancestor matching selector from event target
104
- */
105
- export function getClosest<T extends Element = HTMLElement>(e: Event, selector: string): T | null {
106
- const target = e.target as HTMLElement;
107
- return target?.closest?.(selector) as T | null;
108
- }
109
-
110
- /**
111
- * Prevent default behavior
112
- */
113
- export function preventDefault(e: Event): void {
114
- e.preventDefault();
115
- }
116
-
117
- /**
118
- * Stop event propagation
119
- */
120
- export function stopPropagation(e: Event): void {
121
- e.stopPropagation();
122
- }
123
-
124
- /**
125
- * Prevent default and stop propagation (convenience)
126
- */
127
- export function stopEvent(e: Event): void {
128
- e.preventDefault();
129
- e.stopPropagation();
130
- }
131
-
132
- /**
133
- * Check if IME is composing (for input/keyboard events)
134
- * Use this to avoid handling partial IME input
135
- */
136
- export function isComposing(e: Event): boolean {
137
- return (e as InputEvent | KeyboardEvent).isComposing ?? false;
138
- }
139
-
140
- /**
141
- * Check if modifier key is pressed
142
- */
143
- export function hasModifier(e: KeyboardEvent | MouseEvent, modifier: "ctrl" | "alt" | "shift" | "meta"): boolean {
144
- switch (modifier) {
145
- case "ctrl": return e.ctrlKey;
146
- case "alt": return e.altKey;
147
- case "shift": return e.shiftKey;
148
- case "meta": return e.metaKey;
149
- }
150
- }
151
-
152
- /**
153
- * Check if Cmd (Mac) or Ctrl (Windows/Linux) is pressed
154
- */
155
- export function hasCmdOrCtrl(e: KeyboardEvent | MouseEvent): boolean {
156
- // Mac uses metaKey (Cmd), others use ctrlKey
157
- return e.metaKey || e.ctrlKey;
158
- }
159
-
160
- /**
161
- * Get keyboard key with normalized names
162
- */
163
- export function getKey(e: KeyboardEvent): string {
164
- return e.key;
165
- }
166
-
167
- /**
168
- * Check if Enter key was pressed (outside IME composition)
169
- */
170
- export function isEnterKey(e: KeyboardEvent): boolean {
171
- return e.key === "Enter" && !isComposing(e);
172
- }
173
-
174
- /**
175
- * Check if Escape key was pressed
176
- */
177
- export function isEscapeKey(e: KeyboardEvent): boolean {
178
- return e.key === "Escape";
179
- }
180
-
181
- /**
182
- * Get wheel delta (normalized across browsers)
183
- */
184
- export function getWheelDelta(e: WheelEvent): { deltaX: number; deltaY: number; deltaZ: number } {
185
- return {
186
- deltaX: e.deltaX,
187
- deltaY: e.deltaY,
188
- deltaZ: e.deltaZ,
189
- };
190
- }
191
-
192
- /**
193
- * Get which mouse button was pressed
194
- * 0 = primary (left), 1 = auxiliary (middle), 2 = secondary (right)
195
- */
196
- export function getButton(e: MouseEvent): number {
197
- return e.button;
198
- }
199
-
200
- /**
201
- * Check if primary (left) mouse button was pressed
202
- */
203
- export function isPrimaryButton(e: MouseEvent): boolean {
204
- return e.button === 0;
205
- }
206
-
207
- /**
208
- * Check if secondary (right) mouse button was pressed
209
- */
210
- export function isSecondaryButton(e: MouseEvent): boolean {
211
- return e.button === 2;
212
- }
213
-
214
- /**
215
- * Get files from drag event
216
- */
217
- export function getDroppedFiles(e: DragEvent): FileList | null {
218
- return e.dataTransfer?.files ?? null;
219
- }
220
-
221
- /**
222
- * Get text data from drag/clipboard event
223
- */
224
- export function getTextData(e: DragEvent | ClipboardEvent): string {
225
- const dt = "dataTransfer" in e ? e.dataTransfer : e.clipboardData;
226
- return dt?.getData("text/plain") ?? "";
227
- }
@@ -1,62 +0,0 @@
1
- /**
2
- * createHydrator - Wraps hydration logic with automatic guard and cleanup
3
- *
4
- * Features:
5
- * - Prevents double hydration via data-hydrated check
6
- * - Automatically sets data-hydrated="true" on completion
7
- * - Optional cleanup function support
8
- */
9
-
10
- export type HydrateFn = (
11
- element: Element,
12
- state: unknown,
13
- name: string
14
- ) => void | (() => void);
15
-
16
- export type CleanupFn = () => void;
17
-
18
- /**
19
- * Create a hydration function with automatic guards
20
- *
21
- * @example
22
- * ```ts
23
- * export const hydrate = createHydrator((el, state) => {
24
- * // Your hydration logic here
25
- * el.querySelector('button').onclick = () => { ... };
26
- *
27
- * // Optional: return cleanup function
28
- * return () => { ... };
29
- * });
30
- * ```
31
- */
32
- export function createHydrator(
33
- fn: (element: Element, state: unknown, name: string) => void | CleanupFn
34
- ): HydrateFn {
35
- // Store cleanup functions by element
36
- const cleanups = new WeakMap<Element, CleanupFn>();
37
-
38
- return (element: Element, state: unknown, name: string): void => {
39
- // Skip if already hydrated
40
- if ((element as HTMLElement).dataset.hydrated) {
41
- return;
42
- }
43
-
44
- // Run cleanup if re-hydrating (HMR scenario)
45
- const existingCleanup = cleanups.get(element);
46
- if (existingCleanup) {
47
- existingCleanup();
48
- cleanups.delete(element);
49
- }
50
-
51
- // Run hydration
52
- const cleanup = fn(element, state, name);
53
-
54
- // Store cleanup if provided
55
- if (typeof cleanup === 'function') {
56
- cleanups.set(element, cleanup);
57
- }
58
-
59
- // Mark as hydrated
60
- (element as HTMLElement).dataset.hydrated = 'true';
61
- };
62
- }
@@ -1,62 +0,0 @@
1
- /**
2
- * onDelegate - Event delegation helper
3
- *
4
- * Attach event handlers using event delegation pattern.
5
- * Efficient for multiple similar elements.
6
- */
7
-
8
- /**
9
- * Set up delegated event handling
10
- *
11
- * @example
12
- * ```ts
13
- * onDelegate(el, 'click', '[data-dialog-trigger]', () => {
14
- * dialog.dataset.state = 'open';
15
- * });
16
- *
17
- * onDelegate(el, 'click', '[data-dialog-close]', (e, target) => {
18
- * dialog.dataset.state = 'closed';
19
- * });
20
- * ```
21
- */
22
- export function onDelegate<K extends keyof HTMLElementEventMap>(
23
- root: Element,
24
- event: K,
25
- selector: string,
26
- handler: (event: HTMLElementEventMap[K], target: Element) => void
27
- ): () => void {
28
- const delegateHandler = (e: Event) => {
29
- const target = (e.target as Element)?.closest(selector);
30
- if (target && root.contains(target)) {
31
- handler(e as HTMLElementEventMap[K], target);
32
- }
33
- };
34
-
35
- root.addEventListener(event, delegateHandler);
36
-
37
- // Return cleanup function
38
- return () => {
39
- root.removeEventListener(event, delegateHandler);
40
- };
41
- }
42
-
43
- /**
44
- * Attach click handler to multiple elements matching selector
45
- *
46
- * @example
47
- * ```ts
48
- * onClick(el, '[data-tab-trigger]', (target) => {
49
- * const tabId = target.dataset.tabId;
50
- * // activate tab...
51
- * });
52
- * ```
53
- */
54
- export function onClick(
55
- root: Element,
56
- selector: string,
57
- handler: (target: Element, event: MouseEvent) => void
58
- ): () => void {
59
- return onDelegate(root, 'click', selector, (e, target) => {
60
- handler(target, e);
61
- });
62
- }
@@ -1,214 +0,0 @@
1
- /**
2
- * drag - Horizontal drag helper for sliders/range inputs
3
- *
4
- * Handles mouse and touch drag interactions.
5
- * Updates CSS custom property for visual positioning.
6
- */
7
-
8
- export interface DragOptions {
9
- /**
10
- * Selector for the track element (defines drag bounds).
11
- * Required for calculating position.
12
- */
13
- track: string;
14
-
15
- /**
16
- * CSS custom property to update with percentage (0-100).
17
- * @default '--slider-percent'
18
- */
19
- cssProperty?: string;
20
-
21
- /**
22
- * Attribute to update with the calculated value.
23
- * @default 'data-value'
24
- */
25
- attribute?: string;
26
-
27
- /**
28
- * Minimum value.
29
- * @default 0
30
- */
31
- min?: number;
32
-
33
- /**
34
- * Maximum value.
35
- * @default 100
36
- */
37
- max?: number;
38
-
39
- /**
40
- * Step increment (for keyboard navigation).
41
- * @default 1
42
- */
43
- step?: number;
44
-
45
- /**
46
- * Selector for value display element.
47
- * If provided, its textContent will be updated.
48
- */
49
- display?: string;
50
-
51
- /**
52
- * Callback when value changes.
53
- */
54
- onChange?: (value: number, percent: number, element: Element) => void;
55
- }
56
-
57
- /**
58
- * Set up drag behavior for slider-like components
59
- *
60
- * @example
61
- * ```ts
62
- * // Basic slider
63
- * drag(el, '[data-slider]', {
64
- * track: '[data-slider-track]',
65
- * display: '[data-slider-value]'
66
- * });
67
- *
68
- * // Custom range
69
- * drag(el, '[data-range]', {
70
- * track: '[data-range-track]',
71
- * min: -50,
72
- * max: 50,
73
- * cssProperty: '--range-position',
74
- * onChange: (value) => console.log('Value:', value)
75
- * });
76
- * ```
77
- */
78
- export function drag(
79
- root: Element,
80
- selector: string,
81
- options: DragOptions
82
- ): () => void {
83
- const {
84
- track: trackSelector,
85
- cssProperty = '--slider-percent',
86
- attribute = 'data-value',
87
- min = 0,
88
- max = 100,
89
- step = 1,
90
- display: displaySelector,
91
- onChange,
92
- } = options;
93
-
94
- const cleanups: Array<() => void> = [];
95
-
96
- root.querySelectorAll(selector).forEach((slider) => {
97
- const track = slider.querySelector(trackSelector);
98
- const thumb = slider.querySelector('[data-slider-thumb]');
99
- const display = displaySelector ? root.querySelector(displaySelector) : null;
100
-
101
- if (!track) return;
102
-
103
- // Get initial value from attribute
104
- let value = parseInt(slider.getAttribute(attribute) || '50', 10);
105
-
106
- const update = (val: number) => {
107
- // Clamp value
108
- value = Math.max(min, Math.min(max, val));
109
- const pct = ((value - min) / (max - min)) * 100;
110
-
111
- // Update attribute
112
- slider.setAttribute(attribute, String(value));
113
-
114
- // Update aria
115
- slider.setAttribute('aria-valuenow', String(value));
116
-
117
- // Update CSS custom property
118
- (slider as HTMLElement).style.setProperty(cssProperty, String(pct));
119
-
120
- // Update display
121
- if (display) {
122
- display.textContent = String(value);
123
- }
124
-
125
- onChange?.(value, pct, slider);
126
- };
127
-
128
- // Set initial CSS property
129
- const initialPct = ((value - min) / (max - min)) * 100;
130
- (slider as HTMLElement).style.setProperty(cssProperty, String(initialPct));
131
-
132
- // Calculate value from position
133
- const calcValue = (clientX: number): number => {
134
- const rect = track.getBoundingClientRect();
135
- const pct = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
136
- return Math.round(min + pct * (max - min));
137
- };
138
-
139
- // Drag handlers
140
- const handleDrag = (e: MouseEvent | TouchEvent) => {
141
- const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX;
142
- update(calcValue(clientX));
143
- };
144
-
145
- const handleStart = (e: MouseEvent | TouchEvent) => {
146
- e.preventDefault();
147
- handleDrag(e);
148
-
149
- const handleMove = (e: MouseEvent | TouchEvent) => handleDrag(e);
150
- const handleEnd = () => {
151
- document.removeEventListener('mousemove', handleMove);
152
- document.removeEventListener('mouseup', handleEnd);
153
- document.removeEventListener('touchmove', handleMove as EventListener);
154
- document.removeEventListener('touchend', handleEnd);
155
- };
156
-
157
- document.addEventListener('mousemove', handleMove);
158
- document.addEventListener('mouseup', handleEnd);
159
- document.addEventListener('touchmove', handleMove as EventListener);
160
- document.addEventListener('touchend', handleEnd);
161
- };
162
-
163
- // Attach to track
164
- track.addEventListener('mousedown', handleStart as EventListener);
165
- track.addEventListener('touchstart', handleStart as EventListener);
166
-
167
- // Attach to thumb if exists
168
- if (thumb) {
169
- thumb.addEventListener('mousedown', handleStart as EventListener);
170
- thumb.addEventListener('touchstart', handleStart as EventListener);
171
- }
172
-
173
- // Keyboard navigation
174
- const handleKeydown = (e: KeyboardEvent) => {
175
- switch (e.key) {
176
- case 'ArrowRight':
177
- case 'ArrowUp':
178
- update(value + step);
179
- e.preventDefault();
180
- break;
181
- case 'ArrowLeft':
182
- case 'ArrowDown':
183
- update(value - step);
184
- e.preventDefault();
185
- break;
186
- case 'Home':
187
- update(min);
188
- e.preventDefault();
189
- break;
190
- case 'End':
191
- update(max);
192
- e.preventDefault();
193
- break;
194
- }
195
- };
196
-
197
- if (thumb) {
198
- thumb.addEventListener('keydown', handleKeydown as EventListener);
199
- }
200
-
201
- // Cleanup
202
- cleanups.push(() => {
203
- track.removeEventListener('mousedown', handleStart as EventListener);
204
- track.removeEventListener('touchstart', handleStart as EventListener);
205
- if (thumb) {
206
- thumb.removeEventListener('mousedown', handleStart as EventListener);
207
- thumb.removeEventListener('touchstart', handleStart as EventListener);
208
- thumb.removeEventListener('keydown', handleKeydown as EventListener);
209
- }
210
- });
211
- });
212
-
213
- return () => cleanups.forEach((fn) => fn());
214
- }
@@ -1,12 +0,0 @@
1
- /**
2
- * @luna/hydration - SSR Hydration Helpers
3
- *
4
- * Composable helpers for hydrating SSR-rendered components.
5
- * CSS-first approach: JS handles events, CSS handles visuals.
6
- */
7
-
8
- export { createHydrator, type HydrateFn, type CleanupFn } from './createHydrator';
9
- export { toggle, type ToggleOptions } from './toggle';
10
- export { drag, type DragOptions } from './drag';
11
- export { onDelegate, onClick } from './delegate';
12
- export { keyboard, onEscape, type KeyboardHandlers } from './keyboard';
@@ -1,64 +0,0 @@
1
- /**
2
- * keyboard - Keyboard event handler helper
3
- *
4
- * Map keyboard keys to handler functions.
5
- */
6
-
7
- export type KeyboardHandlers = {
8
- [key: string]: (event: KeyboardEvent) => void;
9
- };
10
-
11
- /**
12
- * Set up keyboard handlers on an element or document
13
- *
14
- * @example
15
- * ```ts
16
- * // Handle Escape on document
17
- * keyboard(document, {
18
- * Escape: () => close()
19
- * });
20
- *
21
- * // Handle arrow keys on a specific element
22
- * keyboard(slider, {
23
- * ArrowUp: () => increment(),
24
- * ArrowDown: () => decrement(),
25
- * ArrowLeft: () => decrement(),
26
- * ArrowRight: () => increment()
27
- * });
28
- * ```
29
- */
30
- export function keyboard(
31
- target: Element | Document | null,
32
- handlers: KeyboardHandlers
33
- ): () => void {
34
- if (!target) return () => {};
35
-
36
- const handler = (e: Event) => {
37
- const key = (e as KeyboardEvent).key;
38
- const fn = handlers[key];
39
- if (fn) {
40
- fn(e as KeyboardEvent);
41
- }
42
- };
43
-
44
- target.addEventListener('keydown', handler);
45
-
46
- // Return cleanup function
47
- return () => {
48
- target.removeEventListener('keydown', handler);
49
- };
50
- }
51
-
52
- /**
53
- * Handle Escape key to close something
54
- *
55
- * @example
56
- * ```ts
57
- * onEscape(() => {
58
- * dialog.dataset.state = 'closed';
59
- * });
60
- * ```
61
- */
62
- export function onEscape(handler: () => void): () => void {
63
- return keyboard(document, { Escape: handler });
64
- }