Skip to content

Commit 215385f

Browse files
committed
0.1.0
0 parents  commit 215385f

12 files changed

+509
-0
lines changed

.github/workflows/test.yaml

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
name: Test
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- uses: actions/setup-node@v4
16+
with:
17+
node-version: 22
18+
19+
- name: Run tests and collect coverage
20+
id: tests
21+
run: |
22+
# Generate lcov coverage report
23+
echo "Generating lcov coverage..."
24+
if LCOV_OUTPUT=$(node --test --experimental-test-coverage --test-coverage-exclude="test/*" --test-reporter=lcov 2>&1); then
25+
echo "status=passing" >> $GITHUB_OUTPUT
26+
echo "color=brightgreen" >> $GITHUB_OUTPUT
27+
echo "lcov<<EOF" >> $GITHUB_OUTPUT
28+
echo "$LCOV_OUTPUT" >> $GITHUB_OUTPUT
29+
echo "EOF" >> $GITHUB_OUTPUT
30+
else
31+
echo "status=failing" >> $GITHUB_OUTPUT
32+
echo "color=red" >> $GITHUB_OUTPUT
33+
echo "Coverage generation failed, but tests may have passed:"
34+
echo "$LCOV_OUTPUT"
35+
exit 1
36+
fi
37+
38+
- name: Generate badges
39+
if: github.ref == 'refs/heads/main'
40+
run: |
41+
# Generate coverage data from step output
42+
LCOV_DATA="${{ steps.tests.outputs.lcov }}"
43+
COVERAGE=$(echo "$LCOV_DATA" | grep -o 'LF:[0-9]*' | awk -F: '{found+=$2} END {print found}')
44+
TOTAL=$(echo "$LCOV_DATA" | grep -o 'LH:[0-9]*' | awk -F: '{hit+=$2} END {print hit}')
45+
PERCENTAGE=$(awk "BEGIN {printf \"%.1f\", ($TOTAL/$COVERAGE)*100}")
46+
47+
if (( $(echo "$PERCENTAGE >= 90" | bc -l) )); then
48+
COV_COLOR="brightgreen"
49+
elif (( $(echo "$PERCENTAGE >= 80" | bc -l) )); then
50+
COV_COLOR="yellow"
51+
else
52+
COV_COLOR="red"
53+
fi
54+
55+
# Create orphan branch for badges
56+
git checkout --orphan badges || git checkout badges
57+
git rm -rf . 2>/dev/null || true
58+
59+
# Create both badges
60+
echo "{\"schemaVersion\": 1, \"label\": \"tests\", \"message\": \"${{ steps.tests.outputs.status }}\", \"color\": \"${{ steps.tests.outputs.color }}\"}" > tests.json
61+
echo "{\"schemaVersion\": 1, \"label\": \"coverage\", \"message\": \"${PERCENTAGE}%\", \"color\": \"${COV_COLOR}\"}" > coverage.json
62+
63+
git config user.name github-actions
64+
git config user.email github-actions@github.com
65+
git add tests.json coverage.json
66+
git commit -m "Update badges"
67+
git push origin badges --force

README.md

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# typed-event-utils
2+
3+
![Tests](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/jsxtools/typed-event-utils/refs/heads/badges/tests.json)
4+
![Coverage](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/jsxtools/typed-event-utils/refs/heads/badges/coverage.json)
5+
6+
A collection of type-safe utilities for working with events.
7+
8+
## Features
9+
10+
- **Type-safe Event**: Strongly typed Event constructor with enhanced type safety
11+
- **Type-safe EventTarget**: Strongly typed event handling with full TypeScript support
12+
- **AbortableEventTarget**: EventTarget with built-in abort functionality for automatic cleanup
13+
14+
## Installation
15+
16+
```shell
17+
npm install typed-event-utils
18+
```
19+
20+
## Usage
21+
22+
### Event
23+
24+
The `Event` constructor provides type safety for event creation with enhanced target typing.
25+
26+
```typescript
27+
import { Event } from "typed-event-utils/event"
28+
29+
// create a typed event with target type information
30+
const event = new Event<HTMLButtonElement>("click")
31+
32+
// the event has properly typed currentTarget and target properties
33+
// event.currentTarget: HTMLButtonElement
34+
// event.target: HTMLButtonElement
35+
```
36+
37+
### EventTarget
38+
39+
The `EventTarget` class provides type safety for event handling.
40+
41+
```typescript
42+
import { EventTarget } from "typed-event-utils"
43+
44+
// define your event types
45+
type Events = {
46+
"success": CustomEvent<{ message: string }>
47+
"error": ErrorEvent
48+
}
49+
50+
// create a typed event target
51+
const target = new EventTarget<Events>()
52+
53+
// add event listeners with full type safety
54+
target.addEventListener("success", (event) => {
55+
// event.detail is properly typed with { message: string }
56+
console.log("success message:", event.detail.message)
57+
})
58+
59+
target.addEventListener("error", (event) => {
60+
// event is properly typed as an ErrorEvent
61+
console.log(event.message)
62+
})
63+
64+
// dispatch an error event
65+
target.dispatchEvent(new ErrorEvent("error", { message: "cause" }))
66+
67+
// @ts-expect-error because "invalid" is not an allowable event
68+
target.addEventListener("invalid", (event) => {})
69+
70+
// @ts-expect-error because MessageEvent is not an allowable instance
71+
target.dispatchEvent(new MessageEvent("error", { message: "cause" }))
72+
```
73+
74+
### AbortableEventTarget
75+
76+
The `AbortableEventTarget` class extends `EventTarget` to support cleanup capabilities.
77+
78+
```typescript
79+
import { AbortableEventTarget } from "typed-event-utils/abortable-event-target"
80+
81+
type Events = {
82+
"update-data": CustomEvent<{ data: any }>
83+
}
84+
85+
const target = new AbortableEventTarget<Events>()
86+
87+
// All listeners are automatically connected to an AbortSignal associated with the target
88+
target.addEventListener("update-data", (event) => {
89+
console.log("Data updated:", event.detail.data)
90+
})
91+
92+
// Remove all listeners at once
93+
target.abort()
94+
95+
// The AbortSignal is accessible if needed
96+
console.log(target.signal.aborted) // true after abort()
97+
```
98+
99+
## API Reference
100+
101+
### Event\<T, U\>
102+
103+
A generic `Event` constructor that provides type safety for event creation with enhanced target typing.
104+
105+
- `new Event<T, U>(type, eventInitDict?)`: Creates a typed event with target type information.
106+
107+
### EventTarget\<T\>
108+
109+
A generic `EventTarget` that provides type safety for event handling.
110+
111+
- `new EventTarget<T>()`: Creates a typed event target with typed events.
112+
113+
#### Methods
114+
115+
- `addEventListener(type, listener, options?)`: Adds a typed event listener.
116+
- `removeEventListener(type, listener, options?)`: Removes a typed event listener.
117+
- `dispatchEvent(event)`: Dispatches a typed event.
118+
119+
### AbortableEventTarget\<T\>
120+
121+
Extends `EventTarget` with automatic cleanup capabilities.
122+
123+
- `new EventTarget<T>()`: Creates a typed event target with typed events and cleanup capabilities.
124+
125+
#### Methods
126+
127+
Extends `EventTarget` methods.
128+
129+
- `abort(reason?)`: Aborts all event listeners on the target.
130+
131+
#### Properties
132+
133+
- `signal`: Read-only `AbortSignal` for the event target.
134+
135+
<br />
136+
137+
## License
138+
139+
### MIT No Attribution
140+
141+
#### Copyright 2025 Jonathan Neal
142+
143+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so.
144+
145+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

abortable-event-target.d.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { Events, EventTarget } from "./event-target.d.ts"
2+
3+
export var AbortableEventTarget: AbortableEventTargetConstructor
4+
5+
/** The **`AbortableEventTarget()`** constructor creates a new AbortableEventTarget object instance. */
6+
export interface AbortableEventTargetConstructor {
7+
new <T extends Events>(): AbortableEventTarget<T>
8+
9+
prototype: AbortableEventTarget
10+
}
11+
12+
/** The **`AbortableEventTarget`** interface is implemented by objects that can receive events and may have listeners for them. */
13+
export interface AbortableEventTarget<T extends Events = Events> extends EventTarget<T> {
14+
/** The **`signal`** read-only property of the AbortableEventTarget interface returns an AbortSignal object instance, which can be used to abort all listeners on the object. */
15+
readonly signal: AbortSignal;
16+
17+
/** The **`abort()`** method of the AbortableEventTarget interface aborts all listeners on the object. */
18+
abort(reason?: any): void;
19+
}

abortable-event-target.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
export class AbortableEventTarget extends EventTarget {
2+
abort() {
3+
this.#controller.abort(...arguments)
4+
}
5+
6+
addEventListener(type, listener, options = null) {
7+
this.signal.aborted || super.addEventListener(type, listener, {
8+
capture: +options,
9+
signal: this.signal,
10+
...options,
11+
})
12+
}
13+
14+
renew() {
15+
if (this.signal.aborted) this.#controller = new AbortController()
16+
}
17+
18+
get signal() {
19+
return this.#controller.signal
20+
}
21+
22+
#controller = new AbortController()
23+
}

event-target.d.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
export var EventTarget: EventTargetConstructor
2+
3+
/**
4+
* The **`EventTarget()`** constructor creates a new EventTarget object instance.
5+
*
6+
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/EventTarget)
7+
*/
8+
export interface EventTargetConstructor {
9+
new <T extends Events = Events>(): EventTarget<T>
10+
11+
prototype: globalThis.EventTarget
12+
}
13+
14+
/**
15+
* The **`EventTarget`** interface is implemented by objects that can receive events and may have listeners for them.
16+
*
17+
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget)
18+
*/
19+
export interface EventTarget<T extends Events = Events> {
20+
/**
21+
* The **`addEventListener()`** method of the EventTarget interface sets up a function that will be called whenever the specified event is delivered to the target.
22+
*
23+
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener)
24+
*/
25+
addEventListener<K extends keyof T>(
26+
type: K,
27+
listener: EventListenerOrEventListenerObject<T[K]> | null,
28+
options?: AddEventListenerOptions | boolean
29+
): void
30+
31+
/**
32+
* The **`dispatchEvent()`** method of the EventTarget sends an Event to the object, (synchronously) invoking the affected event listeners in the appropriate order.
33+
*
34+
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener)
35+
*/
36+
removeEventListener<K extends keyof T>(
37+
type: K,
38+
listener: EventListenerOrEventListenerObject<T[K]> | null,
39+
options?: EventListenerOptions | boolean
40+
): void
41+
42+
/**
43+
* The **`removeEventListener()`** method of the EventTarget interface removes an event listener previously registered with EventTarget.addEventListener() from the target.
44+
*
45+
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/dispatchEvent)
46+
*/
47+
dispatchEvent<K extends keyof T>(event: T[K]): boolean
48+
}
49+
50+
/** The callback function that receives a specified event delivered to the target */
51+
export interface EventListener<T extends Event = Event> {
52+
(event: T): void
53+
}
54+
55+
/** The object whose handleEvent() method serves as the callback function. */
56+
export interface EventListenerObject<T extends Event = Event> {
57+
/** The callback function that receives a specified event delivered to the target */
58+
handleEvent(event: T): void
59+
}
60+
61+
/** The object that specifies characteristics about the event listener. */
62+
export interface AddEventListenerOptions extends EventListenerOptions {
63+
once?: boolean
64+
passive?: boolean
65+
signal?: AbortSignal
66+
}
67+
68+
/** The object that specifies characteristics about the event listener. */
69+
export interface EventListenerOptions {
70+
capture?: boolean
71+
}
72+
73+
/** The map of event types to their corresponding event objects. */
74+
export interface Events {
75+
[type: string]: Event
76+
}
77+
78+
/** The callback function or object whose handleEvent() method serves as the callback function. */
79+
export type EventListenerOrEventListenerObject<T extends Event = Event> = EventListener<T> | EventListenerObject<T>

event-target.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export var { EventTarget } = globalThis

event.d.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
export var Event: EventConstructor
2+
3+
/**
4+
* The **`Event()`** constructor creates a new Event object.
5+
*
6+
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Event/Event)
7+
*/
8+
export interface EventConstructor {
9+
new <T extends EventTarget = EventTarget, U extends EventTarget = T>(type: string, eventInitDict?: EventInit): Event<T, U>
10+
11+
prototype: globalThis.Event
12+
13+
readonly NONE: 0
14+
readonly CAPTURING_PHASE: 1
15+
readonly AT_TARGET: 2
16+
readonly BUBBLING_PHASE: 3
17+
}
18+
19+
/**
20+
* The **`Event`** interface represents an event which takes place on an EventTarget.
21+
*
22+
* [MDN Reference](https://developer.mozilla.org/en-US/docs/Web/API/Event)
23+
*/
24+
export type Event<T extends EventTarget = EventTarget, U extends EventTarget = T> = globalThis.Event & {
25+
readonly currentTarget: T
26+
readonly target: U
27+
}
28+
29+
/** The object that specifies characteristics about the event. */
30+
export interface EventInit {
31+
bubbles?: boolean
32+
cancelable?: boolean
33+
composed?: boolean
34+
}

event.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export var { Event } = globalThis

0 commit comments

Comments
 (0)