cursedbelt 5.76.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/BuildFreshness.d.ts +2 -1
- package/dist/react/components/BuildFreshness.d.ts.map +1 -1
- package/dist/react/components/BuildFreshness.js +11 -4
- package/dist/react/components/BuildFreshness.js.map +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/BuildFreshness.spec.tsx +10 -0
- package/src/react/components/BuildFreshness.tsx +13 -4
- 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,288 @@
|
|
|
1
|
+
// ── A looping GIF, encoded in the browser ────────────────────────────────────────────
|
|
2
|
+
// Owner, 2026-10-11: *"In collections and the cb media player in general allow making a
|
|
3
|
+
// looping gif out of a section of a video."* This is the encoder that ask needs, and only
|
|
4
|
+
// that: GIF89a, a local 256-colour palette per frame, the NETSCAPE2.0 loop block, LZW.
|
|
5
|
+
//
|
|
6
|
+
// 🔴 Written here rather than taken as a dependency. The media entry's heavy libraries are
|
|
7
|
+
// OPTIONAL peers an app has to install and dedupe (see `index.ts`), and the encoder this
|
|
8
|
+
// feature needs is ~200 lines of a frozen 1989 format — smaller than the declaration file,
|
|
9
|
+
// lockfile churn and peer table one more package would cost every app that shows a video.
|
|
10
|
+
// Pure functions over typed arrays, so `gifEncode.spec.ts` decodes what it writes and
|
|
11
|
+
// checks the pixels came back.
|
|
12
|
+
//
|
|
13
|
+
// Per-frame palettes rather than one global one: a clip is captured a frame at a time and
|
|
14
|
+
// a global palette would need every frame in memory first — 300 frames of 640×360 RGBA is
|
|
15
|
+
// 276 MB. Each frame is quantized, written and dropped.
|
|
16
|
+
const BINS = 1 << 15; // 5 bits a channel
|
|
17
|
+
const binOf = (r, g, b) => ((r >> 3) << 10) | ((g >> 3) << 5) | (b >> 3);
|
|
18
|
+
/**
|
|
19
|
+
* Median-cut an RGBA frame to at most `maxColors` colours.
|
|
20
|
+
*
|
|
21
|
+
* Pixels are first counted into 32,768 bins (5 bits a channel) holding their true 8-bit
|
|
22
|
+
* sums, so a palette entry is the real average of what it stands for rather than a bin's
|
|
23
|
+
* corner. The box split is the one with the most pixels × widest channel, at its weighted
|
|
24
|
+
* median — the textbook cut, which keeps a big flat sky from eating every entry. A frame with
|
|
25
|
+
* no more than `maxColors` distinct binned colours keeps them all.
|
|
26
|
+
*/
|
|
27
|
+
export function quantizeFrame(rgba, maxColors = 256) {
|
|
28
|
+
const pixels = rgba.length >> 2;
|
|
29
|
+
const count = new Uint32Array(BINS);
|
|
30
|
+
const sumR = new Float64Array(BINS);
|
|
31
|
+
const sumG = new Float64Array(BINS);
|
|
32
|
+
const sumB = new Float64Array(BINS);
|
|
33
|
+
for (let p = 0, i = 0; p < pixels; p++, i += 4) {
|
|
34
|
+
const r = rgba[i];
|
|
35
|
+
const g = rgba[i + 1];
|
|
36
|
+
const b = rgba[i + 2];
|
|
37
|
+
const bin = binOf(r, g, b);
|
|
38
|
+
count[bin] = count[bin] + 1;
|
|
39
|
+
sumR[bin] = sumR[bin] + r;
|
|
40
|
+
sumG[bin] = sumG[bin] + g;
|
|
41
|
+
sumB[bin] = sumB[bin] + b;
|
|
42
|
+
}
|
|
43
|
+
const used = [];
|
|
44
|
+
for (let bin = 0; bin < BINS; bin++)
|
|
45
|
+
if (count[bin])
|
|
46
|
+
used.push(bin);
|
|
47
|
+
const channel = (bin, c) => (bin >> (10 - 5 * c)) & 31;
|
|
48
|
+
// A box's widest channel is measured once, when it is made — the split loop asks for it
|
|
49
|
+
// of every box on every pass, and re-scanning 32k bins 256 times a frame is the cost here.
|
|
50
|
+
const boxOf = (bins) => {
|
|
51
|
+
let pixels = 0;
|
|
52
|
+
const lo = [31, 31, 31];
|
|
53
|
+
const hi = [0, 0, 0];
|
|
54
|
+
for (const bin of bins) {
|
|
55
|
+
pixels += count[bin];
|
|
56
|
+
for (const c of [0, 1, 2]) {
|
|
57
|
+
const v = channel(bin, c);
|
|
58
|
+
if (v < lo[c])
|
|
59
|
+
lo[c] = v;
|
|
60
|
+
if (v > hi[c])
|
|
61
|
+
hi[c] = v;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const ranges = [0, 1, 2].map((c) => hi[c] - lo[c]);
|
|
65
|
+
const c = ranges.indexOf(Math.max(...ranges));
|
|
66
|
+
return { bins, pixels, c, range: ranges[c] };
|
|
67
|
+
};
|
|
68
|
+
const boxes = [boxOf(used)];
|
|
69
|
+
while (boxes.length < maxColors) {
|
|
70
|
+
let pick = -1;
|
|
71
|
+
let score = 0;
|
|
72
|
+
for (let i = 0; i < boxes.length; i++) {
|
|
73
|
+
const box = boxes[i];
|
|
74
|
+
if (box.bins.length < 2)
|
|
75
|
+
continue;
|
|
76
|
+
const s = box.pixels * (box.range + 1);
|
|
77
|
+
if (s > score) {
|
|
78
|
+
score = s;
|
|
79
|
+
pick = i;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (pick < 0)
|
|
83
|
+
break; // every box is one colour
|
|
84
|
+
const box = boxes[pick];
|
|
85
|
+
const { c } = box;
|
|
86
|
+
box.bins.sort((a, b) => channel(a, c) - channel(b, c));
|
|
87
|
+
let seen = 0;
|
|
88
|
+
let cut = 1;
|
|
89
|
+
for (let i = 0; i < box.bins.length - 1; i++) {
|
|
90
|
+
seen += count[box.bins[i]];
|
|
91
|
+
cut = i + 1;
|
|
92
|
+
if (seen * 2 >= box.pixels)
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
boxes.splice(pick, 1, boxOf(box.bins.slice(0, cut)), boxOf(box.bins.slice(cut)));
|
|
96
|
+
}
|
|
97
|
+
const palette = new Uint8Array(Math.max(1, boxes.length) * 3);
|
|
98
|
+
const lut = new Uint8Array(BINS);
|
|
99
|
+
boxes.forEach((box, entry) => {
|
|
100
|
+
let r = 0;
|
|
101
|
+
let g = 0;
|
|
102
|
+
let b = 0;
|
|
103
|
+
for (const bin of box.bins) {
|
|
104
|
+
r += sumR[bin];
|
|
105
|
+
g += sumG[bin];
|
|
106
|
+
b += sumB[bin];
|
|
107
|
+
}
|
|
108
|
+
const n = Math.max(1, box.pixels);
|
|
109
|
+
palette[entry * 3] = Math.round(r / n);
|
|
110
|
+
palette[entry * 3 + 1] = Math.round(g / n);
|
|
111
|
+
palette[entry * 3 + 2] = Math.round(b / n);
|
|
112
|
+
});
|
|
113
|
+
// Each bin maps to its NEAREST palette entry, not merely the box it fell in: a box's
|
|
114
|
+
// average can sit closer to a neighbour's members than to some of its own.
|
|
115
|
+
const entries = palette.length / 3;
|
|
116
|
+
for (const bin of used) {
|
|
117
|
+
const n = count[bin];
|
|
118
|
+
const r = sumR[bin] / n;
|
|
119
|
+
const g = sumG[bin] / n;
|
|
120
|
+
const b = sumB[bin] / n;
|
|
121
|
+
let best = 0;
|
|
122
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
123
|
+
for (let e = 0; e < entries; e++) {
|
|
124
|
+
const dr = r - palette[e * 3];
|
|
125
|
+
const dg = g - palette[e * 3 + 1];
|
|
126
|
+
const db = b - palette[e * 3 + 2];
|
|
127
|
+
const d = dr * dr + dg * dg + db * db;
|
|
128
|
+
if (d < bestDistance) {
|
|
129
|
+
bestDistance = d;
|
|
130
|
+
best = e;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
lut[bin] = best;
|
|
134
|
+
}
|
|
135
|
+
const indices = new Uint8Array(pixels);
|
|
136
|
+
for (let p = 0, i = 0; p < pixels; p++, i += 4)
|
|
137
|
+
indices[p] = lut[binOf(rgba[i], rgba[i + 1], rgba[i + 2])];
|
|
138
|
+
return { palette, indices };
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* GIF's variable-width LZW over 8-bit indices, as the bytes between the minimum code size and
|
|
142
|
+
* the block terminator (before they are cut into 255-byte sub-blocks).
|
|
143
|
+
*
|
|
144
|
+
* 🔴 The code width grows when the NEXT code to be assigned reaches `1 << width`, and only
|
|
145
|
+
* then — the decoder learns each entry one code later than the encoder makes it, and growing
|
|
146
|
+
* a code early or late desynchronises every code after it into noise.
|
|
147
|
+
*/
|
|
148
|
+
export function lzwEncode(indices, minCodeSize = 8) {
|
|
149
|
+
const clear = 1 << minCodeSize;
|
|
150
|
+
const end = clear + 1;
|
|
151
|
+
const out = [];
|
|
152
|
+
let width = minCodeSize + 1;
|
|
153
|
+
let next = end + 1;
|
|
154
|
+
let bits = 0;
|
|
155
|
+
let held = 0;
|
|
156
|
+
const emit = (code) => {
|
|
157
|
+
held |= code << bits;
|
|
158
|
+
bits += width;
|
|
159
|
+
while (bits >= 8) {
|
|
160
|
+
out.push(held & 0xff);
|
|
161
|
+
held >>>= 8;
|
|
162
|
+
bits -= 8;
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
let table = new Map();
|
|
166
|
+
emit(clear);
|
|
167
|
+
if (indices.length === 0) {
|
|
168
|
+
emit(end);
|
|
169
|
+
if (bits > 0)
|
|
170
|
+
out.push(held & 0xff);
|
|
171
|
+
return Uint8Array.from(out);
|
|
172
|
+
}
|
|
173
|
+
let prefix = indices[0];
|
|
174
|
+
for (let i = 1; i < indices.length; i++) {
|
|
175
|
+
const k = indices[i];
|
|
176
|
+
const key = (prefix << 8) | k;
|
|
177
|
+
const known = table.get(key);
|
|
178
|
+
if (known !== undefined) {
|
|
179
|
+
prefix = known;
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
emit(prefix);
|
|
183
|
+
if (next === 4096) {
|
|
184
|
+
emit(clear);
|
|
185
|
+
table = new Map();
|
|
186
|
+
width = minCodeSize + 1;
|
|
187
|
+
next = end + 1;
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
if (next >= 1 << width)
|
|
191
|
+
width++;
|
|
192
|
+
table.set(key, next++);
|
|
193
|
+
}
|
|
194
|
+
prefix = k;
|
|
195
|
+
}
|
|
196
|
+
emit(prefix);
|
|
197
|
+
emit(end);
|
|
198
|
+
if (bits > 0)
|
|
199
|
+
out.push(held & 0xff);
|
|
200
|
+
return Uint8Array.from(out);
|
|
201
|
+
}
|
|
202
|
+
/** A GIF built a frame at a time. `finish()` closes it and hands back the file. */
|
|
203
|
+
export class GifWriter {
|
|
204
|
+
width;
|
|
205
|
+
height;
|
|
206
|
+
parts = [];
|
|
207
|
+
/** Centiseconds owed to the next frame — see {@link addFrame}. */
|
|
208
|
+
carry = 0;
|
|
209
|
+
constructor(width, height,
|
|
210
|
+
/** 0 loops forever, which is what a GIF made from a clip is for. */
|
|
211
|
+
loop = 0) {
|
|
212
|
+
this.width = width;
|
|
213
|
+
this.height = height;
|
|
214
|
+
if (width < 1 || height < 1 || width > 0xffff || height > 0xffff)
|
|
215
|
+
throw new Error(`A GIF cannot be ${width}×${height}.`);
|
|
216
|
+
const head = new Uint8Array(13 + 19);
|
|
217
|
+
head.set([0x47, 0x49, 0x46, 0x38, 0x39, 0x61]); // GIF89a
|
|
218
|
+
put16(head, 6, width);
|
|
219
|
+
put16(head, 8, height);
|
|
220
|
+
// No global colour table: every frame carries its own.
|
|
221
|
+
head.set([0x00, 0x00, 0x00], 10);
|
|
222
|
+
// NETSCAPE2.0 application extension — the loop count.
|
|
223
|
+
head.set([0x21, 0xff, 0x0b, ...ascii("NETSCAPE2.0"), 0x03, 0x01], 13);
|
|
224
|
+
put16(head, 29, loop);
|
|
225
|
+
head[31] = 0x00;
|
|
226
|
+
this.parts.push(head);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* One frame of `width × height` RGBA, shown for `delayMs`.
|
|
230
|
+
*
|
|
231
|
+
* GIF counts delay in hundredths of a second, so 15 fps (6.67 cs) cannot be written
|
|
232
|
+
* exactly. The remainder is carried into the next frame rather than rounded away, so a
|
|
233
|
+
* 3-second clip plays for 3 seconds instead of drifting to 2.7 or 3.5.
|
|
234
|
+
*/
|
|
235
|
+
addFrame(rgba, delayMs) {
|
|
236
|
+
if (rgba.length !== this.width * this.height * 4)
|
|
237
|
+
throw new Error(`A ${this.width}×${this.height} frame is ${this.width * this.height * 4} bytes, not ${rgba.length}.`);
|
|
238
|
+
const exact = delayMs / 10 + this.carry;
|
|
239
|
+
const delay = Math.max(2, Math.round(exact));
|
|
240
|
+
this.carry = exact - delay;
|
|
241
|
+
const { palette, indices } = quantizeFrame(rgba);
|
|
242
|
+
const tableBits = Math.max(1, Math.ceil(Math.log2(palette.length / 3)));
|
|
243
|
+
const table = new Uint8Array(3 << tableBits);
|
|
244
|
+
table.set(palette);
|
|
245
|
+
const control = new Uint8Array(8);
|
|
246
|
+
control.set([0x21, 0xf9, 0x04, 0x04]); // graphic control, disposal 1: leave the frame in place
|
|
247
|
+
put16(control, 4, delay);
|
|
248
|
+
const descriptor = new Uint8Array(10);
|
|
249
|
+
descriptor[0] = 0x2c;
|
|
250
|
+
put16(descriptor, 5, this.width);
|
|
251
|
+
put16(descriptor, 7, this.height);
|
|
252
|
+
descriptor[9] = 0x80 | (tableBits - 1); // a local colour table of 2^tableBits entries
|
|
253
|
+
// The minimum code size follows the table (never below GIF's floor of 2), so every index
|
|
254
|
+
// is a root code — what every decoder, Safari's included, expects.
|
|
255
|
+
const minCodeSize = Math.max(2, tableBits);
|
|
256
|
+
const data = lzwEncode(indices, minCodeSize);
|
|
257
|
+
const blocks = new Uint8Array(1 + data.length + Math.ceil(data.length / 255) + 1);
|
|
258
|
+
blocks[0] = minCodeSize;
|
|
259
|
+
let at = 1;
|
|
260
|
+
for (let i = 0; i < data.length; i += 255) {
|
|
261
|
+
const piece = data.subarray(i, i + 255);
|
|
262
|
+
blocks[at++] = piece.length;
|
|
263
|
+
blocks.set(piece, at);
|
|
264
|
+
at += piece.length;
|
|
265
|
+
}
|
|
266
|
+
blocks[at] = 0x00;
|
|
267
|
+
this.parts.push(control, descriptor, table, blocks);
|
|
268
|
+
}
|
|
269
|
+
finish() {
|
|
270
|
+
this.parts.push(Uint8Array.of(0x3b));
|
|
271
|
+
const total = this.parts.reduce((n, part) => n + part.length, 0);
|
|
272
|
+
const file = new Uint8Array(total);
|
|
273
|
+
let at = 0;
|
|
274
|
+
for (const part of this.parts) {
|
|
275
|
+
file.set(part, at);
|
|
276
|
+
at += part.length;
|
|
277
|
+
}
|
|
278
|
+
return file;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function put16(bytes, at, value) {
|
|
282
|
+
bytes[at] = value & 0xff;
|
|
283
|
+
bytes[at + 1] = (value >> 8) & 0xff;
|
|
284
|
+
}
|
|
285
|
+
function ascii(text) {
|
|
286
|
+
return [...text].map((ch) => ch.charCodeAt(0));
|
|
287
|
+
}
|
|
288
|
+
//# sourceMappingURL=gifEncode.js.map
|
|
@@ -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
|