@joewinke/jatui 0.26.0 → 0.27.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +106 -1
- package/dist/actions/keyboardScope.d.ts +58 -0
- package/dist/actions/keyboardScope.js +110 -0
- package/dist/actions/listNav.d.ts +48 -3
- package/dist/actions/listNav.js +267 -77
- package/dist/actions/longpress.d.ts +37 -0
- package/dist/actions/longpress.js +105 -0
- package/dist/actions/stickToBottom.d.ts +66 -0
- package/dist/actions/stickToBottom.js +155 -0
- package/dist/actions/swipe.d.ts +64 -0
- package/dist/actions/swipe.js +179 -0
- package/dist/components/BottomSheet.svelte +280 -0
- package/dist/components/BottomSheet.svelte.d.ts +27 -0
- package/dist/components/ConfirmDialog.svelte +21 -0
- package/dist/components/ConfirmDialog.svelte.d.ts +11 -1
- package/dist/components/ConfirmModal.svelte +15 -0
- package/dist/components/ConfirmModal.svelte.d.ts +5 -0
- package/dist/components/Drawer.svelte +18 -1
- package/dist/components/Drawer.svelte.d.ts +9 -1
- package/dist/components/InputDialog.svelte +21 -0
- package/dist/components/InputDialog.svelte.d.ts +11 -1
- package/dist/components/Modal.svelte +18 -1
- package/dist/components/Modal.svelte.d.ts +8 -0
- package/dist/components/basic/Empty.svelte +173 -9
- package/dist/components/basic/Empty.svelte.d.ts +50 -1
- package/dist/components/basic/ErrorState.svelte +128 -0
- package/dist/components/basic/ErrorState.svelte.d.ts +46 -0
- package/dist/components/basic/JumpToLatest.svelte +60 -0
- package/dist/components/basic/JumpToLatest.svelte.d.ts +9 -0
- package/dist/components/basic/PageHeader.svelte +216 -0
- package/dist/components/basic/PageHeader.svelte.d.ts +52 -0
- package/dist/components/basic/ProgressButton.svelte +221 -0
- package/dist/components/basic/ProgressButton.svelte.d.ts +63 -0
- package/dist/components/graph/GraphApiBridge.svelte +36 -0
- package/dist/components/graph/GraphApiBridge.svelte.d.ts +9 -0
- package/dist/components/graph/GraphCanvas.svelte +212 -27
- package/dist/components/graph/GraphCanvas.svelte.d.ts +113 -19
- package/dist/components/graph/api.d.ts +52 -0
- package/dist/components/graph/api.js +66 -0
- package/dist/components/graph/index.d.ts +2 -0
- package/dist/components/graph/index.js +1 -0
- package/dist/components/graph/theme.js +13 -0
- package/dist/components/messaging/MessageItem.svelte +5 -0
- package/dist/components/messaging/MessageItem.svelte.d.ts +7 -0
- package/dist/components/table/DataTable.svelte +452 -78
- package/dist/components/table/DataTable.svelte.d.ts +2 -2
- package/dist/components/table/ManageColumnsDropdown.svelte +466 -0
- package/dist/components/table/ManageColumnsDropdown.svelte.d.ts +26 -0
- package/dist/components/table/columnStorage.d.ts +29 -0
- package/dist/components/table/columnStorage.js +61 -0
- package/dist/components/table/groupRows.d.ts +72 -0
- package/dist/components/table/groupRows.js +118 -0
- package/dist/components/table/index.d.ts +7 -1
- package/dist/components/table/index.js +3 -0
- package/dist/components/table/types.d.ts +121 -20
- package/dist/index.d.ts +24 -2
- package/dist/index.js +36 -1
- package/dist/utils/ansiToHtml.d.ts +83 -0
- package/dist/utils/ansiToHtml.js +543 -0
- package/dist/utils/keyboardShortcutRegistry.d.ts +332 -11
- package/dist/utils/keyboardShortcutRegistry.js +1070 -76
- package/dist/utils/pollBackoff.d.ts +83 -0
- package/dist/utils/pollBackoff.js +151 -0
- package/dist/utils/snapFlick.d.ts +59 -0
- package/dist/utils/snapFlick.js +66 -0
- package/dist/video/advisors/qr.d.ts +1 -1
- package/dist/virtual/createRowWindow.d.ts +75 -0
- package/dist/virtual/createRowWindow.js +129 -0
- package/dist/virtual/index.d.ts +4 -0
- package/dist/virtual/index.js +5 -0
- package/dist/virtual/rowWindowCore.d.ts +202 -0
- package/dist/virtual/rowWindowCore.js +510 -0
- package/package.json +12 -2
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Poll backoff — coordinated exponential backoff for independent pollers.
|
|
3
|
+
* Upstreamed from the JAT IDE `utils/pollBackoff.ts` (adoption audit U12,
|
|
4
|
+
* jatui-rl7w6.5; jat-gtdiz upstream).
|
|
5
|
+
*
|
|
6
|
+
* ## The problem
|
|
7
|
+
*
|
|
8
|
+
* A dozen independent pollers (agents, tasks, sessions, …) each retrying on
|
|
9
|
+
* a fixed `setInterval` cadence no matter what multiply into a refetch storm
|
|
10
|
+
* when their endpoint goes down — every poller keeps firing at full rate and
|
|
11
|
+
* the browser's ~6-socket HTTP/1.1 pool saturates.
|
|
12
|
+
*
|
|
13
|
+
* ## The fix
|
|
14
|
+
*
|
|
15
|
+
* A single self-scheduling poll loop that:
|
|
16
|
+
* - fires `tick()` every `intervalMs` while healthy,
|
|
17
|
+
* - on failure (tick throws, rejects, or resolves `false`) backs off
|
|
18
|
+
* exponentially with jitter, capped at `maxIntervalMs`,
|
|
19
|
+
* - resets to the base interval on the first success,
|
|
20
|
+
* - skips the fetch entirely while `document.visibilityState === 'hidden'`
|
|
21
|
+
* (re-checking on the current cadence so it resumes the moment the tab is
|
|
22
|
+
* visible again),
|
|
23
|
+
* - never overlaps ticks: the next tick is scheduled only after the previous
|
|
24
|
+
* one settles, so failures cannot pile up concurrent loops.
|
|
25
|
+
*
|
|
26
|
+
* Framework-free (no runes) — each store constructs its own instance and owns
|
|
27
|
+
* its lifecycle (start on subscribe/poll-start, stop on
|
|
28
|
+
* unsubscribe/poll-stop). It complements `pollOperation` (one-shot operation
|
|
29
|
+
* polling) and pairs with `useElapsedEstimate`; see also the shared `quota`
|
|
30
|
+
* helpers — this covers the *recurring* case those do not.
|
|
31
|
+
*
|
|
32
|
+
* `createPollLoop` is a tiny factory wrapper so hosts can construct the loop
|
|
33
|
+
* without `new` (and future implementations can swap underneath it).
|
|
34
|
+
*
|
|
35
|
+
* Usage:
|
|
36
|
+
* ```ts
|
|
37
|
+
* const poller = createPollLoop({
|
|
38
|
+
* name: 'agents',
|
|
39
|
+
* intervalMs: 3000,
|
|
40
|
+
* maxIntervalMs: 60_000,
|
|
41
|
+
* tick: () => store.fetch() // return false (or throw) on failure
|
|
42
|
+
* });
|
|
43
|
+
* poller.start();
|
|
44
|
+
* poller.stop(); // on teardown
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export interface PollBackoffOptions {
|
|
48
|
+
/** Stable name for logs — e.g. the store or endpoint being polled. */
|
|
49
|
+
name: string;
|
|
50
|
+
/** Healthy poll interval (also the minimum delay and the backoff base). */
|
|
51
|
+
intervalMs: number;
|
|
52
|
+
/** Backoff cap. Defaults to 60_000 (1 minute). */
|
|
53
|
+
maxIntervalMs?: number;
|
|
54
|
+
/** Jitter as a fraction of the delay (±). Defaults to 0.25. */
|
|
55
|
+
jitterRatio?: number;
|
|
56
|
+
/**
|
|
57
|
+
* The poll body. Resolve `true`/`void` for success or a benign skip
|
|
58
|
+
* (e.g. a duplicate in-flight request). Resolve `false` or throw to
|
|
59
|
+
* signal failure and trigger backoff.
|
|
60
|
+
*/
|
|
61
|
+
tick: () => Promise<boolean | void>;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Pure backoff-delay calculation (extracted for unit testability).
|
|
65
|
+
* `failures` is the number of consecutive failures so far (1 = first failure).
|
|
66
|
+
*/
|
|
67
|
+
export declare function computeBackoffDelayMs(failures: number, intervalMs: number, maxIntervalMs?: number, jitterRatio?: number): number;
|
|
68
|
+
export declare class PollBackoff {
|
|
69
|
+
#private;
|
|
70
|
+
constructor(opts: PollBackoffOptions);
|
|
71
|
+
/** Begin (or resume) the poll loop. Idempotent while already running. */
|
|
72
|
+
start(): void;
|
|
73
|
+
/** Stop the loop and clear any pending tick. Safe to call repeatedly. */
|
|
74
|
+
stop(): void;
|
|
75
|
+
/** Forget accumulated failures so the next tick runs at the base interval. */
|
|
76
|
+
reset(): void;
|
|
77
|
+
get isActive(): boolean;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Factory form of `PollBackoff` — the `createPollLoop` name the adoption
|
|
81
|
+
* audit (U12) lands this under. Same options, same instance API.
|
|
82
|
+
*/
|
|
83
|
+
export declare function createPollLoop(opts: PollBackoffOptions): PollBackoff;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Poll backoff — coordinated exponential backoff for independent pollers.
|
|
3
|
+
* Upstreamed from the JAT IDE `utils/pollBackoff.ts` (adoption audit U12,
|
|
4
|
+
* jatui-rl7w6.5; jat-gtdiz upstream).
|
|
5
|
+
*
|
|
6
|
+
* ## The problem
|
|
7
|
+
*
|
|
8
|
+
* A dozen independent pollers (agents, tasks, sessions, …) each retrying on
|
|
9
|
+
* a fixed `setInterval` cadence no matter what multiply into a refetch storm
|
|
10
|
+
* when their endpoint goes down — every poller keeps firing at full rate and
|
|
11
|
+
* the browser's ~6-socket HTTP/1.1 pool saturates.
|
|
12
|
+
*
|
|
13
|
+
* ## The fix
|
|
14
|
+
*
|
|
15
|
+
* A single self-scheduling poll loop that:
|
|
16
|
+
* - fires `tick()` every `intervalMs` while healthy,
|
|
17
|
+
* - on failure (tick throws, rejects, or resolves `false`) backs off
|
|
18
|
+
* exponentially with jitter, capped at `maxIntervalMs`,
|
|
19
|
+
* - resets to the base interval on the first success,
|
|
20
|
+
* - skips the fetch entirely while `document.visibilityState === 'hidden'`
|
|
21
|
+
* (re-checking on the current cadence so it resumes the moment the tab is
|
|
22
|
+
* visible again),
|
|
23
|
+
* - never overlaps ticks: the next tick is scheduled only after the previous
|
|
24
|
+
* one settles, so failures cannot pile up concurrent loops.
|
|
25
|
+
*
|
|
26
|
+
* Framework-free (no runes) — each store constructs its own instance and owns
|
|
27
|
+
* its lifecycle (start on subscribe/poll-start, stop on
|
|
28
|
+
* unsubscribe/poll-stop). It complements `pollOperation` (one-shot operation
|
|
29
|
+
* polling) and pairs with `useElapsedEstimate`; see also the shared `quota`
|
|
30
|
+
* helpers — this covers the *recurring* case those do not.
|
|
31
|
+
*
|
|
32
|
+
* `createPollLoop` is a tiny factory wrapper so hosts can construct the loop
|
|
33
|
+
* without `new` (and future implementations can swap underneath it).
|
|
34
|
+
*
|
|
35
|
+
* Usage:
|
|
36
|
+
* ```ts
|
|
37
|
+
* const poller = createPollLoop({
|
|
38
|
+
* name: 'agents',
|
|
39
|
+
* intervalMs: 3000,
|
|
40
|
+
* maxIntervalMs: 60_000,
|
|
41
|
+
* tick: () => store.fetch() // return false (or throw) on failure
|
|
42
|
+
* });
|
|
43
|
+
* poller.start();
|
|
44
|
+
* poller.stop(); // on teardown
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
/** Default backoff cap: 1 minute. */
|
|
48
|
+
const DEFAULT_MAX_INTERVAL_MS = 60_000;
|
|
49
|
+
/** Default jitter: ±25%. */
|
|
50
|
+
const DEFAULT_JITTER_RATIO = 0.25;
|
|
51
|
+
/** Doubling stops growing past 2^6 = 64× the base interval. */
|
|
52
|
+
const MAX_EXPONENT = 6;
|
|
53
|
+
/**
|
|
54
|
+
* Pure backoff-delay calculation (extracted for unit testability).
|
|
55
|
+
* `failures` is the number of consecutive failures so far (1 = first failure).
|
|
56
|
+
*/
|
|
57
|
+
export function computeBackoffDelayMs(failures, intervalMs, maxIntervalMs = DEFAULT_MAX_INTERVAL_MS, jitterRatio = DEFAULT_JITTER_RATIO) {
|
|
58
|
+
const exponent = Math.min(Math.max(failures, 1), MAX_EXPONENT);
|
|
59
|
+
const base = Math.min(intervalMs * Math.pow(2, exponent), maxIntervalMs);
|
|
60
|
+
const jitter = base * jitterRatio * (Math.random() * 2 - 1);
|
|
61
|
+
// Never schedule faster than the healthy interval, even with negative jitter.
|
|
62
|
+
return Math.max(intervalMs, Math.round(base + jitter));
|
|
63
|
+
}
|
|
64
|
+
export class PollBackoff {
|
|
65
|
+
#opts;
|
|
66
|
+
#maxIntervalMs;
|
|
67
|
+
#jitterRatio;
|
|
68
|
+
#timer = null;
|
|
69
|
+
#failures = 0;
|
|
70
|
+
#stopped = true;
|
|
71
|
+
constructor(opts) {
|
|
72
|
+
this.#opts = opts;
|
|
73
|
+
this.#maxIntervalMs = opts.maxIntervalMs ?? DEFAULT_MAX_INTERVAL_MS;
|
|
74
|
+
this.#jitterRatio = opts.jitterRatio ?? DEFAULT_JITTER_RATIO;
|
|
75
|
+
if (this.#jitterRatio < 0 || this.#jitterRatio >= 1) {
|
|
76
|
+
throw new Error(`PollBackoff(${opts.name}): jitterRatio must be in [0, 1)`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** Begin (or resume) the poll loop. Idempotent while already running. */
|
|
80
|
+
start() {
|
|
81
|
+
if (!this.#stopped)
|
|
82
|
+
return;
|
|
83
|
+
this.#stopped = false;
|
|
84
|
+
this.#failures = 0;
|
|
85
|
+
this.#schedule(this.#opts.intervalMs);
|
|
86
|
+
}
|
|
87
|
+
/** Stop the loop and clear any pending tick. Safe to call repeatedly. */
|
|
88
|
+
stop() {
|
|
89
|
+
this.#stopped = true;
|
|
90
|
+
if (this.#timer !== null) {
|
|
91
|
+
clearTimeout(this.#timer);
|
|
92
|
+
this.#timer = null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/** Forget accumulated failures so the next tick runs at the base interval. */
|
|
96
|
+
reset() {
|
|
97
|
+
this.#failures = 0;
|
|
98
|
+
}
|
|
99
|
+
get isActive() {
|
|
100
|
+
return !this.#stopped;
|
|
101
|
+
}
|
|
102
|
+
#schedule(delayMs) {
|
|
103
|
+
if (this.#stopped)
|
|
104
|
+
return;
|
|
105
|
+
if (this.#timer !== null)
|
|
106
|
+
clearTimeout(this.#timer);
|
|
107
|
+
this.#timer = setTimeout(() => {
|
|
108
|
+
this.#timer = null;
|
|
109
|
+
void this.#runTick();
|
|
110
|
+
}, delayMs);
|
|
111
|
+
}
|
|
112
|
+
/** Current inter-tick delay given consecutive failures (0 = healthy). */
|
|
113
|
+
#nextDelayMs() {
|
|
114
|
+
if (this.#failures === 0)
|
|
115
|
+
return this.#opts.intervalMs;
|
|
116
|
+
return computeBackoffDelayMs(this.#failures, this.#opts.intervalMs, this.#maxIntervalMs, this.#jitterRatio);
|
|
117
|
+
}
|
|
118
|
+
async #runTick() {
|
|
119
|
+
if (this.#stopped)
|
|
120
|
+
return;
|
|
121
|
+
// Never fetch while the tab is hidden — but keep the loop alive on the
|
|
122
|
+
// current cadence so polling resumes the instant the tab is visible.
|
|
123
|
+
if (typeof document !== 'undefined' && document.visibilityState === 'hidden') {
|
|
124
|
+
this.#schedule(this.#nextDelayMs());
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const result = await this.#opts.tick();
|
|
129
|
+
if (result === false) {
|
|
130
|
+
throw new Error(`PollBackoff(${this.#opts.name}): tick reported failure`);
|
|
131
|
+
}
|
|
132
|
+
this.#failures = 0;
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
this.#failures += 1;
|
|
136
|
+
const delayMs = computeBackoffDelayMs(this.#failures, this.#opts.intervalMs, this.#maxIntervalMs, this.#jitterRatio);
|
|
137
|
+
// First failure logs at warn so a storm is visible; do not spam per retry.
|
|
138
|
+
if (this.#failures === 1 || this.#failures % 5 === 0) {
|
|
139
|
+
console.warn(`PollBackoff(${this.#opts.name}): poll failed ×${this.#failures}, backing off ${delayMs}ms`, err);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
this.#schedule(this.#nextDelayMs());
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Factory form of `PollBackoff` — the `createPollLoop` name the adoption
|
|
147
|
+
* audit (U12) lands this under. Same options, same instance API.
|
|
148
|
+
*/
|
|
149
|
+
export function createPollLoop(opts) {
|
|
150
|
+
return new PollBackoff(opts);
|
|
151
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* snapFlick.ts — pure snap-point + flick-momentum math for the bottom-sheet
|
|
3
|
+
* inspector pattern (docs/reviews/wayflow-ide-adoption-eval.md §6b).
|
|
4
|
+
*
|
|
5
|
+
* The BottomSheet drag gesture feeds pointer deltas and a measured release
|
|
6
|
+
* velocity into `resolveSnapTarget`, which decides where the sheet settles:
|
|
7
|
+
*
|
|
8
|
+
* • flick-momentum release — a fast flick (≥ FLICK_VELOCITY_PX_MS) wins over
|
|
9
|
+
* position: flick up → next (taller) snap, flick down → previous (shorter)
|
|
10
|
+
* snap, or dismissal when already at the peek snap.
|
|
11
|
+
* • otherwise the snap closest to the post-drag height wins (position release).
|
|
12
|
+
*
|
|
13
|
+
* Pure and DOM-free so it is unit-testable; BottomSheet.svelte applies the
|
|
14
|
+
* result to its own drag state.
|
|
15
|
+
*/
|
|
16
|
+
/** Velocity (px/ms) at or above which a release counts as a flick. */
|
|
17
|
+
export declare const FLICK_VELOCITY_PX_MS = 0.5;
|
|
18
|
+
export type SnapName = 'peek' | 'expanded';
|
|
19
|
+
export interface SnapHeight {
|
|
20
|
+
name: SnapName;
|
|
21
|
+
/** Height of the snap point in px (already resolved against the viewport). */
|
|
22
|
+
heightPx: number;
|
|
23
|
+
}
|
|
24
|
+
export interface SnapInput {
|
|
25
|
+
/** Net pointer travel since drag start: positive = dragged down. */
|
|
26
|
+
dragDeltaY: number;
|
|
27
|
+
/** Measured release velocity: positive = moving down, px/ms. */
|
|
28
|
+
velocity: number;
|
|
29
|
+
/** Snap points ordered peek → expanded (ascending height). */
|
|
30
|
+
snapPoints: SnapHeight[];
|
|
31
|
+
/** Index of the snap the sheet was resting at when the drag began. */
|
|
32
|
+
currentIndex: number;
|
|
33
|
+
}
|
|
34
|
+
export interface SnapResult {
|
|
35
|
+
/** Snap index to settle at (bounded to snapPoints). */
|
|
36
|
+
index: number;
|
|
37
|
+
/**
|
|
38
|
+
* True when a downward flick from the shortest snap requests dismissal
|
|
39
|
+
* (the classic flick-the-sheet-away gesture). The caller closes the sheet.
|
|
40
|
+
*/
|
|
41
|
+
dismiss: boolean;
|
|
42
|
+
}
|
|
43
|
+
export declare function resolveSnapTarget(input: SnapInput): SnapResult;
|
|
44
|
+
/**
|
|
45
|
+
* Keyboard avoidance (wayflow §6b): the visible viewport shrinks when the soft
|
|
46
|
+
* keyboard opens. The inset the sheet must clear is what `window.innerHeight`
|
|
47
|
+
* counts but the `visualViewport` no longer covers, plus whatever the visual
|
|
48
|
+
* viewport itself is pushed up by.
|
|
49
|
+
*/
|
|
50
|
+
export declare function keyboardInsetPx(visualViewport: {
|
|
51
|
+
height: number;
|
|
52
|
+
offsetTop: number;
|
|
53
|
+
}, windowInnerHeight: number): number;
|
|
54
|
+
/**
|
|
55
|
+
* Height of the sheet at a snap point, clamped to what is actually visible —
|
|
56
|
+
* under keyboard avoidance the usable height is the visual viewport's, so an
|
|
57
|
+
* expanded snap can never extend behind the keyboard.
|
|
58
|
+
*/
|
|
59
|
+
export declare function clampSnapHeight(snapHeightPx: number, availableHeightPx: number): number;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* snapFlick.ts — pure snap-point + flick-momentum math for the bottom-sheet
|
|
3
|
+
* inspector pattern (docs/reviews/wayflow-ide-adoption-eval.md §6b).
|
|
4
|
+
*
|
|
5
|
+
* The BottomSheet drag gesture feeds pointer deltas and a measured release
|
|
6
|
+
* velocity into `resolveSnapTarget`, which decides where the sheet settles:
|
|
7
|
+
*
|
|
8
|
+
* • flick-momentum release — a fast flick (≥ FLICK_VELOCITY_PX_MS) wins over
|
|
9
|
+
* position: flick up → next (taller) snap, flick down → previous (shorter)
|
|
10
|
+
* snap, or dismissal when already at the peek snap.
|
|
11
|
+
* • otherwise the snap closest to the post-drag height wins (position release).
|
|
12
|
+
*
|
|
13
|
+
* Pure and DOM-free so it is unit-testable; BottomSheet.svelte applies the
|
|
14
|
+
* result to its own drag state.
|
|
15
|
+
*/
|
|
16
|
+
/** Velocity (px/ms) at or above which a release counts as a flick. */
|
|
17
|
+
export const FLICK_VELOCITY_PX_MS = 0.5;
|
|
18
|
+
export function resolveSnapTarget(input) {
|
|
19
|
+
const { dragDeltaY, velocity, snapPoints, currentIndex } = input;
|
|
20
|
+
if (snapPoints.length === 0) {
|
|
21
|
+
return { index: 0, dismiss: false };
|
|
22
|
+
}
|
|
23
|
+
const at = Math.max(0, Math.min(snapPoints.length - 1, currentIndex));
|
|
24
|
+
// Flick-momentum release: a fast flick overrides position entirely.
|
|
25
|
+
if (Math.abs(velocity) >= FLICK_VELOCITY_PX_MS) {
|
|
26
|
+
if (velocity < 0) {
|
|
27
|
+
// Upward flick → grow toward the tallest snap.
|
|
28
|
+
return { index: Math.min(snapPoints.length - 1, at + 1), dismiss: false };
|
|
29
|
+
}
|
|
30
|
+
// Downward flick → shrink one snap; from the shortest snap, dismiss.
|
|
31
|
+
if (at === 0)
|
|
32
|
+
return { index: 0, dismiss: true };
|
|
33
|
+
return { index: at - 1, dismiss: false };
|
|
34
|
+
}
|
|
35
|
+
// Position release: the height the sheet would rest at after the drag
|
|
36
|
+
// (dragging down shrinks the sheet) settles on the nearest snap point.
|
|
37
|
+
const currentHeight = snapPoints[at].heightPx;
|
|
38
|
+
const restHeight = Math.max(0, currentHeight - dragDeltaY);
|
|
39
|
+
let best = at;
|
|
40
|
+
let bestDist = Infinity;
|
|
41
|
+
snapPoints.forEach((p, i) => {
|
|
42
|
+
const dist = Math.abs(p.heightPx - restHeight);
|
|
43
|
+
if (dist < bestDist) {
|
|
44
|
+
bestDist = dist;
|
|
45
|
+
best = i;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
return { index: best, dismiss: false };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Keyboard avoidance (wayflow §6b): the visible viewport shrinks when the soft
|
|
52
|
+
* keyboard opens. The inset the sheet must clear is what `window.innerHeight`
|
|
53
|
+
* counts but the `visualViewport` no longer covers, plus whatever the visual
|
|
54
|
+
* viewport itself is pushed up by.
|
|
55
|
+
*/
|
|
56
|
+
export function keyboardInsetPx(visualViewport, windowInnerHeight) {
|
|
57
|
+
return Math.max(0, windowInnerHeight - (visualViewport.height + visualViewport.offsetTop));
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Height of the sheet at a snap point, clamped to what is actually visible —
|
|
61
|
+
* under keyboard avoidance the usable height is the visual viewport's, so an
|
|
62
|
+
* expanded snap can never extend behind the keyboard.
|
|
63
|
+
*/
|
|
64
|
+
export function clampSnapHeight(snapHeightPx, availableHeightPx) {
|
|
65
|
+
return Math.max(0, Math.min(snapHeightPx, availableHeightPx));
|
|
66
|
+
}
|
|
@@ -50,7 +50,7 @@ declare function rsEncode(data: Uint8Array, ecLen: number): Uint8Array;
|
|
|
50
50
|
declare function rsSyndromes(block: number[], ecLen: number): Uint8Array;
|
|
51
51
|
declare function formatBits(ecc: string, mask: number): number;
|
|
52
52
|
declare function versionBits(version: number): number;
|
|
53
|
-
declare const charCountBits: (version: number) =>
|
|
53
|
+
declare const charCountBits: (version: number) => 16 | 8;
|
|
54
54
|
declare const dataCodewords: (version: number, ecc: EccLevel) => number;
|
|
55
55
|
declare const maxBytes: (version: number, ecc: EccLevel) => number;
|
|
56
56
|
declare function toBytes(text: unknown): Uint8Array;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { type RowWindowAlign, type RowWindowItem } from './rowWindowCore';
|
|
2
|
+
export type { RowWindowAlign, RowWindowItem, RowWindowSnapshot } from './rowWindowCore';
|
|
3
|
+
export interface RowWindowOptions<K = string> {
|
|
4
|
+
/** Number of items in the render list (rows + group headers). Reactive getter. */
|
|
5
|
+
count: () => number;
|
|
6
|
+
/** Stable, unique key for item i (task id, or `group:<key>` for a header). */
|
|
7
|
+
getKey: (index: number) => K;
|
|
8
|
+
/** First-paint height guess per item, px. Measured heights replace it. */
|
|
9
|
+
estimateSize: (index: number) => number;
|
|
10
|
+
/**
|
|
11
|
+
* The element that scrolls. `'auto'` (default) = the nearest ancestor that
|
|
12
|
+
* really scrolls (overflow auto/scroll AND content taller than it), else the
|
|
13
|
+
* window. An `overflow-auto` wrapper that grows with its content is skipped,
|
|
14
|
+
* which fixes the dash case. Re-resolved when the anchor or root resizes.
|
|
15
|
+
*/
|
|
16
|
+
scrollRoot?: HTMLElement | Window | 'auto';
|
|
17
|
+
/** Extra items rendered above and below the viewport. Default 6. */
|
|
18
|
+
overscan?: number;
|
|
19
|
+
/**
|
|
20
|
+
* Keys that stay rendered even when out of view: an exiting row, the focused
|
|
21
|
+
* row, an open inline editor or popover. Reactive getter.
|
|
22
|
+
*/
|
|
23
|
+
pinned?: () => Iterable<K>;
|
|
24
|
+
/**
|
|
25
|
+
* Below this count, render everything. That keeps FLIP and enter animations
|
|
26
|
+
* intact on small lists. Default 0 = always window. Reactive when passed as
|
|
27
|
+
* a getter (`get threshold() { return t }`).
|
|
28
|
+
*/
|
|
29
|
+
threshold?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Indices of sticky items (group headers). The active one for the current
|
|
32
|
+
* scroll offset is always rendered. For nested groups, pass one array per
|
|
33
|
+
* level, outermost first (`[projectHeaders, epicHeaders]`), and each level
|
|
34
|
+
* keeps its own active header rendered. Reactive getter.
|
|
35
|
+
*/
|
|
36
|
+
stickyIndices?: () => readonly number[] | readonly (readonly number[])[];
|
|
37
|
+
/** Height a sticky header covers at the top, px. `scrollTo*` keeps targets below it. */
|
|
38
|
+
scrollPaddingStart?: number;
|
|
39
|
+
/** Height of a sticky footer at the bottom, px. */
|
|
40
|
+
scrollPaddingEnd?: number;
|
|
41
|
+
/** Viewport height assumed on the server and before mount, px. Default 800. */
|
|
42
|
+
initialViewportSize?: number;
|
|
43
|
+
}
|
|
44
|
+
export interface RowWindow<K = string> {
|
|
45
|
+
/** Reactive: the items to render, in order, with their offsets. */
|
|
46
|
+
readonly items: ReadonlyArray<RowWindowItem<K>>;
|
|
47
|
+
/** Spacer above the rendered slice, px. */
|
|
48
|
+
readonly paddingTop: number;
|
|
49
|
+
/** Spacer below the rendered slice, px. */
|
|
50
|
+
readonly paddingBottom: number;
|
|
51
|
+
/** Height of the whole list, px. */
|
|
52
|
+
readonly totalSize: number;
|
|
53
|
+
/** False while `count < threshold` (everything is rendered). */
|
|
54
|
+
readonly windowed: boolean;
|
|
55
|
+
/** Svelte action for each rendered item: `use:win.measure={item.key}`. */
|
|
56
|
+
measure: (node: HTMLElement, key: K) => {
|
|
57
|
+
update(key: K): void;
|
|
58
|
+
destroy(): void;
|
|
59
|
+
};
|
|
60
|
+
/** Svelte action for the anchor element (a `<tbody>` or list container). */
|
|
61
|
+
anchor: (node: HTMLElement) => {
|
|
62
|
+
destroy(): void;
|
|
63
|
+
};
|
|
64
|
+
scrollToIndex(index: number, opts?: {
|
|
65
|
+
align?: RowWindowAlign;
|
|
66
|
+
}): void;
|
|
67
|
+
scrollToKey(key: K, opts?: {
|
|
68
|
+
align?: RowWindowAlign;
|
|
69
|
+
}): void;
|
|
70
|
+
/** Index of `key` in the render list, or -1. */
|
|
71
|
+
indexOf(key: K): number;
|
|
72
|
+
/** Drop cached heights and re-measure, e.g. after a column-width change. */
|
|
73
|
+
remeasure(): void;
|
|
74
|
+
}
|
|
75
|
+
export declare function createRowWindow<K = string>(opts: RowWindowOptions<K>): RowWindow<K>;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/* createRowWindow.svelte.js generated by Svelte v5.53.11 */
|
|
2
|
+
import * as $ from 'svelte/internal/client';
|
|
3
|
+
import { untrack } from 'svelte';
|
|
4
|
+
import { EMPTY_SNAPSHOT, RowWindowEngine } from './rowWindowCore';
|
|
5
|
+
|
|
6
|
+
export function createRowWindow(opts) {
|
|
7
|
+
let snap = $.state(EMPTY_SNAPSHOT);
|
|
8
|
+
|
|
9
|
+
const engine = new RowWindowEngine({
|
|
10
|
+
overscan: opts.overscan ?? 6,
|
|
11
|
+
threshold: opts.threshold ?? 0,
|
|
12
|
+
scrollRoot: opts.scrollRoot ?? 'auto',
|
|
13
|
+
initialViewportSize: opts.initialViewportSize ?? 800,
|
|
14
|
+
scrollPaddingStart: opts.scrollPaddingStart ?? 0,
|
|
15
|
+
scrollPaddingEnd: opts.scrollPaddingEnd ?? 0,
|
|
16
|
+
onChange: (s) => {
|
|
17
|
+
$.set(snap, s);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const readItems = () => {
|
|
22
|
+
const n = Math.max(0, Math.floor(opts.count()));
|
|
23
|
+
const keys = new Array(n);
|
|
24
|
+
const estimates = new Array(n);
|
|
25
|
+
|
|
26
|
+
for (let i = 0; i < n; i++) {
|
|
27
|
+
keys[i] = opts.getKey(i);
|
|
28
|
+
estimates[i] = opts.estimateSize(i);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
engine.setItems(keys, estimates);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Eager first pass, so SSR (where effects never run) and the first client
|
|
35
|
+
// render already have a slice to draw.
|
|
36
|
+
untrack(() => {
|
|
37
|
+
if (opts.stickyIndices) engine.setSticky(opts.stickyIndices());
|
|
38
|
+
if (opts.pinned) engine.setPinned(opts.pinned());
|
|
39
|
+
|
|
40
|
+
readItems();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// dist is compiled with `generate: 'client'` (scripts/compile-rune-modules.mjs),
|
|
44
|
+
// so an effect created during SSR throws `effect_orphan`. The server render
|
|
45
|
+
// has everything it needs from the eager pass above.
|
|
46
|
+
if (typeof window !== 'undefined') {
|
|
47
|
+
$.user_pre_effect(() => {
|
|
48
|
+
engine.setThreshold(opts.threshold ?? 0);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
$.user_pre_effect(() => {
|
|
52
|
+
readItems();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
if (opts.pinned) {
|
|
56
|
+
const pinned = opts.pinned;
|
|
57
|
+
|
|
58
|
+
$.user_pre_effect(() => {
|
|
59
|
+
engine.setPinned(pinned());
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (opts.stickyIndices) {
|
|
64
|
+
const sticky = opts.stickyIndices;
|
|
65
|
+
|
|
66
|
+
$.user_pre_effect(() => {
|
|
67
|
+
engine.setSticky(sticky());
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
$.user_effect(() => () => engine.destroy());
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
get items() {
|
|
76
|
+
return $.get(snap).items;
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
get paddingTop() {
|
|
80
|
+
return $.get(snap).paddingTop;
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
get paddingBottom() {
|
|
84
|
+
return $.get(snap).paddingBottom;
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
get totalSize() {
|
|
88
|
+
return $.get(snap).totalSize;
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
get windowed() {
|
|
92
|
+
return $.get(snap).windowed;
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
measure(node, key) {
|
|
96
|
+
let current = key;
|
|
97
|
+
|
|
98
|
+
engine.measure(node, current);
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
update(next) {
|
|
102
|
+
if (next === current) return;
|
|
103
|
+
|
|
104
|
+
engine.unmeasure(node, current);
|
|
105
|
+
current = next;
|
|
106
|
+
engine.measure(node, current);
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
destroy() {
|
|
110
|
+
engine.unmeasure(node, current);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
anchor(node) {
|
|
116
|
+
engine.attach(node);
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
destroy() {
|
|
120
|
+
engine.detach();
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
},
|
|
124
|
+
scrollToIndex: (index, o) => engine.scrollToIndex(index, o),
|
|
125
|
+
scrollToKey: (key, o) => engine.scrollToKey(key, o),
|
|
126
|
+
indexOf: (key) => engine.indexOf(key),
|
|
127
|
+
remeasure: () => engine.remeasure()
|
|
128
|
+
};
|
|
129
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { createRowWindow } from './createRowWindow';
|
|
2
|
+
export type { RowWindow, RowWindowOptions } from './createRowWindow';
|
|
3
|
+
export { RowWindowEngine, activeStickyIndices, extractWindowIndices, findScrollRoot, layoutWindow, normalizeStickyLevels } from './rowWindowCore';
|
|
4
|
+
export type { RowWindowAlign, RowWindowEngineOptions, RowWindowItem, RowWindowSnapshot, WindowPolicy } from './rowWindowCore';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Subpath entry: @joewinke/jatui/virtual — requires peer @tanstack/virtual-core.
|
|
2
|
+
// Kept out of the root barrel so consumers that never window a list need not
|
|
3
|
+
// install the optional peer (the dnd lesson, 6c9b104).
|
|
4
|
+
export { createRowWindow } from './createRowWindow';
|
|
5
|
+
export { RowWindowEngine, activeStickyIndices, extractWindowIndices, findScrollRoot, layoutWindow, normalizeStickyLevels } from './rowWindowCore';
|