Skip to content

Commit

Permalink
add dispatch support
Browse files Browse the repository at this point in the history
  • Loading branch information
loreanvictor committed Jul 4, 2023
1 parent afc8f1a commit 616e2d9
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 3 deletions.
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ define('say-hi', ({ to }) => {
- [onAttribute](#onattribute)
- [onProperty](#onproperty)
- [on](#on)
- [useDispatch](#usedispatch)
- [onConnected](#onconnected)
- [onDisconnected](#ondisconnected)
- [onAttributeChanged](#onattributechanged)
Expand Down Expand Up @@ -194,6 +195,41 @@ Adds an event listener to the custom element (via `.addEventListener()`). For ex

<br>

### useDispatch

```ts
useDispatch<T>(name: string, options: EventInit = {}): (data: T) => void
```
Returns a dispatch function that will dispatch events of given name (and with given options) from the element. Dispatched events can be caught via `.addEventListener()`, or by by using attributes like `on${name}` (e.g. `onmyevent`):

```js
import { define, useDispatch } from 'minicomp'
import { html } from 'rehtm'


define('my-el', () => {
const dispatch = useDispatch('even')
let count = 0

return html`
<button onclick=${() => ++count % 2 === 0 && dispatch(count)}>
Click Me!
</button>
`
})
```
```html
<my-el oneven="window.alert(event.detail)"></my-el>
```

<div align="right">

[**▷ TRY IT**](https://codepen.io/lorean_victor/pen/GRwEwNb?editors=1010)

</div>

<br>

### onConnected

```ts
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "minicomp",
"version": "0.3.5",
"version": "0.3.6",
"description": "Minimalistic library for creating Web Components",
"main": "dist/commonjs/index.js",
"module": "dist/es/index.js",
Expand Down
10 changes: 9 additions & 1 deletion sample/component.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { define } from '../src'
import { define, useDispatch } from '../src'
import { ref, template } from 'rehtm'

define('a-button', () => {
Expand All @@ -13,3 +13,11 @@ define('a-button', () => {
})

define('b-stuff', () => template`<div>Hellow <slot></slot></div>`)

define('c-thingy', () => {
let count = 0
const dispatch = useDispatch('evenclick')
const click = () => ++count % 2 === 0 && dispatch(count)

return template`<button onclick=${click}>Click ME!</button>`
})
4 changes: 3 additions & 1 deletion sample/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@
<template shadowrootmode="open"><button>Server <span role="status">0</span></button></template>
</a-button>

<b-stuff>World</b-stuff>
<b-stuff>World</b-stuff>

<!-- <c-thingy onevenclick="console.log(event.detail)"></c-thingy> -->
7 changes: 7 additions & 0 deletions sample/index.js
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
import './component'


import { html } from 'rehtm'


document.body.innerHTML += '<c-thingy onevenclick="console.log(`clicked!`)"></c-thingy>'
document.body.appendChild(html`<c-thingy onevenclick=${() => console.log('clicked!')}></c-thingy>`)
26 changes: 26 additions & 0 deletions src/dispatch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { currentNode, ownerDocument } from './hooks'
import { onAttribute } from './attribute'


export function useDispatch<D = any>(name: string, options: EventInit = {}) {
const window = ownerDocument()!.defaultView!
const current = currentNode()!

let listener: EventListener | undefined
onAttribute('on' + name, (value) => {
if (listener) {
current.removeEventListener(name, listener)
listener = undefined
}

if (typeof value === 'function') {
listener = value
current.addEventListener(name, value)
} else if (typeof value === 'string') {
listener = new Function('event', value) as EventListener
current.addEventListener(name, listener)
}
})

return (detail: D) => current.dispatchEvent(new window.CustomEvent(name, { ...options, detail }))
}
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ export * from './cleanup'
export * from './attribute'
export * from './on'
export * from './property'
export * from './dispatch'

2 changes: 2 additions & 0 deletions src/test/exports.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
onPropertyChanged, currentNode, hooksMeta, ownerDocument,
ATTRIBUTE_REMOVED, onAttribute, onProperty, onCleanup, on,
component, FunctionalComponent, ClassBasedComponent,
useDispatch,
} from '../index'


Expand All @@ -27,6 +28,7 @@ test('stuff are exported properly.', () => {
expect(onProperty).toBeDefined()
expect(onCleanup).toBeDefined()
expect(on).toBeDefined()
expect(useDispatch).toBeDefined()

expect(component).toBeDefined()
expect(<FunctionalComponent>{}).toBeDefined()
Expand Down
57 changes: 57 additions & 0 deletions src/test/hooks/use-dispatch.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { useDispatch } from '../../dispatch'
import { on } from '../../on'
import { define } from '../../define'


describe(useDispatch, () => {
test('dispatches an event.', () => {
define('dispatch-1', () => {
const dispatch = useDispatch('test')
on('click', () => dispatch('Hello!'))

return '<div>Hi!</div>'
})

const element = document.createElement('dispatch-1')
document.body.appendChild(element)

const cb = jest.fn()

element.addEventListener('test', cb)
element.click()

expect(cb).toHaveBeenCalledWith(expect.objectContaining({ detail: 'Hello!' }))
})

test('accepts event listeners as attribute.', () => {
define('dispatch-2', () => {
const dispatch = useDispatch('test')
on('click', () => dispatch('Hello!'))

return '<div>Hi!</div>'
})

global.console.log = jest.fn()
document.body.innerHTML = '<dispatch-2 ontest="console.log(event.detail)"></dispatch-2>'

const element = document.querySelector('dispatch-2') as HTMLElement
const cb = jest.fn()

element.click()
element.click()
expect(cb).not.toHaveBeenCalled()
expect(global.console.log).toHaveBeenCalledWith('Hello!')

element.setAttribute('ontest', cb as any)
element.click()

expect(cb).toHaveBeenCalledWith(expect.objectContaining({ detail: 'Hello!' }))
expect(global.console.log).toHaveBeenCalledTimes(2)

element.removeAttribute('ontest')
element.click()

expect(cb).toHaveBeenCalledTimes(1)
expect(global.console.log).toHaveBeenCalledTimes(2)
})
})

0 comments on commit 616e2d9

Please sign in to comment.