@robota-sdk/agent-ui-terminal 3.0.0-beta.81
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 +661 -0
- package/README.md +27 -0
- package/dist/node/index.cjs +32 -0
- package/dist/node/index.d.cts +1066 -0
- package/dist/node/index.d.cts.map +1 -0
- package/dist/node/index.d.ts +1066 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +33 -0
- package/dist/node/index.js.map +1 -0
- package/package.json +93 -0
- package/src/App.tsx +146 -0
- package/src/AppPresentation.tsx +190 -0
- package/src/AppView.tsx +26 -0
- package/src/BackgroundTaskPanel.tsx +73 -0
- package/src/CjkTextInput.tsx +336 -0
- package/src/ConfirmPrompt.tsx +140 -0
- package/src/ContextWarningBanner.tsx +45 -0
- package/src/ExecutionWorkspaceDetailPane.tsx +85 -0
- package/src/ExecutionWorkspaceSwitcher.tsx +213 -0
- package/src/HistorySearchOverlay.tsx +150 -0
- package/src/InputArea.tsx +338 -0
- package/src/ListPicker.tsx +158 -0
- package/src/MenuSelect.tsx +159 -0
- package/src/MessageList.tsx +308 -0
- package/src/MultiSelectList.tsx +294 -0
- package/src/PendingActionPrompt.tsx +121 -0
- package/src/PermissionPrompt.tsx +186 -0
- package/src/PluginTUI.tsx +258 -0
- package/src/RoleLabel.tsx +72 -0
- package/src/SafeText.tsx +66 -0
- package/src/SessionEventNotices.tsx +27 -0
- package/src/SessionPicker.tsx +72 -0
- package/src/SessionStatusBar.tsx +76 -0
- package/src/SlashAutocomplete.tsx +154 -0
- package/src/StatusBar.tsx +284 -0
- package/src/StreamingIndicator.tsx +145 -0
- package/src/SupervisedSessionView.tsx +455 -0
- package/src/TextPrompt.tsx +112 -0
- package/src/ThemePicker.tsx +366 -0
- package/src/ToolCommandOutput.tsx +48 -0
- package/src/ToolDiffBlock.tsx +44 -0
- package/src/TransportTUI.tsx +166 -0
- package/src/TuiInteractionChannel.ts +567 -0
- package/src/UpdateNotice.tsx +18 -0
- package/src/UsageSummaryEntry.tsx +49 -0
- package/src/WaveText.tsx +70 -0
- package/src/__tests__/ListPicker.test.tsx +188 -0
- package/src/__tests__/MenuSelect.test.tsx +127 -0
- package/src/__tests__/PendingActionPrompt.test.tsx +193 -0
- package/src/__tests__/PluginTUI.test.tsx +167 -0
- package/src/__tests__/SlashAutocomplete.test.tsx +162 -0
- package/src/__tests__/SupervisedSessionView-render.test.tsx +24 -0
- package/src/__tests__/SupervisedSessionView.test.tsx +629 -0
- package/src/__tests__/TextPrompt.test.tsx +112 -0
- package/src/__tests__/TransportTUI.test.tsx +124 -0
- package/src/__tests__/TuiInteractionChannel.askUser.test.ts +124 -0
- package/src/__tests__/TuiInteractionChannel.display-contract.test.ts +284 -0
- package/src/__tests__/TuiInteractionChannel.lifecycle.test.ts +642 -0
- package/src/__tests__/UpdateNotice.test.tsx +15 -0
- package/src/__tests__/abort-after-permission.test.tsx +160 -0
- package/src/__tests__/abort-streaming-e2e.test.tsx +183 -0
- package/src/__tests__/attention-bell-and-marks.test.ts +144 -0
- package/src/__tests__/autocomplete-editing-keys.test.tsx +127 -0
- package/src/__tests__/background-task-panel-countdown.test.tsx +75 -0
- package/src/__tests__/background-task-panel.test.tsx +98 -0
- package/src/__tests__/background-task-row-format.test.ts +152 -0
- package/src/__tests__/channel-factory-integration.test.ts +150 -0
- package/src/__tests__/cjk-defer-submit.test.tsx +67 -0
- package/src/__tests__/cjk-fallback-render.test.tsx +78 -0
- package/src/__tests__/cjk-text-input-flow.test.ts +186 -0
- package/src/__tests__/cjk-text-input.test.ts +191 -0
- package/src/__tests__/command-handoff-pty-e2e.test.ts +107 -0
- package/src/__tests__/command-output-summary.test.ts +95 -0
- package/src/__tests__/command-result-handler.test.ts +171 -0
- package/src/__tests__/compact-event-bridge.test.ts +20 -0
- package/src/__tests__/confirm-permission-flow.test.ts +149 -0
- package/src/__tests__/confirm-prompt.test.tsx +103 -0
- package/src/__tests__/contextual-keybindings-input.test.tsx +158 -0
- package/src/__tests__/default-tui-cli-adapter-host-settings.test.ts +45 -0
- package/src/__tests__/effort-reaches-the-session.test.ts +41 -0
- package/src/__tests__/execution-workspace-switcher.test.tsx +147 -0
- package/src/__tests__/execution-workspace-view-model.test.ts +94 -0
- package/src/__tests__/external-prompt-notice.test.tsx +100 -0
- package/src/__tests__/fixtures/command-handoff-driver.tsx +111 -0
- package/src/__tests__/fixtures/fake-editor.sh +7 -0
- package/src/__tests__/fixtures/terminal-handoff-driver.tsx +90 -0
- package/src/__tests__/fork-attach.test.tsx +235 -0
- package/src/__tests__/helpers/terminal-profiles.ts +152 -0
- package/src/__tests__/helpers/vt-cursor-interpreter.ts +175 -0
- package/src/__tests__/history-clear-broadcast.test.ts +66 -0
- package/src/__tests__/history-search-overlay.test.tsx +329 -0
- package/src/__tests__/humanize-tool-name.test.ts +30 -0
- package/src/__tests__/input-area-bottom-border.test.tsx +45 -0
- package/src/__tests__/input-area-flow.test.ts +199 -0
- package/src/__tests__/input-area-focus-handoff.test.tsx +77 -0
- package/src/__tests__/interaction-channel-charter.test.ts +13 -0
- package/src/__tests__/key-hint-consistency.test.tsx +249 -0
- package/src/__tests__/key-hint-footer.test.tsx +69 -0
- package/src/__tests__/live-trace-reaches-session.test.ts +14 -0
- package/src/__tests__/message-list-rendering.test.tsx +473 -0
- package/src/__tests__/org-policy-reaches-the-channel.test.ts +77 -0
- package/src/__tests__/output-style-reaches-session.test.ts +35 -0
- package/src/__tests__/palette-consistency.test.ts +330 -0
- package/src/__tests__/permission-prompt-availability.test.tsx +23 -0
- package/src/__tests__/permission-prompt-peer.test.tsx +34 -0
- package/src/__tests__/product-display-name.test.tsx +79 -0
- package/src/__tests__/prompt-queue.test.tsx +260 -0
- package/src/__tests__/provider-definitions-reach-the-session.test.ts +75 -0
- package/src/__tests__/pty/ask-user-question.ptytest.ts +72 -0
- package/src/__tests__/pty/background-work-switcher.ptytest.ts +53 -0
- package/src/__tests__/pty/behavior-2437-git.ptytest.ts +342 -0
- package/src/__tests__/pty/fixtures/ask-user-question.jsonl +3 -0
- package/src/__tests__/pty/fixtures/replay-conversation.jsonl +4 -0
- package/src/__tests__/pty/fixtures/screen-005-ask-shapes.jsonl +5 -0
- package/src/__tests__/pty/fixtures/screen-005-permission.jsonl +3 -0
- package/src/__tests__/pty/fixtures/screen-006-colors.jsonl +3 -0
- package/src/__tests__/pty/fixtures/screen-2002-themes.jsonl +5 -0
- package/src/__tests__/pty/flag-tui.ptytest.ts +56 -0
- package/src/__tests__/pty/flow-2006-deep-link.ptytest.ts +291 -0
- package/src/__tests__/pty/ime-cursor-tmux.ptytest.ts +165 -0
- package/src/__tests__/pty/ime-cursor.ptytest.ts +186 -0
- package/src/__tests__/pty/isolated-home.ts +64 -0
- package/src/__tests__/pty/org-policy.ptytest.ts +71 -0
- package/src/__tests__/pty/provider-setup.ptytest.ts +61 -0
- package/src/__tests__/pty/pty-driver.ts +153 -0
- package/src/__tests__/pty/replay-conversation.ptytest.ts +68 -0
- package/src/__tests__/pty/screen-005-prompt-footers.ptytest.ts +134 -0
- package/src/__tests__/pty/screen-006-no-color.ptytest.ts +138 -0
- package/src/__tests__/pty/screen-010-scrollback.ptytest.ts +123 -0
- package/src/__tests__/pty/screen-1993-scrollback.ptytest.ts +124 -0
- package/src/__tests__/pty/screen-2002-themes.ptytest.ts +292 -0
- package/src/__tests__/pty/screen-2670-prepark.ptytest.ts +228 -0
- package/src/__tests__/pty/screen-reader-mode.ptytest.ts +228 -0
- package/src/__tests__/pty/settings-screen.ptytest.ts +49 -0
- package/src/__tests__/pty/spawn-pty.test.ts +120 -0
- package/src/__tests__/pty/spawn-pty.ts +229 -0
- package/src/__tests__/pty/terminal-handoff.ptytest.ts +79 -0
- package/src/__tests__/pty/tui-pty.ptytest.ts +65 -0
- package/src/__tests__/pty-driver-dispose.test.ts +43 -0
- package/src/__tests__/real-cursor-positioning.test.tsx +263 -0
- package/src/__tests__/rename-broadcast-persistence.test.tsx +140 -0
- package/src/__tests__/render-channel-options.test.ts +201 -0
- package/src/__tests__/render-lifecycle.test.ts +36 -0
- package/src/__tests__/render-markdown.test.ts +164 -0
- package/src/__tests__/rendered-markdown-styling.test.tsx +98 -0
- package/src/__tests__/safe-text-boundary.test.tsx +75 -0
- package/src/__tests__/screen-reader-input.test.tsx +106 -0
- package/src/__tests__/screen-reader-labels.test.ts +91 -0
- package/src/__tests__/screen-reader-menus.test.tsx +200 -0
- package/src/__tests__/screen-reader-pacing.test.ts +230 -0
- package/src/__tests__/screen-reader-prepark-echo.test.tsx +200 -0
- package/src/__tests__/screen-reader-render-options.test.ts +262 -0
- package/src/__tests__/screen-reader-stdout.test.ts +322 -0
- package/src/__tests__/screen-reader-turn-marks-port.test.tsx +85 -0
- package/src/__tests__/screen-reader-turn-signals.test.tsx +125 -0
- package/src/__tests__/sec-019-terminal-sanitizer.test.ts +276 -0
- package/src/__tests__/sec-019-tool-label-render.test.tsx +207 -0
- package/src/__tests__/selection-flow.test.ts +61 -0
- package/src/__tests__/session-event-notices.test.tsx +31 -0
- package/src/__tests__/session-init-poller.test.ts +102 -0
- package/src/__tests__/session-switch-channel.test.tsx +470 -0
- package/src/__tests__/shutdown-signal.test.ts +34 -0
- package/src/__tests__/status-activity.test.ts +71 -0
- package/src/__tests__/status-bar.test.tsx +282 -0
- package/src/__tests__/status-glyph.test.ts +30 -0
- package/src/__tests__/streaming-indicator.test.tsx +177 -0
- package/src/__tests__/terminal-capabilities.test.ts +153 -0
- package/src/__tests__/terminal-handoff-controller.test.ts +173 -0
- package/src/__tests__/terminal-handoff-pty-e2e.test.ts +77 -0
- package/src/__tests__/text-prompt-flow.test.ts +77 -0
- package/src/__tests__/theme-picker.test.tsx +345 -0
- package/src/__tests__/transport-boundary.test.ts +11 -0
- package/src/__tests__/tui-app-channel-port.test.ts +31 -0
- package/src/__tests__/tui-channel-init-failure.test.ts +62 -0
- package/src/__tests__/tui-channel-lifecycle-coordinator.test.ts +258 -0
- package/src/__tests__/tui-interaction-queues.test.ts +139 -0
- package/src/__tests__/tui-preset-capabilities.test.ts +33 -0
- package/src/__tests__/tui-state-manager.error-stall.test.ts +169 -0
- package/src/__tests__/tui-state-manager.test.ts +451 -0
- package/src/__tests__/ui-intent-rendering.test.tsx +291 -0
- package/src/__tests__/wave-text.test.tsx +133 -0
- package/src/app-banner.tsx +42 -0
- package/src/app-static-items.ts +35 -0
- package/src/app-view-model.ts +136 -0
- package/src/attention/__tests__/attention-coordinator.test.ts +166 -0
- package/src/attention/__tests__/attention-tracker.test.ts +103 -0
- package/src/attention/__tests__/countdown.test.ts +15 -0
- package/src/attention/__tests__/focus-input-filter.test.ts +92 -0
- package/src/attention/__tests__/interval-recap.test.ts +49 -0
- package/src/attention/attention-coordinator.ts +71 -0
- package/src/attention/attention-tracker.ts +124 -0
- package/src/attention/countdown.ts +24 -0
- package/src/attention/focus-input-filter.ts +131 -0
- package/src/attention/interval-recap.ts +148 -0
- package/src/attention/time-units.ts +6 -0
- package/src/attention-bell.ts +79 -0
- package/src/attributed-user-echo.ts +30 -0
- package/src/background-task-row-format.ts +99 -0
- package/src/command-interaction.ts +8 -0
- package/src/command-output-summary.ts +129 -0
- package/src/create-default-tui-cli-adapter.ts +45 -0
- package/src/execution-workspace-switcher-selection.ts +118 -0
- package/src/execution-workspace-view-model.ts +139 -0
- package/src/external-prompt-notice.tsx +38 -0
- package/src/flows/__tests__/background-focus-flow.test.ts +48 -0
- package/src/flows/__tests__/defer-submit.test.ts +87 -0
- package/src/flows/__tests__/real-cursor-flow.test.ts +124 -0
- package/src/flows/background-focus-flow.ts +44 -0
- package/src/flows/cjk-cursor-motion.ts +75 -0
- package/src/flows/cjk-text-deletion.ts +53 -0
- package/src/flows/cjk-text-input-flow.ts +248 -0
- package/src/flows/cjk-text-input-types.ts +24 -0
- package/src/flows/confirm-prompt-flow.ts +90 -0
- package/src/flows/defer-submit.ts +67 -0
- package/src/flows/fork-attach-flow.ts +62 -0
- package/src/flows/input-area-flow.ts +188 -0
- package/src/flows/permission-prompt-flow.ts +108 -0
- package/src/flows/real-cursor-flow.ts +83 -0
- package/src/flows/selection-flow.ts +205 -0
- package/src/flows/session-init-poller.ts +76 -0
- package/src/flows/text-prompt-flow.ts +93 -0
- package/src/history-search/__tests__/history-search-flow.test.ts +86 -0
- package/src/history-search/history-search-flow.ts +94 -0
- package/src/history-search/useHistorySearch.ts +336 -0
- package/src/hooks/__tests__/use-app-loop-escape.test.tsx +74 -0
- package/src/hooks/__tests__/use-plugin-callbacks.test.ts +18 -0
- package/src/hooks/__tests__/use-tui-channel-stability.test.tsx +100 -0
- package/src/hooks/__tests__/useAppThemeState.test.tsx +130 -0
- package/src/hooks/command-result-handler.ts +44 -0
- package/src/hooks/side-effects-types.ts +45 -0
- package/src/hooks/useAppController.ts +267 -0
- package/src/hooks/useAppInputBindings.ts +145 -0
- package/src/hooks/useAppInteractionState.ts +81 -0
- package/src/hooks/useAppLifecycleState.ts +81 -0
- package/src/hooks/useAppOverlays.ts +74 -0
- package/src/hooks/useAppScreenState.ts +81 -0
- package/src/hooks/useAppSubmissionState.ts +45 -0
- package/src/hooks/useAppThemeState.ts +157 -0
- package/src/hooks/useAppWorkspaceState.ts +149 -0
- package/src/hooks/useAppearanceSettings.ts +30 -0
- package/src/hooks/useAutocomplete.ts +101 -0
- package/src/hooks/useBackgroundPanel.ts +58 -0
- package/src/hooks/useCountdownTick.ts +24 -0
- package/src/hooks/useExecutionDetailPage.ts +69 -0
- package/src/hooks/useForkAttach.ts +60 -0
- package/src/hooks/useInputAreaHistorySearch.ts +72 -0
- package/src/hooks/useInputAreaKeys.ts +114 -0
- package/src/hooks/useNumberedSelection.ts +76 -0
- package/src/hooks/usePluginCallbacks.ts +38 -0
- package/src/hooks/usePluginScreenData.ts +85 -0
- package/src/hooks/useRealCursorPosition.ts +125 -0
- package/src/hooks/useRenderMarkdown.ts +38 -0
- package/src/hooks/useScreenReaderTurnSignals.ts +113 -0
- package/src/hooks/useSideEffects.ts +144 -0
- package/src/hooks/useStatusLineSettings.ts +50 -0
- package/src/hooks/useTerminalHandoffSuspension.ts +30 -0
- package/src/hooks/useTuiChannel.ts +129 -0
- package/src/humanize-tool-name.ts +51 -0
- package/src/index.ts +56 -0
- package/src/input-area-rules.tsx +71 -0
- package/src/json-value.ts +18 -0
- package/src/key-hint-footer.tsx +54 -0
- package/src/keybindings/__tests__/keybinding-registry.test.ts +228 -0
- package/src/keybindings/__tests__/node-keybindings-source.test.ts +111 -0
- package/src/keybindings/keybinding-catalogue.ts +171 -0
- package/src/keybindings/keybinding-registry.ts +303 -0
- package/src/keybindings/keybinding-syntax.ts +155 -0
- package/src/keybindings/keybindings-context.tsx +124 -0
- package/src/keybindings/node-keybindings-source.ts +189 -0
- package/src/numbered-list.tsx +113 -0
- package/src/plugin-tui-handlers.ts +163 -0
- package/src/product-display-name-context.tsx +30 -0
- package/src/render-markdown.ts +209 -0
- package/src/render.tsx +544 -0
- package/src/sanitize-terminal-text.ts +171 -0
- package/src/screen-reader-announcement.ts +52 -0
- package/src/screen-reader-context.tsx +32 -0
- package/src/screen-reader-labels.ts +64 -0
- package/src/screen-reader-pacing-context.tsx +47 -0
- package/src/screen-reader-pacing.ts +178 -0
- package/src/screen-reader-stdout.ts +328 -0
- package/src/shutdown-signal.ts +34 -0
- package/src/status-activity.ts +70 -0
- package/src/status-glyph.ts +78 -0
- package/src/terminal-capabilities-context.tsx +27 -0
- package/src/terminal-capabilities.ts +77 -0
- package/src/terminal-focus-reporting.ts +50 -0
- package/src/terminal-handoff-controller.ts +98 -0
- package/src/terminal-marks.ts +63 -0
- package/src/theme/__tests__/built-in-themes.test.ts +117 -0
- package/src/theme/__tests__/color-vision.test.ts +82 -0
- package/src/theme/__tests__/theme-context.test.tsx +146 -0
- package/src/theme/__tests__/theme-document.test.ts +222 -0
- package/src/theme/__tests__/theme-registry.test.ts +138 -0
- package/src/theme/__tests__/theme-styles.test.ts +233 -0
- package/src/theme/built-in-themes.ts +342 -0
- package/src/theme/color-vision.ts +171 -0
- package/src/theme/index.ts +45 -0
- package/src/theme/theme-context.tsx +81 -0
- package/src/theme/theme-contracts.ts +139 -0
- package/src/theme/theme-document.ts +278 -0
- package/src/theme/theme-registry.ts +127 -0
- package/src/theme/theme-styles.ts +177 -0
- package/src/tool-summary-status.ts +59 -0
- package/src/tui-app-channel-port.ts +83 -0
- package/src/tui-channel-lifecycle-coordinator.ts +149 -0
- package/src/tui-channel-options.ts +172 -0
- package/src/tui-cli-adapter-context.tsx +13 -0
- package/src/tui-cli-adapter.ts +24 -0
- package/src/tui-interaction-queues.ts +163 -0
- package/src/tui-session-binding.ts +52 -0
- package/src/tui-session-event-projector.ts +140 -0
- package/src/tui-session-events.ts +82 -0
- package/src/tui-session-notice-store.ts +43 -0
- package/src/tui-session-options.ts +118 -0
- package/src/tui-state-manager.ts +327 -0
- package/src/types.ts +19 -0
- package/src/use-terminal-title.ts +29 -0
- package/src/utils/__tests__/edit-diff.test.ts +426 -0
- package/src/utils/__tests__/paste-detection.test.ts +116 -0
- package/src/utils/__tests__/paste-labels.test.ts +46 -0
- package/src/utils/__tests__/tool-call-extractor.test.ts +227 -0
- package/src/utils/__tests__/tool-diff-summary.test.ts +104 -0
- package/src/utils/edit-diff.ts +151 -0
- package/src/utils/input-top-border.ts +19 -0
- package/src/utils/paste-labels.ts +9 -0
- package/src/utils/tool-call-extractor.ts +92 -0
- package/src/utils/tool-diff-summary.ts +75 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SCREEN-2002 — the four built-in themes, as data.
|
|
3
|
+
*
|
|
4
|
+
* `dark` is today's colour, gathered from the four places that used to decide it: `PALETTE`/`MOTION`
|
|
5
|
+
* (Ink), `ANSI` (the markdown diff rows), `marked-terminal`'s own chalk defaults and `cli-highlight`'s
|
|
6
|
+
* `DEFAULT_THEME`. Making those defaults explicit is what lets a theme change them at all; it is also
|
|
7
|
+
* why a user who sets nothing sees the same colours as before.
|
|
8
|
+
*
|
|
9
|
+
* Over the 300-line file ceiling, deliberately. The file is ONE concern — four colour tables of the
|
|
10
|
+
* same shape — and the ceiling exists to break up files that do several things. Splitting per theme
|
|
11
|
+
* would put the dark and light values that must correspond in different files, which is the drift
|
|
12
|
+
* the token model was built to stop.
|
|
13
|
+
*
|
|
14
|
+
* The daltonized pair is specified in hex rather than colour names on purpose: a name is whatever the
|
|
15
|
+
* terminal says it is, so a colour-vision-deficiency simulation cannot compute it, and the guard
|
|
16
|
+
* REFUSES what it cannot simulate. Their status and diff pairs avoid red/green entirely — blue for
|
|
17
|
+
* "good", orange for "bad" — the distinction protanopes and deuteranopes retain.
|
|
18
|
+
*
|
|
19
|
+
* This module is the only file in the package allowed to hold a colour VALUE; the consistency test
|
|
20
|
+
* ratchets on both that import and on direct `chalk.<colour>(` calls elsewhere.
|
|
21
|
+
*/
|
|
22
|
+
import type { ITuiTheme } from './theme-contracts.js';
|
|
23
|
+
|
|
24
|
+
/** Today's values, name for name. */
|
|
25
|
+
export const DARK_THEME: ITuiTheme = {
|
|
26
|
+
id: 'dark',
|
|
27
|
+
name: 'Dark',
|
|
28
|
+
appearance: 'dark',
|
|
29
|
+
source: 'built-in',
|
|
30
|
+
colors: {
|
|
31
|
+
text: {
|
|
32
|
+
accent: 'cyan',
|
|
33
|
+
emphasis: 'white',
|
|
34
|
+
success: 'green',
|
|
35
|
+
warning: 'yellow',
|
|
36
|
+
error: 'red',
|
|
37
|
+
session: 'magenta',
|
|
38
|
+
muted: 'gray',
|
|
39
|
+
onAccent: 'black',
|
|
40
|
+
},
|
|
41
|
+
border: {
|
|
42
|
+
attention: 'yellow',
|
|
43
|
+
focused: 'cyan',
|
|
44
|
+
active: 'green',
|
|
45
|
+
muted: 'gray',
|
|
46
|
+
error: 'red',
|
|
47
|
+
},
|
|
48
|
+
status: {
|
|
49
|
+
running: 'yellow',
|
|
50
|
+
success: 'green',
|
|
51
|
+
error: 'red',
|
|
52
|
+
denied: 'yellowBright',
|
|
53
|
+
waiting: 'yellow',
|
|
54
|
+
cancelled: 'yellow',
|
|
55
|
+
idle: 'gray',
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
// marked-terminal's own defaults, written down so a theme can change them.
|
|
59
|
+
markdown: {
|
|
60
|
+
heading: 'green',
|
|
61
|
+
firstHeading: 'magenta',
|
|
62
|
+
code: 'yellow',
|
|
63
|
+
codespan: 'yellow',
|
|
64
|
+
link: 'blue',
|
|
65
|
+
href: 'blue',
|
|
66
|
+
blockquote: 'gray',
|
|
67
|
+
del: 'gray',
|
|
68
|
+
html: 'gray',
|
|
69
|
+
// The former `ANSI.lightGreen` / `lightRed` / `darkGreenBackground` / `darkRedBackground`.
|
|
70
|
+
diffAdded: 'ansi256(120)',
|
|
71
|
+
diffRemoved: 'ansi256(210)',
|
|
72
|
+
diffAddedBackground: 'ansi256(22)',
|
|
73
|
+
diffRemovedBackground: 'ansi256(52)',
|
|
74
|
+
diffHunk: 'cyan',
|
|
75
|
+
},
|
|
76
|
+
// cli-highlight's DEFAULT_THEME, written down for the same reason.
|
|
77
|
+
syntax: {
|
|
78
|
+
keyword: 'blue',
|
|
79
|
+
built_in: 'cyan',
|
|
80
|
+
type: 'cyan',
|
|
81
|
+
literal: 'blue',
|
|
82
|
+
number: 'green',
|
|
83
|
+
regexp: 'red',
|
|
84
|
+
string: 'red',
|
|
85
|
+
class: 'blue',
|
|
86
|
+
function: 'yellow',
|
|
87
|
+
comment: 'green',
|
|
88
|
+
doctag: 'green',
|
|
89
|
+
meta: 'gray',
|
|
90
|
+
tag: 'gray',
|
|
91
|
+
name: 'blue',
|
|
92
|
+
attr: 'cyan',
|
|
93
|
+
addition: 'green',
|
|
94
|
+
deletion: 'red',
|
|
95
|
+
},
|
|
96
|
+
motion: { wave: ['#555555', '#777777', '#999999', '#bbbbbb'] },
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/** The same roles against a light background: darker foregrounds, light diff backgrounds. */
|
|
100
|
+
export const LIGHT_THEME: ITuiTheme = {
|
|
101
|
+
id: 'light',
|
|
102
|
+
name: 'Light',
|
|
103
|
+
appearance: 'light',
|
|
104
|
+
source: 'built-in',
|
|
105
|
+
colors: {
|
|
106
|
+
text: {
|
|
107
|
+
accent: 'ansi256(24)',
|
|
108
|
+
emphasis: 'black',
|
|
109
|
+
success: 'ansi256(28)',
|
|
110
|
+
warning: 'ansi256(130)',
|
|
111
|
+
error: 'ansi256(124)',
|
|
112
|
+
session: 'ansi256(90)',
|
|
113
|
+
muted: 'ansi256(243)',
|
|
114
|
+
onAccent: 'white',
|
|
115
|
+
},
|
|
116
|
+
border: {
|
|
117
|
+
attention: 'ansi256(130)',
|
|
118
|
+
focused: 'ansi256(24)',
|
|
119
|
+
active: 'ansi256(28)',
|
|
120
|
+
muted: 'ansi256(249)',
|
|
121
|
+
error: 'ansi256(124)',
|
|
122
|
+
},
|
|
123
|
+
status: {
|
|
124
|
+
running: 'ansi256(130)',
|
|
125
|
+
success: 'ansi256(28)',
|
|
126
|
+
error: 'ansi256(124)',
|
|
127
|
+
denied: 'ansi256(166)',
|
|
128
|
+
waiting: 'ansi256(130)',
|
|
129
|
+
cancelled: 'ansi256(130)',
|
|
130
|
+
idle: 'ansi256(243)',
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
markdown: {
|
|
134
|
+
heading: 'ansi256(28)',
|
|
135
|
+
firstHeading: 'ansi256(90)',
|
|
136
|
+
code: 'ansi256(94)',
|
|
137
|
+
codespan: 'ansi256(94)',
|
|
138
|
+
link: 'ansi256(26)',
|
|
139
|
+
href: 'ansi256(26)',
|
|
140
|
+
blockquote: 'ansi256(243)',
|
|
141
|
+
del: 'ansi256(243)',
|
|
142
|
+
html: 'ansi256(243)',
|
|
143
|
+
diffAdded: 'ansi256(22)',
|
|
144
|
+
diffRemoved: 'ansi256(52)',
|
|
145
|
+
diffAddedBackground: 'ansi256(194)',
|
|
146
|
+
diffRemovedBackground: 'ansi256(224)',
|
|
147
|
+
diffHunk: 'ansi256(24)',
|
|
148
|
+
},
|
|
149
|
+
syntax: {
|
|
150
|
+
keyword: 'ansi256(26)',
|
|
151
|
+
built_in: 'ansi256(24)',
|
|
152
|
+
type: 'ansi256(24)',
|
|
153
|
+
literal: 'ansi256(26)',
|
|
154
|
+
number: 'ansi256(28)',
|
|
155
|
+
regexp: 'ansi256(124)',
|
|
156
|
+
string: 'ansi256(124)',
|
|
157
|
+
class: 'ansi256(26)',
|
|
158
|
+
function: 'ansi256(94)',
|
|
159
|
+
comment: 'ansi256(28)',
|
|
160
|
+
doctag: 'ansi256(28)',
|
|
161
|
+
meta: 'ansi256(243)',
|
|
162
|
+
tag: 'ansi256(243)',
|
|
163
|
+
name: 'ansi256(26)',
|
|
164
|
+
attr: 'ansi256(24)',
|
|
165
|
+
addition: 'ansi256(22)',
|
|
166
|
+
deletion: 'ansi256(52)',
|
|
167
|
+
},
|
|
168
|
+
motion: { wave: ['#bbbbbb', '#999999', '#777777', '#555555'] },
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/** Blue "good" / orange "bad": the pair protanopes and deuteranopes keep apart. */
|
|
172
|
+
export const DARK_DALTONIZED_THEME: ITuiTheme = {
|
|
173
|
+
id: 'dark-daltonized',
|
|
174
|
+
name: 'Dark (daltonized)',
|
|
175
|
+
appearance: 'dark',
|
|
176
|
+
source: 'built-in',
|
|
177
|
+
colors: {
|
|
178
|
+
text: {
|
|
179
|
+
accent: '#56b4e9',
|
|
180
|
+
emphasis: '#ffffff',
|
|
181
|
+
success: '#56b4e9',
|
|
182
|
+
warning: '#e69f00',
|
|
183
|
+
error: '#d55e00',
|
|
184
|
+
session: '#cc79a7',
|
|
185
|
+
muted: '#999999',
|
|
186
|
+
onAccent: '#000000',
|
|
187
|
+
},
|
|
188
|
+
border: {
|
|
189
|
+
attention: '#e69f00',
|
|
190
|
+
focused: '#56b4e9',
|
|
191
|
+
active: '#56b4e9',
|
|
192
|
+
muted: '#999999',
|
|
193
|
+
error: '#d55e00',
|
|
194
|
+
},
|
|
195
|
+
status: {
|
|
196
|
+
running: '#e69f00',
|
|
197
|
+
success: '#56b4e9',
|
|
198
|
+
error: '#d55e00',
|
|
199
|
+
denied: '#f0e442',
|
|
200
|
+
waiting: '#e69f00',
|
|
201
|
+
cancelled: '#cc79a7',
|
|
202
|
+
idle: '#999999',
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
markdown: {
|
|
206
|
+
heading: '#56b4e9',
|
|
207
|
+
firstHeading: '#cc79a7',
|
|
208
|
+
code: '#e69f00',
|
|
209
|
+
codespan: '#e69f00',
|
|
210
|
+
link: '#56b4e9',
|
|
211
|
+
href: '#56b4e9',
|
|
212
|
+
blockquote: '#999999',
|
|
213
|
+
del: '#999999',
|
|
214
|
+
html: '#999999',
|
|
215
|
+
diffAdded: '#8ecdf5',
|
|
216
|
+
diffRemoved: '#ffb066',
|
|
217
|
+
diffAddedBackground: '#00335c',
|
|
218
|
+
diffRemovedBackground: '#5c3000',
|
|
219
|
+
diffHunk: '#56b4e9',
|
|
220
|
+
},
|
|
221
|
+
syntax: {
|
|
222
|
+
keyword: '#56b4e9',
|
|
223
|
+
built_in: '#8ecdf5',
|
|
224
|
+
type: '#8ecdf5',
|
|
225
|
+
literal: '#56b4e9',
|
|
226
|
+
number: '#e69f00',
|
|
227
|
+
regexp: '#d55e00',
|
|
228
|
+
string: '#d55e00',
|
|
229
|
+
class: '#56b4e9',
|
|
230
|
+
function: '#f0e442',
|
|
231
|
+
comment: '#999999',
|
|
232
|
+
doctag: '#999999',
|
|
233
|
+
meta: '#999999',
|
|
234
|
+
tag: '#999999',
|
|
235
|
+
name: '#56b4e9',
|
|
236
|
+
attr: '#8ecdf5',
|
|
237
|
+
addition: '#8ecdf5',
|
|
238
|
+
deletion: '#ffb066',
|
|
239
|
+
},
|
|
240
|
+
motion: { wave: ['#3d3d3d', '#5c5c5c', '#7a7a7a', '#999999'] },
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
export const LIGHT_DALTONIZED_THEME: ITuiTheme = {
|
|
244
|
+
id: 'light-daltonized',
|
|
245
|
+
name: 'Light (daltonized)',
|
|
246
|
+
appearance: 'light',
|
|
247
|
+
source: 'built-in',
|
|
248
|
+
colors: {
|
|
249
|
+
text: {
|
|
250
|
+
accent: '#0072b2',
|
|
251
|
+
emphasis: '#000000',
|
|
252
|
+
success: '#0072b2',
|
|
253
|
+
warning: '#b35900',
|
|
254
|
+
error: '#8c3d00',
|
|
255
|
+
session: '#a8477f',
|
|
256
|
+
muted: '#5c5c5c',
|
|
257
|
+
onAccent: '#ffffff',
|
|
258
|
+
},
|
|
259
|
+
border: {
|
|
260
|
+
attention: '#b35900',
|
|
261
|
+
focused: '#0072b2',
|
|
262
|
+
active: '#0072b2',
|
|
263
|
+
muted: '#a3a3a3',
|
|
264
|
+
error: '#8c3d00',
|
|
265
|
+
},
|
|
266
|
+
status: {
|
|
267
|
+
running: '#b35900',
|
|
268
|
+
success: '#0072b2',
|
|
269
|
+
error: '#8c3d00',
|
|
270
|
+
denied: '#8a7500',
|
|
271
|
+
waiting: '#b35900',
|
|
272
|
+
cancelled: '#a8477f',
|
|
273
|
+
idle: '#5c5c5c',
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
markdown: {
|
|
277
|
+
heading: '#0072b2',
|
|
278
|
+
firstHeading: '#a8477f',
|
|
279
|
+
code: '#8a5a00',
|
|
280
|
+
codespan: '#8a5a00',
|
|
281
|
+
link: '#0072b2',
|
|
282
|
+
href: '#0072b2',
|
|
283
|
+
blockquote: '#5c5c5c',
|
|
284
|
+
del: '#5c5c5c',
|
|
285
|
+
html: '#5c5c5c',
|
|
286
|
+
diffAdded: '#004c77',
|
|
287
|
+
diffRemoved: '#7a3300',
|
|
288
|
+
diffAddedBackground: '#d6ecf9',
|
|
289
|
+
diffRemovedBackground: '#fbe5cd',
|
|
290
|
+
diffHunk: '#0072b2',
|
|
291
|
+
},
|
|
292
|
+
syntax: {
|
|
293
|
+
keyword: '#0072b2',
|
|
294
|
+
built_in: '#004c77',
|
|
295
|
+
type: '#004c77',
|
|
296
|
+
literal: '#0072b2',
|
|
297
|
+
number: '#b35900',
|
|
298
|
+
regexp: '#8c3d00',
|
|
299
|
+
string: '#8c3d00',
|
|
300
|
+
class: '#0072b2',
|
|
301
|
+
function: '#8a7500',
|
|
302
|
+
comment: '#5c5c5c',
|
|
303
|
+
doctag: '#5c5c5c',
|
|
304
|
+
meta: '#5c5c5c',
|
|
305
|
+
tag: '#5c5c5c',
|
|
306
|
+
name: '#0072b2',
|
|
307
|
+
attr: '#004c77',
|
|
308
|
+
addition: '#004c77',
|
|
309
|
+
deletion: '#7a3300',
|
|
310
|
+
},
|
|
311
|
+
motion: { wave: ['#c7c7c7', '#a3a3a3', '#7a7a7a', '#5c5c5c'] },
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
export const BUILT_IN_THEMES: readonly ITuiTheme[] = [
|
|
315
|
+
DARK_THEME,
|
|
316
|
+
LIGHT_THEME,
|
|
317
|
+
DARK_DALTONIZED_THEME,
|
|
318
|
+
LIGHT_DALTONIZED_THEME,
|
|
319
|
+
];
|
|
320
|
+
|
|
321
|
+
/** The theme a run uses when nothing is configured: today's colours. */
|
|
322
|
+
export const DEFAULT_THEME_ID = DARK_THEME.id;
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* The catalogue, as a function rather than the array itself. A caller outside `src/theme/` — the
|
|
326
|
+
* theme registry a picker reads — needs to ENUMERATE the built-ins, which is a different act from
|
|
327
|
+
* reaching into one of them for a colour; the floor refuses the second by refusing the DATA's names,
|
|
328
|
+
* so the first gets an accessor of its own instead of an exemption.
|
|
329
|
+
*/
|
|
330
|
+
export function listBuiltInThemes(): readonly ITuiTheme[] {
|
|
331
|
+
return BUILT_IN_THEMES;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* The one place "no theme was resolved" is answered. A caller outside `src/theme/` asks this instead
|
|
336
|
+
* of importing the built-in data, so the anti-drift floor can keep the colour DATA inside this
|
|
337
|
+
* module and still let a non-React entry point — `renderMarkdown`, which is called with a theme or
|
|
338
|
+
* without one — render at all.
|
|
339
|
+
*/
|
|
340
|
+
export function resolveTheme(theme: ITuiTheme | undefined): ITuiTheme {
|
|
341
|
+
return theme ?? DARK_THEME;
|
|
342
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SCREEN-2002 — colour-vision-deficiency simulation, for the guard over the daltonized built-ins.
|
|
3
|
+
*
|
|
4
|
+
* A daltonized theme's claim is that two colours a viewer must tell apart still differ AFTER their
|
|
5
|
+
* protan or deutan confusion. That is measurable, so it is measured: simulate both deficiencies
|
|
6
|
+
* (Viénot–Brettel–Mollon, the linear-RGB projection published for dichromats), convert to CIE Lab,
|
|
7
|
+
* and take the pairwise distance.
|
|
8
|
+
*
|
|
9
|
+
* The guard REFUSES what it cannot simulate. A colour NAME is whatever the terminal says it is, so a
|
|
10
|
+
* named value in a daltonized theme is an error rather than a pass — the one direction a guard may
|
|
11
|
+
* never take silently.
|
|
12
|
+
*/
|
|
13
|
+
import type { TThemeColor } from './theme-contracts.js';
|
|
14
|
+
|
|
15
|
+
export type TColorVision = 'protanopia' | 'deuteranopia';
|
|
16
|
+
|
|
17
|
+
interface IRgb {
|
|
18
|
+
readonly r: number;
|
|
19
|
+
readonly g: number;
|
|
20
|
+
readonly b: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const HEX_LONG = /^#([0-9a-f]{6})$/iu;
|
|
24
|
+
const HEX_SHORT = /^#([0-9a-f]{3})$/iu;
|
|
25
|
+
const ANSI256 = /^ansi256\((\d{1,3})\)$/iu;
|
|
26
|
+
const RGB = /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/iu;
|
|
27
|
+
const BYTE_MAX = 255;
|
|
28
|
+
const HEX_BASE = 16;
|
|
29
|
+
const HEX_PAIR = /[0-9a-f]{2}/giu;
|
|
30
|
+
const ANSI_CUBE_START = 16;
|
|
31
|
+
const ANSI_GRAY_START = 232;
|
|
32
|
+
const ANSI_CUBE_SIDE = 6;
|
|
33
|
+
/**
|
|
34
|
+
* Published numeric tables are kept as DATA — parsed from their published text rather than spelled
|
|
35
|
+
* as literals in expressions, so the values stay verbatim and reviewable against their source.
|
|
36
|
+
*/
|
|
37
|
+
function coefficients(published: string): number[] {
|
|
38
|
+
return published.split(/\s+/u).map(Number);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The xterm 6×6×6 cube's channel levels. */
|
|
42
|
+
const ANSI_CUBE_STEPS = coefficients('0 95 135 175 215 255');
|
|
43
|
+
const GRAY_STEP = 10;
|
|
44
|
+
const GRAY_BASE = 8;
|
|
45
|
+
|
|
46
|
+
/** An Ink-grammar colour as 0–255 sRGB, or `undefined` when it cannot be simulated. */
|
|
47
|
+
export function toRgb(color: TThemeColor): IRgb | undefined {
|
|
48
|
+
const long = HEX_LONG.exec(color);
|
|
49
|
+
if (long?.[1]) {
|
|
50
|
+
const [r, g, b] = (long[1].match(HEX_PAIR) ?? []).map((pair) =>
|
|
51
|
+
Number.parseInt(pair, HEX_BASE),
|
|
52
|
+
) as [number, number, number];
|
|
53
|
+
return { r, g, b };
|
|
54
|
+
}
|
|
55
|
+
const short = HEX_SHORT.exec(color);
|
|
56
|
+
if (short?.[1]) {
|
|
57
|
+
const [r, g, b] = [...short[1]].map((digit) => Number.parseInt(`${digit}${digit}`, HEX_BASE));
|
|
58
|
+
return r === undefined || g === undefined || b === undefined ? undefined : { r, g, b };
|
|
59
|
+
}
|
|
60
|
+
const rgb = RGB.exec(color);
|
|
61
|
+
if (rgb) return { r: Number(rgb[1]), g: Number(rgb[2]), b: Number(rgb[3]) };
|
|
62
|
+
const ansi = ANSI256.exec(color);
|
|
63
|
+
if (ansi) return fromAnsi256(Number(ansi[1]));
|
|
64
|
+
// A colour NAME resolves in the terminal, not here.
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function fromAnsi256(index: number): IRgb | undefined {
|
|
69
|
+
if (index >= ANSI_GRAY_START) {
|
|
70
|
+
const level = GRAY_BASE + (index - ANSI_GRAY_START) * GRAY_STEP;
|
|
71
|
+
return { r: level, g: level, b: level };
|
|
72
|
+
}
|
|
73
|
+
if (index < ANSI_CUBE_START) return undefined; // the 16 system colours are terminal-defined
|
|
74
|
+
const offset = index - ANSI_CUBE_START;
|
|
75
|
+
const r = ANSI_CUBE_STEPS[Math.floor(offset / (ANSI_CUBE_SIDE * ANSI_CUBE_SIDE))];
|
|
76
|
+
const g = ANSI_CUBE_STEPS[Math.floor(offset / ANSI_CUBE_SIDE) % ANSI_CUBE_SIDE];
|
|
77
|
+
const b = ANSI_CUBE_STEPS[offset % ANSI_CUBE_SIDE];
|
|
78
|
+
return r === undefined || g === undefined || b === undefined ? undefined : { r, g, b };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const GAMMA_THRESHOLD = 0.04045;
|
|
82
|
+
const GAMMA_OFFSET = 0.055;
|
|
83
|
+
const GAMMA_SCALE = 1.055;
|
|
84
|
+
const GAMMA_EXPONENT = 2.4;
|
|
85
|
+
const GAMMA_LINEAR_DIVISOR = 12.92;
|
|
86
|
+
|
|
87
|
+
function toLinear(channel: number): number {
|
|
88
|
+
const value = channel / BYTE_MAX;
|
|
89
|
+
return value <= GAMMA_THRESHOLD
|
|
90
|
+
? value / GAMMA_LINEAR_DIVISOR
|
|
91
|
+
: ((value + GAMMA_OFFSET) / GAMMA_SCALE) ** GAMMA_EXPONENT;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Viénot–Brettel–Mollon dichromat projections, applied in linear RGB. */
|
|
95
|
+
const PROJECTION: Record<TColorVision, readonly number[][]> = {
|
|
96
|
+
protanopia: [
|
|
97
|
+
coefficients('0.1121 0.8853 -0.0005'),
|
|
98
|
+
coefficients('0.1127 0.8897 -0.0001'),
|
|
99
|
+
coefficients('0.0045 0.0 1.0019'),
|
|
100
|
+
],
|
|
101
|
+
deuteranopia: [
|
|
102
|
+
coefficients('0.292 0.7054 -0.0003'),
|
|
103
|
+
coefficients('0.2934 0.7089 0.0'),
|
|
104
|
+
coefficients('-0.0209 0.4614 0.5677'),
|
|
105
|
+
],
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/** sRGB → CIE XYZ (D65), the standard matrix. */
|
|
109
|
+
const SRGB_TO_XYZ = [
|
|
110
|
+
coefficients('0.4124 0.3576 0.1805'),
|
|
111
|
+
coefficients('0.2126 0.7152 0.0722'),
|
|
112
|
+
coefficients('0.0193 0.1192 0.9505'),
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
function project(matrix: readonly number[][], vector: readonly number[]): number[] {
|
|
116
|
+
return matrix.map((row) =>
|
|
117
|
+
row.reduce((sum, factor, index) => sum + factor * (vector[index] ?? 0), 0),
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
interface ILab {
|
|
122
|
+
readonly l: number;
|
|
123
|
+
readonly a: number;
|
|
124
|
+
readonly b: number;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const D65 = coefficients('0.95047 1 1.08883');
|
|
128
|
+
const LAB_EPSILON_NUMERATOR = 216;
|
|
129
|
+
const LAB_EPSILON_DENOMINATOR = 24389;
|
|
130
|
+
const LAB_KAPPA_DIVISOR = 27;
|
|
131
|
+
const LAB_EPSILON = LAB_EPSILON_NUMERATOR / LAB_EPSILON_DENOMINATOR;
|
|
132
|
+
const LAB_KAPPA = LAB_EPSILON_DENOMINATOR / LAB_KAPPA_DIVISOR;
|
|
133
|
+
const LAB_L_SCALE = 116;
|
|
134
|
+
const LAB_L_OFFSET = 16;
|
|
135
|
+
const LAB_A_SCALE = 500;
|
|
136
|
+
const LAB_B_SCALE = 200;
|
|
137
|
+
const THREE = 3;
|
|
138
|
+
const CUBE_ROOT = 1 / THREE;
|
|
139
|
+
|
|
140
|
+
function toLab(linear: readonly number[]): ILab {
|
|
141
|
+
const xyz = project(SRGB_TO_XYZ, linear).map((value, index) => value / (D65[index] ?? 1));
|
|
142
|
+
const f = (value: number): number =>
|
|
143
|
+
value > LAB_EPSILON ? value ** CUBE_ROOT : (LAB_KAPPA * value + LAB_L_OFFSET) / LAB_L_SCALE;
|
|
144
|
+
const [fx, fy, fz] = xyz.map(f) as [number, number, number];
|
|
145
|
+
return {
|
|
146
|
+
l: LAB_L_SCALE * fy - LAB_L_OFFSET,
|
|
147
|
+
a: LAB_A_SCALE * (fx - fy),
|
|
148
|
+
b: LAB_B_SCALE * (fy - fz),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** One colour as a dichromat sees it, in Lab. `undefined` ⇒ the value cannot be simulated. */
|
|
153
|
+
export function simulate(color: TThemeColor, vision: TColorVision): ILab | undefined {
|
|
154
|
+
const rgb = toRgb(color);
|
|
155
|
+
if (rgb === undefined) return undefined;
|
|
156
|
+
const linear = [toLinear(rgb.r), toLinear(rgb.g), toLinear(rgb.b)];
|
|
157
|
+
return toLab(project(PROJECTION[vision], linear));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** CIE76 distance between two simulated colours; `undefined` when either cannot be simulated. */
|
|
161
|
+
export function simulatedDistance(
|
|
162
|
+
first: TThemeColor,
|
|
163
|
+
second: TThemeColor,
|
|
164
|
+
vision: TColorVision,
|
|
165
|
+
): number | undefined {
|
|
166
|
+
const a = simulate(first, vision);
|
|
167
|
+
const b = simulate(second, vision);
|
|
168
|
+
if (a === undefined || b === undefined) return undefined;
|
|
169
|
+
const SQUARE = 2;
|
|
170
|
+
return Math.sqrt((a.l - b.l) ** SQUARE + (a.a - b.a) ** SQUARE + (a.b - b.b) ** SQUARE);
|
|
171
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** SCREEN-2002 — the theme module's internal surface (the package exports nothing from here yet). */
|
|
2
|
+
/**
|
|
3
|
+
* The colour DATA is deliberately NOT re-exported — neither the individual themes nor the array that
|
|
4
|
+
* holds them. The anti-drift floor forbids naming them outside `src/theme/`, and a barrel re-export
|
|
5
|
+
* would be the way around that. The two legitimate needs get accessors instead: a caller that must
|
|
6
|
+
* render without a resolved theme asks {@link resolveTheme}, and one that must ENUMERATE the
|
|
7
|
+
* built-ins asks {@link listBuiltInThemes}.
|
|
8
|
+
*/
|
|
9
|
+
export { DEFAULT_THEME_ID, listBuiltInThemes, resolveTheme } from './built-in-themes.js';
|
|
10
|
+
export {
|
|
11
|
+
ThemeProvider,
|
|
12
|
+
useMotion,
|
|
13
|
+
useMotionTokens,
|
|
14
|
+
usePalette,
|
|
15
|
+
useSyntaxHighlighting,
|
|
16
|
+
useTheme,
|
|
17
|
+
} from './theme-context.js';
|
|
18
|
+
export {
|
|
19
|
+
background,
|
|
20
|
+
diffRowStyles,
|
|
21
|
+
foreground,
|
|
22
|
+
isThemeColor,
|
|
23
|
+
markdownRendererOptions,
|
|
24
|
+
syntaxHighlightTheme,
|
|
25
|
+
} from './theme-styles.js';
|
|
26
|
+
export { THEME_SYNTAX_KEYS } from './theme-contracts.js';
|
|
27
|
+
export { simulate, simulatedDistance, toRgb } from './color-vision.js';
|
|
28
|
+
export {
|
|
29
|
+
escapeThemeText,
|
|
30
|
+
parseThemeDocument,
|
|
31
|
+
quoteThemeText,
|
|
32
|
+
sanitizeThemeProse,
|
|
33
|
+
} from './theme-document.js';
|
|
34
|
+
export type { IThemeDocumentInput, TThemeDocumentResult } from './theme-document.js';
|
|
35
|
+
export type { IDiffRowStyles } from './theme-styles.js';
|
|
36
|
+
export type {
|
|
37
|
+
IThemeColors,
|
|
38
|
+
IThemeMarkdown,
|
|
39
|
+
IThemeMotion,
|
|
40
|
+
IThemeSyntax,
|
|
41
|
+
ITuiTheme,
|
|
42
|
+
TThemeAppearance,
|
|
43
|
+
TThemeColor,
|
|
44
|
+
TThemeSource,
|
|
45
|
+
} from './theme-contracts.js';
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SCREEN-2002 — the resolved theme, published to the tree.
|
|
3
|
+
*
|
|
4
|
+
* The `screen-reader-context` shape: one resolved value in a React context, defaulting to the `dark`
|
|
5
|
+
* built-in outside a provider so a component rendered in a test behaves exactly as it did before the
|
|
6
|
+
* theme existed. Components read `usePalette()` where they used to import `PALETTE`; nothing takes the
|
|
7
|
+
* theme as a prop, so a missed prop cannot leave one component on another theme.
|
|
8
|
+
*/
|
|
9
|
+
import React, { createContext, useContext } from 'react';
|
|
10
|
+
|
|
11
|
+
import { DARK_THEME, resolveTheme } from './built-in-themes.js';
|
|
12
|
+
import { useScreenReader } from '../screen-reader-context.js';
|
|
13
|
+
import { isInteractiveColorTerminal } from '../terminal-capabilities.js';
|
|
14
|
+
|
|
15
|
+
import type { IThemeColors, IThemeMotion, ITuiTheme } from './theme-contracts.js';
|
|
16
|
+
|
|
17
|
+
const ThemeContext = createContext<ITuiTheme>(DARK_THEME);
|
|
18
|
+
const ReducedMotionContext = createContext<boolean>(false);
|
|
19
|
+
const SyntaxHighlightingContext = createContext<boolean>(true);
|
|
20
|
+
|
|
21
|
+
export function ThemeProvider({
|
|
22
|
+
theme,
|
|
23
|
+
reducedMotion = false,
|
|
24
|
+
syntaxHighlighting = true,
|
|
25
|
+
children,
|
|
26
|
+
}: {
|
|
27
|
+
theme?: ITuiTheme;
|
|
28
|
+
reducedMotion?: boolean;
|
|
29
|
+
syntaxHighlighting?: boolean;
|
|
30
|
+
children: React.ReactNode;
|
|
31
|
+
}): React.ReactElement {
|
|
32
|
+
return (
|
|
33
|
+
<ThemeContext.Provider value={resolveTheme(theme)}>
|
|
34
|
+
<ReducedMotionContext.Provider value={reducedMotion}>
|
|
35
|
+
<SyntaxHighlightingContext.Provider value={syntaxHighlighting}>
|
|
36
|
+
{children}
|
|
37
|
+
</SyntaxHighlightingContext.Provider>
|
|
38
|
+
</ReducedMotionContext.Provider>
|
|
39
|
+
</ThemeContext.Provider>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The whole resolved theme. Most components want {@link usePalette} instead. */
|
|
44
|
+
export function useTheme(): ITuiTheme {
|
|
45
|
+
return useContext(ThemeContext);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The colour tokens — the direct replacement for the former `PALETTE` import. */
|
|
49
|
+
export function usePalette(): IThemeColors {
|
|
50
|
+
return useTheme().colors;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The animation ramp — the direct replacement for the former `MOTION` import. */
|
|
54
|
+
export function useMotionTokens(): IThemeMotion {
|
|
55
|
+
return useTheme().motion;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* May this process ANIMATE? One owner for a three-input rule, with exactly one consumer (`WaveText`,
|
|
60
|
+
* the package's only animation). Deliberately NOT gating the background countdown (a once-a-second
|
|
61
|
+
* number is content — freezing it would show a stale `in 59s`) or the streaming indicator's
|
|
62
|
+
* screen-reader collapse (that drops content, which a sighted reduced-motion user still wants).
|
|
63
|
+
*/
|
|
64
|
+
export function useMotion(): boolean {
|
|
65
|
+
const screenReader = useScreenReader();
|
|
66
|
+
const reducedMotion = useContext(ReducedMotionContext);
|
|
67
|
+
// No `useMemo`: the value is a boolean, so memoizing it buys nothing — and a dependency array
|
|
68
|
+
// would have to omit `isInteractiveColorTerminal()`, which reads process state rather than React
|
|
69
|
+
// state. Hiding a non-reactive read inside a memo is how a stale gate gets written later.
|
|
70
|
+
return isInteractiveColorTerminal() && !screenReader && !reducedMotion;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* SCREEN-2002: whether a fenced code block is highlighted. It travels with the theme rather than as
|
|
75
|
+
* a prop because every markdown render site already reads the theme from here, and a setting that
|
|
76
|
+
* reaches only some of them is worse than one that reaches none — `/theme syntax off` would report
|
|
77
|
+
* success while a block somewhere kept its colours.
|
|
78
|
+
*/
|
|
79
|
+
export function useSyntaxHighlighting(): boolean {
|
|
80
|
+
return useContext(SyntaxHighlightingContext);
|
|
81
|
+
}
|