@anyknown/ui 0.1.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/LICENSE +21 -0
- package/README.md +121 -0
- package/dist/components/action-bar/ActionBar.d.ts +28 -0
- package/dist/components/action-bar/ActionBar.js +73 -0
- package/dist/components/badge/Badge.d.ts +28 -0
- package/dist/components/badge/Badge.js +79 -0
- package/dist/components/button/Button.d.ts +7 -0
- package/dist/components/button/Button.js +86 -0
- package/dist/components/card/Card.d.ts +2 -0
- package/dist/components/card/Card.js +17 -0
- package/dist/components/checkbox/Checkbox.d.ts +8 -0
- package/dist/components/checkbox/Checkbox.js +102 -0
- package/dist/components/code-block/CodeBlock.d.ts +10 -0
- package/dist/components/code-block/CodeBlock.js +103 -0
- package/dist/components/composer/Composer.d.ts +27 -0
- package/dist/components/composer/Composer.js +278 -0
- package/dist/components/data-table/DataTable.d.ts +38 -0
- package/dist/components/data-table/DataTable.js +256 -0
- package/dist/components/dialog/Dialog.d.ts +36 -0
- package/dist/components/dialog/Dialog.js +77 -0
- package/dist/components/diff-viewer/DiffViewer.d.ts +13 -0
- package/dist/components/diff-viewer/DiffViewer.js +133 -0
- package/dist/components/dropdown/DropdownMenu.d.ts +43 -0
- package/dist/components/dropdown/DropdownMenu.js +86 -0
- package/dist/components/dropzone/Dropzone.d.ts +32 -0
- package/dist/components/dropzone/Dropzone.js +242 -0
- package/dist/components/empty-state/EmptyState.d.ts +9 -0
- package/dist/components/empty-state/EmptyState.js +41 -0
- package/dist/components/file-row/FileRow.d.ts +32 -0
- package/dist/components/file-row/FileRow.js +153 -0
- package/dist/components/handoff-receipt/HandoffReceipt.d.ts +15 -0
- package/dist/components/handoff-receipt/HandoffReceipt.js +147 -0
- package/dist/components/input/Input.d.ts +48 -0
- package/dist/components/input/Input.js +64 -0
- package/dist/components/interaction-card/InteractionCard.d.ts +59 -0
- package/dist/components/interaction-card/InteractionCard.js +238 -0
- package/dist/components/kbd/Kbd.d.ts +9 -0
- package/dist/components/kbd/Kbd.js +43 -0
- package/dist/components/label/Field.d.ts +11 -0
- package/dist/components/label/Field.js +28 -0
- package/dist/components/label/Label.d.ts +7 -0
- package/dist/components/label/Label.js +21 -0
- package/dist/components/label/fieldContext.d.ts +26 -0
- package/dist/components/label/fieldContext.js +16 -0
- package/dist/components/message/Message.d.ts +29 -0
- package/dist/components/message/Message.js +94 -0
- package/dist/components/password-input/PasswordInput.d.ts +20 -0
- package/dist/components/password-input/PasswordInput.js +141 -0
- package/dist/components/popover/Popover.d.ts +38 -0
- package/dist/components/popover/Popover.js +37 -0
- package/dist/components/progress/Progress.d.ts +36 -0
- package/dist/components/progress/Progress.js +200 -0
- package/dist/components/radio/Radio.d.ts +7 -0
- package/dist/components/radio/Radio.js +89 -0
- package/dist/components/radio/RadioGroup.d.ts +21 -0
- package/dist/components/radio/RadioGroup.js +34 -0
- package/dist/components/reasoning-fold/ReasoningFold.d.ts +10 -0
- package/dist/components/reasoning-fold/ReasoningFold.js +92 -0
- package/dist/components/recovery-key/RecoveryKey.d.ts +17 -0
- package/dist/components/recovery-key/RecoveryKey.js +132 -0
- package/dist/components/select/Select.d.ts +31 -0
- package/dist/components/select/Select.js +209 -0
- package/dist/components/skeleton/Skeleton.d.ts +20 -0
- package/dist/components/skeleton/Skeleton.js +57 -0
- package/dist/components/switch/Switch.d.ts +7 -0
- package/dist/components/switch/Switch.js +108 -0
- package/dist/components/tabs/Tabs.d.ts +28 -0
- package/dist/components/tabs/Tabs.js +133 -0
- package/dist/components/text/Text.d.ts +7 -0
- package/dist/components/text/Text.js +31 -0
- package/dist/components/textarea/Textarea.d.ts +7 -0
- package/dist/components/textarea/Textarea.js +25 -0
- package/dist/components/toast/Toast.d.ts +56 -0
- package/dist/components/toast/Toast.js +175 -0
- package/dist/components/tool-card/ToolCard.d.ts +55 -0
- package/dist/components/tool-card/ToolCard.js +281 -0
- package/dist/components/tooltip/Tooltip.d.ts +12 -0
- package/dist/components/tooltip/Tooltip.js +38 -0
- package/dist/components/voice-indicator/VoiceIndicator.d.ts +7 -0
- package/dist/components/voice-indicator/VoiceIndicator.js +51 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.js +38 -0
- package/dist/lib/diff.d.ts +28 -0
- package/dist/lib/diff.js +133 -0
- package/dist/lib/format.d.ts +2 -0
- package/dist/lib/format.js +24 -0
- package/dist/lib/format.test.d.ts +1 -0
- package/dist/lib/format.test.js +37 -0
- package/dist/lib/mergeRefs.d.ts +2 -0
- package/dist/lib/mergeRefs.js +8 -0
- package/dist/lib/motion.d.ts +1 -0
- package/dist/lib/motion.js +10 -0
- package/dist/lib/paths.d.ts +3 -0
- package/dist/lib/paths.js +11 -0
- package/dist/lib/popup.d.ts +29 -0
- package/dist/lib/popup.js +27 -0
- package/dist/lib/progress.d.ts +2 -0
- package/dist/lib/progress.js +28 -0
- package/dist/lib/silk.d.ts +66 -0
- package/dist/lib/silk.js +337 -0
- package/dist/lib/styled.d.ts +29 -0
- package/dist/lib/styled.js +30 -0
- package/dist/lib/svgId.d.ts +1 -0
- package/dist/lib/svgId.js +4 -0
- package/dist/lib/useAnimationFrame.d.ts +1 -0
- package/dist/lib/useAnimationFrame.js +21 -0
- package/dist/lib/useControllableState.d.ts +1 -0
- package/dist/lib/useControllableState.js +14 -0
- package/dist/lib/useCopy.d.ts +4 -0
- package/dist/lib/useCopy.js +19 -0
- package/dist/lib/voice.d.ts +2 -0
- package/dist/lib/voice.js +30 -0
- package/dist/lib/weave.d.ts +28 -0
- package/dist/lib/weave.js +83 -0
- package/dist/scrollbar.css +29 -0
- package/dist/themes.stylex.d.ts +49 -0
- package/dist/themes.stylex.js +52 -0
- package/dist/tokens.css +109 -0
- package/dist/tokens.stylex.d.ts +126 -0
- package/dist/tokens.stylex.js +149 -0
- package/package.json +81 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as stylex from "@stylexjs/stylex";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { usePrefersReducedMotion } from "../../lib/motion.js";
|
|
5
|
+
import { useAnimationFrame } from "../../lib/useAnimationFrame.js";
|
|
6
|
+
import { voicePath } from "../../lib/voice.js";
|
|
7
|
+
import { color, font, radius, space, text } from "../../tokens.stylex.js";
|
|
8
|
+
const REDUCED = "@media (prefers-reduced-motion: reduce)";
|
|
9
|
+
const styles = stylex.create({
|
|
10
|
+
voice: {
|
|
11
|
+
display: "flex",
|
|
12
|
+
alignItems: "center",
|
|
13
|
+
gap: space.xs,
|
|
14
|
+
backgroundColor: color.surface,
|
|
15
|
+
borderWidth: 1,
|
|
16
|
+
borderStyle: "solid",
|
|
17
|
+
borderColor: color.border,
|
|
18
|
+
borderRadius: radius.lg,
|
|
19
|
+
paddingBlock: space.xs,
|
|
20
|
+
paddingInline: space.sm,
|
|
21
|
+
fontFamily: font.body,
|
|
22
|
+
},
|
|
23
|
+
viz: { width: "3rem", height: "1.5rem", flex: "none", overflow: "visible" },
|
|
24
|
+
fibre: { fill: "none", stroke: color.textFaint, strokeWidth: 1.6, strokeLinecap: "round" },
|
|
25
|
+
fibreActive: { stroke: color.accent },
|
|
26
|
+
label: { fontSize: text.sm, color: color.textMuted },
|
|
27
|
+
labelStrong: { fontWeight: 500, color: color.text },
|
|
28
|
+
motionLabel: {
|
|
29
|
+
display: { default: "none", [REDUCED]: "inline" },
|
|
30
|
+
fontFamily: font.mono,
|
|
31
|
+
fontSize: "0.62rem",
|
|
32
|
+
fontWeight: 600,
|
|
33
|
+
lineHeight: 1,
|
|
34
|
+
letterSpacing: "0.06em",
|
|
35
|
+
textTransform: "uppercase",
|
|
36
|
+
color: color.accent,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
const STATE_TEXT = {
|
|
40
|
+
idle: { strong: "閒置", rest: "" },
|
|
41
|
+
listening: { strong: "聆聽中", rest: "…說完就送" },
|
|
42
|
+
thinking: { strong: "思考中", rest: "" },
|
|
43
|
+
speaking: { strong: "說話中", rest: "…插話會打斷" },
|
|
44
|
+
};
|
|
45
|
+
export function VoiceIndicator({ state, level = 0.4, statusLabel }) {
|
|
46
|
+
const reduced = usePrefersReducedMotion();
|
|
47
|
+
const [t, setT] = useState(1.2);
|
|
48
|
+
useAnimationFrame(!reduced && state !== "idle", (elapsed) => setT(elapsed / 1000));
|
|
49
|
+
const copy = STATE_TEXT[state];
|
|
50
|
+
return (_jsxs("div", { ...stylex.props(styles.voice), children: [_jsx("svg", { viewBox: "0 0 48 24", "aria-hidden": "true", ...stylex.props(styles.viz), children: _jsx("path", { d: voicePath(state, reduced ? 1.2 : t, level), ...stylex.props(styles.fibre, state !== "idle" && styles.fibreActive) }) }), _jsxs("span", { role: "status", ...stylex.props(styles.label), children: [state === "idle" && "通話待命 · ", _jsx("b", { ...stylex.props(styles.labelStrong), children: statusLabel ?? copy.strong }), copy.rest] }), state !== "idle" && (_jsx("span", { "aria-hidden": "true", ...stylex.props(styles.motionLabel), children: statusLabel ?? copy.strong }))] }));
|
|
51
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export { Button } from "./components/button/Button.js";
|
|
2
|
+
export { Card } from "./components/card/Card.js";
|
|
3
|
+
export { Text } from "./components/text/Text.js";
|
|
4
|
+
export { Input, type InputProps } from "./components/input/Input.js";
|
|
5
|
+
export { Textarea, type TextareaProps } from "./components/textarea/Textarea.js";
|
|
6
|
+
export { Label, type LabelProps } from "./components/label/Label.js";
|
|
7
|
+
export { Field, type FieldProps } from "./components/label/Field.js";
|
|
8
|
+
export { Checkbox, type CheckboxProps } from "./components/checkbox/Checkbox.js";
|
|
9
|
+
export { Radio, type RadioProps } from "./components/radio/Radio.js";
|
|
10
|
+
export { RadioGroup, type RadioGroupProps } from "./components/radio/RadioGroup.js";
|
|
11
|
+
export { Switch, type SwitchProps } from "./components/switch/Switch.js";
|
|
12
|
+
export { Select, SelectGroup, SelectItem, type SelectProps, type SelectGroupProps, type SelectItemProps, } from "./components/select/Select.js";
|
|
13
|
+
export { DropdownMenu, DropdownItem, DropdownCheckboxItem, DropdownGroup, DropdownSeparator, DropdownSub, type DropdownMenuProps, type DropdownItemProps, type DropdownCheckboxItemProps, type DropdownGroupProps, type DropdownSubProps, } from "./components/dropdown/DropdownMenu.js";
|
|
14
|
+
export { Dialog, DialogTrigger, DialogContent, DialogActions, DialogClose, ConfirmDialog, } from "./components/dialog/Dialog.js";
|
|
15
|
+
export type { DialogProps, DialogContentProps, ConfirmDialogProps } from "./components/dialog/Dialog.js";
|
|
16
|
+
export { Toaster, toast, useToast, toastManager } from "./components/toast/Toast.js";
|
|
17
|
+
export type { ToasterProps, ToastOptions } from "./components/toast/Toast.js";
|
|
18
|
+
export { Tooltip, TooltipProvider, type TooltipProps } from "./components/tooltip/Tooltip.js";
|
|
19
|
+
export { Popover, PopoverTrigger, PopoverContent, PopoverTitle, PopoverDescription, PopoverClose, } from "./components/popover/Popover.js";
|
|
20
|
+
export type { PopoverProps, PopoverContentProps } from "./components/popover/Popover.js";
|
|
21
|
+
export { Tabs, TabsList, TabsTab, TabsPanel } from "./components/tabs/Tabs.js";
|
|
22
|
+
export type { TabsProps, TabsListProps, TabsTabProps, TabsPanelProps } from "./components/tabs/Tabs.js";
|
|
23
|
+
export { Badge, Chip, type BadgeProps, type ChipProps } from "./components/badge/Badge.js";
|
|
24
|
+
export { Kbd, KbdGroup, KbdToneContext, type KbdProps, type KbdGroupProps } from "./components/kbd/Kbd.js";
|
|
25
|
+
export { Skeleton, SkeletonGroup, ThreadSkeleton } from "./components/skeleton/Skeleton.js";
|
|
26
|
+
export type { SkeletonProps, SkeletonGroupProps, ThreadSkeletonProps } from "./components/skeleton/Skeleton.js";
|
|
27
|
+
export { Spinner, Progress, ProgressBall, ProgressRing } from "./components/progress/Progress.js";
|
|
28
|
+
export type { SpinnerProps, ProgressProps, ProgressBallProps, ProgressRingProps, } from "./components/progress/Progress.js";
|
|
29
|
+
export { EmptyState, type EmptyStateProps } from "./components/empty-state/EmptyState.js";
|
|
30
|
+
export { Thread, UserMessage, AssistantMessage, TextPart, useMessageBody } from "./components/message/Message.js";
|
|
31
|
+
export type { ThreadProps, UserMessageProps, AssistantMessageProps, TextPartProps, } from "./components/message/Message.js";
|
|
32
|
+
export { ToolCard, ToolInput, ToolOutput, ToolError, SubagentLine, SubagentSummary, SubagentThread, SubagentText, } from "./components/tool-card/ToolCard.js";
|
|
33
|
+
export type { ToolCardProps, ToolState, ToolRetry, ToolErrorProps, SubagentLineProps, } from "./components/tool-card/ToolCard.js";
|
|
34
|
+
export { ReasoningFold, type ReasoningFoldProps } from "./components/reasoning-fold/ReasoningFold.js";
|
|
35
|
+
export { ActionBar } from "./components/action-bar/ActionBar.js";
|
|
36
|
+
export type { ActionBarProps, ActionBarButtonProps, CopyActionProps, RegenerateActionProps, } from "./components/action-bar/ActionBar.js";
|
|
37
|
+
export { CodeBlock, InlineCode, type CodeBlockProps } from "./components/code-block/CodeBlock.js";
|
|
38
|
+
export { PermissionCard, DecisionCard } from "./components/interaction-card/InteractionCard.js";
|
|
39
|
+
export type { PermissionCardProps, PermissionReply, PermissionReceipt, DecisionCardProps, DecisionBlock, DecisionOption, DecisionAnswer, } from "./components/interaction-card/InteractionCard.js";
|
|
40
|
+
export { HandoffReceipt } from "./components/handoff-receipt/HandoffReceipt.js";
|
|
41
|
+
export type { HandoffReceiptProps, HandoffReason } from "./components/handoff-receipt/HandoffReceipt.js";
|
|
42
|
+
export { Composer } from "./components/composer/Composer.js";
|
|
43
|
+
export type { ComposerProps, SourceRef, SlashCommand } from "./components/composer/Composer.js";
|
|
44
|
+
export { VoiceIndicator, type VoiceIndicatorProps } from "./components/voice-indicator/VoiceIndicator.js";
|
|
45
|
+
export type { VoiceState } from "./lib/voice.js";
|
|
46
|
+
export { PasswordInput, defaultScorer } from "./components/password-input/PasswordInput.js";
|
|
47
|
+
export type { PasswordInputProps } from "./components/password-input/PasswordInput.js";
|
|
48
|
+
export { RecoveryKey, type RecoveryKeyProps } from "./components/recovery-key/RecoveryKey.js";
|
|
49
|
+
export { Dropzone, UploadList } from "./components/dropzone/Dropzone.js";
|
|
50
|
+
export type { DropzoneProps, UploadListProps, UploadJob, Rejection } from "./components/dropzone/Dropzone.js";
|
|
51
|
+
export { FileRow, FileList } from "./components/file-row/FileRow.js";
|
|
52
|
+
export type { FileRowProps, FileListProps, FileItem, FileRowAction } from "./components/file-row/FileRow.js";
|
|
53
|
+
export { DiffViewer, type DiffViewerProps, type DiffFile } from "./components/diff-viewer/DiffViewer.js";
|
|
54
|
+
export { DataTable } from "./components/data-table/DataTable.js";
|
|
55
|
+
export type { DataTableProps, DataTableColumn, SortState } from "./components/data-table/DataTable.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export { Button } from "./components/button/Button.js";
|
|
2
|
+
export { Card } from "./components/card/Card.js";
|
|
3
|
+
export { Text } from "./components/text/Text.js";
|
|
4
|
+
export { Input } from "./components/input/Input.js";
|
|
5
|
+
export { Textarea } from "./components/textarea/Textarea.js";
|
|
6
|
+
export { Label } from "./components/label/Label.js";
|
|
7
|
+
export { Field } from "./components/label/Field.js";
|
|
8
|
+
export { Checkbox } from "./components/checkbox/Checkbox.js";
|
|
9
|
+
export { Radio } from "./components/radio/Radio.js";
|
|
10
|
+
export { RadioGroup } from "./components/radio/RadioGroup.js";
|
|
11
|
+
export { Switch } from "./components/switch/Switch.js";
|
|
12
|
+
export { Select, SelectGroup, SelectItem, } from "./components/select/Select.js";
|
|
13
|
+
export { DropdownMenu, DropdownItem, DropdownCheckboxItem, DropdownGroup, DropdownSeparator, DropdownSub, } from "./components/dropdown/DropdownMenu.js";
|
|
14
|
+
export { Dialog, DialogTrigger, DialogContent, DialogActions, DialogClose, ConfirmDialog, } from "./components/dialog/Dialog.js";
|
|
15
|
+
export { Toaster, toast, useToast, toastManager } from "./components/toast/Toast.js";
|
|
16
|
+
export { Tooltip, TooltipProvider } from "./components/tooltip/Tooltip.js";
|
|
17
|
+
export { Popover, PopoverTrigger, PopoverContent, PopoverTitle, PopoverDescription, PopoverClose, } from "./components/popover/Popover.js";
|
|
18
|
+
export { Tabs, TabsList, TabsTab, TabsPanel } from "./components/tabs/Tabs.js";
|
|
19
|
+
export { Badge, Chip } from "./components/badge/Badge.js";
|
|
20
|
+
export { Kbd, KbdGroup, KbdToneContext } from "./components/kbd/Kbd.js";
|
|
21
|
+
export { Skeleton, SkeletonGroup, ThreadSkeleton } from "./components/skeleton/Skeleton.js";
|
|
22
|
+
export { Spinner, Progress, ProgressBall, ProgressRing } from "./components/progress/Progress.js";
|
|
23
|
+
export { EmptyState } from "./components/empty-state/EmptyState.js";
|
|
24
|
+
export { Thread, UserMessage, AssistantMessage, TextPart, useMessageBody } from "./components/message/Message.js";
|
|
25
|
+
export { ToolCard, ToolInput, ToolOutput, ToolError, SubagentLine, SubagentSummary, SubagentThread, SubagentText, } from "./components/tool-card/ToolCard.js";
|
|
26
|
+
export { ReasoningFold } from "./components/reasoning-fold/ReasoningFold.js";
|
|
27
|
+
export { ActionBar } from "./components/action-bar/ActionBar.js";
|
|
28
|
+
export { CodeBlock, InlineCode } from "./components/code-block/CodeBlock.js";
|
|
29
|
+
export { PermissionCard, DecisionCard } from "./components/interaction-card/InteractionCard.js";
|
|
30
|
+
export { HandoffReceipt } from "./components/handoff-receipt/HandoffReceipt.js";
|
|
31
|
+
export { Composer } from "./components/composer/Composer.js";
|
|
32
|
+
export { VoiceIndicator } from "./components/voice-indicator/VoiceIndicator.js";
|
|
33
|
+
export { PasswordInput, defaultScorer } from "./components/password-input/PasswordInput.js";
|
|
34
|
+
export { RecoveryKey } from "./components/recovery-key/RecoveryKey.js";
|
|
35
|
+
export { Dropzone, UploadList } from "./components/dropzone/Dropzone.js";
|
|
36
|
+
export { FileRow, FileList } from "./components/file-row/FileRow.js";
|
|
37
|
+
export { DiffViewer } from "./components/diff-viewer/DiffViewer.js";
|
|
38
|
+
export { DataTable } from "./components/data-table/DataTable.js";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type DiffSegment = {
|
|
2
|
+
text: string;
|
|
3
|
+
marked: boolean;
|
|
4
|
+
};
|
|
5
|
+
export type DiffRow = {
|
|
6
|
+
type: "context" | "add" | "del";
|
|
7
|
+
beforeNo?: number;
|
|
8
|
+
afterNo?: number;
|
|
9
|
+
segments: DiffSegment[];
|
|
10
|
+
};
|
|
11
|
+
export type DiffBlock = {
|
|
12
|
+
kind: "lines";
|
|
13
|
+
rows: DiffRow[];
|
|
14
|
+
} | {
|
|
15
|
+
kind: "fold";
|
|
16
|
+
rows: DiffRow[];
|
|
17
|
+
};
|
|
18
|
+
export type DiffKind = "modified" | "added" | "deleted";
|
|
19
|
+
export declare function diffKind(before: string | undefined, after: string | undefined): DiffKind;
|
|
20
|
+
export declare function buildDiffRows(before: string, after: string, { wordDiff }?: {
|
|
21
|
+
wordDiff?: boolean;
|
|
22
|
+
}): DiffRow[];
|
|
23
|
+
/** Collapses runs of untouched lines longer than 2 × context into fold blocks. */
|
|
24
|
+
export declare function collapseRows(rows: DiffRow[], rawContext: number): DiffBlock[];
|
|
25
|
+
export declare function countChanges(rows: DiffRow[]): {
|
|
26
|
+
added: number;
|
|
27
|
+
removed: number;
|
|
28
|
+
};
|
package/dist/lib/diff.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { diffLines, diffWordsWithSpace } from "diff";
|
|
2
|
+
export function diffKind(before, after) {
|
|
3
|
+
if (before == null)
|
|
4
|
+
return "added";
|
|
5
|
+
if (after == null)
|
|
6
|
+
return "deleted";
|
|
7
|
+
return "modified";
|
|
8
|
+
}
|
|
9
|
+
const MAX_CHANGE_RATIO = 0.6;
|
|
10
|
+
function splitLines(value) {
|
|
11
|
+
const lines = value.split("\n");
|
|
12
|
+
if (lines.at(-1) === "")
|
|
13
|
+
lines.pop();
|
|
14
|
+
return lines;
|
|
15
|
+
}
|
|
16
|
+
function plain(text) {
|
|
17
|
+
return [{ text, marked: false }];
|
|
18
|
+
}
|
|
19
|
+
/** Word-level highlight for a paired removed/added line, skipped when almost everything changed. */
|
|
20
|
+
function pairSegments(removed, added) {
|
|
21
|
+
const parts = diffWordsWithSpace(removed, added);
|
|
22
|
+
const changed = parts.reduce((total, part) => (part.added || part.removed ? total + part.value.length : total), 0);
|
|
23
|
+
if (changed > (removed.length + added.length) * MAX_CHANGE_RATIO) {
|
|
24
|
+
return [plain(removed), plain(added)];
|
|
25
|
+
}
|
|
26
|
+
const before = [];
|
|
27
|
+
const after = [];
|
|
28
|
+
for (const part of parts) {
|
|
29
|
+
if (part.added)
|
|
30
|
+
after.push({ text: part.value, marked: true });
|
|
31
|
+
else if (part.removed)
|
|
32
|
+
before.push({ text: part.value, marked: true });
|
|
33
|
+
else {
|
|
34
|
+
before.push({ text: part.value, marked: false });
|
|
35
|
+
after.push({ text: part.value, marked: false });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return [before, after];
|
|
39
|
+
}
|
|
40
|
+
export function buildDiffRows(before, after, { wordDiff = true } = {}) {
|
|
41
|
+
const rows = [];
|
|
42
|
+
let beforeNo = 0;
|
|
43
|
+
let afterNo = 0;
|
|
44
|
+
const parts = diffLines(before, after);
|
|
45
|
+
for (let index = 0; index < parts.length; index += 1) {
|
|
46
|
+
const part = parts[index];
|
|
47
|
+
const lines = splitLines(part.value);
|
|
48
|
+
if (!part.added && !part.removed) {
|
|
49
|
+
for (const line of lines) {
|
|
50
|
+
beforeNo += 1;
|
|
51
|
+
afterNo += 1;
|
|
52
|
+
rows.push({ type: "context", beforeNo, afterNo, segments: plain(line) });
|
|
53
|
+
}
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (part.removed) {
|
|
57
|
+
const next = parts[index + 1];
|
|
58
|
+
const addedLines = wordDiff && next?.added ? splitLines(next.value) : [];
|
|
59
|
+
for (const [offset, line] of lines.entries()) {
|
|
60
|
+
beforeNo += 1;
|
|
61
|
+
const partner = addedLines[offset];
|
|
62
|
+
const segments = partner != null ? pairSegments(line, partner)[0] : plain(line);
|
|
63
|
+
rows.push({ type: "del", beforeNo, segments });
|
|
64
|
+
}
|
|
65
|
+
if (addedLines.length > 0) {
|
|
66
|
+
for (const [offset, line] of addedLines.entries()) {
|
|
67
|
+
afterNo += 1;
|
|
68
|
+
const partner = lines[offset];
|
|
69
|
+
const segments = partner != null ? pairSegments(partner, line)[1] : plain(line);
|
|
70
|
+
rows.push({ type: "add", afterNo, segments });
|
|
71
|
+
}
|
|
72
|
+
index += 1;
|
|
73
|
+
}
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
for (const line of lines) {
|
|
77
|
+
afterNo += 1;
|
|
78
|
+
rows.push({ type: "add", afterNo, segments: plain(line) });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return rows;
|
|
82
|
+
}
|
|
83
|
+
/** Collapses runs of untouched lines longer than 2 × context into fold blocks. */
|
|
84
|
+
export function collapseRows(rows, rawContext) {
|
|
85
|
+
if (!Number.isFinite(rawContext))
|
|
86
|
+
return rows.length > 0 ? [{ kind: "lines", rows }] : [];
|
|
87
|
+
const context = Math.max(0, rawContext);
|
|
88
|
+
const blocks = [];
|
|
89
|
+
let index = 0;
|
|
90
|
+
const push = (kind, slice) => {
|
|
91
|
+
if (slice.length === 0)
|
|
92
|
+
return;
|
|
93
|
+
const last = blocks.at(-1);
|
|
94
|
+
if (last?.kind === kind && kind === "lines")
|
|
95
|
+
last.rows.push(...slice);
|
|
96
|
+
else
|
|
97
|
+
blocks.push({ kind, rows: slice });
|
|
98
|
+
};
|
|
99
|
+
while (index < rows.length) {
|
|
100
|
+
if (rows[index].type !== "context") {
|
|
101
|
+
const start = index;
|
|
102
|
+
while (index < rows.length && rows[index].type !== "context")
|
|
103
|
+
index += 1;
|
|
104
|
+
push("lines", rows.slice(start, index));
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const start = index;
|
|
108
|
+
while (index < rows.length && rows[index].type === "context")
|
|
109
|
+
index += 1;
|
|
110
|
+
const run = rows.slice(start, index);
|
|
111
|
+
if (run.length <= context * 2) {
|
|
112
|
+
push("lines", run);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const leading = start === 0 ? 0 : context;
|
|
116
|
+
const trailing = index === rows.length ? 0 : context;
|
|
117
|
+
push("lines", run.slice(0, leading));
|
|
118
|
+
push("fold", run.slice(leading, run.length - trailing));
|
|
119
|
+
push("lines", trailing === 0 ? [] : run.slice(run.length - trailing));
|
|
120
|
+
}
|
|
121
|
+
return blocks;
|
|
122
|
+
}
|
|
123
|
+
export function countChanges(rows) {
|
|
124
|
+
let added = 0;
|
|
125
|
+
let removed = 0;
|
|
126
|
+
for (const row of rows) {
|
|
127
|
+
if (row.type === "add")
|
|
128
|
+
added += 1;
|
|
129
|
+
else if (row.type === "del")
|
|
130
|
+
removed += 1;
|
|
131
|
+
}
|
|
132
|
+
return { added, removed };
|
|
133
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const UNITS = ["B", "KB", "MB", "GB", "TB"];
|
|
2
|
+
export function formatBytes(bytes) {
|
|
3
|
+
if (!Number.isFinite(bytes) || bytes <= 0)
|
|
4
|
+
return "0 B";
|
|
5
|
+
let unit = Math.min(Math.max(0, Math.floor(Math.log(bytes) / Math.log(1024))), UNITS.length - 1);
|
|
6
|
+
let value = bytes / 1024 ** unit;
|
|
7
|
+
// Pick the unit after rounding, or 1048575 renders as "1024 KB".
|
|
8
|
+
if (value >= 1023.5 && unit < UNITS.length - 1) {
|
|
9
|
+
unit += 1;
|
|
10
|
+
value /= 1024;
|
|
11
|
+
}
|
|
12
|
+
return `${unit === 0 ? Math.round(value) : value.toFixed(value < 10 ? 1 : 0)} ${UNITS[unit]}`;
|
|
13
|
+
}
|
|
14
|
+
export function formatDuration(ms) {
|
|
15
|
+
if (!Number.isFinite(ms) || ms < 0)
|
|
16
|
+
return "0ms";
|
|
17
|
+
if (ms < 1000)
|
|
18
|
+
return `${Math.round(ms)}ms`;
|
|
19
|
+
// Round to whole seconds first, or 119500 renders as "1m 60s".
|
|
20
|
+
const seconds = Math.round(ms / 1000);
|
|
21
|
+
if (seconds < 60)
|
|
22
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
23
|
+
return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { formatBytes, formatDuration } from "./format.js";
|
|
3
|
+
describe("formatBytes", () => {
|
|
4
|
+
test("formats each unit", () => {
|
|
5
|
+
expect(formatBytes(512)).toBe("512 B");
|
|
6
|
+
expect(formatBytes(2400)).toBe("2.3 KB");
|
|
7
|
+
expect(formatBytes(2_400_000)).toBe("2.3 MB");
|
|
8
|
+
expect(formatBytes(40_000_000)).toBe("38 MB");
|
|
9
|
+
});
|
|
10
|
+
test("picks the unit after rounding, not before", () => {
|
|
11
|
+
expect(formatBytes(1_048_575)).toBe("1.0 MB");
|
|
12
|
+
expect(formatBytes(1_073_741_823)).toBe("1.0 GB");
|
|
13
|
+
});
|
|
14
|
+
test("survives degenerate input", () => {
|
|
15
|
+
expect(formatBytes(0)).toBe("0 B");
|
|
16
|
+
expect(formatBytes(0.5)).toBe("1 B");
|
|
17
|
+
expect(formatBytes(-1)).toBe("0 B");
|
|
18
|
+
expect(formatBytes(Number.NaN)).toBe("0 B");
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
describe("formatDuration", () => {
|
|
22
|
+
test("formats milliseconds, seconds and minutes", () => {
|
|
23
|
+
expect(formatDuration(300)).toBe("300ms");
|
|
24
|
+
expect(formatDuration(8100)).toBe("8.1s");
|
|
25
|
+
expect(formatDuration(125_000)).toBe("2m 5s");
|
|
26
|
+
});
|
|
27
|
+
test("never puts 60 in the seconds slot", () => {
|
|
28
|
+
expect(formatDuration(119_500)).toBe("2m 0s");
|
|
29
|
+
expect(formatDuration(3_599_999)).toBe("60m 0s");
|
|
30
|
+
expect(formatDuration(59_950)).toBe("1m 0s");
|
|
31
|
+
});
|
|
32
|
+
test("survives degenerate input", () => {
|
|
33
|
+
expect(formatDuration(0)).toBe("0ms");
|
|
34
|
+
expect(formatDuration(-1)).toBe("0ms");
|
|
35
|
+
expect(formatDuration(Number.NaN)).toBe("0ms");
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function usePrefersReducedMotion(): boolean;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { useSyncExternalStore } from "react";
|
|
2
|
+
const QUERY = "(prefers-reduced-motion: reduce)";
|
|
3
|
+
function subscribe(onChange) {
|
|
4
|
+
const mq = window.matchMedia(QUERY);
|
|
5
|
+
mq.addEventListener("change", onChange);
|
|
6
|
+
return () => mq.removeEventListener("change", onChange);
|
|
7
|
+
}
|
|
8
|
+
export function usePrefersReducedMotion() {
|
|
9
|
+
return useSyncExternalStore(subscribe, () => window.matchMedia(QUERY).matches, () => false);
|
|
10
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const UNWEAVE_PATH = buildUnweave();
|
|
2
|
+
function buildUnweave(width = 320, amplitude = 1.4, period = 34, mid = 3, step = 10) {
|
|
3
|
+
let d = "";
|
|
4
|
+
for (let x = 0; x <= width; x += step) {
|
|
5
|
+
d += `${d ? "L" : "M"}${x},${(mid + amplitude * Math.sin(x / period)).toFixed(1)}`;
|
|
6
|
+
}
|
|
7
|
+
return d;
|
|
8
|
+
}
|
|
9
|
+
// Handoff receipt — two loose ends knotted together when the row opens.
|
|
10
|
+
export const KNOT_LEAD = "M1 7h13";
|
|
11
|
+
export const KNOT_LOOP = "M1 8 C3 3 8.5 2 10 5.5 C11.5 9 6.5 11 5 7.5 C3.8 4.7 8 2.5 14 7.5";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as stylex from "@stylexjs/stylex";
|
|
2
|
+
export declare const growIn: string;
|
|
3
|
+
export declare const popupStyles: Readonly<{
|
|
4
|
+
readonly surface: Readonly<{
|
|
5
|
+
readonly backgroundColor: stylex.StyleXClassNameFor<"backgroundColor", string>;
|
|
6
|
+
readonly borderWidth: stylex.StyleXClassNameFor<"borderWidth", 1>;
|
|
7
|
+
readonly borderStyle: stylex.StyleXClassNameFor<"borderStyle", "solid">;
|
|
8
|
+
readonly borderColor: stylex.StyleXClassNameFor<"borderColor", string>;
|
|
9
|
+
readonly borderRadius: stylex.StyleXClassNameFor<"borderRadius", string>;
|
|
10
|
+
readonly boxShadow: stylex.StyleXClassNameFor<"boxShadow", string>;
|
|
11
|
+
readonly overflow: stylex.StyleXClassNameFor<"overflow", "hidden">;
|
|
12
|
+
readonly transformOrigin: stylex.StyleXClassNameFor<"transformOrigin", "var(--transform-origin)">;
|
|
13
|
+
readonly animationName: stylex.StyleXClassNameFor<"animationName", string>;
|
|
14
|
+
readonly animationDuration: stylex.StyleXClassNameFor<"animationDuration", "140ms">;
|
|
15
|
+
readonly animationTimingFunction: stylex.StyleXClassNameFor<"animationTimingFunction", "ease-out">;
|
|
16
|
+
readonly outline: stylex.StyleXClassNameFor<"outline", "none" | `2px solid ${stylex.StyleXVar<string>}`>;
|
|
17
|
+
readonly outlineOffset: stylex.StyleXClassNameFor<"outlineOffset", -2>;
|
|
18
|
+
}>;
|
|
19
|
+
readonly anchorWidth: Readonly<{
|
|
20
|
+
readonly width: stylex.StyleXClassNameFor<"width", "var(--anchor-width)">;
|
|
21
|
+
}>;
|
|
22
|
+
readonly availableHeight: Readonly<{
|
|
23
|
+
readonly maxHeight: stylex.StyleXClassNameFor<"maxHeight", "var(--available-height)">;
|
|
24
|
+
}>;
|
|
25
|
+
readonly positioner: Readonly<{
|
|
26
|
+
readonly zIndex: stylex.StyleXClassNameFor<"zIndex", 40>;
|
|
27
|
+
readonly outline: stylex.StyleXClassNameFor<"outline", "none">;
|
|
28
|
+
}>;
|
|
29
|
+
}>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as stylex from "@stylexjs/stylex";
|
|
2
|
+
import { color, radius, shadow } from "../tokens.stylex.js";
|
|
3
|
+
const REDUCED = "@media (prefers-reduced-motion: reduce)";
|
|
4
|
+
export const growIn = stylex.keyframes({
|
|
5
|
+
from: { opacity: 0, scale: "1 0.97" },
|
|
6
|
+
to: { opacity: 1, scale: "1 1" },
|
|
7
|
+
});
|
|
8
|
+
export const popupStyles = stylex.create({
|
|
9
|
+
surface: {
|
|
10
|
+
backgroundColor: color.surface,
|
|
11
|
+
borderWidth: 1,
|
|
12
|
+
borderStyle: "solid",
|
|
13
|
+
borderColor: color.border,
|
|
14
|
+
borderRadius: radius.lg,
|
|
15
|
+
boxShadow: shadow.popover,
|
|
16
|
+
overflow: "hidden",
|
|
17
|
+
transformOrigin: "var(--transform-origin)",
|
|
18
|
+
animationName: { default: growIn, [REDUCED]: "none" },
|
|
19
|
+
animationDuration: "140ms",
|
|
20
|
+
animationTimingFunction: "ease-out",
|
|
21
|
+
outline: { default: "none", ":focus-visible": `2px solid ${color.focusRing}` },
|
|
22
|
+
outlineOffset: -2,
|
|
23
|
+
},
|
|
24
|
+
anchorWidth: { width: "var(--anchor-width)" },
|
|
25
|
+
availableHeight: { maxHeight: "var(--available-height)" },
|
|
26
|
+
positioner: { zIndex: 40, outline: "none" },
|
|
27
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// yarn determinate — how a ball of yarn is actually wound: a bundle of parallel
|
|
2
|
+
// great-circle arcs in one direction, then the next bundle turned, and an
|
|
3
|
+
// outline circle to finish.
|
|
4
|
+
export const BALL_STRANDS = (() => {
|
|
5
|
+
const radius = 9.3;
|
|
6
|
+
const centre = 12;
|
|
7
|
+
const families = [25, 70, 115, 160].map((deg) => (deg * Math.PI) / 180);
|
|
8
|
+
const offsets = [-6.6, -3.4, -0.8, 1.8, 4.4, 7];
|
|
9
|
+
const strands = [];
|
|
10
|
+
for (const theta of families) {
|
|
11
|
+
const normal = [Math.cos(theta), Math.sin(theta)];
|
|
12
|
+
const along = [-Math.sin(theta), Math.cos(theta)];
|
|
13
|
+
for (const offset of offsets) {
|
|
14
|
+
const half = Math.sqrt(radius * radius - offset * offset);
|
|
15
|
+
const mx = centre + normal[0] * offset;
|
|
16
|
+
const my = centre + normal[1] * offset;
|
|
17
|
+
const bulge = offset * 0.62;
|
|
18
|
+
const cx = mx + normal[0] * bulge;
|
|
19
|
+
const cy = my + normal[1] * bulge;
|
|
20
|
+
strands.push(`M${(mx - along[0] * half).toFixed(2)},${(my - along[1] * half).toFixed(2)} Q${cx.toFixed(2)},${cy.toFixed(2)} ${(mx + along[0] * half).toFixed(2)},${(my + along[1] * half).toFixed(2)}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
strands.push(`M${centre},${centre - radius} A${radius},${radius} 0 1 1 ${centre - 0.01},${centre - radius}`);
|
|
24
|
+
return strands;
|
|
25
|
+
})();
|
|
26
|
+
export function ballDashOffset(percent, index) {
|
|
27
|
+
return 100 - Math.max(0, Math.min(100, percent * BALL_STRANDS.length - index * 100));
|
|
28
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export interface SilkPalette {
|
|
2
|
+
un: string;
|
|
3
|
+
sh: string;
|
|
4
|
+
y0: string;
|
|
5
|
+
y1: string;
|
|
6
|
+
y2: string;
|
|
7
|
+
y3: string;
|
|
8
|
+
y4: string;
|
|
9
|
+
hi: string;
|
|
10
|
+
}
|
|
11
|
+
export interface SilkOptions {
|
|
12
|
+
palette: SilkPalette;
|
|
13
|
+
/** "press" = 全套(帶動+窩+掃光);"hover" = 僅展示面(帶動+光澤帶) */
|
|
14
|
+
mode: "press" | "hover";
|
|
15
|
+
/** 疏織(ghost):無底紗無縫隙紗,面紗行距 2.1、粗 0.6–1.0 */
|
|
16
|
+
ghost?: boolean;
|
|
17
|
+
/** 光澤帶亮度上限(primary/danger 0.75,secondary/ghost 與卡面 0.5) */
|
|
18
|
+
bandMax?: number;
|
|
19
|
+
/** 面紗行距(大面積依 guide 降密度) */
|
|
20
|
+
pitch?: number;
|
|
21
|
+
hiCount?: number;
|
|
22
|
+
hiSpan?: number;
|
|
23
|
+
/** 圓角 clip(px);省略 = 不 clip(交給 host 的 overflow) */
|
|
24
|
+
radius?: number;
|
|
25
|
+
/** 織一塊固定高度的長布(host 高度變化不重織,只有寬度變了才重織);
|
|
26
|
+
* 動態每幀只更新可視高度內的紗 */
|
|
27
|
+
fixedHeight?: number;
|
|
28
|
+
}
|
|
29
|
+
export declare class SilkBody {
|
|
30
|
+
private host;
|
|
31
|
+
private svg;
|
|
32
|
+
private opts;
|
|
33
|
+
private reduced;
|
|
34
|
+
private id;
|
|
35
|
+
private w;
|
|
36
|
+
private h;
|
|
37
|
+
private built;
|
|
38
|
+
private live;
|
|
39
|
+
private sheen;
|
|
40
|
+
private bandG;
|
|
41
|
+
private sheenG;
|
|
42
|
+
private pointer;
|
|
43
|
+
private pressed;
|
|
44
|
+
private gx;
|
|
45
|
+
private gy;
|
|
46
|
+
private tension;
|
|
47
|
+
private tenV;
|
|
48
|
+
private pressX;
|
|
49
|
+
private pressY;
|
|
50
|
+
private bx;
|
|
51
|
+
private bandOp;
|
|
52
|
+
private sweep;
|
|
53
|
+
private raf;
|
|
54
|
+
private ro;
|
|
55
|
+
private rebuild;
|
|
56
|
+
private removers;
|
|
57
|
+
constructor(host: HTMLElement, svg: SVGSVGElement, options: SilkOptions);
|
|
58
|
+
destroy(): void;
|
|
59
|
+
private disabled;
|
|
60
|
+
private build;
|
|
61
|
+
private liveD;
|
|
62
|
+
private track;
|
|
63
|
+
private release;
|
|
64
|
+
private tick;
|
|
65
|
+
private wake;
|
|
66
|
+
}
|