on-events 0.0.2 → 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/README.md CHANGED
@@ -1,3 +1,14 @@
1
+
2
+ # on-events
3
+
4
+ [![npm version](https://img.shields.io/npm/v/on-events.svg)](https://www.npmjs.com/package/on-events)
5
+ [![npm downloads](https://img.shields.io/npm/dm/on-events.svg)](https://www.npmjs.com/package/on-events)
6
+ [![GitHub stars](https://img.shields.io/github/stars/iWhatty/on-event-js.svg?style=social)](https://github.com/iWhatty/on-event-js)
7
+ [![License](https://img.shields.io/github/license/iWhatty/on-event-js.svg)](https://github.com/iWhatty/on-event-js/blob/main/LICENSE)
8
+
9
+ **A tiny DOM event utility with composable sugar.**
10
+
11
+
1
12
  # on-events
2
13
 
3
14
  **A tiny DOM event utility with composable sugar.**
@@ -5,6 +16,29 @@ Write clean event bindings using fluent chains like `On.click(...)`, `On.capture
5
16
 
6
17
  ---
7
18
 
19
+ ## ⚡ Example Usage
20
+
21
+ Compose `once`, `capture`, `passive`, and delegation without repetitive option objects.
22
+
23
+ ```js
24
+ import { On } from 'on-events'
25
+
26
+ function handleLinkClick(e) {
27
+ e.preventDefault()
28
+ console.log('First captured delegated click:', this.href)
29
+ }
30
+
31
+ On.first.delegate.capture.click(document, 'a.nav-link', handleLinkClick)
32
+ ```
33
+
34
+ * Delegated
35
+ * Capture phase
36
+ * Fires once
37
+ * Clean `this` binding
38
+ * Returns `stop()` if you need manual control
39
+
40
+ ---
41
+
8
42
  ## Features
9
43
 
10
44
  * `on(el, 'click', fn)` — classic binding
@@ -13,10 +47,12 @@ Write clean event bindings using fluent chains like `On.click(...)`, `On.capture
13
47
  * `On.capture.passive.scroll(el, fn)` — fully composable modifiers
14
48
  * `On.delegate.click(el, selector, fn)` — delegated events
15
49
  * `On.hover(el, enter, leave)` — mouseenter/leave pair
16
- * `On.batch(el, { click, ... })` — multi-bind at once
50
+ * `On.batch(el, { click, ... })` — bind multiple events at once
17
51
  * `On.first.batch(...)` — one-time multi-bind
18
52
  * `On.ready(fn)` — run when DOM is ready
19
- * ESM, zero dependencies, < 1KB min+gzip
53
+ * `On.group()` collect related listeners and tear them down together
54
+ * Better TS support for simple binds like `On.input(el, fn)` and `On.change(el, fn)`
55
+ * ESM, zero dependencies, tiny footprint
20
56
 
21
57
  ---
22
58
 
@@ -28,10 +64,6 @@ npm install on-events
28
64
 
29
65
  ---
30
66
 
31
- ## Usage
32
-
33
- ---
34
-
35
67
  ## Fluent & Composable Sugar
36
68
 
37
69
  ```js
@@ -127,7 +159,7 @@ On.first.batch(document, {
127
159
 
128
160
  ---
129
161
 
130
- ## Delegate Batch (Optional Pattern)
162
+ ## Delegate Batch
131
163
 
132
164
  You may pass `[selector, handler]` for delegated batch entries:
133
165
 
@@ -155,9 +187,56 @@ On.ready(handleReady)
155
187
 
156
188
  ---
157
189
 
190
+ ## Grouped Cleanup
191
+
192
+ When a UI module binds listeners across multiple elements, `On.group()` lets you track them under one scoped teardown handle.
193
+
194
+ ```js
195
+ import { On } from 'on-events'
196
+
197
+ const page = On.group()
198
+
199
+ page.click(settingsToggleBtn, () => {
200
+ settingsSection.classList.toggle('collapsed')
201
+ })
202
+
203
+ page.input(searchInput, handleSearch)
204
+ page.delegate.click(document, 'button.save', handleSave)
205
+
206
+ // Later:
207
+ page.stop()
208
+ ```
209
+
210
+ You can also manually add an existing cleanup function:
211
+
212
+ ```js
213
+ const group = On.group()
214
+
215
+ group.add(On.click(button, handleClick))
216
+ group.add(null) // safely ignored
217
+
218
+ group.stop()
219
+ ```
220
+
221
+ ---
222
+
223
+ ## Custom Events
224
+
225
+ For custom or non-standard event names, use `On.event(type)`:
226
+
227
+ ```js
228
+ const stop = On.event('panel:open')(panel, (e) => {
229
+ console.log('opened', e.type)
230
+ })
231
+
232
+ stop()
233
+ ```
234
+
235
+ ---
236
+
158
237
  # API Reference
159
238
 
160
- # Composable Modifiers
239
+ ## Composable Modifiers
161
240
 
162
241
  Modifiers can be chained before the event name.
163
242
 
@@ -216,16 +295,34 @@ Runs `fn` once the DOM is fully loaded (`DOMContentLoaded` or already ready).
216
295
 
217
296
  ---
218
297
 
298
+ ## `On.group()`
299
+
300
+ Creates a scoped cleanup collector for related listeners.
301
+
302
+ ```js
303
+ const group = On.group()
304
+
305
+ group.click(button, onClick)
306
+ group.input(input, onInput)
307
+
308
+ group.stop()
309
+ ```
310
+
311
+ Useful when a page, modal, or UI module binds listeners across multiple elements and wants one teardown call.
312
+
313
+ ---
314
+
219
315
  ## Why not `addEventListener` directly?
220
316
 
221
317
  `addEventListener` is great — this library just removes the repetitive parts when you bind lots of UI events.
222
318
 
223
319
  * One-liners for common patterns (`once`, `capture`, `passive`, `delegate`)
224
- * Every bind returns a `stop()` cleanup function (no “where did I put that handler?”)
320
+ * Every bind returns a `stop()` cleanup function
225
321
  * Delegation helper that sets `this` to the matched element
226
322
  * Batch binding to keep setup code tidy
227
- * Composable modifiers so you don’t have to remember option object shapes
228
- * Zero deps and tiny footprint, so it stays out of your way
323
+ * Grouped cleanup for lifecycle-based teardown
324
+ * Composable modifiers instead of option object juggling
325
+ * Zero deps and tiny footprint
229
326
 
230
327
  ---
231
328
 
@@ -233,11 +330,11 @@ Runs `fn` once the DOM is fully loaded (`DOMContentLoaded` or already ready).
233
330
 
234
331
  This library is a thin wrapper around native `addEventListener`.
235
332
 
236
- * **Direct binding (`On.click(el, fn)` / `on(el, 'click', fn)`)**: adds essentially no runtime overhead beyond one extra function call during setup.
237
- * **`first` / `capture` / `passive`**: uses the browser’s native listener options (`{ once }`, `{ capture }`, `{ passive }`).
238
- * **Delegation (`On.delegate.*`)**: each event does a `closest(selector)` lookup. It’s excellent for reducing the *number* of listeners, but for extremely hot events (e.g. `mousemove`), direct binding may be faster.
333
+ * **Direct binding (`On.click(el, fn)` / `on(el, 'click', fn)`)**: essentially zero runtime overhead beyond one extra function call during setup.
334
+ * **`first` / `capture` / `passive`**: uses native listener options.
335
+ * **Delegation (`On.delegate.*`)**: performs a `closest(selector)` lookup per event. Ideal for reducing listener count, but direct binding is better for extremely hot events like `mousemove`.
239
336
 
240
- Rule of thumb: use delegation for `click/input/submit`, and direct handlers for high-frequency events.
337
+ Rule of thumb: delegate `click`, `input`, and `submit`; bind directly for high-frequency events.
241
338
 
242
339
  ---
243
340
 
@@ -250,14 +347,18 @@ Rule of thumb: use delegation for `click/input/submit`, and direct handlers for
250
347
 
251
348
  ---
252
349
 
253
- ## Low-level API (on / off)
350
+ ## Low-level API (`on` / `off`)
254
351
 
255
352
  If you prefer a minimal, explicit API without fluent modifiers, you can use the core helpers directly.
256
353
 
257
354
  ### `on(el, event, handler)`
258
355
 
356
+ ### `on(el, event, handler, options)`
357
+
259
358
  ### `on(el, event, selector, handler)`
260
359
 
360
+ ### `on(el, event, selector, handler, options)`
361
+
261
362
  Adds a standard or delegated event listener.
262
363
  Returns a `stop()` function that removes the listener.
263
364
 
@@ -5,54 +5,99 @@ export type Stop = () => void
5
5
  export type Handler<E extends Event = Event> = (this: Element, ev: E) => any
6
6
  export type DirectHandler<E extends Event = Event> = (ev: E) => any
7
7
 
8
+ export type BatchMap = Record<
9
+ string,
10
+ | DirectHandler<any>
11
+ | [string, Handler<any>]
12
+ >
13
+
14
+ // --- Low-level API ---
15
+
8
16
  export function on<E extends Event = Event>(
9
- el: EventTarget,
10
- event: string,
11
- handler: DirectHandler<E>
17
+ el: EventTarget,
18
+ event: string,
19
+ handler: DirectHandler<E>,
20
+ options?: AddEventListenerOptions | boolean
12
21
  ): Stop
13
22
 
14
23
  export function on<E extends Event = Event>(
15
- el: Element | Document,
16
- event: string,
17
- selector: string,
18
- handler: Handler<E>
24
+ el: Element | Document,
25
+ event: string,
26
+ selector: string,
27
+ handler: Handler<E>,
28
+ options?: AddEventListenerOptions | boolean
19
29
  ): Stop
20
30
 
21
31
  export function off<E extends Event = Event>(
22
- el: EventTarget,
23
- event: string,
24
- handler: DirectHandler<E>,
25
- selector?: null
32
+ el: EventTarget,
33
+ event: string,
34
+ handler: DirectHandler<E>,
35
+ selector?: null
26
36
  ): void
27
37
 
28
38
  export function off<E extends Event = Event>(
29
- el: Element | Document,
30
- event: string,
31
- handler: Handler<E>,
32
- selector?: string
39
+ el: Element | Document,
40
+ event: string,
41
+ handler: Handler<E>,
42
+ selector?: string
33
43
  ): void
34
44
 
45
+ // --- Fluent Event Binder ---
46
+
47
+ export interface EventBinder<E extends Event = Event> {
48
+ (el: EventTarget, handler: DirectHandler<E>): Stop
49
+ (el: Element | Document, selector: string, handler: Handler<E>): Stop
50
+ }
51
+
52
+ // --- Fluent Chain API core ---
53
+
35
54
  export interface OnChain {
36
- // modifiers
37
- first: OnChain
38
- once: OnChain
39
- capture: OnChain
40
- passive: OnChain
41
- delegate: OnChain
42
-
43
- // utilities
44
- hover(el: Element, enter: (ev: Event) => any, leave: (ev: Event) => any): Stop
45
- batch(
46
- el: EventTarget,
47
- map: Record<
48
- string,
49
- ((ev: any) => any) | [string, (ev: any) => any]
50
- >
51
- ): Stop
52
- ready(fn: () => void): void
53
-
54
- // event binder (unknown event names supported)
55
- [event: string]: any
55
+ first: OnChain
56
+ once: OnChain
57
+ capture: OnChain
58
+ passive: OnChain
59
+ delegate: OnChain
60
+
61
+ hover(
62
+ el: Element,
63
+ enter: (ev: MouseEvent) => any,
64
+ leave: (ev: MouseEvent) => any
65
+ ): Stop
66
+
67
+ batch(
68
+ el: EventTarget,
69
+ map: BatchMap
70
+ ): Stop
71
+
72
+ ready(fn: () => void): void
73
+
74
+ group(): OnGroup
75
+
76
+ <E extends Event = Event>(
77
+ el: EventTarget,
78
+ handler: DirectHandler<E>
79
+ ): Stop
80
+
81
+ <E extends Event = Event>(
82
+ el: Element | Document,
83
+ selector: string,
84
+ handler: Handler<E>
85
+ ): Stop
86
+
87
+ // fallback for custom / non-standard event names
88
+ event(type: string): OnChain & EventBinder<Event>
89
+ }
90
+
91
+ export type OnEventMap = {
92
+ [K in keyof GlobalEventHandlersEventMap]:
93
+ OnChain & EventBinder<GlobalEventHandlersEventMap[K]>
94
+ }
95
+
96
+ export type OnAPI = OnChain & OnEventMap
97
+
98
+ export type OnGroup = OnAPI & {
99
+ stop(): void
100
+ add<T extends Stop | null | undefined | false>(stop: T): T
56
101
  }
57
102
 
58
- export const On: OnChain
103
+ export const On: OnAPI
@@ -1,2 +1,2 @@
1
1
  // On-Events - A tiny DOM event utility with sugar.
2
- var p=new WeakMap;function d(t,c,i,n,e){typeof i=="function"&&(e=n,n=i,i=null);let u=e||void 0,r=i?o=>{let s=o.target instanceof Element?o.target:o.target?.parentElement;if(!s)return;let f=s.closest(i);f&&t.contains(f)&&n.call(f,o)}:n;return t.addEventListener(c,r,u),p.has(t)||p.set(t,[]),p.get(t).push({type:c,cb:n,selector:i,wrapped:r,capture:!!(u&&u.capture)}),()=>L(t,c,n,i)}function L(t,c,i,n=null){let e=p.get(t);if(e){for(let u=e.length;u-- >0;){let r=e[u];r.type===c&&r.cb===i&&(n?r.selector===n:!r.selector)&&(t.removeEventListener(c,r.wrapped,{capture:r.capture}),e.splice(u,1))}e.length||p.delete(t)}}var m=1,E=2,v=4,h=8,O=t=>{if(!(t&(m|E|v)))return;let c={};return t&m&&(c.once=!0),t&E&&(c.capture=!0),t&v&&(c.passive=!0),c},a=(t=0)=>new Proxy(Object.create(null),{get(c,i){let n=String(i);return n==="first"||n==="once"?a(t|m):n==="capture"?a(t|E):n==="passive"?a(t|v):n==="delegate"?a(t|h):n==="hover"?(e,u,r)=>{let o=O(t),s=d(e,"mouseenter",u,o),f=d(e,"mouseleave",r,o);return()=>{s(),f()}}:n==="batch"?(e,u)=>{let r=[];for(let[o,s]of Object.entries(u)){if(typeof s=="function"){r.push(a(t)[o](e,s));continue}if(Array.isArray(s)){let[f,A]=s;r.push(a(t|h)[o](e,f,A))}}return()=>r.forEach(o=>o())}:n==="ready"?e=>{typeof document>"u"||(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",e,{once:!0}):e())}:(e,...u)=>{let r=O(t);if(t&h){let[s,f]=u;return d(e,n,s,f,r)}let[o]=u;return d(e,n,o,r)}}}),y=a();y.once=y.first;var g=(t,c,...i)=>d(t,c,...i);export{y as On,L as off,g as on};
2
+ var h=new WeakMap;function p(n,u,c,r,t){typeof c=="function"&&(t=r,r=c,c=null);let o=t||void 0,e=c?i=>{let f=i.target instanceof Element?i.target:i.target?.parentElement;if(!f)return;let d=f.closest(c);d&&typeof n.contains=="function"&&n.contains(d)&&r.call(d,i)}:r;return n.addEventListener(u,e,o),h.has(n)||h.set(n,[]),h.get(n).push({type:u,cb:r,selector:c,wrapped:e,capture:!!(o&&o.capture)}),()=>w(n,u,r,c)}function w(n,u,c,r=null){let t=h.get(n);if(t){for(let o=t.length;o-- >0;){let e=t[o];e.type===u&&e.cb===c&&(r?e.selector===r:!e.selector)&&(n.removeEventListener(u,e.wrapped,{capture:e.capture}),t.splice(o,1))}t.length||h.delete(n)}}var m=1,v=2,y=4,l=8,E=n=>{if(!(n&(m|v|y)))return;let u={};return n&m&&(u.once=!0),n&v&&(u.capture=!0),n&y&&(u.passive=!0),u},a=(n=0)=>new Proxy(Object.create(null),{get(u,c){if(Reflect.has(u,c))return Reflect.get(u,c);let r=String(c);return r==="first"||r==="once"?a(n|m):r==="capture"?a(n|v):r==="passive"?a(n|y):r==="delegate"?a(n|l):r==="event"?t=>a(n)[t]:r==="hover"?(t,o,e)=>{let s=E(n),i=p(t,"mouseenter",o,s),f=p(t,"mouseleave",e,s);return()=>{i(),f()}}:r==="batch"?(t,o)=>{let e=[];for(let[s,i]of Object.entries(o)){if(typeof i=="function"){e.push(a(n)[s](t,i));continue}if(Array.isArray(i)){let[f,d]=i;e.push(a(n|l)[s](t,f,d))}}return()=>{for(let s of e)s()}}:r==="ready"?t=>{typeof document>"u"||(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",t,{once:!0}):t())}:(t,...o)=>{let e=E(n);if(n&l){let[i,f]=o;return p(t,r,i,f,e)}let[s]=o;return p(t,r,s,e)}}}),g=a();g.group=function(){let u=new Set,c=t=>(typeof t=="function"&&u.add(t),t),r=()=>{let t=[];for(let o of u)try{o()}catch(e){t.push(e)}if(u.clear(),t.length===1)throw t[0];if(t.length>1)throw new AggregateError(t,"On.group().stop() failed for one or more listeners")};return new Proxy(Object.create(null),{get(t,o){if(o==="add")return c;if(o==="stop")return r;let e=g[o];return e==null?e:o==="event"?s=>{let i=e(s);return(...f)=>c(i(...f))}:typeof e=="function"?(...s)=>c(e(...s)):new Proxy(e,{get(s,i){let f=e[i];return typeof f=="function"?(...d)=>c(f(...d)):f}})}})};g.once=g.first;var O=(n,u,...c)=>p(n,u,...c);export{g as On,w as off,O as on};
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/on-events.js"],
4
- "sourcesContent": ["// on-events.js\r\n// On-Events \u2014 tiny DOM event utility with composable sugar.\r\n\r\n// --- internal base ---\r\nconst listeners = new WeakMap()\r\n\r\n/**\r\n * baseOn(el, type, handler)\r\n * baseOn(el, type, selector, handler)\r\n * baseOn(el, type, handler, options)\r\n * baseOn(el, type, selector, handler, options)\r\n */\r\nfunction baseOn(el, type, selector, cb, options) {\r\n // normalize args\r\n if (typeof selector === 'function') {\r\n options = cb\r\n cb = selector\r\n selector = null\r\n }\r\n\r\n const opts = options || undefined\r\n\r\n const wrapped = selector\r\n ? (e) => {\r\n const target = e.target instanceof Element ? e.target : e.target?.parentElement\r\n if (!target) return\r\n const match = target.closest(selector)\r\n if (match && el.contains(match)) cb.call(match, e)\r\n }\r\n : cb\r\n\r\n el.addEventListener(type, wrapped, opts)\r\n\r\n if (!listeners.has(el)) listeners.set(el, [])\r\n listeners.get(el).push({\r\n type,\r\n cb,\r\n selector,\r\n wrapped,\r\n capture: !!(opts && opts.capture), // critical for proper removal\r\n })\r\n\r\n return () => off(el, type, cb, selector)\r\n}\r\n\r\nfunction off(el, type, cb, selector = null) {\r\n const group = listeners.get(el)\r\n if (!group) return\r\n\r\n for (let i = group.length; i-- > 0;) {\r\n const h = group[i]\r\n const match =\r\n h.type === type &&\r\n h.cb === cb &&\r\n (selector ? h.selector === selector : !h.selector)\r\n\r\n if (match) {\r\n // capture must match for removal\r\n el.removeEventListener(type, h.wrapped, { capture: h.capture })\r\n group.splice(i, 1)\r\n }\r\n }\r\n\r\n if (!group.length) listeners.delete(el)\r\n}\r\n\r\n// --- composable sugar: On.<mods>.<event>(...) ---\r\n// bitflags keep the proxy tiny + fast\r\nconst ONCE = 1\r\nconst CAPTURE = 2\r\nconst PASSIVE = 4\r\nconst DELEGATE = 8\r\n\r\nconst optsFor = (f) => {\r\n // Avoid allocating options when none are set.\r\n // Note: capture removal is handled by storing capture in registry.\r\n if (!(f & (ONCE | CAPTURE | PASSIVE))) return undefined\r\n const o = {}\r\n if (f & ONCE) o.once = true\r\n if (f & CAPTURE) o.capture = true\r\n if (f & PASSIVE) o.passive = true\r\n return o\r\n}\r\n\r\nconst makeOn = (flags = 0) =>\r\n new Proxy(Object.create(null), {\r\n get(_t, prop) {\r\n const k = String(prop)\r\n\r\n // modifier chaining\r\n if (k === 'first' || k === 'once') return makeOn(flags | ONCE)\r\n if (k === 'capture') return makeOn(flags | CAPTURE)\r\n if (k === 'passive') return makeOn(flags | PASSIVE)\r\n if (k === 'delegate') return makeOn(flags | DELEGATE)\r\n\r\n // utilities (kept as properties for backwards-compat ergonomics)\r\n if (k === 'hover') {\r\n return (el, enter, leave) => {\r\n const o = optsFor(flags)\r\n const offIn = baseOn(el, 'mouseenter', enter, o)\r\n const offOut = baseOn(el, 'mouseleave', leave, o)\r\n return () => {\r\n offIn()\r\n offOut()\r\n }\r\n }\r\n }\r\n\r\n if (k === 'batch') {\r\n return (el, map) => {\r\n const stops = []\r\n for (const [event, val] of Object.entries(map)) {\r\n // basic: { click: fn }\r\n if (typeof val === 'function') {\r\n stops.push(makeOn(flags)[event](el, val))\r\n continue\r\n }\r\n // optional delegate batch: { click: ['a', fn] }\r\n if (Array.isArray(val)) {\r\n const [selector, fn] = val\r\n stops.push(makeOn(flags | DELEGATE)[event](el, selector, fn))\r\n }\r\n }\r\n return () => stops.forEach((stop) => stop())\r\n }\r\n }\r\n\r\n if (k === 'ready') {\r\n return (fn) => {\r\n if (typeof document === 'undefined') return\r\n if (document.readyState === 'loading') {\r\n document.addEventListener('DOMContentLoaded', fn, { once: true })\r\n } else {\r\n fn()\r\n }\r\n }\r\n }\r\n\r\n // event binder\r\n return (el, ...args) => {\r\n const o = optsFor(flags)\r\n\r\n if (flags & DELEGATE) {\r\n const [selector, handler] = args\r\n return baseOn(el, k, selector, handler, o)\r\n }\r\n\r\n const [handler] = args\r\n return baseOn(el, k, handler, o)\r\n }\r\n },\r\n })\r\n\r\nconst On = makeOn()\r\n\r\n// --- legacy alias for backward compatibility ---\r\nOn.once = On.first\r\n\r\n// --- classic support ---\r\nconst on = (el, event, ...args) => baseOn(el, event, ...args)\r\n\r\nexport { on, On, off }"],
5
- "mappings": "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",
6
- "names": ["listeners", "baseOn", "el", "type", "selector", "cb", "options", "opts", "wrapped", "e", "target", "match", "off", "group", "i", "h", "ONCE", "CAPTURE", "PASSIVE", "DELEGATE", "optsFor", "f", "o", "makeOn", "flags", "_t", "prop", "k", "enter", "leave", "offIn", "offOut", "map", "stops", "event", "val", "fn", "stop", "args", "handler", "On", "on"]
4
+ "sourcesContent": ["// on-events.js\r\n// On-Events \u2014 tiny DOM event utility with composable sugar.\r\n\r\n// --- internal base ---\r\nconst listeners = new WeakMap()\r\n\r\n/**\r\n * baseOn(el, type, handler)\r\n * baseOn(el, type, selector, handler)\r\n * baseOn(el, type, handler, options)\r\n * baseOn(el, type, selector, handler, options)\r\n */\r\nfunction baseOn(el, type, selector, cb, options) {\r\n // Normalize overloaded signatures so everything below can use one path.\r\n if (typeof selector === 'function') {\r\n options = cb\r\n cb = selector\r\n selector = null\r\n }\r\n\r\n const opts = options || undefined\r\n\r\n const wrapped = selector\r\n ? (e) => {\r\n // Delegation can receive a Text node as the event target.\r\n // Normalize that to an element before calling closest().\r\n const target = e.target instanceof Element ? e.target : e.target?.parentElement\r\n if (!target) return\r\n\r\n const match = target.closest(selector)\r\n if (!match) return\r\n\r\n // Guard delegated matching so odd targets like window do not explode.\r\n if (typeof el.contains === 'function' && el.contains(match)) {\r\n cb.call(match, e)\r\n }\r\n }\r\n : cb\r\n\r\n el.addEventListener(type, wrapped, opts)\r\n\r\n if (!listeners.has(el)) listeners.set(el, [])\r\n const group = listeners.get(el)\r\n\r\n group.push({\r\n type,\r\n cb,\r\n selector,\r\n wrapped,\r\n capture: !!(opts && opts.capture), // capture must match during removal\r\n })\r\n\r\n return () => off(el, type, cb, selector)\r\n}\r\n\r\nfunction off(el, type, cb, selector = null) {\r\n const group = listeners.get(el)\r\n if (!group) return\r\n\r\n for (let i = group.length; i-- > 0;) {\r\n const h = group[i]\r\n const match =\r\n h.type === type &&\r\n h.cb === cb &&\r\n (selector ? h.selector === selector : !h.selector)\r\n\r\n if (match) {\r\n // removeEventListener only cares about capture on teardown.\r\n el.removeEventListener(type, h.wrapped, { capture: h.capture })\r\n group.splice(i, 1)\r\n }\r\n }\r\n\r\n if (!group.length) listeners.delete(el)\r\n}\r\n\r\n// --- composable sugar: On.<mods>.<event>(...) ---\r\n// Bitflags keep the proxy small and cheap.\r\nconst ONCE = 1\r\nconst CAPTURE = 2\r\nconst PASSIVE = 4\r\nconst DELEGATE = 8\r\n\r\nconst optsFor = (f) => {\r\n // Allocate listener options only when needed.\r\n // Capture is stored separately so removeEventListener can match it later.\r\n if (!(f & (ONCE | CAPTURE | PASSIVE))) return undefined\r\n\r\n const o = {}\r\n if (f & ONCE) o.once = true\r\n if (f & CAPTURE) o.capture = true\r\n if (f & PASSIVE) o.passive = true\r\n return o\r\n}\r\n\r\nconst makeOn = (flags = 0) =>\r\n new Proxy(Object.create(null), {\r\n get(target, prop) {\r\n // Respect concrete properties assigned directly onto the proxy target.\r\n // This is what makes late-added members like On.group work instead of\r\n // being mistaken for an event name.\r\n if (Reflect.has(target, prop)) {\r\n return Reflect.get(target, prop)\r\n }\r\n\r\n const k = String(prop)\r\n\r\n // Modifier chaining accumulates flags and returns a fresh proxy.\r\n if (k === 'first' || k === 'once') return makeOn(flags | ONCE)\r\n if (k === 'capture') return makeOn(flags | CAPTURE)\r\n if (k === 'passive') return makeOn(flags | PASSIVE)\r\n if (k === 'delegate') return makeOn(flags | DELEGATE)\r\n\r\n // Custom event helper: On.event('panel:open')(el, fn)\r\n if (k === 'event') {\r\n return (type) => makeOn(flags)[type]\r\n }\r\n\r\n // Utilities live on the same fluent surface for ergonomics.\r\n if (k === 'hover') {\r\n return (el, enter, leave) => {\r\n const o = optsFor(flags)\r\n const offIn = baseOn(el, 'mouseenter', enter, o)\r\n const offOut = baseOn(el, 'mouseleave', leave, o)\r\n return () => {\r\n offIn()\r\n offOut()\r\n }\r\n }\r\n }\r\n\r\n if (k === 'batch') {\r\n return (el, map) => {\r\n const stops = []\r\n\r\n for (const [event, val] of Object.entries(map)) {\r\n // Direct entry: { click: fn }\r\n if (typeof val === 'function') {\r\n stops.push(makeOn(flags)[event](el, val))\r\n continue\r\n }\r\n\r\n // Delegated entry: { click: ['a', fn] }\r\n if (Array.isArray(val)) {\r\n const [selector, fn] = val\r\n stops.push(makeOn(flags | DELEGATE)[event](el, selector, fn))\r\n }\r\n }\r\n\r\n return () => {\r\n for (const stop of stops) stop()\r\n }\r\n }\r\n }\r\n\r\n if (k === 'ready') {\r\n return (fn) => {\r\n if (typeof document === 'undefined') return\r\n\r\n // Run immediately if the DOM is already ready.\r\n if (document.readyState === 'loading') {\r\n document.addEventListener('DOMContentLoaded', fn, { once: true })\r\n } else {\r\n fn()\r\n }\r\n }\r\n }\r\n\r\n // Default path: treat the property name as the event type.\r\n return (el, ...args) => {\r\n const o = optsFor(flags)\r\n\r\n if (flags & DELEGATE) {\r\n const [selector, handler] = args\r\n return baseOn(el, k, selector, handler, o)\r\n }\r\n\r\n const [handler] = args\r\n return baseOn(el, k, handler, o)\r\n }\r\n },\r\n })\r\n\r\nconst On = makeOn()\r\n\r\n// Creates a scoped listener collector.\r\n// Any stop() returned by On.* calls is tracked and can be removed together via group.stop().\r\nOn.group = function group() {\r\n const stops = new Set()\r\n\r\n const track = (stop) => {\r\n // Pass through nullish / false values so callers can safely write:\r\n // group.add(maybeElement ? On.click(...) : null)\r\n if (typeof stop === 'function') stops.add(stop)\r\n return stop\r\n }\r\n\r\n const stopAll = () => {\r\n const errors = []\r\n\r\n // Try every tracked cleanup so one bad stop() does not block the rest.\r\n for (const stop of stops) {\r\n try {\r\n stop()\r\n } catch (err) {\r\n errors.push(err)\r\n }\r\n }\r\n\r\n stops.clear()\r\n\r\n if (errors.length === 1) throw errors[0]\r\n if (errors.length > 1) {\r\n throw new AggregateError(errors, 'On.group().stop() failed for one or more listeners')\r\n }\r\n }\r\n\r\n return new Proxy(Object.create(null), {\r\n get(_target, prop) {\r\n // Hard override these names so they never fall through into event binding.\r\n if (prop === 'add') return track\r\n if (prop === 'stop') return stopAll\r\n\r\n const value = On[prop]\r\n if (value == null) return value\r\n\r\n // event(type) is a factory: it returns a binder, not a stop handle.\r\n // Wrap the binder so the eventual stop() is what gets tracked.\r\n if (prop === 'event') {\r\n return (type) => {\r\n const binder = value(type)\r\n return (...args) => track(binder(...args))\r\n }\r\n }\r\n\r\n // Top-level callables like group.click(...), group.batch(...), group.ready(...)\r\n // are wrapped so any returned stop() is automatically tracked.\r\n if (typeof value === 'function') {\r\n return (...args) => track(value(...args))\r\n }\r\n\r\n // Nested chain objects like group.capture or group.delegate are proxies too.\r\n // Wrap their callable leaves so group.capture.click(...) also tracks cleanup.\r\n return new Proxy(value, {\r\n get(_nestedTarget, nestedProp) {\r\n const nestedValue = value[nestedProp]\r\n\r\n if (typeof nestedValue === 'function') {\r\n return (...args) => track(nestedValue(...args))\r\n }\r\n\r\n return nestedValue\r\n },\r\n })\r\n },\r\n })\r\n}\r\n\r\n// --- legacy alias for backward compatibility ---\r\nOn.once = On.first\r\n\r\n// --- classic support ---\r\nconst on = (el, event, ...args) => baseOn(el, event, ...args)\r\n\r\nexport { on, On, off }\r\n"],
5
+ "mappings": "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",
6
+ "names": ["listeners", "baseOn", "el", "type", "selector", "cb", "options", "opts", "wrapped", "e", "target", "match", "off", "group", "i", "h", "ONCE", "CAPTURE", "PASSIVE", "DELEGATE", "optsFor", "f", "o", "makeOn", "flags", "prop", "k", "enter", "leave", "offIn", "offOut", "map", "stops", "event", "val", "fn", "stop", "args", "handler", "On", "track", "stopAll", "errors", "err", "_target", "value", "binder", "_nestedTarget", "nestedProp", "nestedValue", "on"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "on-events",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "Tiny DOM event utility with composable sugar, delegation, and cleanup.",
5
5
  "type": "module",
6
6
  "sideEffects": false,