import { LitElement, html } from 'lit'
import { createDebouncer } from '@tanstack/lit-pacer'
import { TanStackDevtoolsCore } from '@tanstack/devtools'
import { pacerDevtoolsPlugin } from '@tanstack/pacer-devtools'
class Counter extends LitElement {
static properties = {
instantCount: { state: true },
debouncedCount: { state: true },
}
instantCount = 0
debouncedCount = 0
debouncer = createDebouncer(
this,
(value: number) => {
this.debouncedCount = value
},
{
key: 'counter',
wait: 800,
enabled: () => this.instantCount > 2,
},
(state) => state,
)
increment = () => {
this.instantCount++
this.debouncer.maybeExecute(this.instantCount)
}
override createRenderRoot() {
return this
}
override render() {
return html`<div>
<h1>TanStack Pacer createDebouncer Example 1</h1>
<table>
<tbody>
<tr>
<td>Status:</td>
<td>${this.debouncer.state.status}</td>
</tr>
<tr>
<td>Execution Count:</td>
<td>${this.debouncer.state.executionCount}</td>
</tr>
<tr>
<td colspan=${2}><hr /></td>
</tr>
<tr>
<td>Instant Count:</td>
<td>${this.instantCount}</td>
</tr>
<tr>
<td>Debounced Count:</td>
<td>${this.debouncedCount}</td>
</tr>
</tbody>
</table>
<div>
<button @click=${this.increment}>Increment</button
><button
@click=${() => this.debouncer.flush()}
style="margin-left: 10px"
>
Flush
</button>
</div>
<pre style="margin-top: 20px">
${JSON.stringify(this.debouncer.state, null, 2)}</pre>
</div>`
}
}
customElements.define('pacer-counter', Counter)
class Search extends LitElement {
static properties = {
searchText: { state: true },
debouncedSearchText: { state: true },
}
searchText = ''
debouncedSearchText = ''
setSearchDebouncer = createDebouncer(
this,
(value: string) => {
this.debouncedSearchText = value
},
{
key: 'search',
wait: 500,
enabled: () => this.searchText.length > 2,
},
(state) => state,
)
handleSearchChange = (event: Event) => {
this.searchText = (event.target as HTMLInputElement).value
this.setSearchDebouncer.maybeExecute(this.searchText)
}
override createRenderRoot() {
return this
}
override render() {
return html`<div>
<h1>TanStack Pacer createDebouncer Example 2</h1>
<div>
<input
autofocus
type="search"
.value=${this.searchText}
@input=${this.handleSearchChange}
placeholder="Type to search..."
style="width: 100%; margin-bottom: 1rem"
/>
</div>
<table>
<tbody>
<tr>
<td>Is Pending:</td>
<td>${this.setSearchDebouncer.state.isPending.toString()}</td>
</tr>
<tr>
<td>Execution Count:</td>
<td>${this.setSearchDebouncer.state.executionCount}</td>
</tr>
<tr>
<td colspan=${2}><hr /></td>
</tr>
<tr>
<td>Instant Search:</td>
<td>${this.searchText}</td>
</tr>
<tr>
<td>Debounced Search:</td>
<td>${this.debouncedSearchText}</td>
</tr>
</tbody>
</table>
<div>
<button @click=${() => this.setSearchDebouncer.flush()}>Flush</button>
</div>
<pre style="margin-top: 20px">
${JSON.stringify(this.setSearchDebouncer.state, null, 2)}</pre>
</div>`
}
}
customElements.define('pacer-search', Search)
class Range extends LitElement {
static properties = {
currentValue: { state: true },
debouncedValue: { state: true },
instantExecutionCount: { state: true },
wait: { state: true },
enabled: { state: true },
}
currentValue = 50
debouncedValue = 50
instantExecutionCount = 0
wait = 250
enabled = true
setValueDebouncer = createDebouncer(
this,
(value: number) => {
this.debouncedValue = value
},
() => ({ key: 'range', wait: this.wait, enabled: this.enabled }),
(state) => state,
)
handleRangeChange = (event: Event) => {
this.currentValue = Number((event.target as HTMLInputElement).value)
this.instantExecutionCount++
this.setValueDebouncer.maybeExecute(this.currentValue)
}
override createRenderRoot() {
return this
}
override render() {
return html`<div>
<h1>TanStack Pacer createDebouncer Example 3</h1>
<fieldset>
<legend>Reactive options</legend>
<label
>Delay: ${this.wait} ms<input
type="range"
min="0"
max="1500"
step="50"
.value=${this.wait}
@input=${(event: Event) => {
this.wait = (
event.currentTarget as HTMLInputElement
).valueAsNumber
}} /></label
><label
><input
type="checkbox"
.checked=${this.enabled}
@input=${(event: Event) => {
this.enabled = (event.currentTarget as HTMLInputElement).checked
}}
/>Enabled</label
>
<p>
Changing the delay affects the next scheduled call. Disabling cancels
pending work.
</p>
</fieldset>
<div style="margin-bottom: 20px">
<label
>Current Range:<input
type="range"
min="0"
max="100"
.value=${this.currentValue}
@input=${this.handleRangeChange}
style="width: 100%"
/><span>${this.currentValue}</span></label
>
</div>
<div style="margin-bottom: 20px">
<label
>Debounced Range (Readonly):<input
type="range"
min="0"
max="100"
.value=${this.debouncedValue}
disabled
style="width: 100%"
/><span>${this.debouncedValue}</span></label
>
</div>
<table>
<tbody>
<tr>
<td>Is Pending:</td>
<td>${this.setValueDebouncer.state.isPending.toString()}</td>
</tr>
<tr>
<td>Instant Executions:</td>
<td>${this.instantExecutionCount}</td>
</tr>
<tr>
<td>Debounced Executions:</td>
<td>${this.setValueDebouncer.state.executionCount}</td>
</tr>
<tr>
<td>Saved Executions:</td>
<td>
${this.instantExecutionCount - this.setValueDebouncer.state.executionCount}
</td>
</tr>
<tr>
<td>% Reduction:</td>
<td>
${
this.instantExecutionCount === 0
? '0'
: Math.round(
((this.instantExecutionCount -
this.setValueDebouncer.state.executionCount) /
this.instantExecutionCount) *
100,
)
}%
</td>
</tr>
</tbody>
</table>
<div style="color: #666; font-size: 0.9em">
<p>Debounced to ${this.wait}ms wait time</p>
</div>
<div>
<button @click=${() => this.setValueDebouncer.flush()}>Flush</button>
</div>
<pre style="margin-top: 20px">
${JSON.stringify(this.setValueDebouncer.state, null, 2)}</pre>
</div>`
}
}
customElements.define('pacer-range', Range)
class Example extends LitElement {
private devtools?: TanStackDevtoolsCore
private target?: HTMLDivElement
override createRenderRoot() {
return this
}
override connectedCallback() {
super.connectedCallback()
if (!import.meta.env.DEV) return
this.target = document.createElement('div')
document.body.append(this.target)
this.devtools = new TanStackDevtoolsCore({
plugins: [pacerDevtoolsPlugin()],
})
this.devtools.mount(this.target)
}
override disconnectedCallback() {
this.devtools?.unmount()
this.target?.remove()
this.devtools = undefined
this.target = undefined
super.disconnectedCallback()
}
override render() {
return html`<div>
<pacer-counter></pacer-counter>
<hr />
<pacer-search></pacer-search>
<hr />
<pacer-range></pacer-range>
</div>`
}
}
customElements.define('pacer-example', Example)
document.getElementById('app')!.append(document.createElement('pacer-example'))