ranuts 0.4.0-alpha.5 → 0.4.1-alpha.2
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 +180 -32
- package/README.md +2 -0
- package/README.zh-CN.md +2 -0
- package/dist/{color-DPV7TsVU.js → color-BTvZUuf-.js} +1 -1
- package/dist/i18n-DW21b_DJ.js +1 -0
- package/dist/index.js +1 -1
- package/dist/src/conversation/engine.d.ts +109 -0
- package/dist/src/conversation/index.d.ts +12 -0
- package/dist/src/conversation/index.js +1 -0
- package/dist/src/conversation/types.d.ts +99 -0
- package/dist/src/node/index.js +1 -1
- package/dist/src/stream/accumulator.d.ts +51 -0
- package/dist/src/stream/budget.d.ts +89 -0
- package/dist/src/stream/index.d.ts +25 -0
- package/dist/src/stream/index.js +1 -0
- package/dist/src/stream/sse.d.ts +51 -0
- package/dist/src/stream/types.d.ts +106 -0
- package/dist/src/utils/binary.d.ts +33 -0
- package/dist/src/utils/canvas.d.ts +1 -0
- package/dist/src/utils/device.d.ts +8 -0
- package/dist/src/utils/diff.d.ts +51 -0
- package/dist/src/utils/i18n/index.js +1 -1
- package/dist/src/utils/index.d.ts +12 -7
- package/dist/src/utils/index.js +1 -1
- package/dist/src/utils/number.d.ts +20 -0
- package/dist/src/utils/placement.d.ts +42 -5
- package/dist/src/utils/scroll.d.ts +93 -0
- package/dist/src/utils/secure.d.ts +58 -0
- package/dist/src/utils/str.d.ts +75 -0
- package/dist/test/budget.test.d.ts +1 -0
- package/dist/test/conversation.test.d.ts +1 -0
- package/dist/test/diff.test.d.ts +1 -0
- package/dist/test/formatBytes.test.d.ts +1 -0
- package/dist/test/scroll.test.d.ts +1 -0
- package/dist/test/stream.test.d.ts +1 -0
- package/dist/test/utils/secure.test.d.ts +1 -0
- package/dist/test/utils/text-boundary.test.d.ts +1 -0
- package/dist/umd/index.umd.cjs +1 -1
- package/dist/umd/utils/utils.umd.cjs +1 -1
- package/dist/utils-B4FnyUzW.js +1 -0
- package/docs/API.md +94 -6
- package/package.json +11 -2
- package/dist/i18n-Bq-f7htr.js +0 -1
- package/dist/utils-BSvtPyGl.js +0 -1
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-neutral vocabulary for one streamed model response.
|
|
3
|
+
*
|
|
4
|
+
* Every mainstream chat completion API streams the same four things — assistant text,
|
|
5
|
+
* separately-billed reasoning text, tool calls, and a token count — but each names them
|
|
6
|
+
* differently and interleaves them differently. Normalizing to one union at the edge means
|
|
7
|
+
* a renderer, an accumulator, and a test fixture never learn a vendor's wire format.
|
|
8
|
+
*
|
|
9
|
+
* The union is the contract; the shape below is what makes it usable:
|
|
10
|
+
*
|
|
11
|
+
* - **`index` correlates interleaved deltas.** Reasoning and text can arrive interleaved,
|
|
12
|
+
* and a response can open several tool calls at once. Consumers group by `index` rather
|
|
13
|
+
* than assuming a delta belongs to whatever arrived last.
|
|
14
|
+
* - **`block-end` carries the assembled block.** A consumer that accumulated the deltas
|
|
15
|
+
* itself can adopt or verify it; a consumer that only wants finished blocks can ignore
|
|
16
|
+
* every delta.
|
|
17
|
+
* - **Tool arguments stay raw JSON text.** `argumentsDelta` is never parsed mid-stream:
|
|
18
|
+
* half a JSON document is not a value, and pretending otherwise is where streaming tool
|
|
19
|
+
* calls usually break.
|
|
20
|
+
* - **`finish` terminates.** `usage` arrives before it, and nothing follows it. A consumer
|
|
21
|
+
* can treat `finish` as the point where its state is complete.
|
|
22
|
+
*
|
|
23
|
+
* @module ranuts/stream
|
|
24
|
+
*/
|
|
25
|
+
/** Discriminator for the content a block accumulates. */
|
|
26
|
+
export type ContentBlockType = 'text' | 'reasoning' | 'tool-call';
|
|
27
|
+
/** Assistant prose addressed to the user. */
|
|
28
|
+
export interface TextBlock {
|
|
29
|
+
type: 'text';
|
|
30
|
+
text: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Model reasoning, when the provider exposes it. Kept separate from {@link TextBlock}
|
|
34
|
+
* because it is billed, displayed, and retained differently — a UI usually collapses it,
|
|
35
|
+
* and it is normally dropped from the history sent back on the next request.
|
|
36
|
+
*/
|
|
37
|
+
export interface ReasoningBlock {
|
|
38
|
+
type: 'reasoning';
|
|
39
|
+
text: string;
|
|
40
|
+
}
|
|
41
|
+
/** One tool invocation requested by the model. */
|
|
42
|
+
export interface ToolCallBlock {
|
|
43
|
+
type: 'tool-call';
|
|
44
|
+
/** Provider-assigned call id, echoed back with the result. */
|
|
45
|
+
id: string;
|
|
46
|
+
/** Tool name; empty until the provider has sent it. */
|
|
47
|
+
name: string;
|
|
48
|
+
/** Raw JSON text of the arguments — deliberately unparsed. */
|
|
49
|
+
arguments: string;
|
|
50
|
+
}
|
|
51
|
+
/** One completed unit of assistant output. */
|
|
52
|
+
export type ContentBlock = TextBlock | ReasoningBlock | ToolCallBlock;
|
|
53
|
+
/**
|
|
54
|
+
* Why the response ended.
|
|
55
|
+
*
|
|
56
|
+
* `tool-calls` is not a failure: the model finished its turn by asking for tools, and the
|
|
57
|
+
* caller is expected to run them and continue. `aborted` means the consumer stopped the
|
|
58
|
+
* stream, which is why it is distinct from `error`.
|
|
59
|
+
*/
|
|
60
|
+
export type FinishReason = 'stop' | 'length' | 'tool-calls' | 'content-filter' | 'error' | 'aborted';
|
|
61
|
+
/** Token counts reported for one response. Every field is optional: providers differ. */
|
|
62
|
+
export interface TokenUsage {
|
|
63
|
+
inputTokens?: number;
|
|
64
|
+
outputTokens?: number;
|
|
65
|
+
/** Reasoning tokens, where the provider bills them separately from output. */
|
|
66
|
+
reasoningTokens?: number;
|
|
67
|
+
/** Input tokens served from a provider-side cache, where reported. */
|
|
68
|
+
cachedInputTokens?: number;
|
|
69
|
+
totalTokens?: number;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* One normalized event from a streamed response.
|
|
73
|
+
*
|
|
74
|
+
* `block-start` is optional in practice — several providers begin a block with its first
|
|
75
|
+
* delta — so consumers must tolerate a delta for an index they have not seen.
|
|
76
|
+
* {@link createStreamAccumulator} does.
|
|
77
|
+
*/
|
|
78
|
+
export type StreamChunk = {
|
|
79
|
+
type: 'block-start';
|
|
80
|
+
index: number;
|
|
81
|
+
blockType: ContentBlockType;
|
|
82
|
+
} | {
|
|
83
|
+
type: 'text-delta';
|
|
84
|
+
index: number;
|
|
85
|
+
text: string;
|
|
86
|
+
} | {
|
|
87
|
+
type: 'reasoning-delta';
|
|
88
|
+
index: number;
|
|
89
|
+
text: string;
|
|
90
|
+
} | {
|
|
91
|
+
type: 'tool-call-delta';
|
|
92
|
+
index: number;
|
|
93
|
+
id: string;
|
|
94
|
+
name?: string;
|
|
95
|
+
argumentsDelta: string;
|
|
96
|
+
} | {
|
|
97
|
+
type: 'block-end';
|
|
98
|
+
index: number;
|
|
99
|
+
block: ContentBlock;
|
|
100
|
+
} | {
|
|
101
|
+
type: 'usage';
|
|
102
|
+
usage: TokenUsage;
|
|
103
|
+
} | {
|
|
104
|
+
type: 'finish';
|
|
105
|
+
reason: FinishReason;
|
|
106
|
+
};
|
|
@@ -28,6 +28,39 @@ export declare const bytesToBase64: (data: Uint8Array | ArrayBuffer) => string;
|
|
|
28
28
|
* @return {Uint8Array}
|
|
29
29
|
*/
|
|
30
30
|
export declare const base64ToBytes: (base64: string) => Uint8Array<ArrayBuffer>;
|
|
31
|
+
/**
|
|
32
|
+
* @description: Encode bytes as base64url — the URL- and filename-safe alphabet from RFC 4648
|
|
33
|
+
* §5, with `+/` swapped for `-_` and the `=` padding dropped.
|
|
34
|
+
*
|
|
35
|
+
* This is the spelling JWTs, WebAuthn payloads, OAuth device codes and anything that rides in
|
|
36
|
+
* a query string or a path segment use. Plain base64 breaks in all of those: `+` becomes a
|
|
37
|
+
* space when a form-encoded value is parsed, and `/` splits a path.
|
|
38
|
+
*
|
|
39
|
+
* @param {Uint8Array | ArrayBuffer} data
|
|
40
|
+
* @return {string}
|
|
41
|
+
*/
|
|
42
|
+
export declare const bytesToBase64Url: (data: Uint8Array | ArrayBuffer) => string;
|
|
43
|
+
/**
|
|
44
|
+
* @description: Decode base64url back into bytes, restoring the padding the encoder dropped.
|
|
45
|
+
* Returns `null` on anything that is not valid base64url, because the usual caller is
|
|
46
|
+
* checking a token someone else supplied and a throw there is just a crash with extra steps.
|
|
47
|
+
*
|
|
48
|
+
* @param {string} value
|
|
49
|
+
* @return {Uint8Array | null}
|
|
50
|
+
*/
|
|
51
|
+
export declare const base64UrlToBytes: (value: string) => Uint8Array | null;
|
|
52
|
+
/**
|
|
53
|
+
* @description: Join byte chunks into one buffer, in order.
|
|
54
|
+
*
|
|
55
|
+
* The reason this exists as its own function: text that arrives in chunks must be joined
|
|
56
|
+
* *before* it is decoded, never after. A multi-byte UTF-8 character straddling a chunk
|
|
57
|
+
* boundary decodes to replacement characters when each chunk is decoded on its own, and the
|
|
58
|
+
* damage is invisible until someone reads a log with a Chinese name in it.
|
|
59
|
+
*
|
|
60
|
+
* @param {Uint8Array[]} chunks
|
|
61
|
+
* @return {Uint8Array}
|
|
62
|
+
*/
|
|
63
|
+
export declare const concatBytes: (chunks: readonly Uint8Array[]) => Uint8Array;
|
|
31
64
|
/** @description: Whether the bytes start with the gzip magic number (1f 8b). */
|
|
32
65
|
export declare const isGzip: (bytes: Uint8Array) => boolean;
|
|
33
66
|
/**
|
|
@@ -50,5 +50,6 @@ export declare const roundRectByArc: (ctx: CanvasRenderingContext2D, ...[x, y, w
|
|
|
50
50
|
* @param {number} h rectangle height
|
|
51
51
|
* @param {string} background a string such as `linear-gradient(90deg, red, blue)`
|
|
52
52
|
* @return {CanvasGradient} assignable straight to fillStyle / strokeStyle
|
|
53
|
+
* @throws {TypeError} when `background` is not a `linear-gradient(...)` value
|
|
53
54
|
*/
|
|
54
55
|
export declare const getLinearGradient: (ctx: CanvasRenderingContext2D, x: number, y: number, w: number, h: number, background: string) => CanvasGradient;
|
|
@@ -11,6 +11,14 @@ export declare enum DEVICE {
|
|
|
11
11
|
}
|
|
12
12
|
export type CurrentDevice = 'ipad' | 'android' | 'iphone' | 'pc';
|
|
13
13
|
export declare const currentDevice: () => CurrentDevice;
|
|
14
|
+
/**
|
|
15
|
+
* @description: Whether a `window` existed **when this module was first imported**.
|
|
16
|
+
*
|
|
17
|
+
* @deprecated Evaluated once at module load, so it is stuck at `false` for the life of the
|
|
18
|
+
* process in any SSR-then-hydrate flow, and cannot be stubbed in a test. New code checks
|
|
19
|
+
* `typeof window === 'undefined'` inside the function that needs it. Kept exported so
|
|
20
|
+
* existing callers keep compiling.
|
|
21
|
+
*/
|
|
14
22
|
export declare const isClient: boolean;
|
|
15
23
|
/** Viewport breakpoint, matching where the mobile layout takes over */
|
|
16
24
|
export declare const MOBILE_MEDIA_QUERY = "(max-width: 768px)";
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Line-level text diff.
|
|
3
|
+
*
|
|
4
|
+
* Enough to render "what changed in this file" — a review card, a tool result, a config
|
|
5
|
+
* preview — without a diff library. It is deliberately line-level: word- and
|
|
6
|
+
* character-level diffing is a different problem with different trade-offs, and a
|
|
7
|
+
* component that renders lines gains nothing from it.
|
|
8
|
+
*
|
|
9
|
+
* The shape is the familiar unified-diff one, because that is what consumers already know
|
|
10
|
+
* how to render and because it is what a server-side producer would send.
|
|
11
|
+
*/
|
|
12
|
+
/** What happened to one line. */
|
|
13
|
+
export type DiffLineKind = 'context' | 'added' | 'removed';
|
|
14
|
+
/** One line of a diff, carrying both line numbers so a gutter can show either side. */
|
|
15
|
+
export interface DiffLine {
|
|
16
|
+
kind: DiffLineKind;
|
|
17
|
+
text: string;
|
|
18
|
+
/** 1-based line number on the old side, or null for an added line. */
|
|
19
|
+
oldLine: number | null;
|
|
20
|
+
/** 1-based line number on the new side, or null for a removed line. */
|
|
21
|
+
newLine: number | null;
|
|
22
|
+
}
|
|
23
|
+
/** A run of changed lines plus the context around it. */
|
|
24
|
+
export interface DiffHunk {
|
|
25
|
+
/** 1-based first old-side line in this hunk; 0 when the old side is empty. */
|
|
26
|
+
oldStart: number;
|
|
27
|
+
oldLines: number;
|
|
28
|
+
/** 1-based first new-side line in this hunk; 0 when the new side is empty. */
|
|
29
|
+
newStart: number;
|
|
30
|
+
newLines: number;
|
|
31
|
+
lines: DiffLine[];
|
|
32
|
+
}
|
|
33
|
+
/** How to compute a diff. */
|
|
34
|
+
export interface DiffOptions {
|
|
35
|
+
/** Unchanged lines kept either side of a change. Defaults to 3. */
|
|
36
|
+
context?: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Diffs two texts by line.
|
|
40
|
+
*
|
|
41
|
+
* A common prefix and suffix are matched directly, so a one-line edit in a large file
|
|
42
|
+
* costs almost nothing; only the differing middle goes through the quadratic table. Past
|
|
43
|
+
* {@link MAX_TABLE_CELLS} that middle degrades to a wholesale replacement rather than
|
|
44
|
+
* allocating an unbounded table.
|
|
45
|
+
*
|
|
46
|
+
* @param oldText The text before. Pass `''` for a file being created.
|
|
47
|
+
* @param newText The text after. Pass `''` for a file being deleted.
|
|
48
|
+
* @param options Context size.
|
|
49
|
+
* @returns Hunks in order; empty when the texts are identical.
|
|
50
|
+
*/
|
|
51
|
+
export declare function diffLines(oldText: string, newText: string, options?: DiffOptions): DiffHunk[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as r,r as s,t as a}from"../../../i18n-
|
|
1
|
+
import{n as r,r as s,t as a}from"../../../i18n-DW21b_DJ.js";export{a as I18nCore,r as createI18n,s as useI18n};
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import { createBottomFollower } from './scroll';
|
|
2
|
+
import { diffLines } from './diff';
|
|
1
3
|
import { SyncHook } from './subscribe';
|
|
2
|
-
import { Mathjs, addNumSym, clamp, fit, inverseLerp, lerp, linearstep, mathjs, parseChineseNumber, parseEnglishNumber, parseRomanNumber, perToNum, range, remap, smoothstep, transformNumber } from './number';
|
|
3
|
-
import { MessageCodec, arrayBufferToString, checkEncoding, clearBr, clearStr, getMatchingSentences, getRandomString, isString, md5, randomString, strParse, toFullWidth, toHalfWidth, toString, transformText, truncate } from './str';
|
|
4
|
+
import { Mathjs, addNumSym, formatBytes, clamp, fit, inverseLerp, lerp, linearstep, mathjs, parseChineseNumber, parseEnglishNumber, parseRomanNumber, perToNum, range, remap, smoothstep, transformNumber } from './number';
|
|
5
|
+
import { MessageCodec, arrayBufferToString, checkEncoding, clearBr, clearStr, getMatchingSentences, getRandomString, isString, md5, randomString, strParse, toFullWidth, toHalfWidth, toString, transformText, truncate, truncateWithMarker, fenceCode, slugify, csvEscape } from './str';
|
|
6
|
+
import type { BottomFollower, BottomFollowerOptions } from './scroll';
|
|
7
|
+
import type { DiffHunk, DiffLine, DiffLineKind, DiffOptions } from './diff';
|
|
4
8
|
import type { TransformText, TruncateOptions, TruncatePosition } from './str';
|
|
5
9
|
import { createSpeechRecognizer, isSpeechRecognitionSupported } from './speech';
|
|
6
10
|
import type { SpeechError, SpeechErrorKind, SpeechRecognizer, SpeechRecognizerOptions } from './speech';
|
|
@@ -41,11 +45,12 @@ import { I18nCore, createI18n, useI18n } from './i18n';
|
|
|
41
45
|
import type { I18nConfig, LocaleChangeHandler, LocaleMessages, MessageDict, StringValues, TranslateParams } from './i18n';
|
|
42
46
|
import { createLocalePath } from './localePath';
|
|
43
47
|
import type { LocalePath, LocalePathConfig, LocaleRoute } from './localePath';
|
|
44
|
-
import { computePlacement } from './placement';
|
|
45
|
-
import type { ComputePlacementOptions, ComputedPlacement, Placement, PlacementRect } from './placement';
|
|
48
|
+
import { alignCrossAxis, computePlacement } from './placement';
|
|
49
|
+
import type { ComputePlacementOptions, ComputedPlacement, Placement, PlacementAlign, PlacementRect, PlacementSide } from './placement';
|
|
46
50
|
import type { ResolveLocaleOptions, TextLanguage } from './lang';
|
|
47
51
|
import { readFileAsArrayBuffer, readFileAsDataURL, readFileAsText, readFileAsUint8Array } from './file';
|
|
48
|
-
import { base64ToBytes, bytesToBase64, decodeTextBytes, fetchMaybeGzip, gunzipMaybe, isGzip, isHtmlDocument, isZipContainer, saveFileToDisk } from './binary';
|
|
52
|
+
import { base64ToBytes, bytesToBase64, decodeTextBytes, fetchMaybeGzip, gunzipMaybe, isGzip, isHtmlDocument, isZipContainer, saveFileToDisk, bytesToBase64Url, base64UrlToBytes, concatBytes } from './binary';
|
|
53
|
+
import { UNAMBIGUOUS_ALPHABET, safeEqual, secureRandomString, secureToken } from './secure';
|
|
49
54
|
import { WorkerClient, serveWorker } from './worker';
|
|
50
55
|
import type { ServeWorkerOptions, WorkerClientOptions, WorkerHandlerContext, WorkerRequestBase, WorkerResponseBase } from './worker';
|
|
51
56
|
import { isUrlCached, networkAllowsDownload, prefetchUrl, prefetchUrls, prefetchWhenIdle, whenIdle } from './prefetch';
|
|
@@ -72,5 +77,5 @@ import type { CurrentDevice } from './device';
|
|
|
72
77
|
import { MOBILE_MEDIA_QUERY, currentDevice, isClient, isMobile, isWeiXin, matchMediaQuery, watchMediaQuery } from './device';
|
|
73
78
|
import { Color, ColorScheme, FMT, HEX_COLOR_REGEX, Hsl, Hsla, RGBA_REGEX, RGB_REGEX, Rgb, Rgba, blendMultiply, blendOverlay, blendScreen, brightnessContrast, componentToHex, cosinePalette, hexToAlpha, hexToHsb, hexToHsv, hexToRgb, hsbToHsl, hsbToRgb, hslToHsb, hslToHsv, hslToRgb, hsvToHsl, hsvToRgb, hue2rgb, linearToSrgb, luma, randomColor, rgbToHex, rgbToHsb, rgbToHsl, rgbToHsv, rgbaString, rgbaToHex, rgbaToRgb, saturation, srgbToLinear, vibrance } from '@/utils/color';
|
|
74
79
|
import type { RGB } from '@/utils/color';
|
|
75
|
-
export { performanceTime, timeFormat, timestampToTime, throttle, SyncHook, clearBr, clearStr, isString, randomString, strParse, scriptOnLoad, handleFetchHook, createData, report, QuestQueue, noop, getPerformance, querystring, formatJson, filterObj, merge, replaceOld, mergeExports, perToNum, range, clamp, lerp, inverseLerp, remap, fit, linearstep, smoothstep, Mathjs, mathjs, getStatus, status, Monitor, MimeType, getExtensions, setMime, getMime, memoize, once, isImageSize, convertImageToBase64, handleError, addClassToElement, removeClassToElement, createDocumentFragment, escapeHtml, isClient, isWeiXin, isMobile, debounce, currentDevice, handleConsole, compose, Color, FMT, Hsl, Hsla, Rgb, Rgba, randomColor, hexToRgb, componentToHex, rgbToHex, rgbToHsl, hue2rgb, hslToRgb, rgbToHsb, hsbToRgb, hsvToRgb, hsvToHsl, ColorScheme, AudioRecorder, getCookie, requestUrlToBuffer, getPixelRatio, createObjectURL, getFrame, getAllQueryString, queryFlag, isInIframe, appendUrl, getCookieByName, getWindow, connection, encodeUrl, imageRequest, durationHandler, networkSpeed, TOTP, localStorageGetItem, localStorageSetItem, localStorageRemoveItem, createStore, setFontSize2html, autosizeTextarea, createSpeechRecognizer, isSpeechRecognitionSupported, Chain, create, isEqual, cloneDeep, createSignal, subscribers, getMatchingSentences, transformNumber, toString, addNumSym, checkEncoding, transformText, isSafari, md5, getRandomString, bridgeManager, Client, BridgeManager, PostMessageBridge, MessageCodec, Platform, openPortBridge, acceptPortBridge, createPortBridge, BRIDGE_MARKER, DEFAULT_CHANNEL, singleFlight, WebDB, createHandoff, buildOffsets, paginateText, indexForOffset, segmentByRanges, detectLanguage, navigatorLanguage, resolveLocale, readFileAsArrayBuffer, readFileAsUint8Array, base64ToBytes, bytesToBase64, decodeTextBytes, fetchMaybeGzip, gunzipMaybe, isGzip, isHtmlDocument, isZipContainer, saveFileToDisk, readFileAsText, readFileAsDataURL, WorkerClient, serveWorker, whenIdle, networkAllowsDownload, isUrlCached, prefetchUrl, prefetchUrls, prefetchWhenIdle, deferred, withTimeout, withTimeoutFallback, delay, TimeoutError, createRaceGuard, crc32, inflateRaw, readZipEntries, readZipEntry, zipHasEntry, rewriteZip, createZip, ZIP_STORED, ZIP_DEFLATE, arrayBufferToString, toHalfWidth, toFullWidth, truncate, parseChineseNumber, parseRomanNumber, parseEnglishNumber, MOBILE_MEDIA_QUERY, matchMediaQuery, watchMediaQuery, createLocalePath, setReportUrl, getReportUrl, formatDate, formatDuration, formatRelative, parseVttCueTiming, parseVttTimestamp, handleXhrHook, EventManager, createDoubleTapDetector, quad, cubic, quart, quint, sine, expo, circ, getImage, cutRound, opacity, getMatrix, getAngle, getArcPointerByDeg, getTangentByPointer, fanShapedByArc, roundRectByArc, getLinearGradient, adoptStyles, adoptSheetText, ADOPTED_STYLE_MARKER, ADOPTED_SHEET_MARKER, rgbToHsv, hexToHsb, hexToHsv, hsbToHsl, hslToHsb, hslToHsv, hexToAlpha, rgbaString, rgbaToRgb, rgbaToHex, srgbToLinear, linearToSrgb, luma, blendScreen, blendMultiply, blendOverlay, brightnessContrast, saturation, vibrance, cosinePalette, HEX_COLOR_REGEX, RGB_REGEX, RGBA_REGEX, loadScript, I18nCore, createI18n, useI18n, computePlacement, };
|
|
76
|
-
export type { RGB, DateInput, FormatRelativeOptions, RelativeStyle, TruncateOptions, TruncatePosition, SpeechError, SpeechErrorKind, SpeechRecognizer, SpeechRecognizerOptions, EasingFn, SpeedType, ImgSource, LoadScriptOptions, I18nConfig, MessageDict, StringValues, LocaleMessages, TranslateParams, LocaleChangeHandler, Debounced, Throttled, BeaconPayload, ReportConfig, LocalePath, LocalePathConfig, LocaleRoute, SingleFlight, IDBCollection, IDBResult, IDBStoreSchema, WebDBOptions, Handoff, HandoffOptions, JsonStore, Deferred, RaceGuard, DoubleTapDetector, DoubleTapDetectorOptions, ZipEntry, RewriteZipOptions, ResolveLocaleOptions, OffsetRange, PaginateOptions, PaginateResult, TextBox, TextGridMetrics, TextPage, Segment, TextLanguage, ServeWorkerOptions, WorkerClientOptions, WorkerHandlerContext, WorkerRequestBase, WorkerResponseBase, WhenIdleOptions, NetworkAllowanceOptions, PrefetchOptions, CurrentDevice, TransformText, MessageHandler, MessageData, PendingRequest, BridgeManagerOptions, BroadcastPayload, CallToPayload, PortBridge, OpenPortBridgeOptions, AcceptPortBridgeOptions, ComputePlacementOptions, ComputedPlacement, Placement, PlacementRect, };
|
|
80
|
+
export { createBottomFollower, diffLines, formatBytes, performanceTime, timeFormat, timestampToTime, throttle, SyncHook, clearBr, clearStr, isString, randomString, strParse, scriptOnLoad, handleFetchHook, createData, report, QuestQueue, noop, getPerformance, querystring, formatJson, filterObj, merge, replaceOld, mergeExports, perToNum, range, clamp, lerp, inverseLerp, remap, fit, linearstep, smoothstep, Mathjs, mathjs, getStatus, status, Monitor, MimeType, getExtensions, setMime, getMime, memoize, once, isImageSize, convertImageToBase64, handleError, addClassToElement, removeClassToElement, createDocumentFragment, escapeHtml, isClient, isWeiXin, isMobile, debounce, currentDevice, handleConsole, compose, Color, FMT, Hsl, Hsla, Rgb, Rgba, randomColor, hexToRgb, componentToHex, rgbToHex, rgbToHsl, hue2rgb, hslToRgb, rgbToHsb, hsbToRgb, hsvToRgb, hsvToHsl, ColorScheme, AudioRecorder, getCookie, requestUrlToBuffer, getPixelRatio, createObjectURL, getFrame, getAllQueryString, queryFlag, isInIframe, appendUrl, getCookieByName, getWindow, connection, encodeUrl, imageRequest, durationHandler, networkSpeed, TOTP, localStorageGetItem, localStorageSetItem, localStorageRemoveItem, createStore, setFontSize2html, autosizeTextarea, createSpeechRecognizer, isSpeechRecognitionSupported, Chain, create, isEqual, cloneDeep, createSignal, subscribers, getMatchingSentences, transformNumber, toString, addNumSym, checkEncoding, transformText, isSafari, md5, getRandomString, bridgeManager, Client, BridgeManager, PostMessageBridge, MessageCodec, Platform, openPortBridge, acceptPortBridge, createPortBridge, BRIDGE_MARKER, DEFAULT_CHANNEL, singleFlight, WebDB, createHandoff, buildOffsets, paginateText, indexForOffset, segmentByRanges, detectLanguage, navigatorLanguage, resolveLocale, readFileAsArrayBuffer, readFileAsUint8Array, base64ToBytes, bytesToBase64, decodeTextBytes, fetchMaybeGzip, gunzipMaybe, isGzip, isHtmlDocument, isZipContainer, saveFileToDisk, readFileAsText, readFileAsDataURL, WorkerClient, serveWorker, whenIdle, networkAllowsDownload, isUrlCached, prefetchUrl, prefetchUrls, prefetchWhenIdle, deferred, withTimeout, withTimeoutFallback, delay, TimeoutError, createRaceGuard, crc32, inflateRaw, readZipEntries, readZipEntry, zipHasEntry, rewriteZip, createZip, ZIP_STORED, ZIP_DEFLATE, arrayBufferToString, toHalfWidth, toFullWidth, truncate, parseChineseNumber, parseRomanNumber, parseEnglishNumber, MOBILE_MEDIA_QUERY, matchMediaQuery, watchMediaQuery, createLocalePath, setReportUrl, getReportUrl, formatDate, formatDuration, formatRelative, parseVttCueTiming, parseVttTimestamp, handleXhrHook, EventManager, createDoubleTapDetector, quad, cubic, quart, quint, sine, expo, circ, getImage, cutRound, opacity, getMatrix, getAngle, getArcPointerByDeg, getTangentByPointer, fanShapedByArc, roundRectByArc, getLinearGradient, adoptStyles, adoptSheetText, ADOPTED_STYLE_MARKER, ADOPTED_SHEET_MARKER, rgbToHsv, hexToHsb, hexToHsv, hsbToHsl, hslToHsb, hslToHsv, hexToAlpha, rgbaString, rgbaToRgb, rgbaToHex, srgbToLinear, linearToSrgb, luma, blendScreen, blendMultiply, blendOverlay, brightnessContrast, saturation, vibrance, cosinePalette, HEX_COLOR_REGEX, RGB_REGEX, RGBA_REGEX, loadScript, I18nCore, createI18n, useI18n, alignCrossAxis, computePlacement, safeEqual, secureRandomString, secureToken, UNAMBIGUOUS_ALPHABET, bytesToBase64Url, base64UrlToBytes, concatBytes, truncateWithMarker, fenceCode, slugify, csvEscape, };
|
|
81
|
+
export type { RGB, DateInput, FormatRelativeOptions, RelativeStyle, TruncateOptions, TruncatePosition, SpeechError, SpeechErrorKind, SpeechRecognizer, SpeechRecognizerOptions, EasingFn, SpeedType, ImgSource, LoadScriptOptions, I18nConfig, MessageDict, StringValues, LocaleMessages, TranslateParams, LocaleChangeHandler, Debounced, Throttled, BeaconPayload, ReportConfig, LocalePath, LocalePathConfig, LocaleRoute, SingleFlight, IDBCollection, IDBResult, IDBStoreSchema, WebDBOptions, Handoff, HandoffOptions, JsonStore, Deferred, RaceGuard, DoubleTapDetector, DoubleTapDetectorOptions, ZipEntry, RewriteZipOptions, ResolveLocaleOptions, OffsetRange, PaginateOptions, PaginateResult, TextBox, TextGridMetrics, TextPage, Segment, TextLanguage, ServeWorkerOptions, WorkerClientOptions, WorkerHandlerContext, WorkerRequestBase, WorkerResponseBase, WhenIdleOptions, NetworkAllowanceOptions, PrefetchOptions, CurrentDevice, TransformText, MessageHandler, MessageData, PendingRequest, BridgeManagerOptions, BroadcastPayload, CallToPayload, PortBridge, OpenPortBridgeOptions, AcceptPortBridgeOptions, ComputePlacementOptions, ComputedPlacement, Placement, PlacementAlign, PlacementRect, PlacementSide, BottomFollower, BottomFollowerOptions, DiffHunk, DiffLine, DiffLineKind, DiffOptions, };
|
package/dist/src/utils/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{$ as s,$t as a,A as t,An as n,At as o,B as r,Bn as m,Bt as i,C as p,Cn as f,Ct as j,D as c,Dn as l,Dt as u,E as d,En as h,Et as x,F as b,Fn as e,Ft as g,G as k,Gn as v,Gt as w,H as y,Hn as A,Ht as C,I as D,In as E,It as F,J as M,Jt as N,K as O,Kn as P,Kt as S,L as T,Ln as _,Lt as q,M as z,Mn as B,Mt as G,N as H,Nn as I,Nt as K,O as L,On as R,Ot as U,P as V,Pn as W,Pt as J,Q,Qt as X,R as Y,Rn as Z,Rt as $,S as ss,Sn as as,St as ts,T as ns,Tn as os,Tt as rs,U as ms,Un as is,Ut as ps,V as fs,Vn as js,Vt as cs,W as ls,Wn as us,Wt as ds,X as hs,Xt as xs,Y as bs,Yt as es,Z as gs,Zt as ks,_ as vs,_n as ws,_t as ys,a as As,an as Cs,at as Ds,b as Es,bn as Fs,bt as Ms,c as Ns,cn as Os,ct as Ps,d as Ss,dn as Ts,dt as _s,en as qs,et as zs,f as Bs,fn as Gs,ft as Hs,g as Is,gn as Ks,gt as Ls,h as Rs,hn as Us,ht as Vs,i as Ws,in as Js,it as Qs,j as Xs,jn as Ys,jt as Zs,k as $s,kn as sa,kt as aa,l as ta,ln as na,lt as oa,m as ra,mn as ma,mt as ia,n as pa,nn as fa,nt as ja,o as ca,on as la,ot as ua,p as da,pn as ha,pt as xa,q as ba,qn as ea,qt as ga,r as ka,rn as va,rt as wa,s as ya,sn as Aa,st as Ca,t as Da,tn as Ea,tt as Fa,u as Ma,un as Na,ut as Oa,v as Pa,vn as Sa,vt as Ta,w as _a,wn as qa,wt as za,x as Ba,xn as Ga,xt as Ha,y as Ia,yn as Ka,yt as La,z as Ra,zn as Ua,zt as Va}from"../../utils-B4FnyUzW.js";import{i as Wa}from"../../enums-BAgbyGdH.js";import{$ as Ja,A as Qa,B as Xa,C as Ya,D as Za,E as $a,F as st,G as at,H as tt,I as nt,J as ot,K as rt,L as mt,M as it,N as pt,O as ft,P as jt,Q as ct,R as lt,S as ut,T as dt,U as ht,V as xt,W as bt,X as et,Y as gt,Z as kt,_ as vt,a as wt,at as yt,b as At,c as Ct,ct as Dt,d as Et,dt as Ft,et as Mt,f as Nt,g as Ot,h as Pt,i as St,it as Tt,j as _t,k as qt,l as zt,lt as Bt,m as Gt,n as Ht,nt as It,o as Kt,ot as Lt,p as Rt,q as Ut,r as Vt,rt as Wt,s as Jt,st as Qt,t as Xt,tt as Yt,u as Zt,ut as $t,v as sn,w as an,x as tn,y as nn,z as on}from"../../color-BTvZUuf-.js";import{A as rn,C as mn,D as pn,E as fn,F as jn,M as cn,N as ln,O as un,P as dn,S as hn,T as xn,_ as bn,a as en,b as gn,c as kn,d as vn,f as wn,g as yn,h as An,i as Cn,j as Dn,k as En,l as Fn,m as Mn,n as Nn,o as On,p as Pn,r as Sn,s as Tn,t as _n,u as qn,v as zn,w as Bn,x as Gn,y as Hn}from"../../i18n-DW21b_DJ.js";import{n as In,t as Kn}from"../../chain-CTywmh_H.js";export{xa as ADOPTED_SHEET_MARKER,ia as ADOPTED_STYLE_MARKER,Js as AudioRecorder,ws as BRIDGE_MARKER,Sa as BridgeManager,Kn as Chain,Ka as Client,Xt as Color,Ht as ColorScheme,Fs as DEFAULT_CHANNEL,F as EventManager,Vt as FMT,St as HEX_COLOR_REGEX,wt as Hsl,Kt as Hsla,_n as I18nCore,w as MOBILE_MEDIA_QUERY,sa as Mathjs,rt as MessageCodec,tt as MimeType,ca as Monitor,Ga as Platform,as as PostMessageBridge,ka as QuestQueue,Jt as RGBA_REGEX,Ct as RGB_REGEX,zt as Rgb,Zt as Rgba,Wa as SyncHook,Ea as TOTP,_a as TimeoutError,Y as UNAMBIGUOUS_ALPHABET,_s as WebDB,D as WorkerClient,Rs as ZIP_DEFLATE,Is as ZIP_STORED,f as acceptPortBridge,Va as addClassToElement,n as addNumSym,Vs as adoptSheetText,Ls as adoptStyles,Qs as alignCrossAxis,vn as appendUrl,Ut as arrayBufferToString,i as autosizeTextarea,y as base64ToBytes,ms as base64UrlToBytes,Et as blendMultiply,Nt as blendOverlay,Rt as blendScreen,qa as bridgeManager,Gt as brightnessContrast,Ps as buildOffsets,ls as bytesToBase64,k as bytesToBase64Url,ot as checkEncoding,aa as circ,Ys as clamp,gt as clearBr,et as clearStr,Cs as cloneDeep,Pt as componentToHex,qs as compose,Ds as computePlacement,O as concatBytes,wn as connection,ys as convertImageToBase64,Ot as cosinePalette,vs as crc32,In as create,ea as createBottomFollower,Ma as createData,cs as createDocumentFragment,q as createDoubleTapDetector,Hs as createHandoff,Nn as createI18n,ua as createLocalePath,Pn as createObjectURL,os as createPortBridge,ns as createRaceGuard,fa as createSignal,l as createSpeechRecognizer,Tn as createStore,Pa as createZip,kt as csvEscape,o as cubic,S as currentDevice,Ta as cutRound,X as debounce,ba as decodeTextBytes,d as deferred,c as delay,Cn as detectLanguage,P as diffLines,Mn as durationHandler,An as encodeUrl,C as escapeHtml,Zs as expo,j as fanShapedByArc,ct as fenceCode,M as fetchMaybeGzip,la as filterObj,B as fit,I as formatBytes,un as formatDate,En as formatDuration,Aa as formatJson,rn as formatRelative,yn as getAllQueryString,za as getAngle,rs as getArcPointerByDeg,bn as getCookie,zn as getCookieByName,ht as getExtensions,Hn as getFrame,La as getImage,x as getLinearGradient,Ja as getMatchingSentences,Ms as getMatrix,bt as getMime,ra as getPerformance,gn as getPixelRatio,Mt as getRandomString,Ss as getReportUrl,Ws as getStatus,u as getTangentByPointer,Gn as getWindow,bs as gunzipMaybe,a as handleConsole,$ as handleError,Ns as handleFetchHook,ta as handleXhrHook,vt as hexToAlpha,sn as hexToHsb,nn as hexToHsv,At as hexToRgb,tn as hsbToHsl,ut as hsbToRgb,Ya as hslToHsb,an as hslToHsv,dt as hslToRgb,$a as hsvToHsl,Za as hsvToRgb,ft as hue2rgb,hn as imageRequest,oa as indexForOffset,Ia as inflateRaw,W as inverseLerp,ga as isClient,Os as isEqual,hs as isGzip,gs as isHtmlDocument,Ha as isImageSize,mn as isInIframe,N as isMobile,Bn as isSafari,R as isSpeechRecognitionSupported,Yt as isString,t as isUrlCached,es as isWeiXin,Q as isZipContainer,e as lerp,qt as linearToSrgb,E as linearstep,Da as loadScript,kn as localStorageGetItem,Fn as localStorageRemoveItem,qn as localStorageSetItem,Qa as luma,xs as matchMediaQuery,_ as mathjs,It as md5,ma as memoize,na as merge,Na as mergeExports,en as navigatorLanguage,Xs as networkAllowsDownload,xn as networkSpeed,ha as noop,Us as once,ts as opacity,h as openPortBridge,Ca as paginateText,Z as parseChineseNumber,Ua as parseEnglishNumber,m as parseRomanNumber,Dn as parseVttCueTiming,cn as parseVttTimestamp,js as perToNum,ln as performanceTime,z as prefetchUrl,H as prefetchUrls,V as prefetchWhenIdle,G as quad,K as quart,fn as queryFlag,Ts as querystring,J as quint,_t as randomColor,Wt as randomString,A as range,zs as readFileAsArrayBuffer,Fa as readFileAsDataURL,ja as readFileAsText,wa as readFileAsUint8Array,Es as readZipEntries,Ba as readZipEntry,is as remap,ps as removeClassToElement,Gs as replaceOld,Bs as report,pn as requestUrlToBuffer,On as resolveLocale,ss as rewriteZip,it as rgbToHex,pt as rgbToHsb,jt as rgbToHsl,st as rgbToHsv,nt as rgbaString,mt as rgbaToHex,lt as rgbaToRgb,U as roundRectByArc,Ra as safeEqual,on as saturation,s as saveFileToDisk,pa as scriptOnLoad,r as secureRandomString,fs as secureToken,Oa as segmentByRanges,T as serveWorker,ds as setFontSize2html,at as setMime,da as setReportUrl,g as sine,Ks as singleFlight,Tt as slugify,us as smoothstep,Xa as srgbToLinear,As as status,yt as strParse,va as subscribers,ya as throttle,dn as timeFormat,jn as timestampToTime,Lt as toFullWidth,Qt as toHalfWidth,Dt as toString,v as transformNumber,Bt as transformText,$t as truncate,Ft as truncateWithMarker,Sn as useI18n,xt as vibrance,ks as watchMediaQuery,b as whenIdle,L as withTimeout,$s as withTimeoutFallback,p as zipHasEntry};
|
|
@@ -139,4 +139,24 @@ export declare const linearstep: (edge0: number, edge1: number, x: number) => nu
|
|
|
139
139
|
* @return {number}
|
|
140
140
|
*/
|
|
141
141
|
export declare const smoothstep: (edge0: number, edge1: number, x: number) => number;
|
|
142
|
+
/**
|
|
143
|
+
* @description: Format a byte count for a person to read.
|
|
144
|
+
*
|
|
145
|
+
* Steps in units of 1024 and keeps one decimal from KB up, because "1.4 MB" is the number a
|
|
146
|
+
* file manager shows for the same file and a reader comparing the two should not have to
|
|
147
|
+
* wonder. Bytes get no decimal — a fraction of a byte is not a thing.
|
|
148
|
+
*
|
|
149
|
+
* @param {number} bytes Byte count. Negative or non-finite input yields `'0 B'`, since there
|
|
150
|
+
* is no honest reading of a file with a negative size.
|
|
151
|
+
* @return {string}
|
|
152
|
+
* @example
|
|
153
|
+
* ```ts
|
|
154
|
+
* formatBytes(0); // '0 B'
|
|
155
|
+
* formatBytes(999); // '999 B'
|
|
156
|
+
* formatBytes(1024); // '1.0 KB'
|
|
157
|
+
* formatBytes(1536); // '1.5 KB'
|
|
158
|
+
* formatBytes(1048576); // '1.0 MB'
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
export declare const formatBytes: (bytes: number) => string;
|
|
142
162
|
export {};
|
|
@@ -1,4 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
/** The side of the anchor a floating panel sits on. */
|
|
2
|
+
export type PlacementSide = 'top' | 'bottom' | 'left' | 'right';
|
|
3
|
+
/**
|
|
4
|
+
* Where the panel lines up along the cross axis: with the anchor's leading
|
|
5
|
+
* edge, its centre, or its trailing edge. For a `top`/`bottom` placement that
|
|
6
|
+
* is horizontal (left edge / centre / right edge); for `left`/`right` it is
|
|
7
|
+
* vertical (top edge / centre / bottom edge).
|
|
8
|
+
*/
|
|
9
|
+
export type PlacementAlign = 'start' | 'center' | 'end';
|
|
10
|
+
/**
|
|
11
|
+
* A side, optionally suffixed with a cross-axis alignment — the same grammar
|
|
12
|
+
* Floating UI and Popper use (`bottom`, `bottom-end`, `right-center`, …).
|
|
13
|
+
* A bare side means `-start`, which is what this module has always done.
|
|
14
|
+
*/
|
|
15
|
+
export type Placement = PlacementSide | `${PlacementSide}-${PlacementAlign}`;
|
|
2
16
|
export interface PlacementRect {
|
|
3
17
|
top: number;
|
|
4
18
|
left: number;
|
|
@@ -13,7 +27,7 @@ export interface ComputePlacementOptions {
|
|
|
13
27
|
width: number;
|
|
14
28
|
height: number;
|
|
15
29
|
};
|
|
16
|
-
/** Preferred side. Flips to the opposite side when it lacks room and the opposite side has more. */
|
|
30
|
+
/** Preferred side, optionally with a cross-axis alignment. Flips to the opposite side when it lacks room and the opposite side has more; the alignment is carried across the flip. */
|
|
17
31
|
placement: Placement;
|
|
18
32
|
/** Gap kept between the anchor and the floating panel, in px. */
|
|
19
33
|
offset?: number;
|
|
@@ -25,14 +39,37 @@ export interface ComputePlacementOptions {
|
|
|
25
39
|
export interface ComputedPlacement {
|
|
26
40
|
top: number;
|
|
27
41
|
left: number;
|
|
28
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* The placement actually used, after flip, in the form the caller asked for:
|
|
44
|
+
* a bare side stays bare, a suffixed one keeps its suffix. Callers that key a
|
|
45
|
+
* lookup table off four sides therefore keep working unchanged.
|
|
46
|
+
*/
|
|
29
47
|
placement: Placement;
|
|
48
|
+
/** The side actually used, after flip — always bare, whatever form `placement` took. */
|
|
49
|
+
side: PlacementSide;
|
|
50
|
+
/** The cross-axis alignment actually used. `start` when the caller named none. */
|
|
51
|
+
align: PlacementAlign;
|
|
30
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* @description: Where a floating panel starts along the cross axis, given how
|
|
55
|
+
* it should line up with its anchor. `start` pins the leading edges together,
|
|
56
|
+
* `end` the trailing ones, `center` splits the difference. Exported because a
|
|
57
|
+
* caller that computes its own coordinates -- a panel positioned inside a
|
|
58
|
+
* custom container rather than the viewport -- still has to align the same way
|
|
59
|
+
* `computePlacement` does, and two implementations of that would drift.
|
|
60
|
+
* @param align cross-axis alignment
|
|
61
|
+
* @param anchorStart anchor's leading edge on that axis (`left` or `top`)
|
|
62
|
+
* @param anchorSize anchor's size on that axis (`width` or `height`)
|
|
63
|
+
* @param floatingSize the panel's size on that axis
|
|
64
|
+
* @return the panel's leading edge on that axis
|
|
65
|
+
*/
|
|
66
|
+
export declare const alignCrossAxis: (align: PlacementAlign, anchorStart: number, anchorSize: number, floatingSize: number) => number;
|
|
31
67
|
/**
|
|
32
68
|
* Position a floating panel relative to an anchor rect: flips to the opposite
|
|
33
69
|
* side when the preferred side doesn't have room and the opposite side has
|
|
34
|
-
* more,
|
|
35
|
-
* Floating UI's `flip`/`shift` middleware, minus the
|
|
70
|
+
* more, aligns along the cross axis as asked, then shifts to stay within
|
|
71
|
+
* `boundary`. Mirrors Floating UI's `flip`/`shift` middleware, minus the
|
|
72
|
+
* dependency.
|
|
36
73
|
*
|
|
37
74
|
* Coordinates are viewport-relative throughout (same space as `anchor`); the
|
|
38
75
|
* caller adds `scrollX`/`scrollY` when writing `position: absolute` styles.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bottom-follow for append-only scrollers — a streaming chat transcript, a log tail, a
|
|
3
|
+
* terminal pane.
|
|
4
|
+
*
|
|
5
|
+
* The requirement sounds trivial and is not: stay pinned to the floor as content arrives,
|
|
6
|
+
* but the instant the reader scrolls up to read something, stop — and re-pin only when
|
|
7
|
+
* they come back down themselves. Naive implementations fight the reader, because they
|
|
8
|
+
* cannot tell their own scroll writes apart from the reader's.
|
|
9
|
+
*
|
|
10
|
+
* The mechanism here is an **observed-top ledger**. Every programmatic write records the
|
|
11
|
+
* `scrollTop` it produced. When a scroll event arrives, a position that matches the ledger
|
|
12
|
+
* is this module's own write echoing back; a position that deviates is the reader. That
|
|
13
|
+
* covers wheel, touch, scrollbar drag, keyboard, and `scrollIntoView` from unrelated code
|
|
14
|
+
* uniformly, without listening for any input device — which matters because no set of
|
|
15
|
+
* device listeners is ever complete.
|
|
16
|
+
*
|
|
17
|
+
* Two cases the ledger has to survive:
|
|
18
|
+
*
|
|
19
|
+
* - **Shrink-clamp.** When content shrinks, the browser clamps `scrollTop` to the new
|
|
20
|
+
* floor without any reader involvement. Comparing against `min(ledger, floor)` treats
|
|
21
|
+
* that clamp as the ledger's own value rather than as a reader scroll, so a clamp
|
|
22
|
+
* never changes who owns the scroll position — a reader who had scrolled up stays
|
|
23
|
+
* unpinned even when the clamp happens to leave them at the floor, and re-pins on
|
|
24
|
+
* their next scroll. Re-pinning on a clamp would take control back without input.
|
|
25
|
+
* - **Growth while pinned.** Appended content moves the floor away. A scroll event that
|
|
26
|
+
* the reader did not cause, while pinned, re-snaps to the new floor.
|
|
27
|
+
*
|
|
28
|
+
* Scrolling is always instant. Smooth scrolling animates over a moving target, so during
|
|
29
|
+
* streaming it visibly lags the content and, worse, its own intermediate positions read as
|
|
30
|
+
* reader input on the next event.
|
|
31
|
+
*/
|
|
32
|
+
/** How to construct a follower. */
|
|
33
|
+
export interface BottomFollowerOptions {
|
|
34
|
+
/** The element that scrolls. */
|
|
35
|
+
scrollport: HTMLElement;
|
|
36
|
+
/**
|
|
37
|
+
* Elements whose size changes should re-follow the floor while pinned — the content
|
|
38
|
+
* column, and any sticky footer that resizes outside it. Streaming text grows an
|
|
39
|
+
* existing node rather than appending one, so growth is observed rather than announced.
|
|
40
|
+
*/
|
|
41
|
+
observe?: readonly HTMLElement[];
|
|
42
|
+
/** Distance from the floor still counted as pinned. Defaults to 24. */
|
|
43
|
+
threshold?: number;
|
|
44
|
+
/**
|
|
45
|
+
* Called when the pinned state flips, so a caller can show or hide a "jump to latest"
|
|
46
|
+
* affordance. Never called for a change the caller itself just made synchronously.
|
|
47
|
+
*/
|
|
48
|
+
onPinnedChange?: (pinned: boolean) => void;
|
|
49
|
+
}
|
|
50
|
+
/** Imperative bottom-follow controller. */
|
|
51
|
+
export interface BottomFollower {
|
|
52
|
+
/** Whether new content is currently being followed. */
|
|
53
|
+
readonly pinned: boolean;
|
|
54
|
+
/** Scrolls to the floor and pins, regardless of the current state. */
|
|
55
|
+
toBottom(): void;
|
|
56
|
+
/**
|
|
57
|
+
* Follows the floor only if currently pinned. Safe to call on every content change —
|
|
58
|
+
* a reader who has scrolled up is left alone.
|
|
59
|
+
*/
|
|
60
|
+
follow(): void;
|
|
61
|
+
/**
|
|
62
|
+
* Remembers a row's current offset from the scrollport top, before content is prepended
|
|
63
|
+
* above it. Capture a row that is actually on screen, so the restore keeps what the
|
|
64
|
+
* reader is looking at exactly where it was.
|
|
65
|
+
*
|
|
66
|
+
* @param row The row to keep still.
|
|
67
|
+
*/
|
|
68
|
+
captureAnchor(row: HTMLElement): void;
|
|
69
|
+
/**
|
|
70
|
+
* Restores the captured row to its captured offset, then forgets it.
|
|
71
|
+
*
|
|
72
|
+
* @param resolve Re-resolves the row after the prepend, for callers that replace nodes
|
|
73
|
+
* rather than reuse them. Omit when the captured element is still in the document.
|
|
74
|
+
* @returns Whether an anchor was restored.
|
|
75
|
+
*/
|
|
76
|
+
restoreAnchor(resolve?: () => HTMLElement | null): boolean;
|
|
77
|
+
/** Discards any captured anchor without restoring it. */
|
|
78
|
+
releaseAnchor(): void;
|
|
79
|
+
/** Removes the scroll listener and disconnects the resize observer. */
|
|
80
|
+
destroy(): void;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Creates a bottom-follow controller for one scrollport.
|
|
84
|
+
*
|
|
85
|
+
* The controller starts pinned, which is what an append-only view wants on first paint and
|
|
86
|
+
* after a reload. To restore a remembered position instead, set `scrollTop` before or
|
|
87
|
+
* after construction and the first scroll event will settle the pinned state from the
|
|
88
|
+
* geometry.
|
|
89
|
+
*
|
|
90
|
+
* @param options Scrollport, elements to observe, and threshold.
|
|
91
|
+
* @returns The controller; call `destroy` to release its listeners.
|
|
92
|
+
*/
|
|
93
|
+
export declare function createBottomFollower(options: BottomFollowerOptions): BottomFollower;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Primitives whose correctness depends on *how* they compute, not just what they return:
|
|
3
|
+
* comparisons that must not leak their answer through timing, and randomness that must not
|
|
4
|
+
* be predictable.
|
|
5
|
+
*
|
|
6
|
+
* Kept apart from `str.ts` deliberately. `randomString` and `getRandomString` there are
|
|
7
|
+
* `Math.random()` based — fine for a DOM id or a message correlation key, unsafe for anything
|
|
8
|
+
* an attacker would want to guess. Someone reaching for a token generator should land here.
|
|
9
|
+
*
|
|
10
|
+
* Uses the Web Crypto global, which exists in browsers, Node >= 19, Deno, Workers and
|
|
11
|
+
* service workers alike.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* @description: Compare two secrets in time that does not depend on where they first differ.
|
|
15
|
+
*
|
|
16
|
+
* A plain `a === b` returns as soon as it finds a mismatch, so the time it takes reveals how
|
|
17
|
+
* long a shared prefix was — enough to recover a token one character at a time across many
|
|
18
|
+
* requests. Use this whenever one side of the comparison is attacker-supplied: access keys,
|
|
19
|
+
* session tokens, HMAC digests, pairing codes.
|
|
20
|
+
*
|
|
21
|
+
* **It hides the contents, not the length.** Inputs of different lengths return `false`
|
|
22
|
+
* immediately, because comparing to a fixed length would either truncate or pad the very data
|
|
23
|
+
* under test. Length is rarely the secret; when it is, compare fixed-width digests instead.
|
|
24
|
+
*
|
|
25
|
+
* @param {string | Uint8Array} a
|
|
26
|
+
* @param {string | Uint8Array} b
|
|
27
|
+
* @return {boolean} whether the two are byte-for-byte equal
|
|
28
|
+
*/
|
|
29
|
+
export declare const safeEqual: (a: string | Uint8Array, b: string | Uint8Array) => boolean;
|
|
30
|
+
/** Unambiguous by design: no `0`/`O`, no `1`/`l`/`I`. For codes a human reads aloud or retypes. */
|
|
31
|
+
export declare const UNAMBIGUOUS_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
|
32
|
+
/**
|
|
33
|
+
* @description: A random string drawn from `alphabet` using the platform CSPRNG.
|
|
34
|
+
*
|
|
35
|
+
* Unlike `randomString` / `getRandomString` in `str.ts`, this is safe for values an attacker
|
|
36
|
+
* must not be able to predict: device-pairing codes, one-time tokens, invite links.
|
|
37
|
+
*
|
|
38
|
+
* The draw is rejection-sampled, so every character is uniform no matter what the alphabet
|
|
39
|
+
* length is. A plain `byte % alphabet.length` is biased toward the first
|
|
40
|
+
* `256 % alphabet.length` characters — small enough to ignore for a short-lived,
|
|
41
|
+
* rate-limited code, and not small enough to reason about again every time someone reuses
|
|
42
|
+
* this for something longer-lived.
|
|
43
|
+
*
|
|
44
|
+
* @param {number} length how many characters to draw
|
|
45
|
+
* @param {string} alphabet characters to draw from; defaults to an unambiguous set
|
|
46
|
+
* @return {string}
|
|
47
|
+
*/
|
|
48
|
+
export declare const secureRandomString: (length: number, alphabet?: string) => string;
|
|
49
|
+
/**
|
|
50
|
+
* @description: A random hex token of `bytes` bytes, from the platform CSPRNG.
|
|
51
|
+
*
|
|
52
|
+
* The default of 32 bytes is 256 bits — the usual floor for a bearer token that is not
|
|
53
|
+
* expected to expire soon.
|
|
54
|
+
*
|
|
55
|
+
* @param {number} bytes how many random bytes to draw
|
|
56
|
+
* @return {string} lowercase hex, twice as long as `bytes`
|
|
57
|
+
*/
|
|
58
|
+
export declare const secureToken: (bytes?: number) => string;
|