@kgdiff/viewer 0.4.0 → 0.6.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 +43 -16
- package/custom-elements.json +2347 -0
- package/dist/bind.d.ts +41 -0
- package/dist/bind.d.ts.map +1 -0
- package/dist/bind.js +47 -0
- package/dist/bind.js.map +1 -0
- package/dist/context.d.ts +42 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +87 -0
- package/dist/context.js.map +1 -0
- package/dist/controller.d.ts +26 -237
- package/dist/controller.d.ts.map +1 -1
- package/dist/controller.js +33 -1484
- package/dist/controller.js.map +1 -1
- package/dist/dom.d.ts +10 -0
- package/dist/dom.d.ts.map +1 -1
- package/dist/dom.js +29 -0
- package/dist/dom.js.map +1 -1
- package/dist/element.d.ts +51 -42
- package/dist/element.d.ts.map +1 -1
- package/dist/element.js +381 -602
- package/dist/element.js.map +1 -1
- package/dist/elements/actions.d.ts +27 -0
- package/dist/elements/actions.d.ts.map +1 -0
- package/dist/elements/actions.js +81 -0
- package/dist/elements/actions.js.map +1 -0
- package/dist/elements/bound.d.ts +63 -0
- package/dist/elements/bound.d.ts.map +1 -0
- package/dist/elements/bound.js +213 -0
- package/dist/elements/bound.js.map +1 -0
- package/dist/elements/change-detail.d.ts +25 -0
- package/dist/elements/change-detail.d.ts.map +1 -0
- package/dist/elements/change-detail.js +19 -0
- package/dist/elements/change-detail.js.map +1 -0
- package/dist/elements/change-list.d.ts +27 -0
- package/dist/elements/change-list.d.ts.map +1 -0
- package/dist/elements/change-list.js +29 -0
- package/dist/elements/change-list.js.map +1 -0
- package/dist/elements/filters.d.ts +25 -0
- package/dist/elements/filters.d.ts.map +1 -0
- package/dist/elements/filters.js +19 -0
- package/dist/elements/filters.js.map +1 -0
- package/dist/elements/legend.d.ts +40 -0
- package/dist/elements/legend.d.ts.map +1 -0
- package/dist/elements/legend.js +87 -0
- package/dist/elements/legend.js.map +1 -0
- package/dist/elements/minimap.d.ts +38 -0
- package/dist/elements/minimap.d.ts.map +1 -0
- package/dist/elements/minimap.js +59 -0
- package/dist/elements/minimap.js.map +1 -0
- package/dist/elements/root.d.ts +40 -0
- package/dist/elements/root.d.ts.map +1 -0
- package/dist/elements/root.js +67 -0
- package/dist/elements/root.js.map +1 -0
- package/dist/elements/search.d.ts +29 -0
- package/dist/elements/search.d.ts.map +1 -0
- package/dist/elements/search.js +23 -0
- package/dist/elements/search.js.map +1 -0
- package/dist/elements/stage-context.d.ts +31 -0
- package/dist/elements/stage-context.d.ts.map +1 -0
- package/dist/elements/stage-context.js +4 -0
- package/dist/elements/stage-context.js.map +1 -0
- package/dist/elements/stage.d.ts +65 -0
- package/dist/elements/stage.d.ts.map +1 -0
- package/dist/elements/stage.js +259 -0
- package/dist/elements/stage.js.map +1 -0
- package/dist/elements/title.d.ts +25 -0
- package/dist/elements/title.d.ts.map +1 -0
- package/dist/elements/title.js +19 -0
- package/dist/elements/title.js.map +1 -0
- package/dist/elements/transport.d.ts +29 -0
- package/dist/elements/transport.d.ts.map +1 -0
- package/dist/elements/transport.js +24 -0
- package/dist/elements/transport.js.map +1 -0
- package/dist/elements/view-bound.d.ts +39 -0
- package/dist/elements/view-bound.d.ts.map +1 -0
- package/dist/elements/view-bound.js +88 -0
- package/dist/elements/view-bound.js.map +1 -0
- package/dist/elements/view-dock.d.ts +28 -0
- package/dist/elements/view-dock.d.ts.map +1 -0
- package/dist/elements/view-dock.js +28 -0
- package/dist/elements/view-dock.js.map +1 -0
- package/dist/elements/zoom.d.ts +28 -0
- package/dist/elements/zoom.d.ts.map +1 -0
- package/dist/elements/zoom.js +22 -0
- package/dist/elements/zoom.js.map +1 -0
- package/dist/i18n.d.ts +10 -0
- package/dist/i18n.d.ts.map +1 -1
- package/dist/i18n.js +20 -0
- package/dist/i18n.js.map +1 -1
- package/dist/icons.d.ts +1 -0
- package/dist/icons.d.ts.map +1 -1
- package/dist/icons.js +1 -0
- package/dist/icons.js.map +1 -1
- package/dist/index.d.ts +331 -36
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -1
- package/dist/keyboard.d.ts +39 -2
- package/dist/keyboard.d.ts.map +1 -1
- package/dist/keyboard.js +145 -62
- package/dist/keyboard.js.map +1 -1
- package/dist/layout-store.d.ts +26 -0
- package/dist/layout-store.d.ts.map +1 -0
- package/dist/layout-store.js +46 -0
- package/dist/layout-store.js.map +1 -0
- package/dist/panel-levels.d.ts +18 -0
- package/dist/panel-levels.d.ts.map +1 -0
- package/dist/panel-levels.js +41 -0
- package/dist/panel-levels.js.map +1 -0
- package/dist/panels.d.ts +28 -0
- package/dist/panels.d.ts.map +1 -0
- package/dist/panels.js +73 -0
- package/dist/panels.js.map +1 -0
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +3 -2
- package/dist/pipeline.js.map +1 -1
- package/dist/prefs.d.ts +5 -2
- package/dist/prefs.d.ts.map +1 -1
- package/dist/prefs.js +12 -3
- package/dist/prefs.js.map +1 -1
- package/dist/react.d.ts +460 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +2 -0
- package/dist/react.js.map +1 -0
- package/dist/root-element.d.ts +122 -0
- package/dist/root-element.d.ts.map +1 -0
- package/dist/root-element.js +541 -0
- package/dist/root-element.js.map +1 -0
- package/dist/root.d.ts +54 -0
- package/dist/root.d.ts.map +1 -0
- package/dist/root.js +288 -0
- package/dist/root.js.map +1 -0
- package/dist/schedule.d.ts +22 -0
- package/dist/schedule.d.ts.map +1 -0
- package/dist/schedule.js +41 -0
- package/dist/schedule.js.map +1 -0
- package/dist/session.d.ts +269 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +1237 -0
- package/dist/session.js.map +1 -0
- package/dist/standalone/kgdiff-viewer-compute.js +1 -1
- package/dist/standalone/kgdiff-viewer.js +4 -4
- package/dist/styles.d.ts +17 -6
- package/dist/styles.d.ts.map +1 -1
- package/dist/styles.js +367 -258
- package/dist/styles.js.map +1 -1
- package/dist/theme.d.ts +32 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +152 -0
- package/dist/theme.js.map +1 -0
- package/dist/topics.d.ts +8 -0
- package/dist/topics.d.ts.map +1 -0
- package/dist/topics.js +18 -0
- package/dist/topics.js.map +1 -0
- package/dist/ui/change-list.d.ts +18 -0
- package/dist/ui/change-list.d.ts.map +1 -0
- package/dist/ui/change-list.js +331 -0
- package/dist/ui/change-list.js.map +1 -0
- package/dist/ui/context.d.ts +34 -7
- package/dist/ui/context.d.ts.map +1 -1
- package/dist/ui/context.js.map +1 -1
- package/dist/ui/filters.d.ts +9 -0
- package/dist/ui/filters.d.ts.map +1 -0
- package/dist/ui/filters.js +68 -0
- package/dist/ui/filters.js.map +1 -0
- package/dist/ui/header.d.ts +41 -3
- package/dist/ui/header.d.ts.map +1 -1
- package/dist/ui/header.js +101 -36
- package/dist/ui/header.js.map +1 -1
- package/dist/ui/legend.d.ts +15 -0
- package/dist/ui/legend.d.ts.map +1 -0
- package/dist/ui/legend.js +68 -0
- package/dist/ui/legend.js.map +1 -0
- package/dist/ui/popovers.d.ts +3 -1
- package/dist/ui/popovers.d.ts.map +1 -1
- package/dist/ui/popovers.js +62 -23
- package/dist/ui/popovers.js.map +1 -1
- package/dist/ui/search.d.ts +13 -0
- package/dist/ui/search.d.ts.map +1 -0
- package/dist/ui/search.js +192 -0
- package/dist/ui/search.js.map +1 -0
- package/dist/ui/sidebar.d.ts +32 -17
- package/dist/ui/sidebar.d.ts.map +1 -1
- package/dist/ui/sidebar.js +327 -519
- package/dist/ui/sidebar.js.map +1 -1
- package/dist/ui/stage.d.ts +56 -1
- package/dist/ui/stage.d.ts.map +1 -1
- package/dist/ui/stage.js +244 -256
- package/dist/ui/stage.js.map +1 -1
- package/dist/ui/swatch.js +1 -1
- package/dist/ui/swatch.js.map +1 -1
- package/dist/ui/transport.d.ts +12 -0
- package/dist/ui/transport.d.ts.map +1 -0
- package/dist/ui/transport.js +198 -0
- package/dist/ui/transport.js.map +1 -0
- package/dist/ui/zoom.d.ts +15 -0
- package/dist/ui/zoom.d.ts.map +1 -0
- package/dist/ui/zoom.js +30 -0
- package/dist/ui/zoom.js.map +1 -0
- package/dist/view.d.ts +94 -0
- package/dist/view.d.ts.map +1 -0
- package/dist/view.js +441 -0
- package/dist/view.js.map +1 -0
- package/package.json +95 -12
- package/src/bind.ts +80 -0
- package/src/context.ts +106 -0
- package/src/controller.ts +52 -1630
- package/src/dom.ts +30 -0
- package/src/element.ts +386 -619
- package/src/elements/actions.ts +94 -0
- package/src/elements/bound.ts +247 -0
- package/src/elements/change-detail.ts +34 -0
- package/src/elements/change-list.ts +51 -0
- package/src/elements/filters.ts +34 -0
- package/src/elements/legend.ts +103 -0
- package/src/elements/minimap.ts +75 -0
- package/src/elements/root.ts +86 -0
- package/src/elements/search.ts +38 -0
- package/src/elements/stage-context.ts +34 -0
- package/src/elements/stage.ts +285 -0
- package/src/elements/title.ts +34 -0
- package/src/elements/transport.ts +39 -0
- package/src/elements/view-bound.ts +118 -0
- package/src/elements/view-dock.ts +43 -0
- package/src/elements/zoom.ts +38 -0
- package/src/i18n.ts +20 -0
- package/src/icons.ts +1 -0
- package/src/index.ts +350 -35
- package/src/keyboard.ts +188 -61
- package/src/layout-store.ts +71 -0
- package/src/panel-levels.ts +53 -0
- package/src/panels.ts +81 -0
- package/src/pipeline.ts +4 -1
- package/src/prefs.ts +12 -4
- package/src/react.ts +459 -0
- package/src/root-element.ts +624 -0
- package/src/root.ts +335 -0
- package/src/schedule.ts +43 -0
- package/src/session.ts +1375 -0
- package/src/styles.ts +396 -263
- package/src/theme.ts +170 -0
- package/src/topics.ts +37 -0
- package/src/ui/change-list.ts +405 -0
- package/src/ui/context.ts +36 -7
- package/src/ui/filters.ts +79 -0
- package/src/ui/header.ts +129 -58
- package/src/ui/legend.ts +112 -0
- package/src/ui/popovers.ts +68 -25
- package/src/ui/search.ts +225 -0
- package/src/ui/sidebar.ts +362 -639
- package/src/ui/stage.ts +267 -331
- package/src/ui/swatch.ts +1 -1
- package/src/ui/transport.ts +240 -0
- package/src/ui/zoom.ts +45 -0
- package/src/view.ts +516 -0
- package/vscode.html-custom-data.json +726 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `<kgdiff-viewer>` (the default layout) and `<kgdiff-root>` share (RFC 0001 §2–§5): an
|
|
3
|
+
* element that is a root. It owns the session (`ViewerRoot`) and provides it to the regions and
|
|
4
|
+
* elements bound to it, and carries everything about the session the host sets or reads: the
|
|
5
|
+
* preferences as attributes and properties, the payload, the public methods, the events, the
|
|
6
|
+
* keyboard map and its scope, deep links and the hover hold of a looping replay.
|
|
7
|
+
*/
|
|
8
|
+
import { type Delta, type Locale } from '@kgdiff/core';
|
|
9
|
+
import type { PaletteName } from '@kgdiff/scene';
|
|
10
|
+
import type { FitOptions, LoadOptions, ReportPayload, ReviewState, ThemePreference, ViewerMode, ViewerScope } from './index.js';
|
|
11
|
+
import { type Keymap } from './keyboard.js';
|
|
12
|
+
import { LEGACY_CHANGES_MODE } from './prefs.js';
|
|
13
|
+
import { ViewerRoot } from './root.js';
|
|
14
|
+
import { type DiffSession, type SessionDeps, type SessionHost, type Topic } from './session.js';
|
|
15
|
+
import type { UiActions } from './ui/context.js';
|
|
16
|
+
/** Attributes every root observes. */
|
|
17
|
+
export declare const ROOT_ATTRIBUTES: readonly ['mode', 'scope', 'theme', 'palette', 'locale', 'sync-hash', 'loop', 'loop-delay', 'pause-on-hover', 'shortcuts', 'keymap'];
|
|
18
|
+
/** Where keyboard shortcuts apply (`shortcuts`). */
|
|
19
|
+
export type ShortcutScope = 'scoped' | 'global' | 'none';
|
|
20
|
+
/**
|
|
21
|
+
* @attr {boolean} sync-hash - Keep the selection, mode and scope in the page's URL hash
|
|
22
|
+
* (`#kgdiff=hunk:<id>;mode:<mode>;scope:<scope>`) and follow it: deep links.
|
|
23
|
+
* @attr {boolean} global-shortcuts - The same as `shortcuts="global"`.
|
|
24
|
+
*/
|
|
25
|
+
export declare abstract class RootElement<S extends DiffSession = DiffSession> extends HTMLElement {
|
|
26
|
+
#private;
|
|
27
|
+
/** @internal */
|
|
28
|
+
readonly controller: S;
|
|
29
|
+
/** @internal The root: session, keyboard map, live region, events, tokens. */
|
|
30
|
+
protected readonly root: ViewerRoot<S>;
|
|
31
|
+
protected readonly shadow: ShadowRoot;
|
|
32
|
+
constructor();
|
|
33
|
+
/** What the layout does for the regions (`UiActions`); its closures run later. */
|
|
34
|
+
protected abstract actions(): UiActions;
|
|
35
|
+
/** Connected: build and attach what the element shows (before the first render). */
|
|
36
|
+
protected abstract connected(): void;
|
|
37
|
+
/** Disconnected (after the root stopped providing). */
|
|
38
|
+
protected abstract disconnected(): void;
|
|
39
|
+
/** An observed attribute that is not a root attribute changed (from `_old`). */
|
|
40
|
+
protected attributeChanged(_name: string, _value: string | null, _old: string | null): void;
|
|
41
|
+
/** The session this root holds (the default layout's has its canvas built in). */
|
|
42
|
+
protected abstract createSession(host: SessionHost, deps: SessionDeps): S;
|
|
43
|
+
/** The sheets the shadow root adopts before the token set. */
|
|
44
|
+
protected abstract sheets(doc: Document): readonly CSSStyleSheet[];
|
|
45
|
+
/** Properties of the subclass a page may set before the element is defined. */
|
|
46
|
+
protected upgradedProperties(): readonly string[];
|
|
47
|
+
/** Whether keys do anything at all (`chrome="none"` turns them off in the default layout). */
|
|
48
|
+
protected keysEnabled(): boolean;
|
|
49
|
+
/** A key reached the root: return true when the layout took it (an open popover). */
|
|
50
|
+
protected interceptKey(_e: KeyboardEvent): boolean;
|
|
51
|
+
connectedCallback(): void;
|
|
52
|
+
disconnectedCallback(): void;
|
|
53
|
+
attributeChangedCallback(name: string, old: string | null, value: string | null): void;
|
|
54
|
+
/**
|
|
55
|
+
* Apply a preference from an attribute or a property. Valid values are explicit (a report's
|
|
56
|
+
* `view` does not override them); invalid ones fall back to the default (`overlay`, `all`,
|
|
57
|
+
* `auto`, `default`, the browser's language). Properties reflect the normalized value; the
|
|
58
|
+
* legacy mode `changes` reflects as `overlay` + scope `changes`.
|
|
59
|
+
*/
|
|
60
|
+
protected applyPreference(name: 'mode' | 'scope' | 'theme' | 'palette' | 'locale', raw: unknown, reflect: boolean): void;
|
|
61
|
+
get mode(): ViewerMode;
|
|
62
|
+
set mode(value: ViewerMode | typeof LEGACY_CHANGES_MODE);
|
|
63
|
+
get scope(): ViewerScope;
|
|
64
|
+
set scope(value: ViewerScope);
|
|
65
|
+
get theme(): ThemePreference;
|
|
66
|
+
set theme(value: ThemePreference);
|
|
67
|
+
get palette(): PaletteName;
|
|
68
|
+
set palette(value: PaletteName);
|
|
69
|
+
get locale(): Locale;
|
|
70
|
+
set locale(value: Locale);
|
|
71
|
+
get loop(): boolean;
|
|
72
|
+
set loop(value: boolean);
|
|
73
|
+
get loopDelay(): number;
|
|
74
|
+
set loopDelay(value: number);
|
|
75
|
+
get pauseOnHover(): boolean;
|
|
76
|
+
set pauseOnHover(value: boolean);
|
|
77
|
+
/**
|
|
78
|
+
* Where the keyboard shortcuts apply. `scoped` (the default): while focus is inside the root
|
|
79
|
+
* (keys of elements bound with `for` are forwarded to it). `global`: also when nothing on the
|
|
80
|
+
* page has focus, for the first such root, except the keys pages scroll with; the boolean
|
|
81
|
+
* `global-shortcuts` attribute is an alias. `none`: no shortcuts (a focused change list keeps
|
|
82
|
+
* its arrow keys).
|
|
83
|
+
*/
|
|
84
|
+
get shortcuts(): ShortcutScope;
|
|
85
|
+
set shortcuts(value: ShortcutScope);
|
|
86
|
+
/**
|
|
87
|
+
* Keys bound to other shortcuts, or unbound with null: `{ x: null, r: 'review' }`, or as the
|
|
88
|
+
* `keymap` attribute `"x:none r:review"`. Keys not named keep the default map.
|
|
89
|
+
*/
|
|
90
|
+
get keymap(): Keymap | null;
|
|
91
|
+
set keymap(value: Keymap | string | null);
|
|
92
|
+
get payload(): ReportPayload | null;
|
|
93
|
+
set payload(value: ReportPayload | null);
|
|
94
|
+
/** A valid payload was set, before it loads. */
|
|
95
|
+
protected payloadSet(_payload: ReportPayload): void;
|
|
96
|
+
load(options: LoadOptions): Promise<void>;
|
|
97
|
+
selectHunk(id: string | null): void;
|
|
98
|
+
next(): void;
|
|
99
|
+
prev(): void;
|
|
100
|
+
play(): void;
|
|
101
|
+
pause(): void;
|
|
102
|
+
fit(options?: FitOptions): Promise<void>;
|
|
103
|
+
exportDelta(): Delta;
|
|
104
|
+
reviewState(): ReviewState;
|
|
105
|
+
setReviewed(hunkId: string, reviewed: boolean): void;
|
|
106
|
+
/** Show an error state (used for undecodable embedded payloads). */
|
|
107
|
+
showError(error: Error): void;
|
|
108
|
+
/**
|
|
109
|
+
* Draw the canvas with the `--kgdiff-*` tokens as computed now. Call it after changing tokens
|
|
110
|
+
* in a way the root cannot observe (a class on an ancestor, a stylesheet); theme, palette and
|
|
111
|
+
* system color scheme changes are followed by themselves.
|
|
112
|
+
*/
|
|
113
|
+
refreshTheme(): void;
|
|
114
|
+
protected reflect(name: string, value: string | null): void;
|
|
115
|
+
/** The actions every root has: preferences reflect as attributes. */
|
|
116
|
+
protected baseActions(): Pick<UiActions, 'tr' | 'setMode' | 'toggleScope' | 'setThemePreference' | 'setPalette' | 'setLocale'>;
|
|
117
|
+
/** The pointer entered or left the graph (holds a looping replay with `pause-on-hover`). */
|
|
118
|
+
protected pointerOver(over: boolean): void;
|
|
119
|
+
/** Reflect what the session changed (mode, scope, theme) back to the attributes. */
|
|
120
|
+
protected reflectSession(topics: ReadonlySet<Topic>): void;
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=root-element.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"root-element.d.ts","sourceRoot":"","sources":["../src/root-element.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `<kgdiff-viewer>` (the default layout) and `<kgdiff-root>` share (RFC 0001 §2–§5): an
|
|
3
|
+
* element that is a root. It owns the session (`ViewerRoot`) and provides it to the regions and
|
|
4
|
+
* elements bound to it, and carries everything about the session the host sets or reads: the
|
|
5
|
+
* preferences as attributes and properties, the payload, the public methods, the events, the
|
|
6
|
+
* keyboard map and its scope, deep links and the hover hold of a looping replay.
|
|
7
|
+
*/
|
|
8
|
+
import { KgdiffError } from '@kgdiff/core';
|
|
9
|
+
import { formatHash, parseHash } from './hash-sync.js';
|
|
10
|
+
import { detectLocale, translate, translator } from './i18n.js';
|
|
11
|
+
import { toKeymap } from './keyboard.js';
|
|
12
|
+
import { assertPayload } from './payload.js';
|
|
13
|
+
import { DEFAULT_LOOP_DELAY, LEGACY_CHANGES_MODE, sanitizeView, toDelay, toLocale, toMode, toPalette, toScope, toTheme, } from './prefs.js';
|
|
14
|
+
import { isRoot, ViewerRoot, wasForwarded } from './root.js';
|
|
15
|
+
import { ALL_TOPICS, } from './session.js';
|
|
16
|
+
/** Attributes every root observes. */
|
|
17
|
+
export const ROOT_ATTRIBUTES = [
|
|
18
|
+
'mode',
|
|
19
|
+
'scope',
|
|
20
|
+
'theme',
|
|
21
|
+
'palette',
|
|
22
|
+
'locale',
|
|
23
|
+
'sync-hash',
|
|
24
|
+
'loop',
|
|
25
|
+
'loop-delay',
|
|
26
|
+
'pause-on-hover',
|
|
27
|
+
'shortcuts',
|
|
28
|
+
'keymap',
|
|
29
|
+
];
|
|
30
|
+
const SCOPES = ['scoped', 'global', 'none'];
|
|
31
|
+
/** Properties a page may set before the element is defined (they shadow the accessors). */
|
|
32
|
+
const UPGRADED = [
|
|
33
|
+
'mode',
|
|
34
|
+
'scope',
|
|
35
|
+
'theme',
|
|
36
|
+
'palette',
|
|
37
|
+
'locale',
|
|
38
|
+
'loop',
|
|
39
|
+
'loopDelay',
|
|
40
|
+
'pauseOnHover',
|
|
41
|
+
'payload',
|
|
42
|
+
'shortcuts',
|
|
43
|
+
'keymap',
|
|
44
|
+
];
|
|
45
|
+
/** Keys the page itself scrolls with: never taken at document level. */
|
|
46
|
+
const PAGE_KEYS = new Set([
|
|
47
|
+
'ArrowUp',
|
|
48
|
+
'ArrowDown',
|
|
49
|
+
'ArrowLeft',
|
|
50
|
+
'ArrowRight',
|
|
51
|
+
'Home',
|
|
52
|
+
'End',
|
|
53
|
+
'PageUp',
|
|
54
|
+
'PageDown',
|
|
55
|
+
' ',
|
|
56
|
+
'Spacebar',
|
|
57
|
+
]);
|
|
58
|
+
/** Connected roots, in connection order (`global-shortcuts` goes to the first opted-in one). */
|
|
59
|
+
const connected = [];
|
|
60
|
+
function deepFreeze(value) {
|
|
61
|
+
if (typeof value === 'object' && value !== null && !Object.isFrozen(value)) {
|
|
62
|
+
for (const v of Object.values(value))
|
|
63
|
+
deepFreeze(v);
|
|
64
|
+
Object.freeze(value);
|
|
65
|
+
}
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* @attr {boolean} sync-hash - Keep the selection, mode and scope in the page's URL hash
|
|
70
|
+
* (`#kgdiff=hunk:<id>;mode:<mode>;scope:<scope>`) and follow it: deep links.
|
|
71
|
+
* @attr {boolean} global-shortcuts - The same as `shortcuts="global"`.
|
|
72
|
+
*/
|
|
73
|
+
export class RootElement extends HTMLElement {
|
|
74
|
+
/** @internal */
|
|
75
|
+
controller;
|
|
76
|
+
/** @internal The root: session, keyboard map, live region, events, tokens. */
|
|
77
|
+
root;
|
|
78
|
+
shadow;
|
|
79
|
+
#currentPayload = null;
|
|
80
|
+
#reflecting = false;
|
|
81
|
+
#cleanup = [];
|
|
82
|
+
#lastHash = '';
|
|
83
|
+
#upgraded = false;
|
|
84
|
+
/** A mouse or pen rests on the graph (holds a looping replay with `pause-on-hover`). */
|
|
85
|
+
#pointerOver = false;
|
|
86
|
+
constructor() {
|
|
87
|
+
super();
|
|
88
|
+
this.shadow = this.attachShadow({ mode: 'open' });
|
|
89
|
+
this.root = new ViewerRoot(this, this.shadow, this.actions(), (type) => {
|
|
90
|
+
if (type === 'kgdiff-select' || type === 'kgdiff-mode')
|
|
91
|
+
this.#writeHash();
|
|
92
|
+
}, {
|
|
93
|
+
session: (host, deps) => this.createSession(host, deps),
|
|
94
|
+
sheets: (doc) => this.sheets(doc),
|
|
95
|
+
key: (e) => this.#key(e),
|
|
96
|
+
hover: (over) => this.pointerOver(over),
|
|
97
|
+
});
|
|
98
|
+
this.controller = this.root.controller;
|
|
99
|
+
this.controller.locale = toLocale(this.getAttribute('locale')) ?? detectLocale();
|
|
100
|
+
}
|
|
101
|
+
/** An observed attribute that is not a root attribute changed (from `_old`). */
|
|
102
|
+
attributeChanged(_name, _value, _old) { }
|
|
103
|
+
/** Properties of the subclass a page may set before the element is defined. */
|
|
104
|
+
upgradedProperties() {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
/** Whether keys do anything at all (`chrome="none"` turns them off in the default layout). */
|
|
108
|
+
keysEnabled() {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
/** A key reached the root: return true when the layout took it (an open popover). */
|
|
112
|
+
interceptKey(_e) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
// ─── Lifecycle ────────────────────────────────────────────────────────────
|
|
116
|
+
connectedCallback() {
|
|
117
|
+
if (!this.#upgraded) {
|
|
118
|
+
this.#upgraded = true;
|
|
119
|
+
// Properties set before the element was defined shadow the accessors: re-apply them.
|
|
120
|
+
for (const prop of [...UPGRADED, ...this.upgradedProperties()]) {
|
|
121
|
+
if (Object.hasOwn(this, prop)) {
|
|
122
|
+
const value = this[prop];
|
|
123
|
+
delete this[prop];
|
|
124
|
+
this[prop] = value;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Provide the session (the regions bind to it) and follow the system's preferences.
|
|
129
|
+
this.#cleanup.push(this.root.connect());
|
|
130
|
+
this.connected();
|
|
131
|
+
connected.push(this);
|
|
132
|
+
const onKey = (e) => this.#onKeyDown(e);
|
|
133
|
+
document.addEventListener('keydown', onKey);
|
|
134
|
+
this.#cleanup.push(() => document.removeEventListener('keydown', onKey));
|
|
135
|
+
const onHash = () => this.#readHash();
|
|
136
|
+
window.addEventListener('hashchange', onHash);
|
|
137
|
+
this.#cleanup.push(() => window.removeEventListener('hashchange', onHash));
|
|
138
|
+
this.#readHash();
|
|
139
|
+
this.root.render(ALL_TOPICS);
|
|
140
|
+
}
|
|
141
|
+
disconnectedCallback() {
|
|
142
|
+
const i = connected.indexOf(this);
|
|
143
|
+
if (i >= 0)
|
|
144
|
+
connected.splice(i, 1);
|
|
145
|
+
for (const fn of this.#cleanup.splice(0))
|
|
146
|
+
fn();
|
|
147
|
+
this.disconnected();
|
|
148
|
+
// Moved while hovered: no pointerleave follows.
|
|
149
|
+
this.#pointerOver = false;
|
|
150
|
+
this.#applyHold();
|
|
151
|
+
}
|
|
152
|
+
attributeChangedCallback(name, old, value) {
|
|
153
|
+
if (this.#reflecting)
|
|
154
|
+
return;
|
|
155
|
+
switch (name) {
|
|
156
|
+
case 'mode':
|
|
157
|
+
case 'scope':
|
|
158
|
+
case 'theme':
|
|
159
|
+
case 'palette':
|
|
160
|
+
case 'locale':
|
|
161
|
+
this.applyPreference(name, value, false);
|
|
162
|
+
break;
|
|
163
|
+
case 'sync-hash':
|
|
164
|
+
if (value !== null)
|
|
165
|
+
this.#readHash();
|
|
166
|
+
break;
|
|
167
|
+
case 'loop':
|
|
168
|
+
case 'loop-delay':
|
|
169
|
+
this.controller.setLoop(this.loop ? this.loopDelay : null);
|
|
170
|
+
this.#applyHold();
|
|
171
|
+
break;
|
|
172
|
+
case 'pause-on-hover':
|
|
173
|
+
this.#applyHold();
|
|
174
|
+
break;
|
|
175
|
+
case 'shortcuts':
|
|
176
|
+
this.root.shortcutsOff = this.shortcuts === 'none';
|
|
177
|
+
break;
|
|
178
|
+
case 'keymap':
|
|
179
|
+
this.#setKeymap(value);
|
|
180
|
+
break;
|
|
181
|
+
default:
|
|
182
|
+
this.attributeChanged(name, value, old);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Apply a preference from an attribute or a property. Valid values are explicit (a report's
|
|
187
|
+
* `view` does not override them); invalid ones fall back to the default (`overlay`, `all`,
|
|
188
|
+
* `auto`, `default`, the browser's language). Properties reflect the normalized value; the
|
|
189
|
+
* legacy mode `changes` reflects as `overlay` + scope `changes`.
|
|
190
|
+
*/
|
|
191
|
+
applyPreference(name, raw, reflect) {
|
|
192
|
+
const c = this.controller;
|
|
193
|
+
// `mode="changes"` is rewritten to the attribute pair it stands for.
|
|
194
|
+
const legacy = name === 'mode' && raw === LEGACY_CHANGES_MODE;
|
|
195
|
+
let value;
|
|
196
|
+
switch (name) {
|
|
197
|
+
case 'mode': {
|
|
198
|
+
if (legacy) {
|
|
199
|
+
c.explicit.add('scope');
|
|
200
|
+
c.setView('overlay', 'changes');
|
|
201
|
+
value = 'overlay';
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
const v = toMode(raw);
|
|
205
|
+
c.setMode(v ?? 'overlay');
|
|
206
|
+
value = v;
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
case 'scope': {
|
|
210
|
+
const v = toScope(raw);
|
|
211
|
+
c.setScope(v ?? 'all');
|
|
212
|
+
// A locked scope stays `changes`: the attribute says what is shown.
|
|
213
|
+
value = v === null ? null : c.scope;
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
case 'theme': {
|
|
217
|
+
const v = toTheme(raw);
|
|
218
|
+
c.setThemePreference(v ?? 'auto');
|
|
219
|
+
value = v;
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
case 'palette': {
|
|
223
|
+
const v = toPalette(raw);
|
|
224
|
+
c.setPalette(v ?? 'default');
|
|
225
|
+
value = v;
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
case 'locale': {
|
|
229
|
+
const v = toLocale(raw);
|
|
230
|
+
c.setLocale(v ?? detectLocale());
|
|
231
|
+
value = v;
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
default:
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (value === null)
|
|
238
|
+
c.explicit.delete(name);
|
|
239
|
+
else
|
|
240
|
+
c.explicit.add(name);
|
|
241
|
+
if (reflect || legacy)
|
|
242
|
+
this.reflect(name, value);
|
|
243
|
+
if (legacy)
|
|
244
|
+
this.reflect('scope', c.scope);
|
|
245
|
+
}
|
|
246
|
+
// ─── Properties ───────────────────────────────────────────────────────────
|
|
247
|
+
get mode() {
|
|
248
|
+
return this.controller.mode;
|
|
249
|
+
}
|
|
250
|
+
set mode(value) {
|
|
251
|
+
this.applyPreference('mode', value, true);
|
|
252
|
+
}
|
|
253
|
+
get scope() {
|
|
254
|
+
return this.controller.scope;
|
|
255
|
+
}
|
|
256
|
+
set scope(value) {
|
|
257
|
+
this.applyPreference('scope', value, true);
|
|
258
|
+
}
|
|
259
|
+
get theme() {
|
|
260
|
+
return this.controller.themePreference;
|
|
261
|
+
}
|
|
262
|
+
set theme(value) {
|
|
263
|
+
this.applyPreference('theme', value, true);
|
|
264
|
+
}
|
|
265
|
+
get palette() {
|
|
266
|
+
return this.controller.palette;
|
|
267
|
+
}
|
|
268
|
+
set palette(value) {
|
|
269
|
+
this.applyPreference('palette', value, true);
|
|
270
|
+
}
|
|
271
|
+
get locale() {
|
|
272
|
+
return this.controller.locale;
|
|
273
|
+
}
|
|
274
|
+
set locale(value) {
|
|
275
|
+
this.applyPreference('locale', value, true);
|
|
276
|
+
}
|
|
277
|
+
get loop() {
|
|
278
|
+
return this.hasAttribute('loop');
|
|
279
|
+
}
|
|
280
|
+
set loop(value) {
|
|
281
|
+
this.toggleAttribute('loop', Boolean(value));
|
|
282
|
+
}
|
|
283
|
+
get loopDelay() {
|
|
284
|
+
return toDelay(this.getAttribute('loop-delay')) ?? DEFAULT_LOOP_DELAY;
|
|
285
|
+
}
|
|
286
|
+
set loopDelay(value) {
|
|
287
|
+
const v = toDelay(value);
|
|
288
|
+
if (v === null)
|
|
289
|
+
this.removeAttribute('loop-delay');
|
|
290
|
+
else
|
|
291
|
+
this.setAttribute('loop-delay', String(v));
|
|
292
|
+
}
|
|
293
|
+
get pauseOnHover() {
|
|
294
|
+
return this.getAttribute('pause-on-hover') !== 'false';
|
|
295
|
+
}
|
|
296
|
+
set pauseOnHover(value) {
|
|
297
|
+
if (value)
|
|
298
|
+
this.removeAttribute('pause-on-hover');
|
|
299
|
+
else
|
|
300
|
+
this.setAttribute('pause-on-hover', 'false');
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Where the keyboard shortcuts apply. `scoped` (the default): while focus is inside the root
|
|
304
|
+
* (keys of elements bound with `for` are forwarded to it). `global`: also when nothing on the
|
|
305
|
+
* page has focus, for the first such root, except the keys pages scroll with; the boolean
|
|
306
|
+
* `global-shortcuts` attribute is an alias. `none`: no shortcuts (a focused change list keeps
|
|
307
|
+
* its arrow keys).
|
|
308
|
+
*/
|
|
309
|
+
get shortcuts() {
|
|
310
|
+
const v = this.getAttribute('shortcuts');
|
|
311
|
+
if (v !== null && SCOPES.includes(v))
|
|
312
|
+
return v;
|
|
313
|
+
return this.hasAttribute('global-shortcuts') ? 'global' : 'scoped';
|
|
314
|
+
}
|
|
315
|
+
set shortcuts(value) {
|
|
316
|
+
if (SCOPES.includes(value))
|
|
317
|
+
this.setAttribute('shortcuts', value);
|
|
318
|
+
else
|
|
319
|
+
this.removeAttribute('shortcuts');
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Keys bound to other shortcuts, or unbound with null: `{ x: null, r: 'review' }`, or as the
|
|
323
|
+
* `keymap` attribute `"x:none r:review"`. Keys not named keep the default map.
|
|
324
|
+
*/
|
|
325
|
+
get keymap() {
|
|
326
|
+
return this.root.keymap;
|
|
327
|
+
}
|
|
328
|
+
set keymap(value) {
|
|
329
|
+
this.#setKeymap(value);
|
|
330
|
+
}
|
|
331
|
+
#setKeymap(value) {
|
|
332
|
+
const { keymap, invalid } = toKeymap(value);
|
|
333
|
+
if (invalid.length > 0)
|
|
334
|
+
console.warn(`[kgdiff] ignoring invalid keymap: ${invalid.join(' ')}`);
|
|
335
|
+
this.root.keymap = keymap;
|
|
336
|
+
}
|
|
337
|
+
get payload() {
|
|
338
|
+
return this.#currentPayload;
|
|
339
|
+
}
|
|
340
|
+
set payload(value) {
|
|
341
|
+
this.#currentPayload = value;
|
|
342
|
+
if (value === null || value === undefined)
|
|
343
|
+
return;
|
|
344
|
+
let payload;
|
|
345
|
+
try {
|
|
346
|
+
payload = assertPayload(value);
|
|
347
|
+
}
|
|
348
|
+
catch (error) {
|
|
349
|
+
this.controller.fail(error);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
// The report's theme, palette and language apply at once (the loading steps show in
|
|
353
|
+
// them), unless the host or the user chose (validated, and warned about, when it loads).
|
|
354
|
+
const view = sanitizeView(payload.view, true);
|
|
355
|
+
const c = this.controller;
|
|
356
|
+
if (view?.theme && !c.explicit.has('theme'))
|
|
357
|
+
c.setThemePreference(view.theme);
|
|
358
|
+
if (view?.palette && !c.explicit.has('palette'))
|
|
359
|
+
c.setPalette(view.palette);
|
|
360
|
+
if (view?.locale && !c.explicit.has('locale'))
|
|
361
|
+
c.setLocale(view.locale);
|
|
362
|
+
this.payloadSet(payload);
|
|
363
|
+
this.controller.loadPayload(payload).catch(() => {
|
|
364
|
+
// Already surfaced: error state + `kgdiff-error`.
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
/** A valid payload was set, before it loads. */
|
|
368
|
+
payloadSet(_payload) { }
|
|
369
|
+
// ─── Methods ──────────────────────────────────────────────────────────────
|
|
370
|
+
load(options) {
|
|
371
|
+
this.#currentPayload = null;
|
|
372
|
+
return this.controller.load(options);
|
|
373
|
+
}
|
|
374
|
+
selectHunk(id) {
|
|
375
|
+
this.controller.selectHunkById(id, { fly: true });
|
|
376
|
+
}
|
|
377
|
+
next() {
|
|
378
|
+
this.controller.next();
|
|
379
|
+
}
|
|
380
|
+
prev() {
|
|
381
|
+
this.controller.prev();
|
|
382
|
+
}
|
|
383
|
+
play() {
|
|
384
|
+
this.controller.play();
|
|
385
|
+
}
|
|
386
|
+
pause() {
|
|
387
|
+
this.controller.pause();
|
|
388
|
+
}
|
|
389
|
+
fit(options) {
|
|
390
|
+
return this.controller.fit(options);
|
|
391
|
+
}
|
|
392
|
+
exportDelta() {
|
|
393
|
+
const diff = this.controller.diff;
|
|
394
|
+
if (!diff)
|
|
395
|
+
throw new KgdiffError('invalid-input', translate('en', 'error.notLoaded'));
|
|
396
|
+
// A copy: callers can never mutate the diff the viewer shows.
|
|
397
|
+
return deepFreeze(structuredClone(diff.delta));
|
|
398
|
+
}
|
|
399
|
+
reviewState() {
|
|
400
|
+
return this.controller.reviewState();
|
|
401
|
+
}
|
|
402
|
+
setReviewed(hunkId, reviewed) {
|
|
403
|
+
this.controller.setReviewed(hunkId, reviewed);
|
|
404
|
+
}
|
|
405
|
+
/** Show an error state (used for undecodable embedded payloads). */
|
|
406
|
+
showError(error) {
|
|
407
|
+
this.controller.fail(error);
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Draw the canvas with the `--kgdiff-*` tokens as computed now. Call it after changing tokens
|
|
411
|
+
* in a way the root cannot observe (a class on an ancestor, a stylesheet); theme, palette and
|
|
412
|
+
* system color scheme changes are followed by themselves.
|
|
413
|
+
*/
|
|
414
|
+
refreshTheme() {
|
|
415
|
+
this.controller.refreshTheme();
|
|
416
|
+
}
|
|
417
|
+
// ─── Internals ────────────────────────────────────────────────────────────
|
|
418
|
+
reflect(name, value) {
|
|
419
|
+
if (this.getAttribute(name) === value)
|
|
420
|
+
return;
|
|
421
|
+
this.#reflecting = true;
|
|
422
|
+
try {
|
|
423
|
+
if (value === null)
|
|
424
|
+
this.removeAttribute(name);
|
|
425
|
+
else
|
|
426
|
+
this.setAttribute(name, value);
|
|
427
|
+
}
|
|
428
|
+
finally {
|
|
429
|
+
this.#reflecting = false;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
/** The actions every root has: preferences reflect as attributes. */
|
|
433
|
+
baseActions() {
|
|
434
|
+
return {
|
|
435
|
+
tr: () => translator(this.controller.locale),
|
|
436
|
+
setMode: (mode) => this.controller.setMode(mode),
|
|
437
|
+
toggleScope: () => this.controller.toggleScope(),
|
|
438
|
+
setThemePreference: (pref) => {
|
|
439
|
+
this.theme = pref;
|
|
440
|
+
},
|
|
441
|
+
setPalette: (p) => {
|
|
442
|
+
this.palette = p;
|
|
443
|
+
},
|
|
444
|
+
setLocale: (l) => {
|
|
445
|
+
this.locale = l;
|
|
446
|
+
},
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
/** The pointer entered or left the graph (holds a looping replay with `pause-on-hover`). */
|
|
450
|
+
pointerOver(over) {
|
|
451
|
+
this.#pointerOver = over;
|
|
452
|
+
this.#applyHold();
|
|
453
|
+
}
|
|
454
|
+
/** Hold a looping replay while the pointer is over the graph (`pause-on-hover`). */
|
|
455
|
+
#applyHold() {
|
|
456
|
+
this.controller.hold(this.#pointerOver && this.loop && this.pauseOnHover);
|
|
457
|
+
}
|
|
458
|
+
/** Reflect what the session changed (mode, scope, theme) back to the attributes. */
|
|
459
|
+
reflectSession(topics) {
|
|
460
|
+
const c = this.controller;
|
|
461
|
+
if (topics.has('mode')) {
|
|
462
|
+
this.reflect('mode', c.mode);
|
|
463
|
+
this.reflect('scope', c.scope);
|
|
464
|
+
}
|
|
465
|
+
if (topics.has('theme')) {
|
|
466
|
+
this.reflect('theme', c.themePreference === 'auto' && !this.hasAttribute('theme') ? null : c.themePreference);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
#onKeyDown(e) {
|
|
470
|
+
// A key an element forwarded to its own root is that root's, even when it did nothing.
|
|
471
|
+
if (wasForwarded(e))
|
|
472
|
+
return;
|
|
473
|
+
// The nearest root takes a key pressed inside it; an outer one leaves it alone.
|
|
474
|
+
const nearest = e.composedPath().find(isRoot);
|
|
475
|
+
if (nearest !== undefined && nearest !== this)
|
|
476
|
+
return;
|
|
477
|
+
if (nearest === undefined) {
|
|
478
|
+
// Outside every root only with `shortcuts="global"`, when nothing else has focus, for the
|
|
479
|
+
// first such root, and never the keys pages scroll with.
|
|
480
|
+
if (this.shortcuts !== 'global' || PAGE_KEYS.has(e.key))
|
|
481
|
+
return;
|
|
482
|
+
const active = document.activeElement;
|
|
483
|
+
if (active && active !== document.body && active !== document.documentElement)
|
|
484
|
+
return;
|
|
485
|
+
if (connected.find((v) => v.shortcuts === 'global') !== this)
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
this.#key(e);
|
|
489
|
+
}
|
|
490
|
+
/** A key for this root: pressed inside it, or forwarded by an element bound with `for`. */
|
|
491
|
+
#key(e) {
|
|
492
|
+
if (e.defaultPrevented || e.isComposing || !this.keysEnabled())
|
|
493
|
+
return;
|
|
494
|
+
if (this.interceptKey(e))
|
|
495
|
+
return;
|
|
496
|
+
this.root.handleKey(e);
|
|
497
|
+
}
|
|
498
|
+
// ─── Deep links ───────────────────────────────────────────────────────────
|
|
499
|
+
#readHash() {
|
|
500
|
+
if (!this.hasAttribute('sync-hash') || typeof location === 'undefined')
|
|
501
|
+
return;
|
|
502
|
+
if (location.hash === this.#lastHash)
|
|
503
|
+
return;
|
|
504
|
+
const state = parseHash(location.hash);
|
|
505
|
+
this.#lastHash = location.hash;
|
|
506
|
+
if (!state)
|
|
507
|
+
return;
|
|
508
|
+
if (state.mode || state.scope) {
|
|
509
|
+
// A deep link outranks the large-graph default and a report's preferred view.
|
|
510
|
+
const c = this.controller;
|
|
511
|
+
if (state.mode)
|
|
512
|
+
c.explicit.add('mode');
|
|
513
|
+
if (state.scope)
|
|
514
|
+
c.explicit.add('scope');
|
|
515
|
+
c.setView(state.mode ?? c.mode, state.scope ?? c.scope);
|
|
516
|
+
}
|
|
517
|
+
if (state.hunk)
|
|
518
|
+
this.controller.selectHunkById(state.hunk, { fly: true });
|
|
519
|
+
}
|
|
520
|
+
#writeHash() {
|
|
521
|
+
if (!this.hasAttribute('sync-hash') || typeof location === 'undefined')
|
|
522
|
+
return;
|
|
523
|
+
const c = this.controller;
|
|
524
|
+
const hunk = c.selectedHunk >= 0 ? c.scene?.hunks[c.selectedHunk]?.id : undefined;
|
|
525
|
+
const hash = formatHash({
|
|
526
|
+
...(hunk !== undefined ? { hunk } : {}),
|
|
527
|
+
mode: c.mode,
|
|
528
|
+
scope: c.scope,
|
|
529
|
+
});
|
|
530
|
+
if (hash === location.hash)
|
|
531
|
+
return;
|
|
532
|
+
this.#lastHash = hash;
|
|
533
|
+
try {
|
|
534
|
+
history.replaceState(history.state, '', hash);
|
|
535
|
+
}
|
|
536
|
+
catch {
|
|
537
|
+
location.hash = hash;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
//# sourceMappingURL=root-element.js.map
|