@deepseek-ai/dsh-client-ui-primitives 0.1.1-rc.2 → 0.1.2-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.
Files changed (45) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +90 -24
  3. package/README.zh.md +91 -25
  4. package/lib/ConnectionIndicator.module.css +107 -0
  5. package/lib/DiffBlock.module.css +0 -6
  6. package/lib/DisclosureRow.module.css +21 -6
  7. package/lib/ReadBlock.module.css +0 -6
  8. package/lib/SearchBlock.module.css +0 -8
  9. package/lib/TerminalBlock.module.css +0 -6
  10. package/lib/Toast.module.css +7 -10
  11. package/lib/Tooltip.module.css +0 -7
  12. package/lib/WebBlock.module.css +0 -6
  13. package/lib/index.js +878 -334
  14. package/lib/markdown/CodeBlock.module.css +4 -2
  15. package/lib/markdown/MarkdownText.module.css +2 -2
  16. package/lib/types/ConnectionIndicator.d.ts +25 -0
  17. package/lib/types/DiffBlock.d.ts +28 -9
  18. package/lib/types/FishLogo.d.ts +7 -0
  19. package/lib/types/FoldToggle.d.ts +20 -0
  20. package/lib/types/HoverCard.d.ts +4 -4
  21. package/lib/types/JsonTree.d.ts +4 -6
  22. package/lib/types/Modal.d.ts +22 -18
  23. package/lib/types/OnboardingSurface.d.ts +2 -2
  24. package/lib/types/ReadBlock.d.ts +13 -1
  25. package/lib/types/ReferenceIcon.d.ts +16 -0
  26. package/lib/types/RiskConfirmation.d.ts +2 -1
  27. package/lib/types/SearchBlock.d.ts +14 -0
  28. package/lib/types/TerminalBlock.d.ts +4 -10
  29. package/lib/types/Toast.d.ts +9 -1
  30. package/lib/types/WebBlock.d.ts +13 -0
  31. package/lib/types/icons/index.d.ts +8 -0
  32. package/lib/types/index.d.ts +14 -8
  33. package/lib/types/markdown/CodeBlock.d.ts +11 -3
  34. package/lib/types/markdown/JsonBlock.d.ts +1 -1
  35. package/lib/types/markdown/MarkdownText.d.ts +8 -7
  36. package/lib/types/markdown/highlight.d.ts +44 -2
  37. package/lib/types/markdown/render.d.ts +9 -4
  38. package/lib/types/relative-time.d.ts +23 -0
  39. package/lib/types/useAnchoredPosition.d.ts +8 -6
  40. package/lib/types/useDismissOnOutsidePointer.d.ts +3 -1
  41. package/lib/types/user-text.d.ts +19 -0
  42. package/lib/user-text.module.css +35 -0
  43. package/package.json +4 -5
  44. package/lib/ConnectionBanner.module.css +0 -13
  45. package/lib/types/ConnectionBanner.d.ts +0 -12
@@ -56,6 +56,47 @@ export interface HighlightSpan {
56
56
  text: string;
57
57
  style: CSSProperties;
58
58
  }
59
+ /**
60
+ * Incremental highlighter for one growing streaming fence. TextMate
61
+ * tokenization is line-based and forward-only — a line's tokens depend only on
62
+ * its own text and the grammar state entering it — so appended text never
63
+ * changes a completed line's tokens. The session caches the spans of every
64
+ * completed line together with the grammar state after them; each
65
+ * {@link update} tokenizes newly completed text from that state, plus the
66
+ * still-growing last line. Per-call cost therefore excludes the completed
67
+ * prefix, and the result equals a from-scratch tokenization of the same code.
68
+ * Non-append input and a change of resolved grammar reset the cache and
69
+ * re-tokenize fully, so any input stays correct.
70
+ */
71
+ export declare class StreamingHighlightSession {
72
+ /** Grammar id the cache was built with; a different resolution resets it. */
73
+ private resolved;
74
+ /** Newline-terminated source prefix covered by {@link spans}. */
75
+ private prefix;
76
+ /** Cached spans, one entry per completed line of {@link prefix}. */
77
+ private spans;
78
+ /** Grammar state after {@link prefix}; undefined = the grammar's initial state. */
79
+ private state;
80
+ private lastCode;
81
+ private lastLang;
82
+ private lastResult;
83
+ private reset;
84
+ /** Tokenize `text` with `resolved`, resuming from the cached grammar state when one exists. */
85
+ private tokenize;
86
+ /**
87
+ * Tokenize the fence's current text into per-line highlighted runs;
88
+ * `undefined` means the caller renders its plain fallback. Idempotent per
89
+ * (`code`, `lang`) input — repeated calls return the identical result array —
90
+ * and a retained line keeps its span-array identity across growing calls, so
91
+ * a React caller can reuse cached line elements. A lazy grammar not yet
92
+ * loaded returns `undefined` and loads in the background exactly as
93
+ * {@link highlightToHtml} does; the next call after it registers highlights.
94
+ * @param code - the fence text accumulated so far (display-trimmed, no synthetic trailing newline).
95
+ * @param lang - the language hint (a markdown fence info string).
96
+ * @returns one entry per line of `code` (each an array of runs), or `undefined` for unknown or not-yet-loaded languages.
97
+ */
98
+ update(code: string, lang: string | undefined): readonly HighlightSpan[][] | undefined;
99
+ }
59
100
  /**
60
101
  * Tokenize `code` into per-line highlighted runs when `lang` maps to a
61
102
  * registered grammar; `undefined` means the caller renders its plain fallback.
@@ -64,8 +105,9 @@ export interface HighlightSpan {
64
105
  * so this returns shiki's own 2D line/token structure narrowed to what a run
65
106
  * renders. Each run's color is a `--shiki-*` custom property, keeping token
66
107
  * colors on the theme package's sheets exactly as the HTML path does; the
67
- * css-variables theme carries no font-style bits, matching that path's
68
- * color-only output. The trailing newline shiki appends as a final empty line
108
+ * markup font-style bits the theme lets through (bold/italic/underline in
109
+ * markdown scopes) are dropped — the line-numbered file view renders
110
+ * color-only runs. The trailing newline shiki appends as a final empty line
69
111
  * is dropped so the run count matches the caller's own line array.
70
112
  * @param code - the source text.
71
113
  * @param lang - the language hint (a file-extension-derived language id).
@@ -21,9 +21,14 @@ import type { PositionedBlock } from './incremental.ts';
21
21
  /** Copy-button labels forwarded to fence CodeBlocks (this package is cordis-free, so copy arrives via props). */
22
22
  export interface MarkdownCodeLabels {
23
23
  /** Copy-button idle label. */
24
- copyLabel?: string | undefined;
24
+ copyLabel: string;
25
25
  /** Copy-button label during the post-copy confirmation window. */
26
- copiedLabel?: string | undefined;
26
+ copiedLabel: string;
27
+ }
28
+ /** Localized chrome for a Markdown document. */
29
+ export interface MarkdownLabels {
30
+ code: MarkdownCodeLabels;
31
+ footnotes: string;
27
32
  }
28
33
  /** Link/image reference targets collected from a document (first definition per identifier wins, as in CommonMark). */
29
34
  export interface ReferenceTargets {
@@ -67,10 +72,10 @@ export interface MarkdownFileMentions {
67
72
  * numbering accumulated in document order while references render.
68
73
  */
69
74
  export interface MarkdownRenderContext {
70
- /** Streaming arm: fences render plain and TeX stays literal. */
75
+ /** Streaming arm: fences highlight incrementally as they grow; TeX (including ```math fences) stays literal until the settled pass. */
71
76
  readonly streaming: boolean;
72
77
  /** Localized fence copy-button labels. */
73
- readonly codeLabels: MarkdownCodeLabels | undefined;
78
+ readonly labels: MarkdownLabels;
74
79
  /** Inside a blockquote's children: tables there always fill the quote's width. */
75
80
  readonly inBlockquote?: boolean;
76
81
  /** Inline-code file mentions; absent wherever no opener vocabulary exists. */
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Compact relative-time bucketing shared by every surface that dates a
3
+ * session. Bucketing is here so two surfaces naming the same session agree;
4
+ * the words stay in each plugin's own dictionary, per locale-owned copy.
5
+ *
6
+ * @module @deepseek-ai/dsh-client-ui-primitives/relative-time
7
+ */
8
+ /** Relative-time bucket of a dated row's trailing label. */
9
+ export type RelativeTimeUnit = 'now' | 'minutes' | 'hours' | 'days' | 'months' | 'years';
10
+ /** Structured relative time: the bucket plus its magnitude (0 for 'now'). */
11
+ export interface RelativeTime {
12
+ unit: RelativeTimeUnit;
13
+ n: number;
14
+ }
15
+ /**
16
+ * Compact relative time, as a structured bucket the renderer localizes
17
+ * ("now"/"5min"/"3h"/"2d"/"4mo"/"1y" in en).
18
+ * @param at - epoch ms of the dated moment.
19
+ * @param now - current epoch ms (injected for pure rendering).
20
+ * @returns the row's trailing time bucket and magnitude.
21
+ */
22
+ export declare function relativeTime(at: number, now: number): RelativeTime;
23
+ //# sourceMappingURL=relative-time.d.ts.map
@@ -3,10 +3,10 @@
3
3
  *
4
4
  * A portaled panel is positioned from its anchor's viewport rect, which stops
5
5
  * being true the moment anything scrolls or the window resizes. This owns that
6
- * one concern: measure the anchor, offset the panel below it, clamp the result
7
- * inside the viewport, and re-run on scroll (capture phase, so scrollers nested
8
- * inside the page are caught too), on resize, and on the panel's own size
9
- * changes while the element is open.
6
+ * one concern: measure the anchor, offset the panel below or above it, clamp
7
+ * the result inside the viewport, and re-run on scroll (capture phase, so
8
+ * scrollers nested inside the page are caught too), on resize, and on the
9
+ * panel's own size changes while the element is open.
10
10
  * @module @deepseek-ai/dsh-client-ui-primitives/useAnchoredPosition
11
11
  */
12
12
  import { type CSSProperties, type RefObject } from 'react';
@@ -18,14 +18,16 @@ export interface AnchoredPositionOptions {
18
18
  anchorRef: RefObject<HTMLElement | null>;
19
19
  /** The floating element, measured so the clamp uses real dimensions. */
20
20
  panelRef: RefObject<HTMLElement | null>;
21
- /** Distance kept between the anchor's bottom edge and the panel's top. */
21
+ /** Which anchor edge the panel hangs from: below it (`bottom`, the default) or above it (`top`). */
22
+ side?: 'top' | 'bottom';
23
+ /** Distance kept between the anchor edge named by `side` and the panel. */
22
24
  gap: number;
23
25
  /** Distance kept between the panel and each viewport edge. */
24
26
  margin: number;
25
27
  }
26
28
  /**
27
29
  * Track an anchor and return the panel's fixed coordinates.
28
- * @param options - the open state, the two refs, and the gap/margin distances.
30
+ * @param options - the open state, the two refs, the placement side, and the gap/margin distances.
29
31
  * @returns `left`/`top` for the panel, or `null` before the first measurement.
30
32
  */
31
33
  export declare function useAnchoredPosition(options: AnchoredPositionOptions): CSSProperties | null;
@@ -4,6 +4,8 @@ import type { RefObject } from 'react';
4
4
  * @param root - element containing both the trigger and the open surface.
5
5
  * @param open - whether the surface is showing; false detaches the listener.
6
6
  * @param setOpen - state setter invoked with false on an outside pointerdown.
7
+ * @param portal - surface portaled outside the root (a `document.body` dialog)
8
+ * that also counts as inside; omit when the root contains the whole popover.
7
9
  */
8
- export declare function useDismissOnOutsidePointer(root: RefObject<HTMLElement | null>, open: boolean, setOpen: (open: boolean) => void): void;
10
+ export declare function useDismissOnOutsidePointer(root: RefObject<HTMLElement | null>, open: boolean, setOpen: (open: boolean) => void, portal?: RefObject<HTMLElement | null>): void;
9
11
  //# sourceMappingURL=useDismissOnOutsidePointer.d.ts.map
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Display projection of reference forms in sent user text (bubble and queue
3
+ * rows). The logged model text remains the single truth; this is presentation
4
+ * only, and every part renders inline so a single-line message never breaks
5
+ * across lines. Three decoration sources, by precedence: the wire session form
6
+ * `@[label](dsh-session:...)` folds to its label; exact session labels
7
+ * supplied by an adjacent recall decorate their bare `@label` mention; and
8
+ * plain `/name` / `@name` word-boundary tokens decorate by shape alone (sent
9
+ * tokens were validated at compose time).
10
+ */
11
+ import type { ReactNode } from 'react';
12
+ /**
13
+ * Split one sent text into inline plain runs and reference chips.
14
+ * @param text - the logged model text of the message or queue row.
15
+ * @param sessionLabels - exact session mention labels associated by an adjacent recall.
16
+ * @returns inline nodes covering the whole text.
17
+ */
18
+ export declare function projectUserText(text: string, sessionLabels: readonly string[]): ReactNode;
19
+ //# sourceMappingURL=user-text.d.ts.map
@@ -0,0 +1,35 @@
1
+ /* Inline projection of sent user text: plain runs and reference chips flow in
2
+ the consumer's own line box (bubble or queue row); nothing here is block.
3
+ White-space policy stays with the consumer: the bubble wraps with pre-wrap,
4
+ the queue preview keeps its single nowrap/ellipsis line. */
5
+
6
+ .plainRun {
7
+ font-size: inherit;
8
+ line-height: inherit;
9
+ }
10
+
11
+ .refChip {
12
+ /* Plain inline, NOT inline-flex: a flex container takes its baseline from
13
+ its first flex item — the icon svg, which has no text baseline — so the
14
+ chip's label rode ~3px above the surrounding text in both surfaces. An
15
+ inline chip shares the consumer's baseline by construction. */
16
+ display: inline;
17
+ margin: 0 2px;
18
+ color: var(--dsw-alias-state-business-primary);
19
+ font-weight: 500;
20
+ white-space: nowrap;
21
+ }
22
+
23
+ /* Inline reference glyphs (always ReferenceIcon svgs) ride the consumer's own
24
+ font: 1em keeps the glyph at the text's size in the bubble (14px + user
25
+ setting), the queue preview's fixed 13px line, and any future consumer — a
26
+ px+delta size followed only the bubble axis and left the glyph oversized in
27
+ the queue row. */
28
+ .refIcon {
29
+ width: 1em;
30
+ height: 1em;
31
+ margin-right: 4px;
32
+ /* Optical centering against the text: drops the glyph below the baseline by
33
+ a font-relative amount, so it holds across the font axis. */
34
+ vertical-align: -0.125em;
35
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-client-ui-primitives",
3
3
  "description": "Pure React atoms for the dsh web UI: controls, icons, markdown, and JSON inspectors (zero cordis)",
4
- "version": "0.1.1-rc.2",
4
+ "version": "0.1.2-alpha.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -51,8 +51,8 @@
51
51
  "devDependencies": {
52
52
  "@types/react": "~18.3.1",
53
53
  "@types/react-dom": "~18.3.0",
54
- "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
55
- "@deepseek-ai/cordis": "^4.0.1"
54
+ "@deepseek-ai/dsh-invariants": "^0.1.2-alpha.2",
55
+ "@deepseek-ai/cordis": "^4.0.2"
56
56
  },
57
57
  "files": [
58
58
  "lib/index.js",
@@ -61,7 +61,6 @@
61
61
  "lib/types/**/*.d.ts"
62
62
  ],
63
63
  "peerDependencies": {
64
- "@deepseek-ai/cordis": "^4.0.1",
65
- "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2"
64
+ "@deepseek-ai/cordis": "^4.0.2"
66
65
  }
67
66
  }
@@ -1,13 +0,0 @@
1
- .banner {
2
- position: fixed;
3
- top: 0;
4
- left: 0;
5
- right: 0;
6
- z-index: 100;
7
- padding: 4px 12px;
8
- text-align: center;
9
- font-size: 12px;
10
- line-height: 18px;
11
- background: var(--dsw-alias-state-error-primary);
12
- color: var(--dsw-alias-label-primary-foreground);
13
- }
@@ -1,12 +0,0 @@
1
- /**
2
- * Render the reconnecting banner.
3
- * @param props.reconnecting - true while the connection is in backoff/retry.
4
- * @param props.label - banner text; the owner passes localized copy (this
5
- * package is cordis-free, so copy arrives via props).
6
- * @returns the banner, or null when connected.
7
- */
8
- export declare function ConnectionBanner({ reconnecting, label }: {
9
- reconnecting: boolean;
10
- label?: string | undefined;
11
- }): import("react").JSX.Element | null;
12
- //# sourceMappingURL=ConnectionBanner.d.ts.map