@reykjavik/webtools 0.2.7 → 0.2.9

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/CHANGELOG.md CHANGED
@@ -4,6 +4,23 @@
4
4
 
5
5
  - ... <!-- Add new lines here. -->
6
6
 
7
+ ## 0.2.9
8
+
9
+ _2025-09-30_
10
+
11
+ - `@reykjavik/webtools/next/vanillaExtract`:
12
+ - feat: Make `vanillaVars` return a type-safe `setVars()` helper to avoid
13
+ offending VSCode's CSS syntax parser too much.
14
+
15
+ ## 0.2.8
16
+
17
+ _2025-09-17_
18
+
19
+ - `@reykjavik/webtools/async`:
20
+ - feat: Add `debounce` and `throttle` helpers
21
+ - `@reykjavik/webtools/hooks` (new module):
22
+ - feat: Add `useDebounced` and `useThrottled` hooks
23
+
7
24
  ## 0.2.7
8
25
 
9
26
  _2025-09-01_
package/README.md CHANGED
@@ -29,6 +29,11 @@ bun add @reykjavik/webtools
29
29
  - [`@reykjavik/webtools/async`](#reykjavikwebtoolsasync)
30
30
  - [`promiseAllObject`](#promiseallobject)
31
31
  - [`maxWait`](#maxwait)
32
+ - [`debounce`](#debounce)
33
+ - [`throttle`](#throttle)
34
+ - [`@reykjavik/webtools/hoooks`](#reykjavikwebtoolshoooks)
35
+ - [`useDebounced`](#usedebounced)
36
+ - [`useThrottled`](#usethrottled)
32
37
  - [`@reykjavik/webtools/errorhandling`](#reykjavikwebtoolserrorhandling)
33
38
  - [`asError`](#aserror)
34
39
  - [`Result` Singleton](#result-singleton)
@@ -369,6 +374,188 @@ console.log(posts?.reason); // undefined | unknown
369
374
 
370
375
  ---
371
376
 
377
+ ### `debounce`
378
+
379
+ **Syntax:**
380
+ `debounce<A extends Array<unknown>>(func: (...args: A) => void, delay: number, immediate?: boolean): ((...args: A) => void) & { cancel: (finish?: boolean) => void; }`
381
+
382
+ Returns a debounced function that only runs after `delay` milliseconds of
383
+ quiet-time, and can optionally be made to run `immediate`ly on first call
384
+ before dbouncing subsequent calls.
385
+
386
+ ```ts
387
+ import { debounce } from '@reykjavik/webtools/async';
388
+
389
+ // Basic usage:
390
+ const sayHello = debounce((namme: string) => {
391
+ console.log('Hello ' + name);
392
+ }, 200);
393
+
394
+ sayHello('Alice');
395
+ sayHello('Bob');
396
+ sayHello('Charlie');
397
+ sayHello('Dorothy');
398
+ // Only "Hello Dorothy" is logged, 200ms after the last call
399
+
400
+ // With `immediate` param set to true:
401
+ const sayHi = debounce(
402
+ (namme: string) => console.log('Hi ' + name),
403
+ 200,
404
+ true
405
+ );
406
+ sayHi('Alice');
407
+ sayHi('Bob');
408
+ sayHi('Charlie');
409
+ sayHi('Dorothy');
410
+ // "Hi Alice" is logged immediately
411
+ // Then "Hi Dorothy" is logged, 200ms after the last call
412
+ ```
413
+
414
+ The returned function has a nice `.cancel()` method, which can optionally
415
+ invoke the function before cancelling, if it had a debounce pending.
416
+
417
+ ```ts
418
+ sayHello('Erica');
419
+ sayHello('Fiona');
420
+ sayHello.cancel();
421
+ // Nothing is logged because the debounce was cancelled
422
+
423
+ sayHello('George');
424
+ sayHello('Harold');
425
+ sayHello.cancel(true); // `finish` parmeter is true
426
+ // "Hello Harold" is logged immediately because it was pending
427
+ ```
428
+
429
+ ---
430
+
431
+ ### `throttle`
432
+
433
+ **Syntax:**
434
+ `throttle<A extends Array<unknown>>(func: (...args: A) => void, delay: number, skipFirst?: boolean): ((...args: A) => void) & { finish: (cancel?: boolean) => void; }`
435
+
436
+ Returns a throttled function that never runs more often than every `delay`
437
+ milliseconds. It can optionally made to `skipFirst` invocation.
438
+
439
+ ```ts
440
+ import { throttle } from '@reykjavik/webtools/async';
441
+
442
+ // Basic usage:
443
+ const sayHello = throttle((name: string) => {
444
+ console.log('Hello ' + name);
445
+ }, 200);
446
+
447
+ sayHello('Alice');
448
+ sayHello('Bob');
449
+ sayHello('Charlie');
450
+ sayHello('Dorothy');
451
+ // Only "Hello Alice" is logged immediately. The other calls were throttled.
452
+
453
+ // With `skipFirst` param set to true:
454
+ const sayHi = throttle(
455
+ (name: string) => console.log('Hi ' + name),
456
+ 200,
457
+ true
458
+ );
459
+ sayHi('Alice');
460
+ sayHi('Bob');
461
+ sayHi('Charlie');
462
+ sayHi('Dorothy');
463
+ // Nothing is logged. The first call was skipped, and the rest were throttled.
464
+ ```
465
+
466
+ The returned function also has a nice `.finish()` method to reset the throttle
467
+ timer. By default it instantly invokes the function, if the last call was
468
+ throttled (skipped)-.
469
+
470
+ ```ts
471
+ sayHello('Erica');
472
+ sayHello('Fiona');
473
+ sayHello.finish();
474
+ // "Hello Fiona" is logged immediately because it was pending
475
+
476
+ sayHello('George');
477
+ sayHello('Harold');
478
+ sayHello.finish(true); // `cancel` parmeter is true
479
+ // Nothing is logged because the pending call was cancelled
480
+ ```
481
+
482
+ ---
483
+
484
+ ## `@reykjavik/webtools/hoooks`
485
+
486
+ Some useful React hooks.
487
+
488
+ ### `useDebounced`
489
+
490
+ **Syntax:**
491
+ `useDebounced<A extends Array<unknown>>(func: (...args: A) => void, delay: number, immediate?: boolean): ((...args: A) => void) & { cancel: (finish?: boolean) => void; }`
492
+
493
+ Returns a stable debounced function that invokes the supplied function after
494
+ the specified delay. When the component unmounts, any pending (debounced)
495
+ calls are automatically cancelled.
496
+
497
+ **NOTE:** The supplied callback does not need to be memoized. The debouncer
498
+ will always invoke the last supplied version.
499
+
500
+ ```ts
501
+ import { useDebounced } from '@reykjavik/webtools/hoooks';
502
+
503
+ const MyComponent = () => {
504
+ const renderDate = new Date();
505
+ const debouncedSearch = useDebounced((query: string) => {
506
+ console.log('Searching for:', query, 'at', renderDate.toISOString());
507
+ }, 200);
508
+ return (
509
+ <input
510
+ type="text"
511
+ onChange={(e) => {
512
+ debouncedSearch(e.currentTarget.value);
513
+ }}
514
+ />
515
+ );
516
+ };
517
+ ```
518
+
519
+ See [`debounce`](#debounce) for more details about the parameters and the
520
+ returned debounced function's `.cancel()` method.
521
+
522
+ ### `useThrottled`
523
+
524
+ **Syntax:**
525
+ `useThrottled<A extends Array<unknown>>(func: (...args: A) => void, delay: number, skipFirst?: boolean): ((...args: A) => void) & { finish: (cancel?: boolean) => void; }`
526
+
527
+ Returns a stable throttler function that throttles the supplied function.
528
+
529
+ **NOTE:** The supplied callback does not need to be memoized. The throttler
530
+ will always invoke the last supplied version.
531
+
532
+ ```ts
533
+ import { useThrottled } from '@reykjavik/webtools/hoooks';
534
+
535
+ const MyComponent = () => {
536
+ const renderDate = new Date();
537
+ const throttledReportPosition = useThrottled((x: number, y: number) => {
538
+ console.log('Mouse position:', x, ',', y, 'at', renderDate.toISOString());
539
+ }, 200);
540
+
541
+ return (
542
+ <div
543
+ style={{ width: '300px', height: '300px', background: '#eee' }}
544
+ onMouseMove={(e) => {
545
+ throttledReportPosition(e.clientX, e.clientY);
546
+ }}
547
+ >
548
+ Move your mouse here.
549
+ </div>
550
+ );
551
+ };
552
+ ```
553
+
554
+ See [`throttle`](#throttle) for more details about the parameters and the
555
+ returned throttled function's `.finish()` method.
556
+
557
+ ---
558
+
372
559
  ## `@reykjavik/webtools/errorhandling`
373
560
 
374
561
  A small set of lightweight tools for handling errors and promises in a safer,
@@ -660,9 +847,11 @@ editor), but there's a brief summary:
660
847
  `scriptUrl` prop).
661
848
  - `scriptUrl?: string` — The full SiteImprove analytics script URL.
662
849
  (alternative to `accountId` prop).
663
- - `hasConsented?: boolean` — Manual GDPR 'analytics' consent flag. Allows hard
664
- opt-out, but defers to [`CookieHubProvider` values](#usecookiehubconsent) if
665
- they are available.
850
+ - `hasConsented?: boolean` — Manual GDPR 'analytics' consent flag. A `false`
851
+ value allows hard opt-out, but defers to
852
+ [`CookieHubProvider` values](#usecookiehubconsent) if they are available.
853
+ Defaults to `undefined` which means "ask CookieHub if available, otherwise
854
+ no".
666
855
  - `onLoad?: (e: unknown) => void` — Fires when the script has loaded.
667
856
  - `onError?: (e: unknown) => void` — Fires if loading the script failed.
668
857
 
@@ -999,21 +1188,21 @@ import {
999
1188
  vanillaGlobal,
1000
1189
  } from '@reykjavik/webtools/vanillaExtract';
1001
1190
 
1002
- export const { varPrimaryColor, varSecondaryColor } = vanillaVars(
1191
+ const { varPrimaryColor, varSecondaryColor, setVars } = vanillaVars(
1003
1192
  'primaryColor',
1004
1193
  'secondaryColor'
1005
1194
  );
1006
1195
 
1007
- vanillaGlobal(`
1008
- :root {
1009
- ${varPrimaryColor}: #ff0000;
1010
- ${varSecondaryColor}: #00ff00;
1011
- }
1012
- body {
1013
- background-color: var(${varPrimaryColor});
1014
- color: var(${varSecondaryColor});
1015
- }
1016
- `);
1196
+ export { varPrimaryColor, varSecondaryColor };
1197
+
1198
+ export const wrapper = vanillaClass(`
1199
+ ${setVars({
1200
+ primaryColor: '#ff0000',
1201
+ secondaryColor: '#00ff00',
1202
+ })}
1203
+ background-color: var(${varPrimaryColor});
1204
+ color: var(${varSecondaryColor});
1205
+ `);
1017
1206
  ```
1018
1207
 
1019
1208
  …and then in your component:
@@ -1026,6 +1215,7 @@ import * as cl from './someFile.css.ts';
1026
1215
  export function MyComponent() {
1027
1216
  return (
1028
1217
  <div
1218
+ className={cl.wrapper}
1029
1219
  style={{
1030
1220
  [cl.varPrimaryColor]: 'yellow',
1031
1221
  [cl.varSecondaryColor]: 'blue',
package/async.d.ts CHANGED
@@ -31,4 +31,43 @@ export declare function maxWait<PromiseMap extends PlainObj>(timeout: number, pr
31
31
  * @see https://github.com/reykjavikcity/webtools/blob/v0.2/README.md#promiseallobject
32
32
  */
33
33
  export declare const promiseAllObject: <T extends PlainObj>(promisesMap: T) => Promise<{ -readonly [K in keyof T]: Awaited<T[K]>; }>;
34
+ type Cancellable<A extends Array<unknown>> = ((...args: A) => void) & {
35
+ /**
36
+ * Cancels any pending invocation of the debounced function. \
37
+ * If `finish` is true and if a debounce is pending, the function is invoked
38
+ * before cancelling.
39
+ */
40
+ cancel(finish?: boolean): void;
41
+ };
42
+ /**
43
+ * Returns a debounced function that only runs after `delay` milliseconds
44
+ * of quiet-time. \
45
+ * The returned function also has a nice `.cancel()` method.
46
+ *
47
+ * @see https://github.com/reykjavikcity/webtools/blob/v0.2/README.md#debounce
48
+ */
49
+ export declare const debounce: {
50
+ <A extends Array<unknown>>(func: (...args: A) => void, delay: number, immediate?: boolean): Cancellable<A>;
51
+ d(delay: number, immediate?: boolean): Cancellable<[fn: (...args: Array<any>) => void, ...args: any[]]>;
52
+ };
53
+ type Finishable<A extends Array<unknown>> = ((...args: A) => void) & {
54
+ /**
55
+ * Immediately invokes the function if there is a pending throttle and its last
56
+ * invoication was throttled (cancelled). \
57
+ * If `cancel` is true then only the timer is reset without invoking function.
58
+ */
59
+ finish(cancel?: boolean): void;
60
+ };
61
+ /**
62
+ * Returns a throttled function that never runs more often than
63
+ * every `delay` milliseconds. \
64
+ * The returned function also has a nice `.finish()` method to reset the
65
+ * throttle timer
66
+ *
67
+ * @see https://github.com/reykjavikcity/webtools/blob/v0.2/README.md#throttle
68
+ */
69
+ export declare const throttle: {
70
+ <A extends Array<unknown>>(func: (...args: A) => void, delay: number, skipFirst?: boolean): Finishable<A>;
71
+ d(delay: number, skipFirst?: boolean): Finishable<[fn: (...args: Array<any>) => void, ...args: any[]]>;
72
+ };
34
73
  export {};
package/async.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.promiseAllObject = exports.addLag = exports.sleep = void 0;
3
+ exports.throttle = exports.debounce = exports.promiseAllObject = exports.addLag = exports.sleep = void 0;
4
4
  exports.maxWait = maxWait;
5
5
  /**
6
6
  * Simple sleep function. Returns a promise that resolves after `length`
@@ -79,3 +79,107 @@ const promiseAllObject = (promisesMap) => Promise.all(Object.values(promisesMap)
79
79
  return resolvedMap;
80
80
  });
81
81
  exports.promiseAllObject = promiseAllObject;
82
+ /**
83
+ * Returns a debounced function that only runs after `delay` milliseconds
84
+ * of quiet-time. \
85
+ * The returned function also has a nice `.cancel()` method.
86
+ *
87
+ * @see https://github.com/reykjavikcity/webtools/blob/v0.2/README.md#debounce
88
+ */
89
+ /*#__NO_SIDE_EFFECTS__*/
90
+ const debounce = (
91
+ /** The function to debounce */
92
+ func,
93
+ /** The delay, in milliseconds, to wait before running the function */
94
+ delay,
95
+ /** Whether to run the function at the start of the delay instead of the end */
96
+ immediate) => {
97
+ let timeout;
98
+ let _args;
99
+ let _this;
100
+ const debouncedFn = function (...args) {
101
+ _args = args;
102
+ _this = this;
103
+ immediate && !timeout && func.apply(_this, _args);
104
+ timeout && clearTimeout(timeout);
105
+ timeout = setTimeout(() => {
106
+ debouncedFn.cancel(true);
107
+ }, delay);
108
+ };
109
+ debouncedFn.cancel = (finish) => {
110
+ timeout && clearTimeout(timeout);
111
+ finish && timeout && func.apply(_this, _args);
112
+ timeout = undefined;
113
+ };
114
+ return debouncedFn;
115
+ };
116
+ exports.debounce = debounce;
117
+ /**
118
+ * Sugar to produce a dynamic debounced function that accepts its contents/behavior at call time.
119
+ *
120
+ * Usage:
121
+ * ```ts
122
+ * const myDebouncer = debounce.d(500);
123
+ * myDebouncer(() => { alert('Hello world'); });
124
+ * myDebouncer(() => { alert('I mean: Howdy world!'); });
125
+ * myDebouncer((name) => { alert('Wazzap ' + name); }, 'world');
126
+ * ```
127
+ *
128
+ * Not documented in README as its usefulness is still uncertain.
129
+ */
130
+ exports.debounce.d = (delay, immediate) => (0, exports.debounce)(function (fn, ...args) {
131
+ fn.apply(this, args);
132
+ }, delay, immediate);
133
+ /**
134
+ * Returns a throttled function that never runs more often than
135
+ * every `delay` milliseconds. \
136
+ * The returned function also has a nice `.finish()` method to reset the
137
+ * throttle timer
138
+ *
139
+ * @see https://github.com/reykjavikcity/webtools/blob/v0.2/README.md#throttle
140
+ */
141
+ /*#__NO_SIDE_EFFECTS__*/
142
+ const throttle = (
143
+ /** The function to throttle */
144
+ func,
145
+ /** The delay, in milliseconds, to wait between invocations */
146
+ delay,
147
+ /** Whether to skip the first invocation instead of running it immediately */
148
+ skipFirst) => {
149
+ let timeout;
150
+ let throttled = 0;
151
+ let _args;
152
+ let _this;
153
+ const throttledFn = function (...args) {
154
+ _args = args;
155
+ _this = this;
156
+ if (!throttled) {
157
+ skipFirst ? throttled++ : func.apply(_this, _args);
158
+ timeout = setTimeout(throttledFn.finish, delay); // Go home TypeScript, you're drunk!
159
+ }
160
+ throttled++;
161
+ };
162
+ throttledFn.finish = (cancel) => {
163
+ timeout && clearTimeout(timeout);
164
+ !cancel && throttled && func.apply(_this, _args);
165
+ throttled = 0;
166
+ };
167
+ return throttledFn;
168
+ };
169
+ exports.throttle = throttle;
170
+ /**
171
+ * Sugar to produce a dynamic debounced function that accepts its contents/behavior at call time.
172
+ *
173
+ * Usage:
174
+ * ```ts
175
+ * const myThrottler = throttle.d(500);
176
+ * myThrottler(() => { alert('Hello world'); });
177
+ * myThrottler(() => { alert('I mean: Howdy world!'); });
178
+ * myThrottler((name) => { alert('Wazzap ' + name); }, 'world');
179
+ * ```
180
+ *
181
+ * Not documented in README as its usefulness is still uncertain.
182
+ */
183
+ exports.throttle.d = (delay, skipFirst) => (0, exports.throttle)(function (fn, ...args) {
184
+ fn.apply(this, args);
185
+ }, delay, skipFirst);
package/esm/async.d.ts CHANGED
@@ -31,4 +31,43 @@ export declare function maxWait<PromiseMap extends PlainObj>(timeout: number, pr
31
31
  * @see https://github.com/reykjavikcity/webtools/blob/v0.2/README.md#promiseallobject
32
32
  */
33
33
  export declare const promiseAllObject: <T extends PlainObj>(promisesMap: T) => Promise<{ -readonly [K in keyof T]: Awaited<T[K]>; }>;
34
+ type Cancellable<A extends Array<unknown>> = ((...args: A) => void) & {
35
+ /**
36
+ * Cancels any pending invocation of the debounced function. \
37
+ * If `finish` is true and if a debounce is pending, the function is invoked
38
+ * before cancelling.
39
+ */
40
+ cancel(finish?: boolean): void;
41
+ };
42
+ /**
43
+ * Returns a debounced function that only runs after `delay` milliseconds
44
+ * of quiet-time. \
45
+ * The returned function also has a nice `.cancel()` method.
46
+ *
47
+ * @see https://github.com/reykjavikcity/webtools/blob/v0.2/README.md#debounce
48
+ */
49
+ export declare const debounce: {
50
+ <A extends Array<unknown>>(func: (...args: A) => void, delay: number, immediate?: boolean): Cancellable<A>;
51
+ d(delay: number, immediate?: boolean): Cancellable<[fn: (...args: Array<any>) => void, ...args: any[]]>;
52
+ };
53
+ type Finishable<A extends Array<unknown>> = ((...args: A) => void) & {
54
+ /**
55
+ * Immediately invokes the function if there is a pending throttle and its last
56
+ * invoication was throttled (cancelled). \
57
+ * If `cancel` is true then only the timer is reset without invoking function.
58
+ */
59
+ finish(cancel?: boolean): void;
60
+ };
61
+ /**
62
+ * Returns a throttled function that never runs more often than
63
+ * every `delay` milliseconds. \
64
+ * The returned function also has a nice `.finish()` method to reset the
65
+ * throttle timer
66
+ *
67
+ * @see https://github.com/reykjavikcity/webtools/blob/v0.2/README.md#throttle
68
+ */
69
+ export declare const throttle: {
70
+ <A extends Array<unknown>>(func: (...args: A) => void, delay: number, skipFirst?: boolean): Finishable<A>;
71
+ d(delay: number, skipFirst?: boolean): Finishable<[fn: (...args: Array<any>) => void, ...args: any[]]>;
72
+ };
34
73
  export {};
package/esm/async.js CHANGED
@@ -72,3 +72,105 @@ export const promiseAllObject = (promisesMap) => Promise.all(Object.values(promi
72
72
  }
73
73
  return resolvedMap;
74
74
  });
75
+ /**
76
+ * Returns a debounced function that only runs after `delay` milliseconds
77
+ * of quiet-time. \
78
+ * The returned function also has a nice `.cancel()` method.
79
+ *
80
+ * @see https://github.com/reykjavikcity/webtools/blob/v0.2/README.md#debounce
81
+ */
82
+ /*#__NO_SIDE_EFFECTS__*/
83
+ export const debounce = (
84
+ /** The function to debounce */
85
+ func,
86
+ /** The delay, in milliseconds, to wait before running the function */
87
+ delay,
88
+ /** Whether to run the function at the start of the delay instead of the end */
89
+ immediate) => {
90
+ let timeout;
91
+ let _args;
92
+ let _this;
93
+ const debouncedFn = function (...args) {
94
+ _args = args;
95
+ _this = this;
96
+ immediate && !timeout && func.apply(_this, _args);
97
+ timeout && clearTimeout(timeout);
98
+ timeout = setTimeout(() => {
99
+ debouncedFn.cancel(true);
100
+ }, delay);
101
+ };
102
+ debouncedFn.cancel = (finish) => {
103
+ timeout && clearTimeout(timeout);
104
+ finish && timeout && func.apply(_this, _args);
105
+ timeout = undefined;
106
+ };
107
+ return debouncedFn;
108
+ };
109
+ /**
110
+ * Sugar to produce a dynamic debounced function that accepts its contents/behavior at call time.
111
+ *
112
+ * Usage:
113
+ * ```ts
114
+ * const myDebouncer = debounce.d(500);
115
+ * myDebouncer(() => { alert('Hello world'); });
116
+ * myDebouncer(() => { alert('I mean: Howdy world!'); });
117
+ * myDebouncer((name) => { alert('Wazzap ' + name); }, 'world');
118
+ * ```
119
+ *
120
+ * Not documented in README as its usefulness is still uncertain.
121
+ */
122
+ debounce.d = (delay, immediate) => debounce(function (fn, ...args) {
123
+ fn.apply(this, args);
124
+ }, delay, immediate);
125
+ /**
126
+ * Returns a throttled function that never runs more often than
127
+ * every `delay` milliseconds. \
128
+ * The returned function also has a nice `.finish()` method to reset the
129
+ * throttle timer
130
+ *
131
+ * @see https://github.com/reykjavikcity/webtools/blob/v0.2/README.md#throttle
132
+ */
133
+ /*#__NO_SIDE_EFFECTS__*/
134
+ export const throttle = (
135
+ /** The function to throttle */
136
+ func,
137
+ /** The delay, in milliseconds, to wait between invocations */
138
+ delay,
139
+ /** Whether to skip the first invocation instead of running it immediately */
140
+ skipFirst) => {
141
+ let timeout;
142
+ let throttled = 0;
143
+ let _args;
144
+ let _this;
145
+ const throttledFn = function (...args) {
146
+ _args = args;
147
+ _this = this;
148
+ if (!throttled) {
149
+ skipFirst ? throttled++ : func.apply(_this, _args);
150
+ timeout = setTimeout(throttledFn.finish, delay); // Go home TypeScript, you're drunk!
151
+ }
152
+ throttled++;
153
+ };
154
+ throttledFn.finish = (cancel) => {
155
+ timeout && clearTimeout(timeout);
156
+ !cancel && throttled && func.apply(_this, _args);
157
+ throttled = 0;
158
+ };
159
+ return throttledFn;
160
+ };
161
+ /**
162
+ * Sugar to produce a dynamic debounced function that accepts its contents/behavior at call time.
163
+ *
164
+ * Usage:
165
+ * ```ts
166
+ * const myThrottler = throttle.d(500);
167
+ * myThrottler(() => { alert('Hello world'); });
168
+ * myThrottler(() => { alert('I mean: Howdy world!'); });
169
+ * myThrottler((name) => { alert('Wazzap ' + name); }, 'world');
170
+ * ```
171
+ *
172
+ * Not documented in README as its usefulness is still uncertain.
173
+ */
174
+ throttle.d = (delay, skipFirst) => throttle(function (fn, ...args) {
175
+ fn.apply(this, args);
176
+ }, delay, skipFirst);
package/esm/hooks.d.ts ADDED
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Returns a stable debounced function that invokes the supplied function
3
+ * after the specified delay. \
4
+ * When the component unmounts, any pending (debounced) calls are automatically cancelled.
5
+ *
6
+ * **NOTE:** The supplied callback does not need to be memoized. The debouncer
7
+ * will always invoke the last supplied version.
8
+ *
9
+ * @see https://github.com/reykjavikcity/webtools/blob/v0.2/README.md#usedebounced
10
+ */
11
+ export declare const useDebounced: <A extends Array<unknown>>(
12
+ /** The function to debounce */
13
+ func: (...args: A) => void,
14
+ /** The delay, in milliseconds, to wait before running the function */
15
+ delay: number,
16
+ /** Whether to run the function at the start of the delay instead of the end */
17
+ immediate?: boolean) => ((...args: A) => void) & {
18
+ cancel(finish?: boolean): void;
19
+ };
20
+ /**
21
+ * Returns a stable throttler function that throttles the supplied function.
22
+ *
23
+ * **NOTE:** The supplied callback does not need to be memoized. The throttler
24
+ * will always invoke the last supplied version.
25
+ *
26
+ * @see https://github.com/reykjavikcity/webtools/blob/v0.2/README.md#usethrottled
27
+ */
28
+ export declare const useThrottled: <A extends Array<unknown>>(
29
+ /** The function to throttle */
30
+ func: (...args: A) => void,
31
+ /** The delay, in milliseconds, to wait between invocations. */
32
+ delay: number,
33
+ /** Whether to skip the first invocation instead of running it immediately. */
34
+ skipFirst?: boolean) => ((...args: A) => void) & {
35
+ finish(cancel?: boolean): void;
36
+ };
package/esm/hooks.js ADDED
@@ -0,0 +1,46 @@
1
+ import { useEffect, useMemo, useRef } from 'react';
2
+ import { debounce, throttle } from './async.js';
3
+ /**
4
+ * Returns a stable debounced function that invokes the supplied function
5
+ * after the specified delay. \
6
+ * When the component unmounts, any pending (debounced) calls are automatically cancelled.
7
+ *
8
+ * **NOTE:** The supplied callback does not need to be memoized. The debouncer
9
+ * will always invoke the last supplied version.
10
+ *
11
+ * @see https://github.com/reykjavikcity/webtools/blob/v0.2/README.md#usedebounced
12
+ */
13
+ export const useDebounced = (
14
+ /** The function to debounce */
15
+ func,
16
+ /** The delay, in milliseconds, to wait before running the function */
17
+ delay,
18
+ /** Whether to run the function at the start of the delay instead of the end */
19
+ immediate) => {
20
+ const fn = useRef();
21
+ fn.current = func;
22
+ const debouncedFunc = useMemo(() => debounce((...args) => fn.current(...args), delay, immediate), [delay, immediate]);
23
+ useEffect(() => debouncedFunc.cancel, [debouncedFunc]);
24
+ return debouncedFunc;
25
+ };
26
+ /**
27
+ * Returns a stable throttler function that throttles the supplied function.
28
+ *
29
+ * **NOTE:** The supplied callback does not need to be memoized. The throttler
30
+ * will always invoke the last supplied version.
31
+ *
32
+ * @see https://github.com/reykjavikcity/webtools/blob/v0.2/README.md#usethrottled
33
+ */
34
+ export const useThrottled = (
35
+ /** The function to throttle */
36
+ func,
37
+ /** The delay, in milliseconds, to wait between invocations. */
38
+ delay,
39
+ /** Whether to skip the first invocation instead of running it immediately. */
40
+ skipFirst) => {
41
+ const fn = useRef();
42
+ fn.current = func;
43
+ const throttledFunc = useMemo(() => throttle((...args) => fn.current(...args), delay, skipFirst), [delay, skipFirst]);
44
+ // NOTE: We don't need to run throttled.finish() on unmount, as the default behavior is no-op.
45
+ return throttledFunc;
46
+ };
package/esm/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ /// <reference path="./hooks.d.ts" />
1
2
  /// <reference path="./vanillaExtract.d.ts" />
2
3
  /// <reference path="./http.d.ts" />
3
4
  /// <reference path="./async.d.ts" />
@@ -37,5 +37,8 @@ export declare function vanillaClass(debugId: string, css: string | ClassNameCal
37
37
  *
38
38
  * @see https://github.com/reykjavikcity/webtools/blob/v0.2/README.md#vanillacvars
39
39
  */
40
- export declare const vanillaVars: <T extends string>(...varNames: Array<T>) => Record<`var${Capitalize<T>}`, string>;
40
+ export declare const vanillaVars: <T extends string>(...varNames: Array<T>) => Record<`var${Capitalize<T>}`, string> & {
41
+ /** Allows initializing all or some of the variables in CSS, without offending VSCode's CSS syntax parser too much. */
42
+ setVars: (vars: Partial<Record<T, unknown>>) => string;
43
+ };
41
44
  export {};
@@ -36,8 +36,12 @@ export function vanillaClass(cssOrDebugId, css) {
36
36
  export const vanillaVars = (...varNames) => {
37
37
  const id = vanillaClass(``);
38
38
  const vars = {};
39
+ vars.setVars = (vars) => Object.entries(vars)
40
+ .map(([name, value]) => `--${id}--${name}: ${value || ''};`)
41
+ .join('\n');
39
42
  for (const name of varNames) {
40
43
  vars[`var${capitalize(name)}`] = `--${id}--${name}`;
41
44
  }
42
45
  return vars;
43
46
  };
47
+ const { varColor, varBg, setVars } = vanillaVars('color', 'bg');
package/hooks.d.ts ADDED
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Returns a stable debounced function that invokes the supplied function
3
+ * after the specified delay. \
4
+ * When the component unmounts, any pending (debounced) calls are automatically cancelled.
5
+ *
6
+ * **NOTE:** The supplied callback does not need to be memoized. The debouncer
7
+ * will always invoke the last supplied version.
8
+ *
9
+ * @see https://github.com/reykjavikcity/webtools/blob/v0.2/README.md#usedebounced
10
+ */
11
+ export declare const useDebounced: <A extends Array<unknown>>(
12
+ /** The function to debounce */
13
+ func: (...args: A) => void,
14
+ /** The delay, in milliseconds, to wait before running the function */
15
+ delay: number,
16
+ /** Whether to run the function at the start of the delay instead of the end */
17
+ immediate?: boolean) => ((...args: A) => void) & {
18
+ cancel(finish?: boolean): void;
19
+ };
20
+ /**
21
+ * Returns a stable throttler function that throttles the supplied function.
22
+ *
23
+ * **NOTE:** The supplied callback does not need to be memoized. The throttler
24
+ * will always invoke the last supplied version.
25
+ *
26
+ * @see https://github.com/reykjavikcity/webtools/blob/v0.2/README.md#usethrottled
27
+ */
28
+ export declare const useThrottled: <A extends Array<unknown>>(
29
+ /** The function to throttle */
30
+ func: (...args: A) => void,
31
+ /** The delay, in milliseconds, to wait between invocations. */
32
+ delay: number,
33
+ /** Whether to skip the first invocation instead of running it immediately. */
34
+ skipFirst?: boolean) => ((...args: A) => void) & {
35
+ finish(cancel?: boolean): void;
36
+ };
package/hooks.js ADDED
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useThrottled = exports.useDebounced = void 0;
4
+ const react_1 = require("react");
5
+ const async_js_1 = require("./async.js");
6
+ /**
7
+ * Returns a stable debounced function that invokes the supplied function
8
+ * after the specified delay. \
9
+ * When the component unmounts, any pending (debounced) calls are automatically cancelled.
10
+ *
11
+ * **NOTE:** The supplied callback does not need to be memoized. The debouncer
12
+ * will always invoke the last supplied version.
13
+ *
14
+ * @see https://github.com/reykjavikcity/webtools/blob/v0.2/README.md#usedebounced
15
+ */
16
+ const useDebounced = (
17
+ /** The function to debounce */
18
+ func,
19
+ /** The delay, in milliseconds, to wait before running the function */
20
+ delay,
21
+ /** Whether to run the function at the start of the delay instead of the end */
22
+ immediate) => {
23
+ const fn = (0, react_1.useRef)();
24
+ fn.current = func;
25
+ const debouncedFunc = (0, react_1.useMemo)(() => (0, async_js_1.debounce)((...args) => fn.current(...args), delay, immediate), [delay, immediate]);
26
+ (0, react_1.useEffect)(() => debouncedFunc.cancel, [debouncedFunc]);
27
+ return debouncedFunc;
28
+ };
29
+ exports.useDebounced = useDebounced;
30
+ /**
31
+ * Returns a stable throttler function that throttles the supplied function.
32
+ *
33
+ * **NOTE:** The supplied callback does not need to be memoized. The throttler
34
+ * will always invoke the last supplied version.
35
+ *
36
+ * @see https://github.com/reykjavikcity/webtools/blob/v0.2/README.md#usethrottled
37
+ */
38
+ const useThrottled = (
39
+ /** The function to throttle */
40
+ func,
41
+ /** The delay, in milliseconds, to wait between invocations. */
42
+ delay,
43
+ /** Whether to skip the first invocation instead of running it immediately. */
44
+ skipFirst) => {
45
+ const fn = (0, react_1.useRef)();
46
+ fn.current = func;
47
+ const throttledFunc = (0, react_1.useMemo)(() => (0, async_js_1.throttle)((...args) => fn.current(...args), delay, skipFirst), [delay, skipFirst]);
48
+ // NOTE: We don't need to run throttled.finish() on unmount, as the default behavior is no-op.
49
+ return throttledFunc;
50
+ };
51
+ exports.useThrottled = useThrottled;
package/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ /// <reference path="./hooks.d.ts" />
1
2
  /// <reference path="./vanillaExtract.d.ts" />
2
3
  /// <reference path="./http.d.ts" />
3
4
  /// <reference path="./async.d.ts" />
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reykjavik/webtools",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
4
4
  "description": "Misc. JS/TS helpers used by Reykjavík City's web dev teams.",
5
5
  "main": "index.js",
6
6
  "repository": "ssh://git@github.com:reykjavikcity/webtools.git",
@@ -44,6 +44,10 @@
44
44
  "**/fixIcelandicLocale.js"
45
45
  ],
46
46
  "exports": {
47
+ "./hooks": {
48
+ "import": "./esm/hooks.js",
49
+ "require": "./hooks.js"
50
+ },
47
51
  ".": {
48
52
  "import": "./esm/index.js",
49
53
  "require": "./index.js"
@@ -37,5 +37,8 @@ export declare function vanillaClass(debugId: string, css: string | ClassNameCal
37
37
  *
38
38
  * @see https://github.com/reykjavikcity/webtools/blob/v0.2/README.md#vanillacvars
39
39
  */
40
- export declare const vanillaVars: <T extends string>(...varNames: Array<T>) => Record<`var${Capitalize<T>}`, string>;
40
+ export declare const vanillaVars: <T extends string>(...varNames: Array<T>) => Record<`var${Capitalize<T>}`, string> & {
41
+ /** Allows initializing all or some of the variables in CSS, without offending VSCode's CSS syntax parser too much. */
42
+ setVars: (vars: Partial<Record<T, unknown>>) => string;
43
+ };
41
44
  export {};
package/vanillaExtract.js CHANGED
@@ -42,9 +42,13 @@ function vanillaClass(cssOrDebugId, css) {
42
42
  const vanillaVars = (...varNames) => {
43
43
  const id = vanillaClass(``);
44
44
  const vars = {};
45
+ vars.setVars = (vars) => Object.entries(vars)
46
+ .map(([name, value]) => `--${id}--${name}: ${value || ''};`)
47
+ .join('\n');
45
48
  for (const name of varNames) {
46
49
  vars[`var${(0, hanna_utils_1.capitalize)(name)}`] = `--${id}--${name}`;
47
50
  }
48
51
  return vars;
49
52
  };
50
53
  exports.vanillaVars = vanillaVars;
54
+ const { varColor, varBg, setVars } = (0, exports.vanillaVars)('color', 'bg');