a post-component library for building user-interfaces on the web.

add `on` directive (#137)

authored by tombl.dev and committed by

GitHub 113aff61 528450c6

+139 -9
+1 -1
src/client.ts
··· 1 1 export { invalidate, keyed, onMount, onUnmount } from './client/controller.ts' 2 - export { attr_directive as attr, type Directive } from './client/parts.ts' 2 + export { attr_directive as attr, on_directive as on, type Directive } from './client/parts.ts' 3 3 export { createRoot, hydrate, type Root } from './client/root.ts'
+14 -7
src/client/parts.ts
··· 284 284 } 285 285 286 286 export function create_attribute_part(node: Element, name: string): Part { 287 - return value => { 288 - set_attr(node, name, value) 289 - } 287 + return value => set_attr(node, name, value) 290 288 } 291 289 292 - export type Directive = (node: Element) => Cleanup 290 + export type Directive = (el: Element) => Cleanup 293 291 294 292 export function create_directive_part(node: Node): Part { 295 293 let cleanup: Cleanup ··· 310 308 export function attr_directive(name: string, value: string | boolean | null | undefined): Directive { 311 309 return el => { 312 310 set_attr(el, name, value) 313 - return () => { 314 - set_attr(el, name, null) 315 - } 311 + return () => set_attr(el, name, null) 312 + } 313 + } 314 + 315 + export function on_directive( 316 + type: string, 317 + listener: EventListenerOrEventListenerObject, 318 + options?: boolean | AddEventListenerOptions, 319 + ): Directive { 320 + return el => { 321 + el.addEventListener(type, listener, options) 322 + return () => el.removeEventListener(type, listener, options) 316 323 } 317 324 }
+124 -1
src/client/tests/directives.test.ts
··· 1 1 import { html } from 'dhtml' 2 - import { attr, type Directive } from 'dhtml/client' 2 + import { attr, on, type Directive } from 'dhtml/client' 3 3 import { assert, assert_eq, test } from '../../../scripts/test/test.ts' 4 4 import { setup } from './setup.ts' 5 5 ··· 93 93 root.render(template(false)) 94 94 assert_eq(el.querySelector('input')!.disabled, false) 95 95 }) 96 + 97 + test('on directive works correctly', () => { 98 + const { root, el } = setup() 99 + let count = 0 100 + let event: Event 101 + 102 + const template = (handler: EventListener | null) => html` 103 + <button ${handler ? on('click', handler) : null}>Click me</button> 104 + ` 105 + 106 + root.render( 107 + template(e => { 108 + count++ 109 + event = e 110 + }), 111 + ) 112 + const button = el.querySelector('button')! 113 + 114 + button.click() 115 + assert_eq(count, 1) 116 + assert(event! instanceof Event) 117 + assert_eq(event.type, 'click') 118 + 119 + button.click() 120 + assert_eq(count, 2) 121 + 122 + root.render(template(null)) 123 + button.click() 124 + assert_eq(count, 2) 125 + }) 126 + 127 + test('on directive handles event listener updates', () => { 128 + const { root, el } = setup() 129 + let count1 = 0 130 + let count2 = 0 131 + 132 + const template = (handler: EventListener) => html`<button ${on('click', handler)}>Click me</button>` 133 + 134 + root.render( 135 + template(() => { 136 + count1++ 137 + }), 138 + ) 139 + const button = el.querySelector('button')! 140 + 141 + button.click() 142 + assert_eq(count1, 1) 143 + assert_eq(count2, 0) 144 + 145 + root.render( 146 + template(() => { 147 + count2++ 148 + }), 149 + ) 150 + button.click() 151 + assert_eq(count1, 1) 152 + assert_eq(count2, 1) 153 + }) 154 + 155 + test('on directive supports different event types', () => { 156 + const { root, el } = setup() 157 + let enter_count = 0 158 + let leave_count = 0 159 + 160 + const template = () => html` 161 + <div 162 + ${on('mouseenter', () => { 163 + enter_count++ 164 + })} 165 + ${on('mouseleave', () => { 166 + leave_count++ 167 + })} 168 + > 169 + Hover me 170 + </div> 171 + ` 172 + 173 + root.render(template()) 174 + const div = el.querySelector('div')! 175 + 176 + div.dispatchEvent(new MouseEvent('mouseenter')) 177 + assert_eq(enter_count, 1) 178 + assert_eq(leave_count, 0) 179 + 180 + div.dispatchEvent(new MouseEvent('mouseleave')) 181 + assert_eq(enter_count, 1) 182 + assert_eq(leave_count, 1) 183 + }) 184 + 185 + test('on directive supports event listener options', () => { 186 + const { root, el } = setup() 187 + let count = 0 188 + 189 + const template = (options?: AddEventListenerOptions) => html` 190 + <button 191 + ${on( 192 + 'click', 193 + () => { 194 + count++ 195 + }, 196 + options, 197 + )} 198 + > 199 + Click me 200 + </button> 201 + ` 202 + 203 + root.render(template({ once: true })) 204 + const button = el.querySelector('button')! 205 + 206 + button.click() 207 + assert_eq(count, 1) 208 + 209 + button.click() 210 + assert_eq(count, 1) 211 + 212 + root.render(template()) 213 + button.click() 214 + assert_eq(count, 2) 215 + 216 + button.click() 217 + assert_eq(count, 3) 218 + })