@deepseek-ai/dsh-client-ui-primitives 0.1.7-rc.1 → 0.1.7-rc.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/README.i18n.yaml +60 -5
- package/README.md +23 -9
- package/README.zh.md +23 -9
- package/lib/Button.module.css +6 -8
- package/lib/Checkbox.module.css +1 -1
- package/lib/CodeCard.module.css +3 -3
- package/lib/ConnectionIndicator.module.css +6 -2
- package/lib/HoverCard.module.css +4 -8
- package/lib/ImageLightbox.module.css +3 -5
- package/lib/ImagePreview.module.css +1 -1
- package/lib/Input.module.css +2 -2
- package/lib/JsonTree.module.css +3 -3
- package/lib/Menu.module.css +22 -18
- package/lib/MenuSurface.module.css +49 -0
- package/lib/Modal.module.css +27 -6
- package/lib/Pill.module.css +2 -1
- package/lib/RiskConfirmation.module.css +1 -1
- package/lib/SearchBlock.module.css +1 -1
- package/lib/SegmentedControl.module.css +9 -9
- package/lib/SegmentedTabs.module.css +3 -3
- package/lib/ShortcutKeys.module.css +33 -0
- package/lib/Switch.module.css +3 -8
- package/lib/TerminalBlock.module.css +3 -3
- package/lib/Toast.module.css +2 -2
- package/lib/Tooltip.module.css +9 -1
- package/lib/WebBlock.module.css +1 -1
- package/lib/index.js +1174 -673
- package/lib/markdown/CodeBlock.module.css +1 -1
- package/lib/markdown/JsonBlock.module.css +2 -2
- package/lib/markdown/MarkdownText.module.css +7 -7
- package/lib/settings-form/SettingsForm.module.css +2 -2
- package/lib/settings-form/fields.module.css +4 -4
- package/lib/types/Button.d.ts +6 -5
- package/lib/types/HoverCard.d.ts +1 -1
- package/lib/types/Menu.d.ts +9 -1
- package/lib/types/MenuSurface.d.ts +16 -0
- package/lib/types/Modal.d.ts +9 -3
- package/lib/types/ShortcutKeys.d.ts +11 -0
- package/lib/types/Tooltip.d.ts +9 -2
- package/lib/types/code-highlighting.d.ts +1 -8
- package/lib/types/focus.d.ts +9 -0
- package/lib/types/guide-artwork.d.ts +15 -0
- package/lib/types/icons/index.d.ts +4 -4
- package/lib/types/icons/shared-artwork.d.ts +8 -1
- package/lib/types/index.d.ts +7 -1
- package/lib/types/input-modality.d.ts +19 -0
- package/lib/types/keyboard-composition.d.ts +11 -0
- package/lib/types/markdown/highlight.d.ts +16 -10
- package/lib/types/plugin-artwork.d.ts +1 -1
- package/lib/types/settings-form/fields.d.ts +1 -0
- package/lib/types/useModalLayer.d.ts +27 -0
- package/package.json +4 -3
- package/lib/OnboardingSurface.module.css +0 -28
- package/lib/types/OnboardingSurface.d.ts +0 -11
package/README.i18n.yaml
CHANGED
|
@@ -1,6 +1,61 @@
|
|
|
1
|
-
# Bilingual-pair consistency record (docs/i18n/README.md):
|
|
2
|
-
#
|
|
3
|
-
#
|
|
1
|
+
# Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading
|
|
2
|
+
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
|
3
|
+
# After editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/client/ui-primitives/README.md
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
/:
|
|
6
|
+
en: 6da33cc935e210c4
|
|
7
|
+
zh: 752948e68a6e2c32
|
|
8
|
+
/deepseek-ai-dsh-client-ui-primitives:
|
|
9
|
+
en: e39769cae26393b8
|
|
10
|
+
zh: 8743c8cf561fec35
|
|
11
|
+
/deepseek-ai-dsh-client-ui-primitives/summary:
|
|
12
|
+
en: a05472dce901d64f
|
|
13
|
+
zh: 6747ad81824a5c0d
|
|
14
|
+
/deepseek-ai-dsh-client-ui-primitives/table-of-contents:
|
|
15
|
+
en: d152484eb41ac6b4
|
|
16
|
+
zh: 09388d293f9be9cb
|
|
17
|
+
/deepseek-ai-dsh-client-ui-primitives/use-this-package:
|
|
18
|
+
en: ddec1f6f9b018747
|
|
19
|
+
zh: 9555f132daf8901c
|
|
20
|
+
/deepseek-ai-dsh-client-ui-primitives/use-this-package/component-catalog:
|
|
21
|
+
en: 54ced5d959bd06c6
|
|
22
|
+
zh: 5a7fc2504f92a788
|
|
23
|
+
/deepseek-ai-dsh-client-ui-primitives/use-this-package/controls-and-icons:
|
|
24
|
+
en: 83052e14c3fb34cf
|
|
25
|
+
zh: 0adb09885723296a
|
|
26
|
+
/deepseek-ai-dsh-client-ui-primitives/use-this-package/rendering-agent-output:
|
|
27
|
+
en: d79e20306593c4bf
|
|
28
|
+
zh: 5c0f1100e2cd6fcc
|
|
29
|
+
/deepseek-ai-dsh-client-ui-primitives/use-this-package/localizing-copy:
|
|
30
|
+
en: 4aef65dea11de083
|
|
31
|
+
zh: abc22cd7c65cdf38
|
|
32
|
+
/deepseek-ai-dsh-client-ui-primitives/understand-the-implementation:
|
|
33
|
+
en: 464527753be6a4f6
|
|
34
|
+
zh: d9df7442503bd58f
|
|
35
|
+
/deepseek-ai-dsh-client-ui-primitives/understand-the-implementation/source-map:
|
|
36
|
+
en: 44eb32370cbeafe8
|
|
37
|
+
zh: 728009c66375c6bb
|
|
38
|
+
/deepseek-ai-dsh-client-ui-primitives/understand-the-implementation/input-modality:
|
|
39
|
+
en: cb61e1a37d6c27e5
|
|
40
|
+
zh: 1f3fa79dd5ab2e45
|
|
41
|
+
/deepseek-ai-dsh-client-ui-primitives/understand-the-implementation/streaming-markdown:
|
|
42
|
+
en: 8bbfa14c27f3bfd7
|
|
43
|
+
zh: 88f7548ac3a114e1
|
|
44
|
+
/deepseek-ai-dsh-client-ui-primitives/understand-the-implementation/geometry-and-overflow:
|
|
45
|
+
en: 49f6341d81ded00e
|
|
46
|
+
zh: cab87dc54a42ba69
|
|
47
|
+
/deepseek-ai-dsh-client-ui-primitives/further-exploration:
|
|
48
|
+
en: e82b713cdb2f954b
|
|
49
|
+
zh: a877f34b148f433f
|
|
50
|
+
/deepseek-ai-dsh-client-ui-primitives/model-experience:
|
|
51
|
+
en: 3120df665299c634
|
|
52
|
+
zh: 1d9f365d091d0d2a
|
|
53
|
+
/deepseek-ai-dsh-client-ui-primitives/model-experience/kv-cache-effect:
|
|
54
|
+
en: ca75c51c89c2c9b0
|
|
55
|
+
zh: de54f3467ef3b9be
|
|
56
|
+
/deepseek-ai-dsh-client-ui-primitives/known-limitations-and-deferred-work:
|
|
57
|
+
en: 2f14f1e9d3239316
|
|
58
|
+
zh: c3fdaafc4c591dff
|
|
59
|
+
/deepseek-ai-dsh-client-ui-primitives/known-limitations-and-deferred-work/dev-note:
|
|
60
|
+
en: ae1d5d1bcc80c932
|
|
61
|
+
zh: 15709ccdbfcdf26c
|
package/README.md
CHANGED
|
@@ -25,6 +25,10 @@ Use `dsh-client-ui-primitives` to build web-client controls and render agent out
|
|
|
25
25
|
<a id="use-this-package"></a>
|
|
26
26
|
## Use this package
|
|
27
27
|
|
|
28
|
+
`Toast` uses the theme’s toast background and label colors in both light and dark modes. `Menu` data rows and `MenuItemButton` component rows accept owner-provided effective shortcuts and align their keys at the trailing edge as muted text without a background, including submenus. `ShortcutKeys` defaults to unboxed keys for menu and inline hints. Its `tooltip` variant uses lighter keycaps over the dark bubble and groups plus-separated combinations into one filled block. `Tooltip.shortcutKeys` vertically centers keycaps beside optional localized action text, or shows only keys when the label is empty. The optional `className` supplies owner interaction styles. `Modal` and the settings shell share top-layer Escape and Tab handling through `useModalLayer`, restoring the previous focus on close. `closeTopModal(document)` requests the foreground modal’s current close action; newer menus or unregistered dialogs block dismissal of the modal behind them. Components use `isBehindModal` to avoid moving focus behind a nested dialog. Menus consume their local Escape before modal dismissal. `observeComposition` supplies the same composition-end and legacy IME guard to local modal and recording handlers; callers dispose its document listeners with their interaction lifetime.
|
|
29
|
+
|
|
30
|
+
Automatic modal entry and modal/menu return focus, including after Escape and application close shortcuts, use `focusWithoutRing(element, options?)` to suppress the outline until Tab or directional navigation resumes normal focus styling. Dialog containers keep their outline-free styling. Existing borders, shadows, and error states remain intact. Mark a dialog's initial control with `data-modal-autofocus` so the modal captures its invoking control before moving focus. Controls mounted with the dialog must not use React `autoFocus`, which runs before that capture. Tab and Shift+Tab from the dialog container enter its first and last focusable controls.
|
|
31
|
+
|
|
28
32
|
This package is a Web-shell build input. Its static ESM retains third-party imports and styles for Vite; independent consumers supply its development dependencies ([dependency rules](../AGENTS.md#dependency-declaration)).
|
|
29
33
|
|
|
30
34
|
Compose feature UI from these atoms whenever the web client needs a standard control or an agent-output renderer. They render through React only and take `--dsw-*` design tokens from the theme, so they fit any plugin without importing the theme or the slot system.
|
|
@@ -36,7 +40,7 @@ Check this table before writing a control in a feature package. A plugin cannot
|
|
|
36
40
|
|
|
37
41
|
| Export | What it is |
|
|
38
42
|
|---|---|
|
|
39
|
-
| `Button` | Clickable action; `variant` selects `primary`, `ghost`, `outline`, or `toolbar`. |
|
|
43
|
+
| `Button` | Clickable action; `variant` selects `primary`, `ghost`, `outline`, or `toolbar`. Its ref targets the native button for focus and overlay anchoring. |
|
|
40
44
|
| `Switch` | Two-state toggle, 36×20. `label` is required, so the control cannot ship unnamed. |
|
|
41
45
|
| `SegmentedControl` | Tablist of two or more equal-width segments with one sliding indicator, for switching a card or panel between a few modes; the owner holds the selection and `label` names the list. `id` seeds each tab's id (`<id>-<value>`) and the panel it controls (`<id>-<value>-panel`), which the owner renders and points back at the tab with `aria-labelledby`; a segment may be `disabled` with a `title`, and `disabled` on the control locks every segment while the shown panel has work in flight. |
|
|
42
46
|
| `Checkbox` | Labeled native checkbox with controlled state, keyboard interaction, and disabled styling; the caller supplies localized `label` text. |
|
|
@@ -49,23 +53,23 @@ Check this table before writing a control in a feature package. A plugin cannot
|
|
|
49
53
|
| `StateDot` | Solid green `done`, amber `warning`, red `error`, and neutral-grey `idle` marks in a 10px slot, plus a tertiary-grey 14px rotating `ongoing` loader whose animations pin to document time zero so every visible loader rotates in phase. `aria-hidden`, so the render site owns the name. `appearance="step"` shows a filled check for completion and a hollow pending circle. |
|
|
50
54
|
| `ConnectionIndicator` | Inline connection-recovery control across outage, retry, and recovered states. |
|
|
51
55
|
| `DisclosureRow` | 24px compact disclosure that lays title and content side by side. Memoized with shallow prop comparison; keep callbacks and React-node props stable when their content is unchanged. |
|
|
52
|
-
| `Modal` | Centered dialog over a page mask. A nested dialog can intercept keys with `onKeyDownCapture` before document Escape handlers. |
|
|
56
|
+
| `Modal` | Centered dialog over a page mask. A nested dialog can intercept keys with `onKeyDownCapture` before document Escape handlers. The tint and dialog fade in while backdrop blur stays fully applied, honoring reduced motion. Set `backdropBlur={false}` when the caller already blurs the source page. |
|
|
53
57
|
| `RiskConfirmation` | Sensitive action gated behind an explicit checkbox. |
|
|
54
|
-
| `OnboardingSurface` | First-run stage that holds the application root inert. |
|
|
55
58
|
| `Tooltip` | Hover text anchored to a cloned child; optional `portal` rendering escapes clipping containers and ancestor stacking contexts that cap the bubble's z-index. |
|
|
56
59
|
| `HoverCard` | Hover preview the pointer can rest on and select from; optional copy button. |
|
|
57
60
|
| `ImageLightbox` | Shared image modal with focus restoration and Escape dismissal. |
|
|
58
61
|
| `Toast` | Transient top-center banner held for the owner's `holdMs`. |
|
|
59
|
-
| `SettingsForm`, `SettingsValueField`, `SettingsSecretField` | The frame and the controls of a plugin's settings page: the frame takes its copy as `labels`, saves only on its button, and discards on unmount; a value field shows staged text with the overridden badge and reset; a secret field starts blank and reports only whether a value is configured. |
|
|
62
|
+
| `SettingsForm`, `SettingsValueField`, `SettingsSecretField` | The frame and the controls of a plugin's settings page: the frame takes its copy as `labels`, saves only on its button, and discards on unmount; a value field shows staged text with the overridden badge and reset; a secret field starts blank, requests no saved-password autofill, and reports only whether a value is configured. |
|
|
60
63
|
| `SettingsFormModel`, `settingsNumberField`, `settingsTextField` | The staged-edit model behind such a page over a settings scope: drafts are staged and written on save, a field is overridden by its presence in the user layer, and a save that did not land keeps its drafts. |
|
|
61
64
|
| `JsonTree`, `JsonBlock` | Read-only JSON inspection. |
|
|
62
65
|
| `MarkdownText`, `MarkdownDelegateProvider`, `CodeBlock` | Untrusted GFM with TeX math, owner-delegated HTTP(S) navigation, and highlighted code. `CodeBlock` accepts opt-in `lineNumbers`; copied source excludes the gutter, and `contentRef` exposes its stable source wrapper to an owner that uses it as a scrollport. Set `showHeader={false}` when the owner supplies its own language and copy toolbar. |
|
|
63
66
|
| `TerminalBlock`, `ReadBlock`, `DiffBlock`, `SearchBlock`, `WebBlock` | The agent-output card matching each tool-result intent. |
|
|
64
67
|
| `icons/*`, `FishLogo`, `BrandWordmark`, `ReferenceIconRegular`/`ReferenceIconMedium`, `LinkIconRegular`/`LinkIconMedium` | Glyphs and brand marks. Use `LinkIconMedium` for 14px clickable-link categories and known-site marks. |
|
|
65
68
|
| `PermissionIconReadOnlyRegular`/`Medium`, `PermissionIconWorkspaceWriteRegular`/`Medium`, `PermissionIconFullAccessRegular`/`Medium` | Permission-mode glyphs for read-only, workspace-write, and full-access choices. |
|
|
66
|
-
| `PluginArtworkTerminal`/`Loop`/`Subagent`/`Search`/`Default` | Fixed-palette 36×36 plugin artwork
|
|
69
|
+
| `PluginArtworkTerminal`/`Loop`/`Subagent`/`Search`/`Default` | Fixed-palette 36×36 plugin artwork; `Terminal` supplies the light-blue prompt in plugin cards and sidebar guide entries. `Default` marks plugins without artwork of their own. Def ids are per-instance, so the same artwork repeats safely on one page. |
|
|
70
|
+
| `GuideArtworkBrowser`/`Files` | Fixed-palette 36×36 browser and folder artwork for sidebar guide entries. |
|
|
67
71
|
| `FileTypeIcon`, `classifyFileType`, `fileExtension` | A category-colored 28px file or folder glyph and the shared case-insensitive filename mapping behind it. Code and configuration files use detailed full-color technology glyphs; use `LinkIconMedium` for link-leading glyphs and image previews for image content. |
|
|
68
|
-
| `languageForPath`, `CODE_HIGHLIGHT_EXTENSIONS`, `useCodeHighlighter` | The
|
|
72
|
+
| `languageForPath`, `CODE_HIGHLIGHT_EXTENSIONS`, `useCodeHighlighter` | The lazy line-token highlighter shared by code preview and diff review. The filename grammar selection is re-exported from `@deepseek-ai/dsh-util-code-language`, the single extension table also behind the read card's persisted short-id `lang` hint. |
|
|
69
73
|
|
|
70
74
|
Four pairs are easy to confuse:
|
|
71
75
|
|
|
@@ -78,7 +82,7 @@ Writing your own component in your own package is fine when the need is genuinel
|
|
|
78
82
|
|
|
79
83
|
### Controls and icons
|
|
80
84
|
|
|
81
|
-
The catalog above lists what each export is for; this section covers the behavior that props alone do not show. Product icon names omit artboard sizes and end in `Regular` for the supplied one-pixel artwork or `Medium` for the same geometry at a 1.3px stroke; the `size` prop controls rendered dimensions ([decision](../../../.agents/notes/implemented/architecture/2026-09-16-size-neutral-product-icon-weights.md)). Each product, reference, link, and permission glyph intentionally keeps both weight exports even when the current product uses only one, so callers can choose emphasis without adding another API later; fill-only pairs render identically. `IconWarningOutlineRegular`/`Medium` use a circle; `IconWarningTriangleOutlineRegular`/`Medium` use a rounded triangle. `FishLogo` and `BrandWordmark` fill brand slots. `FileTypeIcon` renders the traditional 28px spreadsheet, folder, HTML, image, Markdown, generic, PDF, PPT, video, and Word glyphs, and uses the imported square technology artwork for the established 48 code and configuration categories. The import replaces artwork only: archive-only categories do not extend `CodeFileType`. `classifyFileType` applies exact filename, prefix, suffix, optional project-context, and extension rules in that order; React names win over TypeScript/JavaScript, Angular suffixes win over their base extension, and a Dart file becomes Flutter only when the supplied project files contain a `pubspec.yaml` with `flutter:`. Markdown and SVG remain traditional Markdown and image files. Spreadsheet mappings include CSV, TSV, Excel workbooks and templates, OpenDocument spreadsheets, and Numbers; KEY maps to slides, while RTF/ODT/Pages map to documents. `fileExtension` exposes the same basename and final-dot parsing for adjacent metadata labels. Traditional glyphs use a solid category-colored sheet with a white mark and translucent white corner; the generic file uses a grey sheet and darker grey corner. Callers may override the sheet color through `--dsh-file-type-icon-color`. The full-color technology artwork is the deliberate exception and retains its embedded palette. All glyphs are decorative and carry no label. `LinkIconMedium` is the leading glyph for clickable artifact links — globe, folder, code, image, document, or plain paper, and for a `url` link the mark of a well-known site named by its `href` — the developer sites a transcript usually cites (GitHub, GitLab, npm, PyPI, Stack Overflow, MDN, Wikipedia, Hacker News, YouTube, X, Bilibili, Zhihu, Juejin, CSDN) and mainstream search, video, social, shopping, and reference sites (Google, Baidu, DuckDuckGo, TikTok, Netflix, Spotify, Facebook, Instagram, Reddit, Telegram, WhatsApp, WeChat, QQ, Weibo, Taobao, AliExpress, eBay, Quora, V2EX, Apple) — while `classifyLinkPath` folds the shared file types into that existing six-category vocabulary. `ConnectionIndicator` renders a warning-colored disconnected action whose permanent retry glyph marks the retry action beside the owner-supplied outage label, the shared ongoing loader
|
|
85
|
+
The catalog above lists what each export is for; this section covers the behavior that props alone do not show. Product icon names omit artboard sizes and end in `Regular` for the supplied one-pixel artwork or `Medium` for the same geometry at a 1.3px stroke; the `size` prop controls rendered dimensions ([decision](../../../.agents/notes/implemented/architecture/2026-09-16-size-neutral-product-icon-weights.md)). Each product, reference, link, and permission glyph intentionally keeps both weight exports even when the current product uses only one, so callers can choose emphasis without adding another API later; fill-only pairs render identically. `IconWarningOutlineRegular`/`Medium` use a circle; `IconWarningTriangleOutlineRegular`/`Medium` use a rounded triangle. `FishLogo` and `BrandWordmark` fill brand slots. `FileTypeIcon` renders the traditional 28px spreadsheet, folder, HTML, image, Markdown, generic, PDF, PPT, video, and Word glyphs, and uses the imported square technology artwork for the established 48 code and configuration categories. The import replaces artwork only: archive-only categories do not extend `CodeFileType`. `classifyFileType` applies exact filename, prefix, suffix, optional project-context, and extension rules in that order; React names win over TypeScript/JavaScript, Angular suffixes win over their base extension, and a Dart file becomes Flutter only when the supplied project files contain a `pubspec.yaml` with `flutter:`. Markdown and SVG remain traditional Markdown and image files. Spreadsheet mappings include CSV, TSV, Excel workbooks and templates, OpenDocument spreadsheets, and Numbers; KEY maps to slides, while RTF/ODT/Pages map to documents. `fileExtension` exposes the same basename and final-dot parsing for adjacent metadata labels. Traditional glyphs use a solid category-colored sheet with a white mark and translucent white corner; the generic code glyph uses angle brackets and a slash at the supplied 1.35px stroke; the generic file uses a grey sheet and darker grey corner. Callers may override the sheet color through `--dsh-file-type-icon-color`. The full-color technology artwork is the deliberate exception and retains its embedded palette. All glyphs are decorative and carry no label. `LinkIconMedium` is the leading glyph for clickable artifact links — globe, folder, code, image, document, or plain paper, and for a `url` link the mark of a well-known site named by its `href` — the developer sites a transcript usually cites (GitHub, GitLab, npm, PyPI, Stack Overflow, MDN, Wikipedia, Hacker News, YouTube, X, Bilibili, Zhihu, Juejin, CSDN) and mainstream search, video, social, shopping, and reference sites (Google, Baidu, DuckDuckGo, TikTok, Netflix, Spotify, Facebook, Instagram, Reddit, Telegram, WhatsApp, WeChat, QQ, Weibo, Taobao, AliExpress, eBay, Quora, V2EX, Apple) — while `classifyLinkPath` folds the shared file types into that existing six-category vocabulary. `ConnectionIndicator` renders a warning-colored disconnected action whose permanent retry glyph marks the retry action beside the owner-supplied outage label, the shared ongoing loader in the same warning color as its label whose one-to-three dots advance every 500ms independently of retry timing, or a success-colored recovered status. Clicking either warning state requests an immediate reconnect; no hover interaction changes the copy. The pill fades in on appearance, fades out for 150ms before unmounting, and sizes to its current label. Its owner supplies visibility, the recovery hold, localized labels, and the immediate-reconnect callback; the primitive uses no native title tooltip. `useAnchoredPosition` and `useAnchoredMaxHeight` keep floating panels and bottom-anchored overlays clamped to the viewport and following their anchor; `useAnchoredMaxHeight` and portalled `Menu` widen their 12px top viewport margin to the frame's published `--dsh-frame-top-clearance`. `HoverCard` keeps its portaled preview reachable across the anchor gap and can expose a copy button through the `copyText` prop. Its `preview` variant uses the anchor or `widthAnchorRef` element’s width minus 48px, centers the panel with 24px side insets, and places it above or below the row within the viewport. The panel keeps the frame’s top clearance, caps its height at 420px, and follows content and anchor resizing; Escape or pointer and keyboard activation of the anchor dismisses it. The whole preview fades in and out over 100ms; a closing preview stops receiving pointer input and unmounts after the fade. Re-entering the anchor during the fade restores it. Reduced-motion preferences suppress the transition. Copy labels are required only when copying is enabled. A `Tooltip` nested in the anchor hides that preview while its own hover or focus label is visible; releasing, disabling, or unmounting the nested tooltip restores a still-open preview. `Toast` uses the owner's `holdMs` for both the fade delay and its hold-and-fade lifetime. Rerenders with unchanged `holdMs` do not restart that lifetime; completion uses the latest callback, and fully faded actions cannot receive input. A new component key restarts the banner. `rankByName` is the `/` menu's shared candidate ranker for the command and skill sources: the query must be a case-insensitive ordered subsequence of the name; prefix hits rank first, then alignment score, then source order. Portalled `Menu` lists follow their anchor during dragging and CSS transforms, and stop tracking when closed. `Menu.autoFocus` focuses its first enabled item, supports Arrow Up/Down and Home/End navigation, and focuses the first button in the anchor on Escape; it is opt-in for action menus.
|
|
82
86
|
|
|
83
87
|
`Tooltip` reads the anchor on hover or keyboard focus and fits its bubble from `ResizeObserver` border-box sizes. The bubble stays hidden until its first fit, slides inside the horizontal viewport margin, and flips above or below only when the opposite side fits. Label-size and viewport changes reuse the anchor coordinates; fitting does not synchronously measure the bubble or trigger a React render.
|
|
84
88
|
|
|
@@ -86,7 +90,7 @@ The catalog above lists what each export is for; this section covers the behavio
|
|
|
86
90
|
|
|
87
91
|
The nearest `MarkdownDelegateProvider` supplies optional `openExternalLink` and `openFile` navigation callbacks. Nested providers replace the enclosing capabilities, and callback changes reach already-rendered links without rebuilding Markdown. Its `openFile` makes local Markdown links clickable after settlement. Absolute and workspace-relative paths support percent escapes and `#L24` / `#L24-L30` fragments; ranges open at their first line. Literal `?` and `#` in filenames must be percent-encoded. The tooltip uses the decoded path and supplies the accessible name when the label is empty. The callback receives the decoded path and optional line, while the renderer preserves the label and displays a file icon. Without a callback, local links remain text. URL schemes, queries, unsupported fragments, and malformed destinations never reach the file opener.
|
|
88
92
|
|
|
89
|
-
`MarkdownText` renders untrusted GFM and TeX math, blocks unsafe links and images, and can turn resolved file mentions into explicit controls. A surrounding `MarkdownDelegateProvider` receives sanitized HTTP(S) URLs from ordinary clicks; modified clicks and links outside a provider retain native external-anchor behavior. When the owner passes a `pathImages` vocabulary, image destinations that are local media paths rewrite to displayable URLs on settled renders only (the same streaming gate as file mentions); without a vocabulary, local destinations remain inert alt text. A load or decode failure replaces the image with its authored alt text, or the original destination when alt is empty; `fileImages` additionally supplies a localized failure prefix. Changing the image source permits a fresh load. While a reply streams, it freezes completed blocks, advances a top-level open fence by completed lines, and highlights that fence from saved Shiki grammar state. Completed token lines enter fixed-size React groups, so later chunks reconcile only the growing group; an unchanged fence retains that DOM when the final full parse resolves cross-document syntax. `TerminalBlock`, `ReadBlock`, `DiffBlock`, `SearchBlock`, and `WebBlock` render the matching tool-result intent with copy controls, overflow handling, and ANSI processing where applicable. `JsonTree` and `JsonBlock` inspect JSON values read-only, while `projectUserText` projects sent user text into inline plain runs and reference chips for the message bubble and queue rows. Reference labels inherit the consumer’s wrapping policy: long names wrap within a bubble, while queue previews retain their single-line layout. When supplied with `UserTextReferences`, file and skill references become keyboard-accessible preview buttons using the same hover and focus styling as prose file links; the first pointer click can open a preview, while subsequent clicks and existing text selections retain native selection handling. Keyboard activation opens previews even when text is selected.
|
|
93
|
+
`MarkdownText` renders untrusted GFM and TeX math, blocks unsafe links and images, and can turn resolved file mentions into explicit controls. A surrounding `MarkdownDelegateProvider` receives sanitized HTTP(S) URLs from ordinary clicks; modified clicks and links outside a provider retain native external-anchor behavior. When the owner passes a `pathImages` vocabulary, image destinations that are local media paths rewrite to displayable URLs on settled renders only (the same streaming gate as file mentions); without a vocabulary, local destinations remain inert alt text. Rewritten images accept HTTP(S), data, blob, and the Desktop `dsh-app://app/api/file` route; authored Desktop URLs remain inert. A load or decode failure replaces the image with its authored alt text, or the original destination when alt is empty; `fileImages` additionally supplies a localized failure prefix. Changing the image source permits a fresh load. While a reply streams, it freezes completed blocks, advances a top-level open fence by completed lines, and highlights that fence from saved Shiki grammar state. Completed token lines enter fixed-size React groups, so later chunks reconcile only the growing group; an unchanged fence retains that DOM when the final full parse resolves cross-document syntax. `TerminalBlock`, `ReadBlock`, `DiffBlock`, `SearchBlock`, and `WebBlock` render the matching tool-result intent with copy controls, overflow handling, and ANSI processing where applicable. `JsonTree` and `JsonBlock` inspect JSON values read-only, while `projectUserText` projects sent user text into inline plain runs and reference chips for the message bubble and queue rows. Reference labels inherit the consumer’s wrapping policy: long names wrap within a bubble, while queue previews retain their single-line layout. When supplied with `UserTextReferences`, file and skill references become keyboard-accessible preview buttons using the same hover and focus styling as prose file links; the first pointer click can open a preview, while subsequent clicks and existing text selections retain native selection handling. Keyboard activation opens previews even when text is selected.
|
|
90
94
|
|
|
91
95
|
`MarkdownText` defaults to `variant="body"`. Use `variant="compact"` for secondary content: its 13px text and 20px line height follow the content-size setting, all heading levels use the same size with weight 600, and paragraphs and lists use tighter spacing. Text, links, and code keep the tertiary color; dotted underlines distinguish links. Code headers scroll with their blocks. Tables and math stay enabled at the surrounding text size and scroll horizontally within the available width. Both variants share the parser and streaming cache.
|
|
92
96
|
|
|
@@ -107,7 +111,11 @@ The atoms cannot read the application locale, so every piece of user-facing copy
|
|
|
107
111
|
<a id="understand-the-implementation"></a>
|
|
108
112
|
## Understand the implementation
|
|
109
113
|
|
|
110
|
-
|
|
114
|
+
`Button` uses H36/R12 for `md` and H28/R8 for `sm`, including outlined controls. Menus and cards follow the [shared radius rules](../../../docs/web-styling.md#corner-radii-and-settings-cards); feature classes preserve control geometry.
|
|
115
|
+
|
|
116
|
+
`Menu.listClassName` styles the menu card independently of the anchor wrapper, including in portal mode. Leading icons use the `--dsw-alias-menu-icon` color; destructive icons retain their error color.
|
|
117
|
+
|
|
118
|
+
`Menu` delegates its card material to `MenuSurface`; custom menus use the same component. `MenuSurface` forwards div props and refs, uses translucent fill and blur, and accepts `compact` for the smaller radius. Its default relative positioning contains the material layer; caller classes can supply fixed or absolute placement. On macOS, its non-interactive backing follows the card through CSS anchors and unmounts with it; the backing requires the Web shell’s isolated body. Feature classes control layout and elevation, while the component owns material and outer radius ([menu rules](../../../docs/web-styling.md#component-rules)). Modal masks retain their dark translucent fill without background blur.
|
|
111
119
|
|
|
112
120
|
<details>
|
|
113
121
|
<summary>Implementation internals — click to expand</summary>
|
|
@@ -125,10 +133,16 @@ The package enforces one separation: presentational React atoms with zero Cordis
|
|
|
125
133
|
| [`src/SearchBlock.tsx`](src/SearchBlock.tsx) / [`src/WebBlock.tsx`](src/WebBlock.tsx) | Search and web-retrieval cards |
|
|
126
134
|
| [`src/icons/`](src/icons/) | Size-neutral `Regular` and `Medium` product glyph components |
|
|
127
135
|
| [`src/code-highlighting.ts`](src/code-highlighting.ts) | Shared filename grammar selection and lazy line highlighting |
|
|
136
|
+
| [`src/input-modality.ts`](src/input-modality.ts) | Document-wide input modality published on `<html>` |
|
|
128
137
|
| [`src/plugin-artwork.tsx`](src/plugin-artwork.tsx) | Fixed-palette plugin artwork with per-instance SVG def ids |
|
|
129
138
|
| [`src/useAnchoredPosition.ts`](src/useAnchoredPosition.ts) / [`src/useAnchoredMaxHeight.ts`](src/useAnchoredMaxHeight.ts) | Floating-panel and overlay geometry hooks |
|
|
130
139
|
| [`src/settings-form/`](src/settings-form/) | The settings page kit: the staged form model over a settings scope, the value and secret fields, and the form frame |
|
|
131
140
|
|
|
141
|
+
<a id="input-modality"></a>
|
|
142
|
+
### Input modality
|
|
143
|
+
|
|
144
|
+
[`input-modality.ts`](src/input-modality.ts) tracks input for tooltips and publishes `data-input-modality` on `<html>` for the [theme's focus styles](../ui-theme/README.md#understand-the-implementation). `pointerModality()` is true after pointer input and false after any key, including IME composition keys; `Tooltip` uses it to decide whether focus may show a bubble. The published attribute stays `pointer` until a non-composing navigation key (Tab, arrows, Home/End, PageUp/PageDown), or focus on a different control after a non-composing key. Refocusing the same control does not restore keyboard modality. Pointer input, an IME composition key, and window blur each clear the pending key; focus changes without a pending key leave the modality unchanged. The listeners live for the document lifetime; Node imports install none. The focus-change rule observes events exposed to window; components own additional navigation inside shadow roots that do not expose those events.
|
|
145
|
+
|
|
132
146
|
### Streaming markdown
|
|
133
147
|
|
|
134
148
|
While a reply streams, `MarkdownText` parses incrementally: all but the trailing two blocks freeze as cached React elements and only the source tail re-parses per chunk, so per-chunk work tracks the tail instead of the whole reply. A final unclosed top-level fence keeps its parsed code node and sends only the last completed line plus the current partial line through the same GFM grammar; a closing fence or ambiguous parse returns to the ordinary tail path. Highlighting likewise resumes from saved Shiki grammar state and publishes only newly completed lines plus the mutable tail. `CodeBlock` seals completed lines into fixed-size React groups, reuses earlier groups, and retains the whole highlighted tree across settlement when code and language are unchanged. The settled full parse still resolves references that crossed the freeze boundary.
|
package/README.zh.md
CHANGED
|
@@ -25,6 +25,10 @@ kind: "package-library"
|
|
|
25
25
|
<a id="use-this-package"></a>
|
|
26
26
|
## 使用本包
|
|
27
27
|
|
|
28
|
+
`Toast` 在浅色和深色模式下均使用主题的系统提示背景与文字颜色。`Menu` 数据条目与 `MenuItemButton` 组件条目接收功能 owner 提供的有效快捷键,并在末端以无背景的浅灰色文字对齐显示,子菜单也采用同一呈现。`ShortcutKeys` 默认以无方块的按键文字显示菜单和行内提示。`tooltip` 变体在深色气泡上使用稍浅的键帽,以加号连接的组合则共用一个填充色块。`Tooltip.shortcutKeys` 将键帽与可选的本地化操作文本垂直居中排列,标签为空时只显示按键。可选的 `className` 供调用方设置交互状态样式。`Modal` 与设置外壳通过 `useModalLayer` 共用顶层 Esc 和 Tab 处理,关闭时恢复先前焦点。`closeTopModal(document)` 请求前台弹窗当前的关闭操作;上层菜单或未注册的对话框会阻止关闭其后方弹窗。组件通过 `isBehindModal` 避免将焦点移到嵌套弹窗后方。菜单先消费自己的 Esc,再由模态层处理关闭。 `observeComposition` 为局部弹层和录键处理提供相同的 composition-end 与旧版 IME 保护;调用方随交互生命周期释放其 document 监听。
|
|
29
|
+
|
|
30
|
+
弹窗自动进入及弹窗、菜单回焦,包括通过 Esc 和应用关闭快捷键触发的回焦,均使用 `focusWithoutRing(element, options?)`,在 Tab 或方向键导航恢复正常焦点样式前抑制外轮廓线。弹窗容器仍不绘制焦点外框。原有边框、阴影和错误状态保持不变。用 `data-modal-autofocus` 标记弹窗的初始控件,让模态层先保存触发控件,再移动焦点。随弹窗挂载的控件不得使用 React `autoFocus`,因为它会在保存触发控件前执行。弹窗容器获得焦点时,Tab 和 Shift+Tab 分别进入第一个和最后一个可聚焦控件。
|
|
31
|
+
|
|
28
32
|
本包是 Web 壳的构建输入。静态 ESM 为 Vite 保留第三方导入和样式;独立消费方自行提供开发依赖([依赖规则](../AGENTS.md#dependency-declaration))。
|
|
29
33
|
|
|
30
34
|
只要 Web 客户端需要标准控件或 agent 输出渲染器,就用这些原子组件拼装功能 UI。它们只经 React 渲染,并从主题取得 `--dsw-*` 设计 token,因此无需导入主题或 slot 系统即可适配任意插件。
|
|
@@ -36,7 +40,7 @@ kind: "package-library"
|
|
|
36
40
|
|
|
37
41
|
| 导出 | 是什么 |
|
|
38
42
|
|---|---|
|
|
39
|
-
| `Button` | 可点击操作;`variant` 选择 `primary`、`ghost`、`outline` 或 `toolbar`。 |
|
|
43
|
+
| `Button` | 可点击操作;`variant` 选择 `primary`、`ghost`、`outline` 或 `toolbar`。ref 指向原生按钮,供焦点控制与浮层锚定使用。 |
|
|
40
44
|
| `Switch` | 36×20 的双态开关。`label` 必填,控件不可能在没有名称的情况下发布。 |
|
|
41
45
|
| `SegmentedControl` | 两段或更多等宽分段加一个滑动指示块的 tablist,用于在几种模式间切换一张卡片或面板;选中项由调用方持有,`label` 为列表命名。`id` 派生每个 tab 的 id(`<id>-<value>`)及其控制的面板 id(`<id>-<value>-panel`),面板由调用方渲染并用 `aria-labelledby` 指回 tab;分段可 `disabled` 并带 `title`,控件级 `disabled` 在当前面板有进行中的操作时锁住全部分段。 |
|
|
42
46
|
| `Checkbox` | 带标签的原生复选框,支持受控状态、键盘交互和禁用样式;调用方提供本地化的 `label` 文本。 |
|
|
@@ -49,23 +53,23 @@ kind: "package-library"
|
|
|
49
53
|
| `StateDot` | 10px 槽内的绿色 `done`、琥珀色 `warning`、红色 `error`、中性灰色 `idle` 圆点,以及 tertiary 灰色 14px 旋转 `ongoing` loading,其动画固定到文档时间零点,所以所有可见 loading 同相旋转。它是 `aria-hidden` 的,名称由渲染点提供。 `appearance="step"` 以实心勾表示完成、空心圆表示等待。 |
|
|
50
54
|
| `ConnectionIndicator` | 行内连接恢复控件,覆盖断线、重试与已恢复三种状态。 |
|
|
51
55
|
| `DisclosureRow` | 24px 紧凑折叠行,标题与内容左右排列。使用浅层 prop 比较进行 memo;内容未变时,保持回调与 React 节点 prop 的引用稳定。 |
|
|
52
|
-
| `Modal` | 页面遮罩之上的居中对话框。嵌套对话框可通过 `onKeyDownCapture` 在文档级 Escape 处理器之前拦截按键。 |
|
|
56
|
+
| `Modal` | 页面遮罩之上的居中对话框。嵌套对话框可通过 `onKeyDownCapture` 在文档级 Escape 处理器之前拦截按键。 色层与弹窗淡入,背景模糊始终完整生效,并遵循减少动态效果偏好。调用方已模糊源页面时设置 `backdropBlur={false}`。 |
|
|
53
57
|
| `RiskConfirmation` | 以显式复选框把关的敏感操作确认。 |
|
|
54
|
-
| `OnboardingSurface` | 首次运行的引导舞台,期间保持应用根节点 inert。 |
|
|
55
58
|
| `Tooltip` | 锚定在克隆子元素上的悬停文本;可通过 `portal` 渲染到外层,避免被容器裁剪,或受祖先层叠上下文限制其 z-index。 |
|
|
56
59
|
| `HoverCard` | 指针可停留、可选中的悬停预览;可选带复制按钮。 |
|
|
57
60
|
| `ImageLightbox` | 共享图片浮层,支持焦点恢复与 Esc 关闭。 |
|
|
58
61
|
| `Toast` | 顶部居中的瞬时横幅,保持时长由所有者的 `holdMs` 决定。 |
|
|
59
|
-
| `SettingsForm`、`SettingsValueField`、`SettingsSecretField` | 插件设置页的框架与控件:框架以 `labels`
|
|
62
|
+
| `SettingsForm`、`SettingsValueField`、`SettingsSecretField` | 插件设置页的框架与控件:框架以 `labels` 接收文案,只在按钮点击时保存,卸载即丢弃;值字段显示暂存文本以及已覆盖标签和重置;密文字段每次为空,请求浏览器不要自动填入已保存的密码,只报告是否已配置。 |
|
|
60
63
|
| `SettingsFormModel`、`settingsNumberField`、`settingsTextField` | 这类页面背后基于设置 scope 的暂存编辑模型:草稿先暂存、保存时写入,字段是否被覆盖看用户层是否含有它,未落地的保存保留草稿。 |
|
|
61
64
|
| `JsonTree`、`JsonBlock` | 只读 JSON 查看。 |
|
|
62
65
|
| `MarkdownText`、`MarkdownDelegateProvider`、`CodeBlock` | 不可信 GFM 与 TeX 数学、owner 委托的 HTTP(S) 导航,以及高亮代码。`CodeBlock` 可通过 `lineNumbers` 开启行号;复制的源码不含行号栏,`contentRef` 则向需要把稳定源码包装节点用作滚动区的 owner 提供该节点。调用方提供自己的语言与复制工具栏时,设置 `showHeader={false}`。 |
|
|
63
66
|
| `TerminalBlock`、`ReadBlock`、`DiffBlock`、`SearchBlock`、`WebBlock` | 与各类工具结果意图对应的 agent 输出卡片。 |
|
|
64
67
|
| `icons/*`、`FishLogo`、`BrandWordmark`、`ReferenceIconRegular`/`ReferenceIconMedium`、`LinkIconRegular`/`LinkIconMedium` | 字形与品牌标识。`LinkIconMedium` 用于 14px 的可点击链接分类及已知站点标记。 |
|
|
65
68
|
| `PermissionIconReadOnlyRegular`/`Medium`、`PermissionIconWorkspaceWriteRegular`/`Medium`、`PermissionIconFullAccessRegular`/`Medium` | 只读、工作区写入与完全访问选项使用的权限模式图形。 |
|
|
66
|
-
| `PluginArtworkTerminal`/`Loop`/`Subagent`/`Search`/`Default` |
|
|
69
|
+
| `PluginArtworkTerminal`/`Loop`/`Subagent`/`Search`/`Default` | 固定配色的 36×36 插件插画;`Terminal` 为插件卡片和侧边栏开始页入口提供浅蓝色提示符。`Default` 用于没有自有插画的插件。def id 按实例生成,同一插画可在一页中安全重复。 |
|
|
70
|
+
| `GuideArtworkBrowser`/`Files` | 固定配色的 36×36 浏览器与文件夹插画,用于侧栏引导入口。 |
|
|
67
71
|
| `FileTypeIcon`、`classifyFileType`、`fileExtension` | 按类别着色的 28px 文件或文件夹图形,以及它背后共享的不区分大小写文件名映射。代码与配置文件使用细分的全彩技术图形;链接前置图形使用 `LinkIconMedium`,图片内容使用图片预览。 |
|
|
68
|
-
| `languageForPath`、`CODE_HIGHLIGHT_EXTENSIONS`、`useCodeHighlighter` | 代码预览与 diff review
|
|
72
|
+
| `languageForPath`、`CODE_HIGHLIGHT_EXTENSIONS`、`useCodeHighlighter` | 代码预览与 diff review 共用的惰性逐行 token 高亮。文件名 grammar 选择再导出自 `@deepseek-ai/dsh-util-code-language`,即 read 卡片持久化短 id `lang` 提示背后的同一张扩展名表。 |
|
|
69
73
|
|
|
70
74
|
有四组容易混淆:
|
|
71
75
|
|
|
@@ -78,7 +82,7 @@ kind: "package-library"
|
|
|
78
82
|
|
|
79
83
|
### 控件与图标
|
|
80
84
|
|
|
81
|
-
上面的目录说明每个导出的用途;本节讲 props 本身看不出来的行为。产品图标名称不含画板尺寸,以 `Regular` 表示原始 1px 图形,以 `Medium` 表示同一几何的 1.3px 描边;`size` prop 控制渲染尺寸([决定](../../../.agents/notes/implemented/architecture/2026-09-16-size-neutral-product-icon-weights.zh.md))。每个产品、引用、链接与权限图形都会有意保留两种线重导出,即使当前产品只使用其中一种,也让调用方无需再次扩展 API 就能选择强调程度;仅填充的成对图形外观相同。`IconWarningOutlineRegular`/`Medium` 使用圆形;`IconWarningTriangleOutlineRegular`/`Medium` 使用圆角三角形。`FishLogo` 与 `BrandWordmark` 填充品牌 slot。`FileTypeIcon` 渲染传统的 28px spreadsheet、folder、HTML、image、Markdown、generic、PDF、PPT、video 与 Word 图形,并为现有 48 个代码和配置类别使用导入的方形技术图形。该导入只替换图形:资源包中额外的类别不会扩展 `CodeFileType`。`classifyFileType` 按完整文件名、前缀、后缀、可选项目上下文、扩展名的顺序匹配;React 文件名优先于 TypeScript/JavaScript,Angular 后缀优先于基础扩展名,只有传入的项目文件包含带 `flutter:` 的 `pubspec.yaml` 时 Dart 文件才使用 Flutter。Markdown 与 SVG 仍分别使用传统 Markdown 与图片图形。表格映射包括 CSV、TSV、Excel 工作簿与模板、OpenDocument 表格和 Numbers;KEY 映射为幻灯片,RTF/ODT/Pages 映射为文档。`fileExtension` 为相邻元数据 label 暴露同一套 basename
|
|
85
|
+
上面的目录说明每个导出的用途;本节讲 props 本身看不出来的行为。产品图标名称不含画板尺寸,以 `Regular` 表示原始 1px 图形,以 `Medium` 表示同一几何的 1.3px 描边;`size` prop 控制渲染尺寸([决定](../../../.agents/notes/implemented/architecture/2026-09-16-size-neutral-product-icon-weights.zh.md))。每个产品、引用、链接与权限图形都会有意保留两种线重导出,即使当前产品只使用其中一种,也让调用方无需再次扩展 API 就能选择强调程度;仅填充的成对图形外观相同。`IconWarningOutlineRegular`/`Medium` 使用圆形;`IconWarningTriangleOutlineRegular`/`Medium` 使用圆角三角形。`FishLogo` 与 `BrandWordmark` 填充品牌 slot。`FileTypeIcon` 渲染传统的 28px spreadsheet、folder、HTML、image、Markdown、generic、PDF、PPT、video 与 Word 图形,并为现有 48 个代码和配置类别使用导入的方形技术图形。该导入只替换图形:资源包中额外的类别不会扩展 `CodeFileType`。`classifyFileType` 按完整文件名、前缀、后缀、可选项目上下文、扩展名的顺序匹配;React 文件名优先于 TypeScript/JavaScript,Angular 后缀优先于基础扩展名,只有传入的项目文件包含带 `flutter:` 的 `pubspec.yaml` 时 Dart 文件才使用 Flutter。Markdown 与 SVG 仍分别使用传统 Markdown 与图片图形。表格映射包括 CSV、TSV、Excel 工作簿与模板、OpenDocument 表格和 Numbers;KEY 映射为幻灯片,RTF/ODT/Pages 映射为文档。`fileExtension` 为相邻元数据 label 暴露同一套 basename 与最终点号解析。传统图形使用实色分类底板、白色标记和半透明白色折角;通用代码图形使用尖括号与斜线,保留原图 1.35px 描边;通用文件使用灰色底板与较深灰色折角。调用方可通过 `--dsh-file-type-icon-color` 覆盖底板颜色。全彩技术图形是明确例外,会保留其内嵌调色板。所有图形都是装饰性的,不自带 label。`LinkIconMedium` 是可点击产物链接的前置图形——地球、文件夹、代码、图片、文档或纸张,`url` 链接的 `href` 指向已知站点时则改用该站点自己的标记——转写内容常引用的开发者站点(GitHub、GitLab、npm、PyPI、Stack Overflow、MDN、Wikipedia、Hacker News、YouTube、X、Bilibili、知乎、掘金、CSDN),以及主流搜索、视频、社交、购物与参考资料站点(Google、百度、DuckDuckGo、TikTok、Netflix、Spotify、Facebook、Instagram、Reddit、Telegram、WhatsApp、微信、QQ、微博、淘宝、速卖通、eBay、Quora、V2EX、Apple)——`classifyLinkPath` 把共享文件类型折叠进原有六类词汇。`ConnectionIndicator` 可渲染警告色的断联操作(常驻重试图形指明重试动作,断联文案由持有方提供)、与文案使用相同警告色的共享 ongoing loading 加一至三个点以独立于 retry 时序的 500ms 节奏推进的连接中状态,或成功色的恢复状态。点击任一警告状态都会请求立即重连;没有任何悬停交互会改变文案。药丸出现时淡入、卸载前淡出 150ms,宽度随当前 label 自适应。它的持有方提供可见性、恢复驻留时间、本地化 label 与立即重连回调;该原语不使用原生 title tooltip。`useAnchoredPosition` 与 `useAnchoredMaxHeight` 让浮动面板与底部锚定浮层始终钳制在视口内并跟随锚点;`useAnchoredMaxHeight` 与 portal 模式的 `Menu` 会把 12px 的视口顶部边距加宽到框架发布的 `--dsh-frame-top-clearance`。`HoverCard` 通过指针离开宽限期让采用 portal 的预览在跨过锚点间隙时仍可触及,并可通过 `copyText` prop 提供复制按钮。其 `preview` 变体使用 anchor 或 `widthAnchorRef` 元素的宽度减去 48px,左右各内缩 24px,并在视口内放置于行的上方或下方。浮层避开框架顶部保留区,高度最多 420px,会跟随内容及 anchor 尺寸变化;Escape 或通过鼠标和键盘激活锚点可将其关闭。整个浮层的淡入和淡出各持续 100ms;关闭中的浮层停止接收指针输入,淡出后卸载。在淡出期间移回锚点可恢复显示。减少动态效果偏好会禁用过渡动画。只有启用复制时才必须提供复制标签。锚点中嵌套的 `Tooltip` 在显示悬停或焦点标签时隐藏该预览;嵌套提示释放、禁用或卸载后,仍处于打开状态的预览会恢复。`Toast` 使用调用方的 `holdMs` 同时控制淡出延迟与停留加淡出的总时长。`holdMs` 未变时,父组件重渲染不会重启该生命周期;完成时调用最新回调,完全淡出的操作不能接收输入。新的组件 key 会重新开始横幅周期。`rankByName` 是 `/` 菜单命令源与 skill(技能)源共享的候选排序器:查询必须是名字的不区分大小写的有序子序列;前缀命中排最前,其次按对齐分数,再按来源顺序。 portal 模式的 `Menu` 列表会在拖动和 CSS 变换期间跟随锚点,并在关闭时停止跟踪。`Menu.autoFocus` 聚焦首个启用项,支持上下方向键与 Home/End 导航,并在 Escape 时聚焦 anchor 内的第一个按钮;操作菜单可显式启用。
|
|
82
86
|
|
|
83
87
|
`Tooltip` 在悬停或键盘聚焦时读取锚点位置,再根据 `ResizeObserver` 提供的边框盒尺寸调整气泡。首次定位前气泡保持隐藏;横向移入视口留白,仅在另一侧容得下时上下翻转。标签尺寸与视口变化复用锚点坐标;定位不会同步测量气泡,也不会触发 React 渲染。
|
|
84
88
|
|
|
@@ -86,7 +90,7 @@ kind: "package-library"
|
|
|
86
90
|
|
|
87
91
|
最近的 `MarkdownDelegateProvider` 提供可选的 `openExternalLink` 和 `openFile` 导航回调。嵌套 Provider 替换外层能力,回调变化无需重新构建 Markdown 即可到达已渲染链接。其 `openFile` 使本地 Markdown 链接在落定后可点击。绝对路径和工作区相对路径支持百分号转义以及 `#L24` / `#L24-L30` 片段;范围定位到起始行。文件名中的字面 `?` 和 `#` 必须百分号编码。悬停提示使用解码后的路径,并在标签为空时提供可访问名称。回调接收解码后的路径和可选行号,渲染器保留标签并显示文件图标。不传回调时,本地链接仍为文本。URL 协议、查询串、不支持的片段及格式错误的目标不会传给文件打开器。
|
|
88
92
|
|
|
89
|
-
`MarkdownText` 渲染不可信的 GFM 与 TeX 公式、阻止不安全的链接与图片,并可把已解析的文件提及转换为显式控件。外层 `MarkdownDelegateProvider` 会接收普通点击产生的已净化 HTTP(S) URL;带修饰键的点击和 Provider 外的链接保留原生外部 anchor 行为。当 owner 传入 `pathImages` 词表时,本地媒体路径的图片目标只在落定渲染阶段重写为可展示 URL(与 file mentions 相同的流式门);不传词表时本地目标保持惰性 alt
|
|
93
|
+
`MarkdownText` 渲染不可信的 GFM 与 TeX 公式、阻止不安全的链接与图片,并可把已解析的文件提及转换为显式控件。外层 `MarkdownDelegateProvider` 会接收普通点击产生的已净化 HTTP(S) URL;带修饰键的点击和 Provider 外的链接保留原生外部 anchor 行为。当 owner 传入 `pathImages` 词表时,本地媒体路径的图片目标只在落定渲染阶段重写为可展示 URL(与 file mentions 相同的流式门);不传词表时本地目标保持惰性 alt 文本。重写后的图片支持 HTTP(S)、data、blob 及桌面端 `dsh-app://app/api/file` 路由;正文直接书写的桌面端 URL 仍保持惰性。加载或解码失败后,图片替换为作者的 alt 文本;alt 为空时显示原始目标路径;`fileImages` 还提供本地化的失败提示前缀。图片源变化后可重新加载。回复流式输出时,它冻结已完成的块、按已完成行推进顶层未闭合 fence,并从保存的 Shiki grammar state 为该 fence 增量高亮。已完成的 token 行进入固定大小的 React 分组,后续分片只 reconcile 正在增长的分组;最终全量解析解决跨文档语法时,未变化的 fence 会保留该 DOM。`TerminalBlock`、`ReadBlock`、`DiffBlock`、`SearchBlock` 与 `WebBlock` 把对应的工具结果意图渲染为带复制控件、溢出处理及适用时 ANSI 处理的卡片。`JsonTree` 与 `JsonBlock` 以只读方式检查 JSON 值;`projectUserText` 把已发送的用户文本投影为行内普通文本段与引用 chip,供消息气泡和排队行使用。引用名称继承调用方的换行规则:长名称在气泡内换行,排队预览保持单行布局。传入 `UserTextReferences` 时,文件和 skill 引用成为支持键盘操作的预览按钮,复用正文文件链接的悬停和聚焦样式;第一次指针点击可以打开预览,后续点击和已有选区保留原生选择行为。键盘激活在存在选区时仍可打开预览。
|
|
90
94
|
|
|
91
95
|
`MarkdownText` 默认为 `variant="body"`。次级内容使用 `variant="compact"`:其 13px 字号与 20px 行高跟随内容字号设置,各级标题保持同一字号并使用 600 字重,段落与列表采用更紧凑的间距。正文、链接和代码均保持 tertiary 颜色,以点状下划线区分链接。代码标题栏随代码块滚动。表格和公式仍然启用,使用周围文字的字号,并在可用宽度内横向滚动。两个变体共享解析器与流式缓存。
|
|
92
96
|
|
|
@@ -107,7 +111,11 @@ kind: "package-library"
|
|
|
107
111
|
<a id="understand-the-implementation"></a>
|
|
108
112
|
## 理解实现
|
|
109
113
|
|
|
110
|
-
|
|
114
|
+
`Button` 的 `md` 使用 H36/R12,`sm` 使用 H28/R8,包含描边控件。菜单与卡片遵循[共享圆角规则](../../../docs/web-styling.zh.md#corner-radii-and-settings-cards);功能样式保留控件几何。
|
|
115
|
+
|
|
116
|
+
`Menu.listClassName` 独立控制菜单卡片样式,不影响入口容器,也适用于 portal 模式。前置图标使用 `--dsw-alias-menu-icon` 文本色;破坏性操作图标保留错误色。
|
|
117
|
+
|
|
118
|
+
`Menu` 将卡片材质交给 `MenuSurface`,自定义菜单也使用该组件。`MenuSurface` 转发 div 属性和 ref,采用透明填充及模糊,`compact` 使用较小圆角。默认相对定位使材质层限制在容器内;调用方的类可以设置 fixed 或 absolute 定位。macOS 上,不接收交互的底层通过 CSS 锚点跟随卡片,并随卡片卸载;该底层要求 Web 外壳隔离 body 的层叠上下文。功能类控制布局和层级,组件负责材质和外圆角([菜单规则](../../../docs/web-styling.zh.md#component-rules))。 模态遮罩保留黑色半透明填充,不模糊背景。
|
|
111
119
|
|
|
112
120
|
<details>
|
|
113
121
|
<summary>实现细节——点击展开</summary>
|
|
@@ -125,10 +133,16 @@ Menu.listClassName 独立控制菜单卡片样式,不影响入口容器,也
|
|
|
125
133
|
| [`src/SearchBlock.tsx`](src/SearchBlock.tsx) / [`src/WebBlock.tsx`](src/WebBlock.tsx) | 搜索与网页检索卡片 |
|
|
126
134
|
| [`src/icons/`](src/icons/) | 与尺寸无关的 `Regular` 和 `Medium` 产品图标组件 |
|
|
127
135
|
| [`src/code-highlighting.ts`](src/code-highlighting.ts) | 共享的文件名 grammar 选择与惰性逐行高亮 |
|
|
136
|
+
| [`src/input-modality.ts`](src/input-modality.ts) | 全文档输入模态,发布到 `<html>` |
|
|
128
137
|
| [`src/plugin-artwork.tsx`](src/plugin-artwork.tsx) | 固定配色插件插画,SVG def id 按实例生成 |
|
|
129
138
|
| [`src/useAnchoredPosition.ts`](src/useAnchoredPosition.ts) / [`src/useAnchoredMaxHeight.ts`](src/useAnchoredMaxHeight.ts) | 浮动面板与浮层几何钩子 |
|
|
130
139
|
| [`src/settings-form/`](src/settings-form/) | 设置页套件:基于设置 scope 的暂存表单模型、值字段与密文字段、表单框架 |
|
|
131
140
|
|
|
141
|
+
<a id="input-modality"></a>
|
|
142
|
+
### 输入模态
|
|
143
|
+
|
|
144
|
+
[`input-modality.ts`](src/input-modality.ts) 为 tooltip 跟踪输入,并在 `<html>` 上发布 `data-input-modality`,供[主题焦点样式](../ui-theme/README.zh.md#understand-the-implementation)使用。`pointerModality()` 在指针输入后为 true,在任意按键后为 false,包括 IME(输入法)组合输入按键;`Tooltip` 据此决定聚焦时是否可以显示气泡。发布的属性保持 `pointer`,直到非组合输入的导航键(Tab、方向键、Home/End、PageUp/PageDown)到达,或非组合输入按键之后焦点移到不同控件。重新聚焦同一控件不会恢复键盘模态。指针输入、IME 组合输入按键和 window blur 都会清除待处理按键;没有待处理按键的焦点变化不改变模态。监听器与文档同寿命;从 Node 导入时不安装监听器。 焦点变化规则只观察传到 window 的事件;shadow root 内部不向外暴露这些事件的额外导航由组件负责。
|
|
145
|
+
|
|
132
146
|
### 流式 Markdown
|
|
133
147
|
|
|
134
148
|
回复流式输出期间,`MarkdownText` 增量解析:除末尾两个块外全部冻结为缓存的 React 元素,每个分片只重新解析其后的源文本尾部,因此每分片的工作量跟随尾部而非整个回复。末尾的顶层未闭合 fence 会保留已解析的 code node,只把最后一个已完成行与当前未完成行交给同一套 GFM grammar;闭合 fence 或有歧义的解析会回到普通尾部路径。高亮同样从保存的 Shiki grammar state 续接,并只发布新完成行与可变尾部。`CodeBlock` 把已完成行封入固定大小的 React 分组、复用更早的分组,并在代码与语言未变化时跨定稿保留整棵高亮树。定稿时的全量解析仍会解析跨过冻结边界的引用。
|
package/lib/Button.module.css
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* h36, pad 14/7, gap 4, r18; the wide New Session form is r24 at h38 —
|
|
3
|
-
* owners with the wide form set their own radius/width). */
|
|
1
|
+
/* Button sizes share the global control-radius scale. */
|
|
4
2
|
.button {
|
|
3
|
+
box-sizing: border-box;
|
|
5
4
|
display: inline-flex;
|
|
6
5
|
align-items: center;
|
|
7
6
|
justify-content: center;
|
|
8
7
|
gap: 4px;
|
|
9
8
|
border: none;
|
|
10
|
-
border-radius:
|
|
9
|
+
border-radius: var(--dsw-radius-md);
|
|
11
10
|
cursor: pointer;
|
|
12
11
|
font-size: 14px;
|
|
13
12
|
line-height: 22px;
|
|
@@ -25,14 +24,13 @@
|
|
|
25
24
|
height: 36px;
|
|
26
25
|
}
|
|
27
26
|
|
|
28
|
-
/* Compact
|
|
29
|
-
* 28x28 is the icon-only form) — geometry is ours. */
|
|
27
|
+
/* Compact controls retain their dimensions across filled and outlined variants. */
|
|
30
28
|
.sm {
|
|
31
29
|
height: 28px;
|
|
32
30
|
font-size: 12px;
|
|
33
31
|
line-height: 18px;
|
|
34
32
|
padding: 0 10px;
|
|
35
|
-
border-radius:
|
|
33
|
+
border-radius: var(--dsw-radius-sm);
|
|
36
34
|
}
|
|
37
35
|
|
|
38
36
|
.primary {
|
|
@@ -52,7 +50,7 @@
|
|
|
52
50
|
background: var(--dsw-alias-interactive-bg-active);
|
|
53
51
|
}
|
|
54
52
|
|
|
55
|
-
/*
|
|
53
|
+
/* Outlined actions use the same neutral stroke as settings links. */
|
|
56
54
|
.outline {
|
|
57
55
|
border: 0.5px solid var(--dsw-alias-border-l3);
|
|
58
56
|
background: transparent;
|
package/lib/Checkbox.module.css
CHANGED
package/lib/CodeCard.module.css
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
margin: 16px 0;
|
|
4
4
|
color: var(--dsw-alias-label-primary);
|
|
5
5
|
background: var(--dsw-alias-markdown-code-block);
|
|
6
|
-
border-radius:
|
|
6
|
+
border-radius: var(--dsw-radius-lg);
|
|
7
7
|
font: var(--dsw-font-markdown-code-block);
|
|
8
8
|
}
|
|
9
9
|
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
height: 24px;
|
|
62
62
|
padding: 0;
|
|
63
63
|
border: 0;
|
|
64
|
-
border-radius:
|
|
64
|
+
border-radius: var(--dsw-radius-sm);
|
|
65
65
|
color: var(--dsw-alias-label-secondary);
|
|
66
66
|
background: transparent;
|
|
67
67
|
cursor: pointer;
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
.action:focus-visible {
|
|
75
|
-
outline: 1px solid var(--dsw-alias-state-business-primary);
|
|
75
|
+
outline: 1px solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));
|
|
76
76
|
outline-offset: 2px;
|
|
77
77
|
}
|
|
78
78
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
padding: 0 8px;
|
|
9
9
|
box-sizing: border-box;
|
|
10
10
|
border: 1px solid transparent;
|
|
11
|
-
border-radius:
|
|
11
|
+
border-radius: var(--dsw-radius-sm);
|
|
12
12
|
font-family: inherit;
|
|
13
13
|
font-size: 12px;
|
|
14
14
|
font-weight: 500;
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
.warning:focus-visible {
|
|
59
|
-
outline:
|
|
59
|
+
outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));
|
|
60
60
|
outline-offset: 2px;
|
|
61
61
|
}
|
|
62
62
|
|
|
@@ -73,6 +73,10 @@
|
|
|
73
73
|
height: 14px;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
.icon > svg {
|
|
77
|
+
color: inherit;
|
|
78
|
+
}
|
|
79
|
+
|
|
76
80
|
.label {
|
|
77
81
|
text-align: left;
|
|
78
82
|
}
|
package/lib/HoverCard.module.css
CHANGED
|
@@ -5,11 +5,7 @@
|
|
|
5
5
|
display: block;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
/*
|
|
9
|
-
* menu card's elevation. Surface is #2C2C2E in both themes (figma value,
|
|
10
|
-
* light/dark identical), so a component-level variable, not a theme token.
|
|
11
|
-
* Hit-testable on purpose: resting the pointer on the card holds it open
|
|
12
|
-
* (HoverCard's grace close), which a `pointer-events: none` card cannot do. */
|
|
8
|
+
/* The preview remains hit-testable so pointer entry cancels the grace close. */
|
|
13
9
|
.card {
|
|
14
10
|
--dsw-hovercard-bg: #2C2C2E;
|
|
15
11
|
position: fixed;
|
|
@@ -17,7 +13,7 @@
|
|
|
17
13
|
box-sizing: border-box;
|
|
18
14
|
width: 244px;
|
|
19
15
|
padding: 12px 16px;
|
|
20
|
-
border-radius:
|
|
16
|
+
border-radius: var(--dsw-radius-lg);
|
|
21
17
|
background: var(--dsw-hovercard-bg);
|
|
22
18
|
box-shadow: var(--dsw-shadow-lv3);
|
|
23
19
|
}
|
|
@@ -27,7 +23,7 @@
|
|
|
27
23
|
}
|
|
28
24
|
|
|
29
25
|
.copyable:focus-visible {
|
|
30
|
-
outline:
|
|
26
|
+
outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));
|
|
31
27
|
outline-offset: 2px;
|
|
32
28
|
}
|
|
33
29
|
|
|
@@ -59,7 +55,7 @@
|
|
|
59
55
|
padding: 0;
|
|
60
56
|
overflow: hidden;
|
|
61
57
|
border: 0;
|
|
62
|
-
border-radius:
|
|
58
|
+
border-radius: var(--dsw-radius-lg);
|
|
63
59
|
background: var(--dsw-alias-bg-layer-1);
|
|
64
60
|
color: var(--dsw-alias-label-primary);
|
|
65
61
|
box-shadow: var(--dsw-elevation-panel);
|
|
@@ -7,9 +7,7 @@
|
|
|
7
7
|
padding: 40px;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
/*
|
|
11
|
-
layer, not a background on .backdrop: backdrop-filter there would blur the
|
|
12
|
-
previewed image and the close control along with the page. */
|
|
10
|
+
/* Shared dimming mask below the preview and its controls. */
|
|
13
11
|
.mask {
|
|
14
12
|
position: absolute;
|
|
15
13
|
inset: 0;
|
|
@@ -22,7 +20,7 @@
|
|
|
22
20
|
max-width: min(100%, 1600px);
|
|
23
21
|
max-height: calc(100vh - 80px);
|
|
24
22
|
object-fit: contain;
|
|
25
|
-
border-radius:
|
|
23
|
+
border-radius: var(--dsw-radius-lg);
|
|
26
24
|
background: var(--dsw-specific-input-major);
|
|
27
25
|
border: 0;
|
|
28
26
|
box-shadow: var(--dsw-elevation-prominent);
|
|
@@ -45,4 +43,4 @@
|
|
|
45
43
|
cursor: pointer;
|
|
46
44
|
}
|
|
47
45
|
|
|
48
|
-
.close:focus-visible { outline:
|
|
46
|
+
.close:focus-visible { outline: var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary)); outline-offset: 3px; }
|
package/lib/Input.module.css
CHANGED
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
height: 32px;
|
|
6
6
|
padding: 0 8px;
|
|
7
7
|
border: 0.5px solid var(--dsw-alias-border-l4);
|
|
8
|
-
border-radius:
|
|
8
|
+
border-radius: var(--dsw-radius-md);
|
|
9
9
|
background: var(--dsw-alias-bg-layer-1);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
.wrap:focus-within {
|
|
13
|
-
border-color: var(--dsw-alias-
|
|
13
|
+
border-color: var(--dsw-alias-state-business-primary);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
.icon {
|
package/lib/JsonTree.module.css
CHANGED
|
@@ -187,7 +187,7 @@
|
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
.stringToggle:focus-visible {
|
|
190
|
-
outline: 1px solid var(--dsw-alias-state-business-primary);
|
|
190
|
+
outline: 1px solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));
|
|
191
191
|
outline-offset: 1px;
|
|
192
192
|
}
|
|
193
193
|
|
|
@@ -271,7 +271,7 @@
|
|
|
271
271
|
margin: 0;
|
|
272
272
|
padding: 0;
|
|
273
273
|
border: 0;
|
|
274
|
-
border-radius:
|
|
274
|
+
border-radius: var(--dsw-radius-xs);
|
|
275
275
|
color: var(--dsw-alias-label-secondary);
|
|
276
276
|
background: transparent;
|
|
277
277
|
cursor: pointer;
|
|
@@ -286,7 +286,7 @@
|
|
|
286
286
|
}
|
|
287
287
|
|
|
288
288
|
.actionButton:focus-visible {
|
|
289
|
-
outline: 1px solid var(--dsw-alias-state-business-primary);
|
|
289
|
+
outline: 1px solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));
|
|
290
290
|
outline-offset: -1px;
|
|
291
291
|
}
|
|
292
292
|
|