cursedbelt 5.77.0 → 5.78.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/react/canvas-hud/CanvasToolbar.d.ts +1 -1
- package/dist/react/components/DataTable/TableFilterBuilder.d.ts.map +1 -1
- package/dist/react/components/DataTable/TableFilterBuilder.js +2 -2
- package/dist/react/components/DataTable/TableFilterBuilder.js.map +1 -1
- package/dist/react/components/ListToolbar.d.ts +2 -2
- package/dist/react/components/ListToolbar.d.ts.map +1 -1
- package/dist/react/components/ListToolbar.js +1 -1
- package/dist/react/hooks/useOutsidePress.d.ts.map +1 -1
- package/dist/react/hooks/useOutsidePress.js +18 -3
- package/dist/react/hooks/useOutsidePress.js.map +1 -1
- package/dist/react/hooks/useScrollMemory.d.ts +4 -3
- package/dist/react/hooks/useScrollMemory.d.ts.map +1 -1
- package/dist/react/hooks/useScrollMemory.js +16 -2
- package/dist/react/hooks/useScrollMemory.js.map +1 -1
- package/dist/react/media/FocusViewer.d.ts +8 -1
- package/dist/react/media/FocusViewer.d.ts.map +1 -1
- package/dist/react/media/FocusViewer.js +174 -10
- package/dist/react/media/FocusViewer.js.map +1 -1
- package/dist/react/media/focusViewer.css +31 -0
- package/dist/react/media/gifClip.d.ts +46 -0
- package/dist/react/media/gifClip.d.ts.map +1 -0
- package/dist/react/media/gifClip.js +74 -0
- package/dist/react/media/gifClip.js.map +1 -0
- package/dist/react/media/gifEncode.d.ts +46 -0
- package/dist/react/media/gifEncode.d.ts.map +1 -0
- package/dist/react/media/gifEncode.js +288 -0
- package/dist/react/media/gifEncode.js.map +1 -0
- package/dist/react/media/gifRecord.d.ts +26 -0
- package/dist/react/media/gifRecord.d.ts.map +1 -0
- package/dist/react/media/gifRecord.js +93 -0
- package/dist/react/media/gifRecord.js.map +1 -0
- package/dist/scripts/guardrailsEnforce.d.ts.map +1 -1
- package/dist/styles-areas/analytics.css +9 -9
- package/dist/styles-areas/data-table.css +10 -10
- package/dist/styles-areas/media.css +2 -2
- package/package.json +3 -3
- package/scripts/guardrailsEnforce.spec.ts +10 -0
- package/scripts/guardrailsEnforce.ts +4 -1
- package/scripts/styleAreas.ts +3 -1
- package/src/react/canvas-hud/CanvasToolbar.tsx +1 -1
- package/src/react/components/DataTable/TableFilterBuilder.tsx +9 -13
- package/src/react/components/DataTable/tableSystem.spec.tsx +15 -7
- package/src/react/components/ListToolbar.tsx +2 -2
- package/src/react/hooks/useOutsidePress.spec.tsx +20 -0
- package/src/react/hooks/useOutsidePress.ts +17 -2
- package/src/react/hooks/useScrollMemory.spec.tsx +45 -2
- package/src/react/hooks/useScrollMemory.ts +20 -5
- package/src/react/media/FocusViewer.tsx +322 -25
- package/src/react/media/focusViewer.css +31 -0
- package/src/react/media/gifClip.ts +90 -0
- package/src/react/media/gifEncode.spec.ts +213 -0
- package/src/react/media/gifEncode.ts +298 -0
- package/src/react/media/gifRecord.ts +103 -0
- package/src/styles-areas/analytics.css +9 -9
- package/src/styles-areas/data-table.css +10 -10
- package/src/styles-areas/media.css +2 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gifEncode.js","sourceRoot":"","sources":["../../../src/react/media/gifEncode.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type GifSection } from "./gifClip.js";
|
|
2
|
+
export interface RecordGifOptions extends GifSection {
|
|
3
|
+
fps: number;
|
|
4
|
+
/** Requested output width; see {@link gifSize}. */
|
|
5
|
+
width: number;
|
|
6
|
+
/** The source rectangle, in video pixels — what is on screen in a zoomed viewer. Whole frame when omitted. */
|
|
7
|
+
crop?: {
|
|
8
|
+
sx: number;
|
|
9
|
+
sy: number;
|
|
10
|
+
sw: number;
|
|
11
|
+
sh: number;
|
|
12
|
+
} | null;
|
|
13
|
+
/** A CSS filter baked into every frame, the same string the preview shows. */
|
|
14
|
+
filter?: string;
|
|
15
|
+
onProgress?: (done: number, total: number) => void;
|
|
16
|
+
signal?: AbortSignal;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Seek `video` through the section and encode what it shows as a looping GIF.
|
|
20
|
+
*
|
|
21
|
+
* The element is paused for the capture and handed back where it was, paused or playing.
|
|
22
|
+
* Rejects with an `AbortError` when `signal` aborts, and with a sentence when a frame cannot
|
|
23
|
+
* be read (a cross-origin source without CORS taints the canvas) or a seek never lands.
|
|
24
|
+
*/
|
|
25
|
+
export declare function recordGifClip(video: HTMLVideoElement, options: RecordGifOptions): Promise<Blob>;
|
|
26
|
+
//# sourceMappingURL=gifRecord.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gifRecord.d.ts","sourceRoot":"","sources":["../../../src/react/media/gifRecord.ts"],"names":[],"mappings":"AASA,OAAO,EAAiB,KAAK,UAAU,EAA0B,MAAM,cAAc,CAAC;AAGtF,MAAM,WAAW,gBAAiB,SAAQ,UAAU;IAClD,GAAG,EAAE,MAAM,CAAC;IACZ,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAC;IACd,8GAA8G;IAC9G,IAAI,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACjE,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoCrG"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// ── Making the GIF: seek, draw, encode ────────────────────────────────────────────────
|
|
2
|
+
// Loaded on demand by the focus viewer (see `gifClip.ts`'s header) — it and `gifEncode.ts` are
|
|
3
|
+
// the bytes of this feature, and a gallery that never makes a GIF should not pay for them.
|
|
4
|
+
//
|
|
5
|
+
// 🔴 Frames are taken by SEEKING the element to each moment, never by playing it and
|
|
6
|
+
// sampling what goes past. Playback drops and repeats frames whenever the machine is busy —
|
|
7
|
+
// and encoding is what keeps it busy — so a played capture stutters exactly as much as the
|
|
8
|
+
// encoder is slow. A seek lands on the frame for that time however long the last one took.
|
|
9
|
+
import { formatGifTime, gifFrameTimes, gifSize } from "./gifClip.js";
|
|
10
|
+
import { GifWriter } from "./gifEncode.js";
|
|
11
|
+
/**
|
|
12
|
+
* Seek `video` through the section and encode what it shows as a looping GIF.
|
|
13
|
+
*
|
|
14
|
+
* The element is paused for the capture and handed back where it was, paused or playing.
|
|
15
|
+
* Rejects with an `AbortError` when `signal` aborts, and with a sentence when a frame cannot
|
|
16
|
+
* be read (a cross-origin source without CORS taints the canvas) or a seek never lands.
|
|
17
|
+
*/
|
|
18
|
+
export async function recordGifClip(video, options) {
|
|
19
|
+
if (!video.videoWidth || !video.videoHeight)
|
|
20
|
+
throw new Error("The video has not decoded a frame yet.");
|
|
21
|
+
const crop = options.crop ?? { sx: 0, sy: 0, sw: video.videoWidth, sh: video.videoHeight };
|
|
22
|
+
const size = gifSize(crop, options.width);
|
|
23
|
+
const times = gifFrameTimes(options, options.fps);
|
|
24
|
+
const canvas = document.createElement("canvas");
|
|
25
|
+
canvas.width = size.width;
|
|
26
|
+
canvas.height = size.height;
|
|
27
|
+
const ctx = canvas.getContext("2d", { willReadFrequently: true });
|
|
28
|
+
if (!ctx)
|
|
29
|
+
throw new Error("This browser cannot capture a frame.");
|
|
30
|
+
ctx.imageSmoothingEnabled = true;
|
|
31
|
+
ctx.imageSmoothingQuality = "high";
|
|
32
|
+
const was = { at: video.currentTime, playing: !video.paused };
|
|
33
|
+
video.pause();
|
|
34
|
+
const writer = new GifWriter(size.width, size.height);
|
|
35
|
+
try {
|
|
36
|
+
for (const [i, at] of times.entries()) {
|
|
37
|
+
if (options.signal?.aborted)
|
|
38
|
+
throw new DOMException("The GIF was cancelled.", "AbortError");
|
|
39
|
+
await seekForCapture(video, at);
|
|
40
|
+
if (options.filter && "filter" in ctx)
|
|
41
|
+
ctx.filter = options.filter;
|
|
42
|
+
ctx.drawImage(video, crop.sx, crop.sy, crop.sw, crop.sh, 0, 0, size.width, size.height);
|
|
43
|
+
let frame;
|
|
44
|
+
try {
|
|
45
|
+
frame = ctx.getImageData(0, 0, size.width, size.height);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
throw new Error("This video's frames cannot be read — it may be cross-origin.");
|
|
49
|
+
}
|
|
50
|
+
writer.addFrame(frame.data, 1000 / options.fps);
|
|
51
|
+
options.onProgress?.(i + 1, times.length);
|
|
52
|
+
}
|
|
53
|
+
return new Blob([writer.finish()], { type: "image/gif" });
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
video.currentTime = was.at;
|
|
57
|
+
if (was.playing)
|
|
58
|
+
void video.play().catch(() => undefined);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** How long one seek may take before the capture gives up and says so. */
|
|
62
|
+
const SEEK_TIMEOUT_MS = 8_000;
|
|
63
|
+
/**
|
|
64
|
+
* Move to `seconds` and wait until that frame is the one a canvas will read.
|
|
65
|
+
*
|
|
66
|
+
* `seeked` says the element has the data; on WebKit the PAINTED frame can still be the old one
|
|
67
|
+
* for a tick after it, so where `requestVideoFrameCallback` exists the capture also waits for
|
|
68
|
+
* the new frame to be presented — bounded, because a seek to the frame already showing
|
|
69
|
+
* presents nothing new.
|
|
70
|
+
*/
|
|
71
|
+
async function seekForCapture(video, seconds) {
|
|
72
|
+
if (Math.abs(video.currentTime - seconds) < 1e-4 && !video.seeking && video.readyState >= 2)
|
|
73
|
+
return;
|
|
74
|
+
await new Promise((resolve, reject) => {
|
|
75
|
+
const timer = setTimeout(() => {
|
|
76
|
+
video.removeEventListener("seeked", done);
|
|
77
|
+
reject(new Error(`The video stopped answering at ${formatGifTime(seconds)} — try a shorter section.`));
|
|
78
|
+
}, SEEK_TIMEOUT_MS);
|
|
79
|
+
function done() {
|
|
80
|
+
clearTimeout(timer);
|
|
81
|
+
resolve();
|
|
82
|
+
}
|
|
83
|
+
video.addEventListener("seeked", done, { once: true });
|
|
84
|
+
video.currentTime = seconds;
|
|
85
|
+
});
|
|
86
|
+
const presented = video.requestVideoFrameCallback;
|
|
87
|
+
if (typeof presented === "function")
|
|
88
|
+
await Promise.race([
|
|
89
|
+
new Promise((resolve) => presented.call(video, () => resolve())),
|
|
90
|
+
new Promise((resolve) => setTimeout(resolve, 150)),
|
|
91
|
+
]);
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=gifRecord.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gifRecord.js","sourceRoot":"","sources":["../../../src/react/media/gifRecord.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guardrailsEnforce.d.ts","sourceRoot":"","sources":["../../scripts/guardrailsEnforce.ts"],"names":[],"mappings":";AA6PA,UAAU,SAAS;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB;;;sEAGkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,IAAI;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,SAAS,EAAE,CAAC;CACzD;AAED,wBAAgB,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,EAAO,GAAG,MAAM,EAAE,CA6B9D;AAgwCD,iGAAiG;AACjG,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAE3D;AAoHD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAIpE;
|
|
1
|
+
{"version":3,"file":"guardrailsEnforce.d.ts","sourceRoot":"","sources":["../../scripts/guardrailsEnforce.ts"],"names":[],"mappings":";AA6PA,UAAU,SAAS;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB;;;sEAGkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,IAAI;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,SAAS,EAAE,CAAC;CACzD;AAED,wBAAgB,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,EAAO,GAAG,MAAM,EAAE,CA6B9D;AAgwCD,iGAAiG;AACjG,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAE3D;AAoHD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAIpE;AAm4BD,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,GAAG,IAAI,CAEpF;AAID,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,CAoBhE;AAyKD,kGAAkG;AAClG,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAatE;AAqDD,UAAU,MAAM;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACb;AA0BD,+FAA+F;AAC/F,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAgBpD;AAID,wEAAwE;AACxE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAO5E;AA0FD,eAAO,MAAM,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAiCtC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,WAAW,CAAC,MAAM,CAAiE,CAAC;AAEtH,0EAA0E;AAC1E,eAAO,MAAM,SAAS,EAAE,SAAS,MAAM,EAAwE,CAAC;AAShH,eAAO,MAAM,uBAAuB,aAAkD,CAAC;AAGvF,MAAM,WAAW,SAAS;IACzB,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACb;AAED;2FAC2F;AAC3F,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CA+C3F;AAED,YAAY,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;AAmGhC,wBAAgB,0BAA0B,CAAC,SAAS,GAAE,MAAwB,GAAG,MAAM,EAAE,CAyBxF"}
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* after an app's responsive variant (task 2124). An app that imports every area gets exactly
|
|
19
19
|
* what `cursedbelt/styles-utilities.css` emits — proved rule by rule in
|
|
20
20
|
* src/stylesAreas.spec.ts, which is also what fails when this file is stale.
|
|
21
|
-
*
|
|
21
|
+
* 153 candidate(s).
|
|
22
22
|
*
|
|
23
23
|
* Written by scripts/generateAreaStyles.ts (`bun run styles:areas`), which
|
|
24
24
|
* `bun run build` runs. The map of what is in which area, and why the areas are
|
|
@@ -31,13 +31,13 @@
|
|
|
31
31
|
@source inline("@max-4xl:p-0! @max-4xl:text-[length:0] @max-4xl:w-0! @max-6xl:[&>*]:hidden @max-6xl:border-0");
|
|
32
32
|
@source inline("@max-6xl:invisible @max-6xl:min-w-0! @max-6xl:p-0! @max-6xl:text-[length:0] @max-6xl:w-0!");
|
|
33
33
|
@source inline("@max-xl:contents @xl:basis-auto @xl:block @xl:hidden [&>*]:hidden [&>*]:max-w-none");
|
|
34
|
-
@source inline("[&>svg]:size-3.5 [&_svg]:size-3 [overflow-wrap:anywhere] active:cursor-grabbing
|
|
35
|
-
@source inline("
|
|
36
|
-
@source inline("border-accent
|
|
37
|
-
@source inline("
|
|
38
|
-
@source inline("data-[highlighted]:
|
|
39
|
-
@source inline("data-[starting-style]:
|
|
40
|
-
@source inline("
|
|
34
|
+
@source inline("[&>svg]:size-3.5 [&_svg]:size-3 [overflow-wrap:anywhere] active:cursor-grabbing basis basis-full");
|
|
35
|
+
@source inline("bg-[var(--cb-frozen-bg)] bg-card/95 bg-gradient-to-t bg-muted/30 bg-muted/50 border-accent-solid");
|
|
36
|
+
@source inline("border-accent/60 border-field-error border-solid border-t-2 col collapse cursor-grab");
|
|
37
|
+
@source inline("data-[ending-style]:opacity-0 data-[ending-style]:scale-95 data-[highlighted]:bg-accent/10");
|
|
38
|
+
@source inline("data-[highlighted]:text-accent-strong data-[starting-style]:opacity-0");
|
|
39
|
+
@source inline("data-[starting-style]:scale-95 disabled:opacity-40 duration-100 fade-in");
|
|
40
|
+
@source inline("focus-visible:ring-field-ring/40 focus-within:border-field-ring focus-within:ring-2");
|
|
41
41
|
@source inline("focus-within:ring-field-ring/30 from-card gap-x-6 h-105");
|
|
42
42
|
@source inline("has-[[aria-invalid=true]]:border-field-error hover:bg-background hover:bg-muted/40");
|
|
43
43
|
@source inline("hover:border-field-ring hover:text-warning italic justify-self-end last:border-0 left-11");
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
@source inline("max-sm:min-w-24 max-sm:min-w-32 max-sm:ml-13.5 max-sm:w-auto max-w-32 max-w-36 max-w-45 max-w-48");
|
|
46
46
|
@source inline("max-w-60 min-h-20 min-h-dvh min-w-0! min-w-20 min-w-24 min-w-52 min-w-[var(--anchor-width)]");
|
|
47
47
|
@source inline("min-w-max ml-1.5 mr-2 mt-1.5 mt-3 mt-4 not-italic ordinal p-0! pb-1.5 pl-1.5 pl-2 pr-0.5 pr-1");
|
|
48
|
-
@source inline("pr-10 pr-16 pr-3
|
|
48
|
+
@source inline("pr-10 pr-16 pr-3 pt-1 resize-none resize-y right-3 ring-field-error/30 self self-stretch");
|
|
49
49
|
@source inline("shadow-[1px_0_0_0_var(--color-border)] shrink-[2] size-7 slide-in-from-top-1 table-fixed");
|
|
50
50
|
@source inline("text-[length:0] text-accent-solid-foreground text-field-placeholder text-success text-xl");
|
|
51
51
|
@source inline("to-transparent top-3 transition-[transform,opacity] w-0 w-0! w-11 w-12 w-20 w-32 w-36 w-40 w-88");
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* after an app's responsive variant (task 2124). An app that imports every area gets exactly
|
|
19
19
|
* what `cursedbelt/styles-utilities.css` emits — proved rule by rule in
|
|
20
20
|
* src/stylesAreas.spec.ts, which is also what fails when this file is stale.
|
|
21
|
-
*
|
|
21
|
+
* 148 candidate(s).
|
|
22
22
|
*
|
|
23
23
|
* Written by scripts/generateAreaStyles.ts (`bun run styles:areas`), which
|
|
24
24
|
* `bun run build` runs. The map of what is in which area, and why the areas are
|
|
@@ -31,21 +31,21 @@
|
|
|
31
31
|
@source inline("@max-4xl:p-0! @max-4xl:text-[length:0] @max-4xl:w-0! @max-6xl:[&>*]:hidden @max-6xl:border-0");
|
|
32
32
|
@source inline("@max-6xl:invisible @max-6xl:min-w-0! @max-6xl:p-0! @max-6xl:text-[length:0] @max-6xl:w-0!");
|
|
33
33
|
@source inline("@max-xl:contents @xl:basis-auto @xl:block @xl:hidden [&>*]:hidden [&>*]:max-w-none");
|
|
34
|
-
@source inline("[&>svg]:size-3.5 [&_svg]:size-3 [overflow-wrap:anywhere] active:cursor-grabbing
|
|
35
|
-
@source inline("
|
|
36
|
-
@source inline("border-accent
|
|
37
|
-
@source inline("
|
|
38
|
-
@source inline("data-[highlighted]:
|
|
39
|
-
@source inline("data-[starting-style]:
|
|
40
|
-
@source inline("
|
|
34
|
+
@source inline("[&>svg]:size-3.5 [&_svg]:size-3 [overflow-wrap:anywhere] active:cursor-grabbing basis-full");
|
|
35
|
+
@source inline("bg-[var(--cb-frozen-bg)] bg-card/95 bg-gradient-to-t bg-muted/30 bg-muted/50 border-accent-solid");
|
|
36
|
+
@source inline("border-accent/60 border-field-error border-solid border-t-2 col collapse cursor-grab");
|
|
37
|
+
@source inline("data-[ending-style]:opacity-0 data-[ending-style]:scale-95 data-[highlighted]:bg-accent/10");
|
|
38
|
+
@source inline("data-[highlighted]:text-accent-strong data-[starting-style]:opacity-0");
|
|
39
|
+
@source inline("data-[starting-style]:scale-95 disabled:opacity-40 duration-100 fade-in");
|
|
40
|
+
@source inline("focus-visible:ring-field-ring/40 focus-within:border-field-ring focus-within:ring-2");
|
|
41
41
|
@source inline("focus-within:ring-field-ring/30 from-card gap-x-6 h-105");
|
|
42
42
|
@source inline("has-[[aria-invalid=true]]:border-field-error hover:bg-background hover:bg-muted/40");
|
|
43
43
|
@source inline("hover:border-field-ring hover:text-warning justify-self-end last:border-0 left-11 left-2.5");
|
|
44
44
|
@source inline("list-none max-h-64 max-h-88 max-sm:flex-1 max-sm:flex-wrap max-sm:min-w-0 max-sm:min-w-24");
|
|
45
45
|
@source inline("max-sm:min-w-32 max-sm:ml-13.5 max-sm:w-auto max-w-32 max-w-36 max-w-45 max-w-48 max-w-60");
|
|
46
46
|
@source inline("min-h-20 min-h-dvh min-w-0! min-w-24 min-w-52 min-w-[var(--anchor-width)] min-w-max ml-1.5 mr-2");
|
|
47
|
-
@source inline("mt-1.5 mt-3 mt-4 not-italic ordinal p-0! pb-1.5 pl-1.5 pl-2 pr-0.5 pr-1 pr-10 pr-16 pr-3
|
|
48
|
-
@source inline("
|
|
47
|
+
@source inline("mt-1.5 mt-3 mt-4 not-italic ordinal p-0! pb-1.5 pl-1.5 pl-2 pr-0.5 pr-1 pr-10 pr-16 pr-3 pt-1");
|
|
48
|
+
@source inline("resize-none resize-y right-3 ring-field-error/30 self self-stretch");
|
|
49
49
|
@source inline("shadow-[1px_0_0_0_var(--color-border)] size-7 slide-in-from-top-1 table-fixed text-[length:0]");
|
|
50
50
|
@source inline("text-accent-solid-foreground text-field-placeholder text-success text-xl to-transparent top-3");
|
|
51
51
|
@source inline("transition-[transform,opacity] w-0! w-11 w-12 w-20 w-32 w-36 w-40 w-88");
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* after an app's responsive variant (task 2124). An app that imports every area gets exactly
|
|
19
19
|
* what `cursedbelt/styles-utilities.css` emits — proved rule by rule in
|
|
20
20
|
* src/stylesAreas.spec.ts, which is also what fails when this file is stale.
|
|
21
|
-
*
|
|
21
|
+
* 78 candidate(s).
|
|
22
22
|
*
|
|
23
23
|
* Written by scripts/generateAreaStyles.ts (`bun run styles:areas`), which
|
|
24
24
|
* `bun run build` runs. The map of what is in which area, and why the areas are
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
@source inline("focus-within:opacity-100 from-black/80 g grayscale group-focus-within:opacity-100 h-11");
|
|
33
33
|
@source inline("hover:bg-black/75 hover:bg-destructive hover:bg-white/20 hover:border-accent-solid invert left-1");
|
|
34
34
|
@source inline("max-h-[40vh] max-h-[var(--media-max-h)] max-h-full max-w-50 min-h-64 min-h-dvh min-w-10 min-w-28");
|
|
35
|
-
@source inline("min-w-40 min-w-48 o op opacity-100 opacity-80
|
|
35
|
+
@source inline("min-w-40 min-w-48 o op opacity-100 opacity-80 pt-8 right-1 right-1.5 sh");
|
|
36
36
|
@source inline("shadow-[0_0_0_1px_rgba(0,0,0,0.55)] size-7 size-9 sm:gap-3 sm:grid-cols-3");
|
|
37
37
|
@source inline("text-[color:var(--accent)] text-xl to-transparent top-1 top-1.5 translate translate-x-px w-12");
|
|
38
38
|
@source inline("w-3 zoom zoom-in");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cursedbelt",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.78.0",
|
|
4
4
|
"license": "ISC",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "The React design system of the cursedbelt split — components, styles, theme. cursedbelt-core below it; server tier in cursedbelt-server.",
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"demo:check": "tsc --noEmit --project demo/tsconfig.json",
|
|
76
76
|
"demo:paths": "bun run demo/generatePaths.ts --write",
|
|
77
77
|
"demo:coverage": "bun run scripts/demoCoverage.ts",
|
|
78
|
-
"guardrails:report": "bun run scripts/guardrailsEnforce.ts no-raw-action-button,raw-button-resets-face,no-raw-overflow,no-raw-inputs,no-hardcoded-colors,no-raw-modal-footer,no-unguarded-editing-modal,control-height-tokens,no-raw-zindex,no-raw-data-table,no-hand-rolled-property-list,no-tailwind-palette,no-raw-title-attr,micro-typography-floor,choice-needs-empty-state,no-select-as-action-menu,no-native-dialog,no-fast-tooltip,no-raw-password,no-raw-lazy,no-raw-popover,no-hand-rolled-scroll-memory,no-hand-rolled-surface,no-inline-style,no-arbitrary-length,opt-out-count,opt-out-needs-cause --scope cursedbelt src/react src/styles.css",
|
|
78
|
+
"guardrails:report": "bun run scripts/guardrailsEnforce.ts no-raw-action-button,raw-button-resets-face,no-raw-overflow,no-raw-inputs,no-hardcoded-colors,no-raw-modal-footer,no-unguarded-editing-modal,control-height-tokens,no-raw-zindex,no-raw-data-table,no-hand-rolled-property-list,no-tailwind-palette,no-raw-title-attr,micro-typography-floor,choice-needs-empty-state,no-select-as-action-menu,no-native-select,no-native-dialog,no-fast-tooltip,no-raw-password,no-raw-lazy,no-raw-popover,no-hand-rolled-scroll-memory,no-hand-rolled-surface,no-inline-style,no-arbitrary-length,opt-out-count,opt-out-needs-cause --scope cursedbelt src/react src/styles.css",
|
|
79
79
|
"lint": "biome lint --error-on-warnings src e2e demo scripts",
|
|
80
80
|
"styles": "bun run styles:static && bun run styles:utilities && bun run styles:areas",
|
|
81
81
|
"styles:static": "bun run scripts/generateStaticStyles.ts",
|
|
@@ -922,7 +922,7 @@
|
|
|
922
922
|
"@vidstack/react": "1.15.6",
|
|
923
923
|
"@vitejs/plugin-react": "^5.2.0",
|
|
924
924
|
"cursedbelt-server": "^4.43.1",
|
|
925
|
-
"cursedops": "^0.
|
|
925
|
+
"cursedops": "^0.16.1",
|
|
926
926
|
"diff": "^9.0.0",
|
|
927
927
|
"emoji-mart": "^5.6.0",
|
|
928
928
|
"hls.js": "^1.5.0",
|
|
@@ -1064,6 +1064,16 @@ describe('no-native-select — the OS draws its list (owner, 2026-10-10)', () =>
|
|
|
1064
1064
|
<NativeSelect value={relation} onChange={pick} />`;
|
|
1065
1065
|
expect(check('no-native-select', source)).toHaveLength(0);
|
|
1066
1066
|
});
|
|
1067
|
+
|
|
1068
|
+
// forge task 3063: the rule used to skip all of cursedbelt's components, so the kit's own
|
|
1069
|
+
// DataTable filter builder drew OS menus into apps whose count read 0.
|
|
1070
|
+
test('holds the kit itself to it — only NativeSelect.tsx may render one', () => {
|
|
1071
|
+
const source = `<NativeSelect size='sm' aria-label='Column' />`;
|
|
1072
|
+
const rule = RULES['no-native-select'];
|
|
1073
|
+
expect(rule.check('libs/cursedbelt/src/react/components/DataTable/TableFilterBuilder.tsx', source)).toHaveLength(1);
|
|
1074
|
+
expect(rule.check('libs/cursedbelt/src/react/media/FocusViewer.tsx', source)).toHaveLength(1);
|
|
1075
|
+
expect(rule.check('libs/cursedbelt/src/react/components/NativeSelect.tsx', source)).toHaveLength(0);
|
|
1076
|
+
});
|
|
1067
1077
|
});
|
|
1068
1078
|
|
|
1069
1079
|
describe('typed-field-outside-a-form — Enter submits (owner, 2026-10-11; forge task 3043)', () => {
|
|
@@ -2054,7 +2054,10 @@ const noNativeSelect: Rule = {
|
|
|
2054
2054
|
"Owner, 2026-10-10: unstyled dropdowns break the app's cohesiveness. A `NativeSelect`'s list is drawn by the OS, outside the page, where no stylesheet reaches it. Use `SelectField` / `ComboboxField` for a form field, `PropertyPicker` for a record's property (Type, Folder, Tags), or `ChoiceMenu` to choose-and-act. Where the OS picker is the better control, say why on the line above: `// guardrails-ignore no-native-select: <why>`.",
|
|
2055
2055
|
check(filePath, source) {
|
|
2056
2056
|
const unix = filePath.replace(/\\/g, "/");
|
|
2057
|
-
|
|
2057
|
+
// The kit is held to its own law (forge task 3063: its DataTable filter builder and
|
|
2058
|
+
// FocusViewer drew OS menus into every app whose count read 0). Only the control itself
|
|
2059
|
+
// may render one.
|
|
2060
|
+
if (/(^|\/)(?:packages|libs)\/cursedbelt\/src\/react\/components\/NativeSelect\.tsx$/.test(unix)) return [];
|
|
2058
2061
|
if (/\.(?:spec|test)\.[tj]sx?$/.test(unix)) return [];
|
|
2059
2062
|
const scannable = blankComments(source);
|
|
2060
2063
|
const lines = source.split("\n");
|
package/scripts/styleAreas.ts
CHANGED
|
@@ -299,7 +299,9 @@ export const AREAS: readonly Area[] = [
|
|
|
299
299
|
{
|
|
300
300
|
name: 'media',
|
|
301
301
|
why: 'the video/audio player, the upload client and the player strip',
|
|
302
|
-
|
|
302
|
+
// `media/gifRecord.ts` is the focus viewer's `import()`ed GIF recorder (it draws nothing;
|
|
303
|
+
// it is named so the closure walk, which reads static specifiers only, finds it at all).
|
|
304
|
+
roots: ['media', 'media/FrameGrabber.tsx', 'media/poster.ts', 'media/gifRecord.ts'],
|
|
303
305
|
},
|
|
304
306
|
{
|
|
305
307
|
name: 'media-gallery',
|
|
@@ -6,7 +6,7 @@ export interface CanvasToolbarProps {
|
|
|
6
6
|
/** The left half: what the canvas shows (a reach, a layer switch). Hidden while the bar is
|
|
7
7
|
* under 768px wide when `compactFilters` is given — five stops beside the tools would scroll out of a thumb's sight. */
|
|
8
8
|
filters?: ReactNode;
|
|
9
|
-
/** The same choice as a phone wants it, usually
|
|
9
|
+
/** The same choice as a phone wants it, usually one `SelectField`: shown only while
|
|
10
10
|
* the bar is under 768px wide, on row two beside the legend. */
|
|
11
11
|
compactFilters?: ReactNode;
|
|
12
12
|
/** A key or chip strip (houses, layers, tags): ONE line that scrolls inside itself, its ends
|
|
@@ -3,8 +3,8 @@ import { useState } from 'react';
|
|
|
3
3
|
import { cn } from '../../lib/cn.js';
|
|
4
4
|
import { Button } from '../Button.js';
|
|
5
5
|
import { Input } from '../Input.js';
|
|
6
|
-
import { NativeSelect } from '../NativeSelect.js';
|
|
7
6
|
import { Popover } from '../Popover.js';
|
|
7
|
+
import { SelectField } from '../SelectField.js';
|
|
8
8
|
import {
|
|
9
9
|
countActiveConditions,
|
|
10
10
|
describeFilterQuery,
|
|
@@ -181,31 +181,27 @@ export function TableFilterBuilder({
|
|
|
181
181
|
<span className='w-12 shrink-0 text-right text-caption font-semibold uppercase tracking-wide text-muted-foreground'>
|
|
182
182
|
{conditionIndex === 0 ? 'Where' : 'and'}
|
|
183
183
|
</span>
|
|
184
|
-
<
|
|
184
|
+
<SelectField
|
|
185
185
|
size='sm'
|
|
186
186
|
aria-label='Column'
|
|
187
|
-
className='min-w-0 flex-1'
|
|
188
|
-
wrapperClassName='min-w-0 flex-1 max-sm:min-w-24'
|
|
187
|
+
className='min-w-0 flex-1 max-sm:min-w-24'
|
|
189
188
|
value={condition.column}
|
|
190
189
|
options={columns.map((c) => ({ value: c.key, label: c.label }))}
|
|
191
|
-
|
|
190
|
+
onValueChange={(column) =>
|
|
192
191
|
patchGroup(group.id, (list) =>
|
|
193
|
-
list.map((c) => (c.id === condition.id ? { ...c, column
|
|
192
|
+
list.map((c) => (c.id === condition.id ? { ...c, column } : c)),
|
|
194
193
|
)
|
|
195
194
|
}
|
|
196
195
|
/>
|
|
197
|
-
<
|
|
196
|
+
<SelectField
|
|
198
197
|
size='sm'
|
|
199
198
|
aria-label='Condition'
|
|
200
|
-
className='min-w-0'
|
|
201
|
-
wrapperClassName='w-40 shrink-0 max-sm:w-auto max-sm:min-w-32 max-sm:flex-1'
|
|
199
|
+
className='w-40 min-w-0 shrink-0 max-sm:w-auto max-sm:min-w-32 max-sm:flex-1'
|
|
202
200
|
value={condition.op}
|
|
203
201
|
options={OP_OPTIONS}
|
|
204
|
-
|
|
202
|
+
onValueChange={(op) =>
|
|
205
203
|
patchGroup(group.id, (list) =>
|
|
206
|
-
list.map((c) =>
|
|
207
|
-
c.id === condition.id ? { ...c, op: e.target.value as FilterOp } : c,
|
|
208
|
-
),
|
|
204
|
+
list.map((c) => (c.id === condition.id ? { ...c, op: op as FilterOp } : c)),
|
|
209
205
|
)
|
|
210
206
|
}
|
|
211
207
|
/>
|
|
@@ -238,6 +238,14 @@ describe('search AND the filter builder AND the sort stack compose', () => {
|
|
|
238
238
|
});
|
|
239
239
|
|
|
240
240
|
describe('the filter builder, driven the way a person drives it', () => {
|
|
241
|
+
/** Picks `label` in one of the builder's `SelectField`s as a keyboard does: open the trigger,
|
|
242
|
+
* then take the option. Its Column and Condition were OS menus until forge task 3063. */
|
|
243
|
+
const choose = (trigger: HTMLElement, label: string) => {
|
|
244
|
+
expect(trigger.getAttribute('role')).toBe('combobox');
|
|
245
|
+
fireEvent.keyDown(trigger, { key: 'Enter' });
|
|
246
|
+
fireEvent.click(screen.getByRole('option', { name: label }));
|
|
247
|
+
};
|
|
248
|
+
|
|
241
249
|
/**
|
|
242
250
|
* 🔴 The acceptance test for item 3 is the owner's own last clause — *"easy
|
|
243
251
|
* enough that I don't give up on trying it"* — so this walks the WHOLE
|
|
@@ -255,22 +263,22 @@ describe('the filter builder, driven the way a person drives it', () => {
|
|
|
255
263
|
// A condition with no value yet must NOT empty the table.
|
|
256
264
|
expect(bodyTitles()).toHaveLength(4);
|
|
257
265
|
|
|
258
|
-
const columnSelects = () => screen.getAllByLabelText('Column')
|
|
259
|
-
|
|
260
|
-
|
|
266
|
+
const columnSelects = () => screen.getAllByLabelText('Column');
|
|
267
|
+
choose(columnSelects()[0], 'Artist');
|
|
268
|
+
choose(screen.getAllByLabelText('Condition')[0], 'is');
|
|
261
269
|
fireEvent.change(screen.getAllByLabelText('Value')[0], { target: { value: 'alpha' } });
|
|
262
270
|
expect(bodyTitles()).toEqual(['bravo', 'delta']);
|
|
263
271
|
|
|
264
272
|
// AND: a second condition inside the same group.
|
|
265
273
|
fireEvent.click(screen.getByRole('button', { name: /^And$/ }));
|
|
266
|
-
|
|
274
|
+
choose(columnSelects()[1], 'Title');
|
|
267
275
|
fireEvent.change(screen.getAllByLabelText('Value')[1], { target: { value: 'bra' } });
|
|
268
276
|
expect(bodyTitles()).toEqual(['bravo']);
|
|
269
277
|
|
|
270
278
|
// OR: a second GROUP.
|
|
271
279
|
fireEvent.click(screen.getByRole('button', { name: /Or…/ }));
|
|
272
|
-
|
|
273
|
-
|
|
280
|
+
choose(columnSelects()[2], 'Artist');
|
|
281
|
+
choose(screen.getAllByLabelText('Condition')[2], 'is');
|
|
274
282
|
fireEvent.change(screen.getAllByLabelText('Value')[2], { target: { value: 'beta' } });
|
|
275
283
|
expect(bodyTitles()).toEqual(['alpha', 'bravo', 'charlie']);
|
|
276
284
|
|
|
@@ -297,7 +305,7 @@ describe('the filter builder, driven the way a person drives it', () => {
|
|
|
297
305
|
render(<DataTable columns={COLUMNS} data={TRACKS} getRowId={(r) => r.id} hidePagination />);
|
|
298
306
|
fireEvent.click(screen.getByRole('button', { name: /^Filter/ }));
|
|
299
307
|
fireEvent.click(screen.getByRole('button', { name: /Add a condition/ }));
|
|
300
|
-
|
|
308
|
+
choose(screen.getAllByLabelText('Condition')[0], 'is empty');
|
|
301
309
|
expect(screen.queryAllByLabelText('Value')).toHaveLength(0);
|
|
302
310
|
// Every fixture row has a title, so "title is empty" keeps nothing.
|
|
303
311
|
expect(bodyTitles()).toHaveLength(0);
|
|
@@ -34,7 +34,7 @@ export interface ListToolbarProps {
|
|
|
34
34
|
chipsAllLabel?: string;
|
|
35
35
|
/** Any further FILTER controls — they sit left, after the chips. */
|
|
36
36
|
filters?: ReactNode;
|
|
37
|
-
/** The sort control (a `
|
|
37
|
+
/** The sort control (a `SelectField`, a `ChoiceMenu`, …). Right-hand cluster. */
|
|
38
38
|
sort?: ReactNode;
|
|
39
39
|
/** The density/view-mode control — normally a `TableViewToggle`. Right-hand cluster. */
|
|
40
40
|
view?: ReactNode;
|
|
@@ -95,7 +95,7 @@ export interface ListToolbarProps {
|
|
|
95
95
|
* chips={buckets}
|
|
96
96
|
* chipValue={filter}
|
|
97
97
|
* onChipChange={(v) => setFilter(typeof v === 'string' ? v : null)}
|
|
98
|
-
* sort={<
|
|
98
|
+
* sort={<SelectField size='sm' aria-label='Sort the queue' … />}
|
|
99
99
|
* view={<TableViewToggle value={view.mode} onChange={view.setMode} />}
|
|
100
100
|
* />
|
|
101
101
|
*/
|
|
@@ -110,6 +110,26 @@ describe('useOutsidePress', () => {
|
|
|
110
110
|
expect(isOpen()).toBe(true);
|
|
111
111
|
});
|
|
112
112
|
|
|
113
|
+
/**
|
|
114
|
+
* 🔴 Forge task 3064: a Radix Select opens on the POINTERDOWN and makes the page inert, so the
|
|
115
|
+
* same press's pointerup — and the click it makes — land on `<html>`. Read as a press outside,
|
|
116
|
+
* that click shut the filter builder's popover the moment its Column picker opened (music's
|
|
117
|
+
* browser test, cursedbelt 5.77.1). A click is the end of the press that began it: one that
|
|
118
|
+
* began inside is inside, wherever the release fell.
|
|
119
|
+
*/
|
|
120
|
+
test('🔴 a press that begins inside and is released outside keeps it open', async () => {
|
|
121
|
+
render(<Panel />);
|
|
122
|
+
await press(screen.getByRole('button', { name: 'folders' }));
|
|
123
|
+
fireEvent.pointerDown(screen.getByRole('button', { name: 'open nested' }));
|
|
124
|
+
fireEvent.click(document.documentElement);
|
|
125
|
+
await act(() => new Promise((r) => setTimeout(r, 5)));
|
|
126
|
+
expect(isOpen()).toBe(true);
|
|
127
|
+
// The next press, begun outside, still closes it.
|
|
128
|
+
fireEvent.pointerDown(screen.getByRole('button', { name: 'row' }));
|
|
129
|
+
await press(screen.getByRole('button', { name: 'row' }));
|
|
130
|
+
expect(isOpen()).toBe(false);
|
|
131
|
+
});
|
|
132
|
+
|
|
113
133
|
test('Escape closes it', async () => {
|
|
114
134
|
render(<Panel />);
|
|
115
135
|
await press(screen.getByRole('button', { name: 'folders' }));
|
|
@@ -125,8 +125,23 @@ export function useOutsidePress({
|
|
|
125
125
|
if (outside(target)) swallowUntil = Date.now() + 1000;
|
|
126
126
|
decideLater(target);
|
|
127
127
|
};
|
|
128
|
+
// `click` mode: where the press BEGAN. A Radix Select opens on the pointerdown and makes the
|
|
129
|
+
// page inert, so that same press's release — and the click it makes — land on `<html>`; read
|
|
130
|
+
// as outside, it shut the filter builder the moment its Column picker opened (forge task
|
|
131
|
+
// 3064). A click whose press began inside (in the DOM, or this panel's React tree — the
|
|
132
|
+
// panel's `onPointerDownCapture` runs after this capture listener) is inside.
|
|
133
|
+
let pressBegan: Node | null = null;
|
|
134
|
+
const onPressBegins = (event: PointerEvent) => {
|
|
135
|
+
pressInsideRef.current = false;
|
|
136
|
+
pressBegan = event.target instanceof Node ? event.target : null;
|
|
137
|
+
};
|
|
128
138
|
const onClick = (event: MouseEvent) => {
|
|
129
139
|
const target = event.target instanceof Node ? event.target : null;
|
|
140
|
+
if (decideOn === 'click') {
|
|
141
|
+
const began = pressBegan;
|
|
142
|
+
pressBegan = null;
|
|
143
|
+
if (began && (pressInsideRef.current || isInside(began) || onAnotherLayer(began))) return;
|
|
144
|
+
}
|
|
130
145
|
if (decideOn === 'pointerdown') {
|
|
131
146
|
if (Date.now() < swallowUntil) {
|
|
132
147
|
swallowUntil = 0;
|
|
@@ -149,11 +164,11 @@ export function useOutsidePress({
|
|
|
149
164
|
};
|
|
150
165
|
|
|
151
166
|
document.addEventListener('click', onClick, true);
|
|
152
|
-
|
|
167
|
+
document.addEventListener('pointerdown', decideOn === 'pointerdown' ? onPointerDown : onPressBegins, true);
|
|
153
168
|
if (closeOnEscape) document.addEventListener('keydown', onKeyDown);
|
|
154
169
|
return () => {
|
|
155
170
|
document.removeEventListener('click', onClick, true);
|
|
156
|
-
document.removeEventListener('pointerdown', onPointerDown, true);
|
|
171
|
+
document.removeEventListener('pointerdown', decideOn === 'pointerdown' ? onPointerDown : onPressBegins, true);
|
|
157
172
|
document.removeEventListener('keydown', onKeyDown);
|
|
158
173
|
for (const timer of timers) clearTimeout(timer);
|
|
159
174
|
};
|
|
@@ -41,8 +41,8 @@ function fakeScroller(height: number, viewport = 800) {
|
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
function Shell({ at, el, enabled }: { at: string; el: HTMLElement; enabled?: boolean }) {
|
|
45
|
-
useScrollMemory(at, { scroller: () => el, enabled });
|
|
44
|
+
function Shell({ at, el, enabled, deadlineMs }: { at: string; el: HTMLElement; enabled?: boolean; deadlineMs?: number }) {
|
|
45
|
+
useScrollMemory(at, { scroller: () => el, enabled, deadlineMs });
|
|
46
46
|
return null;
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -103,6 +103,49 @@ describe('useScrollMemory', () => {
|
|
|
103
103
|
expect(s.el.scrollTop).toBe(15_000);
|
|
104
104
|
});
|
|
105
105
|
|
|
106
|
+
/**
|
|
107
|
+
* 🔴 forge task 3069: flix's phone grid, reloaded, came back to 29,731px instead of 30,000 — its
|
|
108
|
+
* pages were still arriving one round trip at a time when the 3s deadline, counted from the
|
|
109
|
+
* START, ran out. A page that is still growing is still being followed.
|
|
110
|
+
*/
|
|
111
|
+
test('a list growing page by page is followed past the deadline, as long as each page lands within it', async () => {
|
|
112
|
+
const s = fakeScroller(20_000);
|
|
113
|
+
const { rerender } = render(<Shell at='/' el={s.el} deadlineMs={60} />);
|
|
114
|
+
s.scrollTo(15_000);
|
|
115
|
+
const list = { state: window.history.state, path: '/' };
|
|
116
|
+
push('/item/7');
|
|
117
|
+
rerender(<Shell at='/item/7' el={s.el} deadlineMs={60} />);
|
|
118
|
+
|
|
119
|
+
s.grow(3_000);
|
|
120
|
+
back(list);
|
|
121
|
+
rerender(<Shell at='/' el={s.el} deadlineMs={60} />);
|
|
122
|
+
// Five pages, 40ms apart: 200ms in all — well past 60ms from the start, never 60ms without a page.
|
|
123
|
+
for (const height of [6_000, 9_000, 12_000, 15_000, 20_000]) {
|
|
124
|
+
await act(() => Bun.sleep(40));
|
|
125
|
+
s.grow(height);
|
|
126
|
+
}
|
|
127
|
+
await frames(2);
|
|
128
|
+
expect(s.el.scrollTop).toBe(15_000);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('a list that STOPS growing is given up on once the deadline passes with no new page', async () => {
|
|
132
|
+
const s = fakeScroller(20_000);
|
|
133
|
+
const { rerender } = render(<Shell at='/' el={s.el} deadlineMs={60} />);
|
|
134
|
+
s.scrollTo(15_000);
|
|
135
|
+
const list = { state: window.history.state, path: '/' };
|
|
136
|
+
push('/item/7');
|
|
137
|
+
rerender(<Shell at='/item/7' el={s.el} deadlineMs={60} />);
|
|
138
|
+
|
|
139
|
+
s.grow(3_000);
|
|
140
|
+
back(list);
|
|
141
|
+
rerender(<Shell at='/' el={s.el} deadlineMs={60} />);
|
|
142
|
+
await act(() => Bun.sleep(150));
|
|
143
|
+
await frames(2);
|
|
144
|
+
s.grow(20_000); // too late: the restore settled where the page ended
|
|
145
|
+
await frames(3);
|
|
146
|
+
expect(s.el.scrollTop).toBe(2_200);
|
|
147
|
+
});
|
|
148
|
+
|
|
106
149
|
test('the viewer taking the page back cancels a restore that is still waiting', async () => {
|
|
107
150
|
const s = fakeScroller(20_000);
|
|
108
151
|
const { rerender } = render(<Shell at='/' el={s.el} />);
|