@adecore/editor 0.41.1 → 0.43.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/dist/types.d.ts CHANGED
@@ -1,9 +1,15 @@
1
1
  import type { EditorSearchClientOptions } from './search-client.ts';
2
2
  import type { Keymap } from './keymap-table.ts';
3
- import type { EditorCommand, FoldRole } from '@adecore/editor-core';
3
+ import type { EditorCommand, FoldRole, WhitespaceCleanupOptions } from '@adecore/editor-core';
4
4
  export type { EditorCommand, FoldRole };
5
+ export type EditorWhitespaceCleanup = WhitespaceCleanupOptions;
5
6
  export type EditorFoldOutline = 'off' | 'hover' | 'always';
6
- export type EditorViewCommand = 'collapseRegion' | 'expandRegion' | 'collapseAllRegions' | 'expandAllRegions' | 'collapseRegionRecursively' | 'expandRegionRecursively' | 'foldSelection' | 'collapseDocComments' | 'expandDocComments' | 'expandAllToLevel1' | 'expandAllToLevel2' | 'expandAllToLevel3' | 'expandAllToLevel4' | 'expandAllToLevel5' | 'toggleColumnMode';
7
+ export type EditorViewCommand = 'collapseRegion' | 'expandRegion' | 'collapseAllRegions' | 'expandAllRegions' | 'collapseRegionRecursively' | 'expandRegionRecursively' | 'foldSelection' | 'collapseDocComments' | 'expandDocComments' | 'expandAllToLevel1' | 'expandAllToLevel2' | 'expandAllToLevel3' | 'expandAllToLevel4' | 'expandAllToLevel5' | 'toggleColumnMode' | 'increaseFontSize' | 'decreaseFontSize' | 'resetFontSize' | 'methodUp' | 'methodDown' | 'nextChange' | 'previousChange' | 'pasteAsPlainText';
8
+ export interface EditorZoom {
9
+ readonly min?: number;
10
+ readonly max?: number;
11
+ readonly wheel?: boolean;
12
+ }
7
13
  export type EditorRunCommand = EditorCommand | EditorViewCommand;
8
14
  export interface EditorFoldRange {
9
15
  readonly startLine: number;
@@ -38,6 +44,9 @@ export interface EditorOptions {
38
44
  readonly line?: number;
39
45
  readonly column?: number;
40
46
  readonly scrollTop?: number;
47
+ readonly todoWords?: readonly string[];
48
+ readonly fontSize?: number;
49
+ readonly zoom?: EditorZoom;
41
50
  }
42
51
  export interface EditorMessages {
43
52
  readonly noMoreOccurrences: string;
@@ -130,6 +139,15 @@ export interface EditorContentChange {
130
139
  readonly range: EditorRange;
131
140
  readonly text: string;
132
141
  }
142
+ export interface EditorCopy {
143
+ readonly text: string;
144
+ readonly wholeLines: boolean;
145
+ readonly cut: boolean;
146
+ }
147
+ export interface EditorPasteOptions {
148
+ readonly wholeLines?: boolean;
149
+ readonly reindent?: boolean;
150
+ }
133
151
  export interface EditorApplyEditsOptions {
134
152
  readonly mapSelections?: (range: EditorRange) => EditorRange;
135
153
  readonly mergeUndo?: boolean;
@@ -231,6 +249,7 @@ export interface EditorInlayHint {
231
249
  readonly parts?: readonly string[];
232
250
  readonly paddingLeft?: boolean;
233
251
  readonly paddingRight?: boolean;
252
+ readonly swatch?: string;
234
253
  }
235
254
  export interface EditorInlayTarget {
236
255
  readonly id: string;
@@ -324,8 +343,15 @@ export interface Editor {
324
343
  setWhitespace(whitespace: boolean): void;
325
344
  setRightMargin(column: number | null): void;
326
345
  setFoldOutline(outline: EditorFoldOutline): void;
346
+ setTodoWords?(words: readonly string[]): void;
327
347
  setLabel(label: string): void;
328
348
  setFoldHints(hints: EditorFoldHints | null): void;
349
+ setDeclarations?(positions: readonly EditorPosition[] | null): void;
350
+ onNavigate?(listener: (from: EditorPosition) => void): () => void;
351
+ hasFocus?(): boolean;
352
+ setWordSources?(source: (() => readonly string[]) | null): void;
353
+ onCopy?(listener: (copy: EditorCopy) => void): () => void;
354
+ paste?(text: string, options?: EditorPasteOptions): boolean;
329
355
  setChangeMarks(marks: readonly EditorChangeMark[], layer?: string): void;
330
356
  setBlocks(blocks: readonly EditorBlock[] | null): void;
331
357
  onScope(listener: (scope: readonly EditorBlock[]) => void): () => void;
@@ -341,7 +367,7 @@ export interface Editor {
341
367
  setHighlights(highlights: readonly EditorHighlight[]): void;
342
368
  setLink(range: EditorRange | null): void;
343
369
  setSemanticTokens(tokens: readonly EditorSemanticToken[] | null): void;
344
- setInlayHints(hints: readonly EditorInlayHint[]): void;
370
+ setInlayHints(hints: readonly EditorInlayHint[], owner?: string): void;
345
371
  onInlayHover?(listener: (target: EditorInlayTarget | null) => void): () => void;
346
372
  onInlayClick?(handler: EditorInlayClickHandler): () => void;
347
373
  setInlayLink?(target: {
@@ -385,8 +411,13 @@ export interface Editor {
385
411
  setSelectionRanges(provider: EditorSelectionRanges | null): void;
386
412
  trackRange(range: EditorRange): EditorTrackedRange;
387
413
  applyEdits(edits: readonly EditorContentChange[], options?: EditorApplyEditsOptions): boolean;
414
+ cleanUpWhitespace?(options: EditorWhitespaceCleanup): boolean;
415
+ markSaved?(): void;
388
416
  setTheme(theme: EditorTheme): void;
389
417
  refreshFont(): void;
418
+ setFontSize?(size: number | null): void;
419
+ getFontSize?(): number;
420
+ onFontSizeChange?(listener: (size: number | null) => void): () => void;
390
421
  setReadOnly(readOnly: boolean, reason?: string): void;
391
422
  isReadOnly?(): boolean;
392
423
  runCommand(command: EditorRunCommand): boolean;
package/dist/view.d.ts CHANGED
@@ -22,6 +22,7 @@ export interface ViewSettings {
22
22
  foldOutline: 'off' | 'hover' | 'always';
23
23
  folding?: boolean;
24
24
  stickyScroll?: boolean;
25
+ todoWords?: readonly string[];
25
26
  }
26
27
  export type ViewFold = Omit<FoldingRange, 'kind'> & {
27
28
  kind: FoldingRange['kind'] | 'custom';
@@ -59,6 +60,7 @@ export declare class EditorView {
59
60
  readonly layout: EditorLayout;
60
61
  readonly find: FindController;
61
62
  readonly tokens: TokenCache;
63
+ private readonly todos;
62
64
  private readonly content;
63
65
  private readonly gutter;
64
66
  private readonly gutterLines;
@@ -125,6 +127,7 @@ export declare class EditorView {
125
127
  private caretTimer;
126
128
  private caretKey;
127
129
  private renderedScroll;
130
+ private fontSize;
128
131
  private font;
129
132
  private layoutVersion;
130
133
  private revision;
@@ -140,6 +143,7 @@ export declare class EditorView {
140
143
  private lenses;
141
144
  private readonly replacements;
142
145
  private occurrences;
146
+ private hippieSource;
143
147
  private replacePreview;
144
148
  private dropCaret;
145
149
  private activeGuideCache;
@@ -154,6 +158,7 @@ export declare class EditorView {
154
158
  private customFolds;
155
159
  private collapsed;
156
160
  private foldHints;
161
+ private declarations;
157
162
  private defaultRoles;
158
163
  private settled;
159
164
  private foldTimer;
@@ -171,6 +176,8 @@ export declare class EditorView {
171
176
  applySettings(): void;
172
177
  repaint(): void;
173
178
  refreshFont(): void;
179
+ setFontSize(size: number | null): void;
180
+ getFontSize(): number;
174
181
  setTokenizer(tokenizer: LineTokenizer | null): void;
175
182
  grammarChanged(): void;
176
183
  setLineTokens(tokens: ((line: number) => readonly LineToken[] | null | undefined) | null): void;
@@ -183,6 +190,10 @@ export declare class EditorView {
183
190
  setLenses(lenses: readonly BlockWidget[]): void;
184
191
  private allBlocks;
185
192
  setOccurrences(occurrences: readonly Occurrence[]): void;
193
+ setHippieSource(range: {
194
+ from: number;
195
+ to: number;
196
+ } | null): void;
186
197
  setLink(link: {
187
198
  from: number;
188
199
  to: number;
@@ -190,6 +201,7 @@ export declare class EditorView {
190
201
  setSemanticTokens(tokens: readonly ViewSemanticToken[] | null): void;
191
202
  setScopeColors(colors: ScopeColors | null): void;
192
203
  private styledTokensOf;
204
+ private semanticTokensOf;
193
205
  private semanticSpans;
194
206
  setMarkers(markers: readonly ViewMarker[]): void;
195
207
  setComposition(text: string | undefined): void;
@@ -233,6 +245,7 @@ export declare class EditorView {
233
245
  private foldStates;
234
246
  private scheduleFolds;
235
247
  refreshFolds(): void;
248
+ structureRanges(): FoldingRange[];
236
249
  private withCustomFolds;
237
250
  private closerOf;
238
251
  private foldAt;
@@ -268,6 +281,10 @@ export declare class EditorView {
268
281
  endLine: number;
269
282
  }[];
270
283
  } | null, defaults?: readonly FoldRole[]): void;
284
+ setDeclarations(offsets: readonly number[] | null): void;
285
+ private methodStops;
286
+ goToChange(direction: -1 | 1): boolean;
287
+ goToMethod(direction: -1 | 1): boolean;
271
288
  setFoldHints(hints: FoldHints | null): void;
272
289
  private applyFoldDefaults;
273
290
  foldRole(role: FoldRole, collapse: boolean): void;
package/dist/view.js CHANGED
@@ -5,6 +5,7 @@ import { createSearchClient } from './search-client.js';
5
5
  import { FindController } from './find.js';
6
6
  import { EditorLayout, RIGHT_PADDING, subRowBand } from './layout.js';
7
7
  import { createMetrics, readEditorFont } from './metrics.js';
8
+ import { zoomedFont } from './zoom.js';
8
9
  import { AttributionRuns, colorValue, tintValue } from './attribution.js';
9
10
  import { renderCodeBlock } from './code-block.js';
10
11
  import { emit, subscribe } from './listeners.js';
@@ -16,6 +17,7 @@ import { Outline, scopeChain, stickyCover, stickyPlacements, structuralEntries }
16
17
  import { overviewTicks, paintOverview } from './overview.js';
17
18
  import { paintCarets, paintGutter, paintOver, paintOverlays, paintRemoteCarets, paintSigns, paintSticky, RowPainter } from './paint.js';
18
19
  import { overlayTokens } from './semantic.js';
20
+ import { TodoMarks } from './todos.js';
19
21
  import { TokenCache } from './tokens.js';
20
22
  function sameRect(left, right) {
21
23
  return left.left === right.left && left.top === right.top && left.right === right.right && left.bottom === right.bottom;
@@ -79,6 +81,7 @@ export class EditorView {
79
81
  layout;
80
82
  find;
81
83
  tokens;
84
+ todos;
82
85
  content;
83
86
  gutter;
84
87
  gutterLines;
@@ -147,6 +150,8 @@ export class EditorView {
147
150
  caretTimer;
148
151
  caretKey = '';
149
152
  renderedScroll = { top: -1, left: -1 };
153
+ /* A size the code is zoomed to, over the page's; null draws it at the page's. */
154
+ fontSize = null;
150
155
  font;
151
156
  layoutVersion = 0;
152
157
  revision;
@@ -163,6 +168,7 @@ export class EditorView {
163
168
  lenses = [];
164
169
  replacements = new Map();
165
170
  occurrences = [];
171
+ hippieSource = null;
166
172
  replacePreview = null;
167
173
  dropCaret = null;
168
174
  activeGuideCache;
@@ -181,6 +187,7 @@ export class EditorView {
181
187
  collapsed = new Set();
182
188
  /* What a language server said about the folds, in offsets that follow the text through edits. */
183
189
  foldHints = null;
190
+ declarations = null;
184
191
  /* The roles that fold by themselves while the file is as it was opened; null once a person edited or the file opened with folds of its own. */
185
192
  defaultRoles = null;
186
193
  /* The folds somebody already decided on, a person or the defaults, so a later answer of a server leaves them as they are. */
@@ -281,6 +288,7 @@ export class EditorView {
281
288
  }
282
289
  });
283
290
  this.tokens = new TokenCache({ getLineCount: () => model.getLineCount(), getLineText: (line) => model.getLine(line).text });
291
+ this.todos = new TodoMarks(model, () => this.requestRender());
284
292
  const ResizeObserverClass = document.defaultView?.ResizeObserver;
285
293
  this.blockObserver = ResizeObserverClass
286
294
  ? new ResizeObserverClass((entries) => {
@@ -311,6 +319,7 @@ export class EditorView {
311
319
  this.root.dataset.readonly = String(this.settings.readOnly);
312
320
  this.root.dataset.wrap = String(this.settings.wrap);
313
321
  this.root.dataset.foldOutline = this.settings.foldOutline;
322
+ this.todos.configure(this.settings.language, this.settings.todoWords);
314
323
  this.layoutVersion++;
315
324
  this.layout.configure({ metrics: createMetrics(this.font, this.settings.tabSize, this.document) });
316
325
  this.render();
@@ -320,8 +329,26 @@ export class EditorView {
320
329
  this.render();
321
330
  }
322
331
  refreshFont() {
323
- this.font = readEditorFont(this.root);
324
- this.applySettings();
332
+ const style = this.root.style;
333
+ style.removeProperty('--code-font-size');
334
+ style.removeProperty('--code-line-height');
335
+ const base = readEditorFont(this.root);
336
+ this.font = this.fontSize === null ? base : zoomedFont(base, this.fontSize);
337
+ // On the element itself, so everything the editor draws at the code's size follows, and the page's size stays the base.
338
+ if (this.fontSize !== null) {
339
+ style.setProperty('--code-font-size', `${this.font.size}px`);
340
+ style.setProperty('--code-line-height', `${this.font.lineHeight}px`);
341
+ }
342
+ this.anchorScroll(() => this.applySettings());
343
+ }
344
+ /* Draws the code at a size of its own in whole pixels; null goes back to the page's. */
345
+ setFontSize(size) {
346
+ this.fontSize = size;
347
+ this.refreshFont();
348
+ }
349
+ /* The size the code is drawn at, in pixels. */
350
+ getFontSize() {
351
+ return this.font.size;
325
352
  }
326
353
  setTokenizer(tokenizer) {
327
354
  this.tokens.setTokenizer(tokenizer);
@@ -402,6 +429,14 @@ export class EditorView {
402
429
  this.occurrences = [...occurrences];
403
430
  this.render();
404
431
  }
432
+ /* The word Hippie completion took from this document, shown until the next key or edit; null takes the mark away. */
433
+ setHippieSource(range) {
434
+ if (range === null && this.hippieSource === null) {
435
+ return;
436
+ }
437
+ this.hippieSource = range;
438
+ this.render();
439
+ }
405
440
  /* A name underlined as a link a press follows, with the pointer to match; null takes it away. */
406
441
  setLink(link) {
407
442
  this.link = link;
@@ -421,8 +456,12 @@ export class EditorView {
421
456
  this.semanticLines.clear();
422
457
  this.render();
423
458
  }
424
- /* The grammar's colors of a line with the servers' laid over them, the same object for as long as neither changed. */
459
+ /* The colors of a line with its TODOs drawn over them, the same object for as long as nothing under them changed. */
425
460
  styledTokensOf(line) {
461
+ return this.todos.styled(line, this.semanticTokensOf(line));
462
+ }
463
+ /* The grammar's colors of a line with the servers' laid over them, the same object for as long as neither changed. */
464
+ semanticTokensOf(line) {
426
465
  const base = this.lineTokenProvider === null ? this.tokens.tokensOf(line) : (this.lineTokenProvider(line) ?? null);
427
466
  if (base === null || this.semantic.length === 0 || this.scopeColors === null) {
428
467
  return base;
@@ -592,6 +631,7 @@ export class EditorView {
592
631
  this.markerVersion++;
593
632
  const anchor = this.topAnchor;
594
633
  this.occurrences = [];
634
+ this.hippieSource = null;
595
635
  this.link = null;
596
636
  this.content.classList.remove('se-content-link');
597
637
  this.outline.edited(batches);
@@ -615,6 +655,7 @@ export class EditorView {
615
655
  this.find.refresh();
616
656
  this.announceFind();
617
657
  this.scheduleFolds();
658
+ this.todos.schedule();
618
659
  this.colorAhead(true);
619
660
  }
620
661
  if (this.rowEndCaret !== null && this.model.getPrimary().head !== this.rowEndCaret) {
@@ -638,6 +679,9 @@ export class EditorView {
638
679
  if (this.foldHints !== null) {
639
680
  this.foldHints = mapFoldHints(this.foldHints, map);
640
681
  }
682
+ if (this.declarations !== null) {
683
+ this.declarations = this.declarations.map((offset) => map(offset));
684
+ }
641
685
  this.customFolds = this.customFolds.map((fold) => mapRange(fold, map));
642
686
  this.find.mapBounds(changes);
643
687
  for (const [layer, marks] of this.changeLayers) {
@@ -771,20 +815,25 @@ export class EditorView {
771
815
  if (this.disposed || this.settings.folding === false) {
772
816
  return;
773
817
  }
774
- const ranges = this.model.getLength() > FOLD_LIMIT
775
- ? []
776
- : this.model.getFoldingRanges({
777
- indentation: /^(python|py|yaml|yml)$/.test(this.settings.language ?? ''),
778
- tabSize: this.settings.tabSize,
779
- ...(this.settings.language === undefined ? {} : { language: this.settings.language }),
780
- ...(this.foldHints === null ? {} : { hints: this.foldHints })
781
- });
818
+ const ranges = this.structureRanges();
782
819
  this.foldRanges = this.withCustomFolds(ranges);
783
820
  this.outline.setStructure(structuralEntries(ranges, this.model, (from, to) => this.model.slice(from, to), (offset) => this.model.matchBracket(offset, this.settings.language)?.mate));
784
821
  const anchors = new Set(this.foldRanges.map((range) => range.from));
785
822
  this.collapsed = new Set([...this.collapsed].filter((anchor) => anchors.has(anchor)));
786
823
  this.refoldLayout();
787
824
  }
825
+ /* The folding ranges the document has now, a server's included, whether or not the folds are drawn. */
826
+ structureRanges() {
827
+ if (this.model.getLength() > FOLD_LIMIT) {
828
+ return [];
829
+ }
830
+ return this.model.getFoldingRanges({
831
+ indentation: /^(python|py|yaml|yml)$/.test(this.settings.language ?? ''),
832
+ tabSize: this.settings.tabSize,
833
+ ...(this.settings.language === undefined ? {} : { language: this.settings.language }),
834
+ ...(this.foldHints === null ? {} : { hints: this.foldHints })
835
+ });
836
+ }
788
837
  /* The folds a person made of a selection, which the document's own structure does not know, among the ones it does. */
789
838
  withCustomFolds(ranges) {
790
839
  const custom = this.customFolds.flatMap((fold) => {
@@ -993,6 +1042,49 @@ export class EditorView {
993
1042
  this.applyFoldDefaults();
994
1043
  this.refoldLayout();
995
1044
  }
1045
+ /* The places Method Up and Down stop at; they follow their text through edits until set again, and null goes back to the editor's own reading. */
1046
+ setDeclarations(offsets) {
1047
+ this.declarations = offsets === null ? null : [...new Set(offsets)].sort((left, right) => left - right);
1048
+ }
1049
+ methodStops() {
1050
+ if (this.declarations !== null) {
1051
+ return this.declarations;
1052
+ }
1053
+ const sections = this.foldRanges.filter((range) => range.kind === 'section').map((range) => this.model.getLine(range.startLine).start);
1054
+ return [...new Set([...this.outline.declarationStops(this.model), ...sections])].sort((left, right) => left - right);
1055
+ }
1056
+ /* Moves the one caret to the first line of the next or previous change mark, of any layer. False when there is none. */
1057
+ goToChange(direction) {
1058
+ const line = this.model.positionAt(this.model.getPrimary().head).line;
1059
+ const { runs } = this.resolvedChanges();
1060
+ // A run the caret is in is where it already is; a deleted run is the line its mark sits on.
1061
+ const target = direction === 1 ? runs.find((run) => run.start > line) : runs.findLast((run) => (run.kind === 'deleted' ? run.start : run.end) < line);
1062
+ if (target === undefined) {
1063
+ return false;
1064
+ }
1065
+ const offset = this.model.getLine(Math.min(target.start, this.model.getLineCount() - 1)).start;
1066
+ this.model.setSelections([{ anchor: offset, head: offset }]);
1067
+ this.revealOffset(offset, 'center');
1068
+ return true;
1069
+ }
1070
+ /* Moves the one caret to the previous or next declaration on another line. False when there is none. */
1071
+ goToMethod(direction) {
1072
+ // The structure is read after a pause in typing, so a press right after an edit reads it now.
1073
+ if (this.declarations === null) {
1074
+ this.refreshFolds();
1075
+ }
1076
+ const line = this.model.positionAt(this.model.getPrimary().head).line;
1077
+ const stops = this.methodStops();
1078
+ const target = direction === 1
1079
+ ? stops.find((offset) => this.model.positionAt(offset).line > line)
1080
+ : stops.findLast((offset) => this.model.positionAt(offset).line < line);
1081
+ if (target === undefined) {
1082
+ return false;
1083
+ }
1084
+ this.model.setSelections([{ anchor: target, head: target }]);
1085
+ this.revealOffset(target, direction === 1 ? 'centerDown' : 'centerUp');
1086
+ return true;
1087
+ }
996
1088
  /* Hands over what a language server knows about the folds; null forgets it. The defaults fold what the answer names, once. */
997
1089
  setFoldHints(hints) {
998
1090
  this.foldHints = hints;
@@ -1836,6 +1928,9 @@ export class EditorView {
1836
1928
  });
1837
1929
  }
1838
1930
  }
1931
+ if (this.hippieSource !== null) {
1932
+ marks.push({ className: 'se-hippie-source', rects: this.paintRectangles(this.hippieSource.from, this.hippieSource.to, rows) });
1933
+ }
1839
1934
  const marginX = this.settings.rightMargin === null ? null : this.settings.rightMargin * this.layout.metrics.charWidth;
1840
1935
  // A margin past the content would stretch the scroll extent to itself, so it shows only where the text or the view reaches it.
1841
1936
  if (marginX !== null && marginX < Math.max(this.layout.width, this.viewportWidth)) {
@@ -2228,7 +2323,7 @@ export class EditorView {
2228
2323
  const matches = this.find.matches;
2229
2324
  const current = this.find.current;
2230
2325
  const alike = this.selectionOccurrences();
2231
- const key = `${this.revision}|${this.changeVersion}|${this.markerVersion}|${this.layout.height}|${trackHeight}|${matches.length}|${current}|${matches[0]?.from}|${matches.at(-1)?.from}|${this.selectionOccurrenceCache.key}`;
2326
+ const key = `${this.revision}|${this.changeVersion}|${this.markerVersion}|${this.todos.version}|${this.layout.height}|${trackHeight}|${matches.length}|${current}|${matches[0]?.from}|${matches.at(-1)?.from}|${this.selectionOccurrenceCache.key}`;
2232
2327
  if (key === this.overviewKey) {
2233
2328
  return;
2234
2329
  }
@@ -2255,6 +2350,11 @@ export class EditorView {
2255
2350
  });
2256
2351
  }
2257
2352
  }
2353
+ for (const todo of this.todos.marks) {
2354
+ if (todo.line < this.model.getLineCount()) {
2355
+ spans.push({ kind: 'todo', top: rowTop(todo.line), bottom: rowBottom(todo.line), offset: todo.from, title: todo.text });
2356
+ }
2357
+ }
2258
2358
  for (const range of alike) {
2259
2359
  const line = this.model.positionAt(range.from).line;
2260
2360
  spans.push({ kind: 'occurrence', top: rowTop(line), bottom: rowBottom(line) });
@@ -2427,6 +2527,7 @@ export class EditorView {
2427
2527
  this.blockObserver?.disconnect();
2428
2528
  clearTimeout(this.bracketTimer);
2429
2529
  clearTimeout(this.foldTimer);
2530
+ this.todos.dispose();
2430
2531
  clearTimeout(this.colorTimer);
2431
2532
  clearTimeout(this.noticeTimer);
2432
2533
  clearTimeout(this.liveTimer);
package/dist/zoom.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ import type { EditorFont } from './metrics.ts';
2
+ import type { EditorZoom } from './types.ts';
3
+ export declare const MIN_FONT_SIZE = 12;
4
+ export declare const MAX_FONT_SIZE = 90;
5
+ export interface FontSizeRange {
6
+ readonly min: number;
7
+ readonly max: number;
8
+ }
9
+ export declare function fontSizeRange(zoom: EditorZoom | undefined): FontSizeRange;
10
+ export declare function clampFontSize(size: number, range: FontSizeRange): number;
11
+ export declare function zoomedFont(base: EditorFont, size: number): EditorFont;
12
+ export declare class WheelZoom {
13
+ private pending;
14
+ step(event: {
15
+ deltaY: number;
16
+ deltaMode: number;
17
+ }): number;
18
+ }
package/dist/zoom.js ADDED
@@ -0,0 +1,41 @@
1
+ /* Nothing a person reads is smaller. */
2
+ export const MIN_FONT_SIZE = 12;
3
+ export const MAX_FONT_SIZE = 90;
4
+ /* A trackpad or a pinch sends the wheel in small steps; this many pixels of them are one step of the size. */
5
+ const PIXELS_PER_STEP = 20;
6
+ export function fontSizeRange(zoom) {
7
+ const min = Math.max(MIN_FONT_SIZE, Math.round(zoom?.min ?? MIN_FONT_SIZE));
8
+ return { min, max: Math.max(min, Math.round(zoom?.max ?? MAX_FONT_SIZE)) };
9
+ }
10
+ /* In whole pixels inside the range. */
11
+ export function clampFontSize(size, range) {
12
+ return Math.min(range.max, Math.max(range.min, Math.round(size)));
13
+ }
14
+ /* The face at another size, with the line as much taller as the page's line is to its text, in whole pixels. */
15
+ export function zoomedFont(base, size) {
16
+ return { family: base.family, size, lineHeight: Math.round((size * base.lineHeight) / base.size) };
17
+ }
18
+ /* Turns the wheel with the zoom modifier, or a pinch, into steps of one pixel: up and apart make the text larger. */
19
+ export class WheelZoom {
20
+ pending = 0;
21
+ /* -1, 0 or 1. A mouse notch is one step however far the platform says it went. */
22
+ step(event) {
23
+ if (event.deltaY === 0) {
24
+ return 0;
25
+ }
26
+ if (event.deltaMode !== 0 || Math.abs(event.deltaY) >= PIXELS_PER_STEP) {
27
+ this.pending = 0;
28
+ return event.deltaY < 0 ? 1 : -1;
29
+ }
30
+ if (Math.sign(this.pending) !== 0 && Math.sign(this.pending) !== Math.sign(event.deltaY)) {
31
+ this.pending = 0;
32
+ }
33
+ this.pending += event.deltaY;
34
+ if (Math.abs(this.pending) < PIXELS_PER_STEP) {
35
+ return 0;
36
+ }
37
+ const direction = this.pending < 0 ? 1 : -1;
38
+ this.pending = 0;
39
+ return direction;
40
+ }
41
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adecore/editor",
3
- "version": "0.41.1",
3
+ "version": "0.43.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -54,7 +54,7 @@
54
54
  "test": "bun test --conditions=source"
55
55
  },
56
56
  "dependencies": {
57
- "@adecore/editor-core": "0.41.1",
57
+ "@adecore/editor-core": "0.43.0",
58
58
  "linkedom": "^0.18.14",
59
59
  "shiki": "^4.5.0"
60
60
  },