mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-10 19:07:17 +01:00
ts_core 0.1.1, web_ui 0.4.0: consume @kuhyx/ts-config
The two shared packages are the first repos on the preset (link:../ts_config inside the monorepo; consumers outside use the git tag). Each eslint.config.js is now the defineConfig() call plus one override with its reason: a public API name (`nextChance`, `fuzzyMatch`) the boolean-prefix rule would rename, which is a breaking change for the three repos importing it. Everything else the preset flagged is fixed in code, not disabled: key order (perfectionist), hex casing, sort compare functions in tests, Iterator#map().toArray(), explicit returns on multiline arrows. web_ui: react-refresh/only-export-components was a real finding -- range-slider.tsx exported `steppedValue` next to the component, which breaks Vite HMR for the module. The keyboard contract moves to stepped-value.ts as `steppedValue` + a new pure `keyTarget(key, values, current)`, which also resolves unicorn/no-late-event-control: the handler now decides the target first and calls preventDefault() only when it owns the key. Its tests move with it (the 250-line gate caught the combined file). Minor bump for the new export. dist/ rebuilt in both packages. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DnAYyNH1Sm4iJNidfYbrZJ
This commit is contained in:
parent
0f9699d4f3
commit
c5b3fceeae
2
ts_core/dist/clock.d.ts.map
vendored
2
ts_core/dist/clock.d.ts.map
vendored
@ -1 +1 @@
|
||||
{"version":3,"file":"clock.d.ts","sourceRoot":"","sources":["../src/clock.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,WAAW,KAAK;IACpB,4EAA4E;IAC5E,GAAG,EAAE,MAAM,MAAM,CAAA;CAClB;AAED,2EAA2E;AAC3E,eAAO,MAAM,eAAe,QAAO,KAEjC,CAAA;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,SAAS,EAAE,KAAyB,CAAA;AAEjD,sEAAsE;AACtE,MAAM,WAAW,WAAY,SAAQ,KAAK;IACxC,mEAAmE;IACnE,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IAElC,2DAA2D;IAC3D,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;CAC9B;AAED,uEAAuE;AACvE,eAAO,MAAM,iBAAiB,GAAI,gBAAW,KAAG,WAW/C,CAAA"}
|
||||
{"version":3,"file":"clock.d.ts","sourceRoot":"","sources":["../src/clock.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,WAAW,KAAK;IACpB,4EAA4E;IAC5E,GAAG,EAAE,MAAM,MAAM,CAAA;CAClB;AAED,2EAA2E;AAC3E,eAAO,MAAM,eAAe,QAAO,KAAmD,CAAA;AAEtF;;;;;;;GAOG;AACH,eAAO,MAAM,SAAS,EAAE,KAAyB,CAAA;AAEjD,sEAAsE;AACtE,MAAM,WAAW,WAAY,SAAQ,KAAK;IACxC,mEAAmE;IACnE,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IAElC,2DAA2D;IAC3D,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;CAC9B;AAED,uEAAuE;AACvE,eAAO,MAAM,iBAAiB,GAAI,gBAAW,KAAG,WAW/C,CAAA"}
|
||||
6
ts_core/dist/clock.js
vendored
6
ts_core/dist/clock.js
vendored
@ -1,7 +1,5 @@
|
||||
/** A {@link Clock} backed by the browser's monotonic performance timer. */
|
||||
export const createRealClock = () => ({
|
||||
now: () => performance.now(),
|
||||
});
|
||||
export const createRealClock = () => ({ now: () => performance.now() });
|
||||
/**
|
||||
* The one real clock instance UI code should pass around.
|
||||
*
|
||||
@ -15,10 +13,10 @@ export const realClock = createRealClock();
|
||||
export const createManualClock = (startMs = 0) => {
|
||||
let current = startMs;
|
||||
return {
|
||||
now: () => current,
|
||||
advance: (deltaMs) => {
|
||||
current += deltaMs;
|
||||
},
|
||||
now: () => current,
|
||||
set: (timeMs) => {
|
||||
current = timeMs;
|
||||
},
|
||||
|
||||
2
ts_core/dist/clock.js.map
vendored
2
ts_core/dist/clock.js.map
vendored
@ -1 +1 @@
|
||||
{"version":3,"file":"clock.js","sourceRoot":"","sources":["../src/clock.ts"],"names":[],"mappings":"AAaA,2EAA2E;AAC3E,MAAM,CAAC,MAAM,eAAe,GAAG,GAAU,EAAE,CAAC,CAAC;IAC3C,GAAG,EAAE,GAAW,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE;CACrC,CAAC,CAAA;AAEF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,SAAS,GAAU,eAAe,EAAE,CAAA;AAWjD,uEAAuE;AACvE,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,OAAO,GAAG,CAAC,EAAe,EAAE;IAC5D,IAAI,OAAO,GAAG,OAAO,CAAA;IACrB,OAAO;QACL,GAAG,EAAE,GAAW,EAAE,CAAC,OAAO;QAC1B,OAAO,EAAE,CAAC,OAAe,EAAQ,EAAE;YACjC,OAAO,IAAI,OAAO,CAAA;QACpB,CAAC;QACD,GAAG,EAAE,CAAC,MAAc,EAAQ,EAAE;YAC5B,OAAO,GAAG,MAAM,CAAA;QAClB,CAAC;KACF,CAAA;AACH,CAAC,CAAA"}
|
||||
{"version":3,"file":"clock.js","sourceRoot":"","sources":["../src/clock.ts"],"names":[],"mappings":"AAaA,2EAA2E;AAC3E,MAAM,CAAC,MAAM,eAAe,GAAG,GAAU,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,GAAW,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,EAAE,CAAC,CAAA;AAEtF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,SAAS,GAAU,eAAe,EAAE,CAAA;AAWjD,uEAAuE;AACvE,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,OAAO,GAAG,CAAC,EAAe,EAAE;IAC5D,IAAI,OAAO,GAAG,OAAO,CAAA;IACrB,OAAO;QACL,OAAO,EAAE,CAAC,OAAe,EAAQ,EAAE;YACjC,OAAO,IAAI,OAAO,CAAA;QACpB,CAAC;QACD,GAAG,EAAE,GAAW,EAAE,CAAC,OAAO;QAC1B,GAAG,EAAE,CAAC,MAAc,EAAQ,EAAE;YAC5B,OAAO,GAAG,MAAM,CAAA;QAClB,CAAC;KACF,CAAA;AACH,CAAC,CAAA"}
|
||||
2
ts_core/dist/index.d.ts
vendored
2
ts_core/dist/index.d.ts
vendored
@ -6,6 +6,6 @@
|
||||
* -- that is the coupling that makes people vendor their own copy instead,
|
||||
* which is how these ended up duplicated four ways to begin with.
|
||||
*/
|
||||
export { createManualClock, createRealClock, realClock, type Clock, type ManualClock, } from './clock.ts';
|
||||
export { type Clock, createManualClock, createRealClock, type ManualClock, realClock, } from './clock.ts';
|
||||
export { createRng, createSeededRng, nextChance, nextFloat, nextInt, pick, type Rng, type SeededRng, } from './rng.ts';
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
2
ts_core/dist/index.d.ts.map
vendored
2
ts_core/dist/index.d.ts.map
vendored
@ -1 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,SAAS,EACT,KAAK,KAAK,EACV,KAAK,WAAW,GACjB,MAAM,YAAY,CAAA;AACnB,OAAO,EACL,SAAS,EACT,eAAe,EACf,UAAU,EACV,SAAS,EACT,OAAO,EACP,IAAI,EACJ,KAAK,GAAG,EACR,KAAK,SAAS,GACf,MAAM,UAAU,CAAA"}
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EACL,KAAK,KAAK,EACV,iBAAiB,EACjB,eAAe,EACf,KAAK,WAAW,EAChB,SAAS,GACV,MAAM,YAAY,CAAA;AACnB,OAAO,EACL,SAAS,EACT,eAAe,EACf,UAAU,EACV,SAAS,EACT,OAAO,EACP,IAAI,EACJ,KAAK,GAAG,EACR,KAAK,SAAS,GACf,MAAM,UAAU,CAAA"}
|
||||
2
ts_core/dist/index.js.map
vendored
2
ts_core/dist/index.js.map
vendored
@ -1 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,SAAS,GAGV,MAAM,YAAY,CAAA;AACnB,OAAO,EACL,SAAS,EACT,eAAe,EACf,UAAU,EACV,SAAS,EACT,OAAO,EACP,IAAI,GAGL,MAAM,UAAU,CAAA"}
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAEL,iBAAiB,EACjB,eAAe,EAEf,SAAS,GACV,MAAM,YAAY,CAAA;AACnB,OAAO,EACL,SAAS,EACT,eAAe,EACf,UAAU,EACV,SAAS,EACT,OAAO,EACP,IAAI,GAGL,MAAM,UAAU,CAAA"}
|
||||
4
ts_core/dist/rng.d.ts
vendored
4
ts_core/dist/rng.d.ts
vendored
@ -54,14 +54,14 @@ export declare const pick: <T>(rng: Rng, items: readonly [T, ...T[]]) => T;
|
||||
* around rather than threading mutable state through free functions.
|
||||
*/
|
||||
export interface SeededRng {
|
||||
/** True with the given probability in [0, 1]. */
|
||||
chance: (probability: number) => boolean;
|
||||
/** Uniform float in [0, 1). */
|
||||
float: () => number;
|
||||
/** Uniform integer in [minInclusive, maxInclusive]. */
|
||||
int: (minInclusive: number, maxInclusive: number) => number;
|
||||
/** Uniform element of a non-empty array. */
|
||||
pick: <T>(items: readonly T[]) => T;
|
||||
/** True with the given probability in [0, 1]. */
|
||||
chance: (probability: number) => boolean;
|
||||
}
|
||||
/**
|
||||
* Creates a {@link SeededRng} over the same mulberry32 core.
|
||||
|
||||
2
ts_core/dist/rng.d.ts.map
vendored
2
ts_core/dist/rng.d.ts.map
vendored
@ -1 +1 @@
|
||||
{"version":3,"file":"rng.d.ts","sourceRoot":"","sources":["../src/rng.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,gDAAgD;AAChD,MAAM,WAAW,GAAG;IAClB,qDAAqD;IACrD,CAAC,EAAE,MAAM,CAAA;CACV;AAED,mDAAmD;AACnD,eAAO,MAAM,SAAS,GAAI,MAAM,MAAM,KAAG,GAA0B,CAAA;AAEnE;;;;;GAKG;AACH,eAAO,MAAM,SAAS,GAAI,KAAK,GAAG,KAAG,MAMpC,CAAA;AAED,uEAAuE;AACvE,eAAO,MAAM,OAAO,GAClB,KAAK,GAAG,EACR,cAAc,MAAM,EACpB,cAAc,MAAM,KACnB,MAC4E,CAAA;AAE/E,2DAA2D;AAC3D,eAAO,MAAM,UAAU,GAAI,KAAK,GAAG,EAAE,aAAa,MAAM,KAAG,OAC7B,CAAA;AAE9B;;;;;;GAMG;AACH,eAAO,MAAM,IAAI,GAAI,CAAC,EAAE,KAAK,GAAG,EAAE,OAAO,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,KAAG,CAW/D,CAAA;AAED;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB,+BAA+B;IAC/B,KAAK,EAAE,MAAM,MAAM,CAAA;IACnB,uDAAuD;IACvD,GAAG,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,MAAM,CAAA;IAC3D,4CAA4C;IAC5C,IAAI,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,KAAK,CAAC,CAAA;IACnC,iDAAiD;IACjD,MAAM,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAA;CACzC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAAI,MAAM,MAAM,KAAG,SAgB9C,CAAA"}
|
||||
{"version":3,"file":"rng.d.ts","sourceRoot":"","sources":["../src/rng.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,gDAAgD;AAChD,MAAM,WAAW,GAAG;IAClB,qDAAqD;IACrD,CAAC,EAAE,MAAM,CAAA;CACV;AAED,mDAAmD;AACnD,eAAO,MAAM,SAAS,GAAI,MAAM,MAAM,KAAG,GAA0B,CAAA;AAEnE;;;;;GAKG;AACH,eAAO,MAAM,SAAS,GAAI,KAAK,GAAG,KAAG,MAMpC,CAAA;AAED,uEAAuE;AACvE,eAAO,MAAM,OAAO,GAClB,KAAK,GAAG,EACR,cAAc,MAAM,EACpB,cAAc,MAAM,KACnB,MAC4E,CAAA;AAE/E,2DAA2D;AAC3D,eAAO,MAAM,UAAU,GAAI,KAAK,GAAG,EAAE,aAAa,MAAM,KAAG,OAC7B,CAAA;AAE9B;;;;;;GAMG;AACH,eAAO,MAAM,IAAI,GAAI,CAAC,EAAE,KAAK,GAAG,EAAE,OAAO,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,KAAG,CAW/D,CAAA;AAED;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB,iDAAiD;IACjD,MAAM,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAA;IACxC,+BAA+B;IAC/B,KAAK,EAAE,MAAM,MAAM,CAAA;IACnB,uDAAuD;IACvD,GAAG,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,MAAM,CAAA;IAC3D,4CAA4C;IAC5C,IAAI,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,KAAK,CAAC,CAAA;CACpC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAAI,MAAM,MAAM,KAAG,SAgB9C,CAAA"}
|
||||
4
ts_core/dist/rng.js
vendored
4
ts_core/dist/rng.js
vendored
@ -32,7 +32,7 @@ export const createRng = (seed) => ({ s: seed >>> 0 });
|
||||
* of it, so they all consume exactly one step per call.
|
||||
*/
|
||||
export const nextFloat = (rng) => {
|
||||
rng.s = (rng.s + 0x6d_2b_79_f5) >>> 0;
|
||||
rng.s = (rng.s + 0x6D_2B_79_F5) >>> 0;
|
||||
let t = rng.s;
|
||||
t = Math.imul(t ^ (t >>> 15), t | 1);
|
||||
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
||||
@ -72,6 +72,7 @@ export const createSeededRng = (seed) => {
|
||||
const state = createRng(seed);
|
||||
const float = () => nextFloat(state);
|
||||
return {
|
||||
chance: (probability) => float() < probability,
|
||||
float,
|
||||
int: (minInclusive, maxInclusive) => nextInt(state, minInclusive, maxInclusive),
|
||||
pick: (items) => {
|
||||
@ -81,7 +82,6 @@ export const createSeededRng = (seed) => {
|
||||
}
|
||||
return chosen;
|
||||
},
|
||||
chance: (probability) => float() < probability,
|
||||
};
|
||||
};
|
||||
//# sourceMappingURL=rng.js.map
|
||||
2
ts_core/dist/rng.js.map
vendored
2
ts_core/dist/rng.js.map
vendored
@ -1 +1 @@
|
||||
{"version":3,"file":"rng.js","sourceRoot":"","sources":["../src/rng.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAQH,mDAAmD;AACnD,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,IAAY,EAAO,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,KAAK,CAAC,EAAE,CAAC,CAAA;AAEnE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,GAAQ,EAAU,EAAE;IAC5C,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,aAAa,CAAC,KAAK,CAAC,CAAA;IACrC,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,CAAA;IACb,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAA;IACpC,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,CAAA;IACzC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,aAAa,CAAA;AACjD,CAAC,CAAA;AAED,uEAAuE;AACvE,MAAM,CAAC,MAAM,OAAO,GAAG,CACrB,GAAQ,EACR,YAAoB,EACpB,YAAoB,EACZ,EAAE,CACV,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,YAAY,GAAG,YAAY,GAAG,CAAC,CAAC,CAAC,CAAA;AAE/E,2DAA2D;AAC3D,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,GAAQ,EAAE,WAAmB,EAAW,EAAE,CACnE,SAAS,CAAC,GAAG,CAAC,GAAG,WAAW,CAAA;AAE9B;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,CAAI,GAAQ,EAAE,KAA2B,EAAK,EAAE;IAClE,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IAChD,IAAI,GAAG,GAAM,KAAK,CAAC,CAAC,CAAC,CAAA;IACrB,IAAI,IAAI,GAAG,CAAC,CAAA;IACZ,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;YACpB,GAAG,GAAG,IAAI,CAAA;QACZ,CAAC;QACD,IAAI,IAAI,CAAC,CAAA;IACX,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC,CAAA;AAiBD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,IAAY,EAAa,EAAE;IACzD,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,CAAA;IAC7B,MAAM,KAAK,GAAG,GAAW,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;IAC5C,OAAO;QACL,KAAK;QACL,GAAG,EAAE,CAAC,YAAoB,EAAE,YAAoB,EAAU,EAAE,CAC1D,OAAO,CAAC,KAAK,EAAE,YAAY,EAAE,YAAY,CAAC;QAC5C,IAAI,EAAE,CAAI,KAAmB,EAAK,EAAE;YAClC,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAA;YACxD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAA;YAC7C,CAAC;YACD,OAAO,MAAM,CAAA;QACf,CAAC;QACD,MAAM,EAAE,CAAC,WAAmB,EAAW,EAAE,CAAC,KAAK,EAAE,GAAG,WAAW;KAChE,CAAA;AACH,CAAC,CAAA"}
|
||||
{"version":3,"file":"rng.js","sourceRoot":"","sources":["../src/rng.ts"],"names":[],"mappings":"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"}
|
||||
@ -1,28 +1,24 @@
|
||||
// Type-aware flat config, mirroring web_ui's so the two shared TS packages
|
||||
// hold an identical bar.
|
||||
import js from "@eslint/js";
|
||||
import tseslint from "typescript-eslint";
|
||||
// The shared preset, nothing else local except one frozen API name. This
|
||||
// package is one of the repos the bar is defined for. Framework-free, so no
|
||||
// React layer.
|
||||
import { defineConfig } from "@kuhyx/ts-config";
|
||||
|
||||
export default tseslint.config(
|
||||
{ ignores: ["dist", "coverage", "eslint.config.js"] },
|
||||
{ linterOptions: { reportUnusedDisableDirectives: "error" } },
|
||||
|
||||
js.configs.recommended,
|
||||
...tseslint.configs.strictTypeChecked,
|
||||
...tseslint.configs.stylisticTypeChecked,
|
||||
|
||||
{
|
||||
files: ["**/*.ts"],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
project: ["./tsconfig.lint.json"],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
export default defineConfig({
|
||||
// Flat files under src/, no layers to fence and no alias configured.
|
||||
aliasOnly: false,
|
||||
overrides: [
|
||||
{
|
||||
files: ["src/rng.ts"],
|
||||
rules: {
|
||||
// `nextChance` is public API consumed by three repos; the preset's
|
||||
// boolean-name prefixes would rename it to `isNextChance`, which is
|
||||
// worse prose and a breaking change for no safety gain.
|
||||
"unicorn/consistent-boolean-name": "off",
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
// Every exported symbol crosses a package boundary, so an explicit
|
||||
// return type is documentation, not ceremony.
|
||||
"@typescript-eslint/explicit-function-return-type": "error",
|
||||
},
|
||||
},
|
||||
);
|
||||
],
|
||||
// tsconfig.json excludes the tests so they never land in dist/; the lint
|
||||
// project widens the include for type-aware rules (see tsconfig.lint.json).
|
||||
project: ["./tsconfig.lint.json"],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
});
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kuhyx/ts-core",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"private": false,
|
||||
"type": "module",
|
||||
"description": "Framework-free TypeScript utilities shared across kuhy's games and web apps: an injectable Clock and a seeded mulberry32 RNG. Deliberately no React and no CSS -- see @kuhyx/web-ui for the UI layer.",
|
||||
@ -9,7 +9,7 @@
|
||||
"node": ">=24",
|
||||
"pnpm": ">=10"
|
||||
},
|
||||
"packageManager": "pnpm@12.4.1+sha512.2e81e399d73fe8390dab25e06aa788ab7a5908248d2f5a370f82b481147a6a7a367bf8048f9a6fdb6460f21a66f0542dedb8b94ca2c8723596741920b1656d4c",
|
||||
"packageManager": "pnpm@12.4.2",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
@ -29,11 +29,12 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "10.0.1",
|
||||
"@kuhyx/ts-config": "link:../ts_config",
|
||||
"@types/node": "26.5.1",
|
||||
"@vitest/coverage-v8": "5.0.0",
|
||||
"@vitest/coverage-v8": "5.0.1",
|
||||
"eslint": "10.10.0",
|
||||
"typescript": "6.0.3",
|
||||
"typescript-eslint": "8.70.0",
|
||||
"vitest": "5.0.0"
|
||||
"vitest": "5.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
@ -169,12 +169,15 @@ importers:
|
||||
'@eslint/js':
|
||||
specifier: 10.0.1
|
||||
version: 10.0.1(eslint@10.10.0(supports-color@7.2.0))
|
||||
'@kuhyx/ts-config':
|
||||
specifier: link:../ts_config
|
||||
version: link:../ts_config
|
||||
'@types/node':
|
||||
specifier: 26.5.1
|
||||
version: 26.5.1
|
||||
'@vitest/coverage-v8':
|
||||
specifier: 5.0.0
|
||||
version: 5.0.0(vitest@5.0.0)
|
||||
specifier: 5.0.1
|
||||
version: 5.0.1(vitest@5.0.1)
|
||||
eslint:
|
||||
specifier: 10.10.0
|
||||
version: 10.10.0(supports-color@7.2.0)
|
||||
@ -185,8 +188,8 @@ importers:
|
||||
specifier: 8.70.0
|
||||
version: 8.70.0(eslint@10.10.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)
|
||||
vitest:
|
||||
specifier: 5.0.0
|
||||
version: 5.0.0(@types/node@26.5.1)(@vitest/coverage-v8@5.0.0)(vite@8.2.1(@types/node@26.5.1))
|
||||
specifier: 5.0.1
|
||||
version: 5.0.1(@types/node@26.5.1)(@vitest/coverage-v8@5.0.1)(vite@8.2.1(@types/node@26.5.1))
|
||||
|
||||
packages:
|
||||
|
||||
@ -280,9 +283,6 @@ packages:
|
||||
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5':
|
||||
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.6.0':
|
||||
resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==}
|
||||
|
||||
@ -471,11 +471,11 @@ packages:
|
||||
resolution: {integrity: sha512-BoC8PiO4Hkdo0TVJh9Ntxr5MxPDI7/oFsrygN5ADelFSeXG/qgNuucIGA+L5Z6JpPTE/uRfcTWtscjbUaufepQ==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@vitest/coverage-v8@5.0.0':
|
||||
resolution: {integrity: sha512-toMg6PZGCIa/lQNCDoASrfb1ly4hsUKXFtFYC9kD4t78o5Y6LyNJU7AENt8eHPr3quYdxaxK7hj2mnbFfUk9NA==}
|
||||
'@vitest/coverage-v8@5.0.1':
|
||||
resolution: {integrity: sha512-FRC8ACiudC3dI6MTplzRSYWHDRnIv2IPfbzs4FdoJNsMal/35sWV8hwIfV8ZcqzSPy+uXHeMVONt9CEqtOU17w==}
|
||||
peerDependencies:
|
||||
'@vitest/browser': 5.0.0
|
||||
vitest: 5.0.0
|
||||
'@vitest/browser': 5.0.1
|
||||
vitest: 5.0.1
|
||||
peerDependenciesMeta:
|
||||
'@vitest/browser':
|
||||
optional: true
|
||||
@ -488,8 +488,8 @@ packages:
|
||||
resolution: {integrity: sha512-1EOLRfsTMnyAr3+kEAsP4o9dhaDlGPpD7H5iLBBeq//YpNB1VIahkPhB+eRp9N2Dkfw8oySROjE3yf9XDeaIkQ==}
|
||||
engines: {node: '>=22'}
|
||||
|
||||
'@vitest/mocker@5.0.0':
|
||||
resolution: {integrity: sha512-66PGTMIiVJP3t4a5yxU9qPtf7MdTBs8jmToMvy+HVflB3Yy13WJZTtPePdvU+wjRV02SKK5doLbSA6o9pwOmiA==}
|
||||
'@vitest/mocker@5.0.1':
|
||||
resolution: {integrity: sha512-6K1DoBNAPGvuOcSsGA4D6x+5zEEff/KmOOP3uetT2TrGpVfI+HRHRnJJfKi5ib/g1vx8IYHQD8s0pbJz8WQI7Q==}
|
||||
peerDependencies:
|
||||
msw: ^2.4.9
|
||||
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||
@ -499,8 +499,8 @@ packages:
|
||||
vite:
|
||||
optional: true
|
||||
|
||||
'@vitest/spy@5.0.0':
|
||||
resolution: {integrity: sha512-uy+luWBAPw9XfthoHi5AkfHUnuPYEESjl0p/r+meoBnU8bxg5GDQ3Ey8MjcJ6sqahkL4PFyrvfMJJBw7LbU06g==}
|
||||
'@vitest/spy@5.0.1':
|
||||
resolution: {integrity: sha512-rbto/mF/SGERxEgYOek7Xm6B9b+y+mVoo+f4b2LymYO8zM1b7uB5nHuhVMTP2hxdzgxvGiZYGxGIaMvL5y180Q==}
|
||||
|
||||
acorn-jsx@5.3.2:
|
||||
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
||||
@ -978,20 +978,20 @@ packages:
|
||||
yaml:
|
||||
optional: true
|
||||
|
||||
vitest@5.0.0:
|
||||
resolution: {integrity: sha512-gpsMNoRhMjMktVxPtstOH4/PJuPyovVaMDr4oDilXaGH1EcqM2OE96SoHT2VIQ6fTGtTjqmHDrEu2X9RQiXf8Q==}
|
||||
vitest@5.0.1:
|
||||
resolution: {integrity: sha512-iA95lQbKEkvrtTkdAgnWbXfbipWiiWe/hDl2P5tMi6WFwD76G0NxXAGp/M9EOcYupeGJRr6wppMc7CoA41TQjg==}
|
||||
engines: {node: ^22.12.0 || ^24.0.0 || >=26.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@edge-runtime/vm': '*'
|
||||
'@opentelemetry/api': ^1.9.0
|
||||
'@types/node': ^22.0.0 || >=24.0.0
|
||||
'@vitest/browser-playwright': 5.0.0
|
||||
'@vitest/browser-preview': 5.0.0
|
||||
'@vitest/browser-playwright': 5.0.1
|
||||
'@vitest/browser-preview': 5.0.1
|
||||
'@vitest/browser-webdriverio': ^5.0.0-beta.5 || >=5.0.0
|
||||
'@vitest/coverage-istanbul': 5.0.0
|
||||
'@vitest/coverage-v8': 5.0.0
|
||||
'@vitest/ui': 5.0.0
|
||||
'@vitest/coverage-istanbul': 5.0.1
|
||||
'@vitest/coverage-v8': 5.0.1
|
||||
'@vitest/ui': 5.0.1
|
||||
happy-dom: '*'
|
||||
jsdom: '*'
|
||||
vite: ^6.4.0 || ^7.0.0 || ^8.0.0
|
||||
@ -1118,14 +1118,12 @@ snapshots:
|
||||
|
||||
'@jridgewell/resolve-uri@3.1.2': {}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5': {}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.6.0': {}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.31':
|
||||
dependencies:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
'@jridgewell/sourcemap-codec': 1.6.0
|
||||
|
||||
'@keyv/bigmap@1.3.1(keyv@5.6.0)':
|
||||
dependencies:
|
||||
@ -1289,7 +1287,7 @@ snapshots:
|
||||
'@typescript-eslint/types': 8.70.0
|
||||
eslint-visitor-keys: 5.0.1
|
||||
|
||||
'@vitest/coverage-v8@5.0.0(vitest@5.0.0)':
|
||||
'@vitest/coverage-v8@5.0.1(vitest@5.0.1)':
|
||||
dependencies:
|
||||
'@bcoe/v8-coverage': 1.0.2
|
||||
'@vitest/istanbul-lib-coverage': 1.0.1
|
||||
@ -1299,7 +1297,7 @@ snapshots:
|
||||
obug: 2.1.4
|
||||
std-env: 4.2.0
|
||||
tinyrainbow: 3.1.1
|
||||
vitest: 5.0.0(@types/node@26.5.1)(@vitest/coverage-v8@5.0.0)(vite@8.2.1(@types/node@26.5.1))
|
||||
vitest: 5.0.1(@types/node@26.5.1)(@vitest/coverage-v8@5.0.1)(vite@8.2.1(@types/node@26.5.1))
|
||||
|
||||
'@vitest/istanbul-lib-coverage@1.0.1': {}
|
||||
|
||||
@ -1307,16 +1305,16 @@ snapshots:
|
||||
dependencies:
|
||||
'@vitest/istanbul-lib-coverage': 1.0.1
|
||||
|
||||
'@vitest/mocker@5.0.0(vite@8.2.1(@types/node@26.5.1))':
|
||||
'@vitest/mocker@5.0.1(vite@8.2.1(@types/node@26.5.1))':
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
'@vitest/spy': 5.0.0
|
||||
'@vitest/spy': 5.0.1
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 1.3.1
|
||||
optionalDependencies:
|
||||
vite: 8.2.1(@types/node@26.5.1)
|
||||
|
||||
'@vitest/spy@5.0.0': {}
|
||||
'@vitest/spy@5.0.1': {}
|
||||
|
||||
acorn-jsx@5.3.2(acorn@8.18.0):
|
||||
dependencies:
|
||||
@ -1725,10 +1723,10 @@ snapshots:
|
||||
'@types/node': 26.5.1
|
||||
fsevents: 2.3.3
|
||||
|
||||
vitest@5.0.0(@types/node@26.5.1)(@vitest/coverage-v8@5.0.0)(vite@8.2.1(@types/node@26.5.1)):
|
||||
vitest@5.0.1(@types/node@26.5.1)(@vitest/coverage-v8@5.0.1)(vite@8.2.1(@types/node@26.5.1)):
|
||||
dependencies:
|
||||
'@types/chai': 5.2.3
|
||||
'@vitest/mocker': 5.0.0(vite@8.2.1(@types/node@26.5.1))
|
||||
'@vitest/mocker': 5.0.1(vite@8.2.1(@types/node@26.5.1))
|
||||
chai: 6.2.2
|
||||
es-module-lexer: 2.3.2
|
||||
expect-type: 1.4.0
|
||||
@ -1743,7 +1741,7 @@ snapshots:
|
||||
why-is-node-running: 2.3.0
|
||||
optionalDependencies:
|
||||
'@types/node': 26.5.1
|
||||
'@vitest/coverage-v8': 5.0.0(vitest@5.0.0)
|
||||
'@vitest/coverage-v8': 5.0.1(vitest@5.0.1)
|
||||
transitivePeerDependencies:
|
||||
- msw
|
||||
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import {
|
||||
type Clock,
|
||||
createManualClock,
|
||||
createRealClock,
|
||||
realClock,
|
||||
type Clock,
|
||||
} from './clock.ts'
|
||||
|
||||
describe('createRealClock', () => {
|
||||
|
||||
@ -12,9 +12,7 @@ export interface Clock {
|
||||
}
|
||||
|
||||
/** A {@link Clock} backed by the browser's monotonic performance timer. */
|
||||
export const createRealClock = (): Clock => ({
|
||||
now: (): number => performance.now(),
|
||||
})
|
||||
export const createRealClock = (): Clock => ({ now: (): number => performance.now() })
|
||||
|
||||
/**
|
||||
* The one real clock instance UI code should pass around.
|
||||
@ -39,10 +37,10 @@ export interface ManualClock extends Clock {
|
||||
export const createManualClock = (startMs = 0): ManualClock => {
|
||||
let current = startMs
|
||||
return {
|
||||
now: (): number => current,
|
||||
advance: (deltaMs: number): void => {
|
||||
current += deltaMs
|
||||
},
|
||||
now: (): number => current,
|
||||
set: (timeMs: number): void => {
|
||||
current = timeMs
|
||||
},
|
||||
|
||||
@ -7,11 +7,11 @@
|
||||
* which is how these ended up duplicated four ways to begin with.
|
||||
*/
|
||||
export {
|
||||
type Clock,
|
||||
createManualClock,
|
||||
createRealClock,
|
||||
realClock,
|
||||
type Clock,
|
||||
type ManualClock,
|
||||
realClock,
|
||||
} from './clock.ts'
|
||||
export {
|
||||
createRng,
|
||||
|
||||
@ -20,8 +20,8 @@ import {
|
||||
* would defeat the entire point of the test.
|
||||
*/
|
||||
const GOLDEN_12345 = [
|
||||
0.979_728_267_760_947_3, 0.306_752_264_499_664_3, 0.484_205_421_525_985,
|
||||
0.817_934_412_509_203, 0.509_428_369_347_006_1,
|
||||
0.9797282677609473, 0.3067522644996643, 0.484205421525985,
|
||||
0.817934412509203, 0.5094283693470061,
|
||||
]
|
||||
|
||||
describe('mulberry32 core', () => {
|
||||
@ -51,7 +51,7 @@ describe('mulberry32 core', () => {
|
||||
|
||||
it('stays within [0, 1) across many draws', () => {
|
||||
const rng = createRng(42)
|
||||
for (let i = 0; i < 1000; i += 1) {
|
||||
for (let index = 0; index < 1000; index += 1) {
|
||||
const value = nextFloat(rng)
|
||||
expect(value).toBeGreaterThanOrEqual(0)
|
||||
expect(value).toBeLessThan(1)
|
||||
@ -68,10 +68,10 @@ describe('nextInt', () => {
|
||||
it('is inclusive of both bounds', () => {
|
||||
const rng = createRng(3)
|
||||
const seen = new Set<number>()
|
||||
for (let i = 0; i < 500; i += 1) {
|
||||
for (let index = 0; index < 500; index += 1) {
|
||||
seen.add(nextInt(rng, 1, 3))
|
||||
}
|
||||
expect([...seen].sort()).toEqual([1, 2, 3])
|
||||
expect([...seen].toSorted((a, b) => a - b)).toEqual([1, 2, 3])
|
||||
})
|
||||
|
||||
it('returns the only value when min equals max', () => {
|
||||
@ -97,7 +97,7 @@ describe('nextChance', () => {
|
||||
it('lands near the requested probability over many draws', () => {
|
||||
const rng = createRng(2024)
|
||||
let hits = 0
|
||||
for (let i = 0; i < 4000; i += 1) {
|
||||
for (let index = 0; index < 4000; index += 1) {
|
||||
if (nextChance(rng, 0.25)) {
|
||||
hits += 1
|
||||
}
|
||||
@ -115,10 +115,10 @@ describe('pick', () => {
|
||||
it('eventually returns every element', () => {
|
||||
const rng = createRng(4)
|
||||
const seen = new Set<string>()
|
||||
for (let i = 0; i < 500; i += 1) {
|
||||
for (let index = 0; index < 500; index += 1) {
|
||||
seen.add(pick(rng, ['a', 'b', 'c']))
|
||||
}
|
||||
expect([...seen].sort()).toEqual(['a', 'b', 'c'])
|
||||
expect([...seen].toSorted((a, b) => a.localeCompare(b))).toEqual(['a', 'b', 'c'])
|
||||
})
|
||||
|
||||
it('is deterministic for a given seed', () => {
|
||||
|
||||
@ -40,7 +40,7 @@ export const createRng = (seed: number): Rng => ({ s: seed >>> 0 })
|
||||
* of it, so they all consume exactly one step per call.
|
||||
*/
|
||||
export const nextFloat = (rng: Rng): number => {
|
||||
rng.s = (rng.s + 0x6d_2b_79_f5) >>> 0
|
||||
rng.s = (rng.s + 0x6D_2B_79_F5) >>> 0
|
||||
let t = rng.s
|
||||
t = Math.imul(t ^ (t >>> 15), t | 1)
|
||||
t ^= t + Math.imul(t ^ (t >>> 7), t | 61)
|
||||
@ -84,14 +84,14 @@ export const pick = <T>(rng: Rng, items: readonly [T, ...T[]]): T => {
|
||||
* around rather than threading mutable state through free functions.
|
||||
*/
|
||||
export interface SeededRng {
|
||||
/** True with the given probability in [0, 1]. */
|
||||
chance: (probability: number) => boolean
|
||||
/** Uniform float in [0, 1). */
|
||||
float: () => number
|
||||
/** Uniform integer in [minInclusive, maxInclusive]. */
|
||||
int: (minInclusive: number, maxInclusive: number) => number
|
||||
/** Uniform element of a non-empty array. */
|
||||
pick: <T>(items: readonly T[]) => T
|
||||
/** True with the given probability in [0, 1]. */
|
||||
chance: (probability: number) => boolean
|
||||
}
|
||||
|
||||
/**
|
||||
@ -105,6 +105,7 @@ export const createSeededRng = (seed: number): SeededRng => {
|
||||
const state = createRng(seed)
|
||||
const float = (): number => nextFloat(state)
|
||||
return {
|
||||
chance: (probability: number): boolean => float() < probability,
|
||||
float,
|
||||
int: (minInclusive: number, maxInclusive: number): number =>
|
||||
nextInt(state, minInclusive, maxInclusive),
|
||||
@ -115,6 +116,5 @@ export const createSeededRng = (seed: number): SeededRng => {
|
||||
}
|
||||
return chosen
|
||||
},
|
||||
chance: (probability: number): boolean => float() < probability,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
{
|
||||
// Strictness comes from the shared base; only output/lib options live here.
|
||||
"extends": "@kuhyx/ts-config/tsconfig",
|
||||
"compilerOptions": {
|
||||
"target": "ES2023",
|
||||
// DOM is here only for `performance.now()` in clock.ts -- the package
|
||||
@ -9,21 +11,6 @@
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
|
||||
/* Strictness: everything on, matching the consumers so a file that
|
||||
compiles here compiles there. */
|
||||
"strict": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noImplicitOverride": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
"useUnknownInCatchVariables": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"isolatedModules": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
|
||||
/* Ships a prebuilt dist/ (ESM + .d.ts) because consumers use Vite, which
|
||||
does not transpile dependencies. */
|
||||
"rewriteRelativeImportExtensions": true,
|
||||
@ -32,10 +19,7 @@
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["src/**/*.test.ts"]
|
||||
|
||||
3
web_ui/dist/index.d.ts
vendored
3
web_ui/dist/index.d.ts
vendored
@ -5,6 +5,7 @@
|
||||
* `import "@kuhyx/web-ui/tokens.css";`
|
||||
*/
|
||||
export { clamp01, fractionFromPointer, nth, quantileValue, valueQuantile } from "./quantile.ts";
|
||||
export { RangeSlider, steppedValue, type RangeSliderProps } from "./range-slider.tsx";
|
||||
export { RangeSlider, type RangeSliderProps } from "./range-slider.tsx";
|
||||
export { keyTarget, steppedValue } from "./stepped-value.ts";
|
||||
export { fuzzyMatch } from "./text.ts";
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
2
web_ui/dist/index.d.ts.map
vendored
2
web_ui/dist/index.d.ts.map
vendored
@ -1 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,OAAO,EAAE,mBAAmB,EAAE,GAAG,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAChG,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtF,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC"}
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,OAAO,EAAE,mBAAmB,EAAE,GAAG,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAChG,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACxE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC"}
|
||||
3
web_ui/dist/index.js
vendored
3
web_ui/dist/index.js
vendored
@ -5,6 +5,7 @@
|
||||
* `import "@kuhyx/web-ui/tokens.css";`
|
||||
*/
|
||||
export { clamp01, fractionFromPointer, nth, quantileValue, valueQuantile } from "./quantile.js";
|
||||
export { RangeSlider, steppedValue } from "./range-slider.js";
|
||||
export { RangeSlider } from "./range-slider.js";
|
||||
export { keyTarget, steppedValue } from "./stepped-value.js";
|
||||
export { fuzzyMatch } from "./text.js";
|
||||
//# sourceMappingURL=index.js.map
|
||||
2
web_ui/dist/index.js.map
vendored
2
web_ui/dist/index.js.map
vendored
@ -1 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,OAAO,EAAE,mBAAmB,EAAE,GAAG,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAChG,OAAO,EAAE,WAAW,EAAE,YAAY,EAAyB,MAAM,mBAAoB,CAAC;AACtF,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC"}
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,OAAO,EAAE,mBAAmB,EAAE,GAAG,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAChG,OAAO,EAAE,WAAW,EAAyB,MAAM,mBAAoB,CAAC;AACxE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC"}
|
||||
2
web_ui/dist/quantile.d.ts.map
vendored
2
web_ui/dist/quantile.d.ts.map
vendored
@ -1 +1 @@
|
||||
{"version":3,"file":"quantile.d.ts","sourceRoot":"","sources":["../src/quantile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;;;GAIG;AACH,wBAAgB,GAAG,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAMpE;AAED,kCAAkC;AAClC,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEzC;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAS1E;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAa9E;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAE1E"}
|
||||
{"version":3,"file":"quantile.d.ts","sourceRoot":"","sources":["../src/quantile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;;;GAIG;AACH,wBAAgB,GAAG,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAMpE;AAED,kCAAkC;AAClC,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEzC;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAS1E;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAY9E;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAE1E"}
|
||||
4
web_ui/dist/quantile.js
vendored
4
web_ui/dist/quantile.js
vendored
@ -52,9 +52,7 @@ export function quantileValue(sorted, f) {
|
||||
*/
|
||||
export function valueQuantile(sorted, value) {
|
||||
const n = sorted.length;
|
||||
if (n <= 1)
|
||||
return 0;
|
||||
if (value <= nth(sorted, 0))
|
||||
if ((n <= 1) || (value <= nth(sorted, 0)))
|
||||
return 0;
|
||||
if (value >= nth(sorted, n - 1))
|
||||
return 1;
|
||||
|
||||
2
web_ui/dist/quantile.js.map
vendored
2
web_ui/dist/quantile.js.map
vendored
@ -1 +1 @@
|
||||
{"version":3,"file":"quantile.js","sourceRoot":"","sources":["../src/quantile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;;;GAIG;AACH,MAAM,UAAU,GAAG,CAAC,MAAyB,EAAE,KAAa;IAC1D,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC5B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,IAAI,UAAU,CAAC,SAAS,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,kCAAkC;AAClC,MAAM,UAAU,OAAO,CAAC,CAAS;IAC/B,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AACrC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,MAAyB,EAAE,CAAS;IAChE,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC;IACxB,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACtB,MAAM,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACjC,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC1B,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC1B,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC1B,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC;AAC9C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,MAAyB,EAAE,KAAa;IACpE,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC;IACxB,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC;IACrB,IAAI,KAAK,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC;IACtC,IAAI,KAAK,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC;IAC1C,qEAAqE;IACrE,kDAAkD;IAClD,MAAM,EAAE,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC;IAC/C,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;IAClB,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC1B,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC1B,yDAAyD;IACzD,OAAO,CAAC,EAAE,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;AAChD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAa,EAAE,OAAe;IAChE,OAAO,IAAI,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;AAC5E,CAAC"}
|
||||
{"version":3,"file":"quantile.js","sourceRoot":"","sources":["../src/quantile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;;;GAIG;AACH,MAAM,UAAU,GAAG,CAAC,MAAyB,EAAE,KAAa;IAC1D,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC5B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,IAAI,UAAU,CAAC,SAAS,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,kCAAkC;AAClC,MAAM,UAAU,OAAO,CAAC,CAAS;IAC/B,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AACrC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,MAAyB,EAAE,CAAS;IAChE,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC;IACxB,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACtB,MAAM,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACjC,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC1B,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC1B,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC1B,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC;AAC9C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,MAAyB,EAAE,KAAa;IACpE,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC;IACxB,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC;IACpD,IAAI,KAAK,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC;IAC1C,qEAAqE;IACrE,kDAAkD;IAClD,MAAM,EAAE,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC;IAC/C,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;IAClB,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC1B,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC1B,yDAAyD;IACzD,OAAO,CAAC,EAAE,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;AAChD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAa,EAAE,OAAe;IAChE,OAAO,IAAI,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;AAC5E,CAAC"}
|
||||
11
web_ui/dist/range-slider.d.ts
vendored
11
web_ui/dist/range-slider.d.ts
vendored
@ -16,16 +16,5 @@ export interface RangeSliderProps {
|
||||
/** The full ascending distribution — not just its endpoints. */
|
||||
readonly values: readonly number[];
|
||||
}
|
||||
/**
|
||||
* Steps one thumb by `delta` positions through the distribution. Pure, so the
|
||||
* keyboard contract is testable without layout.
|
||||
*
|
||||
* Steps to the next *distinct* value rather than the next index. Real
|
||||
* distributions are lumpy -- awesome-mcp-explorer's star counts hold ~1500
|
||||
* duplicate zeros -- and an index step inside a run of equal values changes the
|
||||
* index while leaving the value (and therefore the thumb, and the filter)
|
||||
* exactly where it was. That reads as a dead arrow key.
|
||||
*/
|
||||
export declare function steppedValue(values: readonly number[], current: number, delta: number): number;
|
||||
export declare function RangeSlider({ format, hi, label, lo, onChange, values, }: RangeSliderProps): null | React.JSX.Element;
|
||||
//# sourceMappingURL=range-slider.d.ts.map
|
||||
2
web_ui/dist/range-slider.d.ts.map
vendored
2
web_ui/dist/range-slider.d.ts.map
vendored
@ -1 +1 @@
|
||||
{"version":3,"file":"range-slider.d.ts","sourceRoot":"","sources":["../src/range-slider.tsx"],"names":[],"mappings":"AAyBA,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC5C,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,QAAQ,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,gEAAgE;IAChE,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAID;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GACZ,MAAM,CAkBR;AAED,wBAAgB,WAAW,CAAC,EAC1B,MAAM,EACN,EAAE,EACF,KAAK,EACL,EAAE,EACF,QAAQ,EACR,MAAM,GACP,EAAE,gBAAgB,GAAG,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAyG7C"}
|
||||
{"version":3,"file":"range-slider.d.ts","sourceRoot":"","sources":["../src/range-slider.tsx"],"names":[],"mappings":"AA0BA,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC5C,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,QAAQ,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,gEAAgE;IAChE,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAID,wBAAgB,WAAW,CAAC,EAC1B,MAAM,EACN,EAAE,EACF,KAAK,EACL,EAAE,EACF,QAAQ,EACR,MAAM,GACP,EAAE,gBAAgB,GAAG,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAkG7C"}
|
||||
89
web_ui/dist/range-slider.js
vendored
89
web_ui/dist/range-slider.js
vendored
@ -22,36 +22,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
||||
*/
|
||||
import { useCallback, useState } from "react";
|
||||
import { fractionFromPointer, nth, quantileValue, valueQuantile } from "./quantile.js";
|
||||
/**
|
||||
* Steps one thumb by `delta` positions through the distribution. Pure, so the
|
||||
* keyboard contract is testable without layout.
|
||||
*
|
||||
* Steps to the next *distinct* value rather than the next index. Real
|
||||
* distributions are lumpy -- awesome-mcp-explorer's star counts hold ~1500
|
||||
* duplicate zeros -- and an index step inside a run of equal values changes the
|
||||
* index while leaving the value (and therefore the thumb, and the filter)
|
||||
* exactly where it was. That reads as a dead arrow key.
|
||||
*/
|
||||
export function steppedValue(values, current, delta) {
|
||||
const last = values.length - 1;
|
||||
// Round to a real sample index so a thumb sitting between two samples still
|
||||
// advances rather than stalling on a fractional step.
|
||||
const index = Math.round(valueQuantile(values, current) * last);
|
||||
const direction = Math.sign(delta);
|
||||
if (direction === 0)
|
||||
return nth(values, index);
|
||||
let cursor = index;
|
||||
for (let taken = 0; taken < Math.abs(delta); taken++) {
|
||||
let next = cursor + direction;
|
||||
while (next >= 0 && next <= last && nth(values, next) === nth(values, cursor)) {
|
||||
next += direction;
|
||||
}
|
||||
if (next < 0 || next > last)
|
||||
break;
|
||||
cursor = next;
|
||||
}
|
||||
return nth(values, cursor);
|
||||
}
|
||||
import { keyTarget } from "./stepped-value.js";
|
||||
export function RangeSlider({ format, hi, label, lo, onChange, values, }) {
|
||||
const [drag, setDrag] = useState(null);
|
||||
const apply = useCallback((thumb, value) => {
|
||||
@ -63,29 +34,17 @@ export function RangeSlider({ format, hi, label, lo, onChange, values, }) {
|
||||
// Reads the rect off the event's own target rather than a ref: the pointer
|
||||
// handlers are bound to the track, so currentTarget *is* the track. A ref
|
||||
// would add a null branch that cannot happen once the element is mounted.
|
||||
const valueAt = useCallback((track, clientX) => quantileValue(values, fractionFromPointer(track.getBoundingClientRect(), clientX)), [values]);
|
||||
const valueAt = useCallback((track, clientX) => {
|
||||
const fraction = fractionFromPointer(track.getBoundingClientRect(), clientX);
|
||||
return quantileValue(values, fraction);
|
||||
}, [values]);
|
||||
const onKeyDown = useCallback((thumb, event) => {
|
||||
const current = thumb === "lo" ? lo : hi;
|
||||
const step = (delta) => {
|
||||
event.preventDefault();
|
||||
apply(thumb, steppedValue(values, current, delta));
|
||||
};
|
||||
if (event.key === "ArrowLeft" || event.key === "ArrowDown")
|
||||
step(-1);
|
||||
else if (event.key === "ArrowRight" || event.key === "ArrowUp")
|
||||
step(1);
|
||||
else if (event.key === "PageDown")
|
||||
step(-10);
|
||||
else if (event.key === "PageUp")
|
||||
step(10);
|
||||
else if (event.key === "Home") {
|
||||
event.preventDefault();
|
||||
apply(thumb, nth(values, 0));
|
||||
}
|
||||
else if (event.key === "End") {
|
||||
event.preventDefault();
|
||||
apply(thumb, nth(values, values.length - 1));
|
||||
}
|
||||
const target = keyTarget(event.key, values, current);
|
||||
if (target === undefined)
|
||||
return;
|
||||
event.preventDefault();
|
||||
apply(thumb, target);
|
||||
}, [apply, hi, lo, values]);
|
||||
// Fewer than two values means there is nothing to range over.
|
||||
if (values.length < 2)
|
||||
@ -95,19 +54,21 @@ export function RangeSlider({ format, hi, label, lo, onChange, values, }) {
|
||||
const loFraction = valueQuantile(values, lo) * 100;
|
||||
const hiFraction = valueQuantile(values, hi) * 100;
|
||||
const name = label ?? "Range";
|
||||
const thumbProps = (thumb) => ({
|
||||
"aria-valuemax": highest,
|
||||
"aria-valuemin": lowest,
|
||||
"aria-valuenow": thumb === "lo" ? lo : hi,
|
||||
className: `slider-thumb slider-${thumb}`,
|
||||
onKeyDown: (event) => {
|
||||
onKeyDown(thumb, event);
|
||||
},
|
||||
role: "slider",
|
||||
style: { left: `${String(thumb === "lo" ? loFraction : hiFraction)}%` },
|
||||
tabIndex: 0,
|
||||
type: "button",
|
||||
});
|
||||
const thumbProps = (thumb) => {
|
||||
return {
|
||||
"aria-valuemax": highest,
|
||||
"aria-valuemin": lowest,
|
||||
"aria-valuenow": thumb === "lo" ? lo : hi,
|
||||
className: `slider-thumb slider-${thumb}`,
|
||||
onKeyDown: (event) => {
|
||||
onKeyDown(thumb, event);
|
||||
},
|
||||
role: "slider",
|
||||
style: { left: `${String(thumb === "lo" ? loFraction : hiFraction)}%` },
|
||||
tabIndex: 0,
|
||||
type: "button",
|
||||
};
|
||||
};
|
||||
return (_jsxs("div", { className: "slider", children: [label !== undefined && format !== undefined && (_jsxs("div", { className: "slider-head", children: [_jsx("span", { children: label }), _jsxs("span", { className: "slider-value", children: [format(lo), " \u2013 ", format(hi)] })] })), _jsxs("div", { className: "slider-track", onPointerDown: (event) => {
|
||||
const value = valueAt(event.currentTarget, event.clientX);
|
||||
// Grab whichever thumb is nearer, so a press anywhere on the track
|
||||
|
||||
2
web_ui/dist/range-slider.js.map
vendored
2
web_ui/dist/range-slider.js.map
vendored
File diff suppressed because one or more lines are too long
19
web_ui/dist/stepped-value.d.ts
vendored
Normal file
19
web_ui/dist/stepped-value.d.ts
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Steps one thumb by `delta` positions through the distribution. Pure, so the
|
||||
* keyboard contract is testable without layout.
|
||||
*
|
||||
* Steps to the next *distinct* value rather than the next index. Real
|
||||
* distributions are lumpy -- awesome-mcp-explorer's star counts hold ~1500
|
||||
* duplicate zeros -- and an index step inside a run of equal values changes the
|
||||
* index while leaving the value (and therefore the thumb, and the filter)
|
||||
* exactly where it was. That reads as a dead arrow key.
|
||||
*/
|
||||
export declare function steppedValue(values: readonly number[], current: number, delta: number): number;
|
||||
/**
|
||||
* The value a thumb at `current` moves to for a key, or `undefined` when the
|
||||
* key is not one the slider handles (so the event is left to the browser).
|
||||
* Arrow keys step one distinct value, Page keys ten, Home/End jump to the
|
||||
* bounds.
|
||||
*/
|
||||
export declare function keyTarget(key: string, values: readonly number[], current: number): number | undefined;
|
||||
//# sourceMappingURL=stepped-value.d.ts.map
|
||||
1
web_ui/dist/stepped-value.d.ts.map
vendored
Normal file
1
web_ui/dist/stepped-value.d.ts.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"stepped-value.d.ts","sourceRoot":"","sources":["../src/stepped-value.ts"],"names":[],"mappings":"AAOA;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GACZ,MAAM,CAkBR;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,OAAO,EAAE,MAAM,GACd,MAAM,GAAG,SAAS,CA0BpB"}
|
||||
70
web_ui/dist/stepped-value.js
vendored
Normal file
70
web_ui/dist/stepped-value.js
vendored
Normal file
@ -0,0 +1,70 @@
|
||||
/**
|
||||
* The keyboard contract of the range slider, kept pure and in its own module
|
||||
* so range-slider.tsx exports only the component (react-refresh needs that
|
||||
* for HMR to keep working) and the contract is testable without layout.
|
||||
*/
|
||||
import { nth, valueQuantile } from "./quantile.js";
|
||||
/**
|
||||
* Steps one thumb by `delta` positions through the distribution. Pure, so the
|
||||
* keyboard contract is testable without layout.
|
||||
*
|
||||
* Steps to the next *distinct* value rather than the next index. Real
|
||||
* distributions are lumpy -- awesome-mcp-explorer's star counts hold ~1500
|
||||
* duplicate zeros -- and an index step inside a run of equal values changes the
|
||||
* index while leaving the value (and therefore the thumb, and the filter)
|
||||
* exactly where it was. That reads as a dead arrow key.
|
||||
*/
|
||||
export function steppedValue(values, current, delta) {
|
||||
const last = values.length - 1;
|
||||
// Round to a real sample index so a thumb sitting between two samples still
|
||||
// advances rather than stalling on a fractional step.
|
||||
const index = Math.round(valueQuantile(values, current) * last);
|
||||
const direction = Math.sign(delta);
|
||||
if (direction === 0)
|
||||
return nth(values, index);
|
||||
let cursor = index;
|
||||
for (let taken = 0; taken < Math.abs(delta); taken++) {
|
||||
let next = cursor + direction;
|
||||
while (next >= 0 && next <= last && nth(values, next) === nth(values, cursor)) {
|
||||
next += direction;
|
||||
}
|
||||
if (next < 0 || next > last)
|
||||
break;
|
||||
cursor = next;
|
||||
}
|
||||
return nth(values, cursor);
|
||||
}
|
||||
/**
|
||||
* The value a thumb at `current` moves to for a key, or `undefined` when the
|
||||
* key is not one the slider handles (so the event is left to the browser).
|
||||
* Arrow keys step one distinct value, Page keys ten, Home/End jump to the
|
||||
* bounds.
|
||||
*/
|
||||
export function keyTarget(key, values, current) {
|
||||
switch (key) {
|
||||
case "ArrowDown":
|
||||
case "ArrowLeft": {
|
||||
return steppedValue(values, current, -1);
|
||||
}
|
||||
case "ArrowRight":
|
||||
case "ArrowUp": {
|
||||
return steppedValue(values, current, 1);
|
||||
}
|
||||
case "End": {
|
||||
return nth(values, values.length - 1);
|
||||
}
|
||||
case "Home": {
|
||||
return nth(values, 0);
|
||||
}
|
||||
case "PageDown": {
|
||||
return steppedValue(values, current, -10);
|
||||
}
|
||||
case "PageUp": {
|
||||
return steppedValue(values, current, 10);
|
||||
}
|
||||
default: {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=stepped-value.js.map
|
||||
1
web_ui/dist/stepped-value.js.map
vendored
Normal file
1
web_ui/dist/stepped-value.js.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"stepped-value.js","sourceRoot":"","sources":["../src/stepped-value.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,GAAG,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD;;;;;;;;;GASG;AACH,MAAM,UAAU,YAAY,CAC1B,MAAyB,EACzB,OAAe,EACf,KAAa;IAEb,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;IAC/B,4EAA4E;IAC5E,sDAAsD;IACtD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;IAChE,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACnC,IAAI,SAAS,KAAK,CAAC;QAAE,OAAO,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAE/C,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC;QACrD,IAAI,IAAI,GAAG,MAAM,GAAG,SAAS,CAAC;QAC9B,OAAO,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,IAAI,IAAI,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;YAC9E,IAAI,IAAI,SAAS,CAAC;QACpB,CAAC;QACD,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,IAAI;YAAE,MAAM;QACnC,MAAM,GAAG,IAAI,CAAC;IAChB,CAAC;IACD,OAAO,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAC7B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CACvB,GAAW,EACX,MAAyB,EACzB,OAAe;IAEf,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,WAAW,CAAC;QACjB,KAAK,WAAW,CAAC,CAAC,CAAC;YACjB,OAAO,YAAY,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;QAC3C,CAAC;QACD,KAAK,YAAY,CAAC;QAClB,KAAK,SAAS,CAAC,CAAC,CAAC;YACf,OAAO,YAAY,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;QAC1C,CAAC;QACD,KAAK,KAAK,CAAC,CAAC,CAAC;YACX,OAAO,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACxC,CAAC;QACD,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,OAAO,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QACxB,CAAC;QACD,KAAK,UAAU,CAAC,CAAC,CAAC;YAChB,OAAO,YAAY,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;QAC5C,CAAC;QACD,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,OAAO,YAAY,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;QAC3C,CAAC;QACD,OAAO,CAAC,CAAC,CAAC;YACR,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;AACH,CAAC"}
|
||||
@ -1,42 +1,25 @@
|
||||
// Type-aware flat config for the shared component layer.
|
||||
//
|
||||
// A trimmed version of awesome-mcp-explorer's: typescript-eslint's
|
||||
// *type-checked* strict + stylistic presets, so anything they flag is an error.
|
||||
// The consumers layer unicorn/sonarjs/perfectionist on top; this package keeps
|
||||
// to the presets its own devDependencies actually declare, because a config
|
||||
// referencing plugins that are not installed is a lint script that cannot run.
|
||||
import js from "@eslint/js";
|
||||
import tseslint from "typescript-eslint";
|
||||
// The shared preset with the React layer, plus one frozen API name. This
|
||||
// package is one of the repos the bar is defined for.
|
||||
import { defineConfig } from "@kuhyx/ts-config";
|
||||
|
||||
export default tseslint.config(
|
||||
// eslint.config.js itself is not in tsconfig.lint.json, so type-aware
|
||||
// rules cannot run on it; linting the linter config buys nothing anyway.
|
||||
{ ignores: ["dist", "coverage", "eslint.config.js"] },
|
||||
{ linterOptions: { reportUnusedDisableDirectives: "error" } },
|
||||
|
||||
js.configs.recommended,
|
||||
...tseslint.configs.strictTypeChecked,
|
||||
...tseslint.configs.stylisticTypeChecked,
|
||||
|
||||
{
|
||||
files: ["**/*.{ts,tsx}"],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
project: ["./tsconfig.lint.json"],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
export default defineConfig({
|
||||
// Flat files under src/, no layers to fence and no alias configured.
|
||||
aliasOnly: false,
|
||||
overrides: [
|
||||
{
|
||||
files: ["src/text.ts"],
|
||||
rules: {
|
||||
// `fuzzyMatch` is public API at four call sites in dufs-cloud and
|
||||
// awesome-mcp-explorer; the preset's prefix list would rename it to
|
||||
// `matchesFuzzy`, a breaking change for no safety gain. Revisit at
|
||||
// the next major.
|
||||
"unicorn/consistent-boolean-name": "off",
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
// Every exported symbol here is consumed across a package boundary, so
|
||||
// an explicit return type is documentation, not ceremony.
|
||||
"@typescript-eslint/explicit-function-return-type": "error",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
// Tests reach for non-null assertions on queries that cannot return null
|
||||
// in a passing test; asserting them again would only add noise.
|
||||
files: ["**/*.test.{ts,tsx}"],
|
||||
rules: { "@typescript-eslint/no-non-null-assertion": "off" },
|
||||
},
|
||||
);
|
||||
],
|
||||
// tsconfig.json excludes the tests so they never land in dist/; the lint
|
||||
// project widens the include for type-aware rules (see tsconfig.lint.json).
|
||||
project: ["./tsconfig.lint.json"],
|
||||
react: true,
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
});
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kuhyx/web-ui",
|
||||
"version": "0.3.2",
|
||||
"version": "0.4.0",
|
||||
"private": false,
|
||||
"type": "module",
|
||||
"description": "The shared token + component layer for kuhy's web apps: the frozen palette, spacing/radius/type scales as CSS custom properties, and the components that were each reimplemented in two or more repos.",
|
||||
@ -9,7 +9,7 @@
|
||||
"node": ">=24",
|
||||
"pnpm": ">=10"
|
||||
},
|
||||
"packageManager": "pnpm@12.4.1+sha512.2e81e399d73fe8390dab25e06aa788ab7a5908248d2f5a370f82b481147a6a7a367bf8048f9a6fdb6460f21a66f0542dedb8b94ca2c8723596741920b1656d4c",
|
||||
"packageManager": "pnpm@12.4.2",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
@ -34,19 +34,20 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "10.0.1",
|
||||
"@kuhyx/ts-config": "link:../ts_config",
|
||||
"@testing-library/jest-dom": "7.0.1",
|
||||
"@testing-library/react": "16.3.3",
|
||||
"@testing-library/user-event": "14.6.7",
|
||||
"@types/node": "26.5.1",
|
||||
"@types/react": "19.3.0",
|
||||
"@types/react-dom": "19.3.0",
|
||||
"@vitest/coverage-v8": "5.0.0",
|
||||
"@vitest/coverage-v8": "5.0.1",
|
||||
"eslint": "10.10.0",
|
||||
"jsdom": "30.0.1",
|
||||
"react": "19.3.0",
|
||||
"react-dom": "19.3.0",
|
||||
"typescript": "6.0.3",
|
||||
"typescript-eslint": "8.70.0",
|
||||
"vitest": "5.0.0",
|
||||
"@types/node": "26.5.1"
|
||||
"vitest": "5.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,153 +7,153 @@ importers:
|
||||
configDependencies: {}
|
||||
packageManagerDependencies:
|
||||
pnpm:
|
||||
specifier: 12.4.1
|
||||
version: 12.4.1
|
||||
specifier: 12.4.2
|
||||
version: 12.4.2
|
||||
|
||||
packages:
|
||||
|
||||
'@pnpm/exe.android-arm64@12.4.1':
|
||||
resolution: {integrity: sha512-/HwsqXMSmlOfgtV9+O0ratzjV6Vd/8n1hh4rGHpCGmDURvt52MwZxdbhyxP4K03ZfvgSDvotFPr8O+RzE5Eu8A==}
|
||||
'@pnpm/exe.android-arm64@12.4.2':
|
||||
resolution: {integrity: sha512-E255MbcQ0V1577M2BV0ajWuP7KmTPYA0jqZnk3rK6Lq3kTvZWulMMb7iqRmnLsQbyWTkMEB2CfyM70loVDA8xg==}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@pnpm/exe.android-x64@12.4.1':
|
||||
resolution: {integrity: sha512-+l74Qb4c2YjOzNKHXJLg+1wr8xHM1ckkUhnU5KRUK9TJqiiczt6yeTZqqzHIlJ8i6pAoj5V0OJevDRwnQKLgrQ==}
|
||||
'@pnpm/exe.android-x64@12.4.2':
|
||||
resolution: {integrity: sha512-J1pSeCUwuKxMG70ZzpWn8JzElxiEa8NN/3N0SlZRtU2xbztChaJCKF3HaBNIj9yPfeofWzJo/lwNvDDjraQuZw==}
|
||||
cpu: [x64]
|
||||
os: [android]
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.4.1':
|
||||
resolution: {integrity: sha512-6rkZkT3iGfaxknUdGHraqSWFvTa6N0ajAHluv9Ax0GRWs0sIcGNiFhDopv6xSZCsJZmG483aNS/b6UEDy3blfw==}
|
||||
'@pnpm/exe.darwin-arm64@12.4.2':
|
||||
resolution: {integrity: sha512-A0WDo8iErfZBXgrLseQxw8i8Y9ctUpOEl/Uu+cubnTzpD8tT9ykIB548L8YTM2WD4OS+ZOHSxy8aGZcvKq8PaQ==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@pnpm/exe.darwin-x64@12.4.1':
|
||||
resolution: {integrity: sha512-Vb1CHlR88HghC1qUxjxjs82zQSXnXacPD+btG2CmG8Q/hBU7Q0/b2YWJKSQqZXicunV5khFtfTlAwJddV9RkYA==}
|
||||
'@pnpm/exe.darwin-x64@12.4.2':
|
||||
resolution: {integrity: sha512-MSgJdovBWHcb5DEOvfPH9yNi/T5O1Xa4ess+E1ESGo/5yuty7S4JoiIjami+fsNXfnoQMlwsMUqYU4zAvBcNCw==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@pnpm/exe.freebsd-x64@12.4.1':
|
||||
resolution: {integrity: sha512-iT3iHz3Nl0Sxxj7UPOtZ/aQ81AFsQhjoeWMAlPkSRO04gsgDGAXv3UYxOFaesMWsfNxaGn0A+CITbuCHFF66FA==}
|
||||
'@pnpm/exe.freebsd-x64@12.4.2':
|
||||
resolution: {integrity: sha512-h2YumlQSNvgbRPv+RXwABohX65f9bOBZn+jMIt7bFDISZPCzQ+Nvpt6Awbp4ip5PwQgYxbu5iREJ1fHE39Fm8A==}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.4.1':
|
||||
resolution: {integrity: sha512-aBooZfNXM5f+OGUgCAMFWpE/kAhsWfvmqIyMtHy6zl3aNxyInWrcY/Saln/UElzo7lZWMC0Yktroxd/2J26lNQ==}
|
||||
'@pnpm/exe.linux-arm64-musl@12.4.2':
|
||||
resolution: {integrity: sha512-LwSEtSEDTv6S51YLs3YvSkPyun/QmfMic1UGICUkPWFu6ByP43RdMlkKvmVkfGhAYCpnxO057vrmyJqtfZrPCA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/exe.linux-arm64@12.4.1':
|
||||
resolution: {integrity: sha512-TlOdacTTP09BgcMvwWBFRsu8VAjfwqwnslBj+XSq1JFM3ck4f3k+1O/747EuctxZxs3/o785b6Q3s7Pd92Ptsg==}
|
||||
'@pnpm/exe.linux-arm64@12.4.2':
|
||||
resolution: {integrity: sha512-2dSiDXyhx+RTHsewxex8f/jVjqQXWJ2oow4kCVHEWdZKeBpgMxvZ6fHkTAUBJXgqhbKIDHvuNlZBP7gJfUWL5A==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-ppc64@12.4.1':
|
||||
resolution: {integrity: sha512-r/ab/MlIBo75oizUP5ITiziCCrnXz4SJwfErLQ+603AshB+Yq7xTMCoMtzTSCAMu6aaymIKkAFtZvd2J75Wq0w==}
|
||||
'@pnpm/exe.linux-ppc64@12.4.2':
|
||||
resolution: {integrity: sha512-8Itc+jQk+MTz04LS9D1RcH+VctAWmzM4l1cJQ+Sx7pAJNo7EKHdRMbC0Tok1jyQ7eEHNJZD5EleYneZqWfZM+g==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-riscv64@12.4.1':
|
||||
resolution: {integrity: sha512-C/D1QWdKMiB8+wv/spl1rGITFUuqC+aI/fb6h8NB5sqxsOaGXeYc/g891oCuzggHn6SODk9p+I09hI76x/cMNw==}
|
||||
'@pnpm/exe.linux-riscv64@12.4.2':
|
||||
resolution: {integrity: sha512-hleOeqhTVpH+z9RVMGnxvU4ZnrkBUClWjCbHD9u6kwyqhGSpevoU1wTGish+CBRhmIgMAy9pAfFpqhbAKOKNfw==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-s390x@12.4.1':
|
||||
resolution: {integrity: sha512-nxz5zD4yXt94uzbStDk0QTPKW+aE92hH1b5tFXK9ctB1HE9Xcq1vjTiA2LsZMFC6p4gdu5OwQeFqYNAVGa6QlA==}
|
||||
'@pnpm/exe.linux-s390x@12.4.2':
|
||||
resolution: {integrity: sha512-LAsQRRdP9aToENR6dtcIJ9l+e1zMYOX0tQcLGpRyLPVBQcYRLlvAPcmDshsiIHQjp05SDa9FI0czX1ZQ1a7a/A==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.4.1':
|
||||
resolution: {integrity: sha512-5AwgFdGhVUg2kIweYGfxzSLEHiIG77PQhZAkXC3TwofQHsu1Wr+TrV5/rNX2PopFnHRzuE581zoB8F6Wle32yg==}
|
||||
'@pnpm/exe.linux-x64-musl@12.4.2':
|
||||
resolution: {integrity: sha512-kzfzH2/0BWdTABK14Yj5a1xsdkTEQUp2eXEPNakaD9jKL025lq3hyaKHIz/gIZaPDMe/1bFFK/En4ztFlbBJxw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/exe.linux-x64@12.4.1':
|
||||
resolution: {integrity: sha512-FJOZuuuQMhp0oLzBtcKkLXknBI92hfkmSnlKc47vfin4HrmfID5khY2lGekL9tCzk1cpR+HShEw/meFl+nHtzQ==}
|
||||
'@pnpm/exe.linux-x64@12.4.2':
|
||||
resolution: {integrity: sha512-/pbt0UVTa8NMDhzOWLQRfZ6G9ROKXlJPZtx845BqyWfc7hCrWXhTLBd70yO2y8+E+IWN3oHM1s/JsIQNGk1yvg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.4.1':
|
||||
resolution: {integrity: sha512-OO7eKBL9S+xk5hRy+JUUZSNJknGuGe3GEUffsrlC2LbqKF02g+VX1anGIzSbJDvkkdnpJhSlxF5AUz2JzJu2Jw==}
|
||||
'@pnpm/exe.win32-arm64@12.4.2':
|
||||
resolution: {integrity: sha512-PsW19e4dAUNpZ0cS9flaxFuAmpt2dKlH/Vvi8TZ4qyJjjQzue/CEsWm+6wKVP4CJ7IT8RdL4qh+soOPWIgF2Zw==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@pnpm/exe.win32-x64@12.4.1':
|
||||
resolution: {integrity: sha512-x7gJHZgHo6hp354xCYA2NvoFzYJkHwovt2kVsUBK5EmXULLcP51JGMq09CX+5FRFJUZFKoXjLu3mZWmfP7o6PQ==}
|
||||
'@pnpm/exe.win32-x64@12.4.2':
|
||||
resolution: {integrity: sha512-+xGoeE0g55ztWvl8i5QqdmNfW3nIrTVcoQrNshEOwxthm9Ag48oXton3uxOA3/SANyz5AXexEjj2KO20NnOrEw==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
pnpm@12.4.1:
|
||||
resolution: {integrity: sha512-LoHjmdc/6DkNqyXgaqeIq3pZCCSNL1o3D4K0gRR6ano2e/gEj5pv22Rg8hpm8FQt7bi5TKLIcjWWdBkgsWVtTA==}
|
||||
pnpm@12.4.2:
|
||||
resolution: {integrity: sha512-CK3GYTGAJ1x8ntraOdzwjJxhrU5+rzMKTzRh8QKw+QdCNFTRF/mOctR/7wYWBwZE17/8lzpqV/UJCm18NosHyQ==}
|
||||
engines: {node: '>=18.*'}
|
||||
hasBin: true
|
||||
|
||||
snapshots:
|
||||
|
||||
'@pnpm/exe.android-arm64@12.4.1':
|
||||
'@pnpm/exe.android-arm64@12.4.2':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.android-x64@12.4.1':
|
||||
'@pnpm/exe.android-x64@12.4.2':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.4.1':
|
||||
'@pnpm/exe.darwin-arm64@12.4.2':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.darwin-x64@12.4.1':
|
||||
'@pnpm/exe.darwin-x64@12.4.2':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.freebsd-x64@12.4.1':
|
||||
'@pnpm/exe.freebsd-x64@12.4.2':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.4.1':
|
||||
'@pnpm/exe.linux-arm64-musl@12.4.2':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-arm64@12.4.1':
|
||||
'@pnpm/exe.linux-arm64@12.4.2':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-ppc64@12.4.1':
|
||||
'@pnpm/exe.linux-ppc64@12.4.2':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-riscv64@12.4.1':
|
||||
'@pnpm/exe.linux-riscv64@12.4.2':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-s390x@12.4.1':
|
||||
'@pnpm/exe.linux-s390x@12.4.2':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.4.1':
|
||||
'@pnpm/exe.linux-x64-musl@12.4.2':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64@12.4.1':
|
||||
'@pnpm/exe.linux-x64@12.4.2':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.4.1':
|
||||
'@pnpm/exe.win32-arm64@12.4.2':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-x64@12.4.1':
|
||||
'@pnpm/exe.win32-x64@12.4.2':
|
||||
optional: true
|
||||
|
||||
pnpm@12.4.1:
|
||||
pnpm@12.4.2:
|
||||
optionalDependencies:
|
||||
'@pnpm/exe.android-arm64': 12.4.1
|
||||
'@pnpm/exe.android-x64': 12.4.1
|
||||
'@pnpm/exe.darwin-arm64': 12.4.1
|
||||
'@pnpm/exe.darwin-x64': 12.4.1
|
||||
'@pnpm/exe.freebsd-x64': 12.4.1
|
||||
'@pnpm/exe.linux-arm64': 12.4.1
|
||||
'@pnpm/exe.linux-arm64-musl': 12.4.1
|
||||
'@pnpm/exe.linux-ppc64': 12.4.1
|
||||
'@pnpm/exe.linux-riscv64': 12.4.1
|
||||
'@pnpm/exe.linux-s390x': 12.4.1
|
||||
'@pnpm/exe.linux-x64': 12.4.1
|
||||
'@pnpm/exe.linux-x64-musl': 12.4.1
|
||||
'@pnpm/exe.win32-arm64': 12.4.1
|
||||
'@pnpm/exe.win32-x64': 12.4.1
|
||||
'@pnpm/exe.android-arm64': 12.4.2
|
||||
'@pnpm/exe.android-x64': 12.4.2
|
||||
'@pnpm/exe.darwin-arm64': 12.4.2
|
||||
'@pnpm/exe.darwin-x64': 12.4.2
|
||||
'@pnpm/exe.freebsd-x64': 12.4.2
|
||||
'@pnpm/exe.linux-arm64': 12.4.2
|
||||
'@pnpm/exe.linux-arm64-musl': 12.4.2
|
||||
'@pnpm/exe.linux-ppc64': 12.4.2
|
||||
'@pnpm/exe.linux-riscv64': 12.4.2
|
||||
'@pnpm/exe.linux-s390x': 12.4.2
|
||||
'@pnpm/exe.linux-x64': 12.4.2
|
||||
'@pnpm/exe.linux-x64-musl': 12.4.2
|
||||
'@pnpm/exe.win32-arm64': 12.4.2
|
||||
'@pnpm/exe.win32-x64': 12.4.2
|
||||
|
||||
---
|
||||
lockfileVersion: '9.0'
|
||||
@ -169,9 +169,12 @@ importers:
|
||||
'@eslint/js':
|
||||
specifier: 10.0.1
|
||||
version: 10.0.1(eslint@10.10.0(supports-color@7.2.0))
|
||||
'@kuhyx/ts-config':
|
||||
specifier: link:../ts_config
|
||||
version: link:../ts_config
|
||||
'@testing-library/jest-dom':
|
||||
specifier: 7.0.1
|
||||
version: 7.0.1(@testing-library/dom@10.4.1)(vitest@5.0.0)
|
||||
version: 7.0.1(@testing-library/dom@10.4.1)(vitest@5.0.1)
|
||||
'@testing-library/react':
|
||||
specifier: 16.3.3
|
||||
version: 16.3.3(@testing-library/dom@10.4.1)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)
|
||||
@ -188,8 +191,8 @@ importers:
|
||||
specifier: 19.3.0
|
||||
version: 19.3.0(@types/react@19.3.0)
|
||||
'@vitest/coverage-v8':
|
||||
specifier: 5.0.0
|
||||
version: 5.0.0(vitest@5.0.0)
|
||||
specifier: 5.0.1
|
||||
version: 5.0.1(vitest@5.0.1)
|
||||
eslint:
|
||||
specifier: 10.10.0
|
||||
version: 10.10.0(supports-color@7.2.0)
|
||||
@ -209,8 +212,8 @@ importers:
|
||||
specifier: 8.70.0
|
||||
version: 8.70.0(eslint@10.10.0(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)
|
||||
vitest:
|
||||
specifier: 5.0.0
|
||||
version: 5.0.0(@types/node@26.5.1)(@vitest/coverage-v8@5.0.0)(jsdom@30.0.1)(vite@8.2.2(@types/node@26.5.1))
|
||||
specifier: 5.0.1
|
||||
version: 5.0.1(@types/node@26.5.1)(@vitest/coverage-v8@5.0.1)(jsdom@30.0.1)(vite@8.2.2(@types/node@26.5.1))
|
||||
|
||||
packages:
|
||||
|
||||
@ -372,9 +375,6 @@ packages:
|
||||
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5':
|
||||
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.6.0':
|
||||
resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==}
|
||||
|
||||
@ -615,11 +615,11 @@ packages:
|
||||
resolution: {integrity: sha512-BoC8PiO4Hkdo0TVJh9Ntxr5MxPDI7/oFsrygN5ADelFSeXG/qgNuucIGA+L5Z6JpPTE/uRfcTWtscjbUaufepQ==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@vitest/coverage-v8@5.0.0':
|
||||
resolution: {integrity: sha512-toMg6PZGCIa/lQNCDoASrfb1ly4hsUKXFtFYC9kD4t78o5Y6LyNJU7AENt8eHPr3quYdxaxK7hj2mnbFfUk9NA==}
|
||||
'@vitest/coverage-v8@5.0.1':
|
||||
resolution: {integrity: sha512-FRC8ACiudC3dI6MTplzRSYWHDRnIv2IPfbzs4FdoJNsMal/35sWV8hwIfV8ZcqzSPy+uXHeMVONt9CEqtOU17w==}
|
||||
peerDependencies:
|
||||
'@vitest/browser': 5.0.0
|
||||
vitest: 5.0.0
|
||||
'@vitest/browser': 5.0.1
|
||||
vitest: 5.0.1
|
||||
peerDependenciesMeta:
|
||||
'@vitest/browser':
|
||||
optional: true
|
||||
@ -632,8 +632,8 @@ packages:
|
||||
resolution: {integrity: sha512-1EOLRfsTMnyAr3+kEAsP4o9dhaDlGPpD7H5iLBBeq//YpNB1VIahkPhB+eRp9N2Dkfw8oySROjE3yf9XDeaIkQ==}
|
||||
engines: {node: '>=22'}
|
||||
|
||||
'@vitest/mocker@5.0.0':
|
||||
resolution: {integrity: sha512-66PGTMIiVJP3t4a5yxU9qPtf7MdTBs8jmToMvy+HVflB3Yy13WJZTtPePdvU+wjRV02SKK5doLbSA6o9pwOmiA==}
|
||||
'@vitest/mocker@5.0.1':
|
||||
resolution: {integrity: sha512-6K1DoBNAPGvuOcSsGA4D6x+5zEEff/KmOOP3uetT2TrGpVfI+HRHRnJJfKi5ib/g1vx8IYHQD8s0pbJz8WQI7Q==}
|
||||
peerDependencies:
|
||||
msw: ^2.4.9
|
||||
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||
@ -643,8 +643,8 @@ packages:
|
||||
vite:
|
||||
optional: true
|
||||
|
||||
'@vitest/spy@5.0.0':
|
||||
resolution: {integrity: sha512-uy+luWBAPw9XfthoHi5AkfHUnuPYEESjl0p/r+meoBnU8bxg5GDQ3Ey8MjcJ6sqahkL4PFyrvfMJJBw7LbU06g==}
|
||||
'@vitest/spy@5.0.1':
|
||||
resolution: {integrity: sha512-rbto/mF/SGERxEgYOek7Xm6B9b+y+mVoo+f4b2LymYO8zM1b7uB5nHuhVMTP2hxdzgxvGiZYGxGIaMvL5y180Q==}
|
||||
|
||||
acorn-jsx@5.3.2:
|
||||
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
||||
@ -1265,20 +1265,20 @@ packages:
|
||||
yaml:
|
||||
optional: true
|
||||
|
||||
vitest@5.0.0:
|
||||
resolution: {integrity: sha512-gpsMNoRhMjMktVxPtstOH4/PJuPyovVaMDr4oDilXaGH1EcqM2OE96SoHT2VIQ6fTGtTjqmHDrEu2X9RQiXf8Q==}
|
||||
vitest@5.0.1:
|
||||
resolution: {integrity: sha512-iA95lQbKEkvrtTkdAgnWbXfbipWiiWe/hDl2P5tMi6WFwD76G0NxXAGp/M9EOcYupeGJRr6wppMc7CoA41TQjg==}
|
||||
engines: {node: ^22.12.0 || ^24.0.0 || >=26.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@edge-runtime/vm': '*'
|
||||
'@opentelemetry/api': ^1.9.0
|
||||
'@types/node': ^22.0.0 || >=24.0.0
|
||||
'@vitest/browser-playwright': 5.0.0
|
||||
'@vitest/browser-preview': 5.0.0
|
||||
'@vitest/browser-playwright': 5.0.1
|
||||
'@vitest/browser-preview': 5.0.1
|
||||
'@vitest/browser-webdriverio': ^5.0.0-beta.5 || >=5.0.0
|
||||
'@vitest/coverage-istanbul': 5.0.0
|
||||
'@vitest/coverage-v8': 5.0.0
|
||||
'@vitest/ui': 5.0.0
|
||||
'@vitest/coverage-istanbul': 5.0.1
|
||||
'@vitest/coverage-v8': 5.0.1
|
||||
'@vitest/ui': 5.0.1
|
||||
happy-dom: '*'
|
||||
jsdom: '*'
|
||||
vite: ^6.4.0 || ^7.0.0 || ^8.0.0
|
||||
@ -1487,14 +1487,12 @@ snapshots:
|
||||
|
||||
'@jridgewell/resolve-uri@3.1.2': {}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5': {}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.6.0': {}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.31':
|
||||
dependencies:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
'@jridgewell/sourcemap-codec': 1.6.0
|
||||
|
||||
'@keyv/bigmap@1.3.1(keyv@5.6.0)':
|
||||
dependencies:
|
||||
@ -1564,7 +1562,7 @@ snapshots:
|
||||
picocolors: 1.1.1
|
||||
pretty-format: 27.5.1
|
||||
|
||||
'@testing-library/jest-dom@7.0.1(@testing-library/dom@10.4.1)(vitest@5.0.0)':
|
||||
'@testing-library/jest-dom@7.0.1(@testing-library/dom@10.4.1)(vitest@5.0.1)':
|
||||
dependencies:
|
||||
'@adobe/css-tools': 4.5.0
|
||||
'@testing-library/dom': 10.4.1
|
||||
@ -1574,7 +1572,7 @@ snapshots:
|
||||
picocolors: 1.1.1
|
||||
redent: 3.0.0
|
||||
optionalDependencies:
|
||||
vitest: 5.0.0(@types/node@26.5.1)(@vitest/coverage-v8@5.0.0)(jsdom@30.0.1)(vite@8.2.2(@types/node@26.5.1))
|
||||
vitest: 5.0.1(@types/node@26.5.1)(@vitest/coverage-v8@5.0.1)(jsdom@30.0.1)(vite@8.2.2(@types/node@26.5.1))
|
||||
|
||||
'@testing-library/react@16.3.3(@testing-library/dom@10.4.1)(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)':
|
||||
dependencies:
|
||||
@ -1708,7 +1706,7 @@ snapshots:
|
||||
'@typescript-eslint/types': 8.70.0
|
||||
eslint-visitor-keys: 5.0.1
|
||||
|
||||
'@vitest/coverage-v8@5.0.0(vitest@5.0.0)':
|
||||
'@vitest/coverage-v8@5.0.1(vitest@5.0.1)':
|
||||
dependencies:
|
||||
'@bcoe/v8-coverage': 1.0.2
|
||||
'@vitest/istanbul-lib-coverage': 1.0.1
|
||||
@ -1718,7 +1716,7 @@ snapshots:
|
||||
obug: 2.1.4
|
||||
std-env: 4.2.0
|
||||
tinyrainbow: 3.1.1
|
||||
vitest: 5.0.0(@types/node@26.5.1)(@vitest/coverage-v8@5.0.0)(jsdom@30.0.1)(vite@8.2.2(@types/node@26.5.1))
|
||||
vitest: 5.0.1(@types/node@26.5.1)(@vitest/coverage-v8@5.0.1)(jsdom@30.0.1)(vite@8.2.2(@types/node@26.5.1))
|
||||
|
||||
'@vitest/istanbul-lib-coverage@1.0.1': {}
|
||||
|
||||
@ -1726,16 +1724,16 @@ snapshots:
|
||||
dependencies:
|
||||
'@vitest/istanbul-lib-coverage': 1.0.1
|
||||
|
||||
'@vitest/mocker@5.0.0(vite@8.2.2(@types/node@26.5.1))':
|
||||
'@vitest/mocker@5.0.1(vite@8.2.2(@types/node@26.5.1))':
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
'@vitest/spy': 5.0.0
|
||||
'@vitest/spy': 5.0.1
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 1.3.1
|
||||
optionalDependencies:
|
||||
vite: 8.2.2(@types/node@26.5.1)
|
||||
|
||||
'@vitest/spy@5.0.0': {}
|
||||
'@vitest/spy@5.0.1': {}
|
||||
|
||||
acorn-jsx@5.3.2(acorn@8.18.0):
|
||||
dependencies:
|
||||
@ -2283,10 +2281,10 @@ snapshots:
|
||||
'@types/node': 26.5.1
|
||||
fsevents: 2.3.3
|
||||
|
||||
vitest@5.0.0(@types/node@26.5.1)(@vitest/coverage-v8@5.0.0)(jsdom@30.0.1)(vite@8.2.2(@types/node@26.5.1)):
|
||||
vitest@5.0.1(@types/node@26.5.1)(@vitest/coverage-v8@5.0.1)(jsdom@30.0.1)(vite@8.2.2(@types/node@26.5.1)):
|
||||
dependencies:
|
||||
'@types/chai': 5.2.3
|
||||
'@vitest/mocker': 5.0.0(vite@8.2.2(@types/node@26.5.1))
|
||||
'@vitest/mocker': 5.0.1(vite@8.2.2(@types/node@26.5.1))
|
||||
chai: 6.2.2
|
||||
es-module-lexer: 2.3.2
|
||||
expect-type: 1.4.0
|
||||
@ -2301,7 +2299,7 @@ snapshots:
|
||||
why-is-node-running: 2.3.0
|
||||
optionalDependencies:
|
||||
'@types/node': 26.5.1
|
||||
'@vitest/coverage-v8': 5.0.0(vitest@5.0.0)
|
||||
'@vitest/coverage-v8': 5.0.1(vitest@5.0.1)
|
||||
jsdom: 30.0.1
|
||||
transitivePeerDependencies:
|
||||
- msw
|
||||
|
||||
@ -5,5 +5,6 @@
|
||||
* `import "@kuhyx/web-ui/tokens.css";`
|
||||
*/
|
||||
export { clamp01, fractionFromPointer, nth, quantileValue, valueQuantile } from "./quantile.ts";
|
||||
export { RangeSlider, steppedValue, type RangeSliderProps } from "./range-slider.tsx";
|
||||
export { RangeSlider, type RangeSliderProps } from "./range-slider.tsx";
|
||||
export { keyTarget, steppedValue } from "./stepped-value.ts";
|
||||
export { fuzzyMatch } from "./text.ts";
|
||||
|
||||
@ -55,8 +55,7 @@ export function quantileValue(sorted: readonly number[], f: number): number {
|
||||
*/
|
||||
export function valueQuantile(sorted: readonly number[], value: number): number {
|
||||
const n = sorted.length;
|
||||
if (n <= 1) return 0;
|
||||
if (value <= nth(sorted, 0)) return 0;
|
||||
if ((n <= 1) || (value <= nth(sorted, 0))) return 0;
|
||||
if (value >= nth(sorted, n - 1)) return 1;
|
||||
// value strictly inside the range, so there is an index hi >= 1 with
|
||||
// sorted[hi] >= value and sorted[hi - 1] < value.
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { RangeSlider, steppedValue } from "./range-slider.tsx";
|
||||
import { RangeSlider } from "./range-slider.tsx";
|
||||
|
||||
/** Ten evenly spaced values, so a quantile fraction maps to a predictable value. */
|
||||
const VALUES = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90];
|
||||
@ -29,63 +29,6 @@ function thumbs(): HTMLElement[] {
|
||||
return screen.getAllByRole("slider");
|
||||
}
|
||||
|
||||
describe("steppedValue", () => {
|
||||
it("advances one sample at a time", () => {
|
||||
expect(steppedValue(VALUES, 30, 1)).toBe(40);
|
||||
});
|
||||
|
||||
it("moves backwards for a negative delta", () => {
|
||||
expect(steppedValue(VALUES, 30, -1)).toBe(20);
|
||||
});
|
||||
|
||||
it("clamps at the top", () => {
|
||||
expect(steppedValue(VALUES, 90, 5)).toBe(90);
|
||||
});
|
||||
|
||||
it("clamps at the bottom", () => {
|
||||
expect(steppedValue(VALUES, 0, -5)).toBe(0);
|
||||
});
|
||||
|
||||
it("rounds a between-samples value to a real sample", () => {
|
||||
expect(steppedValue(VALUES, 34, 1)).toBe(40);
|
||||
});
|
||||
|
||||
it("returns the current sample for a zero delta", () => {
|
||||
expect(steppedValue(VALUES, 30, 0)).toBe(30);
|
||||
});
|
||||
|
||||
describe("on a lumpy distribution", () => {
|
||||
// The shape that actually broke in the browser: awesome-mcp-explorer's
|
||||
// star counts hold ~1500 duplicate zeros, so an index step inside the run
|
||||
// moved the index but not the value, and the arrow key looked dead.
|
||||
const LUMPY = [...Array.from({ length: 50 }, () => 0), 5, 5, 5, 100, 5000];
|
||||
|
||||
it("escapes a run of duplicates instead of stalling", () => {
|
||||
expect(steppedValue(LUMPY, 0, 1)).toBe(5);
|
||||
});
|
||||
|
||||
it("keeps stepping past the second run", () => {
|
||||
expect(steppedValue(LUMPY, 5, 1)).toBe(100);
|
||||
});
|
||||
|
||||
it("steps back out of a run", () => {
|
||||
expect(steppedValue(LUMPY, 5, -1)).toBe(0);
|
||||
});
|
||||
|
||||
it("counts distinct values for a multi-step jump", () => {
|
||||
expect(steppedValue(LUMPY, 0, 2)).toBe(100);
|
||||
});
|
||||
|
||||
it("clamps at the top rather than running off the end", () => {
|
||||
expect(steppedValue(LUMPY, 5000, 1)).toBe(5000);
|
||||
});
|
||||
|
||||
it("clamps at the bottom", () => {
|
||||
expect(steppedValue(LUMPY, 0, -1)).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("RangeSlider", () => {
|
||||
it("renders nothing when there is nothing to range over", () => {
|
||||
const { container } = render(<RangeSlider hi={1} lo={1} onChange={vi.fn()} values={[1]} />);
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
import { useCallback, useState } from "react";
|
||||
|
||||
import { fractionFromPointer, nth, quantileValue, valueQuantile } from "./quantile.ts";
|
||||
import { keyTarget } from "./stepped-value.ts";
|
||||
|
||||
export interface RangeSliderProps {
|
||||
/**
|
||||
@ -44,40 +45,6 @@ export interface RangeSliderProps {
|
||||
|
||||
type Thumb = "hi" | "lo";
|
||||
|
||||
/**
|
||||
* Steps one thumb by `delta` positions through the distribution. Pure, so the
|
||||
* keyboard contract is testable without layout.
|
||||
*
|
||||
* Steps to the next *distinct* value rather than the next index. Real
|
||||
* distributions are lumpy -- awesome-mcp-explorer's star counts hold ~1500
|
||||
* duplicate zeros -- and an index step inside a run of equal values changes the
|
||||
* index while leaving the value (and therefore the thumb, and the filter)
|
||||
* exactly where it was. That reads as a dead arrow key.
|
||||
*/
|
||||
export function steppedValue(
|
||||
values: readonly number[],
|
||||
current: number,
|
||||
delta: number,
|
||||
): number {
|
||||
const last = values.length - 1;
|
||||
// Round to a real sample index so a thumb sitting between two samples still
|
||||
// advances rather than stalling on a fractional step.
|
||||
const index = Math.round(valueQuantile(values, current) * last);
|
||||
const direction = Math.sign(delta);
|
||||
if (direction === 0) return nth(values, index);
|
||||
|
||||
let cursor = index;
|
||||
for (let taken = 0; taken < Math.abs(delta); taken++) {
|
||||
let next = cursor + direction;
|
||||
while (next >= 0 && next <= last && nth(values, next) === nth(values, cursor)) {
|
||||
next += direction;
|
||||
}
|
||||
if (next < 0 || next > last) break;
|
||||
cursor = next;
|
||||
}
|
||||
return nth(values, cursor);
|
||||
}
|
||||
|
||||
export function RangeSlider({
|
||||
format,
|
||||
hi,
|
||||
@ -86,7 +53,7 @@ export function RangeSlider({
|
||||
onChange,
|
||||
values,
|
||||
}: RangeSliderProps): null | React.JSX.Element {
|
||||
const [drag, setDrag] = useState<Thumb | null>(null);
|
||||
const [drag, setDrag] = useState<null | Thumb>(null);
|
||||
|
||||
const apply = useCallback(
|
||||
(thumb: Thumb, value: number): void => {
|
||||
@ -100,29 +67,20 @@ export function RangeSlider({
|
||||
// handlers are bound to the track, so currentTarget *is* the track. A ref
|
||||
// would add a null branch that cannot happen once the element is mounted.
|
||||
const valueAt = useCallback(
|
||||
(track: HTMLElement, clientX: number): number =>
|
||||
quantileValue(values, fractionFromPointer(track.getBoundingClientRect(), clientX)),
|
||||
(track: HTMLElement, clientX: number): number => {
|
||||
const fraction = fractionFromPointer(track.getBoundingClientRect(), clientX);
|
||||
return quantileValue(values, fraction);
|
||||
},
|
||||
[values],
|
||||
);
|
||||
|
||||
const onKeyDown = useCallback(
|
||||
(thumb: Thumb, event: React.KeyboardEvent): void => {
|
||||
const current = thumb === "lo" ? lo : hi;
|
||||
const step = (delta: number): void => {
|
||||
event.preventDefault();
|
||||
apply(thumb, steppedValue(values, current, delta));
|
||||
};
|
||||
if (event.key === "ArrowLeft" || event.key === "ArrowDown") step(-1);
|
||||
else if (event.key === "ArrowRight" || event.key === "ArrowUp") step(1);
|
||||
else if (event.key === "PageDown") step(-10);
|
||||
else if (event.key === "PageUp") step(10);
|
||||
else if (event.key === "Home") {
|
||||
event.preventDefault();
|
||||
apply(thumb, nth(values, 0));
|
||||
} else if (event.key === "End") {
|
||||
event.preventDefault();
|
||||
apply(thumb, nth(values, values.length - 1));
|
||||
}
|
||||
const target = keyTarget(event.key, values, current);
|
||||
if (target === undefined) return;
|
||||
event.preventDefault();
|
||||
apply(thumb, target);
|
||||
},
|
||||
[apply, hi, lo, values],
|
||||
);
|
||||
@ -136,19 +94,21 @@ export function RangeSlider({
|
||||
const hiFraction = valueQuantile(values, hi) * 100;
|
||||
const name = label ?? "Range";
|
||||
|
||||
const thumbProps = (thumb: Thumb): React.ComponentProps<"button"> => ({
|
||||
"aria-valuemax": highest,
|
||||
"aria-valuemin": lowest,
|
||||
"aria-valuenow": thumb === "lo" ? lo : hi,
|
||||
className: `slider-thumb slider-${thumb}`,
|
||||
onKeyDown: (event): void => {
|
||||
onKeyDown(thumb, event);
|
||||
},
|
||||
role: "slider",
|
||||
style: { left: `${String(thumb === "lo" ? loFraction : hiFraction)}%` },
|
||||
tabIndex: 0,
|
||||
type: "button",
|
||||
});
|
||||
const thumbProps = (thumb: Thumb): React.ComponentProps<"button"> => {
|
||||
return {
|
||||
"aria-valuemax": highest,
|
||||
"aria-valuemin": lowest,
|
||||
"aria-valuenow": thumb === "lo" ? lo : hi,
|
||||
className: `slider-thumb slider-${thumb}`,
|
||||
onKeyDown: (event): void => {
|
||||
onKeyDown(thumb, event);
|
||||
},
|
||||
role: "slider",
|
||||
style: { left: `${String(thumb === "lo" ? loFraction : hiFraction)}%` },
|
||||
tabIndex: 0,
|
||||
type: "button",
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="slider">
|
||||
|
||||
79
web_ui/src/stepped-value.test.ts
Normal file
79
web_ui/src/stepped-value.test.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { keyTarget, steppedValue } from "./stepped-value.ts";
|
||||
|
||||
/** Ten evenly spaced values, so a quantile fraction maps to a predictable value. */
|
||||
const VALUES = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90];
|
||||
|
||||
describe("keyTarget", () => {
|
||||
it("leaves keys the slider does not own to the browser", () => {
|
||||
expect(keyTarget("Tab", VALUES, 30)).toBeUndefined();
|
||||
expect(keyTarget("a", VALUES, 30)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("maps every owned key onto the distribution", () => {
|
||||
expect(keyTarget("ArrowLeft", VALUES, 30)).toBe(20);
|
||||
expect(keyTarget("ArrowDown", VALUES, 30)).toBe(20);
|
||||
expect(keyTarget("ArrowRight", VALUES, 30)).toBe(40);
|
||||
expect(keyTarget("ArrowUp", VALUES, 30)).toBe(40);
|
||||
expect(keyTarget("Home", VALUES, 30)).toBe(0);
|
||||
expect(keyTarget("End", VALUES, 30)).toBe(90);
|
||||
});
|
||||
});
|
||||
|
||||
describe("steppedValue", () => {
|
||||
it("advances one sample at a time", () => {
|
||||
expect(steppedValue(VALUES, 30, 1)).toBe(40);
|
||||
});
|
||||
|
||||
it("moves backwards for a negative delta", () => {
|
||||
expect(steppedValue(VALUES, 30, -1)).toBe(20);
|
||||
});
|
||||
|
||||
it("clamps at the top", () => {
|
||||
expect(steppedValue(VALUES, 90, 5)).toBe(90);
|
||||
});
|
||||
|
||||
it("clamps at the bottom", () => {
|
||||
expect(steppedValue(VALUES, 0, -5)).toBe(0);
|
||||
});
|
||||
|
||||
it("rounds a between-samples value to a real sample", () => {
|
||||
expect(steppedValue(VALUES, 34, 1)).toBe(40);
|
||||
});
|
||||
|
||||
it("returns the current sample for a zero delta", () => {
|
||||
expect(steppedValue(VALUES, 30, 0)).toBe(30);
|
||||
});
|
||||
|
||||
describe("on a lumpy distribution", () => {
|
||||
// The shape that actually broke in the browser: awesome-mcp-explorer's
|
||||
// star counts hold ~1500 duplicate zeros, so an index step inside the run
|
||||
// moved the index but not the value, and the arrow key looked dead.
|
||||
const LUMPY = [...Array.from({ length: 50 }, () => 0), 5, 5, 5, 100, 5000];
|
||||
|
||||
it("escapes a run of duplicates instead of stalling", () => {
|
||||
expect(steppedValue(LUMPY, 0, 1)).toBe(5);
|
||||
});
|
||||
|
||||
it("keeps stepping past the second run", () => {
|
||||
expect(steppedValue(LUMPY, 5, 1)).toBe(100);
|
||||
});
|
||||
|
||||
it("steps back out of a run", () => {
|
||||
expect(steppedValue(LUMPY, 5, -1)).toBe(0);
|
||||
});
|
||||
|
||||
it("counts distinct values for a multi-step jump", () => {
|
||||
expect(steppedValue(LUMPY, 0, 2)).toBe(100);
|
||||
});
|
||||
|
||||
it("clamps at the top rather than running off the end", () => {
|
||||
expect(steppedValue(LUMPY, 5000, 1)).toBe(5000);
|
||||
});
|
||||
|
||||
it("clamps at the bottom", () => {
|
||||
expect(steppedValue(LUMPY, 0, -1)).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
78
web_ui/src/stepped-value.ts
Normal file
78
web_ui/src/stepped-value.ts
Normal file
@ -0,0 +1,78 @@
|
||||
/**
|
||||
* The keyboard contract of the range slider, kept pure and in its own module
|
||||
* so range-slider.tsx exports only the component (react-refresh needs that
|
||||
* for HMR to keep working) and the contract is testable without layout.
|
||||
*/
|
||||
import { nth, valueQuantile } from "./quantile.ts";
|
||||
|
||||
/**
|
||||
* Steps one thumb by `delta` positions through the distribution. Pure, so the
|
||||
* keyboard contract is testable without layout.
|
||||
*
|
||||
* Steps to the next *distinct* value rather than the next index. Real
|
||||
* distributions are lumpy -- awesome-mcp-explorer's star counts hold ~1500
|
||||
* duplicate zeros -- and an index step inside a run of equal values changes the
|
||||
* index while leaving the value (and therefore the thumb, and the filter)
|
||||
* exactly where it was. That reads as a dead arrow key.
|
||||
*/
|
||||
export function steppedValue(
|
||||
values: readonly number[],
|
||||
current: number,
|
||||
delta: number,
|
||||
): number {
|
||||
const last = values.length - 1;
|
||||
// Round to a real sample index so a thumb sitting between two samples still
|
||||
// advances rather than stalling on a fractional step.
|
||||
const index = Math.round(valueQuantile(values, current) * last);
|
||||
const direction = Math.sign(delta);
|
||||
if (direction === 0) return nth(values, index);
|
||||
|
||||
let cursor = index;
|
||||
for (let taken = 0; taken < Math.abs(delta); taken++) {
|
||||
let next = cursor + direction;
|
||||
while (next >= 0 && next <= last && nth(values, next) === nth(values, cursor)) {
|
||||
next += direction;
|
||||
}
|
||||
if (next < 0 || next > last) break;
|
||||
cursor = next;
|
||||
}
|
||||
return nth(values, cursor);
|
||||
}
|
||||
|
||||
/**
|
||||
* The value a thumb at `current` moves to for a key, or `undefined` when the
|
||||
* key is not one the slider handles (so the event is left to the browser).
|
||||
* Arrow keys step one distinct value, Page keys ten, Home/End jump to the
|
||||
* bounds.
|
||||
*/
|
||||
export function keyTarget(
|
||||
key: string,
|
||||
values: readonly number[],
|
||||
current: number,
|
||||
): number | undefined {
|
||||
switch (key) {
|
||||
case "ArrowDown":
|
||||
case "ArrowLeft": {
|
||||
return steppedValue(values, current, -1);
|
||||
}
|
||||
case "ArrowRight":
|
||||
case "ArrowUp": {
|
||||
return steppedValue(values, current, 1);
|
||||
}
|
||||
case "End": {
|
||||
return nth(values, values.length - 1);
|
||||
}
|
||||
case "Home": {
|
||||
return nth(values, 0);
|
||||
}
|
||||
case "PageDown": {
|
||||
return steppedValue(values, current, -10);
|
||||
}
|
||||
case "PageUp": {
|
||||
return steppedValue(values, current, 10);
|
||||
}
|
||||
default: {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -13,7 +13,7 @@ const TRACK_WIDTH = 1000;
|
||||
if (hasDom) {
|
||||
Object.defineProperty(Element.prototype, "getBoundingClientRect", {
|
||||
configurable: true,
|
||||
value(): DOMRect {
|
||||
value: (): DOMRect => {
|
||||
return {
|
||||
bottom: 20,
|
||||
height: 20,
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
// @vitest-environment node
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const css = readFileSync(new URL("tokens.css", import.meta.url), "utf8");
|
||||
@ -13,9 +12,10 @@ const css = readFileSync(new URL("tokens.css", import.meta.url), "utf8");
|
||||
*/
|
||||
|
||||
function declared(name: string): string[] {
|
||||
return [...css.matchAll(new RegExp(`--${name}:\\s*([^;]+);`, "g"))].map((m) =>
|
||||
(m[1] ?? "").trim(),
|
||||
);
|
||||
return css
|
||||
.matchAll(new RegExp(String.raw`--${name}:\s*([^;]+);`, "g"))
|
||||
.map((m) => (m[1] ?? "").trim())
|
||||
.toArray();
|
||||
}
|
||||
|
||||
describe("tokens.css", () => {
|
||||
@ -124,7 +124,7 @@ describe("tokens.css", () => {
|
||||
const block = /@media \(prefers-reduced-motion: reduce\)\s*\{([\s\S]*?)\n\}/.exec(css)?.[1];
|
||||
expect(block).toBeDefined();
|
||||
for (const step of ["fast", "base", "slow"]) {
|
||||
expect(block).toMatch(new RegExp(`--duration-${step}:\\s*0ms;`));
|
||||
expect(block).toMatch(new RegExp(String.raw`--duration-${step}:\s*0ms;`));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
{
|
||||
// Strictness comes from the shared base; only output/lib options live here.
|
||||
"extends": "@kuhyx/ts-config/tsconfig",
|
||||
"compilerOptions": {
|
||||
"target": "ES2023",
|
||||
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
||||
@ -7,20 +9,6 @@
|
||||
"jsx": "react-jsx",
|
||||
"types": ["vitest/globals"],
|
||||
|
||||
/* Strictness: everything on, matching both consumers so a file that
|
||||
compiles here compiles there. */
|
||||
"strict": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noImplicitOverride": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
"useUnknownInCatchVariables": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"isolatedModules": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
|
||||
/* This package ships a prebuilt dist/ (ESM + .d.ts) because consumers use
|
||||
Vite, which does not transpile dependencies. So unlike the consumers,
|
||||
@ -31,10 +19,7 @@
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["src/**/*.test.ts", "src/**/*.test.tsx", "src/test"]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user