@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,225 @@
1
+ /**
2
+ * Node search (RFC 0001 §3, `<kgdiff-search>` from phase 2): a combobox and the results list it
3
+ * owns. ID references (`aria-controls`, `aria-activedescendant`) do not cross shadow roots, so
4
+ * input and results stay in one component; the layout decides where the results show (the
5
+ * default layout puts them in the change list's place). The query and its hits live in the
6
+ * session, so the change list steps aside without either knowing the other. Registers itself
7
+ * as the root's `search` focus target (`/`).
8
+ */
9
+ import { attr, h, icon, isOpen, replace } from '../dom.js';
10
+ import type { Topic } from '../session.js';
11
+ import type { UiContext } from './context.js';
12
+
13
+ export class Search {
14
+ /** The search box. */
15
+ readonly box: HTMLElement;
16
+ /** The results listbox. */
17
+ readonly results: HTMLElement;
18
+ readonly input: HTMLInputElement;
19
+ readonly #kbd: HTMLElement;
20
+ readonly #clear: HTMLButtonElement;
21
+ #activeHit = 0;
22
+ /** The query the results were rendered for (a new one starts at its first hit). */
23
+ #rendered = '';
24
+
25
+ readonly #ctx: UiContext;
26
+
27
+ constructor(ctx: UiContext) {
28
+ this.#ctx = ctx;
29
+ this.input = h('input', {
30
+ type: 'search',
31
+ autocomplete: 'off',
32
+ spellcheck: 'false',
33
+ role: 'combobox',
34
+ 'aria-autocomplete': 'list',
35
+ 'aria-expanded': 'false',
36
+ 'aria-controls': 'kg-results',
37
+ enterkeyhint: 'go',
38
+ });
39
+ this.#kbd = h('kbd', { 'aria-hidden': 'true', text: '/' });
40
+ this.#clear = h(
41
+ 'button',
42
+ { type: 'button', class: 'clear', hidden: true, onclick: () => this.#setQuery('', true) },
43
+ icon('close'),
44
+ );
45
+ this.input.addEventListener('input', () => this.#setQuery(this.input.value));
46
+ this.input.addEventListener('keydown', (e) => this.#onKey(e));
47
+ this.input.addEventListener('focus', () => this.#renderChrome());
48
+ this.input.addEventListener('blur', () => this.#renderChrome());
49
+ this.results = h('div', {
50
+ class: 'results list scroll',
51
+ id: 'kg-results',
52
+ role: 'listbox',
53
+ hidden: true,
54
+ });
55
+ this.box = h('div', { class: 'search' }, icon('search'), this.input, this.#kbd, this.#clear);
56
+ ctx.target(
57
+ 'search',
58
+ () => {
59
+ this.input.focus();
60
+ this.input.select();
61
+ },
62
+ () => isOpen(this.input),
63
+ );
64
+ }
65
+
66
+ update(topics: ReadonlySet<Topic>): void {
67
+ const all = topics.has('status') || topics.has('data') || topics.has('locale');
68
+ if (all) {
69
+ const tr = this.#ctx.tr();
70
+ this.input.placeholder = tr.t('sidebar.searchPlaceholder');
71
+ attr(this.input, 'aria-label', tr.t('sidebar.search'));
72
+ attr(this.#clear, 'aria-label', tr.t('sidebar.searchClear'));
73
+ attr(this.results, 'aria-label', tr.t('sidebar.searchResults'));
74
+ this.input.disabled = this.#ctx.controller.status !== 'ready';
75
+ }
76
+ if (all || topics.has('search')) this.#render();
77
+ }
78
+
79
+ #setQuery(query: string, focus = false): void {
80
+ this.#ctx.controller.setQuery(query);
81
+ if (focus) this.input.focus();
82
+ }
83
+
84
+ #render(): void {
85
+ const query = this.#ctx.controller.query;
86
+ if (this.input.value !== query) this.input.value = query;
87
+ if (query !== this.#rendered) this.#activeHit = 0;
88
+ this.#rendered = query;
89
+ const searching = query.trim() !== '';
90
+ this.results.hidden = !searching;
91
+ attr(this.input, 'aria-expanded', String(searching));
92
+ this.#renderChrome();
93
+ if (searching) this.#renderResults();
94
+ else replace(this.results);
95
+ }
96
+
97
+ #renderChrome(): void {
98
+ const query = this.#ctx.controller.query;
99
+ const focused = this.#ctx.root.activeElement === this.input;
100
+ this.#kbd.hidden = query !== '' || focused;
101
+ this.#clear.hidden = query === '';
102
+ }
103
+
104
+ #renderResults(): void {
105
+ const tr = this.#ctx.tr();
106
+ const c = this.#ctx.controller;
107
+ const q = c.query.trim();
108
+ const hits = c.hits;
109
+ if (hits.length === 0) {
110
+ replace(
111
+ this.results,
112
+ h(
113
+ 'div',
114
+ { class: 'list-empty' },
115
+ icon('search'),
116
+ h('span', { text: tr.t('sidebar.noResults', { q }) }),
117
+ ),
118
+ );
119
+ attr(this.input, 'aria-activedescendant', null);
120
+ return;
121
+ }
122
+ const scene = c.scene;
123
+ const rows = hits.map((hit, i) => {
124
+ const sceneIndex = scene?.nodeIndex.get(hit.node.id);
125
+ const sn = sceneIndex !== undefined ? scene?.nodes[sceneIndex] : undefined;
126
+ const family =
127
+ sn?.family ??
128
+ (hit.node.status === 'added'
129
+ ? 'added'
130
+ : hit.node.status === 'removed'
131
+ ? 'removed'
132
+ : hit.node.status === 'modified'
133
+ ? 'content'
134
+ : 'neutral');
135
+ const sub = hit.field === 'label' ? hit.node.id : hit.matched;
136
+ return h(
137
+ 'button',
138
+ {
139
+ type: 'button',
140
+ class: 'result',
141
+ role: 'option',
142
+ id: `kg-hit-${i}`,
143
+ tabindex: '-1',
144
+ 'data-family': family,
145
+ 'aria-selected': String(i === this.#activeHit),
146
+ onclick: () => this.#choose(i),
147
+ onpointermove: () => this.#setActiveHit(i),
148
+ },
149
+ h('span', { class: 'sdot', 'aria-hidden': 'true' }),
150
+ h(
151
+ 'span',
152
+ { class: 'txt' },
153
+ h('span', { class: 'title' }, highlight(hit.node.label, hit.field === 'label' ? q : '')),
154
+ h('span', { class: 'sub' }, highlight(sub, hit.field === 'label' ? '' : q)),
155
+ ),
156
+ h('span', { class: 'status', text: tr.t(`status.${hit.node.status}`) }),
157
+ );
158
+ });
159
+ replace(
160
+ this.results,
161
+ h('div', {
162
+ class: 'results-head',
163
+ text: tr.plural('sidebar.results', hits.length),
164
+ }),
165
+ rows,
166
+ );
167
+ attr(this.input, 'aria-activedescendant', `kg-hit-${this.#activeHit}`);
168
+ }
169
+
170
+ #setActiveHit(i: number): void {
171
+ if (i === this.#activeHit || i < 0 || i >= this.#ctx.controller.hits.length) return;
172
+ this.#activeHit = i;
173
+ for (const el of this.results.querySelectorAll('.result')) {
174
+ attr(el, 'aria-selected', String(el.id === `kg-hit-${i}`));
175
+ }
176
+ attr(this.input, 'aria-activedescendant', `kg-hit-${i}`);
177
+ this.results.querySelector(`#kg-hit-${i}`)?.scrollIntoView?.({ block: 'nearest' });
178
+ }
179
+
180
+ /** Choose a hit: its node is revealed and selected, and focus moves to the change list. */
181
+ #choose(i: number): void {
182
+ const hit = this.#ctx.controller.hits[i];
183
+ if (!hit) return;
184
+ this.#setQuery('');
185
+ this.#ctx.controller.revealNode(hit.node.id);
186
+ this.#ctx.focus('list');
187
+ }
188
+
189
+ #onKey(e: KeyboardEvent): void {
190
+ switch (e.key) {
191
+ case 'ArrowDown':
192
+ e.preventDefault();
193
+ this.#setActiveHit(Math.min(this.#ctx.controller.hits.length - 1, this.#activeHit + 1));
194
+ break;
195
+ case 'ArrowUp':
196
+ e.preventDefault();
197
+ this.#setActiveHit(Math.max(0, this.#activeHit - 1));
198
+ break;
199
+ case 'Enter':
200
+ e.preventDefault();
201
+ this.#choose(this.#activeHit);
202
+ break;
203
+ case 'Escape':
204
+ e.preventDefault();
205
+ e.stopPropagation();
206
+ if (this.#ctx.controller.query) this.#setQuery('');
207
+ else this.#ctx.focus('list');
208
+ break;
209
+ default:
210
+ return;
211
+ }
212
+ }
213
+ }
214
+
215
+ /** The label with the first occurrence of `q` (case-insensitive) marked. */
216
+ function highlight(text: string, q: string): (string | HTMLElement)[] {
217
+ if (!q) return [text];
218
+ const at = text.toLowerCase().indexOf(q.toLowerCase());
219
+ if (at < 0) return [text];
220
+ return [
221
+ text.slice(0, at),
222
+ h('mark', { text: text.slice(at, at + q.length) }),
223
+ text.slice(at + q.length),
224
+ ];
225
+ }