use-prms 0.2.0 → 0.4.0
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/README.md +265 -56
- package/dist/hash.cjs +901 -51
- package/dist/hash.cjs.map +1 -1
- package/dist/hash.d.cts +1 -1
- package/dist/hash.d.ts +1 -1
- package/dist/hash.js +874 -49
- package/dist/hash.js.map +1 -1
- package/dist/index.cjs +901 -51
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +462 -22
- package/dist/index.d.ts +462 -22
- package/dist/index.js +874 -49
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/hash.cjs
CHANGED
|
@@ -30,6 +30,25 @@ function serializeMultiParams(params) {
|
|
|
30
30
|
}
|
|
31
31
|
return result;
|
|
32
32
|
}
|
|
33
|
+
var LOCATION_CHANGE_EVENT = "use-prms:locationchange";
|
|
34
|
+
var historyPatched = false;
|
|
35
|
+
function patchHistoryApi() {
|
|
36
|
+
if (typeof window === "undefined" || historyPatched) return;
|
|
37
|
+
historyPatched = true;
|
|
38
|
+
const originalPushState = history.pushState.bind(history);
|
|
39
|
+
const originalReplaceState = history.replaceState.bind(history);
|
|
40
|
+
history.pushState = function(state, title, url) {
|
|
41
|
+
originalPushState(state, title, url);
|
|
42
|
+
window.dispatchEvent(new CustomEvent(LOCATION_CHANGE_EVENT));
|
|
43
|
+
window.dispatchEvent(new PopStateEvent("popstate", { state }));
|
|
44
|
+
};
|
|
45
|
+
history.replaceState = function(state, title, url) {
|
|
46
|
+
originalReplaceState(state, title, url);
|
|
47
|
+
window.dispatchEvent(new CustomEvent(LOCATION_CHANGE_EVENT));
|
|
48
|
+
window.dispatchEvent(new PopStateEvent("popstate", { state }));
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
patchHistoryApi();
|
|
33
52
|
var queryStrategy = {
|
|
34
53
|
getRaw() {
|
|
35
54
|
if (typeof window === "undefined") return "";
|
|
@@ -47,7 +66,11 @@ var queryStrategy = {
|
|
|
47
66
|
if (typeof window === "undefined") return () => {
|
|
48
67
|
};
|
|
49
68
|
window.addEventListener("popstate", callback);
|
|
50
|
-
|
|
69
|
+
window.addEventListener(LOCATION_CHANGE_EVENT, callback);
|
|
70
|
+
return () => {
|
|
71
|
+
window.removeEventListener("popstate", callback);
|
|
72
|
+
window.removeEventListener(LOCATION_CHANGE_EVENT, callback);
|
|
73
|
+
};
|
|
51
74
|
}
|
|
52
75
|
};
|
|
53
76
|
var hashStrategy = {
|
|
@@ -70,12 +93,28 @@ var hashStrategy = {
|
|
|
70
93
|
};
|
|
71
94
|
window.addEventListener("hashchange", callback);
|
|
72
95
|
window.addEventListener("popstate", callback);
|
|
96
|
+
window.addEventListener(LOCATION_CHANGE_EVENT, callback);
|
|
73
97
|
return () => {
|
|
74
98
|
window.removeEventListener("hashchange", callback);
|
|
75
99
|
window.removeEventListener("popstate", callback);
|
|
100
|
+
window.removeEventListener(LOCATION_CHANGE_EVENT, callback);
|
|
76
101
|
};
|
|
77
102
|
}
|
|
78
103
|
};
|
|
104
|
+
function notifyLocationChange() {
|
|
105
|
+
if (typeof window === "undefined") return;
|
|
106
|
+
window.dispatchEvent(new CustomEvent(LOCATION_CHANGE_EVENT));
|
|
107
|
+
}
|
|
108
|
+
function clearParams(strategy = "query") {
|
|
109
|
+
if (typeof window === "undefined") return;
|
|
110
|
+
const url = new URL(window.location.href);
|
|
111
|
+
if (strategy === "hash") {
|
|
112
|
+
url.hash = "";
|
|
113
|
+
} else {
|
|
114
|
+
url.search = "";
|
|
115
|
+
}
|
|
116
|
+
window.history.replaceState({}, "", url.toString());
|
|
117
|
+
}
|
|
79
118
|
var defaultStrategy = queryStrategy;
|
|
80
119
|
function getDefaultStrategy() {
|
|
81
120
|
return defaultStrategy;
|
|
@@ -104,19 +143,21 @@ var boolParam = {
|
|
|
104
143
|
function intParam(init) {
|
|
105
144
|
return {
|
|
106
145
|
encode: (value) => value === init ? void 0 : value.toString(),
|
|
107
|
-
decode: (encoded) =>
|
|
146
|
+
decode: (encoded) => {
|
|
147
|
+
if (encoded === void 0 || encoded === "") return init;
|
|
148
|
+
const parsed = parseInt(encoded, 10);
|
|
149
|
+
return isNaN(parsed) ? init : parsed;
|
|
150
|
+
}
|
|
108
151
|
};
|
|
109
152
|
}
|
|
110
153
|
var optIntParam = {
|
|
111
154
|
encode: (value) => value === null ? void 0 : value.toString(),
|
|
112
|
-
decode: (encoded) =>
|
|
155
|
+
decode: (encoded) => {
|
|
156
|
+
if (encoded === void 0 || encoded === "") return null;
|
|
157
|
+
const parsed = parseInt(encoded, 10);
|
|
158
|
+
return isNaN(parsed) ? null : parsed;
|
|
159
|
+
}
|
|
113
160
|
};
|
|
114
|
-
function floatParam(init) {
|
|
115
|
-
return {
|
|
116
|
-
encode: (value) => value === init ? void 0 : value.toString(),
|
|
117
|
-
decode: (encoded) => encoded !== void 0 ? parseFloat(encoded) : init
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
161
|
function enumParam(init, values) {
|
|
121
162
|
const validSet = new Set(values);
|
|
122
163
|
return {
|
|
@@ -268,6 +309,23 @@ function multiFloatParam(init = []) {
|
|
|
268
309
|
function arraysEqual(a, b) {
|
|
269
310
|
return a.length === b.length && a.every((v, i) => v === b[i]);
|
|
270
311
|
}
|
|
312
|
+
function debounce(fn, ms) {
|
|
313
|
+
let timeoutId = null;
|
|
314
|
+
const debounced = ((...args) => {
|
|
315
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
316
|
+
timeoutId = setTimeout(() => {
|
|
317
|
+
timeoutId = null;
|
|
318
|
+
fn(...args);
|
|
319
|
+
}, ms);
|
|
320
|
+
});
|
|
321
|
+
debounced.cancel = () => {
|
|
322
|
+
if (timeoutId) {
|
|
323
|
+
clearTimeout(timeoutId);
|
|
324
|
+
timeoutId = null;
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
return debounced;
|
|
328
|
+
}
|
|
271
329
|
var snapshotCache = /* @__PURE__ */ new WeakMap();
|
|
272
330
|
function getSnapshot(strategy) {
|
|
273
331
|
const raw = strategy.getRaw();
|
|
@@ -286,10 +344,13 @@ function multiToSingle(multi) {
|
|
|
286
344
|
if (multi.length === 0) return void 0;
|
|
287
345
|
return multi[0];
|
|
288
346
|
}
|
|
289
|
-
function
|
|
347
|
+
function useUrlState(key, param, options = {}) {
|
|
348
|
+
const opts = typeof options === "boolean" ? { push: options } : options;
|
|
349
|
+
const { debounce: debounceMs = 0, push = false } = opts;
|
|
290
350
|
const strategy = getDefaultStrategy();
|
|
291
351
|
const paramRef = react.useRef(param);
|
|
292
352
|
paramRef.current = param;
|
|
353
|
+
const lastWrittenRef = react.useRef(null);
|
|
293
354
|
const urlParams = react.useSyncExternalStore(
|
|
294
355
|
(cb) => strategy.subscribe(cb),
|
|
295
356
|
() => getSnapshot(strategy),
|
|
@@ -297,19 +358,24 @@ function useUrlParam(key, param, push = false) {
|
|
|
297
358
|
);
|
|
298
359
|
const encoded = multiToSingle(urlParams[key] ?? []);
|
|
299
360
|
const cacheRef = react.useRef(null);
|
|
300
|
-
|
|
301
|
-
|
|
361
|
+
let value;
|
|
362
|
+
if (lastWrittenRef.current && lastWrittenRef.current.encoded === encoded) {
|
|
363
|
+
value = lastWrittenRef.current.decoded;
|
|
364
|
+
} else {
|
|
365
|
+
if (cacheRef.current === null || cacheRef.current.encoded !== encoded || cacheRef.current.param !== param) {
|
|
366
|
+
cacheRef.current = { encoded, param, decoded: param.decode(encoded) };
|
|
367
|
+
}
|
|
368
|
+
value = cacheRef.current.decoded;
|
|
369
|
+
lastWrittenRef.current = null;
|
|
302
370
|
}
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
(newValue) => {
|
|
371
|
+
const writeToUrl = react.useCallback(
|
|
372
|
+
(newValue, newEncoded) => {
|
|
306
373
|
if (typeof window === "undefined") return;
|
|
307
374
|
const currentParams = strategy.parse();
|
|
308
|
-
|
|
309
|
-
if (encoded2 === void 0) {
|
|
375
|
+
if (newEncoded === void 0) {
|
|
310
376
|
delete currentParams[key];
|
|
311
377
|
} else {
|
|
312
|
-
currentParams[key] = [
|
|
378
|
+
currentParams[key] = [newEncoded];
|
|
313
379
|
}
|
|
314
380
|
const url = new URL(window.location.href);
|
|
315
381
|
const newUrl = strategy.buildUrl(url, currentParams);
|
|
@@ -319,29 +385,59 @@ function useUrlParam(key, param, push = false) {
|
|
|
319
385
|
},
|
|
320
386
|
[key, push, strategy]
|
|
321
387
|
);
|
|
388
|
+
const debouncedWriteRef = react.useRef(null);
|
|
389
|
+
react.useEffect(() => {
|
|
390
|
+
if (debounceMs > 0) {
|
|
391
|
+
debouncedWriteRef.current = debounce(writeToUrl, debounceMs);
|
|
392
|
+
} else {
|
|
393
|
+
debouncedWriteRef.current = null;
|
|
394
|
+
}
|
|
395
|
+
return () => {
|
|
396
|
+
debouncedWriteRef.current?.cancel();
|
|
397
|
+
};
|
|
398
|
+
}, [debounceMs, writeToUrl]);
|
|
399
|
+
const setValue = react.useCallback(
|
|
400
|
+
(newValue) => {
|
|
401
|
+
const newEncoded = paramRef.current.encode(newValue);
|
|
402
|
+
lastWrittenRef.current = { encoded: newEncoded, decoded: newValue };
|
|
403
|
+
if (debouncedWriteRef.current) {
|
|
404
|
+
debouncedWriteRef.current(newValue, newEncoded);
|
|
405
|
+
} else {
|
|
406
|
+
writeToUrl(newValue, newEncoded);
|
|
407
|
+
}
|
|
408
|
+
},
|
|
409
|
+
[writeToUrl]
|
|
410
|
+
);
|
|
322
411
|
return [value, setValue];
|
|
323
412
|
}
|
|
324
|
-
function
|
|
413
|
+
function useUrlStates(params, options = {}) {
|
|
414
|
+
const opts = typeof options === "boolean" ? { push: options } : options;
|
|
415
|
+
const { debounce: debounceMs = 0, push = false } = opts;
|
|
325
416
|
const strategy = getDefaultStrategy();
|
|
417
|
+
const lastWrittenRef = react.useRef({});
|
|
326
418
|
const urlParams = react.useSyncExternalStore(
|
|
327
419
|
(cb) => strategy.subscribe(cb),
|
|
328
420
|
() => getSnapshot(strategy),
|
|
329
421
|
getServerSnapshot
|
|
330
422
|
);
|
|
331
423
|
const values = Object.fromEntries(
|
|
332
|
-
Object.entries(params).map(([key, param]) =>
|
|
333
|
-
key
|
|
334
|
-
|
|
335
|
-
|
|
424
|
+
Object.entries(params).map(([key, param]) => {
|
|
425
|
+
const encoded = multiToSingle(urlParams[key] ?? []);
|
|
426
|
+
const lastWritten = lastWrittenRef.current[key];
|
|
427
|
+
if (lastWritten && lastWritten.encoded === encoded) {
|
|
428
|
+
return [key, lastWritten.decoded];
|
|
429
|
+
} else {
|
|
430
|
+
const decoded = param.decode(encoded);
|
|
431
|
+
delete lastWrittenRef.current[key];
|
|
432
|
+
return [key, decoded];
|
|
433
|
+
}
|
|
434
|
+
})
|
|
336
435
|
);
|
|
337
|
-
const
|
|
436
|
+
const writeToUrl = react.useCallback(
|
|
338
437
|
(updates) => {
|
|
339
438
|
if (typeof window === "undefined") return;
|
|
340
439
|
const currentParams = strategy.parse();
|
|
341
|
-
for (const [key,
|
|
342
|
-
const param = params[key];
|
|
343
|
-
if (!param) continue;
|
|
344
|
-
const encoded = param.encode(value);
|
|
440
|
+
for (const [key, { encoded }] of Object.entries(updates)) {
|
|
345
441
|
if (encoded === void 0) {
|
|
346
442
|
delete currentParams[key];
|
|
347
443
|
} else {
|
|
@@ -354,29 +450,67 @@ function useUrlParams(params, push = false) {
|
|
|
354
450
|
window.history[method]({}, "", newUrl);
|
|
355
451
|
window.dispatchEvent(new PopStateEvent("popstate"));
|
|
356
452
|
},
|
|
357
|
-
[
|
|
453
|
+
[push, strategy]
|
|
454
|
+
);
|
|
455
|
+
const debouncedWriteRef = react.useRef(null);
|
|
456
|
+
react.useEffect(() => {
|
|
457
|
+
if (debounceMs > 0) {
|
|
458
|
+
debouncedWriteRef.current = debounce(writeToUrl, debounceMs);
|
|
459
|
+
} else {
|
|
460
|
+
debouncedWriteRef.current = null;
|
|
461
|
+
}
|
|
462
|
+
return () => {
|
|
463
|
+
debouncedWriteRef.current?.cancel();
|
|
464
|
+
};
|
|
465
|
+
}, [debounceMs, writeToUrl]);
|
|
466
|
+
const setValues = react.useCallback(
|
|
467
|
+
(updates) => {
|
|
468
|
+
const encodedUpdates = {};
|
|
469
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
470
|
+
const param = params[key];
|
|
471
|
+
if (!param) continue;
|
|
472
|
+
const encoded = param.encode(value);
|
|
473
|
+
encodedUpdates[key] = { encoded, decoded: value };
|
|
474
|
+
lastWrittenRef.current[key] = { encoded, decoded: value };
|
|
475
|
+
}
|
|
476
|
+
if (debouncedWriteRef.current) {
|
|
477
|
+
debouncedWriteRef.current(encodedUpdates);
|
|
478
|
+
} else {
|
|
479
|
+
writeToUrl(encodedUpdates);
|
|
480
|
+
}
|
|
481
|
+
},
|
|
482
|
+
[params, writeToUrl]
|
|
358
483
|
);
|
|
359
484
|
return { values, setValues };
|
|
360
485
|
}
|
|
361
|
-
function
|
|
486
|
+
function useMultiUrlState(key, param, options = {}) {
|
|
487
|
+
const opts = typeof options === "boolean" ? { push: options } : options;
|
|
488
|
+
const { debounce: debounceMs = 0, push = false } = opts;
|
|
362
489
|
const strategy = getDefaultStrategy();
|
|
363
490
|
const paramRef = react.useRef(param);
|
|
364
491
|
paramRef.current = param;
|
|
492
|
+
const lastWrittenRef = react.useRef(null);
|
|
365
493
|
const urlParams = react.useSyncExternalStore(
|
|
366
494
|
(cb) => strategy.subscribe(cb),
|
|
367
495
|
() => getSnapshot(strategy),
|
|
368
496
|
getServerSnapshot
|
|
369
497
|
);
|
|
370
|
-
const
|
|
371
|
-
|
|
372
|
-
|
|
498
|
+
const encoded = urlParams[key] ?? [];
|
|
499
|
+
let value;
|
|
500
|
+
if (lastWrittenRef.current && arraysEqual2(lastWrittenRef.current.encoded, encoded)) {
|
|
501
|
+
value = lastWrittenRef.current.decoded;
|
|
502
|
+
} else {
|
|
503
|
+
value = param.decode(encoded);
|
|
504
|
+
lastWrittenRef.current = null;
|
|
505
|
+
}
|
|
506
|
+
const writeToUrl = react.useCallback(
|
|
507
|
+
(newEncoded) => {
|
|
373
508
|
if (typeof window === "undefined") return;
|
|
374
509
|
const currentParams = strategy.parse();
|
|
375
|
-
|
|
376
|
-
if (encoded.length === 0) {
|
|
510
|
+
if (newEncoded.length === 0) {
|
|
377
511
|
delete currentParams[key];
|
|
378
512
|
} else {
|
|
379
|
-
currentParams[key] =
|
|
513
|
+
currentParams[key] = newEncoded;
|
|
380
514
|
}
|
|
381
515
|
const url = new URL(window.location.href);
|
|
382
516
|
const newUrl = strategy.buildUrl(url, currentParams);
|
|
@@ -386,29 +520,62 @@ function useMultiUrlParam(key, param, push = false) {
|
|
|
386
520
|
},
|
|
387
521
|
[key, push, strategy]
|
|
388
522
|
);
|
|
523
|
+
const debouncedWriteRef = react.useRef(null);
|
|
524
|
+
react.useEffect(() => {
|
|
525
|
+
if (debounceMs > 0) {
|
|
526
|
+
debouncedWriteRef.current = debounce(writeToUrl, debounceMs);
|
|
527
|
+
} else {
|
|
528
|
+
debouncedWriteRef.current = null;
|
|
529
|
+
}
|
|
530
|
+
return () => {
|
|
531
|
+
debouncedWriteRef.current?.cancel();
|
|
532
|
+
};
|
|
533
|
+
}, [debounceMs, writeToUrl]);
|
|
534
|
+
const setValue = react.useCallback(
|
|
535
|
+
(newValue) => {
|
|
536
|
+
const newEncoded = paramRef.current.encode(newValue);
|
|
537
|
+
lastWrittenRef.current = { encoded: newEncoded, decoded: newValue };
|
|
538
|
+
if (debouncedWriteRef.current) {
|
|
539
|
+
debouncedWriteRef.current(newEncoded);
|
|
540
|
+
} else {
|
|
541
|
+
writeToUrl(newEncoded);
|
|
542
|
+
}
|
|
543
|
+
},
|
|
544
|
+
[writeToUrl]
|
|
545
|
+
);
|
|
389
546
|
return [value, setValue];
|
|
390
547
|
}
|
|
391
|
-
function
|
|
548
|
+
function arraysEqual2(a, b) {
|
|
549
|
+
return a.length === b.length && a.every((v, i) => v === b[i]);
|
|
550
|
+
}
|
|
551
|
+
function useMultiUrlStates(params, options = {}) {
|
|
552
|
+
const opts = typeof options === "boolean" ? { push: options } : options;
|
|
553
|
+
const { debounce: debounceMs = 0, push = false } = opts;
|
|
392
554
|
const strategy = getDefaultStrategy();
|
|
555
|
+
const lastWrittenRef = react.useRef({});
|
|
393
556
|
const urlParams = react.useSyncExternalStore(
|
|
394
557
|
(cb) => strategy.subscribe(cb),
|
|
395
558
|
() => getSnapshot(strategy),
|
|
396
559
|
getServerSnapshot
|
|
397
560
|
);
|
|
398
561
|
const values = Object.fromEntries(
|
|
399
|
-
Object.entries(params).map(([key, param]) =>
|
|
400
|
-
key
|
|
401
|
-
|
|
402
|
-
|
|
562
|
+
Object.entries(params).map(([key, param]) => {
|
|
563
|
+
const encoded = urlParams[key] ?? [];
|
|
564
|
+
const lastWritten = lastWrittenRef.current[key];
|
|
565
|
+
if (lastWritten && arraysEqual2(lastWritten.encoded, encoded)) {
|
|
566
|
+
return [key, lastWritten.decoded];
|
|
567
|
+
} else {
|
|
568
|
+
const decoded = param.decode(encoded);
|
|
569
|
+
delete lastWrittenRef.current[key];
|
|
570
|
+
return [key, decoded];
|
|
571
|
+
}
|
|
572
|
+
})
|
|
403
573
|
);
|
|
404
|
-
const
|
|
574
|
+
const writeToUrl = react.useCallback(
|
|
405
575
|
(updates) => {
|
|
406
576
|
if (typeof window === "undefined") return;
|
|
407
577
|
const currentParams = strategy.parse();
|
|
408
|
-
for (const [key,
|
|
409
|
-
const param = params[key];
|
|
410
|
-
if (!param) continue;
|
|
411
|
-
const encoded = param.encode(value);
|
|
578
|
+
for (const [key, encoded] of Object.entries(updates)) {
|
|
412
579
|
if (encoded.length === 0) {
|
|
413
580
|
delete currentParams[key];
|
|
414
581
|
} else {
|
|
@@ -421,11 +588,669 @@ function useMultiUrlParams(params, push = false) {
|
|
|
421
588
|
window.history[method]({}, "", newUrl);
|
|
422
589
|
window.dispatchEvent(new PopStateEvent("popstate"));
|
|
423
590
|
},
|
|
424
|
-
[
|
|
591
|
+
[push, strategy]
|
|
592
|
+
);
|
|
593
|
+
const debouncedWriteRef = react.useRef(null);
|
|
594
|
+
react.useEffect(() => {
|
|
595
|
+
if (debounceMs > 0) {
|
|
596
|
+
debouncedWriteRef.current = debounce(writeToUrl, debounceMs);
|
|
597
|
+
} else {
|
|
598
|
+
debouncedWriteRef.current = null;
|
|
599
|
+
}
|
|
600
|
+
return () => {
|
|
601
|
+
debouncedWriteRef.current?.cancel();
|
|
602
|
+
};
|
|
603
|
+
}, [debounceMs, writeToUrl]);
|
|
604
|
+
const setValues = react.useCallback(
|
|
605
|
+
(updates) => {
|
|
606
|
+
const encodedUpdates = {};
|
|
607
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
608
|
+
const param = params[key];
|
|
609
|
+
if (!param) continue;
|
|
610
|
+
const encoded = param.encode(value);
|
|
611
|
+
encodedUpdates[key] = encoded;
|
|
612
|
+
lastWrittenRef.current[key] = { encoded, decoded: value };
|
|
613
|
+
}
|
|
614
|
+
if (debouncedWriteRef.current) {
|
|
615
|
+
debouncedWriteRef.current(encodedUpdates);
|
|
616
|
+
} else {
|
|
617
|
+
writeToUrl(encodedUpdates);
|
|
618
|
+
}
|
|
619
|
+
},
|
|
620
|
+
[params, writeToUrl]
|
|
425
621
|
);
|
|
426
622
|
return { values, setValues };
|
|
427
623
|
}
|
|
428
624
|
|
|
625
|
+
// src/alphabet.ts
|
|
626
|
+
var ALPHABETS = {
|
|
627
|
+
/**
|
|
628
|
+
* RFC 4648 base64url alphabet (default)
|
|
629
|
+
* Standard URL-safe encoding, but NOT lexicographically sortable.
|
|
630
|
+
*/
|
|
631
|
+
rfc4648: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",
|
|
632
|
+
/**
|
|
633
|
+
* ASCII-ordered alphabet for lexicographic sortability
|
|
634
|
+
* Encoded strings sort in the same order as their numeric values.
|
|
635
|
+
* Uses URL-safe characters only (- and _).
|
|
636
|
+
*/
|
|
637
|
+
sortable: "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"
|
|
638
|
+
};
|
|
639
|
+
var URL_SAFE_CHARS = /^[A-Za-z0-9\-_]+$/;
|
|
640
|
+
function validateAlphabet(alphabet) {
|
|
641
|
+
if (alphabet.length !== 64) {
|
|
642
|
+
throw new Error(`Alphabet must be exactly 64 characters, got ${alphabet.length}`);
|
|
643
|
+
}
|
|
644
|
+
const seen = /* @__PURE__ */ new Set();
|
|
645
|
+
for (const char of alphabet) {
|
|
646
|
+
if (seen.has(char)) {
|
|
647
|
+
throw new Error(`Duplicate character in alphabet: '${char}'`);
|
|
648
|
+
}
|
|
649
|
+
seen.add(char);
|
|
650
|
+
}
|
|
651
|
+
if (!URL_SAFE_CHARS.test(alphabet)) {
|
|
652
|
+
const unsafe = [...alphabet].filter((c) => !URL_SAFE_CHARS.test(c));
|
|
653
|
+
throw new Error(`Alphabet contains non-URL-safe characters: ${unsafe.map((c) => `'${c}'`).join(", ")}`);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
function resolveAlphabet(alphabet) {
|
|
657
|
+
if (alphabet in ALPHABETS) {
|
|
658
|
+
return ALPHABETS[alphabet];
|
|
659
|
+
}
|
|
660
|
+
validateAlphabet(alphabet);
|
|
661
|
+
return alphabet;
|
|
662
|
+
}
|
|
663
|
+
function createLookupMap(alphabet) {
|
|
664
|
+
return new Map(alphabet.split("").map((c, i) => [c, i]));
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// src/binary.ts
|
|
668
|
+
var BASE64_CHARS = ALPHABETS.rfc4648;
|
|
669
|
+
var DEFAULT_LOOKUP = createLookupMap(ALPHABETS.rfc4648);
|
|
670
|
+
var lookupCache = /* @__PURE__ */ new Map();
|
|
671
|
+
function getLookupMap(alphabet) {
|
|
672
|
+
if (alphabet === ALPHABETS.rfc4648) return DEFAULT_LOOKUP;
|
|
673
|
+
let lookup = lookupCache.get(alphabet);
|
|
674
|
+
if (!lookup) {
|
|
675
|
+
lookup = createLookupMap(alphabet);
|
|
676
|
+
lookupCache.set(alphabet, lookup);
|
|
677
|
+
}
|
|
678
|
+
return lookup;
|
|
679
|
+
}
|
|
680
|
+
function base64Encode(bytes, options) {
|
|
681
|
+
const chars = options?.alphabet ? resolveAlphabet(options.alphabet) : ALPHABETS.rfc4648;
|
|
682
|
+
let result = "";
|
|
683
|
+
let i = 0;
|
|
684
|
+
while (i < bytes.length) {
|
|
685
|
+
const b0 = bytes[i++] ?? 0;
|
|
686
|
+
const b1 = bytes[i++] ?? 0;
|
|
687
|
+
const b2 = bytes[i++] ?? 0;
|
|
688
|
+
const n = b0 << 16 | b1 << 8 | b2;
|
|
689
|
+
result += chars[n >> 18 & 63];
|
|
690
|
+
result += chars[n >> 12 & 63];
|
|
691
|
+
if (i - 2 < bytes.length) {
|
|
692
|
+
result += chars[n >> 6 & 63];
|
|
693
|
+
}
|
|
694
|
+
if (i - 1 < bytes.length) {
|
|
695
|
+
result += chars[n & 63];
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
return result;
|
|
699
|
+
}
|
|
700
|
+
function base64Decode(str, options) {
|
|
701
|
+
const alphabet = options?.alphabet ? resolveAlphabet(options.alphabet) : ALPHABETS.rfc4648;
|
|
702
|
+
const lookup = getLookupMap(alphabet);
|
|
703
|
+
str = str.replace(/=+$/, "");
|
|
704
|
+
const bytes = [];
|
|
705
|
+
for (let i = 0; i < str.length; i += 4) {
|
|
706
|
+
const c0 = lookup.get(str[i]) ?? 0;
|
|
707
|
+
const c1 = lookup.get(str[i + 1]) ?? 0;
|
|
708
|
+
const c2 = i + 2 < str.length ? lookup.get(str[i + 2]) ?? 0 : 0;
|
|
709
|
+
const c3 = i + 3 < str.length ? lookup.get(str[i + 3]) ?? 0 : 0;
|
|
710
|
+
const n = c0 << 18 | c1 << 12 | c2 << 6 | c3;
|
|
711
|
+
bytes.push(n >> 16 & 255);
|
|
712
|
+
if (i + 2 < str.length) bytes.push(n >> 8 & 255);
|
|
713
|
+
if (i + 3 < str.length) bytes.push(n & 255);
|
|
714
|
+
}
|
|
715
|
+
return new Uint8Array(bytes);
|
|
716
|
+
}
|
|
717
|
+
function binaryParam(options) {
|
|
718
|
+
const { toBytes, fromBytes, alphabet } = options;
|
|
719
|
+
const encodeOpts = alphabet ? { alphabet } : void 0;
|
|
720
|
+
return {
|
|
721
|
+
encode: (value) => {
|
|
722
|
+
if (value === null) return void 0;
|
|
723
|
+
const bytes = toBytes(value);
|
|
724
|
+
if (bytes.length === 0) return void 0;
|
|
725
|
+
return base64Encode(bytes, encodeOpts);
|
|
726
|
+
},
|
|
727
|
+
decode: (encoded) => {
|
|
728
|
+
if (encoded === void 0 || encoded === "") return null;
|
|
729
|
+
try {
|
|
730
|
+
const bytes = base64Decode(encoded, encodeOpts);
|
|
731
|
+
return fromBytes(bytes);
|
|
732
|
+
} catch {
|
|
733
|
+
return null;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
function base64Param(toBytes, fromBytes, alphabet) {
|
|
739
|
+
return binaryParam({ toBytes, fromBytes, alphabet });
|
|
740
|
+
}
|
|
741
|
+
function floatToBytes(value) {
|
|
742
|
+
const buf = new ArrayBuffer(8);
|
|
743
|
+
const view = new DataView(buf);
|
|
744
|
+
view.setFloat64(0, value, false);
|
|
745
|
+
return new Uint8Array(buf);
|
|
746
|
+
}
|
|
747
|
+
function bytesToFloat(bytes) {
|
|
748
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
749
|
+
return view.getFloat64(0, false);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// src/float.ts
|
|
753
|
+
var precisionSchemes = [
|
|
754
|
+
{ expBits: 5, mantBits: 16 },
|
|
755
|
+
// ~5 decimal digits
|
|
756
|
+
{ expBits: 5, mantBits: 22 },
|
|
757
|
+
// ~7 decimal digits
|
|
758
|
+
{ expBits: 5, mantBits: 28 },
|
|
759
|
+
// ~8 decimal digits
|
|
760
|
+
{ expBits: 5, mantBits: 34 },
|
|
761
|
+
// ~10 decimal digits
|
|
762
|
+
{ expBits: 5, mantBits: 40 },
|
|
763
|
+
// ~12 decimal digits
|
|
764
|
+
{ expBits: 5, mantBits: 46 },
|
|
765
|
+
// ~14 decimal digits
|
|
766
|
+
{ expBits: 5, mantBits: 52 }
|
|
767
|
+
// ~16 decimal digits (near IEEE 754)
|
|
768
|
+
];
|
|
769
|
+
var DEFAULT_EXP_BITS = 5;
|
|
770
|
+
var MIN_MANT_BITS = 8;
|
|
771
|
+
var MAX_MANT_BITS = 52;
|
|
772
|
+
function resolvePrecision(precision) {
|
|
773
|
+
if (precision === void 0) return { expBits: DEFAULT_EXP_BITS, mantBits: 22 };
|
|
774
|
+
if (typeof precision === "object") return precision;
|
|
775
|
+
if (precision < MIN_MANT_BITS || precision > MAX_MANT_BITS) {
|
|
776
|
+
throw new Error(`Precision must be ${MIN_MANT_BITS}-${MAX_MANT_BITS} bits, got ${precision}`);
|
|
777
|
+
}
|
|
778
|
+
return { expBits: DEFAULT_EXP_BITS, mantBits: precision };
|
|
779
|
+
}
|
|
780
|
+
var floatBuf = new ArrayBuffer(8);
|
|
781
|
+
var floatView = new DataView(floatBuf);
|
|
782
|
+
function toFloat(x) {
|
|
783
|
+
floatView.setFloat64(0, x, false);
|
|
784
|
+
const byte0 = floatView.getUint8(0);
|
|
785
|
+
const neg = !!(byte0 & 128);
|
|
786
|
+
const exp = ((floatView.getUint16(0, false) & 32752) >> 4) - 1023;
|
|
787
|
+
const mant = floatView.getBigUint64(0, false) & 0xfffffffffffffn;
|
|
788
|
+
return { neg, exp, mant };
|
|
789
|
+
}
|
|
790
|
+
function fromFloat({ neg, exp, mant }) {
|
|
791
|
+
floatView.setBigUint64(
|
|
792
|
+
0,
|
|
793
|
+
(neg ? 0x8000000000000000n : 0n) | BigInt(exp + 1023) << 52n | mant,
|
|
794
|
+
false
|
|
795
|
+
);
|
|
796
|
+
return floatView.getFloat64(0, false);
|
|
797
|
+
}
|
|
798
|
+
function toFixedPoint(f, opts) {
|
|
799
|
+
let { neg, exp: fExp, mant } = f;
|
|
800
|
+
fExp++;
|
|
801
|
+
const exp = opts.exp === void 0 ? fExp : opts.exp;
|
|
802
|
+
if (fExp > exp) {
|
|
803
|
+
throw Error(`maxExp ${exp} < ${fExp}`);
|
|
804
|
+
}
|
|
805
|
+
const downshiftBy = exp - fExp + 53 - opts.mantBits;
|
|
806
|
+
const roundUp = mant & 1n << BigInt(downshiftBy - 1);
|
|
807
|
+
mant >>= BigInt(downshiftBy);
|
|
808
|
+
if (roundUp) {
|
|
809
|
+
mant += 1n;
|
|
810
|
+
}
|
|
811
|
+
mant |= 1n << BigInt(opts.mantBits - 1 - (exp - fExp));
|
|
812
|
+
return { neg, exp, mant };
|
|
813
|
+
}
|
|
814
|
+
function fromFixedPoint(f, mantBits) {
|
|
815
|
+
const { neg } = f;
|
|
816
|
+
const nonZeroBits = f.mant ? f.mant.toString(2).length : 0;
|
|
817
|
+
const exp = f.exp - (mantBits - nonZeroBits) - 1;
|
|
818
|
+
if (!f.mant) {
|
|
819
|
+
return { neg, exp: -1023, mant: 0n };
|
|
820
|
+
}
|
|
821
|
+
let mant = BigInt(f.mant);
|
|
822
|
+
mant = mant & (1n << BigInt(nonZeroBits - 1)) - 1n;
|
|
823
|
+
mant <<= BigInt(f.exp - exp);
|
|
824
|
+
mant <<= BigInt(52 - mantBits);
|
|
825
|
+
return { neg, exp, mant };
|
|
826
|
+
}
|
|
827
|
+
var BitBuffer = class _BitBuffer {
|
|
828
|
+
constructor(numBytes) {
|
|
829
|
+
this.buf = Array(numBytes || 0).fill(0);
|
|
830
|
+
this.byteOffset = 0;
|
|
831
|
+
this.bitOffset = 0;
|
|
832
|
+
this.end = 0;
|
|
833
|
+
}
|
|
834
|
+
get totalBitOffset() {
|
|
835
|
+
return this.byteOffset * 8 + this.bitOffset;
|
|
836
|
+
}
|
|
837
|
+
seek(totalBitOffset) {
|
|
838
|
+
this.byteOffset = totalBitOffset >> 3;
|
|
839
|
+
this.bitOffset = totalBitOffset & 7;
|
|
840
|
+
return this;
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Encode an integer with specified bit width
|
|
844
|
+
*/
|
|
845
|
+
encodeInt(n, numBits) {
|
|
846
|
+
let { buf, byteOffset, bitOffset } = this;
|
|
847
|
+
while (numBits > 0) {
|
|
848
|
+
if (byteOffset >= buf.length) {
|
|
849
|
+
buf.push(0);
|
|
850
|
+
}
|
|
851
|
+
const remainingBitsInByte = 8 - bitOffset;
|
|
852
|
+
const bitsToWrite = Math.min(numBits, remainingBitsInByte);
|
|
853
|
+
const bitsLeftInByte = remainingBitsInByte - bitsToWrite;
|
|
854
|
+
const bitsLeftToWrite = numBits - bitsToWrite;
|
|
855
|
+
const mask = (1 << bitsToWrite) - 1 << bitsLeftToWrite;
|
|
856
|
+
const shiftedBitsToWrite = (n & mask) >> bitsLeftToWrite;
|
|
857
|
+
buf[byteOffset] |= shiftedBitsToWrite << bitsLeftInByte;
|
|
858
|
+
n &= (1 << bitsLeftToWrite) - 1;
|
|
859
|
+
numBits -= bitsToWrite;
|
|
860
|
+
bitOffset += bitsToWrite;
|
|
861
|
+
if (bitOffset === 8) {
|
|
862
|
+
bitOffset = 0;
|
|
863
|
+
byteOffset++;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
this.byteOffset = byteOffset;
|
|
867
|
+
this.bitOffset = bitOffset;
|
|
868
|
+
if (this.totalBitOffset > this.end) this.end = this.totalBitOffset;
|
|
869
|
+
return this;
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* Decode an integer with specified bit width
|
|
873
|
+
*/
|
|
874
|
+
decodeInt(numBits) {
|
|
875
|
+
let { buf, byteOffset, bitOffset } = this;
|
|
876
|
+
let n = 0;
|
|
877
|
+
while (numBits > 0) {
|
|
878
|
+
const remainingBitsInByte = 8 - bitOffset;
|
|
879
|
+
const bitsToRead = Math.min(numBits, remainingBitsInByte);
|
|
880
|
+
const bitsLeftInByte = remainingBitsInByte - bitsToRead;
|
|
881
|
+
const mask = (1 << bitsToRead) - 1 << bitsLeftInByte;
|
|
882
|
+
const bits = (buf[byteOffset] & mask) >> bitsLeftInByte;
|
|
883
|
+
n = n << bitsToRead | bits;
|
|
884
|
+
numBits -= bitsToRead;
|
|
885
|
+
bitOffset += bitsToRead;
|
|
886
|
+
if (bitOffset === 8) {
|
|
887
|
+
bitOffset = 0;
|
|
888
|
+
byteOffset++;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
this.byteOffset = byteOffset;
|
|
892
|
+
this.bitOffset = bitOffset;
|
|
893
|
+
return n;
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
896
|
+
* Encode a bigint with specified bit width
|
|
897
|
+
*/
|
|
898
|
+
encodeBigInt(n, numBits) {
|
|
899
|
+
let { buf, byteOffset, bitOffset } = this;
|
|
900
|
+
while (numBits > 0) {
|
|
901
|
+
if (byteOffset >= buf.length) {
|
|
902
|
+
buf.push(0);
|
|
903
|
+
}
|
|
904
|
+
const remainingBitsInByte = 8 - bitOffset;
|
|
905
|
+
const bitsToWrite = Math.min(numBits, remainingBitsInByte);
|
|
906
|
+
const bitsLeftInByte = remainingBitsInByte - bitsToWrite;
|
|
907
|
+
const bitsLeftToWrite = numBits - bitsToWrite;
|
|
908
|
+
const mask = (1n << BigInt(bitsToWrite)) - 1n << BigInt(bitsLeftToWrite);
|
|
909
|
+
const shiftedBitsToWrite = Number((n & mask) >> BigInt(bitsLeftToWrite));
|
|
910
|
+
buf[byteOffset] |= shiftedBitsToWrite << bitsLeftInByte;
|
|
911
|
+
n &= (1n << BigInt(bitsLeftToWrite)) - 1n;
|
|
912
|
+
numBits -= bitsToWrite;
|
|
913
|
+
bitOffset += bitsToWrite;
|
|
914
|
+
if (bitOffset === 8) {
|
|
915
|
+
bitOffset = 0;
|
|
916
|
+
byteOffset++;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
this.byteOffset = byteOffset;
|
|
920
|
+
this.bitOffset = bitOffset;
|
|
921
|
+
if (this.totalBitOffset > this.end) this.end = this.totalBitOffset;
|
|
922
|
+
return this;
|
|
923
|
+
}
|
|
924
|
+
/**
|
|
925
|
+
* Decode a bigint with specified bit width
|
|
926
|
+
*/
|
|
927
|
+
decodeBigInt(numBits) {
|
|
928
|
+
let { buf, byteOffset, bitOffset } = this;
|
|
929
|
+
let n = 0n;
|
|
930
|
+
while (numBits > 0) {
|
|
931
|
+
const remainingBitsInByte = 8 - bitOffset;
|
|
932
|
+
const bitsToRead = Math.min(numBits, remainingBitsInByte);
|
|
933
|
+
const bitsLeftInByte = remainingBitsInByte - bitsToRead;
|
|
934
|
+
const mask = (1 << bitsToRead) - 1 << bitsLeftInByte;
|
|
935
|
+
const bits = BigInt((buf[byteOffset] & mask) >> bitsLeftInByte);
|
|
936
|
+
n = n << BigInt(bitsToRead) | bits;
|
|
937
|
+
numBits -= bitsToRead;
|
|
938
|
+
bitOffset += bitsToRead;
|
|
939
|
+
if (bitOffset === 8) {
|
|
940
|
+
bitOffset = 0;
|
|
941
|
+
byteOffset++;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
this.byteOffset = byteOffset;
|
|
945
|
+
this.bitOffset = bitOffset;
|
|
946
|
+
return n;
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* Encode an array of floats with shared exponent
|
|
950
|
+
*/
|
|
951
|
+
encodeFixedPoints(vals, { expBits, mantBits }) {
|
|
952
|
+
const floats = vals.map(toFloat);
|
|
953
|
+
const maxExp = Math.max(...floats.map(({ exp }) => exp + 1));
|
|
954
|
+
if (maxExp >= 1 << expBits - 1) {
|
|
955
|
+
throw Error(`maxExp ${maxExp} >= ${1 << expBits}`);
|
|
956
|
+
}
|
|
957
|
+
const expToWrite = maxExp + (1 << expBits - 1) & (1 << expBits) - 1;
|
|
958
|
+
this.encodeInt(expToWrite, expBits);
|
|
959
|
+
const fixedPoints = floats.map((f) => toFixedPoint(f, { mantBits, exp: maxExp }));
|
|
960
|
+
fixedPoints.forEach(({ neg, mant }) => {
|
|
961
|
+
this.encodeInt(neg ? 1 : 0, 1);
|
|
962
|
+
this.encodeBigInt(mant, mantBits);
|
|
963
|
+
});
|
|
964
|
+
return this;
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* Decode an array of floats with shared exponent
|
|
968
|
+
*/
|
|
969
|
+
decodeFixedPoints(count, { expBits, mantBits }) {
|
|
970
|
+
const expRaw = this.decodeInt(expBits);
|
|
971
|
+
const exp = expRaw - (1 << expBits - 1);
|
|
972
|
+
const result = [];
|
|
973
|
+
for (let i = 0; i < count; i++) {
|
|
974
|
+
const neg = this.decodeInt(1) === 1;
|
|
975
|
+
const mant = this.decodeBigInt(mantBits);
|
|
976
|
+
const fp = { neg, exp, mant };
|
|
977
|
+
const f = fromFixedPoint(fp, mantBits);
|
|
978
|
+
result.push(fromFloat(f));
|
|
979
|
+
}
|
|
980
|
+
return result;
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* Get bytes as Uint8Array
|
|
984
|
+
*/
|
|
985
|
+
toBytes() {
|
|
986
|
+
const numBytes = Math.ceil(this.end / 8);
|
|
987
|
+
return new Uint8Array(this.buf.slice(0, numBytes));
|
|
988
|
+
}
|
|
989
|
+
/**
|
|
990
|
+
* Create from bytes
|
|
991
|
+
*/
|
|
992
|
+
static fromBytes(bytes) {
|
|
993
|
+
const buf = new _BitBuffer();
|
|
994
|
+
buf.buf = Array.from(bytes);
|
|
995
|
+
buf.end = bytes.length * 8;
|
|
996
|
+
return buf;
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* Convert buffer to URL-safe base64 string
|
|
1000
|
+
*
|
|
1001
|
+
* Encodes bits directly to base64 (6 bits per character) for maximum compactness.
|
|
1002
|
+
* This is more efficient than going through bytes when bit count isn't a multiple of 8.
|
|
1003
|
+
*
|
|
1004
|
+
* @param options - Base64 options (alphabet)
|
|
1005
|
+
*/
|
|
1006
|
+
toBase64(options) {
|
|
1007
|
+
const alphabet = resolveAlphabet(options?.alphabet ?? "rfc4648");
|
|
1008
|
+
const overhang = this.end % 6;
|
|
1009
|
+
if (overhang) {
|
|
1010
|
+
this.encodeInt(0, 6 - overhang);
|
|
1011
|
+
}
|
|
1012
|
+
const numChars = this.end / 6;
|
|
1013
|
+
this.seek(0);
|
|
1014
|
+
let result = "";
|
|
1015
|
+
for (let i = 0; i < numChars; i++) {
|
|
1016
|
+
result += alphabet[this.decodeInt(6)];
|
|
1017
|
+
}
|
|
1018
|
+
return result;
|
|
1019
|
+
}
|
|
1020
|
+
/**
|
|
1021
|
+
* Create a BitBuffer from a URL-safe base64 string
|
|
1022
|
+
*
|
|
1023
|
+
* Decodes base64 directly to bits (6 bits per character).
|
|
1024
|
+
*
|
|
1025
|
+
* @param str - The base64 string to decode
|
|
1026
|
+
* @param options - Base64 options (alphabet)
|
|
1027
|
+
*/
|
|
1028
|
+
static fromBase64(str, options) {
|
|
1029
|
+
const alphabet = resolveAlphabet(options?.alphabet ?? "rfc4648");
|
|
1030
|
+
const lookup = createLookupMap(alphabet);
|
|
1031
|
+
const buf = new _BitBuffer();
|
|
1032
|
+
for (const char of str) {
|
|
1033
|
+
const idx = lookup.get(char);
|
|
1034
|
+
if (idx === void 0) {
|
|
1035
|
+
throw new Error(`Invalid base64 character: '${char}'`);
|
|
1036
|
+
}
|
|
1037
|
+
buf.encodeInt(idx, 6);
|
|
1038
|
+
}
|
|
1039
|
+
buf.seek(0);
|
|
1040
|
+
return buf;
|
|
1041
|
+
}
|
|
1042
|
+
};
|
|
1043
|
+
function parsePrecisionString(s) {
|
|
1044
|
+
const match = s.match(/^(\d+)\+(\d+)$/);
|
|
1045
|
+
if (!match) {
|
|
1046
|
+
throw new Error(`Invalid precision format: "${s}". Expected format like "5+22" (exp+mant)`);
|
|
1047
|
+
}
|
|
1048
|
+
return { exp: parseInt(match[1], 10), mant: parseInt(match[2], 10) };
|
|
1049
|
+
}
|
|
1050
|
+
function floatParam(optsOrDefault = 0) {
|
|
1051
|
+
const opts = typeof optsOrDefault === "number" ? { default: optsOrDefault } : optsOrDefault;
|
|
1052
|
+
const {
|
|
1053
|
+
default: defaultValue = 0,
|
|
1054
|
+
encoding = "base64",
|
|
1055
|
+
decimals,
|
|
1056
|
+
exp,
|
|
1057
|
+
mant,
|
|
1058
|
+
precision,
|
|
1059
|
+
alphabet
|
|
1060
|
+
} = opts;
|
|
1061
|
+
if (encoding === "string") {
|
|
1062
|
+
if (exp !== void 0 || mant !== void 0 || precision !== void 0) {
|
|
1063
|
+
throw new Error('exp/mant/precision options are only valid with encoding: "base64"');
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
if (encoding === "base64") {
|
|
1067
|
+
if (decimals !== void 0) {
|
|
1068
|
+
throw new Error('decimals option is only valid with encoding: "string"');
|
|
1069
|
+
}
|
|
1070
|
+
const hasExpMant = exp !== void 0 || mant !== void 0;
|
|
1071
|
+
const hasPrecision = precision !== void 0;
|
|
1072
|
+
if (hasExpMant && hasPrecision) {
|
|
1073
|
+
throw new Error("Cannot specify both exp/mant and precision");
|
|
1074
|
+
}
|
|
1075
|
+
if (hasExpMant) {
|
|
1076
|
+
if (exp === void 0 || mant === void 0) {
|
|
1077
|
+
throw new Error("Both exp and mant must be specified together");
|
|
1078
|
+
}
|
|
1079
|
+
return createLossyBase64Param(defaultValue, { expBits: exp, mantBits: mant }, alphabet);
|
|
1080
|
+
}
|
|
1081
|
+
if (hasPrecision) {
|
|
1082
|
+
const { exp: e, mant: m } = parsePrecisionString(precision);
|
|
1083
|
+
return createLossyBase64Param(defaultValue, { expBits: e, mantBits: m }, alphabet);
|
|
1084
|
+
}
|
|
1085
|
+
return createLosslessBase64Param(defaultValue, alphabet);
|
|
1086
|
+
}
|
|
1087
|
+
if (decimals !== void 0) {
|
|
1088
|
+
return createTruncatedStringParam(defaultValue, decimals);
|
|
1089
|
+
}
|
|
1090
|
+
return createFullStringParam(defaultValue);
|
|
1091
|
+
}
|
|
1092
|
+
function createLosslessBase64Param(defaultValue, alphabet) {
|
|
1093
|
+
const opts = alphabet ? { alphabet } : void 0;
|
|
1094
|
+
return {
|
|
1095
|
+
encode: (value) => {
|
|
1096
|
+
if (value === defaultValue) return void 0;
|
|
1097
|
+
return base64Encode(floatToBytes(value), opts);
|
|
1098
|
+
},
|
|
1099
|
+
decode: (encoded) => {
|
|
1100
|
+
if (encoded === void 0 || encoded === "") return defaultValue;
|
|
1101
|
+
try {
|
|
1102
|
+
return bytesToFloat(base64Decode(encoded, opts));
|
|
1103
|
+
} catch {
|
|
1104
|
+
return defaultValue;
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
function createLossyBase64Param(defaultValue, scheme, alphabet) {
|
|
1110
|
+
const opts = alphabet ? { alphabet } : void 0;
|
|
1111
|
+
return {
|
|
1112
|
+
encode: (value) => {
|
|
1113
|
+
if (value === defaultValue) return void 0;
|
|
1114
|
+
const buf = new BitBuffer();
|
|
1115
|
+
buf.encodeFixedPoints([value], scheme);
|
|
1116
|
+
return buf.toBase64(opts);
|
|
1117
|
+
},
|
|
1118
|
+
decode: (encoded) => {
|
|
1119
|
+
if (encoded === void 0 || encoded === "") return defaultValue;
|
|
1120
|
+
try {
|
|
1121
|
+
const buf = BitBuffer.fromBase64(encoded, opts);
|
|
1122
|
+
const [value] = buf.decodeFixedPoints(1, scheme);
|
|
1123
|
+
return value;
|
|
1124
|
+
} catch {
|
|
1125
|
+
return defaultValue;
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
1130
|
+
function createFullStringParam(defaultValue) {
|
|
1131
|
+
return {
|
|
1132
|
+
encode: (value) => {
|
|
1133
|
+
if (value === defaultValue) return void 0;
|
|
1134
|
+
return value.toString();
|
|
1135
|
+
},
|
|
1136
|
+
decode: (encoded) => {
|
|
1137
|
+
if (encoded === void 0 || encoded === "") return defaultValue;
|
|
1138
|
+
const parsed = parseFloat(encoded);
|
|
1139
|
+
return isNaN(parsed) ? defaultValue : parsed;
|
|
1140
|
+
}
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
function createTruncatedStringParam(defaultValue, decimals) {
|
|
1144
|
+
const multiplier = Math.pow(10, decimals);
|
|
1145
|
+
return {
|
|
1146
|
+
encode: (value) => {
|
|
1147
|
+
if (value === defaultValue) return void 0;
|
|
1148
|
+
const truncated = Math.round(value * multiplier) / multiplier;
|
|
1149
|
+
return truncated.toFixed(decimals);
|
|
1150
|
+
},
|
|
1151
|
+
decode: (encoded) => {
|
|
1152
|
+
if (encoded === void 0 || encoded === "") return defaultValue;
|
|
1153
|
+
const parsed = parseFloat(encoded);
|
|
1154
|
+
return isNaN(parsed) ? defaultValue : parsed;
|
|
1155
|
+
}
|
|
1156
|
+
};
|
|
1157
|
+
}
|
|
1158
|
+
function base64FloatParam(optsOrDefault = 0) {
|
|
1159
|
+
const opts = typeof optsOrDefault === "number" ? { default: optsOrDefault } : optsOrDefault;
|
|
1160
|
+
return floatParam({ ...opts, encoding: "base64" });
|
|
1161
|
+
}
|
|
1162
|
+
function pointParam(opts = {}) {
|
|
1163
|
+
const {
|
|
1164
|
+
encoding = "base64",
|
|
1165
|
+
decimals = 2,
|
|
1166
|
+
precision,
|
|
1167
|
+
default: defaultPoint = null,
|
|
1168
|
+
alphabet
|
|
1169
|
+
} = opts;
|
|
1170
|
+
const scheme = resolvePrecision(precision);
|
|
1171
|
+
const multiplier = Math.pow(10, decimals);
|
|
1172
|
+
const base64Opts = alphabet ? { alphabet } : void 0;
|
|
1173
|
+
return {
|
|
1174
|
+
encode: (point) => {
|
|
1175
|
+
if (point === null) return void 0;
|
|
1176
|
+
if (defaultPoint && point.x === defaultPoint.x && point.y === defaultPoint.y) {
|
|
1177
|
+
return void 0;
|
|
1178
|
+
}
|
|
1179
|
+
if (encoding === "string") {
|
|
1180
|
+
const xTrunc = Math.round(point.x * multiplier) / multiplier;
|
|
1181
|
+
const yTrunc = Math.round(point.y * multiplier) / multiplier;
|
|
1182
|
+
const xStr = xTrunc.toFixed(decimals);
|
|
1183
|
+
const yStr = yTrunc.toFixed(decimals);
|
|
1184
|
+
const delimiter = yTrunc >= 0 ? " " : "";
|
|
1185
|
+
return `${xStr}${delimiter}${yStr}`;
|
|
1186
|
+
} else {
|
|
1187
|
+
const buf = new BitBuffer();
|
|
1188
|
+
buf.encodeFixedPoints([point.x, point.y], scheme);
|
|
1189
|
+
return buf.toBase64(base64Opts);
|
|
1190
|
+
}
|
|
1191
|
+
},
|
|
1192
|
+
decode: (encoded) => {
|
|
1193
|
+
if (encoded === void 0 || encoded === "") return defaultPoint;
|
|
1194
|
+
try {
|
|
1195
|
+
if (encoding === "string") {
|
|
1196
|
+
let x, y;
|
|
1197
|
+
if (encoded.includes(" ")) {
|
|
1198
|
+
const parts = encoded.split(" ");
|
|
1199
|
+
if (parts.length !== 2) return defaultPoint;
|
|
1200
|
+
x = parseFloat(parts[0]);
|
|
1201
|
+
y = parseFloat(parts[1]);
|
|
1202
|
+
} else {
|
|
1203
|
+
const minusIdx = encoded.indexOf("-", encoded[0] === "-" ? 1 : 0);
|
|
1204
|
+
if (minusIdx === -1) return defaultPoint;
|
|
1205
|
+
x = parseFloat(encoded.slice(0, minusIdx));
|
|
1206
|
+
y = parseFloat(encoded.slice(minusIdx));
|
|
1207
|
+
}
|
|
1208
|
+
if (isNaN(x) || isNaN(y)) return defaultPoint;
|
|
1209
|
+
return { x, y };
|
|
1210
|
+
} else {
|
|
1211
|
+
const buf = BitBuffer.fromBase64(encoded, base64Opts);
|
|
1212
|
+
const [x, y] = buf.decodeFixedPoints(2, scheme);
|
|
1213
|
+
return { x, y };
|
|
1214
|
+
}
|
|
1215
|
+
} catch {
|
|
1216
|
+
return defaultPoint;
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
function encodeFloatAllModes(value, opts = {}) {
|
|
1222
|
+
const { decimals = 2, precision } = opts;
|
|
1223
|
+
const scheme = resolvePrecision(precision);
|
|
1224
|
+
const multiplier = Math.pow(10, decimals);
|
|
1225
|
+
const truncated = Math.round(value * multiplier) / multiplier;
|
|
1226
|
+
const stringEncoded = truncated.toFixed(decimals);
|
|
1227
|
+
const buf = new BitBuffer();
|
|
1228
|
+
buf.encodeFixedPoints([value], scheme);
|
|
1229
|
+
return {
|
|
1230
|
+
string: stringEncoded,
|
|
1231
|
+
base64: buf.toBase64(),
|
|
1232
|
+
bits: buf.end
|
|
1233
|
+
};
|
|
1234
|
+
}
|
|
1235
|
+
function encodePointAllModes(point, opts = {}) {
|
|
1236
|
+
const { decimals = 2, precision } = opts;
|
|
1237
|
+
const scheme = resolvePrecision(precision);
|
|
1238
|
+
const multiplier = Math.pow(10, decimals);
|
|
1239
|
+
const xTrunc = Math.round(point.x * multiplier) / multiplier;
|
|
1240
|
+
const yTrunc = Math.round(point.y * multiplier) / multiplier;
|
|
1241
|
+
const xStr = xTrunc.toFixed(decimals);
|
|
1242
|
+
const yStr = yTrunc.toFixed(decimals);
|
|
1243
|
+
const delimiter = yTrunc >= 0 ? "+" : "";
|
|
1244
|
+
const stringEncoded = `${xStr}${delimiter}${yStr}`;
|
|
1245
|
+
const buf = new BitBuffer();
|
|
1246
|
+
buf.encodeFixedPoints([point.x, point.y], scheme);
|
|
1247
|
+
return {
|
|
1248
|
+
string: stringEncoded,
|
|
1249
|
+
base64: buf.toBase64(),
|
|
1250
|
+
bits: buf.end
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
|
|
429
1254
|
// src/index.ts
|
|
430
1255
|
function serializeParams(params) {
|
|
431
1256
|
const searchParams = new URLSearchParams();
|
|
@@ -472,12 +1297,29 @@ function updateUrl(params, push = false) {
|
|
|
472
1297
|
// src/hash.ts
|
|
473
1298
|
setDefaultStrategy(hashStrategy);
|
|
474
1299
|
|
|
1300
|
+
exports.ALPHABETS = ALPHABETS;
|
|
1301
|
+
exports.BASE64_CHARS = BASE64_CHARS;
|
|
1302
|
+
exports.BitBuffer = BitBuffer;
|
|
1303
|
+
exports.PRECISION_SCHEMES = precisionSchemes;
|
|
1304
|
+
exports.base64Decode = base64Decode;
|
|
1305
|
+
exports.base64Encode = base64Encode;
|
|
1306
|
+
exports.base64FloatParam = base64FloatParam;
|
|
1307
|
+
exports.base64Param = base64Param;
|
|
1308
|
+
exports.binaryParam = binaryParam;
|
|
475
1309
|
exports.boolParam = boolParam;
|
|
1310
|
+
exports.bytesToFloat = bytesToFloat;
|
|
1311
|
+
exports.clearParams = clearParams;
|
|
476
1312
|
exports.codeParam = codeParam;
|
|
477
1313
|
exports.codesParam = codesParam;
|
|
1314
|
+
exports.createLookupMap = createLookupMap;
|
|
478
1315
|
exports.defStringParam = defStringParam;
|
|
1316
|
+
exports.encodeFloatAllModes = encodeFloatAllModes;
|
|
1317
|
+
exports.encodePointAllModes = encodePointAllModes;
|
|
479
1318
|
exports.enumParam = enumParam;
|
|
480
1319
|
exports.floatParam = floatParam;
|
|
1320
|
+
exports.floatToBytes = floatToBytes;
|
|
1321
|
+
exports.fromFixedPoint = fromFixedPoint;
|
|
1322
|
+
exports.fromFloat = fromFloat;
|
|
481
1323
|
exports.getCurrentParams = getCurrentParams;
|
|
482
1324
|
exports.getDefaultStrategy = getDefaultStrategy;
|
|
483
1325
|
exports.hashStrategy = hashStrategy;
|
|
@@ -485,21 +1327,29 @@ exports.intParam = intParam;
|
|
|
485
1327
|
exports.multiFloatParam = multiFloatParam;
|
|
486
1328
|
exports.multiIntParam = multiIntParam;
|
|
487
1329
|
exports.multiStringParam = multiStringParam;
|
|
1330
|
+
exports.notifyLocationChange = notifyLocationChange;
|
|
488
1331
|
exports.numberArrayParam = numberArrayParam;
|
|
489
1332
|
exports.optIntParam = optIntParam;
|
|
490
1333
|
exports.paginationParam = paginationParam;
|
|
491
1334
|
exports.parseMultiParams = parseMultiParams;
|
|
492
1335
|
exports.parseParams = parseParams;
|
|
1336
|
+
exports.pointParam = pointParam;
|
|
1337
|
+
exports.precisionSchemes = precisionSchemes;
|
|
493
1338
|
exports.queryStrategy = queryStrategy;
|
|
1339
|
+
exports.resolveAlphabet = resolveAlphabet;
|
|
1340
|
+
exports.resolvePrecision = resolvePrecision;
|
|
494
1341
|
exports.serializeMultiParams = serializeMultiParams;
|
|
495
1342
|
exports.serializeParams = serializeParams;
|
|
496
1343
|
exports.setDefaultStrategy = setDefaultStrategy;
|
|
497
1344
|
exports.stringParam = stringParam;
|
|
498
1345
|
exports.stringsParam = stringsParam;
|
|
1346
|
+
exports.toFixedPoint = toFixedPoint;
|
|
1347
|
+
exports.toFloat = toFloat;
|
|
499
1348
|
exports.updateUrl = updateUrl;
|
|
500
|
-
exports.
|
|
501
|
-
exports.
|
|
502
|
-
exports.
|
|
503
|
-
exports.
|
|
1349
|
+
exports.useMultiUrlState = useMultiUrlState;
|
|
1350
|
+
exports.useMultiUrlStates = useMultiUrlStates;
|
|
1351
|
+
exports.useUrlState = useUrlState;
|
|
1352
|
+
exports.useUrlStates = useUrlStates;
|
|
1353
|
+
exports.validateAlphabet = validateAlphabet;
|
|
504
1354
|
//# sourceMappingURL=hash.cjs.map
|
|
505
1355
|
//# sourceMappingURL=hash.cjs.map
|