Skip to content

Commit 9a7b21f

Browse files
committed
docs: add throttle utility to documentation across all platforms
1 parent 661575b commit 9a7b21f

File tree

9 files changed

+230
-13
lines changed

9 files changed

+230
-13
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ KompKit provides essential utility functions that work seamlessly across Web (Ty
6161
- **📧 isEmail** - Validate email addresses with robust regex patterns
6262
- **💰 formatCurrency** - Format numbers as currency with full locale support
6363
- **📐 clamp** - Constrain a number within an inclusive [min, max] range
64+
- **⏱️ throttle** - Limit a function to execute at most once per wait period
6465

6566
### Key Features
6667

docs/ARCHITECTURE.md

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,15 @@ All modules implement identical functionality:
4646
- **isEmail**: Email validation using regex patterns
4747
- **formatCurrency**: Localized currency formatting
4848
- **clamp**: Constrain a number within an inclusive [min, max] range
49+
- **throttle**: Limit function execution to at most once per wait period
4950

5051
## API Parity Contract
5152

5253
This section defines the formal contract for cross-platform API consistency in KompKit.
5354

5455
### What is Guaranteed
5556

56-
- **Function names** are identical across all platforms (`debounce`, `isEmail`, `formatCurrency`, `clamp`).
57+
- **Function names** are identical across all platforms (`debounce`, `isEmail`, `formatCurrency`, `clamp`, `throttle`).
5758
- **Behavioral semantics** are identical: given the same inputs, all platforms produce the same observable output.
5859
- **Default values** are identical: `wait = 250ms`, `currency = "USD"`, `locale = "en-US"`.
5960
- **Error handling philosophy** is consistent: invalid inputs that cannot produce a meaningful result throw/throw-equivalent errors. Silent fallbacks are not permitted.
@@ -75,6 +76,7 @@ debounce(action, options) → Debounced<T> (with .cancel())
7576
isEmail(value) → Boolean
7677
formatCurrency(amount, options) → String
7778
clamp(value, min, max) → Number
79+
throttle(fn, wait) → Throttled<T> (with .cancel())
7880
```
7981

8082
A developer familiar with the TypeScript API should be able to use the Kotlin or Dart API with only idiomatic adjustments — not conceptual re-learning.
@@ -127,6 +129,16 @@ export function formatCurrency(
127129
): string;
128130

129131
export function clamp(value: number, min: number, max: number): number;
132+
133+
export interface Throttled<T extends (...args: any[]) => void> {
134+
(...args: Parameters<T>): void;
135+
cancel(): void;
136+
}
137+
138+
export function throttle<T extends (...args: any[]) => void>(
139+
fn: T,
140+
wait: number, // must be > 0
141+
): Throttled<T>;
130142
```
131143

132144
**Kotlin:**
@@ -152,6 +164,17 @@ fun formatCurrency(
152164
): String
153165

154166
fun clamp(value: Double, min: Double, max: Double): Double
167+
168+
class Throttled<T>(private val invoke: (T) -> Unit) {
169+
operator fun invoke(value: T): Unit
170+
fun cancel(): Unit
171+
}
172+
173+
fun <T> throttle(
174+
waitMs: Long, // must be > 0
175+
scope: CoroutineScope, // platform constraint: structured concurrency
176+
action: (T) -> Unit,
177+
): Throttled<T>
155178
```
156179

157180
**Dart:**
@@ -176,6 +199,16 @@ String formatCurrency(
176199
});
177200
178201
double clamp(double value, double min, double max);
202+
203+
class Throttled<T> {
204+
void call(T arg);
205+
void cancel();
206+
}
207+
208+
Throttled<T> throttle<T>(
209+
void Function(T) fn,
210+
Duration wait, // must be > Duration.zero
211+
);
179212
```
180213

181214
### Platform-Specific Adaptations
@@ -189,6 +222,7 @@ While maintaining API consistency, we leverage platform strengths:
189222
- **Intl.NumberFormat** for currency formatting
190223
- **RegExp** for email validation
191224
- **Math.min/Math.max** for clamp
225+
- **setTimeout/clearTimeout** for throttle timer
192226

193227
#### Kotlin Implementation
194228

@@ -197,6 +231,7 @@ While maintaining API consistency, we leverage platform strengths:
197231
- **NumberFormat/Currency** for localized formatting
198232
- **Regex** for email validation
199233
- **Double.coerceIn** for clamp
234+
- **Coroutine delay + Job** for throttle wait period
200235

201236
#### Dart/Flutter Implementation
202237

@@ -205,6 +240,7 @@ While maintaining API consistency, we leverage platform strengths:
205240
- **RegExp** for email validation
206241
- **Null safety** with full type-safe APIs
207242
- **num.clamp** for clamp
243+
- **Timer** for throttle scheduling (same as debounce)
208244

209245
## Build System Architecture
210246

@@ -284,17 +320,19 @@ android.yml:
284320
```
285321
packages/core/web/tests/
286322
├── core.test.ts # debounce, isEmail, formatCurrency tests
287-
└── clamp.test.ts # clamp unit tests
323+
├── clamp.test.ts # clamp unit tests
324+
└── throttle.test.ts # throttle unit tests
288325

289326
packages/core/android/src/test/kotlin/com/kompkit/core/
290-
└── CoreTests.kt # All utility tests (incl. ClampTests)
327+
└── CoreTests.kt # All utility tests (incl. ThrottleTests, ClampTests)
291328

292329
packages/core/flutter/test/
293330
├── kompkit_core_test.dart # Integration tests
294331
├── debounce_test.dart # Debounce unit tests
295332
├── validate_test.dart # Validation unit tests
296333
├── format_test.dart # Formatting unit tests
297-
└── clamp_test.dart # Clamp unit tests
334+
├── clamp_test.dart # Clamp unit tests
335+
└── throttle_test.dart # Throttle unit tests
298336
```
299337

300338
### Test Coverage

docs/android.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import com.kompkit.core.debounce
3131
import com.kompkit.core.isEmail
3232
import com.kompkit.core.formatCurrency
3333
import com.kompkit.core.clamp
34+
import com.kompkit.core.throttle
3435
```
3536

3637
## Usage examples
@@ -80,6 +81,23 @@ clamp(-3.0, 0.0, 10.0) // 0.0
8081
clamp(15.0, 0.0, 10.0) // 10.0
8182
```
8283

84+
### throttle
85+
86+
```kotlin
87+
import com.kompkit.core.throttle
88+
import kotlinx.coroutines.CoroutineScope
89+
import kotlinx.coroutines.Dispatchers
90+
91+
val scope = CoroutineScope(Dispatchers.Main)
92+
val onScroll = throttle<Unit>(200L, scope) {
93+
println("scroll event")
94+
}
95+
96+
onScroll(Unit) // executes immediately
97+
onScroll(Unit) // ignored within 200ms
98+
onScroll.cancel() // reset state
99+
```
100+
83101
## Jetpack Compose integration
84102

85103
```kotlin
@@ -111,7 +129,7 @@ fun SearchBox() {
111129

112130
## Notes
113131

114-
- Requires `kotlinx-coroutines-core` for the `debounce` utility.
132+
- Requires `kotlinx-coroutines-core` for the `debounce` and `throttle` utilities.
115133
- All utilities are top-level functions in the `com.kompkit.core` package.
116134
- `formatCurrency` accepts a BCP 47 locale string (e.g., `"en-US"`) — no `java.util.Locale` needed.
117135
- Compatible with Android API 21+.

docs/flutter.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,28 @@ double opacity = clamp(userInput, 0.0, 1.0);
177177
double scrollOffset = clamp(rawOffset, 0.0, maxScrollExtent);
178178
```
179179

180+
### Throttle
181+
182+
Limit a function to execute at most once per wait duration. The first call executes immediately; subsequent calls within the wait period are ignored.
183+
184+
```dart
185+
final onScroll = throttle<void>((_) {
186+
print('scroll event');
187+
}, const Duration(milliseconds: 200));
188+
189+
onScroll(null); // executes immediately
190+
onScroll(null); // ignored within 200ms
191+
onScroll.cancel(); // reset state (e.g. in dispose())
192+
```
193+
194+
Useful for scroll listeners, resize handlers, or any high-frequency event:
195+
196+
```dart
197+
final onResize = throttle<Size>((size) {
198+
setState(() => _size = size);
199+
}, const Duration(milliseconds: 100));
200+
```
201+
180202
#### Flutter Widget Example
181203

182204
```dart
@@ -330,6 +352,7 @@ KompKit Core for Flutter/Dart works on:
330352
- **Email Validation**: Compiled regex for fast validation
331353
- **Currency Formatting**: Leverages Dart's `intl` package for optimal localization
332354
- **Clamp**: Pure arithmetic — zero overhead
355+
- **Throttle**: Uses Dart's `Timer` class — same mechanism as debounce
333356

334357
## Next Steps
335358

docs/getting-started.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,12 @@ npm run test
5656

5757
## Utilities
5858

59-
| Utility | Description |
60-
| ---------------- | -------------------------------------------------------- |
61-
| `debounce` | Debounce a function call by a delay. |
62-
| `isEmail` | Validate a string with a basic email regex. |
63-
| `formatCurrency` | Format numbers into a localized currency string. |
64-
| `clamp` | Constrain a number within an inclusive [min, max] range. |
59+
| Utility | Description |
60+
| ---------------- | --------------------------------------------------------- |
61+
| `debounce` | Debounce a function call by a delay. |
62+
| `isEmail` | Validate a string with a basic email regex. |
63+
| `formatCurrency` | Format numbers into a localized currency string. |
64+
| `clamp` | Constrain a number within an inclusive [min, max] range. |
65+
| `throttle` | Limit a function to execute at most once per wait period. |
6566

6667
Next: read the detailed guides for [Web](./web.md), [Android](./android.md), [Flutter](./flutter.md), and the [Recipes](./recipes.md).

docs/recipes.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,84 @@ class _PriceDisplayState extends State<PriceDisplay> {
290290
}
291291
```
292292

293+
## Throttled scroll handler (TypeScript)
294+
295+
```ts
296+
import { throttle } from "kompkit-core";
297+
298+
const onScroll = throttle(() => {
299+
console.log("scroll position:", window.scrollY);
300+
}, 200);
301+
302+
window.addEventListener("scroll", onScroll);
303+
304+
// On cleanup:
305+
onScroll.cancel();
306+
window.removeEventListener("scroll", onScroll);
307+
```
308+
309+
## Throttled event handler (Kotlin)
310+
311+
```kotlin
312+
import com.kompkit.core.throttle
313+
import kotlinx.coroutines.CoroutineScope
314+
import kotlinx.coroutines.Dispatchers
315+
316+
val scope = CoroutineScope(Dispatchers.Main)
317+
val onSensorUpdate = throttle<Float>(100L, scope) { value ->
318+
updateUI(value)
319+
}
320+
321+
// In sensor callback:
322+
onSensorUpdate(sensorValue)
323+
324+
// On cleanup:
325+
onSensorUpdate.cancel()
326+
```
327+
328+
## Throttled scroll listener (Flutter)
329+
330+
```dart
331+
import 'package:flutter/material.dart';
332+
import 'package:kompkit_core/kompkit_core.dart';
333+
334+
class ScrollTracker extends StatefulWidget {
335+
@override
336+
_ScrollTrackerState createState() => _ScrollTrackerState();
337+
}
338+
339+
class _ScrollTrackerState extends State<ScrollTracker> {
340+
final _controller = ScrollController();
341+
late final Throttled<double> _throttledScroll;
342+
343+
@override
344+
void initState() {
345+
super.initState();
346+
_throttledScroll = throttle<double>(
347+
(offset) => print('scroll: $offset'),
348+
const Duration(milliseconds: 200),
349+
);
350+
_controller.addListener(() => _throttledScroll(_controller.offset));
351+
}
352+
353+
@override
354+
void dispose() {
355+
_throttledScroll.cancel();
356+
_controller.dispose();
357+
super.dispose();
358+
}
359+
360+
@override
361+
Widget build(BuildContext context) {
362+
return ListView.builder(
363+
controller: _controller,
364+
itemCount: 100,
365+
itemBuilder: (_, i) => ListTile(title: Text('Item $i')),
366+
);
367+
}
368+
}
369+
```
370+
293371
## Clamping a slider value (TypeScript / React)
294372

295373
```tsx

docs/web.md

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,25 @@ npm i kompkit-core
1515
ESM:
1616

1717
```ts
18-
import { debounce, isEmail, formatCurrency, clamp } from "kompkit-core";
18+
import {
19+
debounce,
20+
isEmail,
21+
formatCurrency,
22+
clamp,
23+
throttle,
24+
} from "kompkit-core";
1925
```
2026

2127
CommonJS:
2228

2329
```js
24-
const { debounce, isEmail, formatCurrency, clamp } = require("kompkit-core");
30+
const {
31+
debounce,
32+
isEmail,
33+
formatCurrency,
34+
clamp,
35+
throttle,
36+
} = require("kompkit-core");
2537
```
2638

2739
## Usage examples
@@ -68,6 +80,19 @@ clamp(-3, 0, 10); // 0
6880
clamp(15, 0, 10); // 10
6981
```
7082

83+
### throttle
84+
85+
```ts
86+
import { throttle } from "kompkit-core";
87+
88+
const onScroll = throttle(() => {
89+
console.log("scroll");
90+
}, 200);
91+
92+
window.addEventListener("scroll", onScroll);
93+
onScroll.cancel(); // reset state (e.g. on unmount)
94+
```
95+
7196
## React snippet
7297

7398
```tsx

packages/core/flutter/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ print(formatCurrency(1234.56)); // "$1,234.56" (en-US / USD default)
3030

3131
// Clamp a value
3232
print(clamp(15.0, 0.0, 10.0)); // 10.0
33+
34+
// Throttle a function
35+
final onScroll = throttle<double>((offset) => print(offset),
36+
const Duration(milliseconds: 200));
37+
onScroll.cancel(); // reset state
3338
```
3439

3540
## Documentation

0 commit comments

Comments
 (0)