@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,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<kgdiff-actions>` (RFC 0001 §3), documented on its class. Importing this module defines it.
|
|
3
|
+
*/
|
|
4
|
+
import type { Region, ViewerContext } from '../bind.js';
|
|
5
|
+
import { isOpen } from '../dom.js';
|
|
6
|
+
import type { KgdiffBoundElement } from '../index.js';
|
|
7
|
+
import type { UiContext } from '../ui/context.js';
|
|
8
|
+
import { Actions } from '../ui/header.js';
|
|
9
|
+
import { HelpDialog, Menu } from '../ui/popovers.js';
|
|
10
|
+
import { BoundElement, define } from './bound.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The appearance menu (theme, color palette, language: the root's `theme`, `palette` and `locale`)
|
|
14
|
+
* and the keyboard shortcuts dialog, which `?` opens too. Binds to a root (`for`, `htmlFor`,
|
|
15
|
+
* `rootElement`); bound to a `<kgdiff-viewer>` it has the sidebar toggle too (a composed root has
|
|
16
|
+
* no sidebar). Slotted into a viewer it replaces the actions over its canvas.
|
|
17
|
+
*
|
|
18
|
+
* @tagname kgdiff-actions
|
|
19
|
+
* @experimental In 0.6; stable from 0.7 (RFC 0001, decision 7).
|
|
20
|
+
*/
|
|
21
|
+
export class KgdiffActions extends BoundElement implements KgdiffBoundElement {
|
|
22
|
+
protected readonly name = 'actions';
|
|
23
|
+
|
|
24
|
+
protected build(ui: UiContext, ctx: ViewerContext, release: (fn: () => void) => void): Region {
|
|
25
|
+
let menu: Menu | null = null;
|
|
26
|
+
let help: HelpDialog | null = null;
|
|
27
|
+
let actions: Actions | null = null;
|
|
28
|
+
const toggle = (open?: boolean): void => {
|
|
29
|
+
if (!menu || !actions) return;
|
|
30
|
+
const next = open ?? !menu.open;
|
|
31
|
+
if (next) menu.show(actions.menuButton);
|
|
32
|
+
else menu.hide(false);
|
|
33
|
+
actions.setMenuState(next);
|
|
34
|
+
};
|
|
35
|
+
// The menu and the dialog are this element's: they open here, not in the root's layout.
|
|
36
|
+
const local: UiContext = { ...ui, toggleMenu: toggle, openHelp: () => help?.show() };
|
|
37
|
+
// The sidebar toggle where the root has a sidebar (a `<kgdiff-viewer>`); none in a
|
|
38
|
+
// composed root, where `\` does nothing.
|
|
39
|
+
const hasSidebar = ui.hasSidebar;
|
|
40
|
+
menu = new Menu(local);
|
|
41
|
+
help = new HelpDialog(local, hasSidebar ? [] : ['sidebar']);
|
|
42
|
+
actions = new Actions(local, hasSidebar !== undefined);
|
|
43
|
+
actions.attachMenu(menu.el);
|
|
44
|
+
const sidebar = (): void => {
|
|
45
|
+
const button = actions?.sidebarButton;
|
|
46
|
+
if (!button || !hasSidebar) return;
|
|
47
|
+
button.hidden = !hasSidebar();
|
|
48
|
+
actions?.setSidebarState(ui.sidebarOpen());
|
|
49
|
+
};
|
|
50
|
+
if (hasSidebar) {
|
|
51
|
+
// The viewer reports every change of its panels, the sidebar's folds included.
|
|
52
|
+
sidebar();
|
|
53
|
+
ctx.host.addEventListener('kgdiff-layout', sidebar);
|
|
54
|
+
release(() => ctx.host.removeEventListener('kgdiff-layout', sidebar));
|
|
55
|
+
}
|
|
56
|
+
this.shadow.append(actions.el, help.el);
|
|
57
|
+
const shown = actions;
|
|
58
|
+
const dialog = help;
|
|
59
|
+
const popover = menu;
|
|
60
|
+
// `?` opens this element's dialog (the root's `help` target).
|
|
61
|
+
ui.target(
|
|
62
|
+
'help',
|
|
63
|
+
() => dialog.show(),
|
|
64
|
+
() => isOpen(shown.el),
|
|
65
|
+
);
|
|
66
|
+
// The menu closes on Escape by itself: keep the button's state up with it.
|
|
67
|
+
popover.el.addEventListener('keydown', () =>
|
|
68
|
+
queueMicrotask(() => shown.setMenuState(popover.open)),
|
|
69
|
+
);
|
|
70
|
+
const outside = (e: PointerEvent): void => {
|
|
71
|
+
if (!popover.open) return;
|
|
72
|
+
const path = e.composedPath();
|
|
73
|
+
if (!path.includes(popover.el) && !path.includes(shown.menuButton)) toggle(false);
|
|
74
|
+
};
|
|
75
|
+
document.addEventListener('pointerdown', outside);
|
|
76
|
+
release(() => document.removeEventListener('pointerdown', outside));
|
|
77
|
+
return {
|
|
78
|
+
update: (topics) => {
|
|
79
|
+
shown.update(topics);
|
|
80
|
+
if (popover.open && (topics.has('locale') || topics.has('theme'))) popover.render();
|
|
81
|
+
if (dialog.open && topics.has('locale')) dialog.render();
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
define('kgdiff-actions', KgdiffActions);
|
|
88
|
+
|
|
89
|
+
declare global {
|
|
90
|
+
interface HTMLElementTagNameMap {
|
|
91
|
+
/** @experimental In 0.6. */
|
|
92
|
+
'kgdiff-actions': KgdiffBoundElement;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the elements bound to a root share (RFC 0001 §2–§6): finding the root, in every
|
|
3
|
+
* topology, and living with it.
|
|
4
|
+
*
|
|
5
|
+
* - **Binding.** The nearest `<kgdiff-root>` (or `<kgdiff-viewer>`) around the element, across
|
|
6
|
+
* shadow roots, or the one named by `for="<id>"` anywhere on the page. A root that is defined
|
|
7
|
+
* or connected later is found then (the context protocol replays the request; `for` waits
|
|
8
|
+
* for a root of that id to announce itself). Changing `for` binds again.
|
|
9
|
+
* - **Rendering.** The element builds its region in its own shadow root once bound, and the root
|
|
10
|
+
* renders it on every notification from then on.
|
|
11
|
+
* - **Outside the root's subtree** (`for`): the element adopts the root's token set (inside,
|
|
12
|
+
* tokens are inherited, page overrides on the root included) and forwards its key presses to
|
|
13
|
+
* the root, which owns the keyboard map.
|
|
14
|
+
*/
|
|
15
|
+
import { forwardKey, type Region, uiContext, type ViewerContext, viewerContext } from '../bind.js';
|
|
16
|
+
import { type Context, type ContextProviderEvent, requestContext } from '../context.js';
|
|
17
|
+
import type { KgdiffBoundElement, KgdiffRootElement } from '../index.js';
|
|
18
|
+
import { type ElementName, elementSheet, stylesheet } from '../styles.js';
|
|
19
|
+
import { ALL_TOPICS } from '../topics.js';
|
|
20
|
+
import type { UiContext } from '../ui/context.js';
|
|
21
|
+
|
|
22
|
+
/** Whether `el` is inside `host`'s subtree, across shadow roots. */
|
|
23
|
+
function inside(el: Element, host: Element): boolean {
|
|
24
|
+
for (let n: Node | null = el; n; ) {
|
|
25
|
+
if (n === host) return true;
|
|
26
|
+
n = n.parentNode ?? ((n as Partial<ShadowRoot>).host as Node | undefined) ?? null;
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The element of `id` as `el` sees it: in its own tree first, then in the document. */
|
|
32
|
+
function byId(el: Element, id: string): Element | null {
|
|
33
|
+
const tree = el.getRootNode() as { getElementById?: (id: string) => Element | null };
|
|
34
|
+
return tree.getElementById?.(id) ?? el.ownerDocument.getElementById(id);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Find the provider of `context` for `consumer`: the nearest one around it when `id` is empty,
|
|
39
|
+
* else the element of that id as `consumer` sees it (its own tree first, then the document),
|
|
40
|
+
* now, once that element provides `context` (the protocol replays the request), or once a
|
|
41
|
+
* provider of that id announces itself. `answer` gets the first value only, and none after the
|
|
42
|
+
* returned function cancels (a rebinding or a disconnection).
|
|
43
|
+
*/
|
|
44
|
+
export function bindContext<T>(
|
|
45
|
+
consumer: Element,
|
|
46
|
+
context: Context<T>,
|
|
47
|
+
id: string | null,
|
|
48
|
+
answer: (value: T) => void,
|
|
49
|
+
): () => void {
|
|
50
|
+
let live = true;
|
|
51
|
+
const once = (value: T): void => {
|
|
52
|
+
if (!live) return;
|
|
53
|
+
live = false;
|
|
54
|
+
answer(value);
|
|
55
|
+
};
|
|
56
|
+
if (!id) {
|
|
57
|
+
requestContext(consumer, context, once);
|
|
58
|
+
return () => {
|
|
59
|
+
live = false;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const named = byId(consumer, id);
|
|
63
|
+
if (named) {
|
|
64
|
+
requestContext(consumer, context, once, named);
|
|
65
|
+
return () => {
|
|
66
|
+
live = false;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
// No element of that id yet: wait for a provider of that id to announce itself.
|
|
70
|
+
const onProvider = (e: Event): void => {
|
|
71
|
+
const host = e.composedPath()[0];
|
|
72
|
+
if ((e as ContextProviderEvent<unknown>).context?.key !== context.key) return;
|
|
73
|
+
if (!(host instanceof Element) || host.id !== id) return;
|
|
74
|
+
document.removeEventListener('context-provider', onProvider);
|
|
75
|
+
requestContext(consumer, context, once, host);
|
|
76
|
+
};
|
|
77
|
+
document.addEventListener('context-provider', onProvider);
|
|
78
|
+
return () => {
|
|
79
|
+
live = false;
|
|
80
|
+
document.removeEventListener('context-provider', onProvider);
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Define `name` once (another copy of the library may have defined it first). */
|
|
85
|
+
export function define(name: string, ctor: CustomElementConstructor): void {
|
|
86
|
+
if (typeof customElements !== 'undefined' && customElements.get(name) === undefined) {
|
|
87
|
+
customElements.define(name, ctor);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Show `build`'s region in `host`'s shadow root, bound to the root `ctx`: the shared sheet, the
|
|
93
|
+
* host rules and, outside the root's subtree, its token set (kept up with theme changes); the
|
|
94
|
+
* session's language; the root's notifications; and, outside its subtree, the keys pressed in
|
|
95
|
+
* `host`, forwarded to the root (which owns the keyboard map). `release` collects the undo.
|
|
96
|
+
*/
|
|
97
|
+
export function mount(
|
|
98
|
+
host: HTMLElement,
|
|
99
|
+
shadow: ShadowRoot,
|
|
100
|
+
name: ElementName,
|
|
101
|
+
ctx: ViewerContext,
|
|
102
|
+
build: (ui: UiContext) => Region,
|
|
103
|
+
release: (fn: () => void) => void,
|
|
104
|
+
): void {
|
|
105
|
+
const outside = !inside(host, ctx.host);
|
|
106
|
+
const adopt = (): void => {
|
|
107
|
+
const doc = host.ownerDocument;
|
|
108
|
+
shadow.adoptedStyleSheets = [
|
|
109
|
+
stylesheet(doc),
|
|
110
|
+
elementSheet(doc, name),
|
|
111
|
+
...(outside ? [ctx.tokens(doc)] : []),
|
|
112
|
+
];
|
|
113
|
+
};
|
|
114
|
+
const lang = (): void => {
|
|
115
|
+
for (const el of shadow.children) el.setAttribute('lang', ctx.controller.locale);
|
|
116
|
+
};
|
|
117
|
+
const ui: UiContext = {
|
|
118
|
+
...uiContext(ctx, shadow),
|
|
119
|
+
// Focus targets go when the element does.
|
|
120
|
+
target: (target, focus, available) => {
|
|
121
|
+
const off = ctx.target(target, focus, available);
|
|
122
|
+
release(off);
|
|
123
|
+
return off;
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
adopt();
|
|
127
|
+
const region = build(ui);
|
|
128
|
+
release(
|
|
129
|
+
ctx.attach({
|
|
130
|
+
update: (topics) => {
|
|
131
|
+
if (outside && topics.has('theme')) adopt();
|
|
132
|
+
if (topics.has('locale') || topics.has('status')) lang();
|
|
133
|
+
region.update(topics);
|
|
134
|
+
},
|
|
135
|
+
}),
|
|
136
|
+
);
|
|
137
|
+
if (outside) {
|
|
138
|
+
// The root owns the keyboard map; keys pressed here would not reach it (and a root this
|
|
139
|
+
// element sits in must leave them alone).
|
|
140
|
+
const forward = (e: KeyboardEvent): void => forwardKey(ctx, e);
|
|
141
|
+
host.addEventListener('keydown', forward);
|
|
142
|
+
release(() => host.removeEventListener('keydown', forward));
|
|
143
|
+
}
|
|
144
|
+
lang();
|
|
145
|
+
try {
|
|
146
|
+
region.update(ALL_TOPICS);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
console.error('[kgdiff] render failed:', error);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** @experimental The base of the bound elements (`KgdiffBoundElement`), in 0.6. */
|
|
153
|
+
export abstract class BoundElement extends HTMLElement implements KgdiffBoundElement {
|
|
154
|
+
static get observedAttributes(): readonly string[] {
|
|
155
|
+
return ['for'];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
protected readonly shadow: ShadowRoot;
|
|
159
|
+
#ctx: ViewerContext | null = null;
|
|
160
|
+
readonly #release: (() => void)[] = [];
|
|
161
|
+
|
|
162
|
+
constructor() {
|
|
163
|
+
super();
|
|
164
|
+
this.shadow = this.attachShadow({ mode: 'open' });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** The element's name after `kgdiff-` (its host rules). */
|
|
168
|
+
protected abstract readonly name: ElementName;
|
|
169
|
+
|
|
170
|
+
/** Build the region in the shadow root, bound to `ui`; `release` collects what to undo. */
|
|
171
|
+
protected abstract build(
|
|
172
|
+
ui: UiContext,
|
|
173
|
+
ctx: ViewerContext,
|
|
174
|
+
release: (fn: () => void) => void,
|
|
175
|
+
): Region;
|
|
176
|
+
|
|
177
|
+
/** The root this element is bound to, or null while it waits for one. */
|
|
178
|
+
protected get context(): ViewerContext | null {
|
|
179
|
+
return this.#ctx;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Properties a page may set before the element is defined (they shadow the accessors). */
|
|
183
|
+
protected upgradedProperties(): readonly string[] {
|
|
184
|
+
return ['htmlFor'];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Reflects `for`, as on `<label>` and `<output>`: the id of the root to show; '' (no
|
|
189
|
+
* attribute) for the nearest one.
|
|
190
|
+
*/
|
|
191
|
+
get htmlFor(): string {
|
|
192
|
+
return this.getAttribute('for') ?? '';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
set htmlFor(value: string) {
|
|
196
|
+
const id = String(value);
|
|
197
|
+
if (id === '') this.removeAttribute('for');
|
|
198
|
+
else this.setAttribute('for', id);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** The root this element shows, or null while it waits for one. */
|
|
202
|
+
get rootElement(): KgdiffRootElement | null {
|
|
203
|
+
return (this.#ctx?.host as KgdiffRootElement | undefined) ?? null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
connectedCallback(): void {
|
|
207
|
+
for (const prop of this.upgradedProperties()) {
|
|
208
|
+
if (!Object.hasOwn(this, prop)) continue;
|
|
209
|
+
const value = (this as unknown as Record<string, unknown>)[prop];
|
|
210
|
+
delete (this as unknown as Record<string, unknown>)[prop];
|
|
211
|
+
(this as unknown as Record<string, unknown>)[prop] = value;
|
|
212
|
+
}
|
|
213
|
+
this.#bind();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
disconnectedCallback(): void {
|
|
217
|
+
this.#unbind();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
attributeChangedCallback(name: string, old: string | null, value: string | null): void {
|
|
221
|
+
if (name === 'for' && old !== value && this.isConnected) {
|
|
222
|
+
this.#unbind();
|
|
223
|
+
this.#bind();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
#bind(): void {
|
|
228
|
+
const cancel = bindContext(this, viewerContext, this.getAttribute('for'), (ctx) =>
|
|
229
|
+
this.#attach(ctx),
|
|
230
|
+
);
|
|
231
|
+
this.#release.push(cancel);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
#attach(ctx: ViewerContext): void {
|
|
235
|
+
this.#ctx = ctx;
|
|
236
|
+
const release = (fn: () => void): void => {
|
|
237
|
+
this.#release.push(fn);
|
|
238
|
+
};
|
|
239
|
+
mount(this, this.shadow, this.name, ctx, (ui) => this.build(ui, ctx, release), release);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
#unbind(): void {
|
|
243
|
+
this.#ctx = null;
|
|
244
|
+
for (const fn of this.#release.splice(0)) fn();
|
|
245
|
+
this.shadow.replaceChildren();
|
|
246
|
+
}
|
|
247
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<kgdiff-change-detail>` (RFC 0001 §3), documented on its class. Importing this module defines it.
|
|
3
|
+
*/
|
|
4
|
+
import type { Region } from '../bind.js';
|
|
5
|
+
import type { KgdiffBoundElement } from '../index.js';
|
|
6
|
+
import type { UiContext } from '../ui/context.js';
|
|
7
|
+
import { Detail } from '../ui/detail.js';
|
|
8
|
+
import { BoundElement, define } from './bound.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The selected change, or the selected node: what changed first, then the unchanged rest; prev /
|
|
12
|
+
* next and the reviewed toggle. A labelled region.
|
|
13
|
+
*
|
|
14
|
+
* @tagname kgdiff-change-detail
|
|
15
|
+
* @experimental In 0.6; stable from 0.7 (RFC 0001, decision 7).
|
|
16
|
+
*/
|
|
17
|
+
export class KgdiffChangeDetail extends BoundElement implements KgdiffBoundElement {
|
|
18
|
+
protected readonly name = 'change-detail';
|
|
19
|
+
|
|
20
|
+
protected build(ui: UiContext): Region {
|
|
21
|
+
const detail = new Detail(ui);
|
|
22
|
+
this.shadow.append(detail.el);
|
|
23
|
+
return detail;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
define('kgdiff-change-detail', KgdiffChangeDetail);
|
|
28
|
+
|
|
29
|
+
declare global {
|
|
30
|
+
interface HTMLElementTagNameMap {
|
|
31
|
+
/** @experimental In 0.6. */
|
|
32
|
+
'kgdiff-change-detail': KgdiffBoundElement;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<kgdiff-change-list>` (RFC 0001 §3), documented on its class. Importing this module defines it.
|
|
3
|
+
*/
|
|
4
|
+
import type { Region } from '../bind.js';
|
|
5
|
+
import { h } from '../dom.js';
|
|
6
|
+
import type { KgdiffBoundElement } from '../index.js';
|
|
7
|
+
import { ChangeList } from '../ui/change-list.js';
|
|
8
|
+
import type { UiContext } from '../ui/context.js';
|
|
9
|
+
import { BoundElement, define } from './bound.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The "Changes" title with the count and review progress, and the changes grouped by phase (a
|
|
13
|
+
* virtualized listbox; `j` / `k` / `x`, Enter, Home / End). A labelled region. While a search is
|
|
14
|
+
* active (the session's query is not blank) its rows step aside. Registers itself as the root's
|
|
15
|
+
* `list` focus target (choosing a search result moves focus here).
|
|
16
|
+
*
|
|
17
|
+
* @tagname kgdiff-change-list
|
|
18
|
+
* @experimental In 0.6; stable from 0.7 (RFC 0001, decision 7).
|
|
19
|
+
*/
|
|
20
|
+
export class KgdiffChangeList extends BoundElement implements KgdiffBoundElement {
|
|
21
|
+
protected readonly name = 'change-list';
|
|
22
|
+
|
|
23
|
+
protected build(ui: UiContext, _ctx: unknown, release: (fn: () => void) => void): Region {
|
|
24
|
+
const list = new ChangeList(ui);
|
|
25
|
+
const rows = h('div', { class: 'list-wrap' }, list.sticky, list.list, list.phases);
|
|
26
|
+
this.shadow.append(
|
|
27
|
+
h(
|
|
28
|
+
'section',
|
|
29
|
+
{ class: 'change-list', 'aria-labelledby': 'kg-changes-title' },
|
|
30
|
+
h('div', { class: 'sb-head' }, list.head),
|
|
31
|
+
rows,
|
|
32
|
+
),
|
|
33
|
+
);
|
|
34
|
+
if (typeof ResizeObserver === 'function') {
|
|
35
|
+
// The list draws the rows in view: it needs its height.
|
|
36
|
+
const ro = new ResizeObserver(() => list.renderWindow());
|
|
37
|
+
ro.observe(rows);
|
|
38
|
+
release(() => ro.disconnect());
|
|
39
|
+
}
|
|
40
|
+
return list;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
define('kgdiff-change-list', KgdiffChangeList);
|
|
45
|
+
|
|
46
|
+
declare global {
|
|
47
|
+
interface HTMLElementTagNameMap {
|
|
48
|
+
/** @experimental In 0.6. */
|
|
49
|
+
'kgdiff-change-list': KgdiffBoundElement;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<kgdiff-filters>` (RFC 0001 §3), documented on its class. Importing this module defines it.
|
|
3
|
+
*/
|
|
4
|
+
import type { Region } from '../bind.js';
|
|
5
|
+
import type { KgdiffBoundElement } from '../index.js';
|
|
6
|
+
import type { UiContext } from '../ui/context.js';
|
|
7
|
+
import { Filters } from '../ui/filters.js';
|
|
8
|
+
import { BoundElement, define } from './bound.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* One chip per kind of change the diff has, with its count and the legend's swatch, toggling the
|
|
12
|
+
* session's family filter, and "Show all" while a filter is on.
|
|
13
|
+
*
|
|
14
|
+
* @tagname kgdiff-filters
|
|
15
|
+
* @experimental In 0.6; stable from 0.7 (RFC 0001, decision 7).
|
|
16
|
+
*/
|
|
17
|
+
export class KgdiffFilters extends BoundElement implements KgdiffBoundElement {
|
|
18
|
+
protected readonly name = 'filters';
|
|
19
|
+
|
|
20
|
+
protected build(ui: UiContext): Region {
|
|
21
|
+
const filters = new Filters(ui);
|
|
22
|
+
this.shadow.append(filters.el);
|
|
23
|
+
return filters;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
define('kgdiff-filters', KgdiffFilters);
|
|
28
|
+
|
|
29
|
+
declare global {
|
|
30
|
+
interface HTMLElementTagNameMap {
|
|
31
|
+
/** @experimental In 0.6. */
|
|
32
|
+
'kgdiff-filters': KgdiffBoundElement;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<kgdiff-legend>` (RFC 0001 §3), documented on its class. Importing this module defines it.
|
|
3
|
+
*/
|
|
4
|
+
import type { Region } from '../bind.js';
|
|
5
|
+
import type { KgdiffLegendElement, LegendVariant } from '../index.js';
|
|
6
|
+
import type { UiContext } from '../ui/context.js';
|
|
7
|
+
import { Legend } from '../ui/legend.js';
|
|
8
|
+
import { BoundElement, define } from './bound.js';
|
|
9
|
+
|
|
10
|
+
const VARIANTS: readonly LegendVariant[] = ['full', 'mini'];
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* What the colors mean. `variant="full"` (the default): a card of every family's swatch and name,
|
|
14
|
+
* folded to its head until opened; `open` reflects whether it is (the user's toggle included, as on
|
|
15
|
+
* `<details>`). `variant="mini"`: one line of dots for the families the root's diff has, then the
|
|
16
|
+
* unchanged context. Binds to a root (`for`, `htmlFor`, `rootElement`). Slotted into a
|
|
17
|
+
* `<kgdiff-stage>` it replaces the stage's built-in legend; placed anywhere else it is not kept
|
|
18
|
+
* clear of fits and flights unless registered.
|
|
19
|
+
*
|
|
20
|
+
* @tagname kgdiff-legend
|
|
21
|
+
* @experimental In 0.6; stable from 0.7 (RFC 0001, decision 7).
|
|
22
|
+
*/
|
|
23
|
+
export class KgdiffLegend extends BoundElement implements KgdiffLegendElement {
|
|
24
|
+
static override get observedAttributes(): readonly string[] {
|
|
25
|
+
return ['for', 'variant', 'open'];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
protected readonly name = 'legend';
|
|
29
|
+
#legend: Legend | null = null;
|
|
30
|
+
#reflecting = false;
|
|
31
|
+
|
|
32
|
+
/** `full` (the card) or `mini` (one line), reflected to `variant`; another value reads `full`. */
|
|
33
|
+
get variant(): LegendVariant {
|
|
34
|
+
const v = this.getAttribute('variant') as LegendVariant | null;
|
|
35
|
+
return v !== null && VARIANTS.includes(v) ? v : 'full';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
set variant(value: LegendVariant) {
|
|
39
|
+
if (VARIANTS.includes(value) && value !== 'full') this.setAttribute('variant', value);
|
|
40
|
+
else this.removeAttribute('variant');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Whether the card is open, reflected to the boolean `open` attribute (the user's toggle too). */
|
|
44
|
+
get open(): boolean {
|
|
45
|
+
return this.hasAttribute('open');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
set open(value: boolean) {
|
|
49
|
+
this.toggleAttribute('open', Boolean(value));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
override attributeChangedCallback(name: string, old: string | null, value: string | null): void {
|
|
53
|
+
if (name === 'for') super.attributeChangedCallback(name, old, value);
|
|
54
|
+
else if (name === 'open' && !this.#reflecting) this.#legend?.setOpen(this.open);
|
|
55
|
+
else if (name === 'variant') this.#place();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
protected override upgradedProperties(): readonly string[] {
|
|
59
|
+
return [...super.upgradedProperties(), 'variant', 'open'];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
protected build(ui: UiContext): Region {
|
|
63
|
+
const legend = new Legend(ui, (open) => {
|
|
64
|
+
// The user's toggle, reflected as <details> does.
|
|
65
|
+
this.#reflecting = true;
|
|
66
|
+
try {
|
|
67
|
+
this.toggleAttribute('open', open);
|
|
68
|
+
} finally {
|
|
69
|
+
this.#reflecting = false;
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
this.#legend = legend;
|
|
73
|
+
legend.setOpen(this.open);
|
|
74
|
+
this.#place();
|
|
75
|
+
return {
|
|
76
|
+
update: (topics) => {
|
|
77
|
+
if (this.#legend === legend) legend.update(topics);
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Show the variant asked for. */
|
|
83
|
+
#place(): void {
|
|
84
|
+
const legend = this.#legend;
|
|
85
|
+
if (!legend || !this.isConnected) return;
|
|
86
|
+
const shown = this.variant === 'mini' ? legend.mini : legend.card;
|
|
87
|
+
if (this.shadow.firstElementChild !== shown) this.shadow.replaceChildren(shown);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
override disconnectedCallback(): void {
|
|
91
|
+
super.disconnectedCallback();
|
|
92
|
+
this.#legend = null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
define('kgdiff-legend', KgdiffLegend);
|
|
97
|
+
|
|
98
|
+
declare global {
|
|
99
|
+
interface HTMLElementTagNameMap {
|
|
100
|
+
/** @experimental In 0.6. */
|
|
101
|
+
'kgdiff-legend': KgdiffLegendElement;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<kgdiff-minimap>` (RFC 0001 §3), documented on its class. Importing this module defines it.
|
|
3
|
+
*/
|
|
4
|
+
import type { Region } from '../bind.js';
|
|
5
|
+
import { attr, h } from '../dom.js';
|
|
6
|
+
import type { KgdiffMinimapElement } from '../index.js';
|
|
7
|
+
import type { UiContext } from '../ui/context.js';
|
|
8
|
+
import type { StageView } from '../view.js';
|
|
9
|
+
import { define } from './bound.js';
|
|
10
|
+
import { ViewBoundElement } from './view-bound.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* An overview of one stage's view, with the part in view outlined. While the whole graph is in view
|
|
14
|
+
* it has nothing to add: it is in the custom state `overview` then (`:state(overview)`, and the
|
|
15
|
+
* read-only `overview` property). A minimap the page places stays shown
|
|
16
|
+
* (`kgdiff-minimap:state(overview) { visibility: hidden }` hides it without moving the layout); one
|
|
17
|
+
* slotted into a stage replaces the stage's own and hides as it did. Binds to the nearest
|
|
18
|
+
* `<kgdiff-stage>` around it, or to the one `stage="<id>"` names (`stage`, `stageElement`,
|
|
19
|
+
* `rootElement`). Slotted into a stage it replaces the stage's built-in minimap; placed anywhere
|
|
20
|
+
* else it is not kept clear of fits and flights unless registered.
|
|
21
|
+
*
|
|
22
|
+
* @tagname kgdiff-minimap
|
|
23
|
+
* @cssstate overview - While the whole graph is in view. A minimap the page places stays
|
|
24
|
+
* shown; `kgdiff-minimap:state(overview) { visibility: hidden }` hides it in place.
|
|
25
|
+
* Slotted into a stage it hides then, as the stage's own does.
|
|
26
|
+
* @experimental In 0.6; stable from 0.7 (RFC 0001, decision 7).
|
|
27
|
+
*/
|
|
28
|
+
export class KgdiffMinimap extends ViewBoundElement implements KgdiffMinimapElement {
|
|
29
|
+
protected readonly name = 'minimap';
|
|
30
|
+
|
|
31
|
+
/** The custom states (`:state(overview)`); null where `ElementInternals` is missing. */
|
|
32
|
+
readonly #internals: ElementInternals | null =
|
|
33
|
+
typeof this.attachInternals === 'function' ? this.attachInternals() : null;
|
|
34
|
+
#overview = false;
|
|
35
|
+
|
|
36
|
+
/** Whether the whole graph is in view: the custom state `overview` (`:state(overview)`). */
|
|
37
|
+
get overview(): boolean {
|
|
38
|
+
return this.#overview;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
#setOverview(overview: boolean): void {
|
|
42
|
+
if (overview === this.#overview) return;
|
|
43
|
+
this.#overview = overview;
|
|
44
|
+
const states = this.#internals?.states;
|
|
45
|
+
if (!states) return;
|
|
46
|
+
if (overview) states.add('overview');
|
|
47
|
+
else states.delete('overview');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
protected build(ui: UiContext, view: StageView, release: (fn: () => void) => void): Region {
|
|
51
|
+
const host = h('div', { class: 'minimap-host' });
|
|
52
|
+
this.shadow.append(host);
|
|
53
|
+
release(view.addMinimap(host));
|
|
54
|
+
release(() => this.#setOverview(false));
|
|
55
|
+
return {
|
|
56
|
+
update: (topics) => {
|
|
57
|
+
if (topics.has('status') || topics.has('data') || topics.has('locale')) {
|
|
58
|
+
const label = ui.tr().t('minimap.label');
|
|
59
|
+
attr(host, 'aria-label', label);
|
|
60
|
+
attr(host, 'title', label);
|
|
61
|
+
}
|
|
62
|
+
this.#setOverview(view.overview);
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
define('kgdiff-minimap', KgdiffMinimap);
|
|
69
|
+
|
|
70
|
+
declare global {
|
|
71
|
+
interface HTMLElementTagNameMap {
|
|
72
|
+
/** @experimental In 0.6. */
|
|
73
|
+
'kgdiff-minimap': KgdiffMinimapElement;
|
|
74
|
+
}
|
|
75
|
+
}
|