@issuegraph/editor 0.25.0 → 0.26.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 +6 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/overlay/render.d.ts.map +1 -1
- package/dist/overlay/render.js +18 -2
- package/dist/overlay/render.js.map +1 -1
- package/dist/reevaluate/styles.d.ts +7 -1
- package/dist/reevaluate/styles.d.ts.map +1 -1
- package/dist/reevaluate/styles.js +32 -11
- package/dist/reevaluate/styles.js.map +1 -1
- package/dist/scale/render.d.ts +5 -1
- package/dist/scale/render.d.ts.map +1 -1
- package/dist/scale/render.js +19 -2
- package/dist/scale/render.js.map +1 -1
- package/dist/workspace/host.d.ts +33 -2
- package/dist/workspace/host.d.ts.map +1 -1
- package/dist/workspace/host.js +64 -14
- package/dist/workspace/host.js.map +1 -1
- package/dist/workspace/inspector.d.ts +9 -0
- package/dist/workspace/inspector.d.ts.map +1 -1
- package/dist/workspace/inspector.js +16 -0
- package/dist/workspace/inspector.js.map +1 -1
- package/dist/workspace/mount.d.ts +17 -3
- package/dist/workspace/mount.d.ts.map +1 -1
- package/dist/workspace/mount.js +150 -30
- package/dist/workspace/mount.js.map +1 -1
- package/dist/workspace/narrow.d.ts +38 -0
- package/dist/workspace/narrow.d.ts.map +1 -0
- package/dist/workspace/narrow.js +48 -0
- package/dist/workspace/narrow.js.map +1 -0
- package/dist/workspace/related.d.ts +65 -0
- package/dist/workspace/related.d.ts.map +1 -0
- package/dist/workspace/related.js +105 -0
- package/dist/workspace/related.js.map +1 -0
- package/dist/workspace/render.d.ts +102 -19
- package/dist/workspace/render.d.ts.map +1 -1
- package/dist/workspace/render.js +211 -29
- package/dist/workspace/render.js.map +1 -1
- package/dist/workspace/selection.d.ts +44 -3
- package/dist/workspace/selection.d.ts.map +1 -1
- package/dist/workspace/selection.js +58 -0
- package/dist/workspace/selection.js.map +1 -1
- package/dist/workspace/styles.d.ts +1 -1
- package/dist/workspace/styles.d.ts.map +1 -1
- package/dist/workspace/styles.js +265 -8
- package/dist/workspace/styles.js.map +1 -1
- package/package.json +3 -3
package/dist/workspace/mount.js
CHANGED
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
*/
|
|
48
48
|
import { edgeIdentity } from '@issuegraph/core';
|
|
49
49
|
import { nextDocument } from '@issuegraph/store';
|
|
50
|
-
import { navigate, renderViewer, resolveTheme, } from '@issuegraph/viewer';
|
|
50
|
+
import { isFooterSlot, navigate, normalizeDocument, renderViewer, resolveTheme, sameSelection, } from '@issuegraph/viewer';
|
|
51
51
|
import { CONTROL_ATTRIBUTES } from "../a11y/baseline.js";
|
|
52
52
|
import { isChoosingKind, isDraftLive } from "../create/draft.js";
|
|
53
53
|
import { KIND_KEYS, keyIntent } from "../create/keys.js";
|
|
@@ -64,10 +64,9 @@ import { mountStylesheet } from "./chrome.js";
|
|
|
64
64
|
import { renderWorkspaceSkeleton } from "./skeleton.js";
|
|
65
65
|
import { INITIAL_HOST_STATE, editCarrier, narrowOverlayOpen, railRowAt, railSlackFor, railWindowTarget, reconcileHost, reduceHost, targetMatches, } from "./host.js";
|
|
66
66
|
import { conflictDiff } from "./recovery.js";
|
|
67
|
-
import { renderWorkspace, } from "./render.js";
|
|
68
|
-
import { selectedEdgeId, selectedKey } from "./selection.js";
|
|
69
|
-
|
|
70
|
-
export const CANVAS_MODES = Object.freeze(['neighbourhood', 'tree']);
|
|
67
|
+
import { RELATED_STATUS_CLASS, renderWorkspace, } from "./render.js";
|
|
68
|
+
import { selectedEdgeId, selectedKey, toViewerSelection } from "./selection.js";
|
|
69
|
+
export { CANVAS_MODES } from "./narrow.js";
|
|
71
70
|
/** The package default: wide enough that a scroll rarely lands past the drawn rows. */
|
|
72
71
|
export const MOUNT_RAIL_COUNT = 80;
|
|
73
72
|
/** The kind chooser's size for placement — the stylesheet decides the real one; this only picks a corner. */
|
|
@@ -83,6 +82,8 @@ const KEY_ATTRIBUTE = 'data-ig-key';
|
|
|
83
82
|
* what has to be preserved is the live region specifically.
|
|
84
83
|
*/
|
|
85
84
|
const LIVE_REGION = '.ig-change-line[role="status"]';
|
|
85
|
+
/** The Related groups search's status line, carried across redraws the same way. */
|
|
86
|
+
const RELATED_STATUS = `.${RELATED_STATUS_CLASS}[role="status"]`;
|
|
86
87
|
const GROUP_ATTRIBUTE = 'data-ig-group';
|
|
87
88
|
const COMMAND_ATTRIBUTE = 'data-ig-command';
|
|
88
89
|
/**
|
|
@@ -165,6 +166,13 @@ function isNavigating(node) {
|
|
|
165
166
|
function isInput(node) {
|
|
166
167
|
return node !== null && node !== undefined && node.tagName === 'INPUT';
|
|
167
168
|
}
|
|
169
|
+
/** Whether a key press was typed into a text field. */
|
|
170
|
+
function typedInField(event) {
|
|
171
|
+
const target = event.target;
|
|
172
|
+
if (!isElement(target))
|
|
173
|
+
return false;
|
|
174
|
+
return target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.closest('[contenteditable="true"]') !== null;
|
|
175
|
+
}
|
|
168
176
|
function isComposing(event) {
|
|
169
177
|
return 'isComposing' in event && event.isComposing === true;
|
|
170
178
|
}
|
|
@@ -220,7 +228,18 @@ export function mountWorkspace(element, options) {
|
|
|
220
228
|
const surface = doc.createElement('div');
|
|
221
229
|
surface.className = 'ig-mount';
|
|
222
230
|
element.append(styles, surface);
|
|
223
|
-
let state = INITIAL_HOST_STATE;
|
|
231
|
+
let state = { ...INITIAL_HOST_STATE, canvas: options.canvas ?? INITIAL_HOST_STATE.canvas };
|
|
232
|
+
// THE LAST SELECTION THE HOST WAS TOLD, compared by value in `render()`,
|
|
233
|
+
// which every path reaches — a gesture, a store notification, the
|
|
234
|
+
// reconciliation that drops a member — so the notice has one home.
|
|
235
|
+
let announced = state.selection;
|
|
236
|
+
/** The viewer document last drawn, which a Shift range reads its rank order from. */
|
|
237
|
+
let lastViewer = null;
|
|
238
|
+
// THE RAIL OFFSET THE SCROLL HANDLER LAST SAW, which is what every redraw
|
|
239
|
+
// restores. Read back off the old rail instead, a redraw while another tab
|
|
240
|
+
// shows would restore the 0 a hidden rail reports, and the reader would lose
|
|
241
|
+
// their place in the order on the way back.
|
|
242
|
+
let railOffset = 0;
|
|
224
243
|
let pending = false;
|
|
225
244
|
let destroyed = false;
|
|
226
245
|
// What the last redraw drew, kept so a key press can ask the viewer's own
|
|
@@ -949,7 +968,7 @@ export function mountWorkspace(element, options) {
|
|
|
949
968
|
}).scene;
|
|
950
969
|
if (zoneName !== 'canvas')
|
|
951
970
|
return null;
|
|
952
|
-
if (
|
|
971
|
+
if (state.canvas === 'tree')
|
|
953
972
|
return renderViewer(withoutChrome(drawn.viewer), { projection: 'tree', theme: theme(), chrome: false, frame: false, dockLegend: true }).scene;
|
|
954
973
|
const ladder = scaleLadder(drawn.viewer, state.scale);
|
|
955
974
|
return ladder.tier === 'direct'
|
|
@@ -1054,6 +1073,7 @@ export function mountWorkspace(element, options) {
|
|
|
1054
1073
|
const viewer = unsettled.length === 0 && kept.length === projected.viewer.edges.length
|
|
1055
1074
|
? projected.viewer
|
|
1056
1075
|
: { ...projected.viewer, edges: [...kept, ...unsettled] };
|
|
1076
|
+
lastViewer = viewer;
|
|
1057
1077
|
const { audit } = projected;
|
|
1058
1078
|
const resolved = theme();
|
|
1059
1079
|
// THE WRITE STATES ONLY. The workspace holds the one selection, and every
|
|
@@ -1249,6 +1269,12 @@ export function mountWorkspace(element, options) {
|
|
|
1249
1269
|
// that reopened itself on every landed write would be unusable.
|
|
1250
1270
|
canvasOpen: state.canvasOpen,
|
|
1251
1271
|
inspectorDismissed: state.inspectorDismissed,
|
|
1272
|
+
// The layout under 640's tab and the Related groups list's place, for the
|
|
1273
|
+
// same reason: the renderer keeps nothing across a redraw.
|
|
1274
|
+
narrowView: state.narrowView,
|
|
1275
|
+
canvas: state.canvas,
|
|
1276
|
+
relatedGroup: state.relatedGroup,
|
|
1277
|
+
relatedQuery: state.relatedQuery,
|
|
1252
1278
|
// §17d's overlay. Host state, for the same reason the filter beside it
|
|
1253
1279
|
// is: the renderer keeps nothing across a redraw.
|
|
1254
1280
|
auditOpen: state.auditOpen,
|
|
@@ -1280,11 +1306,9 @@ export function mountWorkspace(element, options) {
|
|
|
1280
1306
|
const sheet = [result.styles, pickerStylesheet, firstPassStylesheet, mountStylesheet].join('\n');
|
|
1281
1307
|
if (styles.textContent !== sheet)
|
|
1282
1308
|
styles.textContent = sheet;
|
|
1283
|
-
// What the reader was doing survives the redraw: the rail's scroll offset
|
|
1284
|
-
// the audit panel's, and the caret in whichever
|
|
1285
|
-
// into.
|
|
1286
|
-
const railBefore = zone('rail');
|
|
1287
|
-
const scrollTop = railBefore?.scrollTop ?? 0;
|
|
1309
|
+
// What the reader was doing survives the redraw: the rail's scroll offset
|
|
1310
|
+
// (kept as `railOffset`), the audit panel's, and the caret in whichever
|
|
1311
|
+
// search box they were typing into.
|
|
1288
1312
|
// THE AUDIT SCROLLS TOO, SINCE IT TOOK A SHARE OF ITS COLUMN. Before it had
|
|
1289
1313
|
// a bound it grew to its content and only the ZONE scrolled, so there was no
|
|
1290
1314
|
// second offset to keep. Now a long audit is read inside its own scroller,
|
|
@@ -1360,19 +1384,24 @@ export function mountWorkspace(element, options) {
|
|
|
1360
1384
|
// summary, and only then given the new one. The insertion says nothing and
|
|
1361
1385
|
// the mutation is what announces, which is the sequence the region was
|
|
1362
1386
|
// designed around.
|
|
1363
|
-
|
|
1387
|
+
// THE RELATED SEARCH'S STATUS LINE IS THE SAME KIND OF REGION and is
|
|
1388
|
+
// carried the same way, so the left-out count and the no-match sentence
|
|
1389
|
+
// are announced as they change while the reader types.
|
|
1390
|
+
const carried = [LIVE_REGION, RELATED_STATUS].map((selector) => [selector, surface.querySelector(selector)]);
|
|
1364
1391
|
// Package-rendered markup, escaped by the package.
|
|
1365
1392
|
surface.innerHTML = result.markup;
|
|
1366
|
-
const
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
//
|
|
1373
|
-
//
|
|
1374
|
-
|
|
1375
|
-
|
|
1393
|
+
for (const [selector, before] of carried) {
|
|
1394
|
+
const after = surface.querySelector(selector);
|
|
1395
|
+
if (before === null || after === null)
|
|
1396
|
+
continue;
|
|
1397
|
+
const next = after.innerHTML;
|
|
1398
|
+
after.replaceWith(before);
|
|
1399
|
+
// GUARDED, so a redraw that did not touch the region is not a change to
|
|
1400
|
+
// announce. Every keystroke in a search box redraws this surface, and
|
|
1401
|
+
// re-assigning identical content would re-announce the last result on
|
|
1402
|
+
// each one.
|
|
1403
|
+
if (before.innerHTML !== next)
|
|
1404
|
+
before.innerHTML = next;
|
|
1376
1405
|
}
|
|
1377
1406
|
// THE ORDER'S STATUS, PUBLISHED AS DATA. A write in flight holds the order
|
|
1378
1407
|
// — it does not move until the edit lands — and a host that wants to say
|
|
@@ -1380,7 +1409,7 @@ export function mountWorkspace(element, options) {
|
|
|
1380
1409
|
// attribute is the store's own vocabulary, verbatim, so a host styles or
|
|
1381
1410
|
// reads it without this package inventing a sentence.
|
|
1382
1411
|
surface.setAttribute('data-order', snapshot.order.status);
|
|
1383
|
-
if (
|
|
1412
|
+
if (state.canvas === 'tree') {
|
|
1384
1413
|
const canvas = zone('canvas');
|
|
1385
1414
|
if (canvas !== null) {
|
|
1386
1415
|
// THE SAME STATES THE LADDER DRAWS, on the tree's badges. The tree
|
|
@@ -1425,6 +1454,12 @@ export function mountWorkspace(element, options) {
|
|
|
1425
1454
|
// element is not assembled from optional parts: `isolatedSpec` returns
|
|
1426
1455
|
// null rather than an empty husk, so anything found here has content.
|
|
1427
1456
|
const isolated = canvas.querySelector('.ig-ladder-isolated');
|
|
1457
|
+
// §17d'S FINDINGS PANEL SURVIVES TOO. It is an overlay drawn in THIS
|
|
1458
|
+
// zone (RULINGS.md §3: over the canvas, anchored to the header count),
|
|
1459
|
+
// so the assignment below deleted it and the header's problem count
|
|
1460
|
+
// opened nothing in tree mode, at every width. It is kept whole, as the
|
|
1461
|
+
// isolated list is, and put back last so it still floats over the tree.
|
|
1462
|
+
const findings = canvas.querySelector('.ig-audit-overlay');
|
|
1428
1463
|
canvas.innerHTML = renderViewer(withoutChrome(viewer), {
|
|
1429
1464
|
projection: 'tree',
|
|
1430
1465
|
theme: resolved,
|
|
@@ -1461,6 +1496,8 @@ export function mountWorkspace(element, options) {
|
|
|
1461
1496
|
surface.firstElementChild?.setAttribute('data-canvas-legend', 'drawn');
|
|
1462
1497
|
if (isolated !== null)
|
|
1463
1498
|
canvas.append(isolated);
|
|
1499
|
+
if (findings !== null)
|
|
1500
|
+
canvas.append(findings);
|
|
1464
1501
|
if (toolbar !== null && toolbar.childElementCount > 0)
|
|
1465
1502
|
canvas.prepend(toolbar);
|
|
1466
1503
|
const states = new Map(overlaysFor(viewer.edges, writeStates, selectedEdgeId(state.selection)).map((edge) => [edge.id, overlayFor(edge).attribute]));
|
|
@@ -1489,7 +1526,7 @@ export function mountWorkspace(element, options) {
|
|
|
1489
1526
|
}
|
|
1490
1527
|
const rail = zone('rail');
|
|
1491
1528
|
if (rail !== null)
|
|
1492
|
-
rail.scrollTop =
|
|
1529
|
+
rail.scrollTop = railOffset;
|
|
1493
1530
|
// AFTER the subtree is in place, because it measures what was just drawn.
|
|
1494
1531
|
dockRailLegend();
|
|
1495
1532
|
// RE-QUERIED, NEVER REUSED: `surface.innerHTML` above replaced the subtree,
|
|
@@ -1605,6 +1642,9 @@ export function mountWorkspace(element, options) {
|
|
|
1605
1642
|
if (isFocusable(now) && typeof now.scrollIntoView === 'function')
|
|
1606
1643
|
now.scrollIntoView({ block: 'nearest' });
|
|
1607
1644
|
}
|
|
1645
|
+
// Recorded here as well as by the scroll handler, whose event arrives
|
|
1646
|
+
// later: a redraw before it would put the rail back where the jump began.
|
|
1647
|
+
railOffset = rail.scrollTop;
|
|
1608
1648
|
}
|
|
1609
1649
|
}
|
|
1610
1650
|
else {
|
|
@@ -1726,6 +1766,53 @@ export function mountWorkspace(element, options) {
|
|
|
1726
1766
|
}
|
|
1727
1767
|
firstPassWas = state.firstPass.phase.kind;
|
|
1728
1768
|
searchWasOpen = search !== null;
|
|
1769
|
+
announceSelection();
|
|
1770
|
+
};
|
|
1771
|
+
/**
|
|
1772
|
+
* Tell the host the selection changed, once, with a fresh frozen copy. The
|
|
1773
|
+
* announced value is written BEFORE the callback, so a host that changes the
|
|
1774
|
+
* selection from inside it is told about that change on its own render.
|
|
1775
|
+
*/
|
|
1776
|
+
const announceSelection = () => {
|
|
1777
|
+
const now = state.selection;
|
|
1778
|
+
if (sameSelection(toViewerSelection(announced), toViewerSelection(now)))
|
|
1779
|
+
return;
|
|
1780
|
+
announced = now;
|
|
1781
|
+
const handed = now.kind === 'issues' ? Object.freeze({ kind: 'issues', keys: Object.freeze([...now.keys]) }) : Object.freeze({ ...now });
|
|
1782
|
+
current.onSelectionChange?.(handed);
|
|
1783
|
+
};
|
|
1784
|
+
/**
|
|
1785
|
+
* The WHOLE rank order as last drawn — every member of every ranked slot —
|
|
1786
|
+
* which a Shift range walks. Never the rail window: a range whose anchor is
|
|
1787
|
+
* outside the window covers the full span.
|
|
1788
|
+
*/
|
|
1789
|
+
const rankOrder = () => lastViewer === null
|
|
1790
|
+
? []
|
|
1791
|
+
: normalizeDocument(lastViewer).document.order.slots
|
|
1792
|
+
.filter((slot) => slot.rank !== null)
|
|
1793
|
+
.flatMap((slot) => slot.members);
|
|
1794
|
+
/** The pointer gesture a click's modifiers ask for: Cmd or Ctrl toggles, Shift ranges. */
|
|
1795
|
+
const gestureOf = (event) => {
|
|
1796
|
+
if (event.metaKey || event.ctrlKey)
|
|
1797
|
+
return { gesture: 'toggle' };
|
|
1798
|
+
return event.shiftKey ? { gesture: 'range', order: rankOrder() } : {};
|
|
1799
|
+
};
|
|
1800
|
+
/**
|
|
1801
|
+
* The row across the rail window's edge that ArrowDown on its last drawn row
|
|
1802
|
+
* (or ArrowUp on its first) will focus once the window is re-cut: the next
|
|
1803
|
+
* (or previous) slot in the WHOLE sequence. `null` when the press does not
|
|
1804
|
+
* re-cut the window — exactly the conditions `advanceRail` re-cuts on.
|
|
1805
|
+
*/
|
|
1806
|
+
const acrossEdge = (rail, key, pressedKey) => {
|
|
1807
|
+
const offset = rail.offsetOf(key);
|
|
1808
|
+
if (offset === undefined || lastViewer === null)
|
|
1809
|
+
return null;
|
|
1810
|
+
const down = pressedKey === 'ArrowDown' && rail.after > 0 && (rail.rows.at(-1)?.members.includes(key) ?? false);
|
|
1811
|
+
const up = pressedKey === 'ArrowUp' && rail.before > 0 && (rail.rows[0]?.members.includes(key) ?? false);
|
|
1812
|
+
if (!down && !up)
|
|
1813
|
+
return null;
|
|
1814
|
+
const sequence = normalizeDocument(lastViewer).document.order.slots.filter((slot) => !isFooterSlot(slot));
|
|
1815
|
+
return sequence[offset + (down ? 1 : -1)]?.lead ?? null;
|
|
1729
1816
|
};
|
|
1730
1817
|
/**
|
|
1731
1818
|
* Take the option's scanner, and end the lifecycle the old one was scanning for.
|
|
@@ -1907,17 +1994,20 @@ export function mountWorkspace(element, options) {
|
|
|
1907
1994
|
dispatch({ kind: 'group', id });
|
|
1908
1995
|
return;
|
|
1909
1996
|
}
|
|
1997
|
+
// MODIFIERS ARE READ ON AN ISSUE ONLY. A relationship above stays a plain
|
|
1998
|
+
// relationship click under any modifier, replacing any set.
|
|
1910
1999
|
const key = named.getAttribute(KEY_ATTRIBUTE);
|
|
1911
2000
|
if (key !== null)
|
|
1912
|
-
dispatch({ kind: 'point', key });
|
|
2001
|
+
dispatch({ kind: 'point', key, ...gestureOf(event) });
|
|
1913
2002
|
};
|
|
1914
2003
|
const readInput = (event) => {
|
|
1915
2004
|
const target = isElement(event.target) && isInput(event.target) ? event.target : null;
|
|
1916
2005
|
if (target === null || !surface.contains(target))
|
|
1917
2006
|
return;
|
|
1918
2007
|
const name = target.getAttribute(COMMAND_ATTRIBUTE);
|
|
1919
|
-
if (name === 'search' || name === 'target-query')
|
|
2008
|
+
if (name === 'search' || name === 'target-query' || name === 'related-query') {
|
|
1920
2009
|
dispatch({ kind: 'control', name, value: target.value });
|
|
2010
|
+
}
|
|
1921
2011
|
};
|
|
1922
2012
|
// NOT WHILE AN INPUT METHOD IS COMPOSING. Every keystroke of a composition
|
|
1923
2013
|
// fires `input`, and a redraw replaces the element that owns the
|
|
@@ -1978,6 +2068,7 @@ export function mountWorkspace(element, options) {
|
|
|
1978
2068
|
const rail = zone('rail');
|
|
1979
2069
|
if (rail === null || event.target !== rail)
|
|
1980
2070
|
return;
|
|
2071
|
+
railOffset = rail.scrollTop;
|
|
1981
2072
|
if (drawn === null)
|
|
1982
2073
|
return;
|
|
1983
2074
|
// THE DECISION IS THE REDUCER'S, and `null` is load-bearing: a scroll that
|
|
@@ -2237,16 +2328,33 @@ export function mountWorkspace(element, options) {
|
|
|
2237
2328
|
const key = active.closest(`[${KEY_ATTRIBUTE}]`)?.getAttribute(KEY_ATTRIBUTE) ?? null;
|
|
2238
2329
|
if (owner === null || zoneName === null || key === null || !surface.contains(owner))
|
|
2239
2330
|
return false;
|
|
2240
|
-
|
|
2241
|
-
|
|
2331
|
+
// SHIFT IS READ BEFORE THE RE-CUT, because `advanceRail` returns before
|
|
2332
|
+
// `navigate` runs: the range goes to the neighbour in the whole order that
|
|
2333
|
+
// the re-cut then focuses, so the set extends across the edge exactly as
|
|
2334
|
+
// it does inside the window.
|
|
2335
|
+
if (zoneName === 'rail' && drawn !== null) {
|
|
2336
|
+
const across = event.shiftKey ? acrossEdge(drawn.rail, key, event.key) : null;
|
|
2337
|
+
if (across !== null)
|
|
2338
|
+
dispatch({ kind: 'point', key: across, gesture: 'range', order: rankOrder() });
|
|
2339
|
+
if (drawn !== null && advanceRail(drawn.rail, key, event.key))
|
|
2340
|
+
return true;
|
|
2341
|
+
}
|
|
2242
2342
|
const scene = sceneFor(zoneName);
|
|
2243
2343
|
if (scene === null)
|
|
2244
2344
|
return false;
|
|
2245
|
-
const result = navigate(scene, { focused: key, selected: selectedKey(state.selection) }, event.key
|
|
2345
|
+
const result = navigate(scene, { focused: key, selected: selectedKey(state.selection) }, event.key, {
|
|
2346
|
+
shift: event.shiftKey,
|
|
2347
|
+
});
|
|
2246
2348
|
if (result.command.kind === 'select') {
|
|
2247
2349
|
dispatch({ kind: 'point', key: result.command.key });
|
|
2248
2350
|
return true;
|
|
2249
2351
|
}
|
|
2352
|
+
// SHIFT WITH AN ARROW: a Shift-click on the issue focus moves to.
|
|
2353
|
+
if (result.command.kind === 'extend') {
|
|
2354
|
+
dispatch({ kind: 'point', key: result.command.key, gesture: 'range', order: rankOrder() });
|
|
2355
|
+
focusIn(zoneName, result.command.key);
|
|
2356
|
+
return true;
|
|
2357
|
+
}
|
|
2250
2358
|
if (result.command.kind === 'focus') {
|
|
2251
2359
|
focusIn(zoneName, result.command.key);
|
|
2252
2360
|
return true;
|
|
@@ -2466,6 +2574,14 @@ export function mountWorkspace(element, options) {
|
|
|
2466
2574
|
dispatch({ kind: 'control', name: 'narrow-dismiss' });
|
|
2467
2575
|
return;
|
|
2468
2576
|
}
|
|
2577
|
+
// THE SELECTION CLEARS LAST, only when nothing above claimed the press
|
|
2578
|
+
// and before the create map's cancel, and never from a text field: an
|
|
2579
|
+
// Escape typed there belongs to the field.
|
|
2580
|
+
if (state.selection.kind !== 'none' && !typedInField(event)) {
|
|
2581
|
+
event.preventDefault();
|
|
2582
|
+
dispatch({ kind: 'control', name: 'clear' });
|
|
2583
|
+
return;
|
|
2584
|
+
}
|
|
2469
2585
|
}
|
|
2470
2586
|
const intent = keyIntent(event, context);
|
|
2471
2587
|
if (intent.kind !== 'none') {
|
|
@@ -2796,6 +2912,10 @@ export function mountWorkspace(element, options) {
|
|
|
2796
2912
|
return;
|
|
2797
2913
|
if (next !== undefined) {
|
|
2798
2914
|
current = { ...current, ...next, store };
|
|
2915
|
+
// ONLY AN UPDATE THAT NAMES THE MODE SETS IT. `current` keeps the mount's
|
|
2916
|
+
// seed, so re-reading it here would undo the reader's last tab press.
|
|
2917
|
+
if (next.canvas !== undefined)
|
|
2918
|
+
state = { ...state, canvas: next.canvas };
|
|
2799
2919
|
adoptSource();
|
|
2800
2920
|
}
|
|
2801
2921
|
schedule();
|