@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.
Files changed (257) hide show
  1. package/README.md +43 -16
  2. package/custom-elements.json +2347 -0
  3. package/dist/bind.d.ts +41 -0
  4. package/dist/bind.d.ts.map +1 -0
  5. package/dist/bind.js +47 -0
  6. package/dist/bind.js.map +1 -0
  7. package/dist/context.d.ts +42 -0
  8. package/dist/context.d.ts.map +1 -0
  9. package/dist/context.js +87 -0
  10. package/dist/context.js.map +1 -0
  11. package/dist/controller.d.ts +26 -237
  12. package/dist/controller.d.ts.map +1 -1
  13. package/dist/controller.js +33 -1484
  14. package/dist/controller.js.map +1 -1
  15. package/dist/dom.d.ts +10 -0
  16. package/dist/dom.d.ts.map +1 -1
  17. package/dist/dom.js +29 -0
  18. package/dist/dom.js.map +1 -1
  19. package/dist/element.d.ts +51 -42
  20. package/dist/element.d.ts.map +1 -1
  21. package/dist/element.js +381 -602
  22. package/dist/element.js.map +1 -1
  23. package/dist/elements/actions.d.ts +27 -0
  24. package/dist/elements/actions.d.ts.map +1 -0
  25. package/dist/elements/actions.js +81 -0
  26. package/dist/elements/actions.js.map +1 -0
  27. package/dist/elements/bound.d.ts +63 -0
  28. package/dist/elements/bound.d.ts.map +1 -0
  29. package/dist/elements/bound.js +213 -0
  30. package/dist/elements/bound.js.map +1 -0
  31. package/dist/elements/change-detail.d.ts +25 -0
  32. package/dist/elements/change-detail.d.ts.map +1 -0
  33. package/dist/elements/change-detail.js +19 -0
  34. package/dist/elements/change-detail.js.map +1 -0
  35. package/dist/elements/change-list.d.ts +27 -0
  36. package/dist/elements/change-list.d.ts.map +1 -0
  37. package/dist/elements/change-list.js +29 -0
  38. package/dist/elements/change-list.js.map +1 -0
  39. package/dist/elements/filters.d.ts +25 -0
  40. package/dist/elements/filters.d.ts.map +1 -0
  41. package/dist/elements/filters.js +19 -0
  42. package/dist/elements/filters.js.map +1 -0
  43. package/dist/elements/legend.d.ts +40 -0
  44. package/dist/elements/legend.d.ts.map +1 -0
  45. package/dist/elements/legend.js +87 -0
  46. package/dist/elements/legend.js.map +1 -0
  47. package/dist/elements/minimap.d.ts +38 -0
  48. package/dist/elements/minimap.d.ts.map +1 -0
  49. package/dist/elements/minimap.js +59 -0
  50. package/dist/elements/minimap.js.map +1 -0
  51. package/dist/elements/root.d.ts +40 -0
  52. package/dist/elements/root.d.ts.map +1 -0
  53. package/dist/elements/root.js +67 -0
  54. package/dist/elements/root.js.map +1 -0
  55. package/dist/elements/search.d.ts +29 -0
  56. package/dist/elements/search.d.ts.map +1 -0
  57. package/dist/elements/search.js +23 -0
  58. package/dist/elements/search.js.map +1 -0
  59. package/dist/elements/stage-context.d.ts +31 -0
  60. package/dist/elements/stage-context.d.ts.map +1 -0
  61. package/dist/elements/stage-context.js +4 -0
  62. package/dist/elements/stage-context.js.map +1 -0
  63. package/dist/elements/stage.d.ts +65 -0
  64. package/dist/elements/stage.d.ts.map +1 -0
  65. package/dist/elements/stage.js +259 -0
  66. package/dist/elements/stage.js.map +1 -0
  67. package/dist/elements/title.d.ts +25 -0
  68. package/dist/elements/title.d.ts.map +1 -0
  69. package/dist/elements/title.js +19 -0
  70. package/dist/elements/title.js.map +1 -0
  71. package/dist/elements/transport.d.ts +29 -0
  72. package/dist/elements/transport.d.ts.map +1 -0
  73. package/dist/elements/transport.js +24 -0
  74. package/dist/elements/transport.js.map +1 -0
  75. package/dist/elements/view-bound.d.ts +39 -0
  76. package/dist/elements/view-bound.d.ts.map +1 -0
  77. package/dist/elements/view-bound.js +88 -0
  78. package/dist/elements/view-bound.js.map +1 -0
  79. package/dist/elements/view-dock.d.ts +28 -0
  80. package/dist/elements/view-dock.d.ts.map +1 -0
  81. package/dist/elements/view-dock.js +28 -0
  82. package/dist/elements/view-dock.js.map +1 -0
  83. package/dist/elements/zoom.d.ts +28 -0
  84. package/dist/elements/zoom.d.ts.map +1 -0
  85. package/dist/elements/zoom.js +22 -0
  86. package/dist/elements/zoom.js.map +1 -0
  87. package/dist/i18n.d.ts +10 -0
  88. package/dist/i18n.d.ts.map +1 -1
  89. package/dist/i18n.js +20 -0
  90. package/dist/i18n.js.map +1 -1
  91. package/dist/icons.d.ts +1 -0
  92. package/dist/icons.d.ts.map +1 -1
  93. package/dist/icons.js +1 -0
  94. package/dist/icons.js.map +1 -1
  95. package/dist/index.d.ts +331 -36
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.js +13 -0
  98. package/dist/index.js.map +1 -1
  99. package/dist/keyboard.d.ts +39 -2
  100. package/dist/keyboard.d.ts.map +1 -1
  101. package/dist/keyboard.js +145 -62
  102. package/dist/keyboard.js.map +1 -1
  103. package/dist/layout-store.d.ts +26 -0
  104. package/dist/layout-store.d.ts.map +1 -0
  105. package/dist/layout-store.js +46 -0
  106. package/dist/layout-store.js.map +1 -0
  107. package/dist/panel-levels.d.ts +18 -0
  108. package/dist/panel-levels.d.ts.map +1 -0
  109. package/dist/panel-levels.js +41 -0
  110. package/dist/panel-levels.js.map +1 -0
  111. package/dist/panels.d.ts +28 -0
  112. package/dist/panels.d.ts.map +1 -0
  113. package/dist/panels.js +73 -0
  114. package/dist/panels.js.map +1 -0
  115. package/dist/pipeline.d.ts.map +1 -1
  116. package/dist/pipeline.js +3 -2
  117. package/dist/pipeline.js.map +1 -1
  118. package/dist/prefs.d.ts +5 -2
  119. package/dist/prefs.d.ts.map +1 -1
  120. package/dist/prefs.js +12 -3
  121. package/dist/prefs.js.map +1 -1
  122. package/dist/react.d.ts +460 -0
  123. package/dist/react.d.ts.map +1 -0
  124. package/dist/react.js +2 -0
  125. package/dist/react.js.map +1 -0
  126. package/dist/root-element.d.ts +122 -0
  127. package/dist/root-element.d.ts.map +1 -0
  128. package/dist/root-element.js +541 -0
  129. package/dist/root-element.js.map +1 -0
  130. package/dist/root.d.ts +54 -0
  131. package/dist/root.d.ts.map +1 -0
  132. package/dist/root.js +288 -0
  133. package/dist/root.js.map +1 -0
  134. package/dist/schedule.d.ts +22 -0
  135. package/dist/schedule.d.ts.map +1 -0
  136. package/dist/schedule.js +41 -0
  137. package/dist/schedule.js.map +1 -0
  138. package/dist/session.d.ts +269 -0
  139. package/dist/session.d.ts.map +1 -0
  140. package/dist/session.js +1237 -0
  141. package/dist/session.js.map +1 -0
  142. package/dist/standalone/kgdiff-viewer-compute.js +1 -1
  143. package/dist/standalone/kgdiff-viewer.js +4 -4
  144. package/dist/styles.d.ts +17 -6
  145. package/dist/styles.d.ts.map +1 -1
  146. package/dist/styles.js +367 -258
  147. package/dist/styles.js.map +1 -1
  148. package/dist/theme.d.ts +32 -0
  149. package/dist/theme.d.ts.map +1 -0
  150. package/dist/theme.js +152 -0
  151. package/dist/theme.js.map +1 -0
  152. package/dist/topics.d.ts +8 -0
  153. package/dist/topics.d.ts.map +1 -0
  154. package/dist/topics.js +18 -0
  155. package/dist/topics.js.map +1 -0
  156. package/dist/ui/change-list.d.ts +18 -0
  157. package/dist/ui/change-list.d.ts.map +1 -0
  158. package/dist/ui/change-list.js +331 -0
  159. package/dist/ui/change-list.js.map +1 -0
  160. package/dist/ui/context.d.ts +34 -7
  161. package/dist/ui/context.d.ts.map +1 -1
  162. package/dist/ui/context.js.map +1 -1
  163. package/dist/ui/filters.d.ts +9 -0
  164. package/dist/ui/filters.d.ts.map +1 -0
  165. package/dist/ui/filters.js +68 -0
  166. package/dist/ui/filters.js.map +1 -0
  167. package/dist/ui/header.d.ts +41 -3
  168. package/dist/ui/header.d.ts.map +1 -1
  169. package/dist/ui/header.js +101 -36
  170. package/dist/ui/header.js.map +1 -1
  171. package/dist/ui/legend.d.ts +15 -0
  172. package/dist/ui/legend.d.ts.map +1 -0
  173. package/dist/ui/legend.js +68 -0
  174. package/dist/ui/legend.js.map +1 -0
  175. package/dist/ui/popovers.d.ts +3 -1
  176. package/dist/ui/popovers.d.ts.map +1 -1
  177. package/dist/ui/popovers.js +62 -23
  178. package/dist/ui/popovers.js.map +1 -1
  179. package/dist/ui/search.d.ts +13 -0
  180. package/dist/ui/search.d.ts.map +1 -0
  181. package/dist/ui/search.js +192 -0
  182. package/dist/ui/search.js.map +1 -0
  183. package/dist/ui/sidebar.d.ts +32 -17
  184. package/dist/ui/sidebar.d.ts.map +1 -1
  185. package/dist/ui/sidebar.js +327 -519
  186. package/dist/ui/sidebar.js.map +1 -1
  187. package/dist/ui/stage.d.ts +56 -1
  188. package/dist/ui/stage.d.ts.map +1 -1
  189. package/dist/ui/stage.js +244 -256
  190. package/dist/ui/stage.js.map +1 -1
  191. package/dist/ui/swatch.js +1 -1
  192. package/dist/ui/swatch.js.map +1 -1
  193. package/dist/ui/transport.d.ts +12 -0
  194. package/dist/ui/transport.d.ts.map +1 -0
  195. package/dist/ui/transport.js +198 -0
  196. package/dist/ui/transport.js.map +1 -0
  197. package/dist/ui/zoom.d.ts +15 -0
  198. package/dist/ui/zoom.d.ts.map +1 -0
  199. package/dist/ui/zoom.js +30 -0
  200. package/dist/ui/zoom.js.map +1 -0
  201. package/dist/view.d.ts +94 -0
  202. package/dist/view.d.ts.map +1 -0
  203. package/dist/view.js +441 -0
  204. package/dist/view.js.map +1 -0
  205. package/package.json +95 -12
  206. package/src/bind.ts +80 -0
  207. package/src/context.ts +106 -0
  208. package/src/controller.ts +52 -1630
  209. package/src/dom.ts +30 -0
  210. package/src/element.ts +386 -619
  211. package/src/elements/actions.ts +94 -0
  212. package/src/elements/bound.ts +247 -0
  213. package/src/elements/change-detail.ts +34 -0
  214. package/src/elements/change-list.ts +51 -0
  215. package/src/elements/filters.ts +34 -0
  216. package/src/elements/legend.ts +103 -0
  217. package/src/elements/minimap.ts +75 -0
  218. package/src/elements/root.ts +86 -0
  219. package/src/elements/search.ts +38 -0
  220. package/src/elements/stage-context.ts +34 -0
  221. package/src/elements/stage.ts +285 -0
  222. package/src/elements/title.ts +34 -0
  223. package/src/elements/transport.ts +39 -0
  224. package/src/elements/view-bound.ts +118 -0
  225. package/src/elements/view-dock.ts +43 -0
  226. package/src/elements/zoom.ts +38 -0
  227. package/src/i18n.ts +20 -0
  228. package/src/icons.ts +1 -0
  229. package/src/index.ts +350 -35
  230. package/src/keyboard.ts +188 -61
  231. package/src/layout-store.ts +71 -0
  232. package/src/panel-levels.ts +53 -0
  233. package/src/panels.ts +81 -0
  234. package/src/pipeline.ts +4 -1
  235. package/src/prefs.ts +12 -4
  236. package/src/react.ts +459 -0
  237. package/src/root-element.ts +624 -0
  238. package/src/root.ts +335 -0
  239. package/src/schedule.ts +43 -0
  240. package/src/session.ts +1375 -0
  241. package/src/styles.ts +396 -263
  242. package/src/theme.ts +170 -0
  243. package/src/topics.ts +37 -0
  244. package/src/ui/change-list.ts +405 -0
  245. package/src/ui/context.ts +36 -7
  246. package/src/ui/filters.ts +79 -0
  247. package/src/ui/header.ts +129 -58
  248. package/src/ui/legend.ts +112 -0
  249. package/src/ui/popovers.ts +68 -25
  250. package/src/ui/search.ts +225 -0
  251. package/src/ui/sidebar.ts +362 -639
  252. package/src/ui/stage.ts +267 -331
  253. package/src/ui/swatch.ts +1 -1
  254. package/src/ui/transport.ts +240 -0
  255. package/src/ui/zoom.ts +45 -0
  256. package/src/view.ts +516 -0
  257. 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