@adecore/editor 0.21.0 → 0.21.2

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/dist/fake.js CHANGED
@@ -6,7 +6,7 @@ function positionIn(text, offset) {
6
6
  const lines = before.split('\n');
7
7
  return { line: lines.length - 1, character: lines.at(-1).length };
8
8
  }
9
- /* An editor without a DOM, for tests: `type`, `save` and `blur` do what a person would, the rest says what the client asked of it. */
9
+ /* An editor without a DOM, for tests: `type`, `save` and `blur` do what a person would, the rest says what the host asked of it. */
10
10
  export class FakeEditor {
11
11
  element;
12
12
  language;
@@ -20,10 +20,10 @@ export class FakeEditor {
20
20
  revealedLine;
21
21
  focused = false;
22
22
  indentation;
23
- /* How often the client asked it to read the code face off the page again. */
23
+ /* How often the host asked it to read the code face off the page again. */
24
24
  fontRefreshes = 0;
25
25
  disposed = false;
26
- /* What the client asked to find last; null once it ended the find. */
26
+ /* What the host asked to find last; null once it ended the find. */
27
27
  findQuery = null;
28
28
  findState = { count: 0, current: null };
29
29
  text;
@@ -93,9 +93,7 @@ export class FakeEditor {
93
93
  source,
94
94
  changes: [{ range: { start: positionIn(before, span.start), end: positionIn(before, span.end) }, text: span.text }]
95
95
  };
96
- for (const listener of [...this.textChanges]) {
97
- listener(change);
98
- }
96
+ emit(this.textChanges, change);
99
97
  }
100
98
  tracked = new Set();
101
99
  /* The fake sees one change per edit, the stretch that differs, so a range it tracks is lost a little sooner than the real editor's. */
@@ -123,10 +121,7 @@ export class FakeEditor {
123
121
  };
124
122
  }
125
123
  onTextChange(listener) {
126
- this.textChanges.add(listener);
127
- return () => {
128
- this.textChanges.delete(listener);
129
- };
124
+ return subscribe(this.textChanges, listener);
130
125
  }
131
126
  positionAt(offset) {
132
127
  return positionIn(this.text, offset);
@@ -152,13 +147,13 @@ export class FakeEditor {
152
147
  onBlur(listener) {
153
148
  return subscribe(this.blurs, listener);
154
149
  }
155
- /* How the client last asked the view to follow a jump. */
150
+ /* How the host last asked the view to follow a jump. */
156
151
  lastReveal = null;
157
152
  revealLine(line, reveal = 'center') {
158
153
  this.revealedLine = line;
159
154
  this.lastReveal = reveal;
160
155
  }
161
- /* Counts plain occurrences only; the matcher itself is the editor's, and a test here is about the client's side. */
156
+ /* Counts plain occurrences only; the matcher itself is the editor's, and a test here is about the host's side. */
162
157
  find(query) {
163
158
  this.findQuery = query;
164
159
  const needle = query === null || query.text === '' ? '' : query.caseSensitive ? query.text : query.text.toLowerCase();
@@ -166,12 +161,12 @@ export class FakeEditor {
166
161
  const count = needle === '' ? 0 : haystack.split(needle).length - 1;
167
162
  this.announceFind({ count, current: count === 0 ? null : 0 });
168
163
  }
169
- /* What the client asked to replace, one match at a time and all at once. */
164
+ /* What the host asked to replace, one match at a time and all at once. */
170
165
  replacements = [];
171
166
  replaceOptions = [];
172
- /* What the client last asked to show under the match. */
167
+ /* What the host last asked to show under the match. */
173
168
  replacePreview = null;
174
- /* What the client asked to select all matches of, and how many it was told there were. */
169
+ /* What the host asked to select all matches of, and how many it was told there were. */
175
170
  selectedMatches = 0;
176
171
  lastFindFromCursor = null;
177
172
  replacementsOfAll = [];
@@ -204,10 +199,7 @@ export class FakeEditor {
204
199
  }
205
200
  }
206
201
  onFind(listener) {
207
- this.finds.add(listener);
208
- return () => {
209
- this.finds.delete(listener);
210
- };
202
+ return subscribe(this.finds, listener);
211
203
  }
212
204
  endFind() {
213
205
  this.find(null);
@@ -215,40 +207,37 @@ export class FakeEditor {
215
207
  setWrap(wrap) {
216
208
  this.wrap = wrap;
217
209
  }
218
- /* What the client handed it last; null while it reads the structure itself. */
210
+ /* What the host handed it last; null while it reads the structure itself. */
219
211
  blocks = null;
220
212
  scopes = new Set();
221
- /* What the client marked as changed last. */
213
+ /* What the host marked as changed last. */
222
214
  changeMarks = [];
223
215
  setChangeMarks(marks) {
224
216
  this.changeMarks = marks;
225
217
  }
226
- /* The tinted lines the client set last. */
218
+ /* The tinted lines the host set last. */
227
219
  lineHighlights = [];
228
220
  setLineHighlights(highlights) {
229
221
  this.lineHighlights = highlights;
230
222
  }
231
- /* What the client drew as code outside the document: the text and how, and into which element. */
223
+ /* What the host drew as code outside the document: the text and how, and into which element. */
232
224
  codeBlocks = [];
233
225
  renderCode(container, text, options = {}) {
234
226
  this.codeBlocks.push({ container, text, options });
235
227
  }
236
- /* The cursors of agents the client set last. */
228
+ /* The cursors of agents the host set last. */
237
229
  remoteCursors = [];
238
230
  setRemoteCursors(cursors) {
239
231
  this.remoteCursors = cursors;
240
232
  }
241
- /* What the client marked as written by an agent last. */
233
+ /* What the host marked as written by an agent last. */
242
234
  attributionMarks = [];
243
235
  attributionHovers = new Set();
244
236
  setAttributionMarks(marks) {
245
237
  this.attributionMarks = marks;
246
238
  }
247
239
  onAttributionHover(listener) {
248
- this.attributionHovers.add(listener);
249
- return () => {
250
- this.attributionHovers.delete(listener);
251
- };
240
+ return subscribe(this.attributionHovers, listener);
252
241
  }
253
242
  /* The pointer on the bar of a mark, or leaving it. The rect is where `rectAt` puts the mark's first line unless given. */
254
243
  hoverAttribution(id, rect) {
@@ -256,9 +245,7 @@ export class FakeEditor {
256
245
  const hover = mark === undefined
257
246
  ? null
258
247
  : { id: mark.id, rect: rect ?? this.rectAt({ line: mark.startLine - 1, character: 0 }) ?? { left: 0, top: 0, right: 0, bottom: 0 } };
259
- for (const listener of [...this.attributionHovers]) {
260
- listener(hover);
261
- }
248
+ emit(this.attributionHovers, hover);
262
249
  }
263
250
  /* What each owner put next to lines of the text last. */
264
251
  widgetsByOwner = new Map();
@@ -292,22 +279,19 @@ export class FakeEditor {
292
279
  this.lineActionsByOwner.set(owner, actions);
293
280
  }
294
281
  }
295
- /* The code vision rows the client set last. */
282
+ /* The code vision rows the host set last. */
296
283
  codeVision = [];
297
284
  setCodeVision(rows) {
298
285
  this.codeVision = rows;
299
286
  }
300
- /* The button the client set in the gutter last. */
287
+ /* The button the host set in the gutter last. */
301
288
  gutterAction = null;
302
289
  gutterActionListeners = new Set();
303
290
  setGutterAction(action) {
304
291
  this.gutterAction = action;
305
292
  }
306
293
  onGutterAction(listener) {
307
- this.gutterActionListeners.add(listener);
308
- return () => {
309
- this.gutterActionListeners.delete(listener);
310
- };
294
+ return subscribe(this.gutterActionListeners, listener);
311
295
  }
312
296
  /* What each owner put in the gutter last. */
313
297
  gutterMarkersByOwner = new Map();
@@ -321,39 +305,27 @@ export class FakeEditor {
321
305
  }
322
306
  }
323
307
  onGutterMarker(listener) {
324
- this.gutterMarkerListeners.add(listener);
325
- return () => {
326
- this.gutterMarkerListeners.delete(listener);
327
- };
308
+ return subscribe(this.gutterMarkerListeners, listener);
328
309
  }
329
310
  /* A press on a gutter marker. */
330
311
  pressGutterMarker(id) {
331
- for (const listener of [...this.gutterMarkerListeners]) {
332
- listener(id);
333
- }
312
+ emit(this.gutterMarkerListeners, id);
334
313
  }
335
314
  /* A press on the gutter button, if there is one. */
336
315
  pressGutterAction() {
337
316
  if (this.gutterAction !== null) {
338
- for (const listener of [...this.gutterActionListeners]) {
339
- listener(this.gutterAction.line);
340
- }
317
+ emit(this.gutterActionListeners, this.gutterAction.line);
341
318
  }
342
319
  }
343
320
  setBlocks(blocks) {
344
321
  this.blocks = blocks;
345
322
  }
346
323
  onScope(listener) {
347
- this.scopes.add(listener);
348
- return () => {
349
- this.scopes.delete(listener);
350
- };
324
+ return subscribe(this.scopes, listener);
351
325
  }
352
- /* The caret moving into blocks, for a test of what the client draws from it. */
326
+ /* The caret moving into blocks, for a test of what the host draws from it. */
353
327
  enterScope(scope) {
354
- for (const listener of [...this.scopes]) {
355
- listener(scope);
356
- }
328
+ emit(this.scopes, scope);
357
329
  }
358
330
  smartKeys;
359
331
  guides = true;
@@ -386,7 +358,7 @@ export class FakeEditor {
386
358
  setIndentation(indentation) {
387
359
  this.indentation = indentation;
388
360
  }
389
- /* What the client marked and highlighted last. */
361
+ /* What the host marked and highlighted last. */
390
362
  markers = [];
391
363
  highlights = [];
392
364
  caret = { line: 0, character: 0 };
@@ -400,7 +372,7 @@ export class FakeEditor {
400
372
  setMarkers(markers) {
401
373
  this.markers = markers;
402
374
  }
403
- /* What the client drew as hints last. */
375
+ /* What the host drew as hints last. */
404
376
  inlayHints = [];
405
377
  setInlayHints(hints) {
406
378
  this.inlayHints = hints;
@@ -410,12 +382,12 @@ export class FakeEditor {
410
382
  setGhostText(ghost) {
411
383
  this.ghost = ghost;
412
384
  }
413
- /* What the client classified last. */
385
+ /* What the host classified last. */
414
386
  semanticTokens = null;
415
387
  setSemanticTokens(tokens) {
416
388
  this.semanticTokens = tokens;
417
389
  }
418
- /* What the client drew as a link last. */
390
+ /* What the host drew as a link last. */
419
391
  link = null;
420
392
  setLink(range) {
421
393
  this.link = range;
@@ -426,7 +398,7 @@ export class FakeEditor {
426
398
  getCaret() {
427
399
  return this.caret;
428
400
  }
429
- /* The folds the client asked to keep. */
401
+ /* The folds the host asked to keep. */
430
402
  folds = { collapsed: [], custom: [] };
431
403
  getFolds() {
432
404
  return this.folds;
@@ -472,36 +444,24 @@ export class FakeEditor {
472
444
  this.lastReveal = reveal;
473
445
  this.caret = range.end;
474
446
  this.selection = range;
475
- for (const listener of [...this.carets]) {
476
- listener(range.end);
477
- }
447
+ emit(this.carets, range.end);
478
448
  }
479
449
  /* The caret a person's click or key moved. */
480
450
  moveCaret(position) {
481
451
  this.caret = position;
482
452
  this.selection = null;
483
- for (const listener of [...this.carets]) {
484
- listener(position);
485
- }
453
+ emit(this.carets, position);
486
454
  }
487
455
  onCaret(listener) {
488
- this.carets.add(listener);
489
- return () => {
490
- this.carets.delete(listener);
491
- };
456
+ return subscribe(this.carets, listener);
492
457
  }
493
458
  onHover(listener) {
494
- this.hovers.add(listener);
495
- return () => {
496
- this.hovers.delete(listener);
497
- };
459
+ return subscribe(this.hovers, listener);
498
460
  }
499
461
  /* The pointer resting on a position, or leaving the text. */
500
462
  hover(position) {
501
463
  const hover = position === null ? null : { position, rect: this.rectAt(position) };
502
- for (const listener of [...this.hovers]) {
503
- listener(hover);
504
- }
464
+ emit(this.hovers, hover);
505
465
  }
506
466
  rectAt(position) {
507
467
  const left = this.origin.left + position.character * this.charSize.width;
@@ -529,24 +489,15 @@ export class FakeEditor {
529
489
  this.selectionRanges = provider;
530
490
  }
531
491
  onKeyDown(handler) {
532
- this.keys.add(handler);
533
- return () => {
534
- this.keys.delete(handler);
535
- };
492
+ return subscribe(this.keys, handler);
536
493
  }
537
494
  clickHandlers = new Set();
538
495
  contextListeners = new Set();
539
496
  onClick(handler) {
540
- this.clickHandlers.add(handler);
541
- return () => {
542
- this.clickHandlers.delete(handler);
543
- };
497
+ return subscribe(this.clickHandlers, handler);
544
498
  }
545
499
  onContextMenu(listener) {
546
- this.contextListeners.add(listener);
547
- return () => {
548
- this.contextListeners.delete(listener);
549
- };
500
+ return subscribe(this.contextListeners, listener);
550
501
  }
551
502
  /* A press on a character as the editor sees it first; true when a handler took it, and the caret stays where it is otherwise only until the test moves it. */
552
503
  click(click) {
@@ -554,9 +505,7 @@ export class FakeEditor {
554
505
  }
555
506
  /* The context menu asked for at a position. */
556
507
  openContextMenu(menu) {
557
- for (const listener of [...this.contextListeners]) {
558
- listener(menu);
559
- }
508
+ emit(this.contextListeners, menu);
560
509
  }
561
510
  /* A key as the editor sees it first; true when a handler took it. */
562
511
  press(event) {
@@ -634,11 +583,9 @@ export class FakeEditor {
634
583
  }
635
584
  announceFind(state) {
636
585
  this.findState = state;
637
- for (const listener of [...this.finds]) {
638
- listener(state);
639
- }
586
+ emit(this.finds, state);
640
587
  }
641
- /* A test that drives an editor its client already disposed has found a leak. */
588
+ /* A test that drives an editor its host already disposed has found a leak. */
642
589
  assertLive() {
643
590
  if (this.disposed) {
644
591
  throw new Error('The editor is disposed');
package/dist/keymap.d.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import type { EditorCommand } from '@adecore/editor-core';
2
- import type { EditorViewCommand } from './types.ts';
3
2
  import { type Keymap } from './keymap-table.ts';
4
- import type { KeyChord } from './types.ts';
3
+ import type { EditorViewCommand, KeyChord } from './types.ts';
5
4
  export interface KeyLike {
6
5
  key: string;
7
6
  code?: string;
package/dist/keymap.js CHANGED
@@ -3,6 +3,9 @@ const MOVES = new Set(['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'PageU
3
3
  function command(name) {
4
4
  return { type: 'command', command: name };
5
5
  }
6
+ function view(name) {
7
+ return { type: 'view', command: name };
8
+ }
6
9
  /* What each key of the table does in the editor. A key of the table that is not here is a language command, which the host answers. */
7
10
  const ACTIONS = {
8
11
  undo: command('undo'),
@@ -42,21 +45,21 @@ const ACTIONS = {
42
45
  selectTextStart: { type: 'edge', end: false, extend: true },
43
46
  textEnd: { type: 'edge', end: true, extend: false },
44
47
  selectTextEnd: { type: 'edge', end: true, extend: true },
45
- collapse: { type: 'view', command: 'collapseRegion' },
46
- expand: { type: 'view', command: 'expandRegion' },
47
- collapseAll: { type: 'view', command: 'collapseAllRegions' },
48
- expandAll: { type: 'view', command: 'expandAllRegions' },
49
- collapseRecursively: { type: 'view', command: 'collapseRegionRecursively' },
50
- expandRecursively: { type: 'view', command: 'expandRegionRecursively' },
51
- foldSelection: { type: 'view', command: 'foldSelection' },
52
- collapseDocComments: { type: 'view', command: 'collapseDocComments' },
53
- expandDocComments: { type: 'view', command: 'expandDocComments' },
54
- expandAllToLevel1: { type: 'view', command: 'expandAllToLevel1' },
55
- expandAllToLevel2: { type: 'view', command: 'expandAllToLevel2' },
56
- expandAllToLevel3: { type: 'view', command: 'expandAllToLevel3' },
57
- expandAllToLevel4: { type: 'view', command: 'expandAllToLevel4' },
58
- expandAllToLevel5: { type: 'view', command: 'expandAllToLevel5' },
59
- toggleColumnMode: { type: 'view', command: 'toggleColumnMode' }
48
+ collapse: view('collapseRegion'),
49
+ expand: view('expandRegion'),
50
+ collapseAll: view('collapseAllRegions'),
51
+ expandAll: view('expandAllRegions'),
52
+ collapseRecursively: view('collapseRegionRecursively'),
53
+ expandRecursively: view('expandRegionRecursively'),
54
+ foldSelection: view('foldSelection'),
55
+ collapseDocComments: view('collapseDocComments'),
56
+ expandDocComments: view('expandDocComments'),
57
+ expandAllToLevel1: view('expandAllToLevel1'),
58
+ expandAllToLevel2: view('expandAllToLevel2'),
59
+ expandAllToLevel3: view('expandAllToLevel3'),
60
+ expandAllToLevel4: view('expandAllToLevel4'),
61
+ expandAllToLevel5: view('expandAllToLevel5'),
62
+ toggleColumnMode: view('toggleColumnMode')
60
63
  };
61
64
  const PUNCTUATION_CODES = {
62
65
  ',': 'Comma',
package/dist/layout.d.ts CHANGED
@@ -133,7 +133,7 @@ export declare class EditorLayout {
133
133
  configure(config: LayoutConfig): void;
134
134
  private rowCountOf;
135
135
  private rebuild;
136
- private tailOf;
136
+ private clampToLine;
137
137
  private reflow;
138
138
  setMeasuredHeight(key: string, height: number): boolean;
139
139
  syncRows(rows: readonly LayoutRow[]): boolean;
package/dist/layout.js CHANGED
@@ -8,6 +8,7 @@ const DEFAULT_BLOCK_HEIGHT = 40;
8
8
  const WRAP_INDENT_STEP = 2;
9
9
  const GEOMETRY_CACHE_SIZE = 400;
10
10
  const PLAIN = /^[\x20-\x7e\t]*$/;
11
+ const GRAPHEME_LIMIT = 20000;
11
12
  const SEGMENTER = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
12
13
  function lowerBound(values, value) {
13
14
  let low = 0;
@@ -23,10 +24,10 @@ function lowerBound(values, value) {
23
24
  }
24
25
  return low;
25
26
  }
26
- /* Where each character starts, with the length last. A line that long is cut by code unit, since splitting it by grapheme would cost more than it shows. */
27
+ /* Where each character starts, with the length last. A very long line is cut by code unit, since splitting it by grapheme would cost more than it shows. */
27
28
  function characterOffsets(text) {
28
29
  const offsets = [];
29
- if (text.length > 20000 || PLAIN.test(text)) {
30
+ if (text.length > GRAPHEME_LIMIT || PLAIN.test(text)) {
30
31
  for (let i = 0; i < text.length; i++) {
31
32
  offsets.push(i);
32
33
  }
@@ -277,8 +278,8 @@ export class EditorLayout {
277
278
  }
278
279
  const fold = folds.get(line);
279
280
  const last = Math.max(line, Math.min(count - 1, fold?.endLine ?? line));
280
- const tail = fold?.closer !== undefined && last > line ? this.tailOf(fold.closer, last) : null;
281
- const head = fold?.head !== undefined && last > line ? this.tailOf(fold.head, line) : null;
281
+ const tail = fold?.closer !== undefined && last > line ? this.clampToLine(fold.closer, last) : null;
282
+ const head = fold?.head !== undefined && last > line ? this.clampToLine(fold.head, line) : null;
282
283
  const index = this.rows.length;
283
284
  const bounds = this.wrapWidth === null ? null : this.document.getLine(line);
284
285
  const subRows = this.rowCountOf(line, bounds === null ? 0 : bounds.end - bounds.start);
@@ -305,9 +306,9 @@ export class EditorLayout {
305
306
  this.reflow();
306
307
  }
307
308
  /* An offset of a fold clamped to its line, since the fold may be a moment older than an edit. */
308
- tailOf(closer, line) {
309
+ clampToLine(offset, line) {
309
310
  const bounds = this.document.getLine(line);
310
- return Math.min(bounds.end, Math.max(bounds.start, closer));
311
+ return Math.min(bounds.end, Math.max(bounds.start, offset));
311
312
  }
312
313
  reflow() {
313
314
  let top = 0;
@@ -1,3 +1,3 @@
1
1
  export type Listener = () => void;
2
- export declare function subscribe(listeners: Set<Listener>, listener: Listener): () => void;
3
- export declare function emit(listeners: Set<Listener>): void;
2
+ export declare function subscribe<T>(listeners: Set<T>, listener: T): () => void;
3
+ export declare function emit<Args extends unknown[]>(listeners: Set<(...args: Args) => void>, ...args: Args): void;
package/dist/listeners.js CHANGED
@@ -5,8 +5,8 @@ export function subscribe(listeners, listener) {
5
5
  };
6
6
  }
7
7
  /* Over a copy, so a listener that unsubscribes itself does not skip the next one. */
8
- export function emit(listeners) {
8
+ export function emit(listeners, ...args) {
9
9
  for (const listener of [...listeners]) {
10
- listener();
10
+ listener(...args);
11
11
  }
12
12
  }
@@ -27,8 +27,8 @@ export class DoubleModifierGesture {
27
27
  }
28
28
  /* Another modifier held with the one that counts spoils the gesture. */
29
29
  spoiled(event) {
30
- const others = this.modifier === 'Alt' ? event.ctrlKey || event.metaKey || event.shiftKey : event.altKey || event.metaKey || event.shiftKey;
31
- return others;
30
+ const other = this.modifier === 'Alt' ? event.ctrlKey : event.altKey;
31
+ return other || event.metaKey || event.shiftKey;
32
32
  }
33
33
  keydown(event) {
34
34
  if (event.key === this.modifier) {
@@ -23,4 +23,5 @@ export declare class NativeInput {
23
23
  endComposition(data: string): void;
24
24
  normalizeNewlines(text: string): string;
25
25
  private composedText;
26
+ private replacedText;
26
27
  }
@@ -90,8 +90,7 @@ export class NativeInput {
90
90
  return;
91
91
  }
92
92
  if (this.model.getSelections().length > 1) {
93
- const suffix = context.value.length - context.end;
94
- this.host.replace(this.element.value.slice(context.start, Math.max(context.start, this.element.value.length - suffix)));
93
+ this.host.replace(this.replacedText(context));
95
94
  return;
96
95
  }
97
96
  const edit = {
@@ -140,7 +139,10 @@ export class NativeInput {
140
139
  return textareaText(text).replace(/\n/g, newline);
141
140
  }
142
141
  composedText() {
143
- const context = this.compositionContext;
142
+ return this.replacedText(this.compositionContext);
143
+ }
144
+ /* What the textarea now holds where the selection of `context` was, the text after it kept. */
145
+ replacedText(context) {
144
146
  const suffix = context.value.length - context.end;
145
147
  return this.element.value.slice(context.start, Math.max(context.start, this.element.value.length - suffix));
146
148
  }
package/dist/outline.js CHANGED
@@ -82,7 +82,7 @@ export function structuralEntries(ranges, document, slice, mate) {
82
82
  }
83
83
  return entries;
84
84
  }
85
- function toEditorLines(entry, document) {
85
+ function toBlock(entry, document) {
86
86
  const startLine = document.positionAt(entry.from).line;
87
87
  return {
88
88
  startLine,
@@ -143,7 +143,7 @@ export class Outline {
143
143
  blocks(document) {
144
144
  if (this.cache?.version !== this.version) {
145
145
  const sorted = (this.provided ?? this.structure)
146
- .map((entry) => toEditorLines(entry, document))
146
+ .map((entry) => toBlock(entry, document))
147
147
  .sort((left, right) => left.startLine - right.startLine || right.endLine - left.endLine);
148
148
  this.cache = { version: this.version, blocks: sorted.filter((block, index) => index === 0 || block.startLine !== sorted[index - 1].startLine) };
149
149
  }
package/dist/overview.js CHANGED
@@ -33,7 +33,6 @@ export function overviewTicks(spans, contentHeight, trackHeight) {
33
33
  }
34
34
  /* What is painted later is on top: the current find match, and the worse a problem is the later it goes. */
35
35
  const PAINT_ORDER = { warning: 1, error: 2, 'find-current': 3 };
36
- /* Draws the ticks. */
37
36
  export function paintOverview(container, ticks) {
38
37
  const document = container.ownerDocument;
39
38
  const order = (tick) => PAINT_ORDER[tick.kind] ?? 0;
package/dist/paint.js CHANGED
@@ -266,6 +266,14 @@ export class RowPainter {
266
266
  this.rows.clear();
267
267
  }
268
268
  }
269
+ function gutterButton(document, className, label) {
270
+ const button = document.createElement('button');
271
+ button.type = 'button';
272
+ button.className = className;
273
+ button.tabIndex = -1;
274
+ button.setAttribute('aria-label', label);
275
+ return button;
276
+ }
269
277
  /* The line numbers of the visible rows, and the fold control of the lines that have one. */
270
278
  export function paintGutter(container, layout, rows, paint) {
271
279
  const document = container.ownerDocument;
@@ -286,22 +294,14 @@ export function paintGutter(container, layout, rows, paint) {
286
294
  number.textContent = String(row.line + 1);
287
295
  item.append(number);
288
296
  if (paint.action?.line === row.line) {
289
- const button = document.createElement('button');
290
- button.type = 'button';
291
- button.className = 'se-gutter-action';
297
+ const button = gutterButton(document, 'se-gutter-action', paint.action.label);
292
298
  button.dataset.gutterAction = String(row.line);
293
- button.tabIndex = -1;
294
- button.setAttribute('aria-label', paint.action.label);
295
299
  item.append(button);
296
300
  }
297
301
  const marker = paint.markers.get(row.line);
298
302
  if (marker !== undefined) {
299
- const button = document.createElement('button');
300
- button.type = 'button';
301
- button.className = 'se-gutter-marker';
303
+ const button = gutterButton(document, 'se-gutter-marker', marker.label);
302
304
  button.dataset.gutterMarker = marker.id;
303
- button.tabIndex = -1;
304
- button.setAttribute('aria-label', marker.label);
305
305
  item.append(button);
306
306
  }
307
307
  const sign = paint.highlights.get(row.line)?.sign;
@@ -333,12 +333,8 @@ export function paintGutter(container, layout, rows, paint) {
333
333
  }
334
334
  const collapsed = paint.foldable.get(row.line);
335
335
  if (collapsed !== undefined) {
336
- const button = document.createElement('button');
337
- button.type = 'button';
338
- button.className = collapsed ? 'se-fold-toggle se-folded' : 'se-fold-toggle';
336
+ const button = gutterButton(document, collapsed ? 'se-fold-toggle se-folded' : 'se-fold-toggle', collapsed ? 'Expand' : 'Collapse');
339
337
  button.dataset.foldLine = String(row.line);
340
- button.tabIndex = -1;
341
- button.setAttribute('aria-label', collapsed ? 'Expand' : 'Collapse');
342
338
  item.append(button);
343
339
  }
344
340
  fragment.append(item);
package/dist/pointer.js CHANGED
@@ -4,6 +4,13 @@ const MULTI_CLICK_MS = 500;
4
4
  const MULTI_CLICK_DISTANCE = 4;
5
5
  /* How far the pointer goes, in pixels, before a press on a selection starts carrying it. */
6
6
  const CARRY_THRESHOLD = 5;
7
+ /* How far `value` lies outside `[low, high]`, negative below it and zero inside. */
8
+ function overshoot(value, low, high) {
9
+ if (value < low) {
10
+ return value - low;
11
+ }
12
+ return value > high ? value - high : 0;
13
+ }
7
14
  /* The selection a mouse makes: a click, a drag, shift to extend, alt to add a caret or, dragged, a column, a double click for a word and a triple for a line, which a drag then grows by words and lines. */
8
15
  export class PointerSelection {
9
16
  drag;
@@ -230,8 +237,8 @@ export class PointerSelection {
230
237
  return;
231
238
  }
232
239
  const rect = viewport.getBoundingClientRect();
233
- const dy = this.drag.y < rect.top ? this.drag.y - rect.top : this.drag.y > rect.bottom ? this.drag.y - rect.bottom : 0;
234
- const dx = this.drag.x < rect.left ? this.drag.x - rect.left : this.drag.x > rect.right ? this.drag.x - rect.right : 0;
240
+ const dy = overshoot(this.drag.y, rect.top, rect.bottom);
241
+ const dx = overshoot(this.drag.x, rect.left, rect.right);
235
242
  if (dy || dx) {
236
243
  viewport.scrollTop += Math.max(-SCROLL_STEP, Math.min(SCROLL_STEP, dy));
237
244
  viewport.scrollLeft += Math.max(-SCROLL_STEP, Math.min(SCROLL_STEP, dx));