@jbrowse/core 5.0.0-beta.10 → 5.0.0-beta.11

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 (67) hide show
  1. package/README.md +113 -5
  2. package/esm/BaseFeatureWidget/BaseFeatureDetail/ArrayValue.js +0 -2
  3. package/esm/BaseFeatureWidget/BaseFeatureDetail/BasicValue.js +22 -6
  4. package/esm/BaseFeatureWidget/BaseFeatureDetail/jexlFilterActions.js +3 -4
  5. package/esm/BaseFeatureWidget/SequenceFeatureDetails/useSequenceData.js +8 -5
  6. package/esm/CorePlugin.js +32 -1
  7. package/esm/ReExports/coreModules.generated.js +248 -246
  8. package/esm/ReExports/coreWorkerModules.generated.js +246 -244
  9. package/esm/ReExports/list.js +3 -2
  10. package/esm/configuration/index.d.ts +2 -0
  11. package/esm/configuration/index.js +1 -0
  12. package/esm/configuration/plot.d.ts +61 -0
  13. package/esm/configuration/plot.js +142 -0
  14. package/esm/pluggableElementTypes/DisplayType.d.ts +7 -0
  15. package/esm/pluggableElementTypes/DisplayType.js +6 -0
  16. package/esm/pluggableElementTypes/WidgetType.d.ts +6 -0
  17. package/esm/pluggableElementTypes/WidgetType.js +6 -0
  18. package/esm/pluggableElementTypes/index.js +1 -8
  19. package/esm/pluggableElementTypes/models/BaseDisplayModel.d.ts +57 -1
  20. package/esm/pluggableElementTypes/models/BaseDisplayModel.js +84 -2
  21. package/esm/pluggableElementTypes/models/BaseViewModel.d.ts +2 -2
  22. package/esm/pluggableElementTypes/models/BaseViewModel.js +1 -1
  23. package/esm/ui/JexlFilterDialog.d.ts +1 -1
  24. package/esm/ui/JexlFilterDialog.js +2 -3
  25. package/esm/util/TimeTraveller.d.ts +3 -0
  26. package/esm/util/TimeTraveller.js +34 -2
  27. package/esm/util/baseDisplayConfig.d.ts +8 -0
  28. package/esm/util/baseDisplayConfig.js +27 -0
  29. package/esm/util/cachedSetup.d.ts +16 -6
  30. package/esm/util/cachedSetup.js +108 -38
  31. package/esm/util/cellMatches.d.ts +23 -0
  32. package/esm/util/cellMatches.js +264 -0
  33. package/esm/util/cellsStep.d.ts +29 -0
  34. package/esm/util/cellsStep.js +437 -0
  35. package/esm/util/copyToClipboard.js +8 -1
  36. package/esm/util/createAbortRotation.js +3 -3
  37. package/esm/util/featureTransforms.d.ts +7 -6
  38. package/esm/util/featureTransforms.js +131 -479
  39. package/esm/util/fetchPhases.d.ts +1 -1
  40. package/esm/util/formatBytes.js +1 -1
  41. package/esm/util/formatGuessers.d.ts +25 -14
  42. package/esm/util/formatGuessers.js +42 -27
  43. package/esm/util/formatRelativeTime.js +1 -1
  44. package/esm/util/getLocationUri.d.ts +2 -1
  45. package/esm/util/getLocationUri.js +8 -2
  46. package/esm/util/groupKeys.d.ts +4 -5
  47. package/esm/util/groupKeys.js +7 -6
  48. package/esm/util/intervals.js +9 -4
  49. package/esm/util/io/index.js +3 -1
  50. package/esm/util/jexlFilters.d.ts +31 -49
  51. package/esm/util/jexlFilters.js +29 -39
  52. package/esm/util/locString.js +5 -2
  53. package/esm/util/markEncoding.js +48 -8
  54. package/esm/util/markEncodingTypes.d.ts +8 -1
  55. package/esm/util/springAnimate.js +1 -1
  56. package/esm/util/stepTables.d.ts +14 -0
  57. package/esm/util/stepTables.js +38 -4
  58. package/esm/util/tabix.d.ts +6 -2
  59. package/esm/util/tabix.js +2 -2
  60. package/esm/util/tracks.d.ts +3 -30
  61. package/esm/util/tracks.js +4 -39
  62. package/esm/util/unknownSnapshotKeys.d.ts +3 -1
  63. package/esm/util/unknownSnapshotKeys.js +5 -11
  64. package/esm/util/usePanZoom.js +10 -3
  65. package/esm/util/useRafCommit.js +1 -0
  66. package/esm/util/withFeatureDetails.js +4 -0
  67. package/package.json +9 -8
@@ -0,0 +1,437 @@
1
+ // The `cells` step: each row's runs against the reference it was fanned out
2
+ // of, and the texts its walk reads, which the fused identity kernel in
3
+ // cellMatches.ts walks too. Imported by relative path only.
4
+ import { DerivedTable, numberReaderOf, readerOf, valueAt, } from './featureTable.js';
5
+ import { FannedTable, boundsThrough } from './stepTables.js';
6
+ export const DEFAULT_CELLS_FIELD = 'seq';
7
+ export const DASH = 45;
8
+ export const SPACE = 32;
9
+ const LOWER_BIT = 0x20;
10
+ const CELL_STATES = ['match', 'mismatch', 'gap', 'insertion'];
11
+ export const MATCH = 0;
12
+ export const MISMATCH = 1;
13
+ const GAP = 2;
14
+ const INSERTION = 3;
15
+ export const NO_STATE = 255;
16
+ export function isGapByte(b) {
17
+ return b === DASH || b === SPACE;
18
+ }
19
+ // The columns carrying the row's own sequence: a gap run reaching either end
20
+ // of the row measures where the block was cut, not the alignment, so it is no
21
+ // cell either.
22
+ export function firstDrawn(bytes, at, len) {
23
+ let first = 0;
24
+ while (first < len && isGapByte(bytes[at + first])) {
25
+ first++;
26
+ }
27
+ return first;
28
+ }
29
+ export function lastDrawn(bytes, at, len, first) {
30
+ let last = len - 1;
31
+ while (last > first && isGapByte(bytes[at + last])) {
32
+ last--;
33
+ }
34
+ return last;
35
+ }
36
+ // A reference base's cell: no state outside the drawn columns, else the row's
37
+ // gap, or its base matched or not regardless of case.
38
+ export function cellState(refByte, rowByte, drawn) {
39
+ return !drawn
40
+ ? NO_STATE
41
+ : isGapByte(rowByte)
42
+ ? GAP
43
+ : (refByte | LOWER_BIT) === (rowByte | LOWER_BIT)
44
+ ? MATCH
45
+ : MISMATCH;
46
+ }
47
+ // The row a cell step reads against: past any table that only reordered or
48
+ // annotated the rows, the one they were fanned out of.
49
+ function containerOf(table) {
50
+ let at = table;
51
+ let toBase;
52
+ while (at instanceof DerivedTable && !at.madeFrom) {
53
+ toBase = at.parentRow ? compose(toBase, at.parentRow) : toBase;
54
+ at = at.parent;
55
+ }
56
+ if (!(at instanceof DerivedTable)) {
57
+ return undefined;
58
+ }
59
+ return {
60
+ made: at,
61
+ toMade: toBase,
62
+ table: at.parent,
63
+ row: at.parentRow ? compose(toBase, at.parentRow) : toBase,
64
+ };
65
+ }
66
+ function compose(outer, inner) {
67
+ if (!outer) {
68
+ return inner;
69
+ }
70
+ const out = new Uint32Array(outer.length);
71
+ for (let i = 0; i < outer.length; i++) {
72
+ out[i] = inner[outer[i]];
73
+ }
74
+ return out;
75
+ }
76
+ // Below this length a character loop copies a string faster than
77
+ // `encodeInto`, as the MAF packer measured.
78
+ const ASCII_COPY_MAX_LENGTH = 64;
79
+ const encoder = new TextEncoder();
80
+ // A column's text as bytes, one per character: an adapter's own, or the
81
+ // strings a feature holds copied into one buffer.
82
+ function textOf(column, n) {
83
+ if (column.kind === 'text') {
84
+ return column;
85
+ }
86
+ const read = readerOf(column);
87
+ const texts = new Array(n);
88
+ let total = 0;
89
+ for (let i = 0; i < n; i++) {
90
+ const v = read(i);
91
+ if (typeof v === 'string') {
92
+ texts[i] = v;
93
+ total += v.length;
94
+ }
95
+ }
96
+ const bytes = new Uint8Array(total);
97
+ const offset = new Uint32Array(n);
98
+ const length = new Uint32Array(n);
99
+ let pos = 0;
100
+ for (let i = 0; i < n; i++) {
101
+ const text = texts[i];
102
+ if (text === undefined) {
103
+ continue;
104
+ }
105
+ offset[i] = pos;
106
+ if (text.length <= ASCII_COPY_MAX_LENGTH) {
107
+ for (let c = 0; c < text.length; c++) {
108
+ bytes[pos + c] = text.charCodeAt(c);
109
+ }
110
+ length[i] = text.length;
111
+ }
112
+ else {
113
+ length[i] = encoder.encodeInto(text, bytes.subarray(pos, pos + text.length)).written;
114
+ }
115
+ pos += text.length;
116
+ }
117
+ return { kind: 'text', bytes, offset, length, at: undefined };
118
+ }
119
+ /**
120
+ * The texts a `cells` walk reads, by the row of the table it walked: each
121
+ * row's own, and the reference it is read against, which a row fanned out of
122
+ * a container reads off the container.
123
+ */
124
+ class WalkedTexts {
125
+ row;
126
+ reference;
127
+ refRow;
128
+ constructor(row, ref, refRow) {
129
+ this.row = row;
130
+ this.reference = ref;
131
+ this.refRow = refRow;
132
+ }
133
+ rowOf(r) {
134
+ const { at } = this.row;
135
+ return at ? at[r] : r;
136
+ }
137
+ refOf(r) {
138
+ const p = this.refRow ? this.refRow[r] : r;
139
+ const { at } = this.reference;
140
+ return at ? at[p] : p;
141
+ }
142
+ }
143
+ export function walkedTexts(table, field) {
144
+ const row = textOf(table.column(field), table.length);
145
+ const container = containerOf(table);
146
+ const made = container?.made;
147
+ if (container && !(made instanceof FannedTable && made.hasKept)) {
148
+ const { table: base } = container;
149
+ return new WalkedTexts(row, textOf(base.column(field), base.length), container.row);
150
+ }
151
+ // A container a flatten kept with nothing to fan out is the row itself, so
152
+ // its reference is its own container's, as its `parent()` says.
153
+ const byParent = (r) => table.row(r).parent?.()?.get(field);
154
+ const readContainer = container
155
+ ? readerOf(container.table.column(field))
156
+ : undefined;
157
+ const read = readContainer && made instanceof FannedTable
158
+ ? (r) => {
159
+ const { toMade, row: refRow } = container;
160
+ return made.isKept(toMade ? toMade[r] : r)
161
+ ? byParent(r)
162
+ : readContainer(refRow ? refRow[r] : r);
163
+ }
164
+ : byParent;
165
+ return new WalkedTexts(row, textOf({ kind: 'value', read }, table.length));
166
+ }
167
+ /**
168
+ * The runs a `cells` step answers, one per stretch of one state against the
169
+ * reference, typed: each run's span, its state and the text column it starts
170
+ * at, with `base`, `match` and `length` read back out of the texts when asked
171
+ * for.
172
+ */
173
+ class CellTable extends DerivedTable {
174
+ start;
175
+ end;
176
+ state;
177
+ textAt;
178
+ texts;
179
+ firstOfRow;
180
+ derived = new Map();
181
+ constructor(parent, parentRow, lanes, texts) {
182
+ super(parent, parentRow);
183
+ this.start = lanes.start;
184
+ this.end = lanes.end;
185
+ this.state = lanes.state;
186
+ this.textAt = lanes.textAt;
187
+ this.texts = texts;
188
+ }
189
+ get madeFrom() {
190
+ return true;
191
+ }
192
+ laneOf(name, build) {
193
+ let column = this.derived.get(name);
194
+ if (!column) {
195
+ column = build();
196
+ this.derived.set(name, column);
197
+ }
198
+ return column;
199
+ }
200
+ own(name) {
201
+ switch (name) {
202
+ case 'start': {
203
+ return { kind: 'number', values: this.start, at: undefined };
204
+ }
205
+ case 'end': {
206
+ return { kind: 'number', values: this.end, at: undefined };
207
+ }
208
+ case 'state': {
209
+ return {
210
+ kind: 'category',
211
+ codes: this.state,
212
+ labels: CELL_STATES,
213
+ at: undefined,
214
+ };
215
+ }
216
+ case 'match': {
217
+ return this.laneOf(name, () => {
218
+ const values = new Float32Array(this.length);
219
+ for (let i = 0; i < this.length; i++) {
220
+ const s = this.state[i];
221
+ values[i] = s === MATCH ? 1 : s === MISMATCH ? 0 : Number.NaN;
222
+ }
223
+ return { kind: 'number', values, at: undefined, nanIsAbsent: true };
224
+ });
225
+ }
226
+ case 'length': {
227
+ return this.laneOf(name, () => {
228
+ const values = new Float32Array(this.length).fill(Number.NaN);
229
+ for (let i = 0; i < this.length; i++) {
230
+ if (this.state[i] === INSERTION) {
231
+ values[i] = this.inserted(i).length;
232
+ }
233
+ }
234
+ return { kind: 'number', values, at: undefined, nanIsAbsent: true };
235
+ });
236
+ }
237
+ case 'base': {
238
+ return this.laneOf(name, () => {
239
+ const values = new Array(this.length);
240
+ for (let i = 0; i < this.length; i++) {
241
+ const s = this.state[i];
242
+ values[i] =
243
+ s === MISMATCH
244
+ ? this.mismatched(i)
245
+ : s === INSERTION
246
+ ? this.inserted(i)
247
+ : undefined;
248
+ }
249
+ return { kind: 'value', read: i => values[i] };
250
+ });
251
+ }
252
+ default: {
253
+ return undefined;
254
+ }
255
+ }
256
+ }
257
+ mismatched(i) {
258
+ const { bytes, offset } = this.texts.row;
259
+ const k = this.texts.rowOf(this.parentOf(i));
260
+ return String.fromCharCode(bytes[offset[k] + this.textAt[i]]);
261
+ }
262
+ // The bases a row holds where the reference has none, from the column the
263
+ // insertion starts at to the reference's next base.
264
+ inserted(i) {
265
+ const { row, reference: ref } = this.texts;
266
+ const r = this.parentOf(i);
267
+ const k = this.texts.rowOf(r);
268
+ const f = this.texts.refOf(r);
269
+ const rowAt = row.offset[k];
270
+ const refAt = ref.offset[f];
271
+ const end = Math.min(row.length[k], ref.length[f]);
272
+ let out = '';
273
+ for (let col = this.textAt[i]; col < end && ref.bytes[refAt + col] === DASH; col++) {
274
+ const b = row.bytes[rowAt + col];
275
+ if (!isGapByte(b)) {
276
+ out += String.fromCharCode(b);
277
+ }
278
+ }
279
+ return out;
280
+ }
281
+ id(i) {
282
+ const p = this.parentOf(i);
283
+ this.firstOfRow ??= firstRowIndex(this.parentRow, this.parent.length);
284
+ return `${this.parent.row(p).id()}#${i - this.firstOfRow[p]}`;
285
+ }
286
+ json(i) {
287
+ const out = {
288
+ ...this.parent.row(this.parentOf(i)).toJSON(),
289
+ start: this.start[i],
290
+ end: this.end[i],
291
+ state: CELL_STATES[this.state[i]],
292
+ };
293
+ for (const name of ['base', 'match', 'length']) {
294
+ const v = valueAt(this.column(name), i);
295
+ if (v !== undefined) {
296
+ out[name] = v;
297
+ }
298
+ }
299
+ return out;
300
+ }
301
+ }
302
+ // The lanes a `cells` walk writes, doubled as a row would overrun them.
303
+ class RunLanes {
304
+ start;
305
+ end;
306
+ state;
307
+ textAt;
308
+ parentRow;
309
+ length = 0;
310
+ constructor(capacity) {
311
+ this.start = new Uint32Array(capacity);
312
+ this.end = new Uint32Array(capacity);
313
+ this.state = new Uint8Array(capacity);
314
+ this.textAt = new Uint32Array(capacity);
315
+ this.parentRow = new Uint32Array(capacity);
316
+ }
317
+ reserve(more) {
318
+ const need = this.length + more;
319
+ if (need <= this.start.length) {
320
+ return;
321
+ }
322
+ const size = Math.max(this.start.length * 2, need);
323
+ const widen = (a) => {
324
+ const next = new a.constructor(size);
325
+ next.set(a);
326
+ return next;
327
+ };
328
+ this.start = widen(this.start);
329
+ this.end = widen(this.end);
330
+ this.state = widen(this.state);
331
+ this.textAt = widen(this.textAt);
332
+ this.parentRow = widen(this.parentRow);
333
+ }
334
+ // One row's runs against its reference: a run per column is the most it
335
+ // can answer, so the lanes grow once and the walk writes through locals.
336
+ walk(r, startPos, rowBytes, rowAt, rowLen, refBytes, refAt, refLen) {
337
+ this.reserve(refLen + 1);
338
+ const { start, end, state, textAt, parentRow } = this;
339
+ let n = this.length;
340
+ let pos = startPos;
341
+ const first = firstDrawn(rowBytes, rowAt, rowLen);
342
+ const last = lastDrawn(rowBytes, rowAt, rowLen, first);
343
+ let runStart = -1;
344
+ let runState = NO_STATE;
345
+ let runBase = -1;
346
+ let runCol = 0;
347
+ let insertAt = -1;
348
+ for (let col = 0; col < refLen; col++) {
349
+ const refByte = refBytes[refAt + col];
350
+ const rowByte = col < rowLen ? rowBytes[rowAt + col] : SPACE;
351
+ if (refByte === DASH) {
352
+ if (insertAt < 0 && col < rowLen && !isGapByte(rowByte)) {
353
+ insertAt = col;
354
+ }
355
+ continue;
356
+ }
357
+ if (insertAt >= 0) {
358
+ start[n] = pos;
359
+ end[n] = pos;
360
+ state[n] = INSERTION;
361
+ textAt[n] = insertAt;
362
+ parentRow[n] = r;
363
+ n++;
364
+ insertAt = -1;
365
+ }
366
+ const st = cellState(refByte, rowByte, col >= first && col <= last && col < rowLen);
367
+ const base = st === MISMATCH ? rowByte : -1;
368
+ if (st !== runState || base !== runBase) {
369
+ if (runStart >= 0) {
370
+ start[n] = runStart;
371
+ end[n] = pos;
372
+ state[n] = runState;
373
+ textAt[n] = runCol;
374
+ parentRow[n] = r;
375
+ n++;
376
+ }
377
+ runStart = st === NO_STATE ? -1 : pos;
378
+ runState = st;
379
+ runBase = base;
380
+ runCol = col;
381
+ }
382
+ pos++;
383
+ }
384
+ if (runStart >= 0) {
385
+ start[n] = runStart;
386
+ end[n] = pos;
387
+ state[n] = runState;
388
+ textAt[n] = runCol;
389
+ parentRow[n] = r;
390
+ n++;
391
+ }
392
+ this.length = n;
393
+ }
394
+ }
395
+ // Where each parent row's children start, for children written parent by
396
+ // parent in order.
397
+ function firstRowIndex(parentRow, parents) {
398
+ const first = new Uint32Array(parents);
399
+ for (let i = parentRow.length - 1; i >= 0; i--) {
400
+ first[parentRow[i]] = i;
401
+ }
402
+ return first;
403
+ }
404
+ export function cells({ table, bounds }, step) {
405
+ const { field = DEFAULT_CELLS_FIELD } = step;
406
+ const texts = walkedTexts(table, field);
407
+ const readStart = numberReaderOf(table.column('start'));
408
+ const { bytes: rowBytes, offset: rowOffset, length: rowLength } = texts.row;
409
+ const { bytes: refBytes, offset: refOffset, length: refLength, } = texts.reference;
410
+ // A run per reference base is the most a row can answer and far past what
411
+ // one does, so the lanes start at an eighth of it.
412
+ let bound = 0;
413
+ for (let r = 0; r < table.length; r++) {
414
+ bound += refLength[texts.refOf(r)] + 1;
415
+ }
416
+ const lanes = new RunLanes(Math.max(1024, bound >>> 3));
417
+ for (let r = 0; r < table.length; r++) {
418
+ const f = texts.refOf(r);
419
+ const refLen = refLength[f];
420
+ const pos = readStart(r);
421
+ if (refLen > 0 && pos >= 0) {
422
+ const k = texts.rowOf(r);
423
+ lanes.walk(r, pos, rowBytes, rowOffset[k], rowLength[k], refBytes, refOffset[f], refLen);
424
+ }
425
+ }
426
+ const n = lanes.length;
427
+ const rows = lanes.parentRow.subarray(0, n);
428
+ return {
429
+ table: new CellTable(table, rows, {
430
+ start: lanes.start.subarray(0, n),
431
+ end: lanes.end.subarray(0, n),
432
+ state: lanes.state.subarray(0, n),
433
+ textAt: lanes.textAt.subarray(0, n),
434
+ }, texts),
435
+ bounds: boundsThrough(bounds, rows),
436
+ };
437
+ }
@@ -7,7 +7,14 @@ export default async function copyToClipboard(text, options = {}) {
7
7
  // navigator.clipboard is only present in secure contexts; isSecureContext is
8
8
  // the real gate, so insecure http:// falls through to execCommand below
9
9
  if (format === 'text/plain' && window.isSecureContext) {
10
- await navigator.clipboard.writeText(text);
10
+ try {
11
+ await navigator.clipboard.writeText(text);
12
+ }
13
+ catch (e) {
14
+ if (!execCommandCopy(text, format)) {
15
+ throw e;
16
+ }
17
+ }
11
18
  }
12
19
  else if (!execCommandCopy(text, format)) {
13
20
  throw new Error('the browser rejected the clipboard write');
@@ -81,9 +81,9 @@ export function createAbortRotation(self, report) {
81
81
  // Opened before the superseded slot retires, so the window is never idle
82
82
  // across the handover and the label on screen survives it (ADR-080).
83
83
  // Retired here rather than left to the superseded run's `finally`, which
84
- // comes only when its work settles — and a CRAM record read or a shared
85
- // parse the signal is withheld from runs on past the abort, its last
86
- // reading summed into this fetch's bar the whole time.
84
+ // comes only when its work settles — and a CRAM record read, or a shared
85
+ // `cachedSetup` parse the replacement rejoins, runs on past the abort,
86
+ // its last reading summed into this fetch's bar the whole time.
87
87
  const stream = statusWindow.open({ isCurrent });
88
88
  openStream?.clear();
89
89
  openStream = stream;
@@ -5,19 +5,20 @@ import type { Feature } from './simpleFeature.ts';
5
5
  export { BIN_OVERLAP_FIELD, DEFAULT_BIN_AS } from './stepTables.ts';
6
6
  export declare const DEFAULT_BIN_FIELD = "start";
7
7
  export declare const DEFAULT_FLATTEN_FIELD = "subfeatures";
8
- export declare const DEFAULT_CELLS_FIELD = "seq";
8
+ export { DEFAULT_CELLS_FIELD } from './cellsStep.ts';
9
9
  export declare const DEFAULT_COVERAGE_AS = "coverage";
10
10
  export declare const DEFAULT_PILEUP_AS = "row";
11
11
  export declare const DEFAULT_PILEUP_FIELDS: [string, string];
12
12
  /**
13
13
  * #api
14
14
  * How a record states its other end, or undefined where it states none: the
15
- * `mate` a paired adapter fills (BEDPE, STAR-Fusion), or an `ALT` the breakend
16
- * and symbolic-SV readers resolve. The `mate` step admits exactly the features
17
- * this names one for, so a caller deciding whether links are the picture a
18
- * track wants asks here rather than re-reading the fields.
15
+ * `mate` a paired adapter fills (BEDPE, STAR-Fusion), an `ALT` the breakend
16
+ * and symbolic-SV readers resolve, or a read's `next_ref` and `next_pos`. The
17
+ * `mate` step admits exactly the features this names one for, so a caller
18
+ * deciding whether links are the picture a track wants asks here rather than
19
+ * re-reading the fields.
19
20
  */
20
- export declare function matedBy(f: Feature): "alt" | "mate" | undefined;
21
+ export declare function matedBy(f: Feature): "alt" | "mate" | "pair" | undefined;
21
22
  /**
22
23
  * #api
23
24
  * One layer of a faceted request: its rows in section order, and the stacked