@kgdiff/viewer 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -8
- package/dist/controller.d.ts +23 -7
- package/dist/controller.d.ts.map +1 -1
- package/dist/controller.js +119 -14
- package/dist/controller.js.map +1 -1
- package/dist/element.d.ts +6 -3
- package/dist/element.d.ts.map +1 -1
- package/dist/element.js +68 -19
- package/dist/element.js.map +1 -1
- package/dist/hash-sync.d.ts +4 -2
- package/dist/hash-sync.d.ts.map +1 -1
- package/dist/hash-sync.js +21 -5
- package/dist/hash-sync.js.map +1 -1
- package/dist/i18n.d.ts +7 -7
- package/dist/i18n.d.ts.map +1 -1
- package/dist/i18n.js +14 -14
- package/dist/i18n.js.map +1 -1
- package/dist/index.d.ts +21 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/keyboard.d.ts +6 -4
- package/dist/keyboard.d.ts.map +1 -1
- package/dist/keyboard.js +6 -5
- package/dist/keyboard.js.map +1 -1
- package/dist/prefs.d.ts +7 -1
- package/dist/prefs.d.ts.map +1 -1
- package/dist/prefs.js +15 -3
- package/dist/prefs.js.map +1 -1
- package/dist/standalone/kgdiff-viewer.js +4 -4
- package/dist/styles.d.ts.map +1 -1
- package/dist/styles.js +69 -63
- package/dist/styles.js.map +1 -1
- package/dist/ui/context.d.ts +2 -0
- package/dist/ui/context.d.ts.map +1 -1
- package/dist/ui/context.js.map +1 -1
- package/dist/ui/dock.d.ts +17 -0
- package/dist/ui/dock.d.ts.map +1 -0
- package/dist/ui/dock.js +151 -0
- package/dist/ui/dock.js.map +1 -0
- package/dist/ui/header.d.ts +7 -2
- package/dist/ui/header.d.ts.map +1 -1
- package/dist/ui/header.js +24 -133
- package/dist/ui/header.js.map +1 -1
- package/dist/ui/popovers.d.ts.map +1 -1
- package/dist/ui/popovers.js +3 -2
- package/dist/ui/popovers.js.map +1 -1
- package/dist/ui/sidebar.d.ts.map +1 -1
- package/dist/ui/sidebar.js +2 -1
- package/dist/ui/sidebar.js.map +1 -1
- package/dist/ui/stage.d.ts +4 -0
- package/dist/ui/stage.d.ts.map +1 -1
- package/dist/ui/stage.js +41 -94
- package/dist/ui/stage.js.map +1 -1
- package/dist/ui/swatch.d.ts +7 -0
- package/dist/ui/swatch.d.ts.map +1 -0
- package/dist/ui/swatch.js +26 -0
- package/dist/ui/swatch.js.map +1 -0
- package/package.json +6 -6
- package/src/controller.ts +131 -20
- package/src/element.ts +72 -21
- package/src/hash-sync.ts +20 -6
- package/src/i18n.ts +15 -14
- package/src/index.ts +21 -9
- package/src/keyboard.ts +12 -6
- package/src/prefs.ts +19 -4
- package/src/styles.ts +69 -63
- package/src/ui/context.ts +2 -0
- package/src/ui/dock.ts +198 -0
- package/src/ui/header.ts +38 -168
- package/src/ui/popovers.ts +3 -2
- package/src/ui/sidebar.ts +2 -1
- package/src/ui/stage.ts +49 -114
- package/src/ui/swatch.ts +31 -0
package/src/i18n.ts
CHANGED
|
@@ -10,11 +10,18 @@ import type { Locale } from '@kgdiff/core';
|
|
|
10
10
|
const en = {
|
|
11
11
|
'brand.tagline': 'git diff for knowledge graphs',
|
|
12
12
|
'header.compare': 'Compared versions',
|
|
13
|
-
'
|
|
13
|
+
'header.actions': 'Viewer actions',
|
|
14
|
+
'dock.label': 'View',
|
|
15
|
+
'mode.label': 'Compare by',
|
|
14
16
|
'mode.overlay': 'Overlay',
|
|
15
17
|
'mode.swipe': 'Swipe',
|
|
16
18
|
'mode.replay': 'Replay',
|
|
17
19
|
'mode.changes': 'Changes only',
|
|
20
|
+
'scope.hint': 'Show only the changes, with nearby context',
|
|
21
|
+
'scope.on': 'Showing only the changes',
|
|
22
|
+
'scope.off': 'Showing the whole graph',
|
|
23
|
+
'scope.locked':
|
|
24
|
+
'This graph was laid out around its changes only: the whole graph cannot be shown.',
|
|
18
25
|
'family.added': 'Added',
|
|
19
26
|
'family.removed': 'Removed',
|
|
20
27
|
'family.content': 'Content',
|
|
@@ -41,12 +48,6 @@ const en = {
|
|
|
41
48
|
'status.added': 'Added',
|
|
42
49
|
'status.removed': 'Removed',
|
|
43
50
|
'status.modified': 'Modified',
|
|
44
|
-
'stat.added': '{n} added',
|
|
45
|
-
'stat.removed': '{n} removed',
|
|
46
|
-
'stat.modified': '{n} modified',
|
|
47
|
-
'stat.restructured': '{n} restructured',
|
|
48
|
-
'stat.filter': 'Show only these changes',
|
|
49
|
-
'stat.label': 'Change summary',
|
|
50
51
|
'action.fit': 'Fit to view',
|
|
51
52
|
'action.help': 'Keyboard shortcuts',
|
|
52
53
|
'action.sidebar': 'Toggle sidebar',
|
|
@@ -199,11 +200,17 @@ export type Messages = Readonly<Record<MessageKey, string>>;
|
|
|
199
200
|
const zhCN: Messages = {
|
|
200
201
|
'brand.tagline': '知识图谱的 git diff',
|
|
201
202
|
'header.compare': '对比的版本',
|
|
202
|
-
'
|
|
203
|
+
'header.actions': '查看器操作',
|
|
204
|
+
'dock.label': '视图',
|
|
205
|
+
'mode.label': '对比方式',
|
|
203
206
|
'mode.overlay': '叠加',
|
|
204
207
|
'mode.swipe': '滑动对比',
|
|
205
208
|
'mode.replay': '回放',
|
|
206
209
|
'mode.changes': '仅看变更',
|
|
210
|
+
'scope.hint': '只显示变更,以及附近的上下文',
|
|
211
|
+
'scope.on': '只显示变更',
|
|
212
|
+
'scope.off': '显示全图',
|
|
213
|
+
'scope.locked': '这张图只对变更部分做了布局,无法显示全图。',
|
|
207
214
|
'family.added': '新增',
|
|
208
215
|
'family.removed': '删除',
|
|
209
216
|
'family.content': '内容',
|
|
@@ -230,12 +237,6 @@ const zhCN: Messages = {
|
|
|
230
237
|
'status.added': '新增',
|
|
231
238
|
'status.removed': '删除',
|
|
232
239
|
'status.modified': '修改',
|
|
233
|
-
'stat.added': '新增 {n} 处',
|
|
234
|
-
'stat.removed': '删除 {n} 处',
|
|
235
|
-
'stat.modified': '修改 {n} 处',
|
|
236
|
-
'stat.restructured': '结构调整 {n} 处',
|
|
237
|
-
'stat.filter': '只显示这类变更',
|
|
238
|
-
'stat.label': '变更概览',
|
|
239
240
|
'action.fit': '适应视图',
|
|
240
241
|
'action.help': '键盘快捷键',
|
|
241
242
|
'action.sidebar': '显示或隐藏侧栏',
|
package/src/index.ts
CHANGED
|
@@ -25,7 +25,10 @@ import { KgdiffViewer } from './element.js';
|
|
|
25
25
|
import { hydrate } from './payload.js';
|
|
26
26
|
import { viewerTags } from './registry.js';
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
/** How the two versions are told apart: by color (overlay), side by side (swipe) or over time (replay). */
|
|
29
|
+
export type ViewerMode = 'overlay' | 'swipe' | 'replay';
|
|
30
|
+
/** What is drawn, in any mode: the whole graph, or only the changes with a few hops of context. */
|
|
31
|
+
export type ViewerScope = 'all' | 'changes';
|
|
29
32
|
export type ThemePreference = 'auto' | 'light' | 'dark';
|
|
30
33
|
/** `none`: the graph alone, for small embeds (see `KgdiffViewerElement.chrome`). */
|
|
31
34
|
export type ViewerChrome = 'full' | 'none';
|
|
@@ -49,7 +52,9 @@ export interface ReportPayload {
|
|
|
49
52
|
};
|
|
50
53
|
/** Initial view preferences. */
|
|
51
54
|
readonly view?: {
|
|
55
|
+
/** `changes` (from reports made before scopes existed) means `overlay` + scope `changes`. */
|
|
52
56
|
readonly mode?: ViewerMode;
|
|
57
|
+
readonly scope?: ViewerScope;
|
|
53
58
|
readonly theme?: ThemePreference;
|
|
54
59
|
readonly palette?: PaletteName;
|
|
55
60
|
readonly locale?: Locale;
|
|
@@ -80,7 +85,7 @@ export interface ReviewState {
|
|
|
80
85
|
export interface KgdiffViewerEventMap {
|
|
81
86
|
'kgdiff-ready': CustomEvent<{ readonly stats: DiffStats }>;
|
|
82
87
|
'kgdiff-select': CustomEvent<{ readonly hunk: string | null; readonly node: string | null }>;
|
|
83
|
-
'kgdiff-mode': CustomEvent<{ readonly mode: ViewerMode }>;
|
|
88
|
+
'kgdiff-mode': CustomEvent<{ readonly mode: ViewerMode; readonly scope: ViewerScope }>;
|
|
84
89
|
'kgdiff-review': CustomEvent<{
|
|
85
90
|
readonly hunk: string;
|
|
86
91
|
readonly reviewed: boolean;
|
|
@@ -104,21 +109,28 @@ export interface FitOptions {
|
|
|
104
109
|
/**
|
|
105
110
|
* Public surface of the `<kgdiff-viewer>` element.
|
|
106
111
|
*
|
|
107
|
-
* Boolean attributes: `sync-hash` (deep links `#kgdiff=hunk:<id>;mode:<mode>`) and
|
|
112
|
+
* Boolean attributes: `sync-hash` (deep links `#kgdiff=hunk:<id>;mode:<mode>;scope:<scope>`) and
|
|
108
113
|
* `global-shortcuts`: keyboard shortcuts normally apply only while focus is inside the viewer;
|
|
109
114
|
* with this attribute they also apply when nothing on the page has focus (for full-page
|
|
110
115
|
* viewers such as HTML reports), except the keys pages scroll with (arrows, Home / End, Page
|
|
111
116
|
* Up / Down, Space).
|
|
112
117
|
*
|
|
113
|
-
* `header="compact"` leaves the brand and the compared names out of the
|
|
118
|
+
* `header="compact"` leaves the brand and the compared names out of the canvas, for hosts that
|
|
114
119
|
* show their own (such as the playground).
|
|
115
120
|
*
|
|
116
121
|
* A small looping replay for another page: `mode="replay" chrome="none" loop` (with a height of
|
|
117
122
|
* your choosing, such as 320px).
|
|
118
123
|
*/
|
|
119
124
|
export interface KgdiffViewerElement extends HTMLElement {
|
|
120
|
-
/** Reflected to the `mode` attribute. Default `overlay
|
|
121
|
-
mode: ViewerMode;
|
|
125
|
+
/** Reflected to the `mode` attribute. Default `overlay`. */
|
|
126
|
+
get mode(): ViewerMode;
|
|
127
|
+
/** The legacy `changes` mode sets `overlay` + scope `changes`. */
|
|
128
|
+
set mode(value: ViewerMode | 'changes');
|
|
129
|
+
/**
|
|
130
|
+
* Reflected to the `scope` attribute. Default `all` (`changes` for large graphs). Fixed to
|
|
131
|
+
* `changes` when the layout covers only the changes.
|
|
132
|
+
*/
|
|
133
|
+
scope: ViewerScope;
|
|
122
134
|
/** Reflected to the `theme` attribute. Default `auto`. */
|
|
123
135
|
theme: ThemePreference;
|
|
124
136
|
/** Reflected to the `palette` attribute. Default `default`. */
|
|
@@ -126,9 +138,9 @@ export interface KgdiffViewerElement extends HTMLElement {
|
|
|
126
138
|
/** Reflected to the `locale` attribute. Default from `navigator.language`. */
|
|
127
139
|
locale: Locale;
|
|
128
140
|
/**
|
|
129
|
-
* Reflected to the `chrome` attribute. `none` shows the graph alone: no
|
|
130
|
-
* step caption
|
|
131
|
-
* one-line legend stay. Clicks and keyboard shortcuts do nothing, the minimum height drops
|
|
141
|
+
* Reflected to the `chrome` attribute. `none` shows the graph alone: no title, actions, view
|
|
142
|
+
* dock (with the step caption and playback bar), change list, zoom buttons, minimap or legend
|
|
143
|
+
* card; the hover tooltip and a one-line legend stay. Clicks and keyboard shortcuts do nothing, the minimum height drops
|
|
132
144
|
* from 420px to 120px, and the view stays fitted: when the canvas is resized (unless the user
|
|
133
145
|
* panned or zoomed since) and at the start of every loop. Labels are never cut at the edge:
|
|
134
146
|
* one pushed from its spot by a neighbour moves inside or is left out. Default `full`.
|
package/src/keyboard.ts
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
* The keyboard map (docs/interaction.md §Keyboard map), as a pure function from a key event to
|
|
3
3
|
* an action, so it can be tested without a DOM.
|
|
4
4
|
*/
|
|
5
|
-
import type { ViewerMode } from './index.js';
|
|
5
|
+
import type { ViewerMode, ViewerScope } from './index.js';
|
|
6
6
|
|
|
7
7
|
export type KeyAction =
|
|
8
8
|
| { readonly type: 'mode'; readonly mode: ViewerMode }
|
|
9
|
+
| { readonly type: 'scope' }
|
|
9
10
|
| { readonly type: 'next' }
|
|
10
11
|
| { readonly type: 'prev' }
|
|
11
12
|
| { readonly type: 'first' }
|
|
@@ -36,7 +37,6 @@ const MODE_KEYS: Readonly<Record<string, ViewerMode>> = {
|
|
|
36
37
|
'1': 'overlay',
|
|
37
38
|
'2': 'swipe',
|
|
38
39
|
'3': 'replay',
|
|
39
|
-
'4': 'changes',
|
|
40
40
|
};
|
|
41
41
|
|
|
42
42
|
/** Divider steps for swipe, as a fraction of the width. */
|
|
@@ -44,15 +44,21 @@ export const DIVIDER_STEP = 0.02;
|
|
|
44
44
|
export const DIVIDER_STEP_LARGE = 0.1;
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
|
-
* Map a key press to an action in `mode`, or null when the key means nothing here.
|
|
48
|
-
* Ctrl / Meta / Alt are never handled (they belong to the browser and the host page).
|
|
47
|
+
* Map a key press to an action in `mode` and `scope`, or null when the key means nothing here.
|
|
48
|
+
* Chords with Ctrl / Meta / Alt are never handled (they belong to the browser and the host page).
|
|
49
49
|
*/
|
|
50
|
-
export function keyAction(
|
|
50
|
+
export function keyAction(
|
|
51
|
+
e: KeyLike,
|
|
52
|
+
mode: ViewerMode,
|
|
53
|
+
scope: ViewerScope = 'all',
|
|
54
|
+
): KeyAction | null {
|
|
51
55
|
if (e.ctrlKey || e.metaKey || e.altKey) return null;
|
|
52
56
|
const key = e.key;
|
|
53
57
|
const mapped = MODE_KEYS[key];
|
|
54
58
|
if (mapped !== undefined) return { type: 'mode', mode: mapped };
|
|
55
59
|
switch (key) {
|
|
60
|
+
case '4':
|
|
61
|
+
return { type: 'scope' };
|
|
56
62
|
case 'j':
|
|
57
63
|
case 'ArrowDown':
|
|
58
64
|
return { type: 'next' };
|
|
@@ -104,7 +110,7 @@ export function keyAction(e: KeyLike, mode: ViewerMode): KeyAction | null {
|
|
|
104
110
|
return { type: 'theme' };
|
|
105
111
|
case 'e':
|
|
106
112
|
case 'E':
|
|
107
|
-
return
|
|
113
|
+
return scope === 'changes' ? { type: 'expand' } : null;
|
|
108
114
|
case '?':
|
|
109
115
|
return { type: 'help' };
|
|
110
116
|
default:
|
package/src/prefs.ts
CHANGED
|
@@ -12,9 +12,11 @@ import type {
|
|
|
12
12
|
ViewerChrome,
|
|
13
13
|
ViewerLegend,
|
|
14
14
|
ViewerMode,
|
|
15
|
+
ViewerScope,
|
|
15
16
|
} from './index.js';
|
|
16
17
|
|
|
17
|
-
const MODES: readonly ViewerMode[] = ['overlay', 'swipe', 'replay'
|
|
18
|
+
const MODES: readonly ViewerMode[] = ['overlay', 'swipe', 'replay'];
|
|
19
|
+
const SCOPES: readonly ViewerScope[] = ['all', 'changes'];
|
|
18
20
|
const THEMES: readonly ThemePreference[] = ['auto', 'light', 'dark'];
|
|
19
21
|
const PALETTES: readonly PaletteName[] = ['default', 'colorblind'];
|
|
20
22
|
const CHROMES: readonly ViewerChrome[] = ['full', 'none'];
|
|
@@ -29,6 +31,16 @@ export function toMode(value: unknown): ViewerMode | null {
|
|
|
29
31
|
return MODES.includes(value as ViewerMode) ? (value as ViewerMode) : null;
|
|
30
32
|
}
|
|
31
33
|
|
|
34
|
+
export function toScope(value: unknown): ViewerScope | null {
|
|
35
|
+
return SCOPES.includes(value as ViewerScope) ? (value as ViewerScope) : null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Before scopes existed, “changes only” was a fourth mode. Attributes, properties, deep links
|
|
40
|
+
* and reports may still say `changes`: it means overlay + the `changes` scope.
|
|
41
|
+
*/
|
|
42
|
+
export const LEGACY_CHANGES_MODE = 'changes';
|
|
43
|
+
|
|
32
44
|
export function toTheme(value: unknown): ThemePreference | null {
|
|
33
45
|
return THEMES.includes(value as ThemePreference) ? (value as ThemePreference) : null;
|
|
34
46
|
}
|
|
@@ -61,18 +73,21 @@ type View = NonNullable<ReportPayload['view']>;
|
|
|
61
73
|
export function sanitizeView(view: unknown): View | undefined {
|
|
62
74
|
if (typeof view !== 'object' || view === null) return undefined;
|
|
63
75
|
const v = view as Record<string, unknown>;
|
|
64
|
-
const
|
|
76
|
+
const legacy = v.mode === LEGACY_CHANGES_MODE;
|
|
77
|
+
const mode = legacy ? 'overlay' : toMode(v.mode);
|
|
78
|
+
const scope = toScope(v.scope) ?? (legacy && v.scope === undefined ? 'changes' : null);
|
|
65
79
|
const theme = toTheme(v.theme);
|
|
66
80
|
const palette = toPalette(v.palette);
|
|
67
81
|
const locale = toLocale(v.locale);
|
|
68
|
-
const dropped = (['mode', 'theme', 'palette', 'locale'] as const).filter(
|
|
69
|
-
(k) => v[k] !== undefined && { mode, theme, palette, locale }[k] === null,
|
|
82
|
+
const dropped = (['mode', 'scope', 'theme', 'palette', 'locale'] as const).filter(
|
|
83
|
+
(k) => v[k] !== undefined && { mode, scope, theme, palette, locale }[k] === null,
|
|
70
84
|
);
|
|
71
85
|
if (dropped.length > 0 && typeof console !== 'undefined') {
|
|
72
86
|
console.warn(`[kgdiff] ignoring invalid report view preferences: ${dropped.join(', ')}`);
|
|
73
87
|
}
|
|
74
88
|
return {
|
|
75
89
|
...(mode ? { mode } : {}),
|
|
90
|
+
...(scope ? { scope } : {}),
|
|
76
91
|
...(theme ? { theme } : {}),
|
|
77
92
|
...(palette ? { palette } : {}),
|
|
78
93
|
...(locale ? { locale } : {}),
|
package/src/styles.ts
CHANGED
|
@@ -121,7 +121,7 @@ kbd {
|
|
|
121
121
|
background: var(--kg-bg);
|
|
122
122
|
}
|
|
123
123
|
.body { position: relative; flex: 1; min-height: 0; display: flex; }
|
|
124
|
-
.stage-wrap { position: relative; flex: 1; min-width: 0; min-height: 0; overflow: hidden; background: var(--kg-bg); }
|
|
124
|
+
.stage-wrap { position: relative; flex: 1; min-width: 0; min-height: 0; overflow: hidden; background: var(--kg-bg); container-type: inline-size; }
|
|
125
125
|
.stage { position: absolute; inset: 0; }
|
|
126
126
|
.stage canvas { outline: none; }
|
|
127
127
|
.stage canvas:focus-visible { outline: 2px solid var(--kg-accent); outline-offset: -2px; }
|
|
@@ -148,17 +148,22 @@ kbd {
|
|
|
148
148
|
.link-btn { min-height: 28px; color: var(--kg-accent-text); font-weight: 500; font-size: 12px; border-radius: 4px; }
|
|
149
149
|
.link-btn:hover { text-decoration: underline; text-underline-offset: 3px; }
|
|
150
150
|
|
|
151
|
-
/* ──
|
|
152
|
-
.
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
151
|
+
/* ── HUD: title (top-left) and actions (top-right), over the canvas ──────── */
|
|
152
|
+
.hud { position: absolute; inset: 0; z-index: 3; pointer-events: none; }
|
|
153
|
+
.hud > * { pointer-events: auto; }
|
|
154
|
+
.hud-title {
|
|
155
|
+
position: absolute; top: 12px; left: 12px; max-width: calc(100% - 184px); height: 40px;
|
|
156
|
+
display: flex; align-items: center; gap: 10px; padding: 0 14px 0 10px;
|
|
157
|
+
}
|
|
158
|
+
.hud[data-titled="false"] .header-sep { display: none; }
|
|
159
|
+
:host([header="compact"]) .hud-title { display: none; }
|
|
160
|
+
.hud-end { position: absolute; top: 12px; right: 12px; }
|
|
161
|
+
.hud-actions { display: flex; align-items: center; gap: 2px; padding: 4px; }
|
|
156
162
|
.brand { display: flex; align-items: center; gap: 8px; flex: none; color: var(--kg-text); }
|
|
157
163
|
.brand-mark { width: 20px; height: 20px; display: block; }
|
|
158
164
|
.brand-name { font-size: 14px; font-weight: 650; letter-spacing: -0.01em; }
|
|
159
|
-
.header-sep { width: 1px; height:
|
|
160
|
-
.versions { display: flex; align-items: center; gap: 8px; min-width: 0; flex:
|
|
161
|
-
mask-image: linear-gradient(90deg, #000 calc(100% - 16px), transparent); }
|
|
165
|
+
.header-sep { width: 1px; height: 18px; background: var(--kg-border-strong); flex: none; }
|
|
166
|
+
.versions { display: flex; align-items: center; gap: 8px; min-width: 0; flex: 0 1 auto; font-size: 13px; overflow: hidden; }
|
|
162
167
|
.versions .name { font-weight: 550; color: var(--kg-text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
|
|
163
168
|
.versions .arrow { color: var(--kg-text-3); flex: none; }
|
|
164
169
|
.versions .side { display: flex; align-items: center; gap: 6px; min-width: 0; }
|
|
@@ -167,16 +172,9 @@ kbd {
|
|
|
167
172
|
font: 500 11px/1 var(--kg-mono); color: var(--kg-text-2); background: var(--kg-hover);
|
|
168
173
|
box-shadow: inset 0 0 0 1px var(--kg-border); white-space: nowrap; max-width: 160px; overflow: hidden; text-overflow: ellipsis;
|
|
169
174
|
}
|
|
170
|
-
.header-center { flex: none; display: flex; justify-content: center; }
|
|
171
|
-
/* header="compact": no brand or names (the host shows them); the names keep their space so the
|
|
172
|
-
mode control stays centered. */
|
|
173
|
-
:host([header="compact"]) .brand, :host([header="compact"]) .header-sep { display: none; }
|
|
174
|
-
:host([header="compact"]) .versions { visibility: hidden; }
|
|
175
|
-
.header-end { display: flex; align-items: center; gap: 4px; flex: 1 0 auto; justify-content: flex-end; }
|
|
176
|
-
.stats { display: flex; align-items: center; gap: 4px; margin-right: 6px; }
|
|
177
175
|
|
|
178
176
|
.segmented {
|
|
179
|
-
position: relative; display: inline-flex; align-items: center; gap: 2px; height: 32px; padding: 2px;
|
|
177
|
+
position: relative; display: inline-flex; align-items: center; gap: 2px; height: 32px; padding: 2px; flex: none;
|
|
180
178
|
border-radius: 9px; background: var(--kg-hover); box-shadow: inset 0 0 0 1px var(--kg-border);
|
|
181
179
|
}
|
|
182
180
|
.segmented .thumb {
|
|
@@ -193,17 +191,6 @@ kbd {
|
|
|
193
191
|
.seg[aria-checked="true"] { color: var(--kg-text); }
|
|
194
192
|
.seg .icon { opacity: 0.9; }
|
|
195
193
|
|
|
196
|
-
.pill {
|
|
197
|
-
display: inline-flex; align-items: center; gap: 4px; height: 28px; padding: 0 9px; border-radius: 14px;
|
|
198
|
-
font-size: 12px; font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap;
|
|
199
|
-
transition: box-shadow var(--kg-fast) var(--kg-ease), background var(--kg-fast) var(--kg-ease), opacity var(--kg-fast) var(--kg-ease);
|
|
200
|
-
}
|
|
201
|
-
.stat { color: var(--fam-text); background: var(--fam-soft); box-shadow: inset 0 0 0 1px transparent; }
|
|
202
|
-
.stat:hover { box-shadow: inset 0 0 0 1px var(--fam-base); }
|
|
203
|
-
.stat[aria-pressed="true"] { box-shadow: inset 0 0 0 1.5px var(--fam-base); }
|
|
204
|
-
.stats.filtering .stat[aria-pressed="false"] { opacity: 0.55; }
|
|
205
|
-
.stat .glyph { font-weight: 700; }
|
|
206
|
-
|
|
207
194
|
|
|
208
195
|
${familyRules}
|
|
209
196
|
|
|
@@ -213,7 +200,6 @@ ${familyRules}
|
|
|
213
200
|
backdrop-filter: blur(16px) saturate(140%);
|
|
214
201
|
}
|
|
215
202
|
.float { position: absolute; }
|
|
216
|
-
.top-left { top: 12px; left: 12px; }
|
|
217
203
|
.bottom-left { bottom: 16px; left: 16px; }
|
|
218
204
|
.bottom-right { bottom: 16px; right: 16px; }
|
|
219
205
|
.chip {
|
|
@@ -224,7 +210,10 @@ ${familyRules}
|
|
|
224
210
|
.map-cluster { display: flex; align-items: flex-end; gap: 8px; }
|
|
225
211
|
.zoom-bar { display: flex; flex-direction: column; padding: 2px; gap: 2px; }
|
|
226
212
|
.zoom-bar .icon-btn { width: 30px; height: 30px; }
|
|
227
|
-
.minimap-host { position: relative; width: 184px; height: 128px; border-radius: 12px; box-shadow: 0 0 0 1px var(--kg-border);
|
|
213
|
+
.minimap-host { position: relative; width: 184px; height: 128px; border-radius: 12px; box-shadow: 0 0 0 1px var(--kg-border);
|
|
214
|
+
animation: fade var(--kg-base) var(--kg-ease); }
|
|
215
|
+
/* Everything is in view: the minimap has nothing to add. */
|
|
216
|
+
.minimap-host[data-hidden="true"] { display: none; }
|
|
228
217
|
.minimap-host canvas { border-radius: 12px; }
|
|
229
218
|
|
|
230
219
|
.legend { padding: 6px; min-width: 0; }
|
|
@@ -250,28 +239,49 @@ ${familyRules}
|
|
|
250
239
|
.stage-wrap[data-legend="mini"] .legend, .stage-wrap[data-legend="none"] .legend { display: none; }
|
|
251
240
|
|
|
252
241
|
.caption {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
transition: opacity var(--kg-base) var(--kg-ease);
|
|
242
|
+
max-width: min(560px, 100%); display: flex; align-items: center; gap: 8px; height: 32px; padding: 0 12px 0 6px;
|
|
243
|
+
font-size: 12.5px; font-weight: 550;
|
|
256
244
|
}
|
|
257
245
|
.caption .text { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
258
246
|
.caption .count { color: var(--kg-text-3); font-weight: 500; font-variant-numeric: tabular-nums; flex: none; }
|
|
259
|
-
.caption[data-empty="true"] {
|
|
247
|
+
.caption[data-empty="true"] { display: none; }
|
|
260
248
|
|
|
261
249
|
.glyph-box {
|
|
262
250
|
display: inline-flex; align-items: center; justify-content: center; flex: none; width: 20px; height: 20px; border-radius: 6px;
|
|
263
251
|
font-size: 12px; font-weight: 700; line-height: 1; color: var(--fam-text); background: var(--fam-soft);
|
|
264
252
|
}
|
|
265
253
|
|
|
266
|
-
|
|
267
|
-
.
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
.
|
|
272
|
-
.
|
|
273
|
-
.
|
|
274
|
-
.
|
|
254
|
+
/* View dock (bottom center): replay's finished pill, caption and transport stack above the bar. */
|
|
255
|
+
.chrome > .dock {
|
|
256
|
+
position: absolute; left: 16px; right: 16px; bottom: 16px; display: flex; flex-direction: column; align-items: center;
|
|
257
|
+
gap: 8px; pointer-events: none;
|
|
258
|
+
}
|
|
259
|
+
.dock > * { pointer-events: auto; }
|
|
260
|
+
.dock-bar { display: flex; align-items: center; gap: 4px; height: 44px; padding: 0 6px; max-width: 100%; }
|
|
261
|
+
.dock-sep { width: 1px; height: 20px; margin: 0 2px; flex: none; background: var(--kg-border-strong); }
|
|
262
|
+
.scope-btn {
|
|
263
|
+
display: inline-flex; align-items: center; gap: 6px; height: 32px; padding: 0 10px; border-radius: 9px; flex: none;
|
|
264
|
+
font-size: 12.5px; font-weight: 500; color: var(--kg-text-2); white-space: nowrap;
|
|
265
|
+
transition: background var(--kg-fast) var(--kg-ease), color var(--kg-fast) var(--kg-ease), box-shadow var(--kg-fast) var(--kg-ease);
|
|
266
|
+
}
|
|
267
|
+
.scope-btn:hover { color: var(--kg-text); background: var(--kg-hover); }
|
|
268
|
+
.scope-btn[aria-pressed="true"] { color: var(--kg-accent-text); background: var(--kg-accent-soft); box-shadow: inset 0 0 0 1px var(--kg-accent-soft); }
|
|
269
|
+
.scope-btn[aria-disabled="true"] { cursor: default; }
|
|
270
|
+
.context { display: inline-flex; align-items: center; gap: 8px; padding-left: 2px; flex: none; animation: reveal var(--kg-base) var(--kg-ease); }
|
|
271
|
+
@keyframes reveal { from { opacity: 0; transform: translateX(-6px); } }
|
|
272
|
+
.context .label { font-size: 12px; font-weight: 550; color: var(--kg-text-3); white-space: nowrap; }
|
|
273
|
+
.context .opts { display: inline-flex; gap: 2px; padding: 2px; border-radius: 8px; background: var(--kg-hover); }
|
|
274
|
+
.context .opt { width: 28px; height: 28px; border-radius: 6px; font-size: 12px; font-weight: 600; color: var(--kg-text-2); font-variant-numeric: tabular-nums; }
|
|
275
|
+
.context .opt:hover { color: var(--kg-text); }
|
|
276
|
+
.context .opt[aria-checked="true"] { background: var(--kg-raised); color: var(--kg-text); box-shadow: inset 0 0 0 1px var(--kg-border-strong); }
|
|
277
|
+
.context .expand { display: inline-flex; align-items: center; gap: 4px; height: 28px; padding: 0 10px 0 8px; border-radius: 7px; font-size: 12px; font-weight: 600; color: var(--kg-text-2); box-shadow: inset 0 0 0 1px var(--kg-border-strong); font-variant-numeric: tabular-nums; }
|
|
278
|
+
.context .expand:hover:not(:disabled) { color: var(--kg-text); background: var(--kg-hover); }
|
|
279
|
+
.context .expand .icon { width: 14px; height: 14px; }
|
|
280
|
+
/* Narrower canvases keep the dock clear of the legend and the minimap: labels give way to icons
|
|
281
|
+
(each control keeps its name as a tooltip). */
|
|
282
|
+
@container (max-width: 1180px) { .context .label { display: none; } }
|
|
283
|
+
@container (max-width: 1000px) { .dock-bar .seg .lbl { display: none; } .dock-bar .seg { padding: 0 9px; } }
|
|
284
|
+
@container (max-width: 640px) { .scope-btn .lbl { display: none; } .scope-btn { padding: 0 9px; } }
|
|
275
285
|
|
|
276
286
|
/* Swipe */
|
|
277
287
|
.swipe { position: absolute; inset: 0; pointer-events: none; }
|
|
@@ -291,14 +301,14 @@ ${familyRules}
|
|
|
291
301
|
.swipe .divider:hover .handle, .swipe .divider.dragging .handle { transform: scale(1.08); }
|
|
292
302
|
.swipe .divider:focus-visible { outline: none; }
|
|
293
303
|
.swipe .divider:focus-visible .handle { box-shadow: var(--kg-shadow), 0 0 0 2px var(--kg-accent), 0 0 0 5px var(--kg-accent-soft); }
|
|
294
|
-
|
|
304
|
+
/* Below the HUD's title and actions. */
|
|
305
|
+
.swipe .side-chip { position: absolute; top: 64px; pointer-events: none; }
|
|
295
306
|
.swipe .side-chip.before { left: 12px; }
|
|
296
307
|
.swipe .side-chip.after { right: 12px; }
|
|
297
308
|
|
|
298
|
-
/* Replay transport */
|
|
309
|
+
/* Replay transport (in the dock, clear of the legend and the minimap) */
|
|
299
310
|
.transport {
|
|
300
|
-
|
|
301
|
-
min-width: 360px;
|
|
311
|
+
width: min(600px, calc(100% - 464px)); min-width: min(360px, 100%);
|
|
302
312
|
display: flex; align-items: center; gap: 4px; height: 48px; padding: 0 8px;
|
|
303
313
|
}
|
|
304
314
|
.transport .play { width: 36px; height: 36px; border-radius: 10px; color: var(--kg-text); }
|
|
@@ -321,12 +331,12 @@ ${familyRules}
|
|
|
321
331
|
display: flex; align-items: center; gap: 6px;
|
|
322
332
|
}
|
|
323
333
|
.finished {
|
|
324
|
-
|
|
334
|
+
display: flex; align-items: center; gap: 10px;
|
|
325
335
|
height: 36px; padding: 0 6px 0 12px; font-size: 12.5px; font-weight: 550; color: var(--kg-text);
|
|
326
336
|
animation: rise var(--kg-base) var(--kg-ease);
|
|
327
337
|
}
|
|
328
338
|
.finished .icon { color: var(--kg-accent-text); }
|
|
329
|
-
@keyframes rise { from { opacity: 0; transform:
|
|
339
|
+
@keyframes rise { from { opacity: 0; transform: translateY(6px); } }
|
|
330
340
|
|
|
331
341
|
/* Tooltip */
|
|
332
342
|
.tooltip {
|
|
@@ -424,7 +434,7 @@ ${familyRules}
|
|
|
424
434
|
transition: background var(--kg-fast) var(--kg-ease), box-shadow var(--kg-fast) var(--kg-ease), color var(--kg-fast) var(--kg-ease);
|
|
425
435
|
}
|
|
426
436
|
.fchip:hover { background: var(--kg-hover); color: var(--kg-text); }
|
|
427
|
-
.fchip .
|
|
437
|
+
.fchip .swatch { width: 16px; height: 16px; margin: 0 -1px; }
|
|
428
438
|
.fchip .n { color: var(--kg-text-3); font-variant-numeric: tabular-nums; font-weight: 500; }
|
|
429
439
|
.fchip[aria-pressed="true"] { background: var(--fam-soft); color: var(--fam-text); box-shadow: inset 0 0 0 1px var(--fam-base); }
|
|
430
440
|
.fchip[aria-pressed="true"] .n { color: var(--fam-text); opacity: 0.8; }
|
|
@@ -578,7 +588,7 @@ ins.hl { text-decoration: none; background: var(--kg-added-soft); color: var(--k
|
|
|
578
588
|
|
|
579
589
|
/* ── Popovers ────────────────────────────────────────────────────────────── */
|
|
580
590
|
.menu {
|
|
581
|
-
position: absolute; z-index: 10; top:
|
|
591
|
+
position: absolute; z-index: 10; top: calc(100% + 8px); right: 0; width: 240px; padding: 6px; border-radius: 12px;
|
|
582
592
|
animation: pop var(--kg-fast) var(--kg-ease);
|
|
583
593
|
}
|
|
584
594
|
@keyframes pop { from { opacity: 0; transform: translateY(-4px) scale(0.98); } }
|
|
@@ -607,8 +617,7 @@ ins.hl { text-decoration: none; background: var(--kg-added-soft); color: var(--k
|
|
|
607
617
|
.key-row .ks { display: inline-flex; align-items: center; gap: 3px; flex: none; color: var(--kg-text-3); font-size: 11px; }
|
|
608
618
|
|
|
609
619
|
/* ── chrome="none": the graph, its hover tooltip and the legend line only ──── */
|
|
610
|
-
.root[data-chrome="none"] .
|
|
611
|
-
.root[data-chrome="none"] .transport, .root[data-chrome="none"] .finished, .root[data-chrome="none"] .stepper,
|
|
620
|
+
.root[data-chrome="none"] .hud, .root[data-chrome="none"] .sidebar, .root[data-chrome="none"] .dock,
|
|
612
621
|
.root[data-chrome="none"] .map-cluster, .root[data-chrome="none"] .side-chip { display: none !important; }
|
|
613
622
|
.stage-wrap[data-selectable="false"] .tooltip .t-hint { display: none; }
|
|
614
623
|
|
|
@@ -627,20 +636,17 @@ ins.hl { text-decoration: none; background: var(--kg-added-soft); color: var(--k
|
|
|
627
636
|
}
|
|
628
637
|
.root[data-compact="true"] .sheet-handle::before { content: ""; width: 36px; height: 4px; border-radius: 2px; background: var(--kg-control-border); }
|
|
629
638
|
.root[data-compact="true"] .sb-head { padding-top: 4px; }
|
|
630
|
-
.root[data-compact="true"] .
|
|
631
|
-
.root[data-compact="true"] .seg { padding: 0 9px; }
|
|
632
|
-
.root[data-compact="true"] .versions, .root[data-compact="true"] .header-sep, .root[data-compact="true"] .stats,
|
|
639
|
+
.root[data-compact="true"] .versions, .root[data-compact="true"] .header-sep,
|
|
633
640
|
.root[data-compact="true"] .brand-name { display: none; }
|
|
634
|
-
.root[data-compact="true"] .
|
|
635
|
-
.root[data-compact="true"] .header-end { flex: none; }
|
|
641
|
+
.root[data-compact="true"] .hud-title { padding: 0 10px; }
|
|
636
642
|
.root[data-compact="true"] .minimap-host, .root[data-compact="true"] .legend { display: none; }
|
|
637
|
-
/* Zoom controls move
|
|
638
|
-
.root[data-compact="true"] .map-cluster { top:
|
|
639
|
-
|
|
643
|
+
/* Zoom controls move under the actions, clear of the full-width replay transport. */
|
|
644
|
+
.root[data-compact="true"] .map-cluster { top: 64px; bottom: auto; right: 12px; }
|
|
645
|
+
/* The bottom sheet's rounded top overlaps the canvas. */
|
|
646
|
+
.root[data-compact="true"] .chrome > .dock { left: 12px; right: 12px; bottom: 26px; }
|
|
647
|
+
.root[data-compact="true"] .transport { width: 100%; min-width: 0; }
|
|
640
648
|
.root[data-compact="true"] .transport .pos { min-width: 48px; }
|
|
641
649
|
.root[data-compact="true"] .detail { max-height: 50%; }
|
|
642
|
-
.root[data-compact="true"] .caption { max-width: calc(100% - 24px); }
|
|
643
|
-
.root[data-narrow="true"] .stats { display: none; }
|
|
644
650
|
.root[data-narrow="true"] .versions .tag, .root[data-narrow="true"] .versions .arrow { display: none; }
|
|
645
651
|
|
|
646
652
|
@media (prefers-reduced-motion: reduce) {
|
package/src/ui/context.ts
CHANGED
|
@@ -12,6 +12,8 @@ export interface UiContext {
|
|
|
12
12
|
readonly controller: ViewerController;
|
|
13
13
|
readonly tr: () => Translator;
|
|
14
14
|
setMode(mode: ViewerMode): void;
|
|
15
|
+
/** Toggle the `changes` scope (explains itself when it is locked). */
|
|
16
|
+
toggleScope(): void;
|
|
15
17
|
setThemePreference(pref: ThemePreference): void;
|
|
16
18
|
setPalette(palette: PaletteName): void;
|
|
17
19
|
setLocale(locale: Locale): void;
|